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.

Can two DMA channels be triggered by same source?

Other Parts Discussed in Thread: MSP430F47186

 Hi,

 

I have to implement an SPI slave interface on MSP430F47186. For this purpose, I setup DMA1 for SPI receiving(UCB0RXIFG triggered) and DMA2 for SPI sending (UCB0TXIFG triggered).

Sending and receiving work well, I don’t have problem with it.

Then, for synchronization , I wanted to implement inter character time out functionality. I mean, the time between each received byte by SPI should not exceed timeout duration which is 10uS.

For this functionality, I setup TIMERB to generate CCR0 interrupt every 10uS. To prevent this interrupt while receiving bytes from SPI, I setup DMA0 to clear TIMERB content and trigger source of this channel also is UCB0RXIFG. In other word, it has same trigger source with DMA1.

DMA0 -> used to clear timerb counter (like a watchdog) -> triggered by UCB0RXIFG

DMA1 -> used to receive character from SPI -> triggered by UCB0RXIFG

DMA2 - > used to send character over SPI -> triggered by UCB0TXIFG

My question is, is there a design issue for my logic? DMA0 has always higher priority and, I was expecting MCU to hang because  UCB0RXIFG never get cleared after DMA0 operation. But surprisingly it works in debug mode (while debugger active). But another surprise for me, which I’ve just discovered, DMA stops operating while MCU is not in active debug mode.

 

static volatile u16 TBCTL_FOR_DMA = TBSSEL_2 | ID_3 | MC_2 | TBCLR;

 

static void InitializeDMAForSPI(void)

