Electronic – MSP430F5529 UART: UCTXIFG Doesn’t Set Again After Initiating Transmit

msp430uart

I am attempting to program a sort of bridge between UCA0 and UCA1 in UART mode on the MSP430F5529 using the MSP-EXP430F5529LP development board. I am using an interrupt for receiving data but for transmitting I am just loading the UCAxTXBUF directly (no TX interrupts are enabled). I say UCAxTXBUF because the bridge should be bidirectional, so it can either be UCA0TXBUF or UCA1TXBUF.

My interrupt service routine is based on TI's UART examples available in the MSP430Ware packages. The examples use a busy wait inside the ISR to wait for the TX buffer to become available (UCTXIFG goes high) before loading UCAxTXBUF with the data. I hate the idea of putting a busy-wait inside an interrupt but for the sake of argument I'm following the examples. Anyway, the problem is that, while UCTXIFG is high when I'm preparing to transmit the first byte, as soon as I load the UCAxTXBUF with the data the UCTXIFG bit clears (as expected), but then is never set again. This suggests that for reason unknown, the data is not being transmitted so UCBUSY remains set and UCTXIFG doesn't go high indicating the buffer is ready for more data. What might be the issue? The ISRs for UCA0 and UCA1 are shown below:

UCA0 ISR:

#pragma vector = USCI_A0_VECTOR
__interrupt void USCI_A0_ISR(void) {
    __no_operation();
    switch(__even_in_range(UCA0IV,4)) {
        case 0:                                         // No interrupt
            break;
        case 2:                                         // RX interrupt
            __disable_interrupt();                      // Disable interrupts
            UCA0_rxByte = UCA0RXBUF;

            while (!(UCA1IFG & UCTXIFG));               // Wait for UCA1 TX buffer to be ready
                                                        //  Bad practice - should not have busy
                                                        //  wait in ISR
            UCA1TXBUF = UCA0_rxByte;                    // Pass through to 485
            UCA0_rxString[UCA0_idx] = UCA0_rxByte;      // Save current byte in buffer
            UCA0_idx++;
            TA0_clearTimer();                           // Restart timer
            __enable_interrupt();                       // Re-enable interrupts
            break;
        case 4:                                         // TX interrupt
            break;
    }
}

UCA1 ISR:

#pragma vector=USCI_A1_VECTOR                           // UCA0 RX/TX interrupt vector
__interrupt void USCI_A1_ISR(void) {
    switch(__even_in_range(UCA1IV,4)) {
        case 0:                                         // No interrupt
            break;
        case 2:                                         // RX interrupt
            __disable_interrupt();                      // Disable interrupts
            UCA1_rxByte = UCA1RXBUF;                    // Get current byte

            UCA1_rxString[UCA1_idx] = UCA1_rxByte;      // Save current byte in buffer

            while (!(UCA0IFG & UCTXIFG));               // Wait for UCA0 TX buffer to be ready
                                                        //  Bad practice - should not have busy
                                                        //  wait in ISR
            UCA0TXBUF = UCA1_rxByte;                    // Pass through to TTL
            UCA1_idx++;
            TA0_clearTimer();                           // Restart timer
            __enable_interrupt();                       // Re-enable interrupts
            break;
        case 4:                                         // TX interrupt
            break;
    }
}

The __disable_interrupt() and __enable_interrupt() lines were added in after this problem first appeared, so I am fairly confident they are not the problem.

When I attempt to transmit the first character from UCA0 the test while(!(UCA1IFG & UCTXIFG)) falls straight through, as I expect it to since the UCA1TXBUF should be empty and ready for data. However, when I attempt to transmit the second character from UCA0 the program hangs at that same test. Checking the registers in CCS I see that UCBUSY is set and UCTXIFG is still clear.

What could cause this sort of behavior? I have found multiple questions asked here and on other sites that describe a similar issue, but they all seem to be for I2C or SPI, and the solutions do not seem to apply to my current problem with the UART.

The actual ISR from TI's UCA0 example is shown below. The added bits above are modifications to variables I use for my own purposes.

