MSPM33C321A: Can't get DMA/SPI Ping-Pong to work

Part Number: MSPM33C321A
Other Parts Discussed in Thread: SYSCONFIG

I have a program that sends a serial stream using spi, DMA and a timer to send a 400 us frame every 400us. I thought I would try to get a Ping-Pong setup going to try it out. (The clock is 5 MB/sec, so the single frame is 250 bytes long.)

I think I have it set up correctly, but the  DL_SPI_IIDX_DMA_DONE_TX: interrupt fires every 30 us instead of 800 us as I would expect.

The DMA config is not very interesting, so I won't show it here.

The SPI config is:

image.png

 

image.png

image.png

volatile bool DoTop, DoBottom;

uint8_t FrameData[] = {
    // Frame #1 "The Top"
    // 0    1     2     3     4     5     6     7     8    10     11    12    13
    // 14    15    16
    0xFE, 0x6B, 0x28, 0x40, 0x00, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
    0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23,
    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
    0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
    0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53,
    0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
    0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
    0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83,
    0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B,
    0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
    0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
    0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB,
    0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
    0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3,
    0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9,

    // Frame # 2 "The Bottom"
    // 0    1     2     3     4     5     6     7     8    10     11    12    13
    // 14    15    16
    0xFE, 0x6B, 0x28, 0x40, 0x01, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
    0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
    0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23,
    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
    0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
    0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53,
    0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
    0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
    0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83,
    0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B,
    0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
    0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
    0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB,
    0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
    0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3,
    0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9};

uint8_t *TopFrame = &FrameData[0];
uint8_t *BotFrame = &FrameData[250];
#define FRAME_PACKET_SIZE (250)

#define FRAME_BUFFER_SIZE (sizeof(FrameData) / sizeof(FrameData[0]))

uint32_t NumFrame = 1;

int main(void) {
  DoTop = false;
  DoBottom = false;
  int crc16;

  SYSCFG_DL_init();
  DL_SYSCTL_disableSleepOnExit();

  /*
   * Configure DMA0 source, destination and size to transfer data from
   * gTxPacket to TXDATA. The DMA0 transfer will start when TX interrupt is
   * set, which it should already be set (which indicates that the SPI is
   * ready to transfer) so the DMA0 will begin this transfer when the channel
   * is enabled.
   */
  DL_SPI_disableDMATransmitEvent(SPI_0_INST);

  DL_DMA_setSrcAddr(DMA0, DMA0_CH0_CHAN_ID, (uint32_t)&FrameData[0]);
  DL_DMA_setDestAddr(DMA0, DMA0_CH0_CHAN_ID,
                     (uint32_t)(&SPI_0_INST->spi->TXDATA));

  DL_DMA_setTransferSize(DMA0, DMA0_CH0_CHAN_ID, FRAME_BUFFER_SIZE);

  NVIC_EnableIRQ(SPI_0_INST_INT_IRQN);

  DL_SPI_enableInterrupt(SPI_0_INST, (DL_SPI_INTERRUPT_DMA_PREIRQ_TX |
                                      DL_SPI_INTERRUPT_DMA_DONE_TX |
                                      DL_SPI_INTERRUPT_TX_EMPTY));

  DL_DMA_enableChannel(DMA0, DMA0_CH0_CHAN_ID);
  DL_SPI_enableDMATransmitEvent(SPI_0_INST);
 

  while (1) {
    // Watch the interrupts
    // when the interrupt is at 1/2 fill the top frame
    // when the interrupt says the Xfer is done, fill the bottom half
    if (DoTop == true) {
      // Fill the top 
      DL_GPIO_setPins(GPIO_DEBUG_PORT, GPIO_DEBUG_PC10_PIN);

      // Frame number: word 222, 223, 224, 225: 225 is LSB

      TopFrame[221] = (uint8_t)(0xFF & (NumFrame >> 24));
      TopFrame[222] = (uint8_t)(0xFF & (NumFrame >> 16));
      TopFrame[223] = (uint8_t)(0xFF & (NumFrame >> 8));
      TopFrame[224] = (uint8_t)(0xFF & NumFrame);

      NumFrame++;

      SYSCFG_DL_CRCP_init();

      for (int i = 0; i < (FRAME_PACKET_SIZE-2); i++) { // '-2' since no reason to CRC the CRC bytes!
        DL_CRCP_feedData8(CRCP0, TopFrame[i]); 
      }
      crc16 = DL_CRCP_getResult16(CRCP0);

      
      TopFrame[248] = (uint8_t)(0xFF & (crc16 >> 8));
      TopFrame[249] = (uint8_t)(0xFF & (crc16));

      DL_GPIO_clearPins(GPIO_DEBUG_PORT, GPIO_DEBUG_PC10_PIN);

      DoTop = false;

    }

    if (DoBottom == true) {
      //Fill The bottom
       DL_GPIO_setPins(GPIO_DEBUG_PORT, GPIO_DEBUG_PC11_PIN);
      // We have actually finished the frame

      // Frame number: word 222, 223, 224, 225: 225 is LSB

      BotFrame[221] = (uint8_t)(0xFF & (NumFrame >> 24));
      BotFrame[222] = (uint8_t)(0xFF & (NumFrame >> 16));
      BotFrame[223] = (uint8_t)(0xFF & (NumFrame >> 8));
      BotFrame[224] = (uint8_t)(0xFF & NumFrame);

      NumFrame++;

      SYSCFG_DL_CRCP_init();

      for (int i = 0; i < (FRAME_PACKET_SIZE-2); i++) { // '-2' since no reason to CRC the CRC bytes!
        DL_CRCP_feedData8(CRCP0, BotFrame[i]); 
      }
      crc16 = DL_CRCP_getResult16(CRCP0);

      
      BotFrame[248] = (uint8_t)(0xFF & (crc16 >> 8));
      BotFrame[249] = (uint8_t)(0xFF & (crc16));

      DL_GPIO_clearPins(GPIO_DEBUG_PORT, GPIO_DEBUG_PC11_PIN);

      DoBottom = false;

    }

    // do whatever else
  }
}

