Code Monkey home page Code Monkey logo

lwrb's Introduction

Lightweight ring buffer manager

Library provides generic FIFO ring buffer implementation.

Read first: Documentation

Features

  • Written in C (C11), compatible with size_t for size data types
  • Platform independent default code - with restrictions for smaller CPU architectures (< sizeof(size_t))
  • FIFO (First In First Out) buffer implementation
  • No dynamic memory allocation, data is static array
  • Uses optimized memory copy instead of loops to read/write data from/to memory
  • Thread safe when used as pipe with single write and single read entries - when CPU read/write operation for size_t are single instruction (ARM Cortex-M for instance)
  • Interrupt safe when used as pipe with single write and single read entries - when CPU read/write operation for size_t are single instruction (ARM Cortex-M for instance)
  • For CPU systems with smaller architecture than sizeof(size_t) (AVR for instance), atomic protection is required for read-write operation of buffer writes
  • Suitable for DMA transfers from and to memory with zero-copy overhead between buffer and application memory
  • Supports data peek, skip for read and advance for write
  • Implements support for event notifications
  • User friendly MIT license

Contribute

Fresh contributions are always welcome. Simple instructions to proceed:

  1. Fork Github repository
  2. Follow C style & coding rules already used in the project
  3. Create a pull request to develop branch with new features or bug fixes

Alternatively you may:

  1. Report a bug
  2. Ask for a feature request

lwrb's People

Contributors

dependabot[bot] avatar jackistang avatar jdeokkim avatar jnz86 avatar jyhi avatar linjieqiang avatar majerle avatar matstm avatar thomasdevoogdt avatar tsonono avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lwrb's Issues

Linear block lengths are computed incorrectly

I think that lwrb_get_linear_block_write_length() and lwrb_get_linear_block_read_length() incorrectly compute the linear lengths that can be written or read before wrapping.

The code and comments in the former function suggest you were trying to work through the indexing and settled on the --len; fix for some issue; but I think this is not the correct solution. The whole point of the if (w >= r) clause is that the linear-write-length does not depend on r, it only depends on the number of bytes between w and the end of the usable array. It should perhaps be:

<lwrb_get_linear_block_write_length>
...
if (w >= r) {
  len = buf->size - w - 1;
} else {
  len = r - w - 1;  // no change here
}

Similarly in lwrb_get_linear_block_read_length(), it should perhaps be:

<lwrb_get_linear_block_read_length>
...
if (r > w) {
  len = buf->size - r - 1;
} else {
  len = w - r;  // no change here
}

And you can omit the third clause there.

included c header file in extern "C" section

In lwrb.h, inside the extern "C" section, there is a c header include, specifically #include <stdatomic.h", when including this header file from c++ files, the included stdatomic.h is actually the cpp version, so it does not compile.
It is fixable by moving the include out of the extern "C" section.

about BUF_IS_VALID(b)

File lwrb.c
#define BUF_IS_VALID(b) ((b) != NULL && (b)->magic1 == BUF_MAGIC1 && (b)->magic2 == ~BUF_MAGIC2 && (b)->buff != NULL && (b)->size > 0)

should be

#define BUF_IS_VALID(b) ((b) != NULL && (b)->magic1 == BUF_MAGIC1 && (b)->magic2 == BUF_MAGIC2 && (b)->buff != NULL && (b)->size > 0)


Otherwise, the judgment is wrong。

useless “if"

/* Check maximum number of bytes available to read after skip */
btp = BUF_MIN(full, btp);
if (btp == 0) {
return 0;
}
if "full" or "btp" value is 0, function will return 0 before this

extend API to avoid memcpy

I am wondering if you have considered a version of the API that avoids memory copy of the buffer. In our case we are interested to store pointers' addresses in the ring buffer, so avoiding memcpy might be ideal.

is it possible?

Additions I made you may want to know about

Just a small heads up in case you think any of these are a good idea and worth adding in to the repo.

