MAX30001G 1.0.1 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)
82 RB_CRITICAL_ENTER();
83 size_t c = count;
84 RB_CRITICAL_EXIT();
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;
125 RB_CRITICAL_ENTER();
126
127 size_t available_space = N - count;
128 if (data_len > available_space && !overwrite) {
129 RB_CRITICAL_EXIT();
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
158 RB_CRITICAL_EXIT();
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;
172 RB_CRITICAL_ENTER();
173
174 if (count == 0) {
175 RB_CRITICAL_EXIT();
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
202 RB_CRITICAL_EXIT();
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 {
209 RB_CRITICAL_ENTER();
210 if (count == 0) {
211 RB_CRITICAL_EXIT();
212 return 0; // Buffer empty
213 }
214 output = buffer[tail]; // Read the element at tail without modifying it
215 RB_CRITICAL_EXIT();
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
223 RB_CRITICAL_ENTER();
224 if (count == 0) {
225 RB_CRITICAL_EXIT();
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
241 RB_CRITICAL_EXIT();
242 return peekLen;
243 }
244
245 // Consume a single element
246 template <typename T, size_t N>
247 size_t RingBuffer<T, N>::consume() {
248 RB_CRITICAL_ENTER();
249 if (count == 0) {
250 RB_CRITICAL_EXIT();
251 return 0; // empty
252 }
253 tail = (tail + 1) & (N - 1);
254 --count;
255 if (count == 0) { tail = 0; head = 0; }
256 RB_CRITICAL_EXIT();
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;
264 RB_CRITICAL_ENTER();
265 if (count == 0) {
266 RB_CRITICAL_EXIT();
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; }
273 RB_CRITICAL_EXIT();
274 return to_consume;
275 }
276
277 // Clear the buffer
278 template <typename T, size_t N>
279 void RingBuffer<T, N>::clear() {
280 RB_CRITICAL_ENTER();
281 head = 0;
282 tail = 0;
283 count = 0;
284 RB_CRITICAL_EXIT();
285 }
286
287 #endif // RINGBUFFER_H
RB_MIN
#define RB_MIN(a, b)
Definition: RingBuffer.h:27
RB_CRITICAL_ENTER
#define RB_CRITICAL_ENTER()
Definition: RingBuffer.h:48
RB_CRITICAL_EXIT
#define RB_CRITICAL_EXIT()
Definition: RingBuffer.h:49
RingBuffer
Definition: RingBuffer.h:53
RingBuffer::pop
size_t pop(T &output)
Definition: RingBuffer.h:164
RingBuffer::available
size_t available() const
Definition: RingBuffer.h:79
RingBuffer::push
size_t push(const T &data, bool overwrite=false)
Definition: RingBuffer.h:117
RingBuffer::RingBuffer
RingBuffer()
Definition: RingBuffer.h:55
RingBuffer::clear
void clear()
Definition: RingBuffer.h:279
RingBuffer::peek
size_t peek(T &output) const
Definition: RingBuffer.h:208
RingBuffer::capacity
size_t capacity() const
Definition: RingBuffer.h:90
RingBuffer::consume
size_t consume()
Definition: RingBuffer.h:247