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.

Toggling timer interrupt enable disrupts DMA to USCI? (MSP430F5438A)

Other Parts Discussed in Thread: MSP430F5438A

Hello.

I'm having trouble with a strange behaviour in the DMA and/or USCI controllers of the MSP430F5438A.

In my application I am using a DMA controller to write the contents of a memory buffer to one of the USCI ports, configured as an SPI port. The DMA is configured for a single byte transfer, and is triggered when the TX buffer of the USCI port is empty.

I have found however, that manipulating the interrupt settings of one of the hardware timers while the DMA is enabled can cause this transfer to go wrong in some way and stall. To help explain, I've written a short test program which demonstrates my issue (attached).

#include <msp430.h>

// Pin defines for test points.
#define TEST_POINT_0_LOW    (P11OUT &= ~BIT0)
#define TEST_POINT_0_HIGH   (P11OUT |= BIT0)
#define TEST_POINT_1_LOW    (P11OUT &= ~BIT1)
#define TEST_POINT_1_HIGH   (P11OUT |= BIT1)
#define TEST_POINT_2_LOW    (P1OUT &= ~BIT1)
#define TEST_POINT_2_HIGH   (P1OUT |= BIT1)
#define TEST_POINT_3_LOW    (P1OUT &= ~BIT2)
#define TEST_POINT_3_HIGH   (P1OUT |= BIT2)

char buffer[128];

void startDmaIfIdle(void)
{
    // Debug pin toggle.
    TEST_POINT_0_HIGH; TEST_POINT_0_LOW;

    // When DMA 1 is idle...
    if((DMA1CTL & DMAEN) == 0)
    {
        // Debug pin toggle.
        TEST_POINT_1_HIGH; TEST_POINT_1_LOW;

        // configure DMA for single byte transfer, incrementing source address only.
        DMA1CTL = (DMADT_0 | DMADSTINCR_0 | DMASRCINCR_3 | DMASBDB);

        // source/destination DMA addresses
        _data20_write_long((unsigned long)&DMA1SA, (unsigned long)(&buffer[1]));
        _data20_write_long((unsigned long)&DMA1DA, (unsigned long)&UCA0TXBUF);

        // DMA size set to copy all bytes in buffer, less one, as the first is sent
        // manually.
        DMA1SZ = sizeof(buffer) - 1;

        // Trigger dma when USCIA0 transmit buffer empty
        DMACTL0 = DMA1TSEL_17;

        // Enable DMA
        DMA1CTL |= DMAEN;

        // copy first byte of buffer to USCI. Completion of the USCI will trigger the DMA.
        UCA0TXBUF = buffer[0];
    }
}

#pragma vector=TIMER0_B0_VECTOR
__interrupt void timerbisr(void)
{
    // Debug pin toggle.
    TEST_POINT_3_HIGH; TEST_POINT_3_LOW;
    return;
}

void main(void)
{
    WDTCTL = WDTPW+WDTHOLD;     // stop the watch dog timer

    // configure pins for test points.
    P1DIR |= (BIT2 | BIT1);
    P1REN &= ~(BIT2 | BIT1);
    P11DIR = (BIT1 | BIT0);
    P11OUT &= ~(BIT0 | BIT1);

    // USCI A0 pins
    P3DIR = BIT4 | BIT0;            // MOSI and CLK as output pins
    P3SEL |= (BIT0 | BIT4 | BIT5);  // select pins for USCI mode.

    // USCI A0 configuration
    UCA0CTLW0 = UCSWRST;            // enter reset state
    UCA0CTL0 = (UCMST | UCSYNC);    // MSP SPI master, Synchronous mode
    UCA0CTL1 |= UCSSEL__SMCLK;      // Clock source is SMCLK
    UCA0BRW = 3;                    // Set bit rate
    UCA0CTL1 &= ~UCSWRST;           // leave reset state

    // configure hardware timer TB0.
    TB0CTL = MC__STOP+TBCLR;    // Stop/Clear timer.
    TB0CTL |= TASSEL__SMCLK;    // Clock source is SMCLK
    TB0CCR0 = 16384;            // Set timer period
    TB0CTL |= MC__UP;           // Start the timer.

    __bis_SR_register(GIE);     // enable interrupts
    __no_operation();

    unsigned int n, count = 0;

    while(1)
    {
        // Debug pin toggle.
        TEST_POINT_2_HIGH; TEST_POINT_2_LOW;

        // start the DMA transaction if DMA currently idle.
        startDmaIfIdle();

        // Add some jitter to the timing of this loop. It appears that the issue can be
        // masked with some timings of this loop.
        if (++count > 16)
            count = 0;
        // jitter delay
        for (n=0; n<count; n++)
            __no_operation();

        // enable interrupt for TB0
        TB0CCTL0 |= CCIE;
        // disable interrupt for TB0.
        //TB0CCTL0 &= ~CCIE;
    }
}

