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.

MSP430FR5962: DMA SPI B1 Tx and Rx

Part Number: MSP430FR5962

A follow up question.

If I am using SPI B1, accordingly to table 6-11 (SLASE54C.pdf), dma trigger 18 and 19 shall be used. But both are placed on CHANNEL 3..

Does this mean I cannot run a DMA on both RX and TX, as DMA CHANNEL3 shall be used for both?

  • One more question.

    Does the CPU really stops until all DMA transfers are done?

  • Sure looks that way. I hadn't noticed that conflict before. Do you have the option of switching to UCB0 or one of the UCA-s?

    The CPU is stalled for 2 clocks for each transfer. It lets the CPU run between transfers (bytes). [Ref User Guide Sec 11.2.4] So for each byte (16 MCLKs) the DMA will stall the CPU for (2+2) MCLKs, slowing your program's progress by 4/16=25%. 

  • Thanks again, you are really helpful :)

    I will try to change the port, however last question. Is it possible to run a SPI DMA Rx only as master on B1? Or will the SPI never start because nothing is initiating the SPI clock (which a transmission would do)? I can properly get my hardware to work if the answer is yes :) 

  • Yes, the master has to write to TXBUF to get the SPI to run. You can run the Tx repeating a single byte (no-increment mode), but some entity has to do the TXBUF write.

    One option I've toyed with but never really done is to use a timer to feed TXBUF (one of DMA triggers 1-9). You would tune the timer to trigger every byte time and copy to TXBUF from <anywhere -- copying RXBUF might make a useful scope debug>. There's no Tx flow control with this setup, but the SPI is (joyously) simple and deterministic.

    The DMA resets the trigger source -- independent of anything the register read/write does -- so you can get repeated triggers. With both the timer and the SPI running from the same clock (SMCLK) there should exist an ideal timer count (something like 16) which will match with the SPI data rate (i.e. no gaps).

    There may be some reason why this wouldn't work, but given that you already have most of the infrastructure I think it's only maybe 10 lines of additional code to try it. I'm not sure I have a suitable slave (sends something interesting without a command of some sort) in my gizmo box.

  • My neuron finally fired, and I realized I could use the host as the slave (loopback). I set up the simple case (there are variants) and it seems to function as expected. 

    [Edit: I forgot to mention: This was run on an FR5994 Launchpad (Rev 1.2), Rev C device]

    This is with TA2CCR0=20:

    This is with TA2CCR0=16 (I'm not sure what that initial gap means):

    Here's the program I used:

    #include <msp430.h>
    #include <stdint.h>
    
    #define HZ          1000000UL   // roughly
    #define GAP         4
    #define XSIZE       16          // transaction size
    uint8_t Tx[XSIZE], Rx[XSIZE];
    #define NOP()   do {__no_operation();} while (0)
    int main(void)
    {
        uint16_t i;
    	WDTCTL = WDTPW | WDTHOLD;	// stop watchdog timer
    	P1OUT &= ~(BIT0|BIT1);
    	P1DIR |= (BIT0|BIT1);       // Everyone needs LEDs
    	PM5CTL0 &= ~LOCKLPM5;       // Engage GPIOs
    	for (i = 0 ; i < XSIZE ; ++i)
    	{
    	    Tx[i] = i;
    	    Rx[i] = ~i;         // Poison
    	}
    	//  Master, MSb-first, SMCLK, 3-wire, sync
    	UCB1CTLW0 = UCMST|UCMSB|UCSSEL_2|UCMODE_0|UCSYNC|UCSWRST;
    	UCB1BRW = 2;
    	UCB1CTLW0 &= ~UCSWRST;
    	P5SEL0 |= (BIT0|BIT1|BIT2);     // P5.0-2 as UCB1SIMO/SOMI/CLK (DS table 6-31
    
    	//  Trigger Rx side on UCB1RXIFG, Tx side on TA2CCR0
    	DMACTL1 = DMA3TSEL__UCB1RXIFG|DMA2TSEL__TA2CCR0; // per SLASE54C Table 6-11
    
    	//  SPI Rx side: single, byte(s), increment dest, not source.
        __data20_write_long((uintptr_t)&DMA3SA, (uintptr_t)&UCB1RXBUF); // __SFR_FARPTR nonsense
        __data20_write_long((uintptr_t)&DMA3DA, (uintptr_t)&Rx[0]);     // __SFR_FARPTR nonsense
        DMA3SZ = XSIZE;
        DMA3CTL = DMADT_0 | DMADSTINCR_3 | DMASRCINCR_0 | DMADSTBYTE | DMASRCBYTE ;
    
        //  Timer+SPI Tx side: single, bytes(s), increment source, not dest
        __data20_write_long((uintptr_t)&DMA2SA, (uintptr_t)&Tx[0]);     // __SFR_FARPTR nonsense
        __data20_write_long((uintptr_t)&DMA2DA, (uintptr_t)&UCB1TXBUF); // __SFR_FARPTR nonsense
        DMA2SZ = XSIZE;
        DMA2CTL = DMADT_0 | DMADSTINCR_0 | DMASRCINCR_3 | DMADSTBYTE | DMASRCBYTE ;
    
        //  Timer to trigger once per byte time
        TA2CCR0 = (2*8)+GAP-1;                  // BR=2*8 bits, plus gap
        TA2CCTL0 = (0*CCIFG);                   // Keep it neat
        TA2CTL = TASSEL_2 | ID_0 | MC_0 | TACLR; // SMCLK/1 [,Up RSN] [,Clear]
    	while (1)
    	{
            P1OUT ^= BIT0;
    	    DMA3CTL |= DMAEN;                   // Rx side first
    	    DMA2CTL |= DMAEN;                   // Tx side
    	    TA2CCTL0 = (0*CCIFG);               // Keep it neat
    	    TA2CTL |= MC_1 | TACLR;             // Up mode
    	    while (DMA3CTL & DMAEN) NOP();      // Wait for completion
    	    TA2CTL &= ~MC;                      // Stop timer
    	    P1OUT ^= (BIT0|BIT1);               // P1.0 duration, P1.1 heartbeat
    	    __delay_cycles(HZ/2);
    	}
    	/*NOTREACHED*/
    	return 0;
    }
    

  • Very elegant Bruce!

    If I understand your setup correct, you let the timer DMA fire exactly at the same time it takes to clock a byte out at the SPI. When it fires it take the source TX byte and put it into the SPI Tx.

    By using the timer dma, you actually generates a virtual "SPI DMA TX" channel, and can use B1 both as Tx and Rx.

    This is exactly what I need, without changing my hardware.

    Thank you very much:)

**Attention** This is a public forum