switch(__even_in_range(UCA0IV,4)) {
    case 0:break;                             // Vector 0 - no interrupt
    case 2:                                   // Vector 2 - RXIFG
        while (!(UCA0IFG&UCTXIFG));             // USCI_A0 TX buffer ready?
        UCA0TXBUF = UCA0RXBUF;                  // TX -> RXed character
        break;
    case 4:break;                             // Vector 4 - TXIFG
    default: break;
}

UPDATE:
For sake of completeness, here is the initialization code for UCA1 (effectively identical to the initialization for UCA0):

void UCA1_init(uint32_t smclk, uint32_t baudrate) {
    P4SEL |= BIT4 + BIT5;                                       // Select alternate function for P4.4, 4.5 (UCA1 TXD, RXD)
    UCA1CTL1 |= UCSWRST;                                        // Reset USCI state machine
    UCA0CTL1 |= UCSSEL_2;                                       // Set clock to SMCLK
    UCA1BR0 = 52;                                               // Low byte of clock prescaler (9600 bps)
    UCA1BR1 = 0;                                                // High byte of clock prescaler
    UCA1MCTL = UCBRS_0 + UCBRF_1 + UCOS16;                      // Modulation stages; oversampling mode
    UCA1CTL1 &= ~UCSWRST;                                       // Restart USCI state machine
    UCA1IE |= UCRXIE;                                           // Enable RX interrupt
}

And the port initialization for UCA1 (note that UCA1 is port-mapped):

// PORT4
P4SEL = (BIT4 + BIT5);                                  // Set P4.4 as UCA1TXD, P4.5 as UCA1RXD, rest as I/O
PMAPKEYID = 0x2D52;                                     // Unlock port mapping register configuration
PMAPCTL |= PMAPRECFG;                                   //      Allow reconfiguration of mapping
P4MAP4 = 12;                                            //      Map P4.4 to PM_UCA1
P4DIR |= BIT4;                                          //      Set P4.4 as input (UCA1TXD)
P4MAP5 = 11;                                            //      Map P4.5 to PM_UCA1
P4MAP5 &= ~(BIT5);                                      //      Set P4.5 as input (UCA1RXD)
P4DIR |= (BIT7 + BIT6 + BIT3 + BIT2 + BIT1 + BIT0);     // Set rest of PORT4 as outputs
P4OUT &= ~(GPIO_ALL);                                   // Clear PORT4 outputs

I am beginning to wonder if there may be an issue with my mapping of the ports. I should have included this from the start. Perhaps someone can identify an error?

Best Answer

void UCA1_init(...) {
    ...
    UCA1CTL1 |= UCSWRST;                         // Reset USCI state machine
    UCA0CTL1 |= UCSSEL_2;                        // Set clock to SMCLK
       ^

This leaves the USCI_A1 clock source at its default setting (external), and I guess that P4.0 is not configured for that.


There is a method to avoid the loop inside the interrupt handler (it does require both TX/RX interrupts):

bool UCA0_received;
bool UCA1_txReady;

USCI_A0_ISR() {
    switch (UCA0IV) {
    case USCI_UCRXIFG:
        UCA0_rxByte = UCA0RXBUF;
        UCA0_received = true;
        maybeEchoTo1();
        break;
    }
}

USCI_A1_ISR() {
    switch (UCA1IV) {
    case USCI_UCTXIFG:
        UCA1_txReady = true;
        maybeEchoTo1();
        break;
    }
}

maybeEchoTo1() {
    if (UCA0_received && UCA1_txReady) {
        UCA1TXBUF = UCA0_rxByte;
        UCA0_received = false;
        UCA1_txReady = false;
    }
}

And please do not use magic numbers but the proper symbols for the interrupt vector values.
They are not documented anywhere (even the writers of TI's examples do not know them), so you have to scrape them out of the header file:

/* USCI Interrupt Vector Definitions */
#define USCI_NONE              (0x0000)       /* No Interrupt pending */
#define USCI_UCRXIFG           (0x0002)       /* Interrupt Vector: UCRXIFG */
#define USCI_UCTXIFG           (0x0004)       /* Interrupt Vector: UCTXIFG */