In this program, I configure USCI A0 for SPI master operation, and use DMA1 to write data to the UCA0TXBUF register to write data out of the port. The function startDmaIfIdle() checks if DMA1 is idle, and if so, sets up DMA1 to write out one buffer of data to the port. I call startDmaIfIdle() constantly from a while loop. I also configure Timer TB0 to count up repeatedly to a value. I've added an ISR for the TIMER0_B0_VECTOR which doesn't do anything but toggle a pin.

If I enable the TB0 interrupt, and call startDmaIfIdle() in the while loop, everything works fine. The TB0 ISR routine runs as expected and the DMA is restarted as soon as a transfer is completed. This is demonstrated in the code sample as shown.

If the line at the end of the program to disable the TB0 interrupt is uncommented however, things go wrong. When the CCIE bit in TB0CCTL0 is toggled repeatedly in the while loop, the on-going DMA transfer operation stalls. I see some bytes leave the USCI, but then the transfers stop completely.

Looking at the registers with the debugger shows DMA1 is still enabled and has transfers still to go in the DMA1SZ register. The UCA0IFG register shows that the UCTXIFG flag is zero, which is strange since it should be reset automatically once a transfer has completed.

Does anyone know why toggling the TB0 CCIE bit has an effect on the operation of DMA1/USCIA0?

This issue seems to be timing sensitive. I found that with some combinations of instructions in the while loop, the issue did not occur. I've added some code that introduces variable timing to the while loop which seems to prevent this.

I'm building and running with CCS 5.2. The above code can be dropped into the LED blink sample application and compiled. I've seen the issue on the MSP430F5438A (REV D), running in the MSP-TS430PZ5x100 breakout board.