Nothing I wrote here was good. In fact, it was all quite bad. For the most part I just used your existing functions with small logic around them. This wasn't as efficient as writing them "whole" but got the job done.

  • size_t lwrb_overwrite(lwrb_t* buff, const void* data, size_t btw) - Functions like _write() but does not stop if it runs out of room will always return the total data stored. I needed a function that would take a rb that didn't have enough space for the incoming data, remove old data, and fit the new packet in. My use case for this was storing a log in a rolling buffer, it would be nice to not lose data, but it's more important to me in this case to lose some old data than new. One issue with this (and appeared somewhere else) is because this is an overwrite function, if I sent more than the size of the buffer, I wanted to store the latest data not the earliest. So if the size was 100, and I wrote 200, I wanted the second half of my data to be stored, not the first.

  • size_t lwrb_copy(lwrb_t* dest, lwrb_t* src) - Copies one ring buffer to another. I needed to dump a rb for uart to a tokenizing assembly buffer. The issue here was that you can run into size limitations if one buffer is larger than the other. It also didn't make sense for my application to do this byte by byte, but I also couldn't be sure I had enough stack to allocate the buffer size on entry so I looked at packets of 128 bytes or smaller if there was less data.

  • size_t lwrb_skip_up_to_offset(lwrb_t* buff, size_t len) - Your _peek has an offset. I needed the same thing for skip. I knew in one application I wanted to get rid of everything but the last n bytes. In my case, again I was tokenizing / parsing, and looking for a deliminator of a few bytes. If I couldn't find three byte delim, I didn't want to clear the last two bytes in the buffer, because those might turn into a complete deliminator soon. (ie: 01 02 03 wasn't found but 01 02 was and an 03 would be coming soon).

  • uint8_t lwrb_find_seq(lwrb_t* buff, const void* const TOKEN, const size_t LEN_TOKEN, size_t* len) - I wanted to search through the ring buffer for a sequence. This was more difficult than it seemed it should have been the complexity is that just like ring buffers you don't know if your sequence is wrapped or not. In the end, I was in a hurry and made a linear copy of the whole buffer and quickly searched it. This returned true/false for found, and stored the position in at a de-referenced len. That might not be how you like to do it, but I prefer returning statuses and did not want to deal with a -1 for "not found". I also noticed you didn't use bool, although I normally would (no issue for C23!).

Nothing to do unless you want to. I would certainly find your take on these probably better than mine with the time I put in. Thought you might want to see how someone was using your code.

(int)(!!found == !!(_exp_result_))

Hi, MaJerle
in dev/main.c

#define FIND_TEST(_bts_, _bts_len_, _start_offset_, _exp_result_)                                                      \
    do {                                                                                                               \
        lwrb_sz_t found_idx;                                                                                           \
        uint8_t found;                                                                                                 \
        found = lwrb_find(&buff, (_bts_), (_bts_len_), (_start_offset_), &found_idx);                                  \
        printf("Find \"%s\" (len %d), start_offset: %d, found_index: %d; Found: %d; As expected: %d\r\n", (_bts_),     \
               (_bts_len_), (_start_offset_), (int)found_idx, (int)found, (int)(!!found == !!(_exp_result_)));         \
    } while (0)

why use (int)(!!found == !!(_exp_result_)) but just (int)(found == (_exp_result_)) ?

Is `volatile` keyword necessery for local variables of functions?

Thank you for sharing the lib. And It helps me a lot.

I wonder Is the volatile necessery for local variables of functions. I mean volatile forces compiler to access variables directly and do not use registers for optimization. But these local variables are just temporaries of real values. And they are in stack or registers, unlike the real values in struct, nobody will change them.

V3.0.0 C11<stdatomic.h> is not supported, is it OK to use it on a microcontroller?

hi:
I ported the latest v3.0.0 on top of the STM32F4. The development platform used keil , when compiling it shows cannot open source input file "stdatomic.h":, my compiler does not support C11 syntax.
Define LWRB_DISABLE_ATOMIC , compile can can pass, so I use j it in the microcontroller above to store the serial port to receive data is there any risk. In v2.0.2, the volatile keyword is used, but v3.0.0 does not support it.

Does lwrb_reset are thread safety?

I means the thread 1 are writing data, and thread 2 are calling to lwrb_reset,
does lwrb_reset are thread safety?
,or in reverse mode
If this is not thread safety, how to getting it thread safety, in rare case, I need to calling lwrb_reset
to reset the ring buffer

Find not reaching last elements

I believe lwrb_find is not reaching all the way to the end of the buffer to find the needle.

The function is looping buff full - input len - start_offset, but I believe the condition in the foor loop should be <=.

Design question: why not use a atomic struct?

Hi Majerle,

Thanks for making this repo available.
This is a great tool to understand how to solve concurency issue on microcontrollers.

I have a quick question about one of you design decision.

Instead of defining 2 atomic variables for the head and the tail.

typedef struct lwrb {
    //...
    atomic_ulong r;
    atomic_ulong w; 
} lwrb_t;

Why not use a atomic struct ?

typedef struct {
    long r;
    long w; 
} lwrb_internals_t;

typedef struct lwrb {
    //...
   _Atomic lwrb_internals_t internal;
} lwrb_t;

Wouldn't this approach remove the ambiguity in the lwrb_get_full and lwrb_get_free ?
Maybe I'm overthinking it...

When storing non-8bi data, the reading does not match the actual data

  1. The following code is an example of storing 16bit data,

Before the i=5 write operation: r_fifo->w = 30, r_fifo-r = 40, lwrb_get_free(&r_fifo)=9; after the write operation, r_fifo->w = 39, which is no longer a multiple of 2, then The buffer is full.
When i=6, the write data starts to be written from 39, the data transmission is misplaced, and the reading is not as expected.

  lwrb_t r_fifo;
   int16_t buf[25] = {0};
   lwrb_init(&r_fifo, buf, 25*2);
  
   int16_t abuf[25];
   int16_t cbuf[25];
   int i=0;
   for(;i<24;i++)
     abuf[i] = i+1;

   for(i=0;i<100;i++)
   {
     lwrb_write(&r_fifo,abuf,8*2);
     lwrb_read(&r_fifo,cbuf,4*2);
   }

Test and modify lwrb_write, add the code between /****/, which can store int16_t data. When the buffer is full, reading data is normal.
Do you see if the type of stored data needs to be added to the library.


size_t lwrb_write(lwrb_t* buff, const void* data, size_t btw) {

/*
*
*
*/
     /* Calculate maximum number of bytes available to write */
     free = lwrb_get_free(buff);

  /**************************************************** *****/
     if(free < btw)
     {
       if(free - sizeof(int16_t)+1<=0)
       {
         btw = 0;
       }
       else
       {
         btw = free - sizeof(int16_t)+1;
       }
     }
// btw = BUF_MIN(free, btw);
     /**************************************************** *****/

     if (btw == 0) {
         return 0;
     }
   
     /*
      *
      *
      */
   
}

2.By the way, how to use the lwrb_get_linear_block_read_address() function correctly, is it similar to the processing of lwrb_read(), read to the end, the data is not read, and then read from the head. Is there any good way to deal with it?

Atomic Compilation and Project Setup Issues in PlatformIO

Hello, for some context I saw this lib on the PlatformIO registry and would like to use it for a project with an Arduino Due. The registry page says it is at version 2.0.3 and has not been updated. It is specifically missing the following in its library.json:

"build": {
     "includeDir": "lwrb/src/include",
     "srcDir": "lwrb/src/lwrb"
}

I am not sure if you maintain that registry anymore but I thought I would let you know. Otherwise, I specifically have 2 other question about compiling the project. I was able to submodule and compile the main stable branch with the following limitations.

1) Stable main branch throws error if LWRB_DEV is not defined.

