Need help implementing a Serial Driver

ChibiOS public support forum for all topics not covered by a specific support forum.

Moderators: RoccoMarco, lbednarz, tfAteba

Post Reply
andyturk
Posts: 23
Joined: Tue May 01, 2012 7:22 pm

Need help implementing a Serial Driver

Post by andyturk »

I'm having a problem getting the output side of my UART serial driver implementation to work. All the characters written eventually make it to the UART, but the annoying part is that a "chunk" of output won't be sent until there's more output. A small example will demonstrate:

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);
  }
}
User avatar
Giovanni
Site Admin
Posts: 14891
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Need help implementing a Serial Driver

Post by Giovanni »

Probably the driver closer to your is the one of LPC chips, that UART has FIFO buffers too, try comparing with that code.

Giovanni
andyturk
Posts: 23
Joined: Tue May 01, 2012 7:22 pm

Re: Need help implementing a Serial Driver

Post by andyturk »

Thanks, I did take a look at the LPC drivers. The implementation seems strange though, because the "preload" routine always tries to send 16 characters into the TX fifo, without ever looking to see if it's full. I'm sure it works, but it's fairly different than what TI's implementation will need (I think).

Anyway, in the process of staring at some other code, I realized that I'd been using chOQGetEmptyI() to tell if the output queue was empty. I should have been using chOQIsEmptyI(). The driver works fine now, problem solved!
:mrgreen:
Post Reply