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.

MSP-EXP430FR5994: SPI DMA BQ79600EVM

Part Number: MSP-EXP430FR5994
Other Parts Discussed in Thread: MSP430FR5994

I am trying to incorporate use of DMA with the MSP-EXP430FR5994-BQ79600EVM SPI. Though the BQ79600 operates at 2MHz clock, I am seeing the need to use SPI Rx interrupts for successful auto addressing. The hope is the use of DMA instead of the Rx interrupt is a less stressful setup for the MSP430. The byte I have been using the content of this thread to get started:

MSP430FR5994: DMA not triggered by a I2C interrupt - MSP low-power microcontroller forum - MSP low-power microcontrollers - TI E2E support forums

Here are my initial questions:

  1. Can I set the DMA channel 3 to trigger only on UCB1RXIFG? Will UCB1TXIFG use the DMA or only SPI receive is possible with DMA because of the trigger selection? I have pasted SPI_Init, SPI transmit, and SPI receive functions below. Is the correct value for DMA3SZ 1? Or does it have to be same as blocksize?
  2.  Is the correct value for DMA3SZ 1? Or does it have to be same as blocksize?
  3.  Is the correct value for DMA3SZ 1? Or does it have to be same as blocksize? 
  4. I don’t understand the purpose or how to use dma_value.
  5. If anything else in DMA_Init is amiss, do let me know.

Thanks

void DMA_Init(){
    (uintptr_t) &DMA3SA = (uintptr_t) &UCB1RXBUF;
    (uintptr_t) &DMA3DA = (uintptr_t) &dma_value;


    DMA3CTL &= ~DMAEN;
    DMACTL1 |= DMA3TSEL__UCB1RXIFG;
    DMA3CTL |= DMAARMDIS;
    DMA3CTL &= ~DMA3CTL;

    DMA3SZ = 1;

    DMA3CTL |= DMADT_4|DMADSTINCR_3|DMASRCINCR_0|DMASRCBYTE__BYTE|DMADSTBYTE__BYTE|DMA_TRIGGER_RISINGEDGE|DMAEN;


}