Any help or suggestions are greatly appreciated! I'll post back if I find out more as I continue investigating.

  • When I use Timer A0 or A1 in place of Timer B0, the issue goes away. Anyone got any ideas why this is?

    Edit: Not certain that this is the case.

  • Does your DMA write to a local variable? Then perhaps erratum DMA8 could apply:
    When an interrupt is triggered, it has the same effect as a CALLA instruction. However, DMA8 usually corrupts the data written, and does not stall the DMA transfer.
    However, I don't see what can cause a DMA transfer (for which a local variable on stack is the same s any other location in ram)  fail if the CPU is accessing the stack.

    I see you use _data20_write_long to write the DMA address registers. Why? RAM and USCI registers are in lower 64k and nly require a normal 16 bit write. The 20 bit write must not be interrupted by DMA, so only use it when necessary (currently, it is no problem, but if you have muiltiple DMA transfers...). And the _data20_write_long must not be interrupted by an interrupt, unless oyu use large data model. because if not, the ISR will only save 16 bit register contents and the register that temporarily holds the DMA target address might be altered. I'm not sure whether the compiler automatically generates a critical section around _data20_write_long.

    But none of these, even though potential problems, should cause the problems you see. Apparently, TXBUF got a byte (TXIFG clear) and never finishes sending it.

    What if you place a NOP between settign and clearing CCIE bit?
    I know there is a known and documented problem with setting and clearing GIE in two subsequent instructions. Maybe here is a similar problem. However, that's plain guessing.

  • Hi Jens-Michael,

    Thanks for taking the time to reply, I appreciate your help!

    Jens-Michael Gross said:

    Does your DMA write to a local variable? Then perhaps erratum DMA8 could apply:
    When an interrupt is triggered, it has the same effect as a CALLA instruction. However, DMA8 usually corrupts the data written, and does not stall the DMA transfer.
    However, I don't see what can cause a DMA transfer (for which a local variable on stack is the same s any other location in ram)  fail if the CPU is accessing the stack.

    I see you use _data20_write_long to write the DMA address registers. Why? RAM and USCI registers are in lower 64k and nly require a normal 16 bit write. The 20 bit write must not be interrupted by DMA, so only use it when necessary (currently, it is no problem, but if you have muiltiple DMA transfers...). And the _data20_write_long must not be interrupted by an interrupt, unless oyu use large data model. because if not, the ISR will only save 16 bit register contents and the register that temporarily holds the DMA target address might be altered. I'm not sure whether the compiler automatically generates a critical section around _data20_write_long.

    But none of these, even though potential problems, should cause the problems you see. Apparently, TXBUF got a byte (TXIFG clear) and never finishes sending it.

    In our full system, we never DMA to any local variables (on the stack). We only read/write to USCI data registers and static locations in RAM. Some of these locations are above the 64kbyte boundary, which is why we are using _data20_write_long. We are using large code model and restricted data model. Is _data20_write_long safe from interrupts in restricted data model? I was not aware of the risk to _data20_write_long from interrupts.

    Anyway I tested with a regular 16 bit write to the DMA address registers in the test example and the issue is unchanged, so as expected it is not the cause of the main issue.

    As for the requirement for the DMA not to interrupt a _data20_write_long operation, are you referring to errata DMA4? In our full system we do have multiple concurrent DMA operations, and we work around DMA4 by setting DMARMWDIS as suggested in the errata. But again, that is not related to this issue here as only one DMA is in use.

    Jens-Michael Gross said:

    What if you place a NOP between settign and clearing CCIE bit?
    I know there is a known and documented problem with setting and clearing GIE in two subsequent instructions. Maybe here is a similar problem. However, that's plain guessing.

    In my further testing, I added an additional varying delay loop placed between setting and clearing the CCIE flag. This did not stop the issue occurring.

    I have made an interesting discovery. I found that if GIE is cleared before modifying the CCIE flag, and restored afterwards, then the issue doesn't seem to occur. (GIE is cleared and restored for each change to the CCIE flag, and I've left the delay between clearing and setting the CCIE flag.)

    So the while loop looks like this:

        while(1)
        {
            // Debug pin toggle.
            TEST_POINT_2_HIGH; TEST_POINT_2_LOW;

            // start the DMA transaction if DMA currently idle.
            startDmaIfIdle();

            // Add some jitter to the timing of this loop. It appears that the issue can be
            // masked with some timings of this loop.
            if (++count > 16)
                count = 0;
                
            if (++count2 > 17)
                count2 = 0;

            // jitter delay
            for (n=0; n<count; n++)
                __no_operation();

            _disable_interrupts();
            TB0CCTL0 |= CCIE;
            __bis_SR_register(GIE);

            // jitter delay
            for (n=0; n<count2; n++)
                __no_operation();

            _disable_interrupts();
            TB0CCTL0 &= ~CCIE;
            __bis_SR_register(GIE);
        }

    With this configuration, everything seems to work properly. If the disabling/enabling of GIE is commented out, the issue returns. I don't understand why however, I stumbled on this solution by guessing. I can't see anything in the documentation that explains this. Any ideas what is going on?

  • I'm still struggling to understand what is going on with this issue. While I have appeared to have found a work around, I still need to understand what is causing this behaviour so that I can be sure it has been resolved correctly in our system.

    Is there any support available from Ti for this kind of issue?

  • Hi Terence,

    apologizes for the late answer.

    I tried to compile your code using CCS v5.3, Compiler version 4.1.2 and 4.1.3 with EABI output format, but i can't see the problem.

    I attach a scope to P3.0 which is assigned as UCA0CLK, and can see the clock signals being sent all the time.

    Terence Withers said:

    This issue seems to be timing sensitive. I found that with some combinations of instructions in the while loop, the issue did not occur. I've added some code that introduces variable timing to the while loop which seems to prevent this.

    This could be one reason why i don't see the issue.

    Which CCS compiler version are you using exactly and which output format?

  • Hi Leo, thanks for looking into this for me.

    We are on CCS version 5.2.0.00069, selected compiler is TI v4.1.0, output format is eabi (ELF).

    Just to check - in the file linked in my first post, the code will work correctly if run as is. To expose the problem you need to uncomment line 107: //TB0CCTL0 &= ~CCIE;

    Thanks,

    Terry.

  • Terry,

    i tried exactly what you described (using CCS v5.2.0.00069, Compiler 4.1.0, EABI format, uncomment //TB0CCTL0 &= ~CCIE), but still can't see the issue.

    Could you maybe post the whole CCS project here?

  • Hi Leo,

    I went back to the project and re-tested to verify the code I posted. We have a selection of 7 MSP430 chips that we are using for debug, a mix of revision D and E devices, in the 100 pin LQFP package.

    By chance I discovered that the test program does not function the same on all of these chips. On 5 of them (3 rev D, 2 rev E) the DMA transfer fails almost immediately - within 1 second of starting the program. On one rev E chip, the program can sometimes run several seconds before failing. On the final chip, a rev D, the program has been running for +1hr and has yet to fail.

    This suggests to me that there may be a dependence on some manufacturing tolerance. I've tried applying heat/freezing to the chips but this doesn't seem to have any significant effect.

    Alternativly, we may just have a faulty chip. Still, it might be worth you trying again with a different chip sample?

    I've attached the full CCS project as requested. I hope this helps you re-create the issue.

    7450.dma_issue.zip


    Many thanks,
    Terry.

  • Hi Terry,

    apologizes for the delay. I tried to run your complete project on my bench (I am using MSP430F5438A Rev E on MSP-TS430PZ5x100), i can't see the issue. 

    Are you using custom board or TI development kit? If you are using custom board, could you please try to test it with our development board? If the problem disappear when using TI dev kit, i would suggest this could be a hardware problem (e.g. most common one on the 5xx/6xx devices is missing VCORE cap, etc.).

  • Hi Leo,

    I originally found the issue on our custom boards, but for the testing program we are discussing, I am running on the MSP-TS430PZ5x100 dev board, using a MSP-FET430UIF debugger. I am powering the board using the debugger.

    I've gone back again to try and find what is different from you such that you are not seeing the issue.

    One of the things I tried was building the code to a binary .txt file and flashing that using the MSP430 command line programming tool. Programming this way showed some different behaviour:

    • Once the flash process completed, the MSP430 would be reset and the program would run. For this first run immediately after programming, the issue does not appear.
    • If power to the dev board was then removed and reconnected, the code would restart and fail almost immediately.
    • Further power cycles to the development board would always result in the code failing shortly after power connected.
    • If the line of code that disables the timer interrupt is removed from the program, then the program runs after power cycling without stopping (as expected).
    • The issue does not appear ever, even after power cycling, for one our chips.

    Have you tried:

    • Using a different chip sample of the same model? I have one chip in my set of 7 that the issue does not appear with. Perhaps you have a similar chip.
    • How are you flashing the code onto the device? Perhaps you can try using the command line flasher instead.
    • Have you tried power cycling the development board after programming to see if it fails then?

    Thanks, Terry.

  • Hi Terry,

    Terence Withers said:

    One of the things I tried was building the code to a binary .txt file and flashing that using the MSP430 command line programming tool. 

    Whie programming tool are you using? Is it the MSP430 Flasher - http://processors.wiki.ti.com/index.php/MSP430_Flasher_-_Command_Line_Programmer?

    Which version are you using? Have you tried use the newest version available?

  • Hi Leo,

    Apologies for the long delay in my reply - I have been away on a long vacation.

    We are indeed using the MSP430 flasher tool as you describe. We are using version v1.1.7. What behaviour do you see when using this tool to program the MSP?

    Thanks,

    Terry.

  • Terry,

    i have tried to reproduce the issue using the MSP430Flasher 1.1.7 you mentioned and the newest version MSP430 Flasher 1.2.1, but i still see the device working after programming for both versions of MSP430Flasher.

    Attached you can get my test scripts:

    0336.test.zip

    I also tested the scripts with two silicon revisions Rev D and Rev E, and both silicon revisions also work. Do you do only [VCC] or [VCC,RESET] as exit specification of the MSP430Flasher?

**Attention** This is a public forum