Skip to content

Commit 199e16c

Browse files
committed
extmod/machine_i2c_target: Add machine.I2CTarget.
Signed-off-by: Damien George <[email protected]>
1 parent ebc9525 commit 199e16c

File tree

5 files changed

+367
-0
lines changed

5 files changed

+367
-0
lines changed

extmod/extmod.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set(MICROPY_SOURCE_EXTMOD
1111
${MICROPY_EXTMOD_DIR}/machine_adc_block.c
1212
${MICROPY_EXTMOD_DIR}/machine_bitstream.c
1313
${MICROPY_EXTMOD_DIR}/machine_i2c.c
14+
${MICROPY_EXTMOD_DIR}/machine_i2c_target.c
1415
${MICROPY_EXTMOD_DIR}/machine_i2s.c
1516
${MICROPY_EXTMOD_DIR}/machine_mem.c
1617
${MICROPY_EXTMOD_DIR}/machine_pulse.c

extmod/extmod.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ SRC_EXTMOD_C += \
66
extmod/machine_adc_block.c \
77
extmod/machine_bitstream.c \
88
extmod/machine_i2c.c \
9+
extmod/machine_i2c_target.c \
910
extmod/machine_i2s.c \
1011
extmod/machine_mem.c \
1112
extmod/machine_pinbase.c \

extmod/machine_i2c_target.c

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
/*
2+
* This file is part of the MicroPython project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2025 Damien P. George
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#include "py/runtime.h"
28+
29+
#if MICROPY_PY_MACHINE_I2C_TARGET
30+
31+
#include "extmod/modmachine.h"
32+
#include "shared/runtime/mpirq.h"
33+
34+
enum {
35+
I2C_TARGET_IRQ_ADDR_MATCH = 1 << 0,
36+
I2C_TARGET_IRQ_READ_REQ = 1 << 1,
37+
I2C_TARGET_IRQ_WRITE_REQ = 1 << 2,
38+
I2C_TARGET_IRQ_END = 1 << 3,
39+
I2C_TARGET_IRQ_STOP = 1 << 4, // should be able to support this on all targets (rp2 needs custom code though), but maybe can't support addr_match?
40+
41+
I2C_TARGET_IRQ_MEM_ADDR_MATCH = 1 << 5, // needed?
42+
I2C_TARGET_IRQ_READ_START = 1 << 6, // needed?
43+
I2C_TARGET_IRQ_WRITE_START = 1 << 7, // needed?
44+
I2C_TARGET_IRQ_READ_END = 1 << 8, // needed?
45+
I2C_TARGET_IRQ_WRITE_END = 1 << 9, // needed?
46+
};
47+
48+
/*
49+
mp_machine_i2c_target_event_addr_match(*, read_write)
50+
mp_machine_i2c_target_event_read_req(*)
51+
mp_machine_i2c_target_event_write_req(*)
52+
mp_machine_i2c_target_event_stop(*)
53+
*/
54+
55+
enum {
56+
STATE_IDLE,
57+
STATE_MEM_ADDR_SELECTED,
58+
STATE_READING,
59+
STATE_WRITING,
60+
};
61+
62+
typedef struct _machine_i2c_target_data_t {
63+
uint8_t state;
64+
uint32_t mem_addr;
65+
uint32_t mem_len;
66+
uint8_t *mem_buf;
67+
} machine_i2c_target_data_t;
68+
69+
typedef struct _machine_i2c_target_irq_obj_t {
70+
mp_irq_obj_t base;
71+
uint32_t flags;
72+
uint32_t trigger;
73+
} machine_i2c_target_irq_obj_t;
74+
75+
// The port must provide implementations of these low-level I2C target functions.
76+
77+
static void mp_machine_i2c_target_deinit_all_port(void);
78+
79+
static void mp_machine_i2c_target_event_callback(machine_i2c_target_irq_obj_t *irq);
80+
81+
// read up to N bytes
82+
// return the number of bytes read
83+
static mp_int_t mp_machine_i2c_target_read_bytes(machine_i2c_target_obj_t *self, size_t len, uint8_t *buf);
84+
85+
// write/buffer N bytes
86+
// return the number of bytes written/buffered
87+
static mp_int_t mp_machine_i2c_target_write_bytes(machine_i2c_target_obj_t *self, size_t len, const uint8_t *buf);
88+
89+
static void mp_machine_i2c_target_irq_config(machine_i2c_target_obj_t *self, unsigned int trigger);
90+
91+
static mp_obj_t mp_machine_i2c_target_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args);
92+
static void mp_machine_i2c_target_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind);
93+
static void mp_machine_i2c_target_deinit(machine_i2c_target_obj_t *self);
94+
95+
static const mp_irq_methods_t machine_i2c_target_irq_methods;
96+
97+
#define I2C_TARGET_NUM_IRQ (4)
98+
99+
// Needed to retain a root pointer to the memory object.
100+
MP_REGISTER_ROOT_POINTER(mp_obj_t i2c_target_mem_obj[I2C_TARGET_NUM_IRQ]);
101+
102+
MP_REGISTER_ROOT_POINTER(void *machine_i2c_target_irq_obj[I2C_TARGET_NUM_IRQ]);
103+
104+
void mp_machine_i2c_target_deinit_all(void) {
105+
for (size_t i = 0; i < I2C_TARGET_NUM_IRQ; ++i) {
106+
MP_STATE_PORT(i2c_target_mem_obj[i]) = MP_OBJ_NULL;
107+
MP_STATE_PORT(machine_i2c_target_irq_obj[i]) = NULL;
108+
}
109+
mp_machine_i2c_target_deinit_all_port();
110+
}
111+
112+
static bool event(machine_i2c_target_data_t *data, unsigned int trigger, size_t arg0, size_t arg1) {
113+
unsigned int id = 0; // TODO
114+
machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[id]);
115+
if (irq != NULL && (trigger & irq->trigger)) {
116+
irq->flags = trigger & irq->trigger;
117+
mp_machine_i2c_target_event_callback(irq);
118+
return true; // irq handled
119+
}
120+
return false; // irq not handled
121+
}
122+
123+
static void machine_i2c_target_data_init(machine_i2c_target_data_t *data, mp_obj_t mem_obj, mp_int_t mem_addrsize) {
124+
data->state = STATE_IDLE;
125+
data->mem_addr = 0;
126+
data->mem_len = 0;
127+
data->mem_buf = NULL;
128+
if (mem_obj != mp_const_none) {
129+
mp_buffer_info_t bufinfo;
130+
mp_get_buffer_raise(mem_obj, &bufinfo, MP_BUFFER_RW);
131+
if (mem_addrsize < 8 || mem_addrsize > 32 || mem_addrsize % 8 != 0) {
132+
mp_raise_ValueError(MP_ERROR_TEXT("mem_addrsize must be 8, 16, 24 or 32"));
133+
}
134+
data->mem_len = bufinfo.len;
135+
data->mem_buf = bufinfo.buf;
136+
}
137+
}
138+
139+
static void machine_i2c_target_data_reset_helper(machine_i2c_target_data_t *data) {
140+
size_t len = 0; // TODO
141+
if (data->state == STATE_READING) {
142+
event(data, I2C_TARGET_IRQ_READ_END, data->mem_addr, len);
143+
} else if (data->state == STATE_WRITING) {
144+
event(data, I2C_TARGET_IRQ_WRITE_END, data->mem_addr, len);
145+
}
146+
data->state = STATE_IDLE;
147+
}
148+
149+
static void machine_i2c_target_data_addr_match(machine_i2c_target_data_t *data, bool read) {
150+
event(data, I2C_TARGET_IRQ_ADDR_MATCH, read, 0);
151+
machine_i2c_target_data_reset_helper(data);
152+
}
153+
154+
static void machine_i2c_target_data_read_request(machine_i2c_target_obj_t *self, machine_i2c_target_data_t *data) {
155+
// Let the user handle the read request.
156+
bool event_handled = event(data, I2C_TARGET_IRQ_READ_REQ, 0, 0);
157+
if (data->mem_buf == NULL) {
158+
if (!event_handled) {
159+
// No data source, just write out a zero.
160+
uint8_t val = 0;
161+
mp_machine_i2c_target_write_bytes(self, 1, &val);
162+
}
163+
} else {
164+
// Have a buffer.
165+
if (data->state == STATE_MEM_ADDR_SELECTED) {
166+
data->state = STATE_READING;
167+
event(data, I2C_TARGET_IRQ_READ_START, data->mem_addr, 0);
168+
}
169+
uint8_t val = data->mem_buf[data->mem_addr++];
170+
if (data->mem_addr >= data->mem_len) {
171+
data->mem_addr = 0;
172+
}
173+
mp_machine_i2c_target_write_bytes(self, 1, &val);
174+
}
175+
}
176+
177+
static void machine_i2c_target_data_write_request(machine_i2c_target_obj_t *self, machine_i2c_target_data_t *data) {
178+
// Let the user handle the write request.
179+
bool event_handled = event(data, I2C_TARGET_IRQ_WRITE_REQ, 0, 0);
180+
if (data->mem_buf == NULL) {
181+
if (!event_handled) {
182+
// No data sink, just read and discard the incoming byte.
183+
uint8_t buf = 0;
184+
mp_machine_i2c_target_read_bytes(self, 1, &buf);
185+
}
186+
} else {
187+
// Have a buffer.
188+
uint8_t val = 0;
189+
mp_machine_i2c_target_read_bytes(self, 1, &val);
190+
if (data->state == STATE_IDLE) {
191+
// TODO allow N bytes for address, with N=0 allowed
192+
data->state = STATE_MEM_ADDR_SELECTED;
193+
data->mem_addr = val % data->mem_len;
194+
event(data, I2C_TARGET_IRQ_MEM_ADDR_MATCH, data->mem_addr, 0);
195+
} else {
196+
if (data->state == STATE_MEM_ADDR_SELECTED) {
197+
data->state = STATE_WRITING;
198+
event(data, I2C_TARGET_IRQ_WRITE_START, data->mem_addr, 0);
199+
}
200+
data->mem_buf[data->mem_addr++] = val;
201+
if (data->mem_addr >= data->mem_len) {
202+
data->mem_addr = 0;
203+
}
204+
}
205+
}
206+
}
207+
208+
static inline void machine_i2c_target_data_restart_or_stop(machine_i2c_target_data_t *data) {
209+
machine_i2c_target_data_reset_helper(data);
210+
event(data, I2C_TARGET_IRQ_END, 0, 0);
211+
}
212+
213+
static inline void machine_i2c_target_data_stop(machine_i2c_target_data_t *data) {
214+
machine_i2c_target_data_reset_helper(data);
215+
event(data, I2C_TARGET_IRQ_END | I2C_TARGET_IRQ_STOP, 0, 0);
216+
}
217+
218+
// The port provides implementations of its bindings in this file.
219+
#include MICROPY_PY_MACHINE_I2C_TARGET_INCLUDEFILE
220+
221+
// I2CTarget.deinit()
222+
static mp_obj_t machine_i2c_target_deinit(mp_obj_t self_in) {
223+
machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in);
224+
mp_machine_i2c_target_deinit(self);
225+
return mp_const_none;
226+
}
227+
static MP_DEFINE_CONST_FUN_OBJ_1(machine_i2c_target_deinit_obj, machine_i2c_target_deinit);
228+
229+
// I2CTarget.readinto(buf)
230+
static mp_obj_t machine_i2c_target_readinto(mp_obj_t self_in, mp_obj_t buf_in) {
231+
machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in);
232+
mp_buffer_info_t bufinfo;
233+
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
234+
return MP_OBJ_NEW_SMALL_INT(mp_machine_i2c_target_read_bytes(self, bufinfo.len, bufinfo.buf));
235+
}
236+
static MP_DEFINE_CONST_FUN_OBJ_2(machine_i2c_target_readinto_obj, machine_i2c_target_readinto);
237+
238+
// I2CTarget.write(data)
239+
static mp_obj_t machine_i2c_target_write(mp_obj_t self_in, mp_obj_t data_in) {
240+
machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in);
241+
mp_buffer_info_t bufinfo;
242+
mp_get_buffer_raise(data_in, &bufinfo, MP_BUFFER_READ);
243+
return MP_OBJ_NEW_SMALL_INT(mp_machine_i2c_target_write_bytes(self, bufinfo.len, bufinfo.buf));
244+
}
245+
static MP_DEFINE_CONST_FUN_OBJ_2(machine_i2c_target_write_obj, machine_i2c_target_write);
246+
247+
static machine_i2c_target_irq_obj_t *machine_i2c_target_get_irq(machine_i2c_target_obj_t *self) {
248+
// Get the IRQ object.
249+
machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[0]);
250+
251+
// Allocate the IRQ object if it doesn't already exist.
252+
if (irq == NULL) {
253+
irq = m_new_obj(machine_i2c_target_irq_obj_t);
254+
irq->base.base.type = &mp_irq_type;
255+
irq->base.methods = (mp_irq_methods_t *)&machine_i2c_target_irq_methods;
256+
irq->base.parent = MP_OBJ_FROM_PTR(self);
257+
irq->base.handler = mp_const_none;
258+
irq->base.ishard = false;
259+
MP_STATE_PORT(machine_i2c_target_irq_obj[0]) = irq;
260+
}
261+
return irq;
262+
}
263+
264+
// I2CTarget.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING, hard=False)
265+
static mp_obj_t machine_i2c_target_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
266+
enum { ARG_handler, ARG_trigger, ARG_hard };
267+
static const mp_arg_t allowed_args[] = {
268+
{ MP_QSTR_handler, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
269+
{ MP_QSTR_trigger, MP_ARG_INT, {.u_int = 0} },
270+
{ MP_QSTR_hard, MP_ARG_BOOL, {.u_bool = false} },
271+
};
272+
machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
273+
274+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
275+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
276+
277+
machine_i2c_target_irq_obj_t *irq = machine_i2c_target_get_irq(self);
278+
279+
if (n_args > 1 || kw_args->used != 0) {
280+
// Update IRQ data.
281+
mp_obj_t handler = args[ARG_handler].u_obj;
282+
mp_uint_t trigger = args[ARG_trigger].u_int;
283+
bool hard = args[ARG_hard].u_bool;
284+
285+
if (!hard) {
286+
mp_raise_ValueError(MP_ERROR_TEXT("hard IRQ required"));
287+
}
288+
289+
// Disable all IRQs while data is updated.
290+
mp_machine_i2c_target_irq_config(self, 0);
291+
292+
// Update IRQ data.
293+
irq->base.handler = handler;
294+
irq->base.ishard = hard;
295+
irq->flags = 0;
296+
irq->trigger = trigger;
297+
298+
// Enable IRQ if a handler is given.
299+
if (handler != mp_const_none && trigger != 0) {
300+
mp_machine_i2c_target_irq_config(self, trigger);
301+
}
302+
}
303+
return MP_OBJ_FROM_PTR(irq);
304+
}
305+
static MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2c_target_irq_obj, 1, machine_i2c_target_irq);
306+
307+
static const mp_rom_map_elem_t machine_i2c_target_locals_dict_table[] = {
308+
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2c_target_deinit_obj) },
309+
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&machine_i2c_target_readinto_obj) },
310+
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&machine_i2c_target_write_obj) },
311+
{ MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_i2c_target_irq_obj) },
312+
313+
{ MP_ROM_QSTR(MP_QSTR_IRQ_ADDR_MATCH), MP_ROM_INT(I2C_TARGET_IRQ_ADDR_MATCH) },
314+
{ MP_ROM_QSTR(MP_QSTR_IRQ_READ_REQ), MP_ROM_INT(I2C_TARGET_IRQ_READ_REQ) },
315+
{ MP_ROM_QSTR(MP_QSTR_IRQ_WRITE_REQ), MP_ROM_INT(I2C_TARGET_IRQ_WRITE_REQ) },
316+
{ MP_ROM_QSTR(MP_QSTR_IRQ_END), MP_ROM_INT(I2C_TARGET_IRQ_END) },
317+
{ MP_ROM_QSTR(MP_QSTR_IRQ_STOP), MP_ROM_INT(I2C_TARGET_IRQ_STOP) },
318+
};
319+
static MP_DEFINE_CONST_DICT(machine_i2c_target_locals_dict, machine_i2c_target_locals_dict_table);
320+
321+
MP_DEFINE_CONST_OBJ_TYPE(
322+
machine_i2c_target_type,
323+
MP_QSTR_I2CTarget,
324+
MP_TYPE_FLAG_NONE,
325+
make_new, mp_machine_i2c_target_make_new,
326+
print, mp_machine_i2c_target_print,
327+
locals_dict, &machine_i2c_target_locals_dict
328+
);
329+
330+
static mp_uint_t machine_i2c_target_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) {
331+
machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in);
332+
machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[0]);
333+
mp_machine_i2c_target_irq_config(self, 0);
334+
irq->flags = 0;
335+
irq->trigger = new_trigger;
336+
mp_machine_i2c_target_irq_config(self, new_trigger);
337+
return 0;
338+
}
339+
340+
static mp_uint_t machine_i2c_target_irq_info(mp_obj_t self_in, mp_uint_t info_type) {
341+
// machine_i2c_target_obj_t *self = MP_OBJ_TO_PTR(self_in);
342+
machine_i2c_target_irq_obj_t *irq = MP_STATE_PORT(machine_i2c_target_irq_obj[0]);
343+
if (info_type == MP_IRQ_INFO_FLAGS) {
344+
return irq->flags;
345+
} else if (info_type == MP_IRQ_INFO_TRIGGERS) {
346+
return irq->trigger;
347+
}
348+
return 0;
349+
}
350+
351+
static const mp_irq_methods_t machine_i2c_target_irq_methods = {
352+
.trigger = machine_i2c_target_irq_trigger,
353+
.info = machine_i2c_target_irq_info,
354+
};
355+
356+
#endif // MICROPY_PY_MACHINE_I2C_TARGET

extmod/modmachine.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ static const mp_rom_map_elem_t machine_module_globals_table[] = {
219219
#if MICROPY_PY_MACHINE_I2C
220220
{ MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) },
221221
#endif
222+
#if MICROPY_PY_MACHINE_I2C_TARGET
223+
{ MP_ROM_QSTR(MP_QSTR_I2CTarget), MP_ROM_PTR(&machine_i2c_target_type) },
224+
#endif
222225
#if MICROPY_PY_MACHINE_I2S
223226
{ MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) },
224227
#endif

0 commit comments

Comments
 (0)