mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Add ringbuf.c. This works like a FreeRTOS queue, but allows for variable-length items which in some cases is more memory efficient than a queue.
This commit is contained in:
parent
b77bf9bc90
commit
a9502dffd3
206
components/freertos/include/freertos/ringbuf.h
Normal file
206
components/freertos/include/freertos/ringbuf.h
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
#ifndef FREERTOS_RINGBUF_H
|
||||||
|
#define FREERTOS_RINGBUF_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
Header definitions for a FreeRTOS ringbuffer object
|
||||||
|
|
||||||
|
A ringbuffer instantiated by these functions essentially acts like a FreeRTOS queue, with the
|
||||||
|
difference that it's strictly FIFO and with the main advantage that you can put in randomly-sized
|
||||||
|
items. The capacity, accordingly, isn't measured in the amount of items, but the amount of memory
|
||||||
|
that is used for storing the items. Dependent on the size of the items, more or less of them will
|
||||||
|
fit in the ring buffer.
|
||||||
|
|
||||||
|
This ringbuffer tries to be efficient with memory: when inserting an item, the item data will
|
||||||
|
be copied to the ringbuffer memory. When retrieving an item, however, a reference to ringbuffer
|
||||||
|
memory will be returned. The returned memory is guaranteed to be 32-bit aligned and contiguous.
|
||||||
|
The application can use this memory, but as long as it does, ringbuffer writes that would write
|
||||||
|
to this bit of memory will block.
|
||||||
|
|
||||||
|
The requirement for items to be contiguous is slightly problematic when the only way to place
|
||||||
|
the next item would involve a wraparound from the end to the beginning of the ringbuffer. This can
|
||||||
|
be solved in two ways:
|
||||||
|
- allow_split_items = pdTRUE: The insertion code will split the item in two items; one which fits
|
||||||
|
in the space left at the end of the ringbuffer, one that contains the remaining data which is placed
|
||||||
|
in the beginning. Two xRingbufferReceive calls will be needed to retrieve the data.
|
||||||
|
- allow_split_items = pdFALSE: The insertion code will leave the room at the end of the ringbuffer
|
||||||
|
unused and instead will put the entire item at the start of the ringbuffer, as soon as there is
|
||||||
|
enough free space.
|
||||||
|
|
||||||
|
The maximum size of an item will be affected by this decision. When split items are allowed, it's
|
||||||
|
acceptable to push items of (buffer_size)-16 bytes into the buffer. When it's not allowed, the
|
||||||
|
maximum size is (buffer_size/2)-8 bytes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//An opaque handle for a ringbuff object.
|
||||||
|
typedef void * RingbufHandle_t;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a ring buffer
|
||||||
|
*
|
||||||
|
* @param buf_length : Length of circular buffer, in bytes. Each entry will take up its own length, plus a header
|
||||||
|
* that at the moment is equal to sizeof(size_t).
|
||||||
|
* @param allow_split_items : pdTRUE if it is acceptable that item data is inserted as two
|
||||||
|
* items instead of one.
|
||||||
|
*
|
||||||
|
* @return A RingbufHandle_t handle to the created ringbuffer, or NULL in case of error.
|
||||||
|
*/
|
||||||
|
RingbufHandle_t xRingbufferCreate(size_t buf_length, BaseType_t allow_split_items);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete a ring buffer
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to delete
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void vRingbufferDelete(RingbufHandle_t ringbuf);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get maximum size of an item that can be placed in the ring buffer
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to query
|
||||||
|
*
|
||||||
|
* @return Maximum size, in bytes, of an item that can be placed in a ring buffer.
|
||||||
|
*/
|
||||||
|
size_t xRingbufferGetMaxItemSize(RingbufHandle_t ringbuf);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insert an item into the ring buffer
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to insert the item into
|
||||||
|
* @param data - Pointer to data to insert. NULL is allowed if data_size is 0.
|
||||||
|
* @param data_size - Size of data to insert. A value of 0 is allowed.
|
||||||
|
* @param xTicksToWait - Ticks to wait for room in the ringbuffer.
|
||||||
|
*
|
||||||
|
* @return pdTRUE if succeeded, pdFALSE on time-out or when the buffer is larger
|
||||||
|
* than indicated by xRingbufferGetMaxItemSize(ringbuf).
|
||||||
|
*/
|
||||||
|
BaseType_t xRingbufferSend(RingbufHandle_t ringbuf, void *data, size_t data_size, TickType_t ticks_to_wait);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insert an item into the ring buffer from an ISR
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to insert the item into
|
||||||
|
* @param data - Pointer to data to insert. NULL is allowed if data_size is 0.
|
||||||
|
* @param data_size - Size of data to insert. A value of 0 is allowed.
|
||||||
|
* @param higher_prio_task_awoken - Value pointed to will be set to pdTRUE if the push woke up a higher
|
||||||
|
* priority task.
|
||||||
|
*
|
||||||
|
* @return pdTRUE if succeeded, pdFALSE when the ring buffer does not have space.
|
||||||
|
*/
|
||||||
|
BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t data_size, BaseType_t *higher_prio_task_awoken);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieve an item from the ring buffer
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to retrieve the item from
|
||||||
|
* @param item_size - Pointer to a variable to which the size of the retrieved item will be written.
|
||||||
|
* @param xTicksToWait - Ticks to wait for items in the ringbuffer.
|
||||||
|
*
|
||||||
|
* @return Pointer to the retrieved item on success; *item_size filled with the length of the
|
||||||
|
* item. NULL on timeout, *item_size is untouched in that case.
|
||||||
|
*/
|
||||||
|
void *xRingbufferReceive(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieve an item from the ring buffer from an ISR
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to retrieve the item from
|
||||||
|
* @param item_size - Pointer to a variable to which the size of the retrieved item will be written.
|
||||||
|
*
|
||||||
|
* @return Pointer to the retrieved item on success; *item_size filled with the length of the
|
||||||
|
* item. NULL when the ringbuffer is empty, *item_size is untouched in that case.
|
||||||
|
*/
|
||||||
|
void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a previously-retrieved item to the ringbuffer
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer the item was retrieved from
|
||||||
|
* @param item - Item that was received earlier
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a previously-retrieved item to the ringbuffer from an ISR
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer the item was retrieved from
|
||||||
|
* @param item - Item that was received earlier
|
||||||
|
* @param higher_prio_task_awoken - Value pointed to will be set to pdTRUE if the push woke up a higher
|
||||||
|
* priority task.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void vRingbufferReturnItemFromISR(RingbufHandle_t ringbuf, void *item, BaseType_t *higher_prio_task_awoken);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add the ringbuffer to a queue set. This specifically adds the semaphore that indicates
|
||||||
|
* more space has become available in the ringbuffer.
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to add to the queue set
|
||||||
|
* @param xQueueSet - Queue set to add the ringbuffer to
|
||||||
|
*
|
||||||
|
* @return pdTRUE on success, pdFALSE otherwise
|
||||||
|
*/
|
||||||
|
BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add the ringbuffer to a queue set. This specifically adds the semaphore that indicates
|
||||||
|
* something has been written into the ringbuffer.
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to add to the queue set
|
||||||
|
* @param xQueueSet - Queue set to add the ringbuffer to
|
||||||
|
*
|
||||||
|
* @return pdTRUE on success, pdFALSE otherwise
|
||||||
|
*/
|
||||||
|
BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the ringbuffer from a queue set. This specifically removes the semaphore that indicates
|
||||||
|
* more space has become available in the ringbuffer.
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to remove from the queue set
|
||||||
|
* @param xQueueSet - Queue set to remove the ringbuffer from
|
||||||
|
*
|
||||||
|
* @return pdTRUE on success, pdFALSE otherwise
|
||||||
|
*/
|
||||||
|
BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the ringbuffer from a queue set. This specifically removes the semaphore that indicates
|
||||||
|
* something has been written to the ringbuffer.
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to remove from the queue set
|
||||||
|
* @param xQueueSet - Queue set to remove the ringbuffer from
|
||||||
|
*
|
||||||
|
* @return pdTRUE on success, pdFALSE otherwise
|
||||||
|
*/
|
||||||
|
BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Debugging function to print the internal pointers in the ring buffer
|
||||||
|
*
|
||||||
|
* @param ringbuf - Ring buffer to show
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void xRingbufferPrintInfo(RingbufHandle_t ringbuf);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
474
components/freertos/ringbuf.c
Normal file
474
components/freertos/ringbuf.c
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/xtensa_api.h"
|
||||||
|
#include "freertos/ringbuf.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
flag_allowsplit = 1,
|
||||||
|
} rbflag_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
iflag_free = 1, //Buffer is not read and given back by application, free to overwrite
|
||||||
|
iflag_dummydata = 2, //Data from here to end of ringbuffer is dummy. Restart reading at start of ringbuffer.
|
||||||
|
} itemflag_t;
|
||||||
|
|
||||||
|
|
||||||
|
//The ringbuffer structure
|
||||||
|
typedef struct {
|
||||||
|
SemaphoreHandle_t free_space_sem; //Binary semaphore, wakes up writing threads when there's more free space
|
||||||
|
SemaphoreHandle_t items_buffered_sem; //Binary semaphore, indicates there are new packets in the circular buffer. See remark.
|
||||||
|
size_t size; //Size of the data storage
|
||||||
|
uint8_t *write_ptr; //Pointer where the next item is written
|
||||||
|
uint8_t *read_ptr; //Pointer from where the next item is read
|
||||||
|
uint8_t *free_ptr; //Pointer to the last block that hasn't been given back to the ringbuffer yet
|
||||||
|
uint8_t *data; //Data storage
|
||||||
|
portMUX_TYPE mux; //Spinlock for actual data/ptr/struct modification
|
||||||
|
rbflag_t flags;
|
||||||
|
} ringbuf_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remark: A counting semaphore for items_buffered_sem would be more logical, but counting semaphores in
|
||||||
|
FreeRTOS need a maximum count, and allocate more memory the larger the maximum count is. Here, we
|
||||||
|
would need to set the maximum to the maximum amount of times a null-byte unit firs in the buffer,
|
||||||
|
which is quite high and so would waste a fair amount of memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//The header prepended to each ringbuffer entry. Size is assumed to be a multiple of 32bits.
|
||||||
|
typedef struct {
|
||||||
|
size_t len;
|
||||||
|
itemflag_t flags;
|
||||||
|
} buf_entry_hdr_t;
|
||||||
|
|
||||||
|
|
||||||
|
//Calculate space free in the buffer
|
||||||
|
static int ringbufferFreeMem(ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
int free_size = rb->free_ptr-rb->write_ptr;
|
||||||
|
if (free_size <= 0) free_size += rb->size;
|
||||||
|
//Reserve one byte. If we do not do this and the entire buffer is filled, we get a situation
|
||||||
|
//where read_ptr == free_ptr, messing up the next calculation.
|
||||||
|
return free_size-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copies a single item to the ring buffer. Assumes there is space in the ringbuffer and
|
||||||
|
//the ringbuffer is locked. Increases write_ptr to the next item. Returns pdTRUE on
|
||||||
|
//success, pdFALSE if it can't make the item fit and the calling routine needs to retry
|
||||||
|
//later or fail.
|
||||||
|
//This function by itself is not threadsafe, always call from within a muxed section.
|
||||||
|
static BaseType_t copyItemToRingbuf(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
size_t rbuffer_size=(buffer_size+3)&~3; //Payload length, rounded to next 32-bit value
|
||||||
|
configASSERT(((int)rb->write_ptr&3)==0); //write_ptr needs to be 32-bit aligned
|
||||||
|
configASSERT(rb->write_ptr-(rb->data+rb->size) >= sizeof(buf_entry_hdr_t)); //need to have at least the size
|
||||||
|
//of a header to the end of the ringbuff
|
||||||
|
size_t rem_len=(rb->data + rb->size) - rb->write_ptr; //length remaining until end of ringbuffer
|
||||||
|
|
||||||
|
//See if we have enough contiguous space to write the buffer.
|
||||||
|
if (rem_len < rbuffer_size + sizeof(buf_entry_hdr_t)) {
|
||||||
|
//The buffer can't be contiguously written to the ringbuffer, but needs special handling. Do
|
||||||
|
//that depending on how the ringbuffer is configured.
|
||||||
|
//The code here is also expected to check if the buffer, mangled in whatever way is implemented,
|
||||||
|
//will still fit, and return pdFALSE if that is not the case.
|
||||||
|
if (rb->flags & flag_allowsplit) {
|
||||||
|
//Buffer plus header is not going to fit in the room from wr_pos to the end of the
|
||||||
|
//ringbuffer... we need to split the write in two.
|
||||||
|
//First, see if this will fit at all.
|
||||||
|
if (ringbufferFreeMem(rb) < (sizeof(buf_entry_hdr_t)*2)+rbuffer_size) {
|
||||||
|
//Will not fit.
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
//Because the code at the end of the function makes sure we always have
|
||||||
|
//room for a header, this should never assert.
|
||||||
|
configASSERT(rem_len>=sizeof(buf_entry_hdr_t));
|
||||||
|
//Okay, it should fit. Write everything.
|
||||||
|
//First, place bit of buffer that does fit. Write header first...
|
||||||
|
buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->write_ptr;
|
||||||
|
hdr->flags=0;
|
||||||
|
hdr->len=rem_len-sizeof(buf_entry_hdr_t);
|
||||||
|
rb->write_ptr+=sizeof(buf_entry_hdr_t);
|
||||||
|
rem_len-=sizeof(buf_entry_hdr_t);
|
||||||
|
if (rem_len!=0) {
|
||||||
|
//..then write the data bit that fits.
|
||||||
|
memcpy(rb->write_ptr, buffer, rem_len);
|
||||||
|
//Update vars so the code later on will write the rest of the data.
|
||||||
|
buffer+=rem_len;
|
||||||
|
rbuffer_size-=rem_len;
|
||||||
|
buffer_size-=rem_len;
|
||||||
|
} else {
|
||||||
|
//Huh, only the header fit. Mark as dummy so the receive function doesn't receive
|
||||||
|
//an useless zero-byte packet.
|
||||||
|
hdr->flags|=iflag_dummydata;
|
||||||
|
}
|
||||||
|
rb->write_ptr=rb->data;
|
||||||
|
} else {
|
||||||
|
//Buffer plus header is not going to fit in the room from wr_pos to the end of the
|
||||||
|
//ringbuffer... but we're not allowed to split the buffer. We need to fill the
|
||||||
|
//rest of the ringbuffer with a dummy item so we can place the data at the _start_ of
|
||||||
|
//the ringbuffer..
|
||||||
|
//First, find out if we actually have enough space at the start of the ringbuffer to
|
||||||
|
//make this work (Again, we need 4 bytes extra because otherwise read_ptr==free_ptr)
|
||||||
|
if (rb->free_ptr-rb->data < rbuffer_size+sizeof(buf_entry_hdr_t)+4) {
|
||||||
|
//Will not fit.
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
//If the read buffer hasn't wrapped around yet, there's no way this will work either.
|
||||||
|
if (rb->free_ptr > rb->write_ptr) {
|
||||||
|
//No luck.
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Okay, it will fit. Mark the rest of the ringbuffer space with a dummy packet.
|
||||||
|
buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->write_ptr;
|
||||||
|
hdr->flags=iflag_dummydata;
|
||||||
|
//Reset the write pointer to the start of the ringbuffer so the code later on can
|
||||||
|
//happily write the data.
|
||||||
|
rb->write_ptr=rb->data;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//No special handling needed. Checking if it's gonna fit probably still is a good idea.
|
||||||
|
if (ringbufferFreeMem(rb) < sizeof(buf_entry_hdr_t)+rbuffer_size) {
|
||||||
|
//Buffer is not going to fit, period.
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we are here, the buffer is guaranteed to fit in the space starting at the write pointer.
|
||||||
|
buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->write_ptr;
|
||||||
|
hdr->len=buffer_size;
|
||||||
|
hdr->flags=0;
|
||||||
|
rb->write_ptr+=sizeof(buf_entry_hdr_t);
|
||||||
|
memcpy(rb->write_ptr, buffer, buffer_size);
|
||||||
|
rb->write_ptr+=rbuffer_size;
|
||||||
|
|
||||||
|
//The buffer will wrap around if we don't have room for a header anymore.
|
||||||
|
if ((rb->data+rb->size)-rb->write_ptr < sizeof(buf_entry_hdr_t)) {
|
||||||
|
//'Forward' the write buffer until we are at the start of the ringbuffer.
|
||||||
|
//The read pointer will always be at the start of a full header, which cannot
|
||||||
|
//exist at the point of the current write pointer, so there's no chance of overtaking
|
||||||
|
//that.
|
||||||
|
rb->write_ptr=rb->data;
|
||||||
|
}
|
||||||
|
return pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Retrieves a pointer to the data of the next item, or NULL if this is not possible.
|
||||||
|
//This function by itself is not threadsafe, always call from within a muxed section.
|
||||||
|
static uint8_t *getItemFromRingbuf(ringbuf_t *rb, size_t *length)
|
||||||
|
{
|
||||||
|
uint8_t *ret;
|
||||||
|
configASSERT(((int)rb->read_ptr&3)==0);
|
||||||
|
if (rb->read_ptr == rb->write_ptr) {
|
||||||
|
//No data available.
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//The item written at the point of the read pointer may be a dummy item.
|
||||||
|
//We need to skip past it first, if that's the case.
|
||||||
|
buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->read_ptr;
|
||||||
|
configASSERT((hdr->len < rb->size) || (hdr->flags & iflag_dummydata));
|
||||||
|
if (hdr->flags & iflag_dummydata) {
|
||||||
|
//Hdr is dummy data. Reset to start of ringbuffer.
|
||||||
|
rb->read_ptr=rb->data;
|
||||||
|
//Get real header
|
||||||
|
hdr=(buf_entry_hdr_t *)rb->read_ptr;
|
||||||
|
configASSERT(hdr->len < rb->size);
|
||||||
|
//No need to re-check if the ringbuffer is empty: the write routine will
|
||||||
|
//always write a dummy item plus the real data item in one go, so now we must
|
||||||
|
//be at the real data item by definition.
|
||||||
|
}
|
||||||
|
//Okay, pass the data back.
|
||||||
|
ret=rb->read_ptr+sizeof(buf_entry_hdr_t);
|
||||||
|
*length=hdr->len;
|
||||||
|
//...and move the read pointer past the data.
|
||||||
|
rb->read_ptr+=sizeof(buf_entry_hdr_t)+((hdr->len+3)&~3);
|
||||||
|
//The buffer will wrap around if we don't have room for a header anymore.
|
||||||
|
if ((rb->data + rb->size) - rb->read_ptr < sizeof(buf_entry_hdr_t)) {
|
||||||
|
rb->read_ptr=rb->data;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Returns an item to the ringbuffer. Will mark the item as free, and will see if the free pointer
|
||||||
|
//can be increase.
|
||||||
|
//This function by itself is not threadsafe, always call from within a muxed section.
|
||||||
|
static void returnItemToRingbuf(ringbuf_t *rb, void *item) {
|
||||||
|
uint8_t *data=(uint8_t*)item;
|
||||||
|
configASSERT(((int)rb->free_ptr&3)==0);
|
||||||
|
configASSERT(data >= rb->data);
|
||||||
|
configASSERT(data < rb->data+rb->size);
|
||||||
|
//Grab the buffer entry that preceeds the buffer
|
||||||
|
buf_entry_hdr_t *hdr=(buf_entry_hdr_t*)(data-sizeof(buf_entry_hdr_t));
|
||||||
|
configASSERT(hdr->len < rb->size);
|
||||||
|
configASSERT((hdr->flags & iflag_dummydata)==0);
|
||||||
|
configASSERT((hdr->flags & iflag_free)==0);
|
||||||
|
//Mark the buffer as free.
|
||||||
|
hdr->flags|=iflag_free;
|
||||||
|
|
||||||
|
//Do a cleanup pass.
|
||||||
|
hdr=(buf_entry_hdr_t *)rb->free_ptr;
|
||||||
|
//basically forward free_ptr until we run into either a block that is still in use or the write pointer.
|
||||||
|
while (((hdr->flags & iflag_free) || (hdr->flags & iflag_dummydata)) && rb->free_ptr != rb->write_ptr) {
|
||||||
|
if (hdr->flags & iflag_dummydata) {
|
||||||
|
//Rest is dummy data. Reset to start of ringbuffer.
|
||||||
|
rb->free_ptr=rb->data;
|
||||||
|
} else {
|
||||||
|
//Skip past item
|
||||||
|
size_t len=(hdr->len+3)&~3;
|
||||||
|
rb->free_ptr+=len+sizeof(buf_entry_hdr_t);
|
||||||
|
configASSERT(rb->free_ptr<=rb->data+rb->size);
|
||||||
|
}
|
||||||
|
//The buffer will wrap around if we don't have room for a header anymore.
|
||||||
|
if ((rb->data+rb->size)-rb->free_ptr < sizeof(buf_entry_hdr_t)) {
|
||||||
|
rb->free_ptr=rb->data;
|
||||||
|
}
|
||||||
|
//Next header
|
||||||
|
hdr=(buf_entry_hdr_t *)rb->free_ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void xRingbufferPrintInfo(RingbufHandle_t ringbuf)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
configASSERT(rb);
|
||||||
|
ets_printf("Rb size %d free %d rptr %d freeptr %d wptr %d\n",
|
||||||
|
rb->size, ringbufferFreeMem(rb), rb->read_ptr-rb->data, rb->free_ptr-rb->data, rb->write_ptr-rb->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
RingbufHandle_t xRingbufferCreate(size_t buf_length, BaseType_t allow_split_items)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb = malloc(sizeof(ringbuf_t));
|
||||||
|
if (rb==NULL) goto err;
|
||||||
|
memset(rb, 0, sizeof(ringbuf_t));
|
||||||
|
rb->data = malloc(buf_length);
|
||||||
|
if (rb->data == NULL) goto err;
|
||||||
|
rb->size = buf_length;
|
||||||
|
rb->free_ptr = rb->data;
|
||||||
|
rb->read_ptr = rb->data;
|
||||||
|
rb->write_ptr = rb->data;
|
||||||
|
rb->free_space_sem = xSemaphoreCreateBinary();
|
||||||
|
rb->items_buffered_sem = xSemaphoreCreateBinary();
|
||||||
|
rb->flags=0;
|
||||||
|
if (allow_split_items) rb->flags|=flag_allowsplit;
|
||||||
|
if (rb->free_space_sem == NULL || rb->items_buffered_sem == NULL) goto err;
|
||||||
|
vPortCPUInitializeMutex(&rb->mux);
|
||||||
|
return (RingbufHandle_t)rb;
|
||||||
|
|
||||||
|
err:
|
||||||
|
//Some error has happened. Free/destroy all allocated things and return NULL.
|
||||||
|
if (rb) {
|
||||||
|
free(rb->data);
|
||||||
|
if (rb->free_space_sem) vSemaphoreDelete(rb->free_space_sem);
|
||||||
|
if (rb->items_buffered_sem) vSemaphoreDelete(rb->items_buffered_sem);
|
||||||
|
}
|
||||||
|
free(rb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vRingbufferDelete(RingbufHandle_t ringbuf) {
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
if (rb) {
|
||||||
|
free(rb->data);
|
||||||
|
if (rb->free_space_sem) vSemaphoreDelete(rb->free_space_sem);
|
||||||
|
if (rb->items_buffered_sem) vSemaphoreDelete(rb->items_buffered_sem);
|
||||||
|
}
|
||||||
|
free(rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t xRingbufferGetMaxItemSize(RingbufHandle_t ringbuf)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
configASSERT(rb);
|
||||||
|
//In both cases, we return 4 bytes less than what we actually can have. If the ringbuffer is
|
||||||
|
//indeed entirely filled, read_ptr==free_ptr, which throws off the free space calculation.
|
||||||
|
if (rb->flags & flag_allowsplit) {
|
||||||
|
//Worst case, we need to split an item into two, which means two headers of overhead.
|
||||||
|
return rb->size-(sizeof(buf_entry_hdr_t)*2)-4;
|
||||||
|
} else {
|
||||||
|
//Worst case, we have the write ptr in such a position that we are lacking four bytes of free
|
||||||
|
//memory to put an item into the rest of the memory. If this happens, we have to dummy-fill
|
||||||
|
//(item_data-4) bytes of buffer, then we only have (size-(item_data-4) bytes left to fill
|
||||||
|
//with the real item. (item size being header+data)
|
||||||
|
return (rb->size/2)-sizeof(buf_entry_hdr_t)-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType_t xRingbufferSend(RingbufHandle_t ringbuf, void *data, size_t dataSize, TickType_t ticks_to_wait)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
size_t needed_size=dataSize+sizeof(buf_entry_hdr_t);
|
||||||
|
BaseType_t done=pdFALSE;
|
||||||
|
portTickType ticks_end=xTaskGetTickCount() + ticks_to_wait;
|
||||||
|
|
||||||
|
configASSERT(rb);
|
||||||
|
|
||||||
|
if (dataSize > xRingbufferGetMaxItemSize(ringbuf)) {
|
||||||
|
//Data will never ever fit in the queue.
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!done) {
|
||||||
|
//Check if there is enough room in the buffer. If not, wait until there is.
|
||||||
|
do {
|
||||||
|
if (ringbufferFreeMem(rb) < needed_size) {
|
||||||
|
//Data does not fit yet. Wait until the free_space_sem is given, then re-evaluate.
|
||||||
|
|
||||||
|
BaseType_t r = xSemaphoreTake(rb->free_space_sem, ticks_to_wait);
|
||||||
|
if (r == pdFALSE) {
|
||||||
|
//Timeout.
|
||||||
|
return pdFALSE;
|
||||||
|
}
|
||||||
|
//Adjust ticks_to_wait; we may have waited less than that and in the case the free memory still is not enough,
|
||||||
|
//we will need to wait some more.
|
||||||
|
ticks_to_wait = ticks_end - xTaskGetTickCount();
|
||||||
|
}
|
||||||
|
} while (ringbufferFreeMem(rb) < needed_size && ticks_to_wait>=0);
|
||||||
|
|
||||||
|
//Lock the mux in order to make sure no one else is messing with the ringbuffer and do the copy.
|
||||||
|
portENTER_CRITICAL(&rb->mux);
|
||||||
|
//Another thread may have been able to sneak its write first. Check again now we locked the ringbuff, and retry
|
||||||
|
//everything if this is the case. Otherwise, we can write and are done.
|
||||||
|
done=copyItemToRingbuf(rb, data, dataSize);
|
||||||
|
portEXIT_CRITICAL(&rb->mux);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(rb->items_buffered_sem);
|
||||||
|
return pdTRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t dataSize, BaseType_t *higher_prio_task_awoken)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
BaseType_t write_succeeded;
|
||||||
|
configASSERT(rb);
|
||||||
|
size_t needed_size=dataSize+sizeof(buf_entry_hdr_t);
|
||||||
|
portENTER_CRITICAL_ISR(&rb->mux);
|
||||||
|
if (needed_size>ringbufferFreeMem(rb)) {
|
||||||
|
//Does not fit in the remaining space in the ringbuffer.
|
||||||
|
write_succeeded=pdFALSE;
|
||||||
|
} else {
|
||||||
|
copyItemToRingbuf(rb, data, dataSize);
|
||||||
|
write_succeeded=pdTRUE;
|
||||||
|
}
|
||||||
|
portEXIT_CRITICAL_ISR(&rb->mux);
|
||||||
|
if (write_succeeded) {
|
||||||
|
xSemaphoreGiveFromISR(rb->items_buffered_sem, higher_prio_task_awoken);
|
||||||
|
}
|
||||||
|
return write_succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void *xRingbufferReceive(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
uint8_t *itemData;
|
||||||
|
BaseType_t done=pdFALSE;
|
||||||
|
configASSERT(rb);
|
||||||
|
while(!done) {
|
||||||
|
//See if there's any data available. If not, wait until there is.
|
||||||
|
while (rb->read_ptr == rb->write_ptr) {
|
||||||
|
BaseType_t r=xSemaphoreTake(rb->items_buffered_sem, ticks_to_wait);
|
||||||
|
if (r == pdFALSE) {
|
||||||
|
//Timeout.
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Okay, we seem to have data in the buffer. Grab the mux and copy it out if it's still there.
|
||||||
|
portENTER_CRITICAL(&rb->mux);
|
||||||
|
itemData=getItemFromRingbuf(rb, item_size);
|
||||||
|
portEXIT_CRITICAL(&rb->mux);
|
||||||
|
if (itemData) {
|
||||||
|
//We managed to get an item.
|
||||||
|
done=pdTRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (void*)itemData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
uint8_t *itemData;
|
||||||
|
configASSERT(rb);
|
||||||
|
portENTER_CRITICAL_ISR(&rb->mux);
|
||||||
|
itemData=getItemFromRingbuf(rb, item_size);
|
||||||
|
portEXIT_CRITICAL_ISR(&rb->mux);
|
||||||
|
return (void*)itemData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
portENTER_CRITICAL_ISR(&rb->mux);
|
||||||
|
returnItemToRingbuf(rb, item);
|
||||||
|
portEXIT_CRITICAL_ISR(&rb->mux);
|
||||||
|
xSemaphoreGive(rb->free_space_sem);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void vRingbufferReturnItemFromISR(RingbufHandle_t ringbuf, void *item, BaseType_t *higher_prio_task_awoken)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
portENTER_CRITICAL_ISR(&rb->mux);
|
||||||
|
returnItemToRingbuf(rb, item);
|
||||||
|
portEXIT_CRITICAL_ISR(&rb->mux);
|
||||||
|
xSemaphoreGiveFromISR(rb->free_space_sem, higher_prio_task_awoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
configASSERT(rb);
|
||||||
|
return xQueueAddToSet(rb->items_buffered_sem, xQueueSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
configASSERT(rb);
|
||||||
|
return xQueueAddToSet(rb->free_space_sem, xQueueSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
configASSERT(rb);
|
||||||
|
return xQueueRemoveFromSet(rb->items_buffered_sem, xQueueSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet)
|
||||||
|
{
|
||||||
|
ringbuf_t *rb=(ringbuf_t *)ringbuf;
|
||||||
|
configASSERT(rb);
|
||||||
|
return xQueueRemoveFromSet(rb->free_space_sem, xQueueSet);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user