This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C123GH6PM: Clarity on TX FIFO half full or less and other SSI Interrupts

Part Number: TM4C123GH6PM

HI all,

I'm working on an interrupt driven SSI (SPI) driver for a TM4C123GH6PM MCU and am seeing some strange behavior regarding interrupt generation. At first, I wanted to trigger an interrupt on transmit complete, which is the flag SSI_TXEOT (value of 0x40). The datasheet for the TM4C123 says that this interrupt should be available, but when looking closer at the masked interrupt status register (SSIMIS), we only have access to the first 3 bits, and it seems the third bit shares an interrupt condition with FIFO half full or less or TX complete, based on the EOT bit. What is the EOT bit in this case? I can't seem to find this anywhere.

Secondly, when I enabled interrupts for the FIFO Half Full or Less condition (SSI_TXFF of value 0x08), my interrupt handler fires immediately and the main while loop doesn't ever run. Maybe the language isn't clear, but to me, if the interrupt fires on transmit fifo half full or less, then as soon as we enable this interrupt, it's going to constantly fire because the TX fifo is always half full or less because we haven't had the chance top put anything in it yet. Is this a correct assumption? 

To recap, if I enable interrupts with the flag SSI_TXEOT, no interrupts fire, which I guess makes sense because the SSIMIS only allows masks up to bit 3. But then if I enable interrupts with the flag SSI_TXFF, the interrupt handler immediately fires and the main function doesn't get a chance to run because the interrupt handler is constantly firing, so the main task is basically getting starved.

I appreciate any clarification, thank you!

