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.

MSP430F6659: SPI module busy flag always raised

Part Number: MSP430F6659

I've had this problem on two different peripheral chips, but the one I'm showing here is the code for a DRV8353RS motor driver chip since that's also a ti product.

I'm writing in the last update of version 8 of Code Composer Studio.

Here's the SPI init.

void drv_spi_init(){
    // p9.4-6
       P9SEL |= (BIT4 | BIT5 | BIT6);
       USCI_B_SPI_initMasterParam drv_param = {0};

       drv_param.selectClockSource = USCI_B_SPI_CLOCKSOURCE_SMCLK;     //Verified
       drv_param.clockSourceFrequency = UCS_getSMCLK();                //"In SPI master mode, there is only one reason why UCBUSY never gets clear: you don’t have a clock."
       drv_param.desiredSpiClock = 9600000;                            //Verified, subject to change
       drv_param.msbFirst = USCI_B_SPI_MSB_FIRST;                      //Verified
       drv_param.clockPhase = USCI_B_SPI_PHASE_DATA_CHANGED_ONFIRST_CAPTURED_ON_NEXT;  //Somewhat Verified. (Data is captured on the falling edge and propagated on the rising edge)

       drv_param.clockPolarity = USCI_B_SPI_CLOCKPOLARITY_INACTIVITY_LOW;             //Verified via diagram, no text.
       if(USCI_B_SPI_initMaster(USCI_B2_BASE, &drv_param)){
          _no_operation();
       } else{
          drv_error_vector |= SPI_NO_INIT;
       }
       //This enable definitely goes after the init, I checked it twice.
       USCI_B_SPI_enable(USCI_B2_BASE);

}

Here's the read function


uint16_t drv_single_read_only(uint8_t address){
    address &= 0x0F;    //Addresses for this device are only 4 bits.
    address <<= 3;      //And for read only commands, should be placed immediately following a leading 0 bit.
    //They want a full data frame to be 16 bits even if you're only reading a single register and have no data to give them.
    drv_rx_pos = 0;
    drv_tx_pos = 0;

    drv_tx_buff[drv_tx_pos] = address;      //Load the command (This is somewhat redundant since this is read only, and only one register, but for write ops and multiregister ops, it's useful)
    drv_tx_buff[drv_tx_pos + 1] = 0x00;     //Load the data
    //Turn on RX interrupt
    USCI_B_SPI_enableInterrupt (USCI_B2_BASE, USCI_B_SPI_RECEIVE_INTERRUPT);
    //Turn on TX interrupt
    USCI_B_SPI_enableInterrupt (USCI_B2_BASE, USCI_B_SPI_TRANSMIT_INTERRUPT);

    for(regular_timer_outer = 0xFFFF; ((regular_timer_outer > 0) && (!USCI_B_SPI_isBusy(USCI_B2_BASE))); regular_timer_outer--){
                _no_operation();
            }

    if (!(regular_timer_outer > 0)){
        drv_error_vector |= SPI_BUSY;
    }
    USCI_B_SPI_transmitData (USCI_B2_BASE, drv_tx_buff[drv_tx_pos]);
    //UCB2TXBUF = drv_tx_buff[drv_tx_pos];    //This starts off the chain. Once it shifts out, the interrupt triggers and shifts out the next byte.

    for(regular_timer_outer = 0xFFFF; (regular_timer_outer > 0) && ((drv_tx_pos < 2) || (drv_rx_pos < 2)); regular_timer_outer--){
        _no_operation();
    }
    if (!(regular_timer_outer > 0)){
        if(drv_tx_pos < 2){
            drv_error_vector |= TX_TIMEOUT;
        }
        if(drv_rx_pos < 2){
            drv_error_vector |= RX_TIMEOUT;
        }
    }

    //Turn off RX interrupt
    USCI_B_SPI_disableInterrupt (USCI_B2_BASE, USCI_B_SPI_RECEIVE_INTERRUPT);
    //Turn off TX interrupt
    USCI_B_SPI_disableInterrupt (USCI_B2_BASE, USCI_B_SPI_TRANSMIT_INTERRUPT);

    uint16_t temp = 0;
    temp = drv_rx_buff[0];
    temp <<= 8;
    temp |= drv_rx_buff[1];

    return temp;


}


