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.

MSP430FR5969: Multibyte transmission to MSP430 slave device only receives every other byte.

Part Number: MSP430FR5969

Hi all,

I am trying to send multiple bytes at once to an MSP430FR5969 slave using FTDI SPI USB to Serial cable (C232HM). I already was able to create a working loop-back test where I send and receive one byte at a time. However, if I try to send multiple bytes at once, only every other byte is ever received. For example, If I were to send the buffer [5,3,6], the microcontroller will only have [5,6] in its buffer. I checked using an oscilloscope and the entire byte stream is being sent rather than parts of it (i.e. [5,3,6] is visible on MOSI). As such, I suspect there is something incorrect with my initialization of the SPI connection on the slave device or with my interrupt service routine rather than there being something wrong with the master device. I have attached the relevant code sections below.

On the MicroController:

uint8_t ReceiveIndex = 0;
uint8_t ReceiveBuffer[128] = {0};

/* Initialize SPI Interface for 3-wire SPI Interface.
 * Chip-select is active low (using GPIO)
 * Used only for communicating to the computer */
void SpiDriver_InitCom(void)
{

    /* Set SPI Chip Select as input */
    GPIO_setAsPeripheralModuleFunctionInputPin(
        SPI_CS_N_PORT_COM,
        SPI_CS_N_PIN_COM,
        GPIO_SECONDARY_MODULE_FUNCTION
        );

    /* Set SPI Clock as input */
    GPIO_setAsPeripheralModuleFunctionInputPin(
        SPI_CLK_PORT_COM,
        SPI_CLK_PIN_COM,
        GPIO_SECONDARY_MODULE_FUNCTION
        );

    /* Set SPI MOSI as input */
    GPIO_setAsPeripheralModuleFunctionInputPin(
        SPI_MOSI_PORT_COM,
        SPI_MOSI_PIN_COM,
        GPIO_SECONDARY_MODULE_FUNCTION
        );

    /* Set SPI MISO as output */
    GPIO_setAsPeripheralModuleFunctionOutputPin(
        SPI_MISO_PORT_COM,
        SPI_MISO_PIN_COM,
        GPIO_SECONDARY_MODULE_FUNCTION
        );

    /* Set SPI CS_n as input (active low) */
    GPIO_setAsInputPin(SPI_CS_N_PORT_COM, SPI_CS_N_PIN_COM);

    /* Initialize Slave */
    EUSCI_B_SPI_initSlaveParam param = {0};
    param.msbFirst = EUSCI_B_SPI_MSB_FIRST;
    param.clockPhase = EUSCI_B_SPI_PHASE_DATA_CHANGED_ONFIRST_CAPTURED_ON_NEXT;
param.clockPolarity = EUSCI_B_SPI_CLOCKPOLARITY_INACTIVITY_HIGH; param.spiMode = EUSCI_B_SPI_3PIN; EUSCI_B_SPI_initSlave(EUSCI_B0_BASE, &param); /* Enable SPI module */ EUSCI_B_SPI_enable(EUSCI_B0_BASE); /* Clear receive/transmit interrupt */ EUSCI_B_SPI_clearInterrupt(EUSCI_B0_BASE, EUSCI_B_SPI_RECEIVE_INTERRUPT); /* Enable receive/transmit interrupt */ EUSCI_B_SPI_enableInterrupt(EUSCI_B0_BASE, EUSCI_B_SPI_RECEIVE_INTERRUPT); __bis_SR_register(LPM0_bits + GIE); // Enter LPM0, enable interrupts } #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__) #pragma vector=USCI_B0_VECTOR __interrupt void USCI_B0_ISR(void) #elif defined(__GNUC__) void __attribute__ ((interrupt(USCIB0_VECTOR))) USCI_B0_ISR(void) #else #error Compiler not supported! #endif { uint8_t rx_data = 0; switch(__even_in_range(UCB0IV, USCI_SPI_UCTXIFG)) { case USCI_NONE: break; case USCI_SPI_UCRXIFG: rx_data = EUSCI_B_SPI_receiveData(EUSCI_B0_BASE); UCB0IFG &= ~UCRXIFG; if (!(SPI_CS_N_PORT & SPI_CS_N_PIN)) { if (rx_data != 0 || rx_data != -1) { ReceiveBuffer[ReceiveIndex++] = rx_data; } } case USCI_SPI_UCTXIFG: break; default: break; } }


Main.c:

int main(void)
{

        int ii;
        PMM_unlockLPM5();
        ClockDriver_Init();
        WatchdogDriver_Init();  // Will start Watchdog
        WatchdogDriver_SetEnable(false);



        SpiDriver_InitCom();
        SpiControl_Init();
        UartControl_Init();

        __enable_interrupt();

        //EUSCI_B_SPI_transmitData(EUSCI_B0_BASE, (uint8_t)0xA5);

        int testflag = 0;
        while(1)
        {
            printOutBuff();
        }

On the Master Device:

SPI_CONFIG_OPTION_MODE0 =  0x00000000
SPI_CONFIG_OPTION_CS_DBUS3 = 0x00000000  # 000 00 #
SPI_CONFIG_OPTION_CS_ACTIVELOW = 0x00000020
SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES =  0x00000000
SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE = 0x00000002
SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE = 0x00000004

def main(): libMPSSE = load_spi_library() device = FT_DEVICE_LIST_INFO_NODE() config = ChannelConfig() numChannels, status = getNumChannels(libMPSSE) indx = ctypes.c_uint32(0) device, status = getChannelInfo(libMPSSE, indx, device) handle, status = openChannel(libMPSSE, indx) config.ClockRate = ctypes.c_uint32(1000000) config.LatencyTimer = ctypes.c_byte(1) config.configOptions = ctypes.c_uint32(mask.SPI_CONFIG_OPTION_MODE0 | mask.SPI_CONFIG_OPTION_CS_DBUS3 | mask.SPI_CONFIG_OPTION_CS_ACTIVELOW) config, status = initChannel(libMPSSE, handle, config) options = ctypes.c_uint32(mask.SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES | mask.SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE | mask.SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE) tx_buffer = [0,0,0,0,0,0] rx_buffer = [0,0,0,0,0,0] tx = (ctypes.c_uint8*len(tx_buffer))(*tx_buffer) rx = (ctypes.c_uint8*len(rx_buffer))(*rx_buffer) transferCount = ctypes.c_uint32(0) tx[0] = 0x0F tx[1] = 0x06 tx[2] = 0x0E status = libMPSSE.SPI_Write(handle, ctypes.byref(tx), ctypes.c_uint32(3), ctypes.byref(transferCount), options)

The code in the master device is based of the technical documentation for FTDI the SPI library: www.ftdichip.com/.../AN_178_User Guide for LibMPSSE-SPI.pdf

At this point I am pretty stumped and any help would be greatly appreciated!!

  • It sure looks as though you're overrunning your slave. With a 1MHz ClockRate, you only have 8usec to grab the byte before the next one arrives, and this ISR is doing more work than that.

    One solution is to speed up the slave (MCLK).

    Alternatively, slow down the data at the master. The simplest way to do this is to run a slower ClockRate.

    Unsolicited:
    > UCB0IFG &= ~UCRXIFG;
    I recommend you not do this. It's unnecessary, since receiveData already cleared it, and doing this opens you up to a couple of hazards.

    > if (rx_data != 0 || rx_data != -1)
    This condition is always true. Did you maybe mean '&&'?
  • Hi Bruce,

    Thank you for the advice! It seems the clock was the issue. I dropped my master clock to 10 kHz and I started seeing all the values in my buffer. However, I am surprised that I was overrunning the clock, as I was running the slave device's clock at 16MHz. Do you have any suggestions as to why this would be the case? Ideally I would need the clock to run as fast as I can, which is why I started at such a high value in the first place.

    On a side note:

    I only put in the line: UCB0IFG &= ~UCRXIFG in because I saw it in an example and I was getting pretty desperate. Now that I know this isn't best practice, I will avoid doing this in the future.

    The second line was mostly a debugging statement. It was to make sure that I only got my true data (in this case 3, 5,6) and not -1 which is my no data here condition or 0 which sometimes appears in my buffer but is not desired. 

  • OK, I didn't know that the clock was changed. It does seem as though this ISR should complete in 8*16=128 clocks, but that's just guessing.

    Interrupt latency (competing ISRs) might be a concern, but since main() never finishes initialization, I suppose(?) the SPI is the only enabled interrupt in this test case.

    Whatever the hypothesis, the diagnostic Usual Suspects are the same:
    1) Step up the SPI ClockRate (from 10kHz) and see where it fails
    and/or
    2) Study the scope traces (maybe add something to the ISR to wiggle a GPIO pin) to see what the numbers actually are.
  • Hi Bruce,

    I changed my ISR to this:

    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector=USCI_B0_VECTOR
    __interrupt void USCI_B0_ISR(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(USCIB0_VECTOR))) USCI_B0_ISR(void)
    #else
    #error Compiler not supported!
    #endif
    {
        int8_t rx_data = 0;
        int8_t tx_data = 0;
        switch(__even_in_range(UCB0IV, USCI_SPI_UCTXIFG))
        {
        case USCI_NONE:
            break;
        case USCI_SPI_UCRXIFG:
            rx_data = EUSCI_B_SPI_receiveData(EUSCI_B0_BASE);
            SpiControl_PutIntoRxBuffer(rx_data);
            /* If there's something in the Ring Buffer, transmit it
            * If not, then disable TX interrupts until new data gets written. */
            tx_data = SpiControl_GetFromTxBuffer();
            if (tx_data != -1)
            {
                EUSCI_B_SPI_transmitData(EUSCI_B0_BASE, (uint8_t)tx_data);
            }
        case USCI_SPI_UCTXIFG:
            break;
        default:
            break;
        }
    }

    and my main to this:

    int main(void)
    {
    
            int ii;
            PMM_unlockLPM5();
            ClockDriver_Init();
            WatchdogDriver_Init();  // Will start Watchdog
            WatchdogDriver_SetEnable(false);
    
    
    
            SpiDriver_InitCom();
            SpiControl_Init();
    
            __enable_interrupt();
    
            while(1)
            {
                ii = SpiControl_GetFromRxBuffer();
                if (ii != -1)
                {
                    SpiControl_PutIntoTxBufferChar(((uint8_t)ii));
                }
            }
    }

    and tried your suggestion. My results turned out to be pretty weird. If I send only 2 bytes, I found that I am able to send the bytes (buffer of [5,6]) up to a clock of 2 MHz and still see it in my slave device. However, If I send out 3 bytes at once (buffer of [3, 5, 7]) I am only able to send at a clock cycle of between 400 and 500 KHz (at 500 KHz, 5 disappears and only 3 and 7 make it to the buffer). This make me wonder if it is something else rather than my clock that is causing the issue. I am also having issues sending data back from my slave device as I need to ask for additional bytes before my data is returned. For example:

    At 10 KHz:
    master sends: |data|     0  |   0   |
    master reads:  |   0    |data|    0  |


    At 50 KHz:
    master sends: |data|    0   |   0    |
    master reads: |    0   |    0   |data|


    At 100 KHz:
    master sends: |data|   0    |   0    |
    master reads: |    0   |   0    |data|


    At 500 KHz:
    master sends: |  data     |   0    |   0    |
    master reads: |prev data|   0    |   0   |

    Any suggestions as what could be going on would be greatly appreciated!

  • Keep in mind that each SPI byte is an exchange. By the time the slave sees a byte, that exchange is already over, so it can't respond earlier than the next exchange. In other words, any response from the slave will be off-by-one (at least). This is intrinsic to SPI.

    The slave will pretty much always see the final byte, since that will be left over in the RXBUF no matter how late it looks. There's also a high probability it will see the first byte, since it fetches the RXBUF early in the ISR.

    Given all that, what I see is consistent with overrunning the slave. The only mystery is why the slave seems to be taking so long. Are you sure your clock (MCLK) is at 16MHz? You can configure PJ.1 to put out MCLK for your scope.
  • Thanks Bruce.

    Hi Gadiel,

    Have you solved the problem?

    Ling
  • Hi Ling,

    I haven't had a chance to take a look yet as other things have come up. I should be able find out by the end of this week though.

    Thanks for all your help!

    Gadiel
  • Hi Bruce and Ling,

    Sorry for the late follow up. I just checked the clock of my microcontroller using an oscilloscope and it is 16MHz, which is what I expected. This makes me think that the clock is not the issue... or at least not the clock of the slave. Could there be another reason why it is not working properly?

    Thanks in advance!

    Gadiel

  • Do you know how long your ISR actually takes? At 500kHz, the budget is 8*2*16=256 clocks. There's a fair amount of code we can't see.

    I usually get a first approximation by starting a free-running (continuous mode) timer, e.g. TA0, capturing TA0R at ISR entry and exit, then subtracting the two with a calculator. Not fancy, but easy and cheap to insert.
  • Hi Bruce,

    Thank you for the suggestion! I will try that and see how long the ISR actually takes. I am relatively new to this process so it might take me a while though.

    Best regards,

    Gadiel

  • I forgot you had a scope. Even quicker/simpler might be to wiggle a GPIO on entry/exit from the ISR and time it on the scope.
  • I can do that as well, but wouldn't that only give me the relative time it takes to run the ISR rather than the absolute number of clock cycles it takes?
  • Hi Gadiel,

    Have you considered using DMA? It essentially works as a buffer. It can take each byte received by the SPI peripheral and move it to an array in memory, in the background, without interrupts. Another DMA channel can be configured to do transmission as well. You put an array of data into memory and DMA will feed it to the SPI peripheral one byte at a time for transmission.

    Since this is exactly the task your interrupt is attempting to accomplish and nothing more, using DMA would replace it very nicely. Your code can then process the data whenever there is time (non-realtime task.. you can process the data in main without an interrupt). DMA is very fast. But keep in mind that there are only 3 DMA channels available on your device, so use it sparingly.

    I also noticed that you do not really use the chip-select pin as effectively as you could. At first you initialize it as a SPI pin, then you reinitialize it as a GPIO input and read it in code. Ideally, you should leave it as a SPI pin and configure the SPI peripheral to use it. (By setting param.spiMode = EUSCI_B_SPI_4PIN_UCxSTE_ACTIVE_HIGH or EUSCI_B_SPI_4PIN_UCxSTE_ACTIVE_LOW if I'm not mistaken. Sorry, I don't have any experience with the library. I do direct register manipulation.) If you configure it this way, then the SPI peripheral will automatically ignore data while the chip-select pin is inactive. But if you have only one master and one slave, then you really don't need to use a chip-select at all.

    I hope this helps.

  • The scope will measure microseconds, while the timer will measure clock ticks. Since you've confirmed the Slave's clock (MCLK) speed you can switch between the two as convenient. Ultimately you want to compare this quantity to the SPI clock speed (500kHz I think it was) where the failures started.

    In case I was overly glib before: What I was suggesting was taking an unused GPIO, setting it (e.g.) high on ISR entry and low on ISR exit, then using the scope to measure the width (microseconds) of the pulse. This won't count the CPU's raw ISR entry and exit cost, but it will give you a first approximation. (My guess is that you'll find that it's either much longer or much shorter than you thought, and the overhead won't matter much.)

    If your scope is multi-channel, it might be instructive to hook another channel to SCLK, to give you a picture of the overall dynamics.
  • Hi Gadiel,

    Have you tried the code example?

    msp430fr59xx_eusci_spi_standard_master.c
    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    //******************************************************************************
    // MSP430FR59xx Demo - eUSCI_A0, SPI 3-Wire Master multiple byte RX/TX
    //
    // Description: SPI master communicates to SPI slave sending and receiving
    // 3 different messages of different length. SPI master will enter LPM0 mode
    // while waiting for the messages to be sent/receiving using SPI interrupt.
    // SPI Master will initially wait for a port interrupt in LPM0 mode before
    // starting the SPI communication.
    // ACLK = NA, MCLK = SMCLK = DCO 16MHz.
    //
    //
    // MSP430FR5969
    // -----------------
    // /|\ | P1.3|-> Slave Chip Select (GPIO)
    // | | |
    // ---|RST P1.4|-> Slave Reset (GPIO)
    // | |
    // | P2.0|-> Data Out (UCA0SIMO)
    // | |
    // Button ->|P1.1 P2.1|<- Data In (UCA0SOMI)
    // | |
    // | P1.5|-> Serial Clock Out (UCA0CLK)
    //
    // Nima Eskandari
    // Texas Instruments Inc.
    // April 2017
    // Built with CCS V7.0
    //******************************************************************************
    #include <msp430.h>
    #include <stdint.h>
    //******************************************************************************
    // Example Commands ************************************************************
    //******************************************************************************
    #define DUMMY 0xFF
    #define SLAVE_CS_OUT P1OUT
    #define SLAVE_CS_DIR P1DIR
    #define SLAVE_CS_PIN BIT3
    /* CMD_TYPE_X_SLAVE are example commands the master sends to the slave.
    * The slave will send example SlaveTypeX buffers in response.
    *
    * CMD_TYPE_X_MASTER are example commands the master sends to the slave.
    * The slave will initialize itself to receive MasterTypeX example buffers.
    * */
    #define CMD_TYPE_0_SLAVE 0
    #define CMD_TYPE_1_SLAVE 1
    #define CMD_TYPE_2_SLAVE 2
    #define CMD_TYPE_0_MASTER 3
    #define CMD_TYPE_1_MASTER 4
    #define CMD_TYPE_2_MASTER 5
    #define TYPE_0_LENGTH 1
    #define TYPE_1_LENGTH 2
    #define TYPE_2_LENGTH 6
    #define MAX_BUFFER_SIZE 20
    /* MasterTypeX are example buffers initialized in the master, they will be
    * sent by the master to the slave.
    * SlaveTypeX are example buffers initialized in the slave, they will be
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    msp430fr59xx_eusci_spi_standard_slave.c
    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    //******************************************************************************
    // MSP430FR59xx Demo - eUSCI_A0, SPI 3-Wire Slave multiple byte RX/TX
    //
    // Description: SPI master communicates to SPI slave sending and receiving
    // 3 different messages of different length. SPI slave will enter LPM0
    // while waiting for the messages to be sent/receiving using SPI interrupt.
    // ACLK = NA, MCLK = SMCLK = DCO 16MHz.
    //
    //
    // MSP430FR5969
    // -----------------
    // /|\ | P1.3|<- Master's GPIO (Chip Select)
    // | | |
    // ---|RST RST |<- Master's GPIO (To reset slave)
    // | |
    // | P2.0|<- Data In (UCA0SIMO)
    // | |
    // | P2.1|-> Data Out (UCA0SOMI)
    // | |
    // | P1.5|<- Serial Clock In (UCA0CLK)
    //
    // Nima Eskandari
    // Texas Instruments Inc.
    // April 2017
    // Built with CCS V7.0
    //******************************************************************************
    #include <msp430.h>
    #include <stdint.h>
    #include <stdbool.h>
    //******************************************************************************
    // Example Commands ************************************************************
    //******************************************************************************
    #define DUMMY 0xFF
    #define SLAVE_CS_IN P1IN
    #define SLAVE_CS_DIR P1DIR
    #define SLAVE_CS_PIN BIT3
    /* CMD_TYPE_X_SLAVE are example commands the master sends to the slave.
    * The slave will send example SlaveTypeX buffers in response.
    *
    * CMD_TYPE_X_MASTER are example commands the master sends to the slave.
    * The slave will initialize itself to receive MasterTypeX example buffers.
    * */
    #define CMD_TYPE_0_SLAVE 0
    #define CMD_TYPE_1_SLAVE 1
    #define CMD_TYPE_2_SLAVE 2
    #define CMD_TYPE_0_MASTER 3
    #define CMD_TYPE_1_MASTER 4
    #define CMD_TYPE_2_MASTER 5
    #define TYPE_0_LENGTH 1
    #define TYPE_1_LENGTH 2
    #define TYPE_2_LENGTH 6
    #define MAX_BUFFER_SIZE 20
    /* MasterTypeX are example buffers initialized in the master, they will be
    * sent by the master to the slave.
    * SlaveTypeX are example buffers initialized in the slave, they will be
    * sent by the slave to the master.
    * */
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Ling

  • Hi Ling,

    My setup is that I have a laptop as the master device and the microcontroller as the slave device. As such, I initially tried copying the ISR of the slave example, but I still had similar issues.

    best regards,

    Gadiel
  • Hi Bruce,

    I was finally able to measure the time it took the ISR to finish its operation. I found that the ISR takes 25 microseconds to complete the read/transmit byte action and based on your calculations, my budget of 8*2*16 = 256 clock cycles only allows me budget of 16 microseconds. I believe this is my issue. Thank you for all your help!

    Best regards,

    Gadiel
  • Rounding up a little, it seems that you should be able to manage 8bits/(25+5)usec or around 266kHz. You may want to add some more padding since these functions always seem to grow.
  • Thank you for the suggestion! Hopefully, I can decrease the ISR size instead rather than having it increase!
  • Please consider my DMA suggestion above. It should allow you to achieve much higher clock rates and eliminate the interrupt altogether.
  • Hi Greg,


    Thank you for your suggestion! I do not believe that I am using any DMA channels anywhere else in the project, so it should be possible to implement it for the SPI interface. However, I am unfamiliar with DMA so I would need to look into it more.

    Thanks in advance,

    Gadiel

**Attention** This is a public forum