Skip to content

Commit 7932d59

Browse files
committed
py/ringbuf: Add micropython.ringbuffer() interface for general use.
1 parent bdbc444 commit 7932d59

File tree

6 files changed

+215
-4
lines changed

6 files changed

+215
-4
lines changed

py/modmicropython.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "py/runtime.h"
3232
#include "py/gc.h"
3333
#include "py/mphal.h"
34+
#include "py/ringbuf.h"
3435

3536
// Various builtins specific to MicroPython runtime,
3637
// living in micropython module
@@ -201,6 +202,9 @@ STATIC const mp_rom_map_elem_t mp_module_micropython_globals_table[] = {
201202
#if MICROPY_ENABLE_SCHEDULER
202203
{ MP_ROM_QSTR(MP_QSTR_schedule), MP_ROM_PTR(&mp_micropython_schedule_obj) },
203204
#endif
205+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
206+
{ MP_ROM_QSTR(MP_QSTR_ringbuffer), MP_ROM_PTR(&mp_type_micropython_ringbuffer) },
207+
#endif
204208
};
205209

206210
STATIC MP_DEFINE_CONST_DICT(mp_module_micropython_globals, mp_module_micropython_globals_table);

py/mpconfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,11 @@ typedef double mp_float_t;
904904
#define MICROPY_ENABLE_SCHEDULER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
905905
#endif
906906

907+
// Support for micropython.ringbuffer()
908+
#ifndef MICROPY_PY_MICROPYTHON_RINGBUFFER
909+
#define MICROPY_PY_MICROPYTHON_RINGBUFFER (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
910+
#endif
911+
907912
// Whether the scheduler supports scheduling static nodes with C callbacks
908913
#ifndef MICROPY_SCHEDULER_STATIC_NODES
909914
#define MICROPY_SCHEDULER_STATIC_NODES (0)

py/ringbuf.c

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,154 @@ int ringbuf_put16(ringbuf_t *r, uint16_t v) {
7171
r->iput = iput_b;
7272
return 0;
7373
}
74+
75+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
76+
77+
#include "py/runtime.h"
78+
#include "py/stream.h"
79+
#include "py/mphal.h"
80+
81+
typedef struct _micropython_ringbuffer_obj_t {
82+
mp_obj_base_t base;
83+
ringbuf_t ringbuffer;
84+
uint16_t timeout; // timeout waiting for first char (in ms)
85+
} micropython_ringbuffer_obj_t;
86+
87+
STATIC mp_obj_t micropython_ringbuffer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
88+
mp_arg_check_num(n_args, n_kw, 1, 2, false);
89+
mp_int_t buff_size = mp_obj_get_int(args[0]);
90+
micropython_ringbuffer_obj_t *self = mp_obj_malloc(micropython_ringbuffer_obj_t, type);
91+
// Add one extra to buff_size as ringbuf consumes one byte for tracking.
92+
ringbuf_alloc(&(self->ringbuffer), buff_size + 1);
93+
if (n_args > 1) {
94+
self->timeout = mp_obj_get_int(args[1]);
95+
}
96+
return MP_OBJ_FROM_PTR(self);
97+
}
98+
99+
STATIC mp_obj_t micropython_ringbuffer_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) {
100+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
101+
self->timeout = mp_obj_get_int(timeout_in);
102+
return mp_const_none;
103+
}
104+
STATIC MP_DEFINE_CONST_FUN_OBJ_2(micropython_ringbuffer_settimeout_obj, micropython_ringbuffer_settimeout);
105+
106+
107+
STATIC mp_uint_t micropython_ringbuffer_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
108+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
109+
uint32_t t = mp_hal_ticks_ms() + self->timeout;
110+
uint8_t *dest = buf_in;
111+
112+
for (size_t i = 0; i < size; i++) {
113+
// Wait for the first/next character.
114+
while (ringbuf_avail(&self->ringbuffer) == 0) {
115+
if (mp_hal_ticks_ms() > t) { // timed out
116+
if (i <= 0) {
117+
*errcode = MP_EAGAIN;
118+
return MP_STREAM_ERROR;
119+
} else {
120+
return i;
121+
}
122+
}
123+
MICROPY_EVENT_POLL_HOOK
124+
}
125+
*dest++ = ringbuf_get(&(self->ringbuffer));
126+
t = mp_hal_ticks_ms() + self->timeout;
127+
}
128+
return size;
129+
}
130+
131+
STATIC mp_uint_t micropython_ringbuffer_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
132+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
133+
uint32_t t = mp_hal_ticks_ms() + self->timeout;
134+
const uint8_t *src = buf_in;
135+
size_t i = 0;
136+
137+
// Put as many bytes as possible into the transmit buffer.
138+
while (i < size && ringbuf_free(&(self->ringbuffer)) > 0) {
139+
ringbuf_put(&(self->ringbuffer), *src++);
140+
++i;
141+
}
142+
// If ringbuf full, block until drained elsewhere (eg. irq) or timeout.
143+
while (i < size) {
144+
while (ringbuf_free(&(self->ringbuffer)) == 0) {
145+
if (mp_hal_ticks_ms() > t) { // timed out
146+
if (i <= 0) {
147+
*errcode = MP_EAGAIN;
148+
return MP_STREAM_ERROR;
149+
} else {
150+
return i;
151+
}
152+
}
153+
MICROPY_EVENT_POLL_HOOK
154+
}
155+
ringbuf_put(&(self->ringbuffer), *src++);
156+
++i;
157+
t = mp_hal_ticks_ms() + self->timeout;
158+
}
159+
// Just in case the fifo was drained during refill of the ringbuf.
160+
return size;
161+
}
162+
163+
STATIC mp_uint_t micropython_ringbuffer_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) {
164+
micropython_ringbuffer_obj_t *self = self_in;
165+
mp_uint_t ret;
166+
if (request == MP_STREAM_POLL) {
167+
uintptr_t flags = arg;
168+
ret = 0;
169+
if ((flags & MP_STREAM_POLL_RD) && ringbuf_avail(&self->ringbuffer) > 0) {
170+
ret |= MP_STREAM_POLL_RD;
171+
}
172+
if ((flags & MP_STREAM_POLL_WR) && ringbuf_free(&self->ringbuffer) > 0) {
173+
ret |= MP_STREAM_POLL_WR;
174+
}
175+
} else if (request == MP_STREAM_FLUSH) {
176+
// Should we wait here until empty / timeout?
177+
ret = 0;
178+
} else if (request == MP_STREAM_CLOSE) {
179+
// Should we flush here?
180+
ret = 0;
181+
} else {
182+
*errcode = MP_EINVAL;
183+
ret = MP_STREAM_ERROR;
184+
}
185+
return ret;
186+
}
187+
188+
STATIC mp_obj_t micropython_ringbuffer_any(mp_obj_t self_in) {
189+
micropython_ringbuffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
190+
return MP_OBJ_NEW_SMALL_INT(ringbuf_avail(&self->ringbuffer));
191+
}
192+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(micropython_ringbuffer_any_obj, micropython_ringbuffer_any);
193+
194+
195+
STATIC const mp_rom_map_elem_t micropython_ringbuffer_locals_dict_table[] = {
196+
{ MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&micropython_ringbuffer_any_obj) },
197+
{ MP_ROM_QSTR(MP_QSTR_settimeout), MP_ROM_PTR(&micropython_ringbuffer_settimeout_obj) },
198+
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
199+
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
200+
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
201+
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
202+
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
203+
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
204+
205+
};
206+
STATIC MP_DEFINE_CONST_DICT(micropython_ringbuffer_locals_dict, micropython_ringbuffer_locals_dict_table);
207+
208+
STATIC const mp_stream_p_t ringbuffer_stream_p = {
209+
.read = micropython_ringbuffer_read,
210+
.write = micropython_ringbuffer_write,
211+
.ioctl = micropython_ringbuffer_ioctl,
212+
.is_text = false,
213+
};
214+
215+
MP_DEFINE_CONST_OBJ_TYPE(
216+
mp_type_micropython_ringbuffer,
217+
MP_QSTR_ringbuffer,
218+
MP_TYPE_FLAG_NONE,
219+
make_new, micropython_ringbuffer_make_new,
220+
protocol, &ringbuffer_stream_p,
221+
locals_dict, &micropython_ringbuffer_locals_dict
222+
);
223+
224+
#endif

