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.

How to use dma0 to trigger dma1?

Other Parts Discussed in Thread: MSP430F2618

I'm using MSP430F2618 to build a sensing pipeline. TimerB periodically triggers the ADC sampling. Once each ADC sample is obtained, the ADC12IFG triggers DMA0 to transfer the ADC values to a RAM location. Once the DMA0 transfer is done, the DMA0 should trigger another DMA transfer (DMA1) to transfer the RAM data to UART. However, the last step, where the DMA0 triggers the DMA1 transfer fails. I spent long time on debugging and still cannot figure out the bug. Can you guys help figure out the possible reason? Attached are my codes.

#include "msp430x26x.h"

void Record(void);

volatile char buf[2000];

int main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

// voltage sampling configuration.
P4DIR |= BIT3;
P4OUT |= BIT3;

// config uart
P3SEL |= BIT6 + BIT7; // P3.6,7 = USART1 TXD/RXD
UCA1CTL1 = UCSSEL_1; // ACLK
UCA1BR0 = 0x01; // 32768Hz 9600 32k/9600=3.41
UCA1BR1 = 0x00;
UCA1CTL1 &= ~UCSWRST; // **Initialize USCI state machine**

// config adc
P6SEL |= BIT4;
ADC12MCTL4 = SREF_0 + INCH_4; // Channel A4, Vref+
ADC12IFG = 0;

ADC12CTL1 = CSTARTADD_4 + SHS_3 + CONSEQ_2; // S&H TB.OUT1, rep. single chan
ADC12CTL0 = ADC12ON+SHT0_0; // VRef ADC12 on, enabled

// config dma0 - adc to ram
__data16_write_addr((unsigned short) &DMA0SA,(unsigned long) &ADC12MEM4);
__data16_write_addr((unsigned short) &DMA0DA,(unsigned long) &buf);
DMA0SZ = 1; // words
DMACTL0 = DMA0TSEL_6; // ADC12IFGx triggers DMA0
DMA0CTL = DMADT_4 + DMADSTINCR_3 + DMAIE + DMAEN; // Config

// config dma1 - adc to ram
__data16_write_addr((unsigned short) &DMA1SA,(unsigned long) &buf);
__data16_write_addr((unsigned short) &DMA1DA,(unsigned long) &UCA1TXBUF);
DMA1SZ = 3;
DMACTL0 |= DMA1TSEL_14;
DMA1CTL = DMADT_5 + DMASRCINCR_3 + DMASBDB + DMAEN; // Config

// turn on adc
ADC12CTL1 |= CONSEQ_2;
ADC12CTL0 |= ENC;

// turn on timer
TBCCR0 = 30000; // Init TBCCR0 w/ sample prd
TBCCR1 = 70; // Trigger for ADC12 SC
TBCCTL1 = OUTMOD_7; // Reset OUT1 on EQU1, set on EQU0

TBCTL = TBSSEL_1 + MC_1 + TBCLR; // SMCLK, clear TBR, up mode
__bis_SR_register(LPM0_bits + GIE);

while(1)
{
P1OUT |= DEBUG_1_7;
P1OUT &= ~DEBUG_1_7;
}
}

void Record(void)
{
// turn on adc
ADC12CTL1 |= CONSEQ_2;
ADC12CTL0 |= ENC;

// turn on timer
TBCCR0 = 3000; // Init TBCCR0 w/ sample prd
TBCCR1 = 70; // Trigger for ADC12 SC
TBCCTL1 = OUTMOD_7; // Reset OUT1 on EQU1, set on EQU0

TBCTL = TBSSEL_1 + MC_1 + TBCLR; // SMCLK, clear TBR, up mode
__bis_SR_register(LPM0_bits + GIE); // Enter LPM0, enable interrupts
}

