Code: Select all
int main(void) {
halInit();
chSysInit();
sdStart(&SD1, NULL);
BaseSequentialStream *tty = (BaseSequentialStream *) &SD1;
while (TRUE) {
unsigned char msg[] = "0123456789ABCDEF 0123456789ABCDEF 0123456789ABCDEF\r\n";
chSequentialStreamWrite(tty, msg, sizeof msg);
chThdSleep(1000);
}
}
Once each second, the application prints out a line via the serial port. Ideally, each line should appear complete with a \r\n at the end. However, the last line is always incomplete. The final 14 characters on a given line don't actually make it to the UART until the next line is started (one second later). In this case, 14 characters are delayed. If I change the SERIAL_BUFFERS_SIZE constant in halconf.h to a larger number (default is 16) more characters are delayed. A smaller buffer size results in fewer characters being delayed.
Code: Select all
0123456789ABCDEF 0123456789ABCDEF 0123456789ABCDEF
0123456789ABCDEF 0123456789ABCDEF 0123456789ABCDEF
0123456789ABCDEF 0123456789ABCDEF 0123
My best guess for what is happening is that when the first line of output is written, it fills up the UART's TX fifo. At that point, the implementation of chSequentialStreamWrite() starts putting characters into a ChibiOS output queue. The characters keep coming fast enough to fill up the output queue, at which point the main thread is suspended. Later on when the UART has space in it's TX fifo, the ISR wakes up and copies a character from the output queue to the UART, which creates a free space in the queue and wakes up the main thread which writes another character before being suspended. Ideally, this will ping-pong back and forth until the output queue is empty.
Unfortunately, my implementation isn't emptying the queue correctly and I can't figure out why. Maybe one of the ChibiOS gods can spot the error? Here's my driver code:
Code: Select all
/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/
int tm4c_serial_fill_tx_fifo(SerialDriver *driver) {
uint32_t uart = driver->tm4c_uart;
int count = 0;
if (!chOQGetEmptyI(&driver->oqueue)) {
/* disable UART interrupt (ref TivaWare uartstdio.c:281) */
ROM_IntDisable(driver->tm4c_interrupt_id);
/* move characters from the output queue to the UART TX fifo */
while (ROM_UARTSpaceAvail(uart) && !chOQGetEmptyI(&driver->oqueue)) {
unsigned char data = (unsigned char) sdRequestDataI(driver);
ROM_UARTCharPutNonBlocking(uart, data);
count += 1;
}
/* reenable UART interrupt */
ROM_IntEnable(driver->tm4c_interrupt_id);
}
return count;
}
void tm4c_serial_uart_irq_handler(SerialDriver *driver) {
uint32_t uart = driver->tm4c_uart;
uint32_t uart_status = ROM_UARTIntStatus(uart, TRUE);
uint32_t uart_errors = uart_status & UART_ERROR_MASK;
ROM_UARTIntClear(uart, uart_status);
/* Output FIFO has space */
if (uart_status & UART_INT_TX) {
chSysLockFromIsr();
tm4c_serial_fill_tx_fifo(driver);
/* when the output queue is empty, turn off the TX interrupt */
if (chOQGetEmptyI(&driver->oqueue)) {
ROM_UARTIntDisable(uart, UART_INT_TX);
}
chSysUnlockFromIsr();
}
/* Read chars from input FIFO */
if (uart_status & (UART_INT_RT | UART_INT_RX)) {
chSysLockFromIsr();
while (ROM_UARTCharsAvail(uart)) {
int32_t ch = ROM_UARTCharGetNonBlocking(uart);
sdIncomingDataI(driver, (uint8_t) ch);
}
chSysUnlockFromIsr();
}
/* check for errors */
if (uart_errors) {
flagsmask_t ch_errors = 0;
/* convert Tiva error flags to ChibiOS flags */
if (uart_status & UART_INT_OE) ch_errors |= SD_OVERRUN_ERROR;
if (uart_status & UART_INT_BE) ch_errors |= SD_BREAK_DETECTED;
if (uart_status & UART_INT_PE) ch_errors |= SD_PARITY_ERROR;
if (uart_status & UART_INT_FE) ch_errors |= SD_FRAMING_ERROR;
/* broadcast updated flags to anyone listening */
chSysLockFromIsr();
chnAddFlagsI(driver, ch_errors);
chSysUnlockFromIsr();
/* clear error flags since they've been dealt with */
ROM_UARTRxErrorClear(uart);
}
}
/* output queue notification callback */
void tm4c_notify_write(GenericQueue *queue) {
SerialDriver *driver = (SerialDriver *) queue->q_link;
tm4c_serial_fill_tx_fifo(driver);
/* enable UART's TX interrupt if there are more characters to send */
if (!chOQGetEmptyI(&driver->oqueue)) {
ROM_UARTIntEnable(driver->tm4c_uart, UART_INT_TX);
}
}