void SPI_0_INST_IRQHandler(void) {
  switch (DL_SPI_getPendingInterrupt(SPI_0_INST)) {
  case DL_SPI_IIDX_DMA_DONE_TX:
    /* DMA0 is done transferring the bottom half, start filling it */
    DoBottom = true;
    DL_GPIO_setPins(GPIO_DEBUG_PORT, GPIO_DEBUG_PC12_PIN);
    DL_GPIO_clearPins(GPIO_DEBUG_PORT, GPIO_DEBUG_PC12_PIN);
  case DL_SPI_IIDX_PREIRQ_TX:
    /* DMA0 is done transferring the top half, start filling it */
    DoTop = true;
    break;

  default:
    break;
  }
}

Is there some setting I am missing?

 

  • For a serial (SPI) port I would expect to see Transfer Mode "[Repeat] Single" and Address Mode "Block address to fixed address" (for Tx)..

    The first allows the SPI unit to meter the data, and the second puts each byte into TXDATA.

    This configuration will DMA the entire packet on each trigger, spread over the register space.

  • Thanks everyone, I am now getting full frames with the correct timimg.

    However, I am not getting the PREIRQ interrupt. I thought I was because the DoTop variable was getting set. But then I saw the bug in my interrupt routine above. I was missing the break after the case, so I was falling through the second case statement! When I put the break in and moved the PC12 GPIO pulse to the PREIRQ case, I don't get the pulse. My Sysconfig for the PreIRQ interrupt is as above and I actually re-enable the interrupt in my init code. What am I missing?

  • Notwithstanding the verbiage in the TRM , it appears that the PREIRQ path (back) to the Unicomm isn't implemented (yet?). CMSIS defines a bit for this (IMASK bit 19) but attempts to set it are ignored and the corresponding RIS bit is never set. 

    The PREIRQ event can be caught as a DMA0 interrupt. The ISR might resemble:

    void
    DMA0_IRQHandler(void)
    {
      switch (DL_DMA_getPendingInterrupt(DMA0)) {
      case DL_DMA_FULL_CH_EVENT_IIDX_EARLY_IRQ_DMACH0:
        DoTop = true;
        break;
      default:
        break;
      }
      return;
    }

    and you'll also need to enable it somewhere:

      NVIC_EnableIRQ(DMA0_INT_IRQn);

    Ancillary notes: 

    1) You're enabling the (SPI) TX_EMPTY interrupt, but I expect you don't want to do this. This condition will occur fairly frequently, but the DMA takes care of it (you shouldn't get in the way).

    2) The DMA channel initialization generated by Sysconfig sets the PREIRQ threshold, but then DL_DMA_initChannel() sets it back to =0 (disabled) by omission. I suppose they'll fix this bug someday, but for now you should add this after the call to SYSCFG_DL_init:

        DL_DMA_Full_Ch_setEarlyInterruptThreshold(DMA0, DMA0_CH0_CHAN_ID, DL_DMA_EARLY_INTERRUPT_THRESHOLD_HALF);

    [Edit: If it simplifies anything, you could also catch the DMA Complete interrupt (DL_DMA_EVENT_IIDX_DMACH0) in the DMA0 ISR instead of the SPI ISR.]

  • Thanks Bruce.

    "Notwithstanding the verbiage in the TRM" And the SysConfig DMA settings in the SPI, and all the enums they have...

    I will test it Monday. Because I kind of reached a brick wall with this, I switched to testing an isolated ADC that goes with the same project. In the process of doing that I shorted the 5 V USB supply to ground, which messed up everything. It took me a while to get the Launchpad going again.

  • Do I set up the sysconfig DMA interrupt settings pretty much like the SPI DMA settings? (I guess I should remove the pre-irq stuff in the SPI)

  • Observed behavior: When you request the SPI->DMA Channel Event->Interrupt Configuration for Enable Early Channel Interrupt, it enables PREIRQ in the DMA channel (not the SPI). 

  • Thanks! That is probably why I did not get a conflict error from SysConfig.

  • Thanks Bruce! Both interrupts are firing now. Monday I am going to see if I am dropping any frames...

    I did add a 

    DL_DMA_enableInterrupt(DMA0, DL_DMA_FULL_CH_INTERRUPT_EARLY_CHANNEL0);

    To enable the specific interrupt.

    I am marking this solved

  • One more question: What does the DL_SPI_IIDX_DMA_DONE_TX actually signify?

    I notice that I am still getting data from the SPI *after* this interrupt fires. Does "Done" in this case mean that all the data is in the FIFO, and the data I am seeing after the interrupt is just the FIFO draining?

    I ask because I am trying to write into this area after the done interrupt and want to make sure that I am not writing to data before it gets sent out.

  • DMA_DONE means the DMA has finished loading the FIFO, so yes the (Tx-side) SPI will still be running. 

    So you can't use it to de-assert /CS, but you can use it to gate updating the now-stale Tx buffer (RAM).

  • Exactly what I thought, thanks.

    (I am not using *CS, so that does not affect me)