#pragma vector = DMA_VECTOR
__interrupt void DMA_ISR(void)
{
//ADC12CTL1 &= ~CONSEQ_2; // Stop conversion immediately
//ADC12CTL0 &= ~ENC; // Disable ADC12 conversion
//TBCTL = 0; // Disable Timer_B

DMA0CTL &= ~DMAIFG; // Clear DMA0 interrupt flag

//P1OUT |= DEBUG_1_7;
//P1OUT &= ~DEBUG_1_7;

//__bic_SR_register_on_exit(LPM0_bits); // Exit LPMx, interrupts enabled
}

  • Pengyu Zhang said:
    DMA0CTL = DMADT_4 + DMADSTINCR_3 + DMAIE + DMAEN; // Config

    Setting DMAIE in DMA0CTL prevents DMA0 from triggering DMA1. You can either trigger DMA or ISR with the IFG bit, but not both.

    You could set DMAIE for channel 1, so your ISR is called when the data has been moved to TXBUF.
    But this won't work anyway: Your second DMA will start moving three(why that?) bytes to TXBUF as soon as DMA0 is done. However, this will move the three bytes to TXBUF within 8 MCLK cycles. So unless your UART baudrate is by a factor >5 higher than MCLK, this won't work. The DMA doesn't wait until the last byte has been sent.
    Unfortunately, you cannot start a DMA transfer on other channel completion and trigger subsequent transfers by TXIFG bit. Combined triggers aren't available.
    So you can revert the code to triggering an ADC ISR, adn in thsi ISR, you copy the ADC result to buffer and enable DMA transfer to TXBUF. The DMA for the UART TX is then triggered by TXIFG.
    You might need to write the first byte to TXBUF manually and only transfer the remaining two bytes by DMA, as TXIFG is already set a this point and level triggers are only allowed for external DMAE0 signal.

    BTW: sending bursts of two bytes could be done, assuming that USCI clock is >=MCLK and TXBUF is empty for sure, when the DMA starts. In this case, the byte written by first DMA transfer has already moved on to the output shift when the DMA copies the second byte to TXBUF two MCLK ticks later.

  • Thanks Jens-Michael. Basically we want to use a timer to periodically sample a sensor and dma the sensor data to the uart which sends the data out. We want to implement this sensing-transmission pipeline when the micro controller is always in sleep mode. That says 1) we do not want to wakeup the micro controller, and 2) we want to reduce the opportunities of using ISR because the code within the ISR also consumes lots of power. It would be great if you can comment on how to implement this pipeline.

    I modified the codes according to your comments. Now I can see one byte transmitted by the uart each time. Unfortunately, I cannot see the 2 bytes sensor data sampled by ADC. Can you give me further comments on the revised codes? Thanks.

    #include "msp430x26x.h"

    void Record(void);

    volatile char buf[2000];

    int main(void)
    {
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

    DRIVE_ALL_PINS;

    P4OUT |= VSENSE_POWER;

    // config uart
    P3SEL |= BIT6 + BIT7; // P3.6,7 = USART1 TXD/RXD
    UCA1CTL1 = UCSSEL_1; // ACLK
    UCA1BR0 = 0x01; // 32768Hz 9600 32k/9600=3.41
    UCA1BR1 = 0x00;
    UCA1CTL1 &= ~UCSWRST; // **Initialize USCI state machine**

    // config adc
    P6SEL |= BIT4;
    ADC12MCTL4 = SREF_0 + INCH_4; // Channel A4, Vref+
    ADC12IFG = 0;

    ADC12CTL1 = CSTARTADD_4 + SHS_3 + CONSEQ_2 + ADC12SSEL_1; // S&H TB.OUT1, rep. single chan
    ADC12CTL0 = ADC12ON+SHT0_0; // VRef ADC12 on, enabled

    // config dma0 - adc to ram
    __data16_write_addr((unsigned short) &DMA0SA,(unsigned long) &ADC12MEM4);
    __data16_write_addr((unsigned short) &DMA0DA,(unsigned long) &buf);
    DMA0SZ = 1; // words
    DMACTL0 = DMA0TSEL_6; // ADC12IFGx triggers DMA0
    DMA0CTL = DMADT_4 + DMADSTINCR_3 + DMAEN; // Config

    // config dma1 - adc to ram
    __data16_write_addr((unsigned short) &DMA1SA,(unsigned long) &buf);
    __data16_write_addr((unsigned short) &DMA1DA,(unsigned long) &UCA1TXBUF);
    DMA1SZ = 2;
    DMACTL0 |= DMA1TSEL_14;
    DMA1CTL = DMADT_5 + DMASRCINCR_3 + DMASBDB + DMAIE + DMAEN; // Config

    // turn on adc
    ADC12CTL1 |= CONSEQ_2;
    ADC12CTL0 |= ENC;

    // turn on timer
    TBCCR0 = 30000; // Init TBCCR0 w/ sample prd
    TBCCR1 = 70; // Trigger for ADC12 SC
    TBCCTL1 = OUTMOD_7; // Reset OUT1 on EQU1, set on EQU0

    TBCTL = TBSSEL_1 + MC_1 + TBCLR; // SMCLK, clear TBR, up mode
    __bis_SR_register(LPM3_bits + GIE);

    while(1)
    {
    P1OUT |= DEBUG_1_7;
    P1OUT &= ~DEBUG_1_7;
    }
    }

    #pragma vector = DMA_VECTOR
    __interrupt void DMA_ISR(void)
    {
    //ADC12CTL1 &= ~CONSEQ_2; // Stop conversion immediately
    //ADC12CTL0 &= ~ENC; // Disable ADC12 conversion
    //TBCTL = 0; // Disable Timer_B

    DMA0CTL &= ~DMAIFG;
    DMA1CTL &= ~DMAIFG; // Clear DMA0 interrupt flag

    //P1OUT |= DEBUG_1_7;
    //P1OUT &= ~DEBUG_1_7;

    //__bic_SR_register_on_exit(LPM0_bits); // Exit LPMx, interrupts enabled
    }

  • Pengyu Zhang said:
    UCA1BR0 = 0x01; // 32768Hz 9600 32k/9600=3.41

    Shouldn't there be an 0x03 then instead of 0x01?

    However, I said that for the double-byte burst method, it is mandatory that the MCLK is not higher than the USCI clock (apparently 32768Hz in your case). So that it is ensured that at least one USCI clock cycle has passed (and moved the first byte from TXBUF to the output shift register) before the DMA does the second transfer.
    Well, you can switch MCLK to XT1. This will also allow you to switch the DCO off completely and it isn't even needed for driving the DMA. But sure it makes executing your ISR quite slow.

  • Hi Jens-Michael,

    Is it possible to run the adc-dma-uart pipeline in LPM3 mode without waking up the MCU? Each module (adc, dma, or uart) can be run in LPM3 mode. However, I cannot run the whole pipeline in LPM3 mode...

    The problem is that the adc generates 1 word data for each sample. However, the size of the uart tx buffer is only 1 byte. So I cannot use adc to trigger the dma transfer directly. The dma will send 1 word data directly to the uart tx buffer. Do you have any idea on how to implement this pipeline? Thanks.

    Pengyu

  • Pengyu Zhang said:
    The problem is that the adc generates 1 word data for each sample. However, the size of the uart tx buffer is only 1 byte. So I cannot use adc to trigger the dma transfer directly. The dma will send 1 word data directly to the uart tx buffer. Do you have any idea on how to implement this pipeline? Thanks.

    I have idea, but honestly don't know if it will work. You shall run ADC conversion start from timer which is tuned precisely to 2byte transmission time over uart. One DMA places ADC data into circular buffer, 2nd DMA fetch data from same circular buffer going slightly behind 1st DMA by 2..4 bytes. You must ensure that ADC data comes into buffer at precise rate so none of DMA will "run away" from another. Thou I am not sure if DMA'ed uart does not do any unexpected bit slips.. if it does, then whole concept fails.

  • Ilmars said:
    have idea, but honestly don't know if it will work.

    That's like what I proposed in my first post.

    It should work if the USCI clock is >= MCLK, so the USCI will start sending the first byte before the DMA moves the second byte in. You only need a 2-byte buffer and the ADC conversion time must be higher (not precisely timed) than the required transfer time for two bytes.
    One DMA copies the value from ADC12MEMx to the buffer (repeated single transfer, triggered by ADC12IFG) and triggers the second DMA, which does a block transfer from the buffer to TXBUF.

    If ADC12MEMx could be read byte-wise, one DMA channel would be enough (no need of a buffer), but it seems the ADC12MEM registers are only accessible in word mode. (in 5x family, this restriction has AFAIK been removed, but I never tried - the DMA registers are still word-access only)

  • I come up another way to implement the pipeline, although it only works when the MCU runs at 1MHz.

    When a ADC reading is done, ADC12IFG triggers DMA0 to copy the ADC data (2 bytes) info a RAM buffer using word transfer. Once the DMA0 transfer is done, DMA0IFG triggers DMA1 to copy the first byte in the RAM buffer to UART. When DMA1 transfer is done, DMA1IFG triggers DMA2 to copy the second byte in the RAM buffer to UART. As long as the data in DMA1 and DMA2 do not overlap, this pipeline should work. Attached is my implementation of this pipeline.

    However, this implementation works only when the MCU runs MCLK/SMCLK at 1MHz or higher. When I put the MCU into LPM3 mode, one byte of the DMA transfer is dropped or over written. I guess the trouble comes up when the UART clock rate is low. In this case, the first byte UART does not finish when the second byte is copied to the UART TX buffer.

    Any idea on how to solve this problem?

    Can you give an example to run the UART with external clock UCLK? Maybe I can use an external clock to drive the UART and still put the MCU in deep sleep mode.

    #include "msp430x26x.h"

    volatile char buf[2000];

    int main(void)
    {
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

    DRIVE_ALL_PINS;

    P4OUT |= VSENSE_POWER;

    // config uart
    P3SEL |= BIT6 + BIT7; // P3.6,7 = USART1 TXD/RXD
    UCA1CTL1 = UCSSEL_3; // ACLK
    UCA1BR0 = 0x01; // 32768Hz 9600 32k/9600=3.41
    UCA1BR1 = 0x00;
    UCA1CTL1 &= ~UCSWRST; // **Initialize USCI state machine**

    // config adc
    P6SEL |= BIT4;
    ADC12MCTL4 = SREF_0 + INCH_4; // Channel A4, Vref+
    ADC12IFG = 0;

    ADC12CTL1 = CSTARTADD_4 + SHS_3 + CONSEQ_2 + ADC12SSEL_1; // S&H TB.OUT1, rep. single chan
    ADC12CTL0 = ADC12ON+SHT0_0; // VRef ADC12 on, enabled

    // config dma0 - adc to ram
    __data16_write_addr((unsigned short) &DMA0SA,(unsigned long) &ADC12MEM4);
    __data16_write_addr((unsigned short) &DMA0DA,(unsigned long) &buf);
    DMA0SZ = 1; // words
    DMACTL0 = DMA0TSEL_6; // ADC12IFGx triggers DMA0
    DMA0CTL = DMADT_4 + DMADSTINCR_3 + DMAEN; // Config

    // config dma1 - ram to uart
    __data16_write_addr((unsigned short) &DMA1SA,(unsigned long) &buf);
    __data16_write_addr((unsigned short) &DMA1DA,(unsigned long) &UCA1TXBUF);
    DMA1SZ = 1;
    DMACTL0 |= DMA1TSEL_14;
    //DMA1CTL = DMADT_5 + DMASRCINCR_3 + DMASWDB + DMAIE + DMAEN; // Config
    DMA1CTL = DMADT_4 + DMASBDB + DMAEN;

    // config dma2 - ram to uart
    __data16_write_addr((unsigned short) &DMA2SA,(unsigned long) &buf[1]);
    __data16_write_addr((unsigned short) &DMA2DA,(unsigned long) &UCA1TXBUF);
    DMA2SZ = 1;
    DMACTL0 |= DMA2TSEL_14;
    DMA2CTL = DMADT_4 + DMASBDB + DMAIE + DMAEN;

    // turn on adc
    ADC12CTL1 |= CONSEQ_2;
    ADC12CTL0 |= ENC;

    // turn on timer
    TBCCR0 = 30000; // Init TBCCR0 w/ sample prd
    TBCCR1 = 70; // Trigger for ADC12 SC
    TBCCTL1 = OUTMOD_7; // Reset OUT1 on EQU1, set on EQU0

    TBCTL = TBSSEL_1 + MC_1 + TBCLR; // ACLK, clear TBR, up mode
    __bis_SR_register(LPM0_bits + GIE);

    while(1)
    {
    P1OUT |= DEBUG_1_7;
    P1OUT &= ~DEBUG_1_7;
    }
    }

    #pragma vector = DMA_VECTOR
    __interrupt void DMA_ISR(void)
    {
    DMA0CTL &= ~DMAIFG;
    DMA1CTL &= ~DMAIFG;
    DMA2CTL &= ~DMAIFG;
    }

  • Is it possible to delay the DMA2 transfer a bit after DMA2 gets DMA1IFG?

  • Pengyu Zhang said:
    I guess the trouble comes up when the UART clock rate is low. In this case, the first byte UART does not finish when the second byte is copied to the UART TX buffer.

    Any idea on how to solve this problem?

    Eight before going into LPM, you could set a bigger MCLK divider, so MCLK is slower than the clock used for the UART.

    Pengyu Zhang said:
    Is it possible to delay the DMA2 transfer a bit after DMA2 gets DMA1IFG?

    There is no way to add a delay to the DMA once it got triggered.
    You could add a delay by using another DMA channel that starts a timer which triggers the final transfer

    Or again you can lower MCLK, so the DMA is carried out slower compared with the other module clocks.

**Attention** This is a public forum