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.

MSP430F5438A: Missing last few bits when Chip Select is deselected in isr

Part Number: MSP430F5438A

I am working with MSP430F5438A microcontroller and I am using FreeRTOS cooperative mode with 1ms tick time.
I have a scenario where I have to ensure that the Chip Select is pulled high (deselected) as soon as possible after transmission has completed (within 1 ms).
As a result, I tried to pull the CS high inside the ISR after the last byte have been sent out.

However, I noticed on the oscilloscope that the CS is being pulled high before the last byte have been fully transmitted.
I tried observing the results at two different SPI clock rate (12.8KHz and 128KHz) and attached the oscilloscope images.

spi clock (yellow)
Chip Select (pink)
MOSI (green)


with spi clock 12.8KHz (UCA1BR0 set to 100)


with spi clock 128KHz (UCA1BR0 set to 10)

From the image, it can be seen that after the CS has been pulled high, the last few bits are still in the midst of transmitting. The number of bits missed seems to vary with different spi clock rate.

#include <msp430.h> 
#include <stdio.h>
#include <stdint.h>
#include "msp430f5xx_6xxgeneric.h"


/**
 * main.c
 */
typedef void (*myCallback)(void);
#define HWREG8(x)                                                             \
    (*((volatile uint8_t *)((uint16_t)x)))

/*******************************************************************************
 * Function declaration
 ******************************************************************************/
void spib_init(void);
void spib_setTx_buf(const uint8_t spibTxBuf[], uint16_t spibTxLen,
                    myCallback spibLocalCallback);
void transmit_data(void);
void deselect_CS(void);
/******************************************************************************/
uint8_t tx_testdata[6] = {'A','B','C','D','E','\n'};

void main(void)
{
	WDTCTL = WDTPW | WDTHOLD;	// stop watchdog timer
	// config CS pin
	P9SEL &= ~0x40;
	P9DIR |= 0x40;
	P9OUT |= 0x40;

	spib_init();
	__bis_SR_register(GIE);       // CPU off, enable interrupts
    transmit_data();
	while (1)
	{
	}
//	return 0;
}

void transmit_data(void)
{
    P9OUT &= ~0x40;
    spib_setTx_buf(tx_testdata, 6, &deselect_CS);
}

void deselect_CS(void)
{
    P9OUT |= 0x40;
    __delay_cycles(1000000);
    transmit_data();
}

//******************************************************************************
//
//SPI
//
//******************************************************************************
myCallback spibCallback;
volatile uint8_t *spibPTxData;                     // Pointer to TX data
volatile uint16_t spibTXByteCtr;

void spib_init(void)
{
    // config UCA1 MOSI and MISO pin
    P5SEL |= 0xC0;
    P5DIR |= 0x40;
    // config UCA1 clk pin
    P3SEL |= 0x40;
    P3DIR |= 0x40;

    UCA1CTL1 |= UCSWRST;
    UCA1CTL0 |= UCMST+UCSYNC+UCMODE_0+UCCKPH+UCCKPL+UCMSB;
    UCA1CTL1 |= UCSSEL__SMCLK;
    UCA1BR0 = 0x64;                           // 100
    UCA1BR1 = 0;
    UCA1CTL1 &= ~UCSWRST;
}

void spib_setTx_buf(const uint8_t spibTxBuf[], uint16_t spibTxLen,
                    myCallback spibLocalCallback)
{
    spibCallback = spibLocalCallback;
    spibPTxData = (unsigned char *)spibTxBuf;      // TX array start address
                                            // Place breakpoint here to see each
                                            // transmit operation.
    spibTXByteCtr = spibTxLen;              // Load TX byte counter

    HWREG8(USCI_A1_BASE + OFS_UCAxIFG) &= ~UCTXIE;
    HWREG8(USCI_A1_BASE + OFS_UCAxIE) |= UCTXIE;
    if (spibTXByteCtr > 0)
    {
        if (spibPTxData == NULL)
        {
            UCA1TXBUF = 0xFF;
        }
        else
        {
            UCA1TXBUF = *spibPTxData++;
        }
        spibTXByteCtr--;
    }
//    UCA1TXBUF = spitxbufdata;
}

#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=USCI_A1_VECTOR
__interrupt
#elif defined(__GNUC__)
__attribute__((interrupt(USCI_A1_VECTOR)))
#endif
void USCI_A1_ISR (void)
{
    switch (__even_in_range(UCA1IV,4)){
        //Vector 2 - RXIFG
        case 2:
            break;
        case 4:
            if(0 < spibTXByteCtr)
            {
//                UCA1TXBUF = spitxbufdata;
                if (spibPTxData == NULL)
                {
                    UCA1TXBUF = 0xFF;
                }
                else
                {
                    UCA1TXBUF = *spibPTxData++;
                }
                spibTXByteCtr--;
            }
            else if(0 == spibTXByteCtr)
            {
                HWREG8(USCI_A1_BASE + OFS_UCAxIFG) &= ~UCTXIE;
                HWREG8(USCI_A1_BASE + OFS_UCAxIE) &= ~UCTXIE;
                if(NULL != spibCallback)
                    (*spibCallback)();
            }
            break;
        default:
            break;
    }
}

