Tuesday, May 12, 2020

Ring Buffers


In my earlier post about redirecting printf() to a UART, I mentioned that the implementation was 'blocking'. This means that the calling code has to wait for all the data to be printed out the serial port before it can continue. If, for example, you are sending out a modest string of 20 characters at 115,200 baud, your code will be sitting idle for around 1.7ms, and at 100MHz clock speed, that translates to around 173,000 (single-cycle) instructions that could have done something productive in the meantime.
What you really want is to hand the character processing off to a background routine so your mainline code can execute as quickly as possible. One very effective way to do this is to implement a ring buffer.
Imagine an array of 16 characters and two indexes:

#define TX_BUFF_SZ 16
char TxBuffer[TX_BUFF_SZ];
int InsertIDX = 0;
int ExtractIDX = 0;

When we want to write data into the buffer, we use the InsertIDX to write into the next available slot, and then increment the index;

TxBuffer[InsertIDX++] = write_ch;

And when InsertIDX gets to the end of the buffer, we wrap it back to zero as follows:

If (InsertIDX >= TX_BUFF_SZ)
InsertIDX = 0;

This code works in the generic case, but if you initially set your buffer size to a power of 2, you can get a little fancy by logically AND'ing InsertIDX to mask off the high-order bits.

InsertIDX &= (TX_BUFF_SZ - 1);

Putting it all together, we can create a generic ring-buffer write function that also enables a transmitter empty interrupt as follows:

void rb_putchar(char ch)
{
   TxBuffer[InsertIDX++] = ch;
   InsertIDX &= (TX_BUFF_SZ - 1);
   TxEmptyInterruptEnable = 1;    //Enable the TxEmpty interrupt
}

So long as TxBuffer is at least as large as the largest amount of data we might want to send out in a single printf() call, the above code will allow us to buffer the data and return control back to the calling routine as quickly as possible - i.e. it is no longer 'blocking'.

Now let's deal with feeding each character one-by-one to the USART. This would be done inside an TxEmpty interrupt service routine (ISR). Observe that when ExtractIDX catches up to InsertIDX there is no more data in the ring buffer to send out so we need to disable further TxEmpty interrupts:

void ISR_TxEmpty(void)
{
   UartTX = TxBuffer[ExtractIDX++];
   ExtractIDX &=(TX_BUFF_SZ - 1);
   if (ExtractIDX == InsertIDX)
      TxEmptyInterruptEnable = 0; //Disable further TX empty interrupts
}

Caveats

To help keep the focus on the core concepts, I've kept the code above very simply, but in practice, you'll need to qualify your declarations of InsertIDX and ExtractIDX as volatile so that the compiler recognizes these variables can be updated outside of the normal function execution path.

Monday, May 11, 2020

stdio - to buffer or not to buffer?


In my previous post, I showed how stdio could be redirected to send printf() output to an available USART. It was a basic implementation designed to get things moving, but you may encounter a little quirk when sending strings that don't end with a newline '\n' character.
By default, stdio buffers (holds onto) outgoing data until it sees a newline character; at which point it sends the entire line (to the serial port). The benefit of this is that it reduces the amount of low level interactions you need to have with the USART driver code, but the disadvantage is that you have to ensure each printf() call ends with a newline if you want the string to be immediately presented to the COM port.
I'll leave it up to you as to which implementation is best for your situation, but in case you decide to eliminate the input and output buffering, here's how to do it in C.
Somewhere in your initialization code, add the following lines:
setbuf(stdout, NULL);
setbuf(stderr, NULL);
setvbuf(stdin, NULL, _IONBF, 0);
The first line will remove buffering from stdout (which is where printf() goes by default)
The second line will remove buffering from stderr (if you are using that for directing error information)
The last line will remove buffering from stdin - which is the default for where scanf() will look for input.

The juicy details can be found here:
https://www.gnu.org/software/libc/manual/html_node/Controlling-Buffering.html


Tuesday, May 5, 2020

Implementing printf() on a UART

When developing on a new embedded (bare metal) platform, one of the first things I look for (after getting an LED to flash) is the ability to output text to a display. Usually the simplest path is to commandeer one of the UARTs and to start bashing characters out the TX pin and receiving them at the host end through a PuTTY terminal. So how to implement this efficiently?

The first question you'll need to resolve is how to get characters from your code into the UART. Hopefully the frawework you are using provides a simple setup process and wrapper to allow you to initialize the UART and send a single character. In the STM32CubeIDE environment, the UART properties can be setup fairly simply:

115200 is a pretty safe baud rate to start with. I've also shown the corresponding PuTTY settings too. Once you've proved that basic communications are working, you will probably be able to set the baud rate much higher; although take care when doing this. Even though the STM32 might be able to do 8Mbaud, FTDI-based USB Serial devices will top out at 3Mbaud: (https://www.ftdichip.com/Support/Knowledgebase/index.html?whatbaudratesareachieveabl.htm)
In any case, let's assume you've correctly setup the comms basics (don't forget to make sure the parity and stop bits match). Next job is to send something simple.

Once again, using the STM32CubeIDE environment as an example, ST offers a simple function call as part of the HAL (Hardware Abstraction Layer) interface:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
Assuming STM32CubeIDE has instantiated the UART handle as "huart1", you can make a simple call from your code like this:
char msg[] = "Hello World";
hres = HAL_UART_Transmit(huart1, msg, strlen(msg), 100);

Tying into printf()

Sending out "Hello World" might be initially very gratifying, but eventually you'll need to send out more advanced information such as variable values. One tempting option is to jump in and write your own MyPrintf() function, but that would involve a whole lot of re-inventing the wheel. A much better option is to tie into the existing functionality that already exists. Fortunately, the authors of most embedded frameworks understand that developers will want to redirect their output through various peripherals so they provide the infrastructure to readily support it.
If you dig deep enough, you'll find that after several layers of code, the standard printf() function distills down to one or two low-level functions. Take a look in the syscalls.c file for the _write() function:
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar(*ptr++);
}
return len;
}
Take note that this function is prefaced by __attribute__((weak)). This is a message to the compiler to look elsewhere in the code for a _write() function and use that one first, but if it can't find one then use this one. In effect, it allows you to implement your own _write() function within your codebase without needing to modify the library source files. A UART-based implementation would therefore be:
int _write(int file, char *ptr, int len)
{
HAL_StatusTypeDef hres;
hres = HAL_UART_Transmit(huart1, (uint8_t*)ptr, len, TX_BLOCKING_TIMEOUT);
if (hres == HAL_OK)
return len;
else
return -1;
}
With this function now in your code, you can access all of the wonderful formatting capabilities that come with printf() and your output will be magically directed to the serial port.

Concluding Remarks

The implementation above gets you up and running with basic character output capabilities but there's a hidden problem you may face further down the track. This implementation is 'blocking'; which means that it waits until all characters have been successfully piped out the serial port before it returns control back to the calling function. In timing critical applications or when sending large volumes of data, this could have an impact on the performance of your system, so in another blog entry, I'll talk about how to implement a ring-buffer and interrupts so your code doesn't have to wait around for the UART to finish its thing.