Ring buffer using head and tail in STM32

Few months ago, I made a tutorial about circular buffer in STM32 using DMA and idle line detection. Although it was working pretty well, it was a little bit complex to work with and had few problems. Today in this tutorial, I am going to show you guys an alternative for that. Here, we are going to implement ring buffer using head and tail method in STM32.

************ UPDATE 3 ************

I have modified the code so that it can be used with other functions as well, so no more blocking usage. Although it’s not fully stable, but it can be used. I will push these updates to github from now onwards. Any suggestions are welcome.. you can download the ring buffer library from

GitHub – controllerstech/stm32-uart-ring-buffer

************* UPDATE 2 ***************

L4 series and G series MCUs have some issues with UART_ISR_TXE. If you are having issues even after making changes according to UPDATE 1, then Replace the Uart_isr with the file https://controllerstech.com/wp-content/uploads/2020/08/Uart_Isr_single_new.c

************* UPDATE 1 ***************

Those, who have errors in the Uart_isr function, eg- F7 series MCU, replace the Uart_isr functon with the one provided in this file https://controllerstech.com/wp-content/uploads/2020/04/Uart_Isr_single.c

How the ring buffer works

Well, I would suggest that you google this query and take a look at the wikipedia page. There you will get a better explanation of the topic. But anyway, i will try to explain it, as much as i can.

Whenever UART receives data in the rx_buffer, the head gets incremented by 1. And when we read that data, tail gets incremented by 1. Now let’s assume that we sent a string “hello“. So the UART received 5 characters and they get stored in the rx_buffer. So head will now have a value of 5. The tail is still at 0, as we haven’t read the characters yet. If we read the first character, ‘h‘, tail will get incremented by 1. And once we read the entire string, head and tail will both be at 5.

Once the buffer is full, or the head is at the end of the buffer, on receiving new character, it will again go to the beginning of the buffer i.e at 0.

Reading the buffer, so that the tail can increment, is very important part of this process. The data will only be written in circular direction, if the data at that position (start of buffer) is already read. In other words, if the difference between head and tail is more than the buffer size, no data will be received in the buffer.



HOW TO

Setting up the ring buffer in STM32 is very easy, Easier than circular buffer at least. Below is my Cubemx setup. I am using baud rate of 115200 and interrupt is enabled. The rest is the usual setup.

CubeMx setup for uart

Next copy the uartringbuffer.h and uartringbuffer.c files in the code and also include it in the main file. These files are located under /src and /inc folders of the code attached here. You should also copy them to the same locations in your project.

Once copied, open the stm32….it.c file and copy extern void Uart_isr (UART_HandleTypeDef *huart); in the file. And at last we need to replace the default ISR with the one we have. So browse down the file to the void USART1_IRQHandler(void) and replace the default ISR just as shown in the picture below

ISR file edit

This completes the setup part Now let’s take a look inside some functions available.






Some Insight into the CODE

int IsDataAvailable(void);

checks whether the data is available to read. It returns 1 is there is some data in the rx_buffer, which is not read yet.


int Uart_read(void);

Reads the data from the rx_buffer and increment the tail by 1. The head in the rx_buffer get incremented when the data is received in the buffer via the interrupt. only reads 1 character and returns it.


/* Peek for the data in the Rx Bffer without incrementing the tail count 
* Returns the character
* USAGE: if (Uart_peek () == 'M') do something 
*/
int Uart_peek();

Uart_peek Peek for the data in the Rx Buffer without incrementing the tail count. This is useful, if you want to check a character or a string, without reading the data. It returns the character.


void Uart_write(int c); 

writes the data to the tx_buffer and increment the head counter in the tx_buffer by 1. From where, the data is sent to the uart using interrupt and tail get incremented. This function only writes 1 character at a time.


void Uart_sendstring(const char *s);

prints the entire string to the uart.


void Uart_printbase (long n, uint8_t base);

prints the numbers in different format to the uart.


/* Copy the data from the Rx buffer into the buffer, Upto and including the entered string 
* This copying will take place in the blocking mode, so you won't be able to perform any other operations
* Returns 1 on success and -1 otherwise
* USAGE: while (!(Copy_Upto ("some string", buffer)));
*/
int Copy_upto (char *string, char *buffertocopyinto);

Copy_upto Copies the data from the Rx buffer into the entered buffer. But it will copy until the entered string is reached.


