Advanced UART Functions

From Nutwiki
Revision as of 17:02, 27 October 2016 by Harald (Talk | contribs) (1 revision imported)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Modifying buffer sizes

Nut/OS uses a circular buffer for UART communication. Filling the receive buffer and flushing the transmit buffer is done in the background by the interrupt handler. This releases the application from the trying to catch up with each single character (87us at 115200 Baud) and allows to use high level functions like printf or gets without hassle.

The standard U(S)ART driver allows to change transmit and receive buffer sizes by calling specific ioctl functions. Typically both buffers are set to 256 characters. You can save some RAM by reducing these sizes. Or you may increase the receive buffer size to let slow applications receive large amounts of data without losing characters. In general, larger buffers increase the overall performance of the system.

Be aware, that some drivers may not support variable buffers. For example devUart0 and devUart1 for the AVR are quite limited. They use fixed buffers for maximum performance at reduced interrupt latency times. On the AVR platform use devUsartAvr0 and devUsartAvr1 to get the full set of features. It is always a good idea to check the result of your ioctl() call. If it returns 0, then the function is supported.

Note, that the debug drivers are running in polling mode. They are usable for console output only and often do not even allow to change the baud rate.


Receive buffer size

Nut/OS uses three configuration values to handle the receive buffer. Beside the buffer size there is also a low and high watermark. When changing the buffer size you need to make sure, that both watermarks are within the buffer's range. Otherwise strange things may happen.

The receive buffer watermarks are used with hardware and software handshake. Let's assume the following settings:

<source lang="c"> uint32_t parm; FILE *uart1 = fopen(DEV_UART1_NAME, "rb");

parm = 128; _ioctl(_fileno(uart1), UART_SETRXBUFSIZ, &parm); parm = 64; _ioctl(_fileno(uart1), UART_SETRXBUFLWMARK, &parm); parm = 120; _ioctl(_fileno(uart1), UART_SETRXBUFHWMARK, &parm); </source>

The total size of the receive buffer is 128, but as soon as 120 characters are received, the driver will try to stop the transmitting party by either disabling RTS (hardware handshake) or transmitting an XOFF character (software handshake). The transmitter may not be able stop immediately, but there are still 8 bytes available in our buffer.

Sooner or later the application will read one or more characters. As soon as the buffer contents drops below 64 characters, the RTS line is enabled again or an XON character is transmitted to signal the transmitter that we are ready for more data.

Transmit buffer size

This works quite similar. Again we must ensure, that high and low watermarks are properly set when changing the total buffer size. However, the meaning of the watermarks is quite different. As an example we use the following settings:

<source lang="c"> uint32_t parm; FILE *uart1 = fopen(DEV_UART1_NAME, "wb");

parm = 128; _ioctl(_fileno(uart1), UART_SETTXBUFSIZ, &parm); parm = 64; _ioctl(_fileno(uart1), UART_SETTXBUFLWMARK, &parm); parm = 120; _ioctl(_fileno(uart1), UART_SETTXBUFHWMARK, &parm); </source>

When the application starts writing to the UART device, nothing will happen, except that the buffer gets filled. As soon as the high watermark is reached, the driver will enable transmit interrupts. Typically the application is writing faster than characters are sent out. If the buffer becomes completely filled, then the application thread will be blocked and remains blocked until the buffer reaches the low watermark.

Querying the buffer status

While UART communication is done in the background, it is sometimes required to query the current status.

<source lang="c"> _ioctl(_fileno(uart1), UART_GETSTATUS, &parm); if (parm & UART_RXBUFFEREMPTY) {

 /* No more bytes in the receiver buffer. */

} if (parm & UART_TXBUFFEREMPTY) {

 /* No more bytes in the transmit buffer. */

} </source>

Cooked vs. raw mode

Displaying or printing an end of a text line requires two control characters:

  1. A carriage return (ASCII 13) to move the current position back to the beginning of the line
  2. A linefeed (ASCII 10) to advance the position to the next line

