Skip to content
Snippets Groups Projects
Commit e976f1d9 authored by Václav Jelínek's avatar Václav Jelínek
Browse files

Add time synchronization to multicube I2C

parent 886eb1ba
No related branches found
No related tags found
No related merge requests found
Pipeline #124642 canceled
from machine import I2C, Pin
import time
from machine import I2C, Pin, Timer
from micropython import const
from lib.hw_defs.pins import UTZ_I2C_SDA_PIN, UTZ_I2C_SCK_PIN, I2C_STRONG_PULL_RPIN_BIT
from lib.robot_consts import I2C_MULTICUBE_FREQ
from lib.robot_consts import I2C_MULTICUBE_FREQ, I2C_NXT_UTZ_BUS
I2C_TIME_TRIGGER_REG = const(256-4)
class I2C_master:
def __init__(self, pcf_buttons, general_add=0x41):
self.general_add = general_add
def __init__(self, pcf_buttons):
self.pcf_buttons = pcf_buttons
self.data_received = None
self.data_sending = None
self.start()
def start(self):
self.trigger_timer = Timer(-1)
self.pcf_buttons.set_pin(I2C_STRONG_PULL_RPIN_BIT, False)
self.i2c = I2C(id=0,scl=Pin(UTZ_I2C_SCK_PIN),
self.i2c = I2C(id=I2C_NXT_UTZ_BUS,scl=Pin(UTZ_I2C_SCK_PIN),
sda=Pin(UTZ_I2C_SDA_PIN), freq=I2C_MULTICUBE_FREQ)
def read(self, slave_add, len=1, mem_add=None):
......@@ -37,6 +39,23 @@ class I2C_master:
return False
return True
def trigger(self, add, time_us, callback=None):
try:
time_start = time.ticks_us()
self.i2c.writeto_mem(add, I2C_TIME_TRIGGER_REG, time_us.to_bytes(4, 'little'))
time_diff = time_us-time.ticks_diff(time.ticks_us(), time_start)
print("Time diff: ", time_diff)
if callback:
if time_diff > 0:
self.trigger_timer.init(freq=1000000/time_diff, mode=Timer.ONE_SHOT, callback=callback)
else:
callback(None)
except Exception as e:
return False
return True
def deinit(self):
self.trigger_timer.deinit()
self.i2c = None
self.pcf_buttons.set_pin(I2C_STRONG_PULL_RPIN_BIT, True)
\ No newline at end of file
self.pcf_buttons.set_pin(I2C_STRONG_PULL_RPIN_BIT, True)
......@@ -9,19 +9,25 @@ ADDRESS = 0x44
W_ADDRESS = 0x0b
W_ADDRESS_MAX = 0x1b
R_ADDRESS = 0x0a
SLAVE_TRIGGER_TIME = 1000000 # us
TIMEOUT = 2
TIMEOUT = 3
def i2c_master_run(robot):
def master_trigger_callback(timer):
robot.led.toggle()
robot.init_i2c_master()
write_mem_add = W_ADDRESS
data_sending = 0
while True:
robot.led.off()
data_received_parsed = 0x5F
data_received = None
debounce = False
debounce = True
timeout = 0
robot.display.fill(0)
......@@ -41,6 +47,7 @@ def i2c_master_run(robot):
return
slave_add = all_add[0]
robot.led.off()
while timeout < TIMEOUT:
robot.display.fill(0)
robot.display.centered_text(f"I2C master", 0, 1)
......@@ -52,14 +59,12 @@ def i2c_master_run(robot):
robot.display.draw_arrow(84, 61, robot.display.DOWN, 1)
robot.display.show()
data_written = robot.i2c_master.write(slave_add, (data_sending + ASCII_a).to_bytes(1, 'big'), mem_add=write_mem_add)
data_written = robot.i2c_master.write(slave_add, (data_sending + ASCII_a).to_bytes(1, 'little'), mem_add=write_mem_add)
data_received = robot.i2c_master.read(slave_add, 1, mem_add=R_ADDRESS)
if data_received and data_received != b'\x00':
print("Received: ", data_received)
data_received_parsed = int.from_bytes(data_received, 'big')
data_received_parsed = int.from_bytes(data_received, 'little')
elif not data_written:
timeout += 1
print(data_written, data_received)
buttons = robot.buttons.pressed_since()
if buttons[Button.LEFT]:
......@@ -74,6 +79,9 @@ def i2c_master_run(robot):
data_sending += 1
data_sending = data_sending % 26
debounce = True
if buttons[Button.OK]:
robot.i2c_master.trigger(slave_add, SLAVE_TRIGGER_TIME, callback=master_trigger_callback)
debounce = True
if buttons[Button.RIGHT]:
#robot.buttons.set_pin(I2C_STRONG_PULL_RPIN_BIT, True)
write_mem_add += 1
......@@ -82,6 +90,6 @@ def i2c_master_run(robot):
debounce = True
if not buttons[Button.UP] and not buttons[Button.DOWN] and not buttons[Button.RIGHT] and not buttons[Button.LEFT]:
if not buttons[Button.UP] and not buttons[Button.DOWN] and not buttons[Button.RIGHT] and not buttons[Button.LEFT] and not buttons[Button.OK]:
debounce = False
utime.sleep(0.1)
\ No newline at end of file
import utime
from machine import mem32,mem8,Pin,
from machine import mem32,mem8,Pin
from lib.robot_consts import Button
from lib.hw_defs.pins import UTZ_I2C_SDA_PIN, UTZ_I2C_SCK_PIN, I2C_STRONG_PULL_RPIN
ASCII_a = 97
ADDRESS = 0x44
ADDRESS_MAX = 0x50
W_ADDRESS = 0x0b
R_ADDRESS = 0x0a
drdy = 0
def i2c_slave_irq(i2c_slave):
global drdy
if (i2c_slave.irq().flags() & i2c_slave.RECEIVED):
drdy = 1
drdy = 0
def i2c_slave_run(robot):
global drdy,irq_c
def i2c_slave_irq(i2c_slave):
global drdy
if (i2c_slave.irq().flags() & i2c_slave.RECEIVED):
drdy = 1
if (i2c_slave.irq().flags() & i2c_slave.TRIGGER):
robot.led.toggle()
robot.init_i2c_slave(ADDRESS)
robot.i2c_slave.irq(i2c_slave_irq, (robot.i2c_slave.READ | robot.i2c_slave.RECEIVED), False)
I2C_SLAVE_IRQ_FLAGS = robot.i2c_slave.TRIGGER | robot.i2c_slave.RECEIVED
robot.i2c_slave.irq(i2c_slave_irq, I2C_SLAVE_IRQ_FLAGS, False)
data_received = None
data_show = 0x5F
......@@ -35,6 +39,8 @@ def i2c_slave_run(robot):
robot.display.text('< exit', 0, 54, 1)
robot.display.show()
while True:
print(robot.i2c_slave)
print(robot.i2c_slave.written_flags())
if drdy:
flags = robot.i2c_slave.written_flags()
for i in range(len(flags)):
......@@ -83,7 +89,8 @@ def i2c_slave_run(robot):
slave_add = ADDRESS
robot.i2c_slave.init(address=slave_add)
robot.i2c_slave.write(R_ADDRESS, data_sending + ASCII_a)
robot.i2c_slave.irq(i2c_slave_irq, (robot.i2c_slave.READ | robot.i2c_slave.RECEIVED), False)
robot.i2c_slave.irq(i2c_slave_irq, I2C_SLAVE_IRQ_FLAGS, False)
robot.led.off()
debounce = True
if not buttons[Button.UP] and not buttons[Button.DOWN] and not buttons[Button.RIGHT]:
......
/*
* The OpenCube I2C slave module allows the RP2040 to act as an I2C slave device.
* The slave can be enabled and disabled, and the I2C address can be set.
* The slave can also be configured to trigger an IRQ when a specific event occurs.
* The IRQ can be triggered on read, write, or time trigger events.
*/
#include <string.h>
#include <py/runtime.h>
#include <py/mperrno.h>
......@@ -5,13 +11,21 @@
#include <hardware/gpio.h>
#include <pico/i2c_slave.h>
#include <shared/runtime/mpirq.h>
#include "pico/time.h"
#include <opencube_hw.h>
#include "PCF8575.h"
#define ALARM_ID_INVALID (-1)
#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)
#define OC_I2C_SLAVE_TRIGGER _u(0x00000004)
#define OC_I2C_SLAVE_ALLOWED_FLAGS (OC_I2C_SLAVE_READ | OC_I2C_SLAVE_RECEIVE | OC_I2C_SLAVE_TRIGGER)
#define OC_I2C_SLAVE_TIME_TRIGGER_REG_START (256-4)
#define OC_I2C_SLAVE_TIME_TRIGGER_REG_END (256-1)
// 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
......@@ -23,17 +37,20 @@ typedef struct {
uint64_t mem_read[4];
uint8_t mem_address;
bool mem_address_written;
} oc_i2c_slave_memory;
} oc_i2c_slave_memory_t;
typedef struct _oc_i2c_slave_obj_t {
mp_obj_base_t base;
bool enabled;
uint32_t time_trigger;
uint8_t address;
uint16_t mp_irq_trigger; // user IRQ trigger mask
uint16_t mp_irq_flags; // user IRQ active IRQ flags
uint16_t mp_irq_flags_unfinished; // future IRQ flags
mp_irq_obj_t *mp_irq_obj; // user IRQ object
oc_i2c_slave_memory context;
struct alarm_pool *pool; // alarm pool for master time trigger
alarm_id_t alarm_id;
oc_i2c_slave_memory_t context;
} oc_i2c_slave_obj_t;
const mp_obj_type_t oc_i2c_slave_type;
......@@ -45,12 +62,17 @@ static oc_i2c_slave_obj_t oc_i2c_slave_obj = {
0,
0,
0,
0,
NULL,
NULL,
ALARM_ID_INVALID,
{{0}},
};
static bool reserved_addr(uint8_t addr);
static void oc_i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event);
static int64_t alarm_callback(alarm_id_t id, void *user_data);
static void oc_cancel_alarm(oc_i2c_slave_obj_t *self);
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);
......@@ -63,36 +85,55 @@ static bool reserved_addr(uint8_t addr) {
// 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 oc_i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) {
oc_i2c_slave_memory_t *context = &oc_i2c_slave_obj.context;
switch (event) {
case I2C_SLAVE_RECEIVE: // master has written some data
if (!oc_i2c_slave_obj.context.mem_address_written) {
if (!context->mem_address_written) {
// writes always start with the memory address
oc_i2c_slave_obj.context.mem_address = i2c_read_byte_raw(i2c);
oc_i2c_slave_obj.context.mem_address_written = true;
context->mem_address = i2c_read_byte_raw(i2c);
context->mem_address_written = true;
} else {
// save into memory
oc_i2c_slave_obj.context.mem[oc_i2c_slave_obj.context.mem_address] = i2c_read_byte_raw(i2c);
// save a flag of the written memory address using bits withou using division uint64_t
oc_i2c_slave_obj.context.mem_written[oc_i2c_slave_obj.context.mem_address >> 6] |= 1 << (oc_i2c_slave_obj.context.mem_address & (0xff>>2));
oc_i2c_slave_obj.context.mem_address++;
context->mem[context->mem_address] = i2c_read_byte_raw(i2c);
if (context->mem_address == OC_I2C_SLAVE_TIME_TRIGGER_REG_END) {
uint32_t* delay_us = (uint32_t*) &context->mem[OC_I2C_SLAVE_TIME_TRIGGER_REG_START];
oc_i2c_slave_obj.time_trigger = *delay_us;
if (oc_i2c_slave_obj.mp_irq_trigger & OC_I2C_SLAVE_TRIGGER) {
oc_cancel_alarm(&oc_i2c_slave_obj);
oc_i2c_slave_obj.alarm_id = alarm_pool_add_alarm_in_us(oc_i2c_slave_obj.pool,
(uint64_t)(*delay_us), alarm_callback, &oc_i2c_slave_obj, true);
if (oc_i2c_slave_obj.alarm_id == -1) {
mp_raise_OSError(MP_ENOMEM);
}
}
*delay_us = 0;
}
// save a flag of the written memory address
context->mem_written[context->mem_address >> 6] |= 1 << (context->mem_address & (0xff>>2));
context->mem_address++;
if (context->mem_address == OC_I2C_SLAVE_TIME_TRIGGER_REG_START) {
context->mem_address = 0;
}
oc_i2c_slave_obj.mp_irq_flags_unfinished |= OC_I2C_SLAVE_RECEIVE;
}
break;
case I2C_SLAVE_REQUEST: // master is requesting data
// load from memory
i2c_write_byte_raw(i2c, oc_i2c_slave_obj.context.mem[oc_i2c_slave_obj.context.mem_address]);
i2c_write_byte_raw(i2c, context->mem[context->mem_address]);
//save a flag of read memory address
oc_i2c_slave_obj.context.mem_read[oc_i2c_slave_obj.context.mem_address >> 6] |= 1 << (oc_i2c_slave_obj.context.mem_address & (0xff>>2));
oc_i2c_slave_obj.context.mem_address++;
context->mem_read[context->mem_address >> 6] |= 1 << (context->mem_address & (0xff>>2));
context->mem_address++;
if (context->mem_address == OC_I2C_SLAVE_TIME_TRIGGER_REG_START) {
context->mem_address = 0;
}
oc_i2c_slave_obj.mp_irq_flags_unfinished |= OC_I2C_SLAVE_READ;
break;
case I2C_SLAVE_FINISH: // master has signalled Stop / Restart
//oc_i2c_slave_obj.context.mem_address = 0;
oc_i2c_slave_obj.context.mem_address_written = false;
// TODO: check osciloscope if I2C master sends stop condition during readfrom_mem
//context->mem_address = 0;
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_unfinished) {
oc_i2c_slave_obj.mp_irq_flags = oc_i2c_slave_obj.mp_irq_flags_unfinished;
......@@ -105,6 +146,22 @@ static void oc_i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) {
}
}
static int64_t alarm_callback(alarm_id_t id, void *user_data) {
oc_i2c_slave_obj_t *oc_i2c_slave = (oc_i2c_slave_obj_t*) user_data;
if (oc_i2c_slave->mp_irq_trigger & OC_I2C_SLAVE_TRIGGER) {
oc_i2c_slave->mp_irq_flags = OC_I2C_SLAVE_TRIGGER;
mp_irq_handler(oc_i2c_slave->mp_irq_obj);
}
return 0;
}
static void oc_cancel_alarm(oc_i2c_slave_obj_t *self) {
if (self->alarm_id != ALARM_ID_INVALID) {
alarm_pool_cancel_alarm(self->pool, self->alarm_id);
self->alarm_id = ALARM_ID_INVALID;
}
}
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[] = {
......@@ -124,12 +181,15 @@ static mp_obj_t oc_i2c_slave_init_helper(oc_i2c_slave_obj_t *self, size_t n_args
}
if (self->enabled) {
i2c_slave_deinit(UTZ_I2C_BUS);
oc_cancel_alarm(self);
}
self->pool = alarm_pool_get_default();
self->alarm_id = ALARM_ID_INVALID;
self->mp_irq_obj = NULL;
self->mp_irq_trigger = 0;
self->mp_irq_flags = 0;
memset(&self->context, 0, sizeof(oc_i2c_slave_memory));
memset(&self->context, 0, sizeof(oc_i2c_slave_memory_t));
self->enabled = true;
self->address = args[ARG_address].u_int;
......@@ -176,8 +236,8 @@ 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, flags=%d",
self->address,self->mp_irq_trigger, self->mp_irq_flags);
mp_printf(print, "Open-Cube I2C slave at address %u, flags=%d, time=%d, mem_add=%u",
self->address, self->mp_irq_trigger, self->time_trigger, self->context.mem_address);
}
// I2C_slave.read()
......@@ -246,6 +306,7 @@ 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 oc_i2c_slave_deinit(mp_obj_t self_in) {
oc_i2c_slave_obj_t *self = MP_OBJ_TO_PTR(self_in);
oc_cancel_alarm(self);
i2c_slave_deinit(UTZ_I2C_BUS);
i2c_deinit(UTZ_I2C_BUS);
self->enabled = false;
......@@ -321,6 +382,7 @@ static const mp_rom_map_elem_t oc_i2c_slave_locals_dict[] = {
{ 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) }, \
{ MP_ROM_QSTR(MP_QSTR_TRIGGER), MP_ROM_INT(OC_I2C_SLAVE_TRIGGER) }, \
};
static MP_DEFINE_CONST_DICT(oc_i2c_slave_locals_dict_obj, oc_i2c_slave_locals_dict);
......
Subproject commit 3823aeb0f14c04084eba6566164d9b7dbd9e7ced
Subproject commit af2770e02c1fcd9e6517223ae81c9a0d089a23e1
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment