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.

MSPDRIVERLIB: Interrupt-based UART TX missing the first character

Part Number: MSPDRIVERLIB


Tool/software:

Hi,

I’m currently implementing interrupt-based UART APIs to handle transmitting and receiving strings using DriverLib functions.

For some reason, when I use the uart_puts() function after uart_putc(), the first characters of the strings I'm transmitting using uart_puts() are missing.(This is not observed when I print multiple strings using uart_puts() without the preceding uart_putc(), so I suspect some operations in the uart_putc() might be affecting the subsequent operations in uart_puts().)

Please find the test code, implementation of the APIs and the result described below:

Here's the test code in main().

#include "ti_msp_dl_config.h"
#include "uart.h"

static void sys_init(void);

int main(void)
{
    sys_init();

    uart_putc('c');
    uart_puts("123\r\n");
    uart_puts("456\r\n");

    while (1) 
    {
        /* Do nothing. */
    }
} /* main() */

/*!
 * @brief Initializes the system.
 */
void sys_init(void)
{
    SYSCFG_DL_init();
    uart_init();
} /* sys_init() */

Here's the implementation of the custom 'uart_putc()' and the 'uart_puts()' functions:

#include "uart.h"
#include "ti_msp_dl_config.h"

static volatile const char *       g_p_char           = NULL;
static volatile uart_comm_status_t g_uart_comm_status = UART_STATUS_INIT;
static volatile char g_rx_char = 0;

/*!
 * @brief Performs any additional initializations necessary for the UART
 * peripheral.
 *
 * @par
 * The UART peripheral initialization is performed by the initialization
 * functions automatically generated by the sysconfig tool. See
 * 'ti_msp_dl_config.c' for more details.
 */
void uart_init(void)
{
    /* Disable interrupts and clear interrupt statuses until necessary. */
    DL_UART_Main_disableInterrupt(UART_COMM_INST, DL_UART_MAIN_INTERRUPT_TX);
    DL_UART_clearInterruptStatus(UART_COMM_INST, DL_UART_MAIN_INTERRUPT_TX);
    DL_UART_Main_disableInterrupt(UART_COMM_INST, DL_UART_MAIN_INTERRUPT_RX);
    DL_UART_clearInterruptStatus(UART_COMM_INST, DL_UART_MAIN_INTERRUPT_RX);

    /* To prevent any unexpected pending IRQ from triggering an interrupt when
     * enabling the IRQ, clear the pending IRQ before enabling it. */
    NVIC_ClearPendingIRQ(UART_COMM_INST_INT_IRQN);
    NVIC_EnableIRQ(UART_COMM_INST_INT_IRQN);
} /* uart_init() */

/*!
 * @brief Sends a character over UART.
 *
 * @par
 * This function by itself does not trigger the UART TX interrupt.
 */
void uart_putc(const char c)
{
    /* Wait until the TXDATA register is empty and there is no ongoing data
     * reception before proceeding with transmission. */
    while (DL_UART_Main_isBusy(UART_COMM_INST))
    {
        /* Do nothing. */
    }

    DL_UART_Main_transmitData(UART_COMM_INST, c);
} /* uart_putc() */

/*!
 * @brief Sends a null-terminated string over UART.
 * @param[in] p_str A pointer to a null-terminated string to be transmitted over
 * UART.
 */
void uart_puts(const char * p_str)
{
    /* Enable the interrupt to perform data transmission over UART using
     * interrupts. */
    DL_UART_Main_enableInterrupt(UART_COMM_INST, DL_UART_MAIN_INTERRUPT_TX);

    /* NOTE: Ensure that the 'g_p_char' setting is performed after enabling the
     * TX interrupt. This is because, when the TX interrupt is enabled while
     * the TXDATA register of the UART peripheral is empty, the TXINT bit will
     * be set and trigger the interrupt immediately. For the interrupt handler
     * to handle this case 'g_p_char' must be a null pointer. */
    g_p_char           = p_str;
    g_uart_comm_status = UART_STATUS_TX_STARTED;

    /* Wait until the TXDATA register is empty and there is no ongoing data
     * reception before proceeding with transmission. */
    while (DL_UART_Main_isBusy(UART_COMM_INST))
    {
        /* Do nothing. */
    }

    /* Initiate data transmission. */
    DL_UART_Main_transmitData(UART_COMM_INST, *g_p_char);

    /* Wait for the entire transmission to complete. */
    while (g_uart_comm_status != UART_STATUS_TX_COMPLETE)
    {
        /* Do nothing. */
    }
} /* uart_puts() */

