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