I am not sure if this is intended but it might be preferable for the define statements to remove any of the development code if LWRB_DEV is not defined instead of throwing an error during compilation as including the lwrb.h will always make the compiler try to compile lwrb_ex.c.

#error "This file needs development & extensive tests - not to be used!"
  ^~~~~

2) Atomic lwrb does not compile for GCC arm-none-eabi 7.2.1.

The library compiles correctly whenLWRB_DISABLE_ATOMIC is defined but produces the following errors when attempting to enable the atomic feature of the code.

In file included from lib/lwrb/lwrb/src/include/lwrb/lwrb.h:53:0,
                 from src/main.cpp:5:
.platformio/packages/toolchain-gccarmnoneeabi/lib/gcc/arm-none-eabi/7.2.1/include/stdatomic.h:49:9: error: '_Atomic' does not name a type
 typedef _Atomic unsigned long atomic_ulong;
         ^~~~~~~
...

(omitted for brevity as above repeats for all std:: types)

.platformio/packages/toolchain-gccarmnoneeabi/lib/gcc/arm-none-eabi/7.2.1/include/stdatomic.h:218:9: error: '_Atomic' does not name a type
 typedef _Atomic struct
         ^~~~~~~
.platformio/packages/toolchain-gccarmnoneeabi/lib/gcc/arm-none-eabi/7.2.1/include/stdatomic.h:225:3: error: 'atomic_flag' does not name a type; did you mean 'atomic_load'?
 } atomic_flag;
   ^~~~~~~~~~~
   atomic_load