/*!
 * @brief Interrupt handler for UART_COMM instance.
 *
 * @par
 * This handler processes UART interrupts based on the type of interrupt pending
 * (RX or TX). For TX interrupts, it handles the transmission of the next
 * character or completes the transmission when the end of the string is
 * reached.
 */
void UART_COMM_INST_IRQHandler(void)
{
    /* Check the pending interrupt type for the UART instance. */
    switch (DL_UART_Main_getPendingInterrupt(UART_COMM_INST))
    {
        case DL_UART_MAIN_IIDX_RX:
        {
            if (UART_STATUS_RX_STARTED != g_uart_comm_status)
            {
                /* Interrupt or user input(s) uncalled for. Ignore. */
                return;
            }

            g_rx_char = DL_UART_Main_receiveData(UART_COMM_INST);

            DL_UART_Main_disableInterrupt(UART_COMM_INST,
                                          DL_UART_MAIN_INTERRUPT_RX);
            g_uart_comm_status = UART_STATUS_RX_COMPLETE;

            break;
        }
        case DL_UART_MAIN_IIDX_TX:
        {
            if (NULL == g_p_char)
            {
                /* No character is pending to be transmitted, return. */
                return;
            }

            g_uart_comm_status = UART_STATUS_TX_INPROGRESS;

            /* Proceed to the next character to send. */
            g_p_char++;

            if (*g_p_char != '\0')
            {
                /* Wait until the TXDATA register is empty and there is no ongoing data
                 * reception before proceeding with transmission. */
                while (DL_UART_Main_isBusy(UART_COMM_INST))
                {
                    /* Do nothing. */
                }

                /* There are more characters to send, transmit the next one. */
                DL_UART_Main_transmitData(UART_COMM_INST, *g_p_char);
            }
            else
            {
                /* If the string is finished, disable TX interrupt and update
                 * status. */
                DL_UART_Main_disableInterrupt(UART_COMM_INST,
                                              DL_UART_MAIN_INTERRUPT_TX);
                g_p_char           = NULL;
                g_uart_comm_status = UART_STATUS_TX_COMPLETE;
            }
            break;
        }
        default:
            break;
    }
} /* UART_COMM_INST_IRQHandler() */

Here's the result:

1 and 4 are missing.

c23
56

When I'm epecting:

c123
456

I would appreciate it if anyone could help me understand if there's any point where I'm using the DriverLib functions incorrectly.

Thank you,

Kyungjae Lee

  • The block comments at the beginning of uart_puts() discuss having TXINT trigger immediately, but don't account for the case where TXINT doesn't trigger immediately. That is what you trigger by calling uart_putc() first, so when uart_puts() starts the previous byte is still in flight, which sets the state machine off-by-1.

    The TXINT then triggers later (probably) in the isBusy() loop, so the entire string -- starting with [1] -- is sent before isBusy() returns false. Then uart_puts() writes *g_p_char (*NULL == 0x00 usually), then returns (TX_COMPLETE). Since the 0x00 is in flight on the next uart_puts() call , the process repeats. 

    Perhaps the simplest way to unwind this might be to have uart_puts(): [a] set g_p_char [b] write the first byte [c] enable TXINT.

  • Hi Bruce,

    I appreciate the clarification. I guess that explains what I've been observing. Reordering the sequence as follows solved the issue:

    /*!
     * @brief Sends a null-terminated string over UART.
     * @param[in] p_str A pointer to a null-terminated string to be transmitted over
     * UART.
     */
    void uart_puts(const char * p_str)
    {
        g_p_char           = p_str;
        g_uart_comm_status = UART_STATUS_TX_STARTED;
    
        /* Wait until the TXDATA register is empty and there is no ongoing data
         * reception before proceeding with transmission. */
        while (DL_UART_Main_isBusy(UART_COMM_INST))
        {
            /* Do nothing. */
        }
    
        /* Initiate data transmission. */
        DL_UART_Main_transmitData(UART_COMM_INST, *g_p_char);
    
        /* NOTE: The TXINT bit of the UART will be set as soon as the first
         * character of the string is sent and the TXDATA register becomes empty. To
         * trigger the pending interrupt and transmit the rest of the characters,
         * enable the TX interrupt. */
        DL_UART_Main_enableInterrupt(UART_COMM_INST, DL_UART_MAIN_INTERRUPT_TX);
    
        /* Wait for the entire transmission to complete. */
        while (g_uart_comm_status != UART_STATUS_TX_COMPLETE)
        {
            /* Do nothing. */
        }
    } /* uart_puts() */
    

    Thank you,

    Kyungjae Lee