/* Copies the entered number of characters (blocking mode) from the Rx buffer into the buffer, after some particular string is detected
* Returns 1 on success and -1 otherwise
* USAGE: while (!(Get_after ("some string", 6, buffer)));
*/
int Get_after (char *string, uint8_t numberofchars, char *buffertosave);

Get_after function is used to get the predefined number of characters after the entered string is detected in the incoming data. It will save the characters into the entered buffer.


/* Wait until a paricular string is detected in the Rx Buffer
* Return 1 on success and -1 otherwise
* USAGE: while (!(Wait_for("some string")));
*/
int Wait_for (char *string);

Wait_for will wait until a predefined string is received in the Rx buffer. If it does, than the function will return 1.


/* Look for a particular string in the given buffer
 * @return 1, if the string is found and -1 if not found
 * @USAGE:: if (Look_for ("some string", buffer)) do something
 */
int Look_for (char *str, char *buffertolookinto);

Look_for will look for a particular string inside the given buffer. If the string is found, it will return 1.



Let’s see an example to read and write the data

while (IsDataAvailable())
{
  int data = Uart_read();  // read the data in the rx_buffer
  Uart_write(data);  // send the data to the uart 
}

So, if the data is available in the rx_buffer, it will be read using Uart_read () function and than again written back to the uart. This way you can test whether the things are working properly or not. Below is the screenshot of the terminal

ring buffer working

Note that the pink color is the one that I am sending to the microcontroller and the black one is the received data back in the computer. As you can see above that either I sent 1 character, 2 characters or 5 characters, I always received the same data back from the microcontroller.

This way we can always receive the data of unknown length. Make sure that the size of the rx_buffer (in the uartringbuffer.h) is according to your need. If the data received at once, is higher than the size, the whole code will block there and you might have to reset the controller.



Result

Check out the Video Below










Info

You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