.platformio/packages/toolchain-gccarmnoneeabi/lib/gcc/arm-none-eabi/7.2.1/include/stdatomic.h:230:8: error: '_Bool' does not name a type; did you mean 'Bool'?
 extern _Bool atomic_flag_test_and_set (volatile atomic_flag *);
        ^~~~~
        Bool
.platformio/packages/toolchain-gccarmnoneeabi/lib/gcc/arm-none-eabi/7.2.1/include/stdatomic.h:233:8: error: '_Bool' does not name a type; did you mean 'Bool'?
 extern _Bool atomic_flag_test_and_set_explicit (volatile atomic_flag *,
        ^~~~~
        Bool
.platformio/packages/toolchain-gccarmnoneeabi/lib/gcc/arm-none-eabi/7.2.1/include/stdatomic.h:238:41: error: 'atomic_flag' does not name a type; did you mean 'atomic_load'?
 extern void atomic_flag_clear (volatile atomic_flag *);
                                         ^~~~~~~~~~~
                                         atomic_load
.platformio/packages/toolchain-gccarmnoneeabi/lib/gcc/arm-none-eabi/7.2.1/include/stdatomic.h:240:50: error: 'atomic_flag' does not name a type; did you mean 'atomic_load'?
 extern void atomic_flag_clear_explicit (volatile atomic_flag *, memory_order);
                                                  ^~~~~~~~~~~
                                                  atomic_load
In file included from src/main.cpp:5:0:
lib/lwrb/lwrb/src/include/lwrb/lwrb.h:54:9: error: 'atomic_ulong' does not name a type; did you mean 'atomic_load'?
 typedef atomic_ulong lwrb_ulong_t;
         ^~~~~~~~~~~~
         atomic_load
lib/lwrb/lwrb/src/include/lwrb/lwrb.h:85:5: error: 'lwrb_ulong_t' does not name a type
     lwrb_ulong_t r; /*!< Next read pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */
     ^~~~~~~~~~~~
lib/lwrb/lwrb/src/include/lwrb/lwrb.h:86:5: error: 'lwrb_ulong_t' does not name a type
     lwrb_ulong_t w; /*!< Next write pointer. Buffer is considered empty when `r == w` and full when `w == r - 1` */

The project correctly determines that the code is being compiled with c++ so the atomic code is wrapped in extern "C" {}. But this is where I was unable to figure out where why the compiler toolchain fails to define the _Atomic struct.

RX Buffer clear after reading

Thanks for the great example, I'm using it on the Nucleo-G474RE at the moment.
When I use the function ringbuff_read it works until the RX buffer is full (512Bytes). What am I doing wrong? With a terminal tool I send a string to the STM32, the STM32 sends it back. As soon as 512Bytes are in the Buffer, it doesn't works anymore. The data in the buffer should be marked as free after reading but something is not working on my code:

void UARTRxComplete(void)
{
uint8_t addr;
uint16_t len;
rxThisPos=UART_DMA_WRITE_PTR; //get current write pointer
len=(rxThisPos-rxLastPos+UART_RX_RINGBUFF_SZ)%UART_RX_RINGBUFF_SZ; //calculate how far the DMA write pointer has moved
if(len<=UART_RX_MAX_MESSAGE_LEN) { //check message size
ringbuff_advance(&rxRing,len); //move the ring buffer write pointer
rxLastPos=rxThisPos;

//test: send the received data out -> its working until 512bytes, then the RX buffer is full
ringbuff_read(&rxRing,processBuf,len); //read out the data to an array for processing
UARTAddToTxBuff(processBuf, len);

}
else {
while(1); //implement message to large exception
}
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.