diff --git a/micropython/modules/opencube_brick/brick_api.c b/micropython/modules/opencube_brick/brick_api.c index 310d7691b03779132b5e3ce4b96bb031eb4aac45..807a3156b66f8bcfeed764cbfa7ec1f59e8a6537 100644 --- a/micropython/modules/opencube_brick/brick_api.c +++ b/micropython/modules/opencube_brick/brick_api.c @@ -9,7 +9,7 @@ extern const mp_obj_type_t opencube_battery_type; extern const mp_obj_type_t opencube_ICM_type; extern const mp_obj_type_t opencube_buttons_type; extern const mp_obj_type_t opencube_led_type; -extern const mp_obj_type_t opencube_i2c_slave_type; +extern const mp_obj_type_t oc_i2c_slave_type; static mp_obj_t i2c_lock_fun(void) { opencube_lock_i2c_or_raise(); @@ -31,7 +31,7 @@ static const mp_rom_map_elem_t brick_globals_dict[] = { {MP_ROM_QSTR(MP_QSTR_ICM), MP_ROM_PTR(&opencube_ICM_type)}, {MP_ROM_QSTR(MP_QSTR_Buttons), MP_ROM_PTR(&opencube_buttons_type)}, {MP_ROM_QSTR(MP_QSTR_Led), MP_ROM_PTR(&opencube_led_type)}, - {MP_ROM_QSTR(MP_QSTR_I2C_slave), MP_ROM_PTR(&opencube_i2c_slave_type)}, + {MP_ROM_QSTR(MP_QSTR_I2C_slave), MP_ROM_PTR(&oc_i2c_slave_type)}, {MP_ROM_QSTR(MP_QSTR_lock_i2c), MP_ROM_PTR(&i2c_lock_fun_obj)}, {MP_ROM_QSTR(MP_QSTR_unlock_i2c), MP_ROM_PTR(&i2c_unlock_fun_obj)}, {MP_ROM_QSTR(MP_QSTR_InternalI2CBusyError), MP_ROM_PTR(&mp_type_InternalI2CBusyError)}, diff --git a/micropython/modules/opencube_brick/oc_i2c_slave.c b/micropython/modules/opencube_brick/oc_i2c_slave.c index 7ba2827a7b84f34c42c99585d9e27f90e5c872d6..8d8a55324babc2108732aa007415feb2b8eb8e35 100644 --- a/micropython/modules/opencube_brick/oc_i2c_slave.c +++ b/micropython/modules/opencube_brick/oc_i2c_slave.c @@ -1,129 +1,287 @@ +#include <string.h> #include <py/runtime.h> +#include <py/mperrno.h> #include <hardware/i2c.h> #include <hardware/gpio.h> #include <pico/i2c_slave.h> +#include <shared/runtime/mpirq.h> #include <opencube_hw.h> #include "PCF8575.h" +#define OC_I2C_SLAVE_READ _u(0x00000001) +#define OC_I2C_SLAVE_RECEIVE _u(0x00000002) +#define OC_I2C_SLAVE_ALLOWED_FLAGS (OC_I2C_SLAVE_READ | OC_I2C_SLAVE_RECEIVE) + // The slave implements a 256 byte memory. To write a series of bytes, the master first // writes the memory address, followed by the data. The address is automatically incremented // for each byte transferred, looping back to 0 upon reaching the end. Reading is done // sequentially from the current memory address. -static struct -{ +typedef struct { uint8_t mem[256]; uint8_t mem_address; bool mem_address_written; -} context; +} oc_i2c_slave_memory; -typedef struct _opencube_i2c_slave_obj_t { +typedef struct _oc_i2c_slave_obj_t { mp_obj_base_t base; -} opencube_i2c_slave_obj_t; - -const mp_obj_type_t opencube_i2c_slave_type; - -static void opencube_i2c_slave_print(const mp_print_t *print, mp_obj_t self_in); -static void opencube_i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event); - -// Called on i2c_slave init -static mp_obj_t opencube_i2c_slave_make_new(const mp_obj_type_t* type, size_t n_args, size_t n_kw, const mp_obj_t* args) { - opencube_i2c_slave_obj_t *self = mp_obj_malloc_with_finaliser(opencube_i2c_slave_obj_t, &opencube_i2c_slave_type); - - PCF8575_init(); - PCF8575_write_pin(I2C_STRONG_PULL_RPIN, true); + bool enabled; + uint8_t address; + oc_i2c_slave_memory context; + uint16_t mp_irq_trigger; // user IRQ trigger mask + uint16_t mp_irq_flags; // user IRQ active IRQ flags + mp_irq_obj_t *mp_irq_obj; // user IRQ object +} oc_i2c_slave_obj_t; - gpio_init(UTZ_I2C_SDA_PIN); - gpio_set_function(UTZ_I2C_SDA_PIN, GPIO_FUNC_I2C); +const mp_obj_type_t oc_i2c_slave_type; - gpio_init(UTZ_I2C_SCK_PIN); - gpio_set_function(UTZ_I2C_SCK_PIN, GPIO_FUNC_I2C); +static oc_i2c_slave_obj_t oc_i2c_slave_obj = { + {&oc_i2c_slave_type}, + false, + 0, + {{0}}, + 0, + 0, + NULL +}; - i2c_init(UTZ_I2C_BUS, MULTICUBE_I2C_BAUD_RATE); - // configure for slave mode - i2c_slave_init(UTZ_I2C_BUS, 0, &opencube_i2c_slave_handler); - - return MP_OBJ_FROM_PTR(self); -} +static bool reserved_addr(uint8_t addr); +static void oc_i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event); +static mp_obj_t oc_i2c_slave_init_helper(oc_i2c_slave_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +static mp_irq_obj_t *oc_i2c_slave_irq_helper(oc_i2c_slave_obj_t *self, bool any_args, mp_arg_val_t *args); -static void opencube_i2c_slave_print(const mp_print_t *print, mp_obj_t self_in) { - //i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); +// I2C reserves some addresses for special purposes. We exclude these from the scan. +// These are any addresses of the form 000 0xxx or 111 1xxx +static bool reserved_addr(uint8_t addr) { + return (addr & 0x78) == 0 || (addr & 0x78) == 0x78; } - // Our handler is called from the I2C ISR, so it must complete quickly. Blocking calls / // printing to stdio may interfere with interrupt handling. -static void opencube_i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { +static void oc_i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { + switch (event) { case I2C_SLAVE_RECEIVE: // master has written some data - if (!context.mem_address_written) { + if (oc_i2c_slave_obj.context.mem_address_written) { // writes always start with the memory address - context.mem_address = i2c_read_byte_raw(i2c); - context.mem_address_written = true; + oc_i2c_slave_obj.context.mem_address = i2c_read_byte_raw(i2c); + oc_i2c_slave_obj.context.mem_address_written = true; } else { // save into memory - context.mem[context.mem_address] = i2c_read_byte_raw(i2c); - context.mem_address++; + oc_i2c_slave_obj.context.mem[oc_i2c_slave_obj.context.mem_address] = i2c_read_byte_raw(i2c); + oc_i2c_slave_obj.context.mem_address++; + oc_i2c_slave_obj.mp_irq_flags |= OC_I2C_SLAVE_RECEIVE; } break; + case I2C_SLAVE_REQUEST: // master is requesting data // load from memory - i2c_write_byte_raw(i2c, context.mem[context.mem_address]); - context.mem_address++; + i2c_write_byte_raw(i2c, oc_i2c_slave_obj.context.mem[oc_i2c_slave_obj.context.mem_address]); + oc_i2c_slave_obj.context.mem_address++; + oc_i2c_slave_obj.mp_irq_flags |= OC_I2C_SLAVE_READ; break; + case I2C_SLAVE_FINISH: // master has signalled Stop / Restart - context.mem_address = 0; - context.mem_address_written = false; + oc_i2c_slave_obj.context.mem_address = 0; + oc_i2c_slave_obj.context.mem_address_written = false; + // Check the flags to see if the user handler should be called + if (oc_i2c_slave_obj.mp_irq_trigger & oc_i2c_slave_obj.mp_irq_flags) { + mp_irq_handler(oc_i2c_slave_obj.mp_irq_obj); + } break; default: break; } } -// read -static mp_obj_t opencube_i2c_slave_read(mp_obj_t self_in) { - //opencube_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); +static mp_obj_t oc_i2c_slave_init_helper(oc_i2c_slave_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_address }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_address, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (args[ARG_address].u_int > 0) { + if (reserved_addr(args[ARG_address].u_int)) { + mp_raise_msg_varg( + &mp_type_ValueError, + MP_ERROR_TEXT("I2C slave address %d is reserved"), + args[ARG_address].u_int + ); + } + if (self->enabled) { + i2c_slave_deinit(UTZ_I2C_BUS); + } + memset(&self->context, 0, sizeof(oc_i2c_slave_memory)); + self->mp_irq_obj = NULL; + self->mp_irq_trigger = 0; + self->mp_irq_flags = 0; + self->enabled = true; + self->address = args[ARG_address].u_int; + + PCF8575_init(); + PCF8575_write_pin(I2C_STRONG_PULL_RPIN, true); + + gpio_init(UTZ_I2C_SDA_PIN); + gpio_set_function(UTZ_I2C_SDA_PIN, GPIO_FUNC_I2C); + + gpio_init(UTZ_I2C_SCK_PIN); + gpio_set_function(UTZ_I2C_SCK_PIN, GPIO_FUNC_I2C); + + i2c_init(UTZ_I2C_BUS, MULTICUBE_I2C_BAUD_RATE); + // configure for slave mode + i2c_slave_init(UTZ_I2C_BUS, self->address, &oc_i2c_slave_handler); + } + return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_1(opencube_i2c_slave_read_obj, opencube_i2c_slave_read); -// write -static mp_obj_t opencube_i2c_slave_write(mp_obj_t self_in) { - //opencube_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); +// Called on i2c_slave init I2C_slave() +static mp_obj_t oc_i2c_slave_make_new(const mp_obj_type_t* type, size_t n_args, size_t n_kw, const mp_obj_t* args) { + // Get static peripheral object. + oc_i2c_slave_obj_t *self = (oc_i2c_slave_obj_t *)&oc_i2c_slave_obj; + if (!self->enabled) { + self->enabled = true; + + if (n_args > 0 || n_kw > 0) { + + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + oc_i2c_slave_init_helper(self, n_args, args, &kw_args); + } + } + return MP_OBJ_FROM_PTR(self); +} + +// I2C_slave.init() +static mp_obj_t oc_i2c_slave_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(args[0]); + return oc_i2c_slave_init_helper(self, n_args - 1, args + 1, kw_args); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(oc_i2c_slave_init_obj, 1, oc_i2c_slave_init); + +static void oc_i2c_slave_print(const mp_print_t *print, mp_obj_t self_in) { + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "Open-Cube I2C slave at address %u, irq=%d", self->address,self->mp_irq_trigger); +} + +// I2C_slave.read() +static mp_obj_t oc_i2c_slave_read(mp_obj_t self_in, mp_obj_t addr) { + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_obj_is_int(addr)) { + uint8_t address = mp_obj_get_int(addr); + return mp_obj_new_int(self->context.mem[address]); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(oc_i2c_slave_read_obj, oc_i2c_slave_read); + +// I2C_slave.write() +static mp_obj_t oc_i2c_slave_write(mp_obj_t self_in, mp_obj_t addr, mp_obj_t data) { + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (mp_obj_is_int(addr) && mp_obj_is_int(data)) { + uint8_t address = mp_obj_get_int(addr); + uint8_t value = mp_obj_get_int(data); + self->context.mem[address] = value; + } return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_1(opencube_i2c_slave_write_obj, opencube_i2c_slave_write); +static MP_DEFINE_CONST_FUN_OBJ_3(oc_i2c_slave_write_obj, oc_i2c_slave_write); + +// I2C_slave.irq() +static mp_obj_t oc_i2c_slave_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + mp_arg_val_t args[MP_IRQ_ARG_INIT_NUM_ARGS]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_IRQ_ARG_INIT_NUM_ARGS, mp_irq_init_args, args); + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + bool any_args = n_args > 1 || kw_args->used != 0; + return MP_OBJ_FROM_PTR(oc_i2c_slave_irq_helper(self, any_args, args)); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(oc_i2c_slave_irq_obj, 1, oc_i2c_slave_irq); // Called on i2c_slave deinit -static mp_obj_t opencube_i2c_slave_deinit(mp_obj_t self_in) { - //opencube_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); +static mp_obj_t oc_i2c_slave_deinit(mp_obj_t self_in) { + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); i2c_slave_deinit(UTZ_I2C_BUS); + i2c_deinit(UTZ_I2C_BUS); + self->enabled = false; + self->mp_irq_obj = NULL; + self->mp_irq_trigger = 0; return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_1(opencube_i2c_slave_deinit_obj, opencube_i2c_slave_deinit); +static MP_DEFINE_CONST_FUN_OBJ_1(oc_i2c_slave_deinit_obj, oc_i2c_slave_deinit); -// I2C reserves some addresses for special purposes. We exclude these from the scan. -// These are any addresses of the form 000 0xxx or 111 1xxx -bool reserved_addr(uint8_t addr) { - return (addr & 0x78) == 0 || (addr & 0x78) == 0x78; +static mp_uint_t oc_i2c_slave_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); + self->mp_irq_trigger = new_trigger; + return 0; +} + +static mp_uint_t oc_i2c_slave_irq_info(mp_obj_t self_in, mp_uint_t info_type) { + oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (info_type == MP_IRQ_INFO_FLAGS) { + return self->mp_irq_flags; + } else if (info_type == MP_IRQ_INFO_TRIGGERS) { + return self->mp_irq_trigger; + } + return 0; +} + +static const mp_irq_methods_t oc_i2c_slave_irq_methods = { + .trigger = oc_i2c_slave_irq_trigger, + .info = oc_i2c_slave_irq_info, +}; + +static mp_irq_obj_t *oc_i2c_slave_irq_helper(oc_i2c_slave_obj_t *self, bool any_args, mp_arg_val_t *args) { + if (self->mp_irq_obj == NULL) { + self->mp_irq_trigger = 0; + self->mp_irq_obj = mp_irq_new(&oc_i2c_slave_irq_methods, MP_OBJ_FROM_PTR(self)); + } + + if (any_args) { + // Check the handler + mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; + if (handler != mp_const_none && !mp_obj_is_callable(handler)) { + mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); + } + + // Check the trigger + mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; + mp_uint_t not_supported = trigger & ~OC_I2C_SLAVE_ALLOWED_FLAGS; + if (trigger != 0 && not_supported) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%04x unsupported"), not_supported); + } + + self->mp_irq_obj->handler = handler; + self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; + self->mp_irq_trigger = trigger; + } + + return self->mp_irq_obj; } // This collects all methods and other static class attributes of the i2c_slave. // The table structure is similar to the module table, as detailed below. -static const mp_rom_map_elem_t opencube_i2c_slave_locals_dict[] = { - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&opencube_i2c_slave_read_obj) }, - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&opencube_i2c_slave_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&opencube_i2c_slave_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&opencube_i2c_slave_deinit_obj) }, +static const mp_rom_map_elem_t oc_i2c_slave_locals_dict[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&oc_i2c_slave_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&oc_i2c_slave_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&oc_i2c_slave_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&oc_i2c_slave_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&oc_i2c_slave_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&oc_i2c_slave_deinit_obj) }, + + { MP_ROM_QSTR(MP_QSTR_RECEIVED), MP_ROM_INT(OC_I2C_SLAVE_RECEIVE) }, \ + { MP_ROM_QSTR(MP_QSTR_READ), MP_ROM_INT(OC_I2C_SLAVE_READ) }, \ }; -static MP_DEFINE_CONST_DICT(opencube_i2c_slave_locals_dict_obj, opencube_i2c_slave_locals_dict); +static MP_DEFINE_CONST_DICT(oc_i2c_slave_locals_dict_obj, oc_i2c_slave_locals_dict); // This defines the type(i2c_slave) object. MP_DEFINE_CONST_OBJ_TYPE( - opencube_i2c_slave_type, + oc_i2c_slave_type, MP_QSTR_i2c_slave, MP_TYPE_FLAG_NONE, - make_new, &opencube_i2c_slave_make_new, - locals_dict, &opencube_i2c_slave_locals_dict_obj, - print, &opencube_i2c_slave_print + make_new, &oc_i2c_slave_make_new, + locals_dict, &oc_i2c_slave_locals_dict_obj, + print, &oc_i2c_slave_print ); \ No newline at end of file