Kyle Garland

  • I wanted to follow up on this. I found the EOT flag in the SSICR1 register, and made a function to check whether that bit is ever set. What happens is as soon as I enable interrupts with the SSI_TXFF flag set, the interrupt handler fires immediately and no data is ever sent because we sit inside that interrupt handler and the main function never executes. I can send data once that interrupt fires and see if that works but I am worried about an application that jumps immediately into an interrupt handler and starves the main function, as the interrupt handler is constantly firing. I have attached some code below to see if that helps, but just curious if I am going about this the right way.

    The datasheet says that when the SSI_TXFF interrupt fires, I need to check the EOT flag to see whether we are at half full or transaction completed, but I can't even send any bytes at all since we stay in the ISR the whole time. 

    void TivaSPI::Initialize() {
      kBaseAddress = SSI0_BASE;
      IntEnable(INT_SSI0);
      SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0);
      while (!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0)) {
      }
      SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
      while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA)) {
      }
    
      GPIOPinConfigure(GPIO_PA2_SSI0CLK);
      // GPIOPinConfigure(GPIO_PA3_SSI0FSS);
      GPIOPinTypeGPIOOutput(GPIO_PORTA_BASE, GPIO_PIN_3);
      GPIOPinConfigure(GPIO_PA4_SSI0RX);
      GPIOPinConfigure(GPIO_PA5_SSI0TX);
    
      GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_5 | GPIO_PIN_4 | GPIO_PIN_2);
      // SSIIntEnable(SSI0_BASE, SSI_TXEOT);
      // SSIIntEnable(SSI0_BASE, SSI_RXFF | SSI_TXFF);
      SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0,
                         SSI_MODE_MASTER, 1000000, 8);
    
      SSIEnable(SSI0_BASE);
      SSIIntEnable(SSI0_BASE, SSI_TXFF);
    }
    
    
    void SSI0Handler() {
      uint32_t int_status = SSIIntStatus(SSI0_BASE, true);
    
      if (int_status & SSI_TXFF) {
        SSIIntClear(SSI0_BASE, SSI_TXFF);
        if (SSIIsEndOfTransmissionFlagSet(SSI0_BASE)) {
          GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, GPIO_PIN_2);
        }
      }
      /* toggling to capture on logic analyzer */
      GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, GPIO_PIN_3);
      GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, 0);
    }
    
    void TivaSPI::BlockingWrite(uint32_t *data_out, uint8_t num_bytes) {
      uint8_t bytes_processed = 0;
      ClearReceiveBuffer(&data_out[0]);
      AssertCS();
      while (bytes_processed < num_bytes) {
        SSIDataPut(kBaseAddress, data_out[bytes_processed]);
    
        bytes_processed++;
      }
      // Wait until bus is no longer busy before returning
      while (SSIBusy(kBaseAddress))
        ;  // look into this
      DeAssertCS();
      // check to see if all bytes have been sent and return false if not
      if (bytes_processed != num_bytes) {
        return;
      }
    }

  • I just wanted to add an image from my logic analyzer, which shows the interrupt firing and then me having to kick off a transaction manually in the ISR. To me this just seems strange that the TXFF interrupt is triggered basically immediately and then we infinitely stuck in the interrupt handler. The purple line is toggling a gpio when the ISR is called, yellow is clock, orange is cs, red is miso, and the bottom redish orange looking color is mosi.

      

  • What is the EOT bit in this case? I can't seem to find this anywhere.

    The EOT indicates a transaction is complete. Let's say you load only one 16-bit word of data to the FIFO, a EOT interrupt is not generated until the entire 16 bits are completely shifted out of the shift register. 

    Secondly, when I enabled interrupts for the FIFO Half Full or Less condition (SSI_TXFF of value 0x08), my interrupt handler fires immediately and the main while loop doesn't ever run. Maybe the language isn't clear, but to me, if the interrupt fires on transmit fifo half full or less, then as soon as we enable this interrupt, it's going to constantly fire because the TX fifo is always half full or less because we haven't had the chance top put anything in it yet. Is this a correct assumption?

    Your understanding is correct. As soon as you enable the TXFF interrupt, an interrupt is immediately generated because there is no data in the FIFO yet. The interrupt allows the processor to fill the FIFO to start the transmission. 

    if I enable interrupts with the flag SSI_TXFF, the interrupt handler immediately fires and the main function doesn't get a chance to run because the interrupt handler is constantly firing, so the main task is basically getting starved.

    In your SSI0Handler, I don't see you loading data to the FIFO. As soon as the interrupt is exited the FIFO remains half full or less and hence generates an interrupt again. 

    I just wanted to add an image from my logic analyzer, which shows the interrupt firing and then me having to kick off a transaction manually in the ISR.

    You can also load the FIFO before you enable the SPI and the interrupt. 

  • Hey Charles,

    Thanks for the response and the clarification, this is the information I was looking for. I like the idea about enabling interrupts after loading data into the FIFO, but I think I would also need to disable to interrupts after the end of each transaction as to not trigger the interrupt continuously. 

    I will modify my application for this design and get back with some results. Thanks again!

  • So I have some follow up questions after making some changes. I see that the SSI_TXFF interrupt fires continuously until the FIFO is half full or less, but I'm not getting an EOT interrupt. I played around with when to load data into the FIFO, and am currently masking the interrupt status register. I'm wondering how to configure my transmit callback to only send the next byte after a byte has finished, but since the interrupts are firing so fast, I send all the data too fast and it doesn't operate correctly. Currently I am trying to write 4 bytes. I've attached my updated code and logic analyzer trace for review, any insight is appreciated.

    bool TivaSPI::StartNonBlockingWrite(uint8_t *data, uint8_t num_bytes) {
      if (state == SPI_Internal_State::kIdle) {
        state = SPI_Internal_State::kWriting;
    
        std::memcpy(tx_data_buffer, data, num_bytes);
        spi_control.write_index = 0;
        spi_control.num_write_bytes = num_bytes;
    
        AssertCS();
    
        SSIIntEnable(SSI0_BASE, SSI_TXFF);
    
        return true;
      } else {
        return false;
      }
    }
    
    void TivaSPI::TransmitCallback() {
    
      if (!SSIBusy(SSI0_BASE)) {
        SSIDataPutNonBlocking(SSI0_BASE, tx_data_buffer[spi_control.write_index]);
        spi_control.write_index++;
      } else {
        return;
      }
    
      if (spi_control.write_index == spi_control.num_write_bytes - 1) {
        SSIIntDisable(SSI0_BASE, SSI_TXFF);
        state = SPI_Internal_State::kIdle;
        DeAssertCS();
      }
    }
    
    void SSI0Handler() {
      uint32_t int_status = SSIIntStatus(SSI0_BASE, true);
      SSIIntClear(SSI0_BASE, SSI_TXFF);
      /* toggling to capture on logic analyzer */
      GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, GPIO_PIN_3);
      GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, 0);
    
      if (int_status & SSI_TXFF) {
        spi_bus_0.TransmitCallback();
    
      }
    }

  • Hi,

      Can you try SSIDataPut instead of SSIDataPutNonBlocking? Do you see any difference?

  • Hi Charles,

    Thanks for the response. Changing to SSIDataPut has the same outcome as SSIDataPutNonBlocking. 

  • Hi Kyle,

      Sorry for the late response. Can you try to prefill the entire TXFIFO before you enable SSI module? Will it make a difference? I was hoping by doing so it will transmit 4 words before the FIFO becoming half empty. Each time you receive an interrupt, you will fill 4 words at a time. 

  • No worries, I appreciate the assistance. So I loaded the FIFO first and then enabled chip select and then interrupts, and I think we are getting closer. But as you can see from the logic analyzer trace, the interrupts are firing quickly and continuously every 2.1us, so in the callback I'm not sure when to add another 4 bytes to the FIFO or even when to un-assert the chip select line. I'm wondering if I'm on the wrong approach here but I'm not sure what other interrupts I can check for. I'm wondering how I can only trigger the callback when all bytes are done. Maybe check if the bus is busy and if it isn't I can then un assert CS and start another transaction?

     

    bool TivaSPI::StartNonBlockingWrite(uint8_t *data, uint8_t num_bytes) {
      if (state == SPI_Internal_State::kIdle) {
        state = SPI_Internal_State::kWriting;
    
        std::memcpy(tx_data_buffer, data, num_bytes);
        spi_control.write_index = 0;
        spi_control.num_write_bytes = num_bytes;
    
        AssertCS();
    
        for (uint8_t index = 0; index < num_bytes; index++) {
          SSIDataPutNonBlocking(SSI0_BASE, tx_data_buffer[index]);
        }
    
        SSIIntEnable(SSI0_BASE, SSI_TXFF);
    
        return true;
      } else {
        return false;
      }
    }

  • Hi,

     Your LA capture does show the interrupt ISR being entered and exited multiple times while only one word is being transmitted. I understand you have tried to wait for EOT but not seeing it because the TXFF interrupt come too fast. Can you try to first prefill the FIFO and wait for only the EOT interrupt? In another word, don't enable the TXFF interrupt. In the meantime, I'm going to do some searching if this problem has been reported in the past and what would be be the recommendation. 

  • Hi Charles,

    Thanks for the response and sorry for misunderstanding at first. I disabled TXFF interrupt, and then I made a helper function that sets the EOT bit in the SSI_CR1 register, and that generates an EOT interrupt! (see logic analzer trace, it's perfect).

    For some reason I thought I needed to check the status of the EOT bit in SSI_CR1 register, but actually I set that bit in startup and then I can un-assert CS in my callback and what not.

    I greatly appreciate the assistance on this. Take care!

    edit: I looked back at my code and I still have TXFF interrupts enabled, which is interesting given the capture and response that I'm seeing. Just FYI. It seems both are necessary.