2
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ Not an answer, just some thoughts crossing my mind. (1) The global interrupts are cleared once the status byte is pushed during the interrupt event, so I've no idea why example code includes a call to disable the interrupts within the interrupt vector code. Makes no sense. (2) Using someone else's code that you don't agree with, independently, is fraught with possible errors. I would choose NOT to take the convenient/easy path but would instead work out my own idea. At least I'd know WHY I wrote what I wrote, then. \$\endgroup\$ – jonk Oct 31 '18 at 14:57
  • \$\begingroup\$ A third thought comes to mind, though. I believe (I would need to go look) that this is a double-buffered situation. When you first load the transmitter buffer, if the second "in-progress shift register" is NOT busy at the time then the transmit buffer will be instantly moved into that in-progress shift register. I think this transfer is what signals an interrupt. If you are inside the interrupt code when this happens, it's possible that things have already gone back to a quiescent state and you won't get an interrupt. This is something I'd have to go research, though. Your job, I think. \$\endgroup\$ – jonk Oct 31 '18 at 15:00
  • \$\begingroup\$ The enabling and disabling of global interrupts was something I added later for testing, and you're right, it doesn't appear to make any difference. I currently have them commented out. Regarding the possibility of a double-buffer system, I'm not entirely sure. I believe the datasheet and family guide for this device mention the value in TXBUF gets moved to a shift register, at which point TXIFG goes high again indicating it's ready for more data. \$\endgroup\$ – DerStrom8 Oct 31 '18 at 15:06
  • \$\begingroup\$ What I'm worried about is that you need to be sure you make certain that you don't lose an interrupt here. I normally arrange my serial port drivers in a fashion that covers my long experiences with a wide variety of peripheral behaviors. I always make sure that I handle the special-cases. So within the interrupt code if I load TXBUF then I immediately check to see if TXBUF is made re-available right away. If so, I load a 2nd byte (assuming there is one in the outgoing buffer.) If I can't do that, then the buffer is empty and I've sent out the last byte and the whole system is shut down. \$\endgroup\$ – jonk Oct 31 '18 at 15:23
  • \$\begingroup\$ That forces me to restart the system when I have another byte to transmit the first time. Keep in mind that all drivers are pairs of actual driver code: a high side routine used by the main code and a low side routine which operates the interrupt events and drains the buffer. That's for transmit. For receive, there will be ANOTHER pair of such routines. So for full-duplex, there are FOUR routines, two high-side and two low-side, each with buffers isolating them. Special handling takes care of the case where re-priming is needed. \$\endgroup\$ – jonk Oct 31 '18 at 15:26
1
\$\begingroup\$
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 */
\$\endgroup\$
  • \$\begingroup\$ Thank you for the suggestion for getting rid of the busy wait. Regarding UCTXIE I have been very careful to ensure it is not set anywhere. I am aware that this could have caused issues, so I even performed a code search through the entire project to ensure it is not set anywhere, which it isn't. \$\endgroup\$ – DerStrom8 Oct 31 '18 at 15:30
  • \$\begingroup\$ Do you ever get TX in the interrupt handlers? \$\endgroup\$ – CL. Oct 31 '18 at 15:31
  • \$\begingroup\$ No, I never hit the TX interrupt case \$\endgroup\$ – DerStrom8 Oct 31 '18 at 15:32
  • \$\begingroup\$ DOH! That's the problem with copying and pasting similar code - at some point you forget to change something that needs to be changed. I'll fix that error tomorrow and see what happens. Cheers! \$\endgroup\$ – DerStrom8 Nov 1 '18 at 2:31
  • \$\begingroup\$ Yes indeed, it was a stupid mistake. When I copied my code from UCA0.c to UCA1.c I forgot to change UCA0CTL1 to UCA1CTL1 when selecting the clock source. Once that was corrected it seems to be working as expected. \$\endgroup\$ – DerStrom8 Nov 1 '18 at 10:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.