This is the simplified code I have used to generate the above results.

I would like to ask why are the last few cycles happening after CS has been pulled high if I pull the CS high immediately inside ISR after the last byte transmission?
Also, why are the number of cycles left after CS has been pulled high different as the spi clock rate changes?
Other than spi clock rate, are there other possible factors that may cause the number of cycles remaining after CS high to differ (and hence a different amount of delay required before CS is pulled high)?

  • You must wait until the data has finished shifting out before changing CS. There is no interrupt to help you with this like there is on other parts.

    You will get a receive interrupt at the end of each byte so the best place to change CS would be there.

  • For some reason I got an email notice of a reply which never appeared here.

    I think you don't understand the operation of the serial hardware. TXIFG is set when TXBUF is empty, not when the shift register is empty.

    So after you put the last byte in TXBUF there is one byte in the process of being shifted out plus the byte waiting in TXBUF. The next transmit interrupt will happen when TXBUF is transferred to the shift register. Which of course takes some time to empty.

    Assuming that you are never late in providing data to TXBUF, you could check the UCBUSY status flag when the receive interrupt happens. If it is set, there is more data shifting out. If clear, then transmission is complete.

    Another approach which I have seen is to not use the transmit interrupt at all. The receive interrupt is used instead. That interrupt always happens after all of the data is shifted out. The downside is that there will be a slight pause in the data going out.

  • Hi david, thanks for the thorough explanation, it helps in my understanding.

    Sorry for the email notification, I posted a reply but realised some mistakes in my testing and hence removed the post. Wanted to test more thoroughly before updating again.

    After trying to use the receive interrupt instead, I realised my code seems to fail the timing requirement once in a while. It could be due to the slight pause in the data going out as u mentioned, or due to some other parts of my project. I will try to do more testing before updating again. But for now, the issue of CS being pulled high too early seems to be resolved. Thanks for the help

  • So here's the update.

    I tried using the receive interrupt and count the number of times it has triggered to keep track which is the last byte received. Once the last byte has been received, I will pull the CS high. This resolves the issue of CS being pulled high too early.

    However, I notice the issue whereby the receive interrupt is missed sometimes, especially when some other part of my project is triggering a higher priority interrupt. As a result, my count of the number of receive interrupt triggered does not tally with the expected number and the CS is not pulled high. I tried checking the UCBUSY status flag at the start of receive interrupt to see if it can somehow be used to indicate that the received byte is the last byte but it did not help. Other than counting the number of times the receive interrupt has been triggered, is there a more reliable way to tell that this particular receive interrupt happened for the last byte?

    Otherwise, I may need to consider setting a timer interrupt to trigger after a certain delay during the last transmit interrupt. The CS will then be pulled high when the timer interrupt triggers. In this case, other than spi clock rate, are there other factors which may cause the required delay to change?

  • If you're using the TXIFG to keep the Tx "pipeline" full, then it's not difficult to overrun the Rx side, since the Tx side has (8+8) bits of buffering but the Rx side has only (8+7) bits.

    What (I think) David was suggesting was to not use the TXIFG at all, rather set the TXBUF based on the RXIFG. Doing it this way, the "pipeline" is always empty when you load TXBUF. You would need to deal with the first byte of the transaction, but it looks like you're already doing that.

    An alternative is to use the TXIFG, ignore the RXIFG, but when the ISR discovers it has no more bytes to Tx, just spin on "while (UCA1STAT & UCBUSY);" before de-asserting CS. For a fast SPI, this isn't very long (8*BR0, minus any time you've already spent in the ISR), but you should judge that.

  • Hi Bruce,

    Thanks for the alternative suggestion.

    The while(UCA1STAT & UCBUSY) check before de-asserting CS helps to resolve the issue too but since my spi clock rate will only be at 128kHz, the amount of time spent in the while loop is a concern for me.

    Regarding David's suggestion of not using the TXIFG at all and setting the TXBUF based on the RXIFG, I have tried that implementation too. However, maybe I should have clarified that I am working with a SBAND tranceiver. The slight pause after every byte of transmission is affecting the RF transmission and causing difficulty in decoding the data captured on the receiving end of the RF transmission.

    I am planning to set a timer interrupt after the last byte TXIFG is triggered. The timer interrupt will trigger 100us later, during which I will check for (UCA1STAT & UCBUSY). Under the expected scenario, the UCBUSY bit should not be set by then and I will de-assert the CS then. However, if the UCBUSY is still set, I will have to do some exception handling. For now, my plan will be to reset the spi bus when that occurs.

**Attention** This is a public forum