py/ringbuf.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@
2828

2929
#include <stddef.h>
3030
#include <stdint.h>
31-
32-
#ifdef _MSC_VER
33-
#include "py/mpconfig.h" // For inline.
34-
#endif
31+
#include "py/obj.h"
32+
#include "py/mpconfig.h"
3533

3634
typedef struct _ringbuf_t {
3735
uint8_t *buf;
@@ -96,4 +94,8 @@ int ringbuf_get16(ringbuf_t *r);
9694
int ringbuf_peek16(ringbuf_t *r);
9795
int ringbuf_put16(ringbuf_t *r, uint16_t v);
9896

97+
#if MICROPY_PY_MICROPYTHON_RINGBUFFER
98+
extern const mp_obj_type_t mp_type_micropython_ringbuffer;
99+
#endif
100+
99101
#endif // MICROPY_INCLUDED_PY_RINGBUF_H

tests/micropython/ringbuffer.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# check that micropython.ringbuffer works correctly.
2+
3+
import micropython
4+
5+
try:
6+
micropython.ringbuffer
7+
except AttributeError:
8+
print("SKIP")
9+
raise SystemExit
10+
11+
rb = micropython.ringbuffer(16)
12+
print(rb)
13+
14+
print(rb.any())
15+
16+
rb.write(b"\x00")
17+
print(rb.any())
18+
19+
rb.write(b"\x00")
20+
print(rb.any())
21+
22+
print(rb.read(2))
23+
print(rb.any())
24+
25+
26+
rb.write(b"\x00\x01")
27+
print(rb.read())
28+
29+
print(rb.read(1))
30+
31+
print(rb.write(b"\x00\x01" * 10))
32+
print(rb.read())
33+
34+
try:
35+
# size must be int.
36+
micropython.ringbuffer(None)
37+
except TypeError as ex:
38+
print(ex)

tests/micropython/ringbuffer.py.exp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<ringbuffer>
2+
0
3+
1
4+
2
5+
b'\x00\x00'
6+
0
7+
b'\x00\x01'
8+
None
9+
15
10+
can't convert NoneType to int
11+
buffer size must be power of 2

0 commit comments

Comments
 (0)