MAX30001G  1.0.2
Arduino library for MAX30001G ECG and BIOZ AFE
RingBuffer.h
Go to the documentation of this file.
1 // ************************************************************************
2 // RingBuffer.h
3 //
4 // Ring buffer (circular buffer) implementation in C++
5 // Thread safe on ESP32 using per-instance spinlocks
6 // Template class supporting any data type T
7 // Buffer size N must be a power of 2 for efficiency
8 //
9 // push
10 // Push data into the ring buffer
11 // If overwrite is true, older data is overwritten when buffer is full
12 // pop
13 // Pop data from the ring buffer
14 // peek
15 // Peek at data in the ring buffer without removing it
16 // consume
17 // Remove data from the ring buffer without copying it out
18 // available
19 // Get number of elements currently in the buffer
20 // capacity
21 // Get total capacity of the buffer
22 // clear
23 // Clear the buffer
24 //
25 // ************************************************************************
26 #ifndef RB_MIN
27  #define RB_MIN(a, b) ((a) < (b) ? (a) : (b))
28 #endif
29 
30 #ifndef RINGBUFFER_H
31 #define RINGBUFFER_H
32 
33 #include <string.h> // for memcpy
34 #include <stdint.h> // for uint8_t, uint16_t
35 #include <type_traits> // for std::conditional
36 
37 #if defined(ESP32)
38  # include "freertos/FreeRTOS.h"
39  # include "freertos/portmacro.h"
40 #endif
41 
42 // Define critical section helpers BEFORE class so they are visible where used.
43 // They reference 'this->mux_' which is a per-instance spinlock.
44 #ifdef ARDUINO_ARCH_ESP32
45 # define RB_CRITICAL_ENTER() portENTER_CRITICAL(const_cast<portMUX_TYPE*>(&this->mux_))
46 # define RB_CRITICAL_EXIT() portEXIT_CRITICAL(const_cast<portMUX_TYPE*>(&this->mux_))
47 #else
48 # define RB_CRITICAL_ENTER()
49 # define RB_CRITICAL_EXIT()
50 #endif
51 
52 template <typename T, size_t N>
53 class RingBuffer {
54 public:
55  RingBuffer() : head(0), tail(0), count(0) {
56  #if defined(ESP32)
57  mux_ = portMUX_INITIALIZER_UNLOCKED;
58  #endif
59  }
60  // Push data into the ring buffer
61  // data: pointer to data that will be added to the buffer
62  // data_len: number of data elements
63  // overwrite: if true, older data is overwritten when buffer is full
64  size_t push(const T& data, bool overwrite = false);
65  size_t push(const T* data, size_t data_len, bool overwrite = false);
66 
67  // Pop a specified number of characters from the ring buffer
68  size_t pop(T& output);
69  size_t pop(T* output, size_t len);
70 
71  // Peek a specified number of characters from the ring buffer and leave them in the buffer
72  size_t peek(T& output) const;
73  size_t peek(T* output, size_t len) const;
74 
75  // Consume (remove) a specified number of characters from the ring buffer without copying them out
76  size_t consume();
77  size_t consume(size_t len);
78 
79  size_t available() const {
80  // Snapshot under critical section to avoid torn reads
81  #if defined(ESP32)
83  size_t c = count;
85  return c;
86  #else
87  return count;
88  #endif
89  }
90  size_t capacity() const { return N; }
91  void clear();
92 
93 private:
94  T buffer[N]; // fixed size buffer
95 
96  // Select optimal index type based on buffer size
97  using IndexType = typename std::conditional<
98  (N <= 256), uint8_t,
99  typename std::conditional<(N <= 65536), uint16_t, size_t>::type
100  >::type;
101 
102  IndexType head;
103  IndexType tail;
104  IndexType count;
105 
106  static constexpr bool isPowerOfTwo(size_t n) { return (n & (n - 1)) == 0; }
107  static_assert(isPowerOfTwo(N), "RingBuffer capacity must be a power of 2 for efficiency");
108 
109 #if defined(ESP32)
110  // Per-instance spinlock for critical sections. Mark mutable so it can be used in const methods.
111  mutable portMUX_TYPE mux_;
112 #endif
113 };
114 
115 // Push a single element
116 template <typename T, size_t N>
117 size_t RingBuffer<T, N>::push(const T& data, bool overwrite) {
118  return push(&data, 1, overwrite);
119 }
120 
121 // Push multiple elements
122 template <typename T, size_t N>
123 size_t RingBuffer<T, N>::push(const T* data, size_t data_len, bool overwrite) {
124  if (data_len == 0) return 0;
126 
127  size_t available_space = N - count;
128  if (data_len > available_space && !overwrite) {
130  return 0;
131  }
132 
133  if (data_len > available_space) {
134  // Overwriting: advance tail to free up space
135  size_t overflow = data_len - available_space;
136  tail = (tail + overflow) & (N - 1);
137  }
138 
139  // Optimized single-element push
140  if (data_len == 1) {
141  buffer[head] = *data;
142  } else {
143  // Multi-element push
144  size_t firstPart = RB_MIN(data_len, N - head);
145  memcpy(&buffer[head], data, firstPart * sizeof(T));
146 
147  size_t secondPart = data_len - firstPart;
148  if (secondPart > 0) {
149  // Wrap around the buffer and write remaining bytes at the beginning
150  memcpy(buffer, data + firstPart, secondPart * sizeof(T));
151  }
152  }
153 
154  // Update head and count
155  head = (head + data_len) & (N - 1);
156  count = RB_MIN(static_cast<size_t>(N), static_cast<size_t>(count + data_len));
157 
159  return data_len;
160 }
161 
162 // Pop a single element
163 template <typename T, size_t N>
164 size_t RingBuffer<T, N>::pop(T& output) {
165  return pop(&output, 1);
166 }
167 
168 // Pop multiple elements
169 template <typename T, size_t N>
170 size_t RingBuffer<T, N>::pop(T* output, size_t len) {
171  if (len == 0) return 0;
173 
174  if (count == 0) {
176  return 0; // Buffer empty
177  }
178 
179  size_t charsToRead = RB_MIN(len, static_cast<size_t>(count));
180  size_t firstPart = RB_MIN(charsToRead, N - tail);
181 
182  if (charsToRead == 1) {
183  *output = buffer[tail];
184  } else {
185  memcpy(output, &buffer[tail], firstPart * sizeof(T));
186  size_t secondPart = charsToRead - firstPart;
187  if (secondPart > 0) {
188  // Wrap around the buffer and read remaining bytes from the beginning
189  memcpy(output + firstPart, buffer, secondPart * sizeof(T));
190  }
191  }
192 
193  tail = (tail + charsToRead) & (N - 1);
194  count -= charsToRead;
195 
196  // Reset tail & head when buffer becomes empty
197  if (count == 0) {
198  tail = 0;
199  head = 0;
200  }
201 
203  return charsToRead;
204 }
205 
206 // Peek at single element
207 template <typename T, size_t N>
208 size_t RingBuffer<T, N>::peek(T& output) const {
210  if (count == 0) {
212  return 0; // Buffer empty
213  }
214  output = buffer[tail]; // Read the element at tail without modifying it
216  return 1;
217 }
218 
219 // Peek at multiple elements
220 template <typename T, size_t N>
221 size_t RingBuffer<T, N>::peek(T* output, size_t len) const {
222  if (len == 0) return 0; // Nothing to peek
224  if (count == 0) {
226  return 0; // Nothing to peek
227  }
228 
229  size_t peekLen = RB_MIN(len, static_cast<size_t>(count));
230  size_t firstPart = RB_MIN(peekLen, N - tail);
231 
232  // Copy first segment
233  memcpy(output, &buffer[tail], firstPart * sizeof(T));
234 
235  // Copy second segment if wrap-around occurs
236  size_t secondPart = peekLen - firstPart;
237  if (secondPart > 0) {
238  memcpy(output + firstPart, buffer, secondPart * sizeof(T));
239  }
240 
242  return peekLen;
243 }
244 
245 // Consume a single element
246 template <typename T, size_t N>
249  if (count == 0) {
251  return 0; // empty
252  }
253  tail = (tail + 1) & (N - 1);
254  --count;
255  if (count == 0) { tail = 0; head = 0; }
257  return 1;
258 }
259 
260 // Consume multiple elements
261 template <typename T, size_t N>
262 size_t RingBuffer<T, N>::consume(size_t len) {
263  if (len == 0) return 0;
265  if (count == 0) {
267  return 0;
268  }
269  size_t to_consume = RB_MIN(len, static_cast<size_t>(count));
270  tail = (tail + to_consume) & (N - 1);
271  count = static_cast<IndexType>(static_cast<size_t>(count) - to_consume);
272  if (count == 0) { tail = 0; head = 0; }
274  return to_consume;
275 }
276 
277 // Clear the buffer
278 template <typename T, size_t N>
281  head = 0;
282  tail = 0;
283  count = 0;
285 }
286 
287 #endif // RINGBUFFER_H
#define RB_MIN(a, b)
Definition: RingBuffer.h:27
#define RB_CRITICAL_ENTER()
Definition: RingBuffer.h:48
#define RB_CRITICAL_EXIT()
Definition: RingBuffer.h:49
Definition: RingBuffer.h:53
size_t pop(T &output)
Definition: RingBuffer.h:164
size_t available() const
Definition: RingBuffer.h:79
size_t push(const T &data, bool overwrite=false)
Definition: RingBuffer.h:117
RingBuffer()
Definition: RingBuffer.h:55
void clear()
Definition: RingBuffer.h:279
size_t peek(T &output) const
Definition: RingBuffer.h:208
size_t capacity() const
Definition: RingBuffer.h:90
size_t consume()
Definition: RingBuffer.h:247