So, we're getting consistent SPI Busy being raised, and we're never getting a TX/RX interrupt trigger.
I have already tried disabling and re-enabling the SPI module before the read, and during each SPI busy check. I have one version of the board that has a 3.3v pullup resistor on the SDO line, and another board that does not, both get the same error. I have also tried to bypass the SPI module's clock scaler by setting SMCLK to the desired 96000 baud. I've also lowered the speed by a factor of 10, which also did not see any change in behavior. I've also tried to use ACLK, though I didn't change as many of those settings afterwards.
Questions:
1) How can I further determine whether this is a hardware or a software issue?

2) Can you point me to where I can find the register info for the SPI module so I can try a more manual approach?

3) Is there something obvious I'm missing from having spent so long bashing my head against this? (Which reminds me, yes I did double check that the chip I'm trying to talk to is in fact on B2).

  • 2) The registers are described in the User Guide (SLAU208Q) Section 40.5 37.5. The CCS Register View does a fairly good job of decoding the bits to the names.

    1) I don't see any Errata in this vicinity [Ref Errata sheet (SLAZ337Y), updated April 2019]

    3) How do you know that the UCBUSY flag is always set? Did you actually see it stuck for a long (by some definition) time, or are you deducing it from some other condition in the code?

    Also, what does your ISR do? I expect it loads TXBUF, but then you're loading it again in this code. 

    You're running the SPI fairly fast at 10MHz, at least for an MSP430. Each byte will take about 4-5 instruction times at maximum MCLK. I wouldn't be surprised if your "startup" loop never sees the SPI go busy, since it will be done by the time you check. I suggest a flag in memory -- done/not-done -- that the ISR sets when it runs out of bytes.

    Unsolicited: With a fast SPI (10MHz counts) using interrupts is a net loss -- between the complexity and the ISR overhead it slows everything down.

    [Edit: Fixed UG section reference -- it's the USCI, not the eUSCI.]

  • I'm checking the busy flag in this section of my read

     for(regular_timer_outer = 0xFFFF; ((regular_timer_outer > 0) && (!USCI_B_SPI_isBusy(USCI_B2_BASE))); regular_timer_outer--){
                    _no_operation();
                }
    
        if (!(regular_timer_outer > 0)){
            drv_error_vector |= SPI_BUSY;
        }
    

    Well that's embarrassing. I threw an "!" in there for some reason, so I'm actually just proving here that the busy flag isn't set. Which sets me back to square 0 on knowing what's actually wrong with this.
    Functionally, my issue is that my ISR here is never triggered. So, I can load data into the TX buff but it's never trying to shift it out

    My ISR transmits a new byte if the one it just transmitted was an even numbered byte since we're zero indexed, and puts any read byte into an array and advances the array. Well, at least in theory, as I said, I have a breakpoint at the beginning and it's never been tripped.

    #pragma vector = USCI_B2_VECTOR
    __interrupt void USCI_B2_ISR(void) {
        if(USCI_B_SPI_getInterruptStatus(USCI_B2_BASE, USCI_B_SPI_RECEIVE_INTERRUPT)){
            for(interrupt_timer_outer = 0x0FFF; ((interrupt_timer_outer > 0) && (USCI_B_SPI_isBusy(USCI_B2_BASE))); interrupt_timer_outer--){
                _no_operation();
            }
            //while(USCI_B_SPI_isBusy(USCI_B2_BASE));
            if(interrupt_timer_outer > 0){
                if(drv_rx_pos > 5){
                    drv_error_vector |= RX_BUFF_FLUSH;
                    drv_rx_pos = 0;
                }
                drv_rx_buff[drv_rx_pos] = UCB2RXBUF;
                drv_rx_pos++;
            }   else{
                drv_error_vector |= SPI_BUSY;
            }
    
        }
        if(USCI_B_SPI_getInterruptStatus(USCI_B2_BASE, USCI_B_SPI_TRANSMIT_INTERRUPT)){
            //Transmissions must be 16 bits, but we have an 8 bit register, so.
            if(!(drv_tx_pos % 2)){ //If the thing we just transmitted was the first 8 bits of a 16 bit word
                for(interrupt_timer_outer = 0x0FFF; ((interrupt_timer_outer > 0) && (USCI_B_SPI_isBusy(USCI_B2_BASE))); interrupt_timer_outer--){
                    _no_operation();
                }
                if(interrupt_timer_outer > 0){
                    UCB2TXBUF = drv_tx_buff[++drv_tx_pos];  //Immediately transmit the next byte
                    //(Preincrement is important, also this is overflow safe because the buffer ends at pos = 5, which is odd.)
                    USCI_B_SPI_clearInterrupt (USCI_B2_BASE, UCTXIE); //Immediately clear the flag so it can shift out.
                } else{
                    drv_error_vector |= SPI_BUSY;
                }
            } else{
                if(drv_tx_pos > 5){
                    drv_error_vector |= TX_BUFF_FLUSH;
                    drv_tx_pos = 0;
                } else{
                    drv_tx_pos++;
                }
                USCI_B_SPI_clearInterrupt (USCI_B2_BASE, UCTXIE);
    
            }
        }
    
    }

  • If your ISR is Never triggered, you should probably check (again) to make sure GIE is set (__enable_interrupt()). I would expect the first ISR call to be at the (1st) word "for" in drv_single_read_only.

    ---------------------

    > USCI_B_SPI_clearInterrupt (USCI_B2_BASE, UCTXIE);

    This is in your ISR, and so isn't related to the ISR-never-called symptom. But I suspect you don't want this line or its sibling below. Either:

    1) You meant SPI_disableInterrupt, which doesn't seem correct in context or

    2) You meant to clear UCTXIFG, which is unneeded and hazardous, and could cause your SPI to stall (though not with UCBUSY set).

  • Well, you called it. I didn't have the GIE set. Forgot to do it when I was setting up my first inits and didn't remember to check for it afterwards.

    Did a few tests, when I don't clear UCTXIFG that interrupt never stops triggering. Side note, the interrupt flags get raised whether the interrupt enable is there or not. The enable only changes whether or not the ISR is enabled or not. I get a TX interrupt as soon as I enable the RX interrupt now that I've changed my code to look like this

    uint16_t drv_single_read_only(uint8_t address){
        address &= 0x0F;    //Addresses for this device are only 4 bits.
        address <<= 3;      //And for read only commands, should be placed immediately following a leading 0 bit.
        //They want a full data frame to be 16 bits even if you're only reading a single register and have no data to give them.
        drv_rx_pos = 0;
        drv_tx_pos = 0;
    
        drv_tx_buff[drv_tx_pos] = address;      //Load the command (This is somewhat redundant since this is read only, and only one register, but for write ops and multiregister ops, it's useful)
        drv_tx_buff[drv_tx_pos + 1] = 0x00;     //Load the data
    
        for(regular_timer_outer = 0xFFFF; ((regular_timer_outer > 0) && (USCI_B_SPI_isBusy(USCI_B2_BASE))); regular_timer_outer--){
                    _no_operation();
                }
    
        if (!(regular_timer_outer > 0)){
            drv_error_vector |= SPI_BUSY;
        }
        USCI_B_SPI_transmitData (USCI_B2_BASE, drv_tx_buff[drv_tx_pos]);
        //Turn on RX interrupt
        USCI_B_SPI_enableInterrupt (USCI_B2_BASE, USCI_B_SPI_RECEIVE_INTERRUPT);
        //Turn on TX interrupt
        USCI_B_SPI_enableInterrupt (USCI_B2_BASE, USCI_B_SPI_TRANSMIT_INTERRUPT);
    
        for(regular_timer_outer = 0xFFFF; (regular_timer_outer > 0) && ((drv_tx_pos < 2) || (drv_rx_pos < 2)); regular_timer_outer--){
            _no_operation();
        }
        if (!(regular_timer_outer > 0)){
            if(drv_tx_pos < 2){
                drv_error_vector |= TX_TIMEOUT;
            }
            if(drv_rx_pos < 2){
                drv_error_vector |= RX_TIMEOUT;
            }
        }
    
        //Turn off RX interrupt
        USCI_B_SPI_disableInterrupt (USCI_B2_BASE, USCI_B_SPI_RECEIVE_INTERRUPT);
        //Turn off TX interrupt
        USCI_B_SPI_disableInterrupt (USCI_B2_BASE, USCI_B_SPI_TRANSMIT_INTERRUPT);
    
        uint16_t temp = 0;
        temp = drv_rx_buff[0];
        temp <<= 8;
        temp |= drv_rx_buff[1];
    
        return temp;
    
    
    }

    Though now I'm always getting 0's in my RX. But, that's probably a different enough issue I should close this thread and ask a followup question in a new one once I've spent a few hours testing things myself. Thanks for the help!

  • Thanks Bruce for helping solve the problem.