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.

C6748 SPI1 TX interrupt triggered only once

Other Parts Discussed in Thread: OMAP-L137

Hi All,

I have a Maxim MAX11040K 4 channel, 24-bit ADC chip controlled from the SPI1 port of a C6748. I created an APP from the StarterWare's spi.c example which reads data from the fingerprint sensor of the development kit (which is not assembled!). Finally the APP works but I found this approach funny.

It enables the receive + transmit interrupts which immediately triggers the TX_EMPTY event, because the TX buffer is empty. This calls the SP1Isr() interrupt service routine which returns only if all the data bytes was sent and received. In other words it holds up the SoC, the interrupt service routine runs – in my case – for 25usec.

I modified the original interrupt service function to send and/or receive one byte only then immediately return from the interrupt. Once the byte is sent the next TX Empty interrupt should call the SPI1Isr() function again and the it should send and receive the next byte. But somehow the SP1Isr() function is called only once so the SPI sends the command byte but never receives the data from the ADC! I can’t understand this? Why the first TX EMPTY interrupt is fired and why the second and further TX EMPY interrupts do not?

Note: The SPI interrupts are disabled only if the desired count of bytes sent and received. In my case I have to send 13 bytes. The first byte is the read conversion result command, the next 12 bytes are dummy writes to keep the SPI clock going while I receive the 12-byte answer. 12 bytes = 4 * 3 bytes = 4 channels * 24 bits.

Many thanks if somebody has some idea what I am doing wrong.