{

    DMA0CTL = 0;

    DMA1CTL = 0;

    DMA2CTL = 0;

    TBCTL = 0; // stop

    TBCCTL0 = 0;

       

    UCB0CTL1 = UCSWRST;

    UCB0CTL0 = UCSYNCSPI | UCCKPLSPI | UCMSBSPI;

   

    // MSP Slave SPI Pin Conf.

    P3DIR_bit.P3DIR_0 = 0;  // CS Input

    P3DIR_bit.P3DIR_1 = 0;  // SIMO Input

    P3DIR_bit.P3DIR_2 = 1;  // SOMI Output

    P3DIR_bit.P3DIR_3 = 0;  // MCLK Input

   

    // Master is STR

    P3SEL_bit.P3SEL_0 = 0;  // CS

    P3SEL_bit.P3SEL_1 = 1;  // SIMO

    P3SEL_bit.P3SEL_2 = 1;  // SOMI

    P3SEL_bit.P3SEL_3 = 1;  // MCLK

   

    UCB0CTL1 &= ~UCSWRST;   // Initalize USART state machine

   

    IE2_bit.UCB0RXIE = 0;

    IE2_bit.UCB0TXIE = 0;

   

    // TimerB Settings for inter-character timeout

    TBCCR0 = 10; // ftmr = SMCLK / (8*TBCCR0) -> period = 10us for 8Mhz

    TBCTL = 0; // stop

    TBCCTL0 = CCIE; // enable timer a CCR0 interrupt

 

    // DMA0 for Intercharacter timeout

    DMACTL0_bit.DMA0TSEL0 = 0;

    DMACTL0_bit.DMA0TSEL1 = 0;

    DMACTL0_bit.DMA0TSEL2 = 1;

    DMACTL0_bit.DMA0TSEL3 = 1;  // UCB0RXIFG triggered

   

    DMA0CTL = DMADT_4 | DMASRCINCR_0 | DMADSTINCR_0 | DMASWDW;  // Edge triger, single repeat, source unchanged, dest unchanged

    DMA0SAL = (uint16_t)&TBCTL_FOR_DMA;                         // Start block address

    DMA0DAL = (uint16_t)&TBCTL;                                 // Destination block address

    DMA0SZ = 1;                                                 // max length

   

    // DMA1 for SPI Receive Buffer

    DMACTL0_bit.DMA1TSEL0 = 0; 

    DMACTL0_bit.DMA1TSEL1 = 0;

    DMACTL0_bit.DMA1TSEL2 = 1;

    DMACTL0_bit.DMA1TSEL3 = 1;  // UCB0RXIFG triggered

   

    DMA1CTL = DMADT_0 | DMASRCINCR_0 | DMADSTINCR_3 | DMASBDB | DMA1IE; // single, source unchanged, dest increment, interrupt enabled

    DMA1SAL = (uint16_t)&UCB0RXBUF;                             // Start block address

    DMA1DAL = (uint16_t)&m_SPI_RX_BUF.Stream[0];                // Destination block address

    DMA1SZ = sizeof(m_SPI_RX_BUF);                              // max length

   

    // DMA2 for SPI Transmit Buffer

    DMACTL0_bit.DMA2TSEL0 = 1;

    DMACTL0_bit.DMA2TSEL1 = 0;

    DMACTL0_bit.DMA2TSEL2 = 1;

    DMACTL0_bit.DMA2TSEL3 = 1;  // UCB0TXIFG triggered

   

    DMA2CTL = DMADT_0 | DMASRCINCR_3 | DMADSTINCR_0 | DMASBDB; // Level triger, single, source increment, dest unchanged

    DMA2SAL = (uint16_t)&m_SPI_TX_BUF.Stream[1];                // Start block address

    DMA2DAL = (uint16_t)&UCB0TXBUF;                             // Destination block address

    DMA2SZ = sizeof(m_SPI_TX_BUF)-1;                            // max length  

   

    // once we start receiving frame, other side will receive frame start character to be sure synchronization

    m_SPI_TX_BUF.Base.Header.FrameStart = SPI_FRAME_START;

    UCB0TXBUF = m_SPI_TX_BUF.Stream[0];

   

    DMA2CTL |= DMA2EN;  // Enable DMA2

    DMA1CTL |= DMA1EN;  // Enable DMA1 

    DMA0CTL |= DMA0EN;  // Enable DMA0 

}

 

  • Unfortunately it won't work. The DMAs are not executed simultaneously. One has priority over the other. So the first one acting on teh IFG bit, will also clear it and the second one will never trigger.

    The only thing that you can try is to make the first DMA trigger an interrupt itself and use this trigger. It only works for a certain chain (DMA1 can only be triggered by DMA0 and DMA2 only by DMA1 (or the other way, check the datasheet)

    Also, you cannot use an interrupt flag for DMA if you set teh matching IE bit for triggering an ISR too. Here again, it's either-or.

  • BP,

    Seems like it ought to work to me.  They are edge sensitive triggers.

    The lower-priority channel would perform its operation after the higher-priority channel is done.

    By the way that's quite a comm requirement - 10 microseconds!  Is the master a 100MHz DSP with queued SPI or something?

    Jeff

  • Hi Michael,

     

    I want to discuss DMA0 functionality for the code snippet below. At the beginning, TIMERB is disabled and DMA0 is programmed to start TIMERB when it is triggered by UBC0RXIFG. At trigger, it will simply put TBCTL_FOR_DMA value predefined in RAM to TBCTL so that TIMERB starts running from 0.

     

    My question is that, does this single transfer clear UCB0RXIFG? Because there is still a byte to read from SPI buffer. SPI buffer will be read by DMA1 which has not completed its task yet.

     

    DMA0-> single repeated word transfer for timerb starting and clearing

    DMA1-> block byte transfer

    DMA2-> block byte transfer

     

    I have interrupt enabled only for TimerB_CC0 and, DMA1IFG those are not used as any DMA trigger source at all.

     

    I have also tried using DMA0 as trigger source for DMA1. I have changed source of DMA1 from UCB0RXIFG to DMA0IFG but it didn’t work, I saw DMA1 triggered only one time. I didn’t understand why. Maybe it is related with my first question above

     

     

    static volatile u16 TBCTL_FOR_DMA = TBSSEL_2 | ID_3 | MC_2 | TBCLR;

     

    static void InitializeDMAForSPI(void)

    {

        ..

        // TimerB Settings for inter-character timeout

        TBCCR0 = 10; // ftmr = SMCLK / (8*TBCCR0) -> period = 10us for 8Mhz

        TBCTL = 0; // stop

        TBCCTL0 = CCIE; // enable timer a CCR0 interrupt

       

        ..

       

        // DMA0 for Intercharacter timeout

        DMACTL0_bit.DMA0TSEL0 = 0;

        DMACTL0_bit.DMA0TSEL1 = 0;

        DMACTL0_bit.DMA0TSEL2 = 1;

        DMACTL0_bit.DMA0TSEL3 = 1;  // UCB0RXIFG triggered

       

    // Edge triger, single repeat, source unchanged, dest unchanged, word transfer

        DMA0CTL = DMADT_4 | DMASRCINCR_0 | DMADSTINCR_0 | DMASWDW; 

        DMA0SAL = (uint16_t)&TBCTL_FOR_DMA; // Start block address

        DMA0DAL = (uint16_t)&TBCTL;     // Destination block address, TBCTL

        DMA0SZ = 1;                 // max length

        ..

        ..

    }

     

    Thanks,

    BP..

     

  • Hi Jeff,

    The think which is not clear in user manual, the manual says that, after DMA operation, trigger source is cleared. For my case, DMA0 source is SPI RX IFG. But since it wasn't programmed to read SPI RX Buffer, I don't know how it will be cleared.

    Master is a ARM has 20 byte FIFO for SPI and SPI speed is around 2.7Mhz. I guess peripheral speed for it is 32Mhz. :) Not that fast, relatively msp is slow :P

    BP.

  • BP,

    In your case I don't know whether to expect DMA channel 0 to clear the RXIFG.  I think it will, but it doesn't really matter.  DMA1 will definitely clear it.  If DMA0 does clear it, DMA1 should still respond since it was triggered already and is just waiting for DMA0 to finish.

    Also the DMA IFGs don't work quite like the others.  They are *not* automatically cleared when used as a chained DMA trigger.  You'll want an interrupt handler to clear it.  Also they still work as triggers even if you enable the interrupt.  (That's also different from most others.)  If you don't clear the DMA IFG yourself, there won't be any more edges to trigger continuing DMA.

    Jeff

  • Jeff, the DMA channels can be set to edge-triggered and level-triggered. It's a configuration thing. And I think it has nothing to do with whether the interrupt event for the source IFG flag is level- or edge triggered.

    I don't know whether an event that triggers two DMA channels will trigger two DAM transfers when edge-triggering is selected. If level-triggering is selected, the higher-priority DMA will execute. And if it does not clear the event, it will execute again and again, and the second one will never. Unless you activate round-robin priority.

    I can imagine that a combination of level-triggered DMA and round-robin priority change will work - if the first DMA does not touch the trigger cause and the second does.

    Whether an edge-triggered DMA will execute when teh trigger event has disappeared before the DMA was started is not 100% certain. One could expect this, but then, edge-triggered may also jsut mean that there will be no second DMA transfer even if the transfer will not clear the event (e.g. a clock signal with 50% duty cycle)

    unless something is explicitely written, I take nothing for granted anymore.

  • The DMA should be configured for edge-sensitive triggering because of the following warning from the User's Guide:

    "For proper operation, level-sensitive triggers can only be used when external trigger DMAE0 is selected as the trigger."

    I think triggering two DMA channels from one trigger source should work because the DMA is configured for edge triggering in BP's code (and should always be configured for edge triggering with these triggers).  Once an edge trigger occurs, it cannot be undone.  The edge has occurred.

    It would be helpful for the manual to address the situation.  It doesn't say anything about triggering two channels from the same trigger.  It doesn't say you can't; it doesn't say you can.  However it's difficult for documentation to describe all the things you CAN do; it's often easier to describe the things you CAN'T do.

    Jeff

  • Jeff Tenney said:
    "For proper operation, level-sensitive triggers can only be used when external trigger DMAE0 is selected as the trigger."

    It's the 'for proper operation' part that makes me think that it isn't as it appears at first. it rather sounds like a 'except for DMAE0, there might be side-effects which are too difficult to explain here'

    Jeff Tenney said:
    Once an edge trigger occurs, it cannot be undone.  The edge has occurred.

    Not necessarily. Only if the trigger is latched.
    Example: what if a level-sensitive trigger is used (DMAE0) but the trigger vanishes befor the DMA can be performed (MCLK off, other DMA has priority, whatever). Will it still execute? Only a test will tell. It's possible it won't. Depends on implementation.
    Same for the edge-triggered DMA. For sure a signel edge won't trigger two DMAs (as a level-triggered could). So there is some sort of hysteresis that ensures the trigger has to vanis before it can reappear and trigger a second DMA. But what if the trigger disappears before the DMA can be performed? Is it latched? The documentation doesn't tell. Only a look into the DMA controllers state machin or circuitry cann tell - or an extensive test.

    I won't 'put my hand into the fire' for either one possibility. And experience shows that that the documentation isn't always exhaustive or accurate. Just take the sentence about set/reset output mode not being useful for CCR0 - it actually is!

    Jeff Tenney said:
    However it's difficult for documentation to describe all the things you CAN do; it's often easier to describe the things you CAN'T do.

    Well, best documentation doesn't tell what you can or can't, it just tells what is and lets you decide what you can imagine to do with this or not. Unfortunately, this will often  require to disclose information that is kept as secret IP.

  • After some quick testing, it looks like you can trigger multiple DMA channels from the same channel as long as you use edge triggering in the DMA as recommended in the manual.  This code uses SPI in master mode instead of slave mode for easier illustration.  And the DMA controller does clear RXIFG even without reading from RXBUF.  (Same goes for TXIFG and writing to TXBUF.)

    int main( void )
    {
      int ChannelZeroTimestamp = 0;
      int ChannelOneTimestamp = 0;
      int ChannelTwoTimestamp = 0;
     
      WDTCTL = WDTPW + WDTHOLD;

      UCA0CTL1 = UCSSEL_2 + UCSWRST;
      UCA0CTL0 = UCMST + UCSYNC;
      UCA0BRW = 1;
      UCA0STAT = UCLISTEN;
      UCA0CTL1 &= ~UCSWRST;

      TA0CTL = TASSEL__SMCLK + MC__CONTINUOUS + TACLR;
     
      DMACTL0 = 16*DMA0TSEL0 + 16*DMA1TSEL0;
      DMACTL1 = 16*DMA2TSEL0;

      DMA0CTL = DMADT_0 + DMADSTINCR_0 + DMASRCINCR_0 + DMASBDB;
      DMA1CTL = DMADT_0 + DMADSTINCR_0 + DMASRCINCR_0 + DMASWDW;
      DMA2CTL = DMADT_0 + DMADSTINCR_0 + DMASRCINCR_0 + DMASWDW;

    //  DMA0SAL = (unsigned short) &UCA0RXBUF;
      DMA0SAL = (unsigned short) &TA0R;
      DMA1SAL = (unsigned short) &TA0R;
      DMA2SAL = (unsigned short) &TA0R;

      DMA0DAL = (unsigned short) &ChannelZeroTimestamp;
      DMA1DAL = (unsigned short) &ChannelOneTimestamp;
      DMA2DAL = (unsigned short) &ChannelTwoTimestamp;

      DMA0SZ = 1;
      DMA1SZ = 1;
      DMA2SZ = 1;

      DMACTL4 |= DMARMWDIS;

      DMA0CTL |= DMAEN;
      DMA1CTL |= DMAEN;
      DMA2CTL |= DMAEN;
     
      UCA0TXBUF = 0xFF; // send/receive; cause DMA trigger
     
      while(DMA2CTL & DMAEN);
     
    }  // put breakpoint here; examine timestamps and UCA0IFG

    The commented line for DMA0SAL and the line below it are alternates.  Both show the same behavior as far as DMA triggering goes.

    Jeff

  • Jeff Tenney said:
    fter some quick testing, it looks like you can trigger multiple DMA channels from the same channel as long as you use edge triggering in the DMA as recommended in the manual.

    Then we can take this for confirmed behavior.

    Jeff Tenney said:
    And the DMA controller does clear RXIFG even without reading from RXBUF.  (Same goes for TXIFG and writing to TXBUF.)

    This is rather surprising, since normally, the TX/RXIFG are not cleared by pickign up the interrupt but only be either manually clearign the flag or reading/writing the buffers. So DMA is different here too (it implicitely clears the flag). This is even more surprising because we are in edge triggered mode, which means (on common understanding) that no more triggers will happen unless the bit is reset, but not that it will be reset. Unde rsome circumstances, this behavior is rather counterproductive, as edge-triggered DMA is usally chosen to avoid another trigger until the trigger has been reset. So on an overflow or whatever, no more DMAs occur. But if the trigger is automatically reset, more DMAs will occur when the trigger is set again while it was not intended to be cleared in the meantime.

**Attention** This is a public forum