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.