Best regards,
Louis

  • Louis,

    The StarterWare code is a great starting place for developing an application. It sounds like you were able to get it to work functionally with the StarterWare and then started the process of optimizing the code to behave the way you wanted it to, with better performance.

    The best approach is to make as few changes as possible at each step along the way. When things are working, move on to the next set of changes that you have in mind. If things do not work, at least you have a small set of code changes to examine to look for the reason for the failure.

    You will want to look at the SPI portion of the TRM to understand how the interrupt signalling works. Quite often, you have to make sure to handle all outstanding interrupt conditions before leaving the ISR in order for the next interrupt to trigger an actual event to the DSP. For example, if there is a TX_EMPTY event that triggers the ISR, then the ISR detects and clears that flag, processes the data, and returns from the ISR, it may be possible that another interrupt condition occurred before the TX_EMPTY was cleared so there could be an outstanding RX_READY interrupt. This might prevent future interrupts from occurring, if a 0 status is required before any new interrupts can get through.

    That is just a possible scenario to get you thinking about what could go wrong. The StarterWare you started with apparently did not have the problem you are seeing, so there is something it was doing right that you are now doing wrong.

    Good luck, and keep us posted on what you find.

    Regards,
    RandyP
  • Hi RandyP
    Thanks for the reply. There is no other interrupts in my APP except SPI TX_EMPTY and RX_READY. It does nothing just reads the ADC and stores the channel1-2 data in an array for the CCS6 Dual Graph function.
    Regards,
    Louis
  • It looks like nobody has idea about this problem. Maybe some code helps.
    This is the SPI interrupt service routine which returns only if all the bytes were sent from txBuf[] and received to rxBuf[].

    void SPI1Isr(void)
    {
        unsigned int state;
        unsigned char data;
    
        // Clear SPI1 interrupt event
        IntEventClear(SYS_INT_SPI1_INT);
    
        // Get pending interrupt.
        state = SPIInterruptVectorGet(SOC_SPI_1_REGS);
    
        while (state)
        {
        	// Transmit IT?
    		if (state == SPI_TX_BUF_EMPTY)
    		{
    			if (txLength > 0)
    			{
    				// Send next byte
    				data = txBuf[txIndex++];
    				// Transmit data
    				SPITransmitData1(SOC_SPI_1_REGS, data);
    				// Decrement TX data length
    				txLength--;
    			}
    			else
    			{
    				// No more bytes to send.
    				SPIIntDisable(SOC_SPI_1_REGS, SPI_TRANSMIT_INT);
    			}
    		}
    
    		// Receive IT?
    		if (state == SPI_RECV_FULL)
    		{
    			// Read data
    			data = SPIDataReceive(SOC_SPI_1_REGS);
    			// Save data
    			.rxBuf[rxIndex++] = data;
    		}
    	    // Get pending interrupt.
    	    state = SPIInterruptVectorGet(SOC_SPI_1_REGS);
    	}
    
    	// No more bytes to send.
    	SPIIntDisable(SOC_SPI_1_REGS, (SPI_TRANSMIT_INT | SPI_RECV_INT));
    
    	// Release CS
    	SPIDat1Config(SOC_SPI_1_REGS, SPI_DATA_FORMAT0, SPI1_DEFAULT_CS);
    }

    The ISR works fine but it keeps the global interrupt disabled for around 20us. Which is not long time, but it is unnecessary. The interrupt service routine should send or receive one byte then return immediately.

    This is the SPI interrupt service routine which returns immediately after one byte was sent from txBuf[] and received to rxBuf[].

    void SPI1IsrOneByte(void)
    {
        unsigned int state;
        unsigned char data;
    
        // Clear SPI1 interrupt event
        IntEventClear(SYS_INT_SPI1_INT);
    
        // Get pending interrupt.
        state = SPIInterruptVectorGet(SOC_SPI_1_REGS);
    
    	// Transmit IT?
    	if (state == SPI_TX_BUF_EMPTY)
    	{
    		if (txLength > 0)
    		{
    			// Send next byte
    			data = txBuf[txIndex++];
    			// Transmit data
    			SPITransmitData1(SOC_SPI_1_REGS, data);
    			// Decrement TX data length
    			txLength--;
    		}
    		else
    		{
    			// No more bytes to send.
    			SPIIntDisable(SOC_SPI_1_REGS, SPI_TRANSMIT_INT);
    		}
    	}
    
    	// Receive IT?
    	if (state == SPI_RECV_FULL)
    	{
    		// Read data
    		data = SPIDataReceive(SOC_SPI_1_REGS);
    		// Save data
    		.rxBuf[rxIndex++] = data;
    		// Decrement TX data length
    		rxLength--;
    		if (rxlLength == 0)
    		{
    			// If last byte received disable SPI interrupts
    			SPIIntDisable(SOC_SPI_1_REGS, (SPI_TRANSMIT_INT | SPI_RECV_INT));
    			// Release CS
    			SPIDat1Config(SOC_SPI_1_REGS, SPI_DATA_FORMAT0, SPI1_DEFAULT_CS);
    		}
    	}
    }

    The second version runs once, so the first byte is sent out. But it never runs again. Even it never receives any byte.

    More details:

    I have checked the status of SPI1 register in CSS6 before leaving the single byte mode ISR function:

    • SPI1.SPICGR1 (Global control reg) SPI is enabled, no loopback, no power down, internal clock, master mode
    • SPI1.SPIINT0 (Interrupt reg) TXINTENA = 1, RXINTENA = 1, others disabled
    • SPI1.SPILVL (Level reg) TX and TX uses INT1
    • SPI1.SPIFLG (Flag reg) TXINTFLG = 1, Empty, RXINTFLG = 1, FULL, others show no error
    • SPI1.INTVEC_1: funny it is not in the registers list. There is an INTVEC_2 register which is not in the manual. Bug in CSS6?
    • Global interrupt is enabled once it returns from the ISR function

  • The SPI1IsrOneByte() code assumes that you will get the SPI_TX_BUF_EMPTY and SPI_RECV_FULL on separate interrupts. They can occur at once and the code must loop on SPIInterruptVectorGet() to get both Tx and RX interrupts. The SPI1Isr(void) code does loop until SPIInterruptVectorGet() says there is no more pending interrupts. Both SPI1IsrOneByte() and SPIInterruptVectorGet() assume that Rx will be the last interrupt. I have seen occasions with my OMAP-L137 board where the Tx interrupt is last.

    void SPI1Isr(void)
    {
      unsigned int state;
      unsigned char data;
    
      IntEventClear(SYS_INT_SPI1_INT);
      state = SPIInterruptVectorGet(SOC_SPI_1_REGS);
      while (state)
      {
        if (state == SPI_TX_BUF_EMPTY)
        {
          if (txLength > 0)
          {
            data = txBuf[txIndex++];
            txLength--;
            SPITransmitData1(SOC_SPI_1_REGS, data);
          }
          else
            SPIIntDisable(SOC_SPI_1_REGS, SPI_TRANSMIT_INT);
        }
        else if (state == SPI_RECV_FULL)
        {
          data = SPIDataReceive(SOC_SPI_1_REGS);
          if(rxLength > 0)
          {
            rxBuf[rxIndex++] = data;
            rxLength--;
          }
          else
            SPIIntDisable(SOC_SPI_1_REGS, SPI_RECV_INT);
        }
        state = SPIInterruptVectorGet(SOC_SPI_1_REGS);
      }
    
      if((txLength==0)&&(rxLength==0))
        SPIDat1Config(SOC_SPI_1_REGS, SPI_DATA_FORMAT0, SPI1_DEFAULT_CS);
    }
    
    

    The ISR code above assumes that Tx and Rx buiffers are always the same size.

  • Hi Norman.

    Thanks for the reply. Yes, in my case the TX and RX buffers have the same size. The TX interrupt has higher priority than the RX interrupt so if the RX and TX happen at the same time the TX will be served first then the RX. This tells me the RX is always the last interrupt. Anyway this part works fine.

    What I need is to send or receive a byte then quit from the interrupt service routine. The next TX_EMTPY should call the ISR again when the TX buffer will be empty. Once a byte is sent then the SPI receives a byte back so the RX_FULL interrupt should also called. The problem is this happens only once. More exactly the TX_EMPTY interrupt is fired once, the RX_FULL interrupt gets never served.

    SPI1Isr: When a prepared the data to be sent then I enable the TX and RX interrupt in SPI1. This causes a TXM_EMPTY interrupt and SPI1Isr is called. But SPI1Isr – while the whole communication is running – blocks the other interrupts. This is what I would like to avoid.

    Thanks,

  • Somebody flagged my reply as "Suggested Answer". I can't seem to find a forum button to remove hat. If there are any forum moderators out there, please remove the "Suggested Answer".

    I am not sure sbout this, I think the IntEventClear(SYS_INT_SPI1_INT) call will clear pending interrupts. If Tx an Rx occur simultaneously and you call SPIInterruptVectorGet(SOC_SPI_1_REGS) only once, you will miss the second pending interrupt.

    From what I've seen, you are likely to stay in the SPI ISR for one Rx byte and one Tx byte. Have you measured the time to measure handling 1 vector vs handling 2 vector per interrupt call?

    I guess you could poll the SPI instead of using interrupts. Assumes the SPI speed is fairly slow and the main line code gets enough cycles.
  • Louis,

    LouisB said:
    But SPI1Isr – while the whole communication is running – blocks the other interrupts. This is what I would like to avoid.

    Have you consider using nested interrupts? This would avoid blocking the other interrupts.

    I cannot tell you the library function calls to use. If you were using TI-RTOS, then you can enable interrupts selectively for nesting within the RTOS configuration; if that is the case, then you can easily do what you need to do.

    The process that needs to be followed is

    1. All interrupts are globally disabled by default, meaning the GIE bit in the main status register is 0.
    2. Save a copy of the IER register.
    3. To enable nesting of selected interrupts, clear unselected interrupt bits in IER and be sure to clear the SPI1 interrupt bit, too.
    4. Set GIE to 1 using an appropriate CSL or system call.
    5. Complete the processing of SPI1Isr.
    6. Clear GIE.
    7. Restore IER to the saved value,

    Regards,
    RandyP

  • Hi RandyP,         

    Thanks for the idea. Yes I am still thinking in “nested interrupts” but I had no time to figure out how to do this yet. To be honest it was a shocking info while an interrupt request is serving the other interrupts a blocked. A simple 8-bit 8051 allows nested interrupt…

    No, I do not use TI-RTOS. I am using StarterWare.

    My original questions were:

    1. Why the TX_Empty interrupt does run only once?
    2. Is this my fault or this is a feature of the C6748 DSP chip?
    3. And what is wrong with the SPI1IsrOnByte sample routine above?

    Best regards,

  • Louis,

    LouisB said:
    Yes I am still thinking in “nested interrupts” but I had no time to figure out how to do this yet.

    You said you were trying to avoid other interrupts being blocked. Allowing nested interrupts is the only way to do that if you intend to wait for device status to change in the ISR. I do not recommend doing that, but then I recommend using TI-RTOS so you can have a strong multi-threaded system. You can adapt from StarterWare to TI-RTOS and make life easier for yourself. Or you can continue the way you are going and eventually figure out your issues successfully.

    What has changed from your original post? You originally said that you had only the TX_EMPTY and RX_READY interrupts in your application, but you now have other interrupts that are being blocked. It probably does not matter since you are the one putting everything together and trying to make it work, but it is confusing to readers trying to help you.

    LouisB said:
    To be honest it was a shocking info while an interrupt request is serving the other interrupts a blocked. A simple 8-bit 8051 allows nested interrupt…

    You are sure to find other differences between the VLIW C6748 456MHz fixed-/floating-point SoC and the 8051. It may help you succeed

    LouisB said:

    My original questions were:

    1. Why the TX_Empty interrupt does run only once?
    2. Is this my fault or this is a feature of the C6748 DSP chip?
    3. And what is wrong with the SPI1IsrOnByte sample routine above?

    1. I have offered [only] philosophical advice and Norman Wong has offered excellent direct advice to help you debug you application. We can answer direct questions like "how do I enable nested interrupts" but we cannot duplicate your hardware operation to be able to debug your code or function. Sometimes that is easy to do, sometimes not. In this case, it is probably a race condition with timing of interrupts and/or over-clearing of registers. My advice is to study the SPI User Guide to understand the timing of the interrupts and the handling of the register bits, while Norman Wong has told you specific issues that occur between Tx and Tx based on his experience and his understanding of the functions you are calling.

    2. Since you have the StarterWare-based block-date ISR that works and the single byte one does not work, this is your fault. It is definitely not a feature of the C6748 DSP chip.

    3. This is the same as question #1, is it not? I think Norman Wong's advice and comments are the best on-point to your troubles. But if using the block-based ISR with nested interrupts solves your problem, we will all be happy.

    Regards,
    RandyP

  • Thanks for the reply.

    1. The 8051 example referred to the nested interrupts without any magic instructions only. No doubt the C6478 is a very nice chip, this why I picked it. The C6748 can calculate 1024 sample FFT within 2-3 ms which is world record.
    2. I am developing a measurement tool which measures the phase/time difference between two sinusoidal signals. The accuracy is a couple on nanoseconds. This APP can not be written under any RTOS. (And it already works with simple 8-bit 8051, just it is slow)
    3. Yes, the sample application had one SPI interrupt only but it does not matter whether you have other interrupts or you don’t. The question is whether it blocks the other interrupts or not. Of course my final firmware will have USB/UART based communication and timer interrupts. Blocking the time measurement interrupts (timers and counters which measure the elapsed time and counting pulses) will cause false time and pulse count.
    4. AND THIS IS THE POINT: SPI is something similar to UART (except it is synchronized and you must transmit dummy bytes in order to keep the clock running and be able to receive the answer). In my UART handler I use the send/receive one byte method (SPI1IsrOneByte). The UART interrupt is always enabled. When I need to send information to Host PC I just
      a. setup the TX buffer
      b. set txLength to the byte count in the TX buffer
      c. write the first byte the TXBUF1.
      When the first byte has been sent out then I get TX_EMPTY interrupt then I send the next byte. If the txLength == 0 then do not send more data. That’s all. Why does not it work with SPI???
    5.  Yes, I kept Norman’s advice and I modified the code:
      // Loop until all the three statements are true
      // 1. while we have TX or RX interrupts
      // 2. we have bytes to be send
      // 3. we have not received all the bytes
      while (state != 0 || spi1Ctrl.txLength > 0 || spi1Ctrl.rxLength > 0)
      {
      	// Transmit IT?
      	if (state == SPI_TX_BUF_EMPTY)
      	{
      		if (spi1Ctrl.txLength > 0)
      	...

      If all of these are false then I quit from the ISR. This way I capture all the bytes what the AD converter sends back and I also handle both the RX_FULL and TX_EMPTY interrupts.

    Thanks for your help.

  • I reread the doc on the INTVECT1 register. Reading this register clears the Rx Full interrupt. If Tx and Rx IRQs occur and the Tx IRQ is of higher priority, servicing the Tx IRQ will automatically clear the Rx IRQ. Servicing just the TX IRQ will result is the Rx IRQ being lost. The doc would suggest the Rx IRQ is of higher priority than the Tx IRQ and this scenario would not happen. Perhaps theres is a priority bug in the silcon.

    If you are trying to sample the ADC with an exact period between samples, you should use DMA to L2 memory or static RAM. DMA in theory should not have jitter regardless of memory type but I have seem problems when DMA with DDR. DDR can hold off the bus for refresh, causing jitter in the DMA.

    Using DMA would remove sampling jitter due to other interrupt being processed. Also some small jitter in code if the interrupt code resides in DDR. Key is to use the FIFO buffers on the peripherals to reduce the number and duration of ISRs. For example UARTs should use the full FIFO depth.

    From what I have seen, your SPI ISR should only process 1 TX and/or 1 Rx per interrupt. The state variable should become zero and the processing loop in the ISR will exit. If you are sending N Tx bytes and M Rx bytes, your should receive max(M,N) to (M+N) interrupts. The ISR loop description is reduced to "Loop until we have serviced pending TX or RX interrupts". This variablity would cause sampling jitter. I assume this is why you choose to stay in the ISR for the entire the entire SPI transaction. You probably be better off using a main line polled I/O with global interrupts disabled.


  • Hi Norman,
    This is a very valuable analysis of the SPI problem. It explains why only 1 Tx interrupt is get served and why the Rx interrupt never was served.

    And, yes I am trying to move to the DMA but to set DMA up for this ADC is not easy job. To read the data register of MAX1140K ADC (http://www.maximintegrated.com/en/products/analog/data-converters/analog-to-digital-converters/MAX11040K.html) I have to write the “0xF0” command (Tx) into the chip and ignore the received answer byte. Then I have write 12 0x00 dummy bytes to keep the clock running. The answer for the 12 bytes contains 4x24 bit ADC conversion results which were simultaneously sampled. This is what the DMA should do.

    You are right about the sampling jitter but it does not have any influence to the sampling of the ADC. The sampling is fixed to 16 kHz and independent from the SPI port. This means I get an interrupt in every 62.5 us (16KHz). From this interrupt I enable the SPI RX and TX interrupts which immediately raises the TX_EMPTY interrupt. And that’s all.

    I just decided to stay in the SPI1Isr() function because the UART like method (Single byre read and write) did not work.

    Thanks for your valuable suggestion and your help.
    Best regards
    Louis