The C language uses a single linefeed character as a line terminator. This saves space, but provides a problem when displaying or printing raw contents. To solve this, an output device can be put into cooked mode. That means, that a carriage return is automatically inserted before transmitting the linefeed character. If you want to transmit binary data, this additional carriage return will probably spoil your data. Thus, when opening a file, you must specify whether you want to transmit text in cooked mode or binary data in raw mode.

<source lang="c"> uart1 = fopen(DEV_UART1_NAME, "w"); /* Open for writing text. */ uart1 = fopen(DEV_UART1_NAME, "r"); /* Open for reading text. */ uart1 = fopen(DEV_UART1_NAME, "r+"); /* Open for reading and writing text. */ uart1 = fopen(DEV_UART1_NAME, "wb"); /* Open for writing binary data. */ uart1 = fopen(DEV_UART1_NAME, "rb"); /* Open for reading binary data. */ uart1 = fopen(DEV_UART1_NAME, "r+b"); /* Open for reading and writing binary data. */ </source>

In most cases this works fine and is handled by the C library while moving the data from the application space to the stream I/O buffer. In order to save memory and to avoid another memory copy, Nut/OS moves the data directly to the driver's buffer and the translation takes place in the driver's transmit routine.

One problem will arise when you try to open the same device concurrently from two threads. In general this is possible, but the driver won't be able to switch the mode, if it differs on each open.

In some cases you may need to switch from binary to text mode or vice versa without reopening the file. Here's how this can be done:

<source lang="c"> int on;

/* Open device in raw mode. */ FILE *uart1 = fopen(DEV_UART1_NAME, "wb");

/* Switch to cooked text mode. */ on = 1; _ioctl(_fileno(uart1), UART_SETCOOKEDMODE, &on); /* Switch back to raw binary mode. */ on = 0; _ioctl(_fileno(uart1), UART_SETCOOKEDMODE, &on); </source>

Note, that you can access the same file stream concurrently from several threads. Thus, there is actually no need to open a device more than once.

Timeouts

Almost all blocking Nut/OS drivers allow to specify a maximum waiting time. Again, the polling debug driver is an exception to this rule.

Receive timeout

By default the application thread will be blocked on reading until at least one character is available in the receiver buffer.

<source lang="c"> uint32_t ms;

ms = 1000; /* 1 second timeout. */ _ioctl(_fileno(uart1), UART_SETREADTIMEOUT, &ms); </source>

You'll typically use high level functions to read from the device. If no more characters are available in the buffer after the timeout time elapses, then the application is woken up and the high level function will return zero as the number of characters read.

However, getc(), fgetc() and similar functions either return the next character or EOF, which may mean two things: Either an error occurred (out of memory, device was closed by another thread etc.) or the timeout had been reached. Internally a timeout is handled like an end of file. You can use ferror() and feof() to determine the actual cause.

Transmit timeout

When writing, the application thread is blocked until all characters had been moved to the drivers transmit buffer. When using handshake, it may be blocked forever. To avoid this, you can set a transmit timeout, given in milliseconds:

<source lang="c"> uint32_t ms;

ms = 1000; /* 1 second timeout. */ _ioctl(_fileno(uart1), UART_SETWRITETIMEOUT, &ms); </source>

If the timeout time elapses the application is woken up. The high level function may either return less characters transmitted than the number of characters passed to this function or return an error if no characters were transmitted at all. Thus, when using timeouts, you should always check the result of fprintf etc.

When using low baud rates, you must not specify too short timeouts. Blocking should be allowed for at least the transmission time of 2 characters.

Checking for errors

Error checking is not that advanced. In fact, only the advanced drivers keep track of a few specific errors. Anyway, the following code should work with all drivers, even if not all errors are reported:

<source lang="c"> uint32_t parm;

if (_ioctl(_fileno(uart1), UART_GETSTATUS, &parm) == 0) {

 if (parm & UART_FRAMINGERROR) {
   /* Receiver framing error. */
 }
 if (parm & UART_PARITYERROR) {
   /* Receiver parity error. */
 }
 if (parm & UART_OVERRUNERROR) {
   /* Receiver buffer overflow. */
 }

} </source>

See also

External Links



Template:Languages