Electronic – How to receive multiple bytes on SPI with CS being held low

avr32spi

I have an FPGA which sends out a stream of bits over SPI communicating to 3 AD8403 daisy chained together. The FPGA keeps CS low, until 30 bits (2 address bits, 8 data bits x 3) have been transmitted, then brings CS high.

The way the AD8403 are setup is that as bits come in, they get shifted out to the next chip if the internal shift register is full. The first 10 bits transmitted becomes the data for the last AD8403 and the next 10 bits becomes the data for the second AD8403 and finally the last 10 bits transmitted become the data for the first AD8403.

enter image description here

I need to be able to read this data as well. I'm thinking of just tapping into the SDI (MOSI) of the first chip. I have not used SPI in slave mode on a microcontroller before so I am a little lost when it comes to how the reception works.

I'm looking at using the AT32UC3C2256C (AVR32) for this task. It can transmit 8/16 bytes and I assume it can receive the same, but can it receive an arbitrary number of bits? The FIFO is 4 layers deep, so I would have to read data and store them some place else.

Question: How can I receive an arbitrary number of bits (multiples of 10) properly?

Best Answer

I did the same thing on a AT32UC3C0512 (yet, I was the master which probably made it a bit easier).

The idea is probably the following (if you're using the ASF framework, it should work in slave mode as well):

  • Call spi_get(SPI)
  • Loop until spi_is_rx_ready(SPI) or until you run into a timeout
  • Call spi_get(SPI) for the next byte (or 10 byte or whatever you set the spi_rx register size to be)
  • Do this a few times until you received all bytes

Here's some code as a starting point, hope it helps a little:

volatile avr32_spi_t *spi = device->spi;
unsigned int timeout = SPI_TIMEOUT;    

timeout = SPI_TIMEOUT;
while (!spi_is_rx_ready(spi)) {
    if (!timeout--) {
        tmc260_deselect_device(device);
        return ERR_TIMEOUT;
    }
}
// Store the received data
rx_msb = spi_get(spi);

I'm not quite sure what has to be done to reset the flag which is set once a byte was received. I would assume simply reading the rx register should do the trick.

It should also be possible to port this code to using interrupts (call the ISR whenever a byte was received/RX register is full).

From the AT32UC3C0512 datasheet:

The SPI waits for NSS to go active before receiving the serial clock from an external master. When NSS falls, the clock is validated on the serializer, which processes the number of bits defined by the Bits Per Transfer field of the Chip Select Register 0 (CSR0.BITS). These bits are processed following a phase and a polarity defined respectively by the CSR0.NCPHA and CSR0.CPOL bits. Note that the BITS, CPOL, and NCPHA bits of the other Chip Select Registers have no effect when the SPI is configured in Slave Mode. The bits are shifted out on the MISO line and sampled on the MOSI line. When all the bits are processed, the received data is transferred in the Receive Data Register and the SR.RDRF bit rises. If the RDR register has not been read before new data is received, the SR.OVRES bit is set. Data is loaded in RDR even if this flag is set. The user has to read the SR register to clear the SR.OVRES bit.

As far as I understand, a flag will be set after 8 to 10 databits. For a first try, you will need to read the data and see if the flag gets raised a second time, even if CS doesn't get asserted a second time.

An alternative solution

You can also use an interrupt on the SCK line (depending on the mode on the rising or falling edge) and sample the data "manually". Once you discover a condition where SCK rises (or falls) and CS is high, you are outside the "normal" datatransfer and you start a new reception.