49 Comments. Leave new

  • Thank you sir for your great explanation!, I have a question I got this error after compiling ( multiple definition of `huart1′ , first defined here). so the first definition was in the main and the second one was in the UartRingbuffer.c line 15. how this can be solved? also I got this error (make: *** [makefile:67: Ring_Buffer_Uart.elf] Error 1) maybe it is related to the first error. thank you so much

    Reply
  • Hi,
    Thanks a lot for this great tutorial.
    I have a question that when we use this  

    if (IsDataAvailable())
       {
       int data = Uart_read();
       Uart_write (data);
       }

    Function Uart_read() will read the data in the rx_buffer and assign to variable “data”. But the question is that how are the data stored in the rx_buffer?

    Reply
  • Hi,
    Thanks a lot for this great tutorial.
    Can we use this library for multiple uart?

    Reply
  • Thanks a lot for this video …
    i am using you code with Stm32f4 and it works perfect .
    now i am trying to use it with Stm32h7 but it is totally not working, could you help me please

    Reply
  • Jestina Joy
    May 25, 2021 12:38 PM

    Whether the code will work on STM32H7 series?. This is not working for me when i tried. Please tell me what are the changes required for STM32H7A3 board?

    Reply
    • wait for few days if you can. I am working on something very simpler, and more reliable. I will update you once it’s ready.
      Or contact on telegram or discord, if you need urgently

      Reply
    • Hi,
      Could you please write if you found any solution for STM32H7 with Ringbuffer.

      Reply
  • Thanks for a great alternative to the DMA + Idle Line Detection. However, isn’t DMA more efficient? Would the best way be to try combine both?

    Reply
  • Hi there,
    Thank you for the useful tutorial. TOP
    The function “void Uart_printbase (long n, uint8_t base);” is missing in the c file. It’s just declarated in the header file of ringbuffer. Can you post the function code?Many thanks Thomas

    Reply
  • thanks for the great tutorial:

    Remark, copy paste example code will produce a waiting for hello string!

       //general repeater
       {
        if (IsDataAvailable())
        {
        int data = Uart_read();
        Uart_write (data);
        }
    is what is shown in tutorial!

    Reply
  • Hi! I’m wondering, is there any chance that UART might cause an interrupt at the same time as you are sending data (uart write? Or like if you want to save the data to a sdcard (spi). Is there any danger than another incoming uart message interrupts the mcu, and crashes the current transmission? Or is it safe to toggle between sending and uart interrupt?

    Reply
    • well there is possibility that it can interfere with the ongoing operation. you can change priorities of interrupts according to your requirement

      Reply
  • Hi,
    In souce file: UartRingbuffer.c 34 line is a empty void function store_char? These same function is in 49 line with function.

    How can I get a data buffer? copy upto dosent work, can you write the example in comment??

    You using goto function. Maybe it is better use a do while loop?
    Example (im not tested this function)

    int Look_for (char *str, char *buffertolookinto)
    {
    int stringlength = strlen (str);
    int bufferlength = strlen (buffertolookinto);
    int so_far = 0;
    int indx = 0;
    do{
    int k=0;
    while (str[so_far] != buffertolookinto[indx]) indx++;
    if (str[so_far] == buffertolookinto[indx])
    {
    while (str[so_far] == buffertolookinto[indx])
    {
    so_far++;
    indx++;
    }
    }

    else
    {
    so_far =0;
    if (indx >= bufferlength) return -1;
    k=1;
    }
    while(k=0)

    if (so_far == stringlength) return 1;
    else return -1;
    }
    What you think?

    Reply
    • 34 is function definition and 49 is where the function is written

      copy_upto works fine. You should probably use the wait_for function first. For example let’s say the incoming string is “This is string one for test” and from this i want to copy “strng one” into some buffer
      while (!(wait_for (“this “));
      copy_upto (“one”, buffer);

      I don’t have enough time for now, one I do i will test that function and let you know. In the mean time you can test too and if the results are good, please inform me
      this ring buffer needs a lot of improvements and suggestions are always welcome

      I have included timeout feature also, try it and see if it’s any good
      https://github.com/controllerstech/stm32-uart-ring-buffer

      Reply
  • vitthal muddapur
    January 22, 2021 10:19 PM

    Hi
    Thank you

    In wait_for function lets consider only one byte of data available in buffer, first while loop break, in second while loop entered string is not equal to buffer char for first character, then while loop is true, increment the tail, next iteration not checking data is available or not. only check string of index correct or not. continuously increases the tail.

    while (!IsDataAvailable());
    while (Uart_peek() != string[so_far]) _rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1)

    int Wait_for (char *string)
    {
    int so_far =0;
    int len = strlen (string);

    again:
    while (!IsDataAvailable());
    while (Uart_peek() != string[so_far]) _rx_buffer->tail = (unsigned int)(_rx_buffer->tail + 1) % UART_BUFFER_SIZE;
    while (Uart_peek() == string [so_far])
    {
    so_far++;
    Uart_read();
    if (so_far == len) return 1;
    while (!IsDataAvailable());
    }

    if (so_far != len)
    {
    so_far = 0;
    goto again;
    }

    if (so_far == len) return 1;
    else return -1;
    }

    Instead of second while loop, use if condition am I right ?

    or

    check inside second while loop need to check while (!IsDataAvailable());

    while (!IsDataAvailable());
    Thanks for your effort.

    Reply
    • next iteration not checking data is available or not. only check string of index correct or not. continuously increases the tail.

      No it won’t increment the tail. while (Uart_peek() == string [so_far]) this will be false and the goto:again statement will be executed

      Reply
  • Hi dude, great code!! Quick question, is UART BUFFER SIZE 1024 max? Or if the MCU has more flash memory to spare, can the value be greater? Maybe I’ve misunderstood, but the larger the buffer size the better, no?

    Reply
  • Hi! Thank you for your kind explanation.
    At first, I tested that with baud rate 9600. Unfortunately it didn’ work..
    hmm.. After changing to 115200, it works.
    Do you know why did that happen?

    Reply
    • Did you set up uart with baud rate 9600 on the microcontroller ?

      Reply
      • Yes, when i setup the baud rate of the MCU uart to 9600, the characters didn’t show up on serial terminal.
        Finally, i solved it, by reducing the size of standard ouput buffer.

        Ringbuf_init();
        setvbuf(stdout, NULL, _IONBF, 0);

        By the way, is it possible to use multi-RingBuffer on stm32f103c8?
        both UART1 and UART2 ?

        Reply
        • yes it is possible.
          search for ring buffer in the website, there is an article about managing multiple uarts using ring buffer

          Reply
  • Vũ Thị Hoàng Lan
    December 7, 2020 4:33 PM

    Your guide has been very helpful and I have used Ring_buffer many times. But this time I have to use HAL_UART_Transmit, but when I use it, Ringbuf rx_buffer can’t get bigger data than bufsize! (It seems like a normal buffer.)
    Is there a way to use HAL_UART_Transmit and Ring_buffer at the same time?

    As always, thank you very much!

    Reply
    • Vũ Thị Hoàng Lan
      December 7, 2020 8:05 PM

      Oh no, since I didn’t notice one small bug, my goodness it still runs fine

      Reply
    • hy,
      are you using ring buffer without an interrupt function .if yes can you help me out .
      I am using stm32f7 series and facing issue after updating 3 as well from the author.

      Reply
  • Hi, thanks for the outstanding solution of the ring buffer for stm32.

    I have a question about the working of the Copy_upto function. In the code I use it to copy incoming data to my local temporary buffer which I then parse. The thing is the function works fine when the string until which it should copy is present in the data, but the program freezes if the string until which it should copy is not present in the incoming data. I use it in the code as follows:

    if(IsDataAvailable())
    {
    Copy_upto(“\n”, buf);
    Parser(buf);
    }

    Is there a way to handle the situation when the incoming data doesn’t contain the needed string so that program does not freeze and continues its work ignoring the absence of the string?
    Thank you in advance
    Best regards

    Reply
  • Hi, I really apreciate the knowledge you’re sharing.
    I met a problem while using HAL_UART_Receive_DMA. The first call leads to a CompleteRXCallback fucntion. But then, even if an external emitter device continuously sends 100 bytes blocks very 500ms, I never get RXCompleteCallback fucntion called. Only if I press the reset button on the STM32 Discovery board, I get a new call to the function.
    Any idea will be welcome

    Many thanks.

    Reply
    • how many bytes are you receiving with DMA ?
      until the entered number of bytes aren’t received, callback won’t be called.
      another thing you can try is use the DMA in circular mode instead of the normal one

      Reply
  • Ishak abdellahi
    July 17, 2020 4:50 PM

    Hello thanks a lot it’s very helpful,
    I’ve used this code to implement an uart communication I want to send 160 characters and receive it, and I make UART_BUFFER_SIZE 1024 but when I do this 6 time the buffer get overflow and it crash.
    I want that the header go back to 0 every time I send and receive the 160 characters to prevent overflow problem?
    Thank you again.

    Reply
    • There is no way that the buffer can overflow. This code works as a ring buffer, so when you reach 1023, the next data will be automatically received in the first position.

      I used the same code in the esp webserver video, where the data received was more than 1KB, and UART BUFFER SIZE was only 64 bytes. And still everything works properly.

      Make sure you are following the steps properly. You need to edit the interrupt handler in STM32xxxIT.c file.

      Reply
  • Hi,

    Thank you for the tutorial! It is very helpful.
    However, I tried to send number of hexadecimals and the receiver side only gets numbers without “00”. for example,
    Transmitter side sends 55 00 00 c1 a2, and receiver side gets 55 c1 a2 without 00 00.
    Do you know why it happens and how to fix?
    -> Just found the answer in .c file. Changed if(c>0) to if(c>=0) and it gets “00” as well.

    Another question is that, I’m sending 55 00 00 c1 a2 hexdecimal.
    And would like to check 55 as a header. and a2 is an checksum. Is there any way that I can read those 5 bytes are one set of procotol so that I can check each bytes location and compare?

    Thank you in advance.
    Won

    Reply
  • Hi

    Thanks for the code and explanation. i have a question: is it possible to send an interrupt when receiving data is completely done and the line is idle using this library?

    Reply
  • How about porting this great code to xilinx SDK ? Should work ? XUartPs_Send() and XUartPs_Recv() ?

    Reply
  • Hi,

    Thanks for sharing this code. The ZIP file that is included with this post has two methods that look very similar, namely Get_string and wait_until. The wait_until method included probably does not work as intended. It has two input parameters, “char *string” and “char *buffertostore”. In the method, one would expect the “*string” parameter to be used to, as the name implies, wait until that string has been received into the ring buffer, but the method does not use this parameter at all (in the shared code). Instead it looks like it only searches for the “\n” (line feed) character to be received. Your example in your video seems to have a wait_until method that works as intended. Could you maybe update the ZIP of this post with the wait_until method that you’ve used in that example?

    Thank you,
    Thomas

    Reply
  • Hey is it Possible to control 5 different LED by One push button switch?

    Reply
    • yes..
      if (button pressed)
      {
      while (button pressed);
      count++;
      if (count>5) count =1;
      }

      if (count == 1) led1 = ON; Other leds off;
      if (count == 2) led2 = ON; Other leds off;
      and so on…..

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

keyboard_arrow_up

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×