0
\$\begingroup\$

I am streaming output on MAX32660 SPI0 at 48Mbaud. Since it is streaming, I can't predict the length when I start sending.

The problem I'm running into is that slave-select assertion and deassertion seems to be based on SPI0_CTRL1.tx_num_char. I can maintain transmission by periodically writing a large number to SPI0_CTRL1.tx_num_char, and writing bytes directly into the 32-byte hardware FIFO as they become available. But then when I get to the end of the stream, how do I tell hardware that I'm done with the transaction? I tried writing SPI0_CTRL1.tx_num_char to 0 (or 1 and then writing a final character), but it didn't work completely. The SPI clock stops after everything is transmitted, so all data is properly handled, but the slave select remains asserted.

It's a bit odd that SPI0_CTRL0 has a start bit, but no corresponding bit to stop. Surely I don't have to know the length in advance?

I suppose I could just use another GPIO as a slave select, which would also enable me to use more than one peripheral. Is that what everyone does? But then why do they bother with all the fancy slave-select timing features in hardware? (Do the manufacturers do this just to make us think we need to buy a more expensive device if we want more than 1 slave select?)

Or is there a more proper way (i.e., more likely compatible across multiple devices) to start and finish streaming SPI output?

\$\endgroup\$
5
  • \$\begingroup\$ Since you are streaming data out, it means that you are slave. Then chip select is asserted by the master device, not yours. \$\endgroup\$ – Marko Buršič Jul 6 '20 at 9:10
  • \$\begingroup\$ @MarkBuršič that assumption is not at all correct in the general case, and obviously contrary to the facts here \$\endgroup\$ – Chris Stratton Jul 6 '20 at 10:49
  • 1
    \$\begingroup\$ I suspect this desire may not be accommodated in the design because typically one would not operate SPI this way. Instead a "streaming" operation would consist of multiple block transfers one after another with close spacing. See if you can make the transfer length fixed but the number of transfers variable, or switch to a GPIO select mode, if you rarely change it anyway that won't cost much. Note that SPI style "streaming" schemes such as I2S cycle their comparable signal once per transfer unit (eg stereo pair) so they can quickly (re) synch. \$\endgroup\$ – Chris Stratton Jul 6 '20 at 10:53
  • \$\begingroup\$ @Chris Actually it is very common to find SPI devices that support streaming an indefinite amount, particularly with buffer read and write commands (see, e.g., ENC28J60 Ethernet adapter, or most flash chips). I would like to take advantage of this; for example to stop when I encounter a delimiter or some other stop condition that is not known when I start the transfer. \$\endgroup\$ – personal_cloud Sep 7 '20 at 20:22
  • \$\begingroup\$ @personal_cloud - what you are overlooking is that in those cases, the master chooses how much of the buffer they want to read, and can start the next read at a suitable place to continue. Also they're given something they can read to find out how much data is in the buffer. \$\endgroup\$ – Chris Stratton Sep 7 '20 at 20:26
0
\$\begingroup\$

As Chris suggested, the stream can be broken into blocks. With the following buffering code using blocks of 1 to 32 bytes each, the overhead in my case is 30%, which could be acceptable.

#define SPI_BUF_SIZE 8

uint32_t spi_buf[SPI_BUF_SIZE];
uint8_t* spi_ptr = (uint8_t*)spi_buf;
bool     spi_wait = false;

void SPI_Send(char c)
{
  *spi_ptr++ = c;

  if (spi_ptr == (uint8_t*)(spi_buf + SPI_BUF_SIZE))
    {
      spi_ptr = (uint8_t*)spi_buf;
      if (spi_wait)
      while (!(MXC_SPI17Y->int_fl & MXC_F_SPI17Y_INT_FL_M_DONE));
        spi_wait = true;
      MXC_SPI17Y->ctrl1  = SPI_BUF_SIZE * 4;           // set tx_num
      MXC_SPI17Y->int_fl = MXC_F_SPI17Y_INT_FL_M_DONE; // clear master done
      MXC_SPI17Y->ctrl0 |= MXC_F_SPI17Y_CTRL0_START;   // start transaction
      for (int i=0; i<SPI_BUF_SIZE; i++)
        MXC_SPI17Y->data32 = spi_buf[i];
    }
}

void SPI_Flush()
{
  int len = spi_ptr - (uint8_t*)spi_buf;
  if (!len)
    return;
  spi_ptr = (uint8_t*)spi_buf;
  
  if (spi_wait)
    while (!(MXC_SPI17Y->int_fl & MXC_F_SPI17Y_INT_FL_M_DONE));
  spi_wait = true;

  MXC_SPI17Y->ctrl1  = len;                        // set tx_num
  MXC_SPI17Y->int_fl = MXC_F_SPI17Y_INT_FL_M_DONE; // clear master done flag
  MXC_SPI17Y->ctrl0 |= MXC_F_SPI17Y_CTRL0_START;   // start transaction

  uint32_t* p = spi_buf;
  while (len >= 4)
    {
      MXC_SPI17Y->data32 = *p++;
      len -= 4;
    }
  uint32_t final = *p;
  if (len >= 2)
    {
      *MXC_SPI17Y->data16 = final;
      final >>= 16;
    }
  if (len & 1)
    *MXC_SPI17Y->data8 = final;
}

The performance of an application that sends 20 bursts, with a flush after each burst, and total size 355 bytes is as follows:

buf        runtime   thrupt
4  bytes   178 us
8  bytes   161 us
12 bytes   146 us
16 bytes   172 us
20 bytes   132 us
24 bytes   132 us
28 bytes   133 us
32 bytes   133 us    2.7 MB/s
none*      101 us    3.5 MB/s

* setting tx_num to a large value before each burst.
  But this hangs slave_select (and probably other things too).
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.