void SPI_Init()
{
    //BQ79600 SPI1_B1
    UCB1CTLW0 = UCSWRST;                                 // **Put state machine in reset**
    UCB1CTLW0 |=  UCCKPH | UCCKPL_0 | UCMSB | UCSYNC
                | UCMST | UCSSEL__SMCLK;                 // 3-pin, 8-bit MAB SPI master SMCLK=1M
    UCB1BRW = 0x00;                                      //UCB1CLK=SMCLK=1M
    UCB1CTLW0 &= ~UCSWRST;                               // **Initialize USCI state machine**
}
void spiTransmitData(uint32_t blocksize)
{
    TransmitIndex=0;

    P4OUT  = ~BIT4;
    while(blocksize != 0U)
    {
        UCB1TXBUF=spiFrame[TransmitIndex];
        while((UCB1STAT&UCBUSY));
        dummy=UCB1RXBUF;
        TransmitIndex++;
        blocksize--;
    }
    P4OUT  = BIT4;
    return;
}
void spiTransmitAndReceiveData(uint32_t blocksize,uint16_t * destbuff)
{
    ReceiveIndex = 0;

    P4OUT  &= ~BIT4;
    while(blocksize != 0U)
    {
        UCB1TXBUF=dummy;
        while((UCB1STAT&UCBUSY));
        destbuff[ReceiveIndex]=UCB1RXBUF;
        ReceiveIndex++;
        blocksize--;
    }
    P4OUT |= BIT4;

    return;
}


  • Hi Priya,

    What happens when you run this code? I would like to know if you are seeing the expected results.

    Answers to your questions:

    1. Yes. I'm not sure I understand the second part of your questions. You may be able to have the DMA respond to UCB1RX1FG and also trigger an interrupt.

    2/3. I believe DMA3SZ1 = 1 is what you want

    4. DMA3DA is the destination address. This is were the DMA will place the data from the SPI RX

    5. Why are clearing DMA3CTL on line 9.

  • Thank you for your response.

    Please confirm my understanding of the following:

    1. The DMA Channel 3 can be set up only for one trigger correct either the DMA3TSEL__UCB1RXIFG OR DMA3TSEL__UCB1TXIFG. The MSP430FR5994/5962 has the UCB1 available only on one DMA channel.

    2. Setting DMA3SZ1 = 1 will take care of the entire blocksize, even if blocksize  varies with each transfer.

    3.  Do I need to set up a DMA ISR and have the following executed at the DMAIV_DMA3IFG? 

    DMA3CTL &= ~DMAEN;

    DMA3CTL |= DMAARMDIS;
    //DMA3CTL &= ~DMA3CTL;

    DMA3SZ = 1;

    DMA3CTL |= DMADT_4|DMADSTINCR_3|DMASRCINCR_0|DMASRCBYTE__BYTE|DMADSTBYTE__BYTE|DMA_TRIGGER_RISINGEDGE|DMAEN;

    This will replace the spiTransmitAndReceiveData function, correct? I need to include the SPI chip select control with this.

    4. The DMAInit function will be:

    (uintptr_t) &DMA3SA = (uintptr_t) &UCB1RXBUF;
    (uintptr_t) &DMA3DA = (uintptr_t) &dma_value;

    DMACTL1 |= DMA3TSEL__UCB1RXIFG;

    I am yet to run this code. I am trying to understand it first.

    I appreciate your timely reply.

  • Thanks for you clarification.

    1. Yes. If you are trying use DMA for both TX and RX you will not be able to use UCB1 however, UCB0 should be ok.

    2. the SPI RX will receive one byte at a time therefore DMA3SZ = 1

    3. No, the code you list below is only needed for configuration of the DMA it does not need to be run for each ISR

    4. No, see my answer 3 above. You should have the DMA configuration code in you DMAInit function.

    It may be worth considering if you need DMA in both directions for your design. If you don't plan to transfer too much data you may only need DMA on the RX side or you may not need DMA at all.

  • Apart from configuring the DMA in the DMA_Init, will there be changes needed to the spiTransmitAndReceiveData function? (function pasted below). Is there a need for a DMA_Isr? What will the DMA ISR process? There is more transfer happening in the SPI receive direction than the transmit due to the stack of BQ devices sending back measurement data, could be max 128-192 bytes. Compared to SPI Rx interrupt for each byte, will the DMA offer an advantage? 

    void spiTransmitAndReceiveData(uint32_t blocksize,uint16_t * destbuff)
    {
        ReceiveIndex = 0;
    
        P4OUT  &= ~BIT4;
        while(blocksize != 0U)
        {
            UCB1TXBUF=dummy;
            while((UCB1STAT&UCBUSY));
            destbuff[ReceiveIndex]=UCB1RXBUF;
            ReceiveIndex++;
            blocksize--;
        }
        P4OUT |= BIT4;
    
        return;
    }

  • DMASZ =1 is a bad idea.

    The DMA controller will generate an interrupt after each transfer, adding a lot of extra load. Which is what you are trying to eliminate by using DMA. Worse, the temporary address registers are reloaded from DMASA and DMADA. Meaning that all of the data read gets stored in a single location.

    If you want to receive multiple bytes of data, set DMASZ to that count.

  • I tried my first run of this code. A whole bunch of 0xFFs. DMA interrupt is not firing.

    void DMA_Init(){
        __data20_write_long((uintptr_t)&DMA3SA, (uintptr_t)&UCB1RXBUF);
        __data20_write_long((uintptr_t)&DMA3DA, (uintptr_t)&dma_value);
    
    
        DMA3CTL &= ~DMAEN;
        DMACTL1 |= DMA3TSEL__UCB1RXIFG;
        DMA3CTL |= DMARMWDIS;
    
        DMA3SZ = MAXBYTES;
    
        DMA3CTL |= DMADT_4|DMADSTINCR_3|DMASRCINCR_0|DMASRCBYTE__BYTE|DMADSTBYTE__BYTE|DMA_TRIGGER_RISINGEDGE|DMAEN;
    
    
    }
    /*******************************************************************************
      SPI_Init *********************************************************************
     *******************************************************************************
    * Function: Initialize SPI_B1andA3
    ********************************************************************************/
    void SPI_Init()
    {
        //BQ79600 SPI1_B1
        UCB1CTLW0 = UCSWRST;                                 // **Put state machine in reset**
        UCB1CTLW0 |=  UCCKPH | UCCKPL_0 | UCMSB | UCSYNC
                    | UCMST | UCSSEL__SMCLK;                 // 3-pin, 8-bit MAB SPI master SMCLK=1M
        UCB1BRW = 0x00;                                      //UCB1CLK=SMCLK=1M
        UCB1CTLW0 &= ~UCSWRST;                               // **Initialize USCI state machine**
    //    UCB1IE |= UCRXIE;                                    // Enable USCI0 RX interrupt
    
    }
    
    void spiTransmitAndReceiveData(uint32_t blocksize,uint16_t * destbuff)
    {
        ReceiveIndex = 0;
    
        P4OUT  &= ~BIT4;
        DMA3CTL |= DMAIE;
    
        while(dmaDone == 0);
        memcpy(destbuff, dma_value, blocksize);
        dmaDone = 0;
    
        return;
    }
    
    #pragma vector=DMA_VECTOR
    __interrupt void dmaIsrHandler(void)
    {
        switch(__even_in_range(DMAIV, DMAIV_DMA3IFG))
        {
        case DMAIV_DMA3IFG:
            DMA3CTL |= DMAEN;
            P4OUT |= BIT4;
            DMA3CTL &= ~(DMAIE);
            DMA3CTL &= ~DMAEN;
            dmaDone = 1;
            // Exit low power mode on wake-up
            __bic_SR_register_on_exit(LPM4_bits);
            break;
        case DMAIV_DMA0IFG:
            break;
    
        case DMAIV_DMA1IFG:
            break;
    
        case DMAIV_DMA2IFG:
            break;
    
        case DMAIV_DMA4IFG:
            break;
    
        case DMAIV_DMA5IFG:
            break;
    
        default: break;
        }
    }

  • A whole bunch of 0xFFs. I was thinking the transfer should start off with one interrupt and run through to completion there after.

    What do you mean by start with one interrupt? Which interrupt are you referring to?

    A couple of things I noticed:

    1. On line 8 in your last post you are setting DMARMWDIS in DMA3CTL, however DMARMWDIS is a field in DMACTL4. You are in advertently enabling interrupts with this line (which you probably want anyway, so it's probably not a problem, but you should make it correct)

    2. DMA_TRIGGER_RISINGEDGE isn't defined in my environment, not sure if you've defined in yours

    3. You haven't shown your DMA ISR. Do you have one? Without an ISR the CPU is probably going to be sent to an ISR trap.

    4. If you transfer is shorter than MAXBYTES, DMA won't generate an interrupt. It will only generate an interrupt when it has received MAX_BYTES.

    Regards,

    Evan

  • Here is the code with recommended edits. If I set DMASZ=1, the DMA interrupt finally triggers. I don't know how best to capture end of SPI receive because the blocksize varies with different transfers. I can see the byteCnt is incrementing, but dma_value is not gathering the bytes in different locations; first element is FF and rest are 0.

    Edited to add dma.h has the following:

    //*****************************************************************************
    //
    // The following are values that can be passed to the triggerTypeSelect
    // parameter for functions: DMA_init(); the param parameter for functions:
    // DMA_init().
    //
    //*****************************************************************************
    #define DMA_TRIGGER_RISINGEDGE (!(DMALEVEL))
    #define DMA_TRIGGER_HIGH (DMALEVEL)

    void DMA_Init(){
        __data20_write_long((uintptr_t)&DMA3SA, (uintptr_t)&UCB1RXBUF);
        __data20_write_long((uintptr_t)&DMA3DA, (uintptr_t)&dma_value);
    
    
        DMA3CTL &= ~DMAEN;
        DMACTL1 |= DMA3TSEL__UCB1RXIFG;
        DMACTL4 |= DMARMWDIS;
    
        DMA3SZ = 1;
    
        DMA3CTL |= DMADT_4|DMADSTINCR_3|DMASRCINCR_0|DMASRCBYTE__BYTE|DMADSTBYTE__BYTE|DMA_TRIGGER_RISINGEDGE|DMAEN;
    
    
    }
    /*******************************************************************************
      SPI_Init *********************************************************************
     *******************************************************************************
    * Function: Initialize SPI_B1andA3
    ********************************************************************************/
    void SPI_Init()
    {
        //BQ79600 SPI1_B1
        UCB1CTLW0 = UCSWRST;                                 // **Put state machine in reset**
        UCB1CTLW0 |=  UCCKPH | UCCKPL_0 | UCMSB | UCSYNC
                    | UCMST | UCSSEL__SMCLK;                 // 3-pin, 8-bit MAB SPI master SMCLK=1M
        UCB1BRW = 0x00;                                      //UCB1CLK=SMCLK=1M
        UCB1CTLW0 &= ~UCSWRST;                               // **Initialize USCI state machine**
    }
    
    /*******************************************************************************
      spiTransmitAndReceiveData ****************************************************
     *******************************************************************************
    * blocksize:   length of data.
    * destbuff:    data
    * ******************************************************************************
    * Function: SPI receive data
    ********************************************************************************/
    void spiTransmitAndReceiveData(uint32_t blocksize,uint16_t * destbuff)
    {
    
        P4OUT  &= ~BIT4;
        byteCnt = 0;
        DMA3CTL |= DMAEN;
        DMA3CTL |= DMAIE;
    
    
        if (byteCnt == blocksize-1){
            DMA3CTL &= ~DMAIE;
            memcpy(destbuff, dma_value, blocksize);
            P4OUT |= BIT4;
        }
    
        return;
    }
    
    #pragma vector=DMA_VECTOR
    __interrupt void dmaIsrHandler(void)
    {
        switch(__even_in_range(DMAIV, DMAIV_DMA3IFG))
        {
        case DMAIV_DMA3IFG:
            byteCnt++;
    
            // Exit low power mode on wake-up
            __bic_SR_register_on_exit(LPM4_bits);
            break;
        case DMAIV_DMA0IFG:
            break;
    
        case DMAIV_DMA1IFG:
            break;
    
        case DMAIV_DMA2IFG:
            break;
    
        case DMAIV_DMA4IFG:
            break;
    
        case DMAIV_DMA5IFG:
            break;
    
        default: break;
        }
    }
    
    /*******************************************************************************
      spiTransmitData **************************************************************
     *******************************************************************************
    * blocksize:      length of data.
    * srcbuff :       data
    * ******************************************************************************
    * Function: transmit data
    ********************************************************************************/
    void spiTransmitData(uint32_t blocksize)
    {
        TransmitIndex=0;
    
        P4OUT  = ~BIT4;
        while(blocksize != 0U)
        {
            UCB1TXBUF=spiFrame[TransmitIndex];
            while((UCB1STAT&UCBUSY));
            dummy=UCB1RXBUF;
            TransmitIndex++;
            blocksize--;
        }
        P4OUT  = BIT4;
    
    
        return;
    }

  • Thanks for the update. As mentioned by David, DMA3SZ=1 is probably not what you want. I think the best place to start is to determine how you want the DMA to behave.

    1. Do you want it interrupt after all the RX words are received? Implications:

    a. All transfer contents will be stored in a contiguous buffer

    b. You must know the total number of words transfered to set DMAxSZ

    2. Or do you want it to interrupt after each transfer? Implications:

    a. You will have to reconfigure DMAxSZ between transfers

  • I am setting the DMASZ before starting DMA transfers. I am still getting FFs for data. Two bytes are getting written to each dma_value location as a 16 bit value.

  • Have you confirmed that the SPI RX data is not 0xFF? What are you expecting the data to be?

  • Here is an example of expected good data.

  • When using SPI without the DMA, there is a "dummy" transmit that must happen when receiving a byte. How does this work when using the DMA with SPI?

  • The SPI port will not generate the clocks required to receive data unless it has data to send. Or it is configured as a slave and something else provides the clocks. DMA or not.

  • I added a SPI Tx edge transition to the SPI receive function. I can see activity on SPI_CLK on the scope, but no meaningful SPI Rx data yet.

    Does this mean I must have DMA channels setup for both SPI Rx and SPI Tx? I am limited to UCB0

    void DMA_Init(){
        __data20_write_long((uintptr_t)&DMA3SA, (uintptr_t)&UCB1RXBUF);
        __data20_write_long((uintptr_t)&DMA3DA, (uintptr_t) dma_value);
    
    
        DMA3CTL &= ~DMAEN;
        DMACTL1 |= DMA3TSEL__UCB1RXIFG;
        DMACTL4 |= DMARMWDIS;
    
    
    
        DMA3CTL |= DMADT_0|DMADSTINCR_3|DMASRCINCR_0|DMASRCBYTE__BYTE|DMADSTBYTE__BYTE|DMA_TRIGGER_RISINGEDGE|DMAEN;
    
    
    }
    /*******************************************************************************
      SPI_Init *********************************************************************
     *******************************************************************************
    * Function: Initialize SPI_B1andA3
    ********************************************************************************/
    void SPI_Init()
    {
        //BQ79600 SPI1_B1
        UCB1CTLW0 = UCSWRST;                                 // **Put state machine in reset**
        UCB1CTLW0 |=  UCCKPH | UCCKPL_0 | UCMSB | UCSYNC
                    | UCMST | UCSSEL__SMCLK;                 // 3-pin, 8-bit MAB SPI master SMCLK=1M
        UCB1BRW = 0x00;                                      //UCB1CLK=SMCLK=1M
        UCB1CTLW0 &= ~UCSWRST;                               // **Initialize USCI state machine**
    }
    
    /*******************************************************************************
      spiTransmitAndReceiveData ****************************************************
     *******************************************************************************
    * blocksize:   length of data.
    * destbuff:    data
    * ******************************************************************************
    * Function: SPI receive data
    ********************************************************************************/
    void spiTransmitAndReceiveData(uint32_t blocksize,uint16_t * destbuff)
    {
    
        P4OUT  &= ~BIT4;
        DMA3SZ = blocksize;
        rxComplete = 0;
        DMA3CTL |= DMAEN;
        DMA3CTL |= DMAIE;
        UCB1IFG &= ~UCTXIFG;
        UCB1IFG |= UCTXIFG;
    
        return;
    }
    
    #pragma vector=DMA_VECTOR
    __interrupt void dmaIsrHandler(void)
    {
        switch(__even_in_range(DMAIV, DMAIV_DMA3IFG))
        {
        case DMAIV_DMA3IFG:
            DMA3CTL &= ~DMAIE;
            P4OUT |= BIT4;
            rxComplete = 1;
    
            // Exit low power mode on wake-up
            __bic_SR_register_on_exit(LPM4_bits);
            break;
        case DMAIV_DMA0IFG:
            break;
    
        case DMAIV_DMA1IFG:
            break;
    
        case DMAIV_DMA2IFG:
            break;
    
        case DMAIV_DMA4IFG:
            break;
    
        case DMAIV_DMA5IFG:
            break;
    
        default: break;
        }
    }

  • In order to receive [blocksize] bytes, you need to send that many bytes (0xFF if you're only receiving). Ordinarily this would be done using a second (Tx) DMA channel, but as you pointed out the DMA trigger assignments don't permit doing this for UCB1.

    So I expect you'll have to write the (dummy) Tx bytes to TXBUF (gated by TXIFG) in a loop in your program. This reduces the value of DMA somewhat for this case.

    About the Rx data you're getting (all 0xFF): Are you sending a read command somewhere? I think the device sends back 0xFF if you haven't' primed its FIFO with a read request first.

  • Yes, there are elaborate BQ SPI read commands prior to the MSP430 SPI receiving data. A read command is sent to the BQ device, it responds with a SPI_RDY signal and then the MSP430 calls its SPI receive routine.

    Yes, I have come to realize I need to limit DMA transfer size to 1 byte to transmit a dummy SPI byte with UCB1. This is no different than simply receiving on SPI Rx interrupt. Which is how the current firmware is setup. I can make a request for future hardware to use a different SPI peripheral so DMA channels can be setup for Tx and Rx. Even this setup will have a byte wise overhead. I don't have all the specifics of two DMA channels worked out.

    I have enough information gathered to comment on the use of DMA. Unless TI has a different solution. 

    Thanks

  • If you were to do this, you would still want DMAxSZ=[blocksize], for the reasons David mentioned.

    But yes, you would still have a polling loop, so you don't gain much from the DMA for this case. DMA could still be useful for Tx-only transactions (where you ignore the Rx side), but it looks like for the BQ device those frames are pretty short.

  • From the user guide SLAU367P, the DMA needs 2 MCLK cycles to transfer one byte. I am not seeing how using two DMA channels can work for synchronous SPI transmit/receive. It looks like the only option is to use one DMA channel for receive trigger, while generating a dummy tx byte for every byte in the block size. This byte overhead reduces the throughput advantage of the DMA.

  • It works because of the double buffering. The TX DMA would be triggered by TXIFG which happens when TXBUF is emptied. Which gives you 8 SPI clocks (Usually slower than MCLK) to refill it before the data in the shift register is gone and it needs the data in TXBUF again.

    The operation of the transmit and receive data requests isn't synchronous but that shouldn't matter because the data is.

**Attention** This is a public forum