-
-
Save szczys/638a8db4802d79a6c96f33f6b54a37c3 to your computer and use it in GitHub Desktop.
Golioth Supercon 2023 badge hack to add i2c peripheral mode to Micropython (and add data chunking features)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include <string.h> | |
#include <hardware/i2c.h> | |
#include "../micropython/lib/pico-sdk/src/rp2_common/pico_i2c_slave/include/pico/i2c_slave.h" | |
//#include "led_ctrl.h" | |
#include "pico/stdlib.h" | |
#include "i2c_fifo.h" | |
#include "supercon_data_transfer.h" | |
uint8_t _semver[3] = { 1, 2, 3 }; | |
#define REG_SEMVER 0x42 | |
#define REG_HASDATA 0xE0 | |
#define REG_GETDATA 0xE1 | |
#define OSTENTUS_ADDR 0x12 | |
#define OSTENTUS_I2C_PORT i2c1 | |
#define OSTENTUS_SDA_PIN 14 | |
#define OSTENTUS_SCL_PIN 15 | |
#define OSTENTUS_BAUDRATE 100000 // 100 kHz | |
uint8_t buffer[I2C_BUFFER_SIZE] = {0}; | |
#define FIFO_SIZE 32 | |
#define FIFO_DATA_OFFSET 2 // cmd addr, data_len | |
#define FIFO_DATA_SIZE I2C_BUFFER_SIZE-FIFO_DATA_OFFSET | |
typedef struct { | |
uint8_t reg; | |
uint8_t len; | |
char data[I2C_BUFFER_SIZE-FIFO_DATA_OFFSET]; | |
} i2c_msg_t; | |
typedef enum { | |
I2C_IDLE, | |
I2C_RECEIVE, | |
I2C_REQUEST, | |
I2C_HAS_REGISTER | |
} fifo_state_t; | |
typedef struct { | |
fifo_state_t state; | |
uint8_t head; | |
uint8_t tail; | |
uint8_t has_data; | |
uint8_t idx; | |
i2c_msg_t fifo[FIFO_SIZE]; | |
} fifo_status_t; | |
fifo_status_t ctx = { | |
.state = I2C_IDLE, | |
.head = 0, | |
.tail = 0, | |
.has_data = 0, | |
.idx = 0 | |
}; | |
static void reset_ctx(void) { | |
ctx.state = I2C_IDLE; | |
ctx.head = 0; | |
ctx.tail = 0; | |
ctx.has_data = 0; | |
ctx.idx = 0; | |
} | |
static i2c_msg_t *get_head(void) { | |
return &ctx.fifo[ctx.head]; | |
} | |
static i2c_msg_t *get_tail(void) { | |
return &ctx.fifo[ctx.tail]; | |
} | |
static void store_byte(uint8_t data) { | |
if ((ctx.head == ctx.tail) && ctx.has_data) { | |
//Buffer is full, do nothing. | |
} | |
if (ctx.state == I2C_IDLE) { | |
get_tail()->reg = data; | |
ctx.state = I2C_RECEIVE; | |
} else { | |
get_tail()->data[ctx.idx] = data; | |
++ctx.idx; | |
if (ctx.idx >= FIFO_DATA_SIZE) { | |
ctx.idx = FIFO_DATA_OFFSET - 1; | |
} | |
} | |
} | |
static uint8_t read_byte(void) { | |
switch(get_tail()->reg) { | |
case REG_SEMVER: | |
if (ctx.idx < sizeof(_semver)) { | |
return _semver[ctx.idx++]; | |
} else { | |
return 0xff; | |
} | |
break; | |
case REG_HASDATA: | |
return (uint8_t)data_available(); | |
break; | |
case REG_GETDATA: | |
if (ctx.idx >= I2C_PACKET_SIZE) { | |
return 0xff; | |
} else { | |
return get_data(); | |
} | |
break; | |
default: | |
return 0xff; | |
} | |
} | |
static void stop_byte(void) | |
{ | |
if (ctx.state == I2C_REQUEST) { | |
ctx.state = I2C_IDLE; | |
ctx.idx = 0; | |
return; | |
} | |
if (ctx.state == I2C_RECEIVE) { | |
if (ctx.idx == 0) { | |
// Got register address for upcoming read | |
ctx.state = I2C_HAS_REGISTER; | |
return; | |
} | |
i2c_msg_t *buf = get_tail(); | |
// Write data len | |
buf->len = ctx.idx; | |
switch(buf->reg) { | |
// Handle LED change directly (don't save to fifo) | |
/* | |
case ADDR_POWER: | |
if (buf->len) { | |
led_push_single(LED_POWER, buf->data[0]); | |
} | |
break; | |
case ADDR_BATTERY: | |
if (buf->len) { | |
led_push_single(LED_BATTERY, buf->data[0]); | |
} | |
break; | |
case ADDR_INTERNET: | |
if (buf->len) { | |
led_push_single(LED_INTERNET, buf->data[0]); | |
} | |
break; | |
case ADDR_GOLIOTH: | |
if (buf->len) { | |
led_push_single(LED_GOLIOTH, buf->data[0]); | |
} | |
break; | |
case ADDR_USER: | |
if (buf->len) { | |
led_push_single(LED_USER, buf->data[0]); | |
} | |
break; | |
case ADDR_BITMASK: | |
if (buf->len) { | |
led_push_mask(buf->data[0]); | |
} | |
break; | |
*/ | |
// Everything else goes into the fifo | |
default: | |
// Indicate there's info in the fifo | |
if (++ctx.has_data > FIFO_SIZE) { | |
ctx.has_data = FIFO_SIZE; | |
}; | |
if (++ctx.tail >= FIFO_SIZE) { | |
ctx.tail = 0; | |
} | |
} | |
//Reset to defaults | |
ctx.state = I2C_IDLE; | |
ctx.idx = 0; | |
} | |
} | |
static void i2c_responder_handler(i2c_inst_t *i2c, i2c_slave_event_t event) | |
{ | |
switch (event) { | |
case I2C_SLAVE_RECEIVE: // master has written some data | |
store_byte(i2c_read_byte_raw(i2c)); | |
break; | |
case I2C_SLAVE_REQUEST: // master is requesting data | |
// load from memory | |
ctx.state = I2C_REQUEST; | |
i2c_write_byte_raw(i2c, read_byte()); | |
break; | |
case I2C_SLAVE_FINISH: // master has signalled Stop / Restart | |
stop_byte(); | |
break; | |
default: | |
break; | |
} | |
} | |
int fifo_pop(uint8_t * r_buf) { | |
if (ctx.has_data == 0) { | |
return -1; | |
} | |
r_buf[0] = get_head()->reg; | |
r_buf[1] = get_head()->len; | |
memcpy(r_buf+2, get_head()->data, get_head()->len); | |
++ctx.head; | |
if (ctx.head >= FIFO_SIZE) { | |
ctx.head = 0; | |
} | |
--ctx.has_data; | |
return 0; | |
} | |
bool fifo_has_data(void) { | |
if (ctx.has_data) { | |
return true; | |
} | |
return false; | |
} | |
void fifo_init(void) | |
{ | |
reset_ctx(); | |
//led_init(); | |
gpio_init(OSTENTUS_SDA_PIN); | |
gpio_set_function(OSTENTUS_SDA_PIN, GPIO_FUNC_I2C); | |
gpio_pull_up(OSTENTUS_SDA_PIN); | |
gpio_init(OSTENTUS_SCL_PIN); | |
gpio_set_function(OSTENTUS_SCL_PIN, GPIO_FUNC_I2C); | |
gpio_pull_up(OSTENTUS_SCL_PIN); | |
i2c_init(OSTENTUS_I2C_PORT, OSTENTUS_BAUDRATE); | |
// configure I2C0 for slave mode | |
i2c_slave_init(OSTENTUS_I2C_PORT, OSTENTUS_ADDR, &i2c_responder_handler); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdint.h> | |
#define I2C_BUFFER_SIZE 64 | |
void fifo_init(void); | |
int fifo_pop(uint8_t * r_buf); | |
bool fifo_has_data(void); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Create an INTERFACE library for our C module. | |
add_library(usermod_ostentus_i2c INTERFACE) | |
# Add our source files to the lib | |
target_sources(usermod_ostentus_i2c INTERFACE | |
${CMAKE_CURRENT_LIST_DIR}/i2c_fifo.c | |
${CMAKE_CURRENT_LIST_DIR}/ostentus_i2c.c | |
${CMAKE_CURRENT_LIST_DIR}/supercon_data_cache.c | |
${CMAKE_CURRENT_LIST_DIR}/supercon_data_transfer.c | |
) | |
# Add the current directory as an include directory. | |
target_include_directories(usermod_ostentus_i2c INTERFACE | |
${CMAKE_CURRENT_LIST_DIR} | |
) | |
# Link our INTERFACE library to the usermod target. | |
target_link_libraries(usermod INTERFACE usermod_ostentus_i2c) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
EXAMPLE_MOD_DIR := $(USERMOD_DIR) | |
# Add all C files to SRC_USERMOD. | |
SRC_USERMOD += $(EXAMPLE_MOD_DIR)/ostentus_i2c.c | |
# We can add our module folder to include paths if needed | |
# This is not actually needed in this example. | |
CFLAGS_USERMOD += -I$(EXAMPLE_MOD_DIR) | |
OSTENTUS_I2C_MOD_DIR := $(USERMOD_DIR) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// include micropython api. | |
#include "py/runtime.h" | |
#include "i2c_fifo.h" | |
#include "supercon_data_cache.h" | |
#include "supercon_data_transfer.h" | |
static mp_obj_t init(void) { | |
fifo_init(); | |
return mp_const_none; | |
} | |
mp_define_const_fun_obj_0(init_obj, init); | |
static mp_obj_t fifo_finalize_samples(void) { | |
finalize_samples(); | |
return mp_const_none; | |
} | |
mp_define_const_fun_obj_0(fifo_finalize_samples_obj, fifo_finalize_samples); | |
static mp_obj_t fifo_put_point(mp_obj_t x_obj, mp_obj_t y_obj) { | |
mp_int_t x = mp_obj_get_int(x_obj); | |
mp_int_t y = mp_obj_get_int(y_obj); | |
put_point(x, y); | |
return mp_const_none; | |
} | |
mp_define_const_fun_obj_2(fifo_put_point_obj, fifo_put_point); | |
static mp_obj_t fifo_init_samples(void) { | |
init_samples("philip j. fry", 50); | |
return mp_const_none; | |
} | |
mp_define_const_fun_obj_0(fifo_init_samples_obj, fifo_init_samples); | |
static mp_obj_t outgoing_data_available(void) { | |
return mp_obj_new_bool(data_available()); | |
} | |
mp_define_const_fun_obj_0(outgoing_data_available_obj, outgoing_data_available); | |
static mp_obj_t has_data(void) { | |
return mp_obj_new_bool(fifo_has_data()); | |
} | |
mp_define_const_fun_obj_0(has_data_obj, has_data); | |
static mp_obj_t pop(void) { | |
// your code here! | |
uint8_t f_buf[64]; | |
fifo_pop(f_buf); | |
// signature: mp_obj_t mp_obj_new_bytes(const byte* data, size_t len); | |
return mp_obj_new_bytes((const unsigned char *)f_buf, f_buf[1]+2); | |
} | |
mp_define_const_fun_obj_0(pop_obj, pop); | |
// define all properties of the module. | |
// table entries are key/value pairs of the attribute name (a string) | |
// and the micropython object reference. | |
// all identifiers and strings are written as mp_qstr_xxx and will be | |
// optimized to word-sized integers by the build system (interned strings). | |
static const mp_rom_map_elem_t example_module_globals_table[] = { | |
{ mp_rom_qstr(mp_qstr___name__), mp_rom_qstr(mp_qstr_ostentus_i2c) }, | |
{ mp_rom_qstr(mp_qstr_init), mp_rom_ptr(&init_obj) }, | |
{ mp_rom_qstr(mp_qstr_fifo_finalize_samples), mp_rom_ptr(&fifo_finalize_samples_obj) }, | |
{ mp_rom_qstr(mp_qstr_fifo_put_point), mp_rom_ptr(&fifo_put_point_obj) }, | |
{ mp_rom_qstr(mp_qstr_fifo_init_samples), mp_rom_ptr(&fifo_init_samples_obj) }, | |
{ mp_rom_qstr(mp_qstr_outgoing_data_available), mp_rom_ptr(&outgoing_data_available_obj) }, | |
{ mp_rom_qstr(mp_qstr_pop), mp_rom_ptr(&pop_obj) }, | |
{ mp_rom_qstr(mp_qstr_has_data), mp_rom_ptr(&has_data_obj) }, | |
}; | |
static mp_define_const_dict(example_module_globals, example_module_globals_table); | |
// define module object. | |
const mp_obj_module_t ostentus_i2c_cmodule = { | |
.base = { &mp_type_module }, | |
.globals = (mp_obj_dict_t *)&example_module_globals, | |
}; | |
// register the module to make it available in python. | |
mp_register_module(mp_qstr_ostentus_i2c, ostentus_i2c_cmodule); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include "../micropython/lib/pico-sdk/src/rp2_common/pico_rand/include/pico/rand.h" | |
#include "supercon_data_cache.h" | |
#include "supercon_data_transfer.h" | |
struct context { | |
uint16_t point_idx; | |
} cache_ctx; | |
Samples _samples; | |
SuperconMeta _meta; | |
void finalize_samples(void) { | |
stage_data(&_meta, &_samples, cache_ctx.point_idx); | |
sample_cache_set_ready(true); | |
} | |
void init_samples(char *name, uint16_t interval_us) { | |
cache_ctx.point_idx = 0; | |
snprintf(_meta.header.name, sizeof(_meta.header.name), "%s", name); | |
_meta.header.interval_us = interval_us; | |
_meta.header.uid = (uint16_t)get_rand_32(); | |
} | |
int put_point(uint8_t x, uint8_t y) { | |
if (sample_cache_is_ready()) { | |
/* Buffer is alreay staged for Alduel */ | |
return -1; | |
} | |
Point p = { | |
.x = x, | |
.y = y, | |
}; | |
_samples.points[cache_ctx.point_idx] = p; | |
if (cache_ctx.point_idx++ >= MAX_SAMPLES) { | |
finalize_samples(); | |
} | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SUPERCON_DATA_CACHE_H | |
#define SUPERCON_DATA_CACHE_H | |
#include <stdint.h> | |
//FIXME: This should be dynamically allocated | |
#define MAX_SAMPLES 32768 // Samples are 2 bytes each | |
typedef struct { | |
uint8_t x; | |
uint8_t y; | |
} Point; | |
typedef union { | |
Point points[MAX_SAMPLES]; | |
uint8_t bytes[MAX_SAMPLES * 2]; | |
} Samples; | |
void finalize_samples(void); | |
void init_samples(char *name, uint16_t interval_us); | |
int put_point(uint8_t x, uint8_t y); | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include "supercon_data_cache.h" | |
#include "supercon_data_transfer.h" | |
typedef struct { | |
bool ready; | |
SuperconMeta *meta; | |
Samples *data_array; | |
uint16_t data_len; | |
uint16_t packet_idx; | |
uint16_t data_idx; | |
bool is_last_packet; | |
} TransferCtx; | |
TransferCtx _cached = { | |
.ready = false, | |
.meta = NULL, | |
.data_array = NULL, | |
.data_len = 0, | |
.packet_idx = 0, | |
.data_idx = 0, | |
.is_last_packet = false, | |
}; | |
void stage_data(SuperconMeta *meta, Samples *data_array, uint16_t data_len) { | |
meta->header.data_len = data_len; | |
_cached.meta = meta; | |
_cached.data_array = data_array; | |
_cached.data_len = data_len; | |
_cached.is_last_packet = false; | |
} | |
bool data_available(void) { | |
return _cached.ready; | |
} | |
static uint8_t get_next_header_byte(void) { | |
return _cached.meta->bytes[_cached.packet_idx]; | |
} | |
bool is_last_packet(void) { | |
if (_cached.packet_idx < I2C_PACKET_SIZE) return false; | |
/* we're indexing bytes, but data_len is ints */ | |
uint16_t data_bytes_per_packet = I2C_PACKET_SIZE - 4; | |
uint16_t bytes_remaining = (_cached.data_len * 2) - _cached.data_idx; | |
return bytes_remaining <= data_bytes_per_packet; | |
} | |
static uint8_t get_next_data_byte(void) { | |
if (_cached.data_idx >= _cached.data_len * 2) { | |
return 0; | |
} else { | |
return _cached.data_array->bytes[_cached.data_idx++]; | |
} | |
} | |
uint8_t get_byte_from_int(uint16_t starting_int, uint8_t send_upper_byte) { | |
if (send_upper_byte) { | |
return (uint8_t)(starting_int >> 8); | |
} else { | |
return (uint8_t)starting_int; | |
} | |
} | |
static uint8_t get_next_in_packet(void) { | |
uint16_t mod_idx = _cached.packet_idx % I2C_PACKET_SIZE; | |
uint8_t return_data = 0; | |
if (mod_idx < 2) { | |
/* UID */ | |
return_data = get_byte_from_int(_cached.meta->header.uid, mod_idx); | |
} else if (mod_idx < 4) { | |
/* Block number */ | |
if (is_last_packet()) { | |
/* This is the final block, use 0xFFFF for the block number */ | |
return_data = 0xFF; | |
} else { | |
return_data = get_byte_from_int(_cached.packet_idx / I2C_PACKET_SIZE, mod_idx % 2); | |
} | |
} else if (_cached.packet_idx < I2C_PACKET_SIZE) { | |
/* Currently sending header */ | |
return_data = get_next_header_byte(); | |
} else { | |
/* Currently sending data */ | |
return_data = get_next_data_byte(); | |
} | |
/* Handle increment and checks for end of data */ | |
_cached.packet_idx++; | |
if (_cached.data_idx >= _cached.data_len * 2) { | |
/* Set last packet when we run out of data */ | |
_cached.is_last_packet = true; | |
} | |
if (_cached.is_last_packet && _cached.packet_idx % I2C_PACKET_SIZE == 0) { | |
/* This was the last byte of the last packet */ | |
_cached.ready = false; | |
} | |
return return_data; | |
} | |
uint8_t get_data(void) { | |
if (!_cached.ready) return -1; | |
if (_cached.meta == NULL) return -2; | |
if (_cached.data_array == NULL) return -3; | |
if (_cached.data_len == 0) return -4; | |
return get_next_in_packet(); | |
} | |
bool sample_cache_is_ready(void) { | |
return _cached.ready; | |
} | |
void sample_cache_set_ready(bool ready) { | |
_cached.ready = ready; | |
if (ready) { | |
_cached.packet_idx = 0; | |
_cached.data_idx = 0; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef SUPERCON_DATA_TRANSFER_H | |
#define SUPERCON_DATA_TRANSFER_H | |
#include <stdint.h> | |
#include <stdbool.h> | |
#include "supercon_data_cache.h" | |
#define I2C_PACKET_SIZE 36 | |
/* Sizing 36 bytes i2c packets: | |
* | |
* Header packet | |
* 2 unique id | |
* 2 blocknum (0x00, 0x00 indicates header packet) | |
* 2 bytes interval between data points (in us) | |
* 2 bytes total number of sample bytes (# of samples * 2 bytes) | |
* 25 bytes for name (including null terminaor) | |
* 3 bytes reserved for future use | |
* | |
* Data packets | |
* 2 unique id | |
* 2 blocknum (max == final block) | |
* 32 bytes of data (16 stucts of 8-bit x and 8-bit y pairs) | |
*/ | |
typedef struct { | |
uint16_t uid; | |
uint16_t block_id; | |
uint16_t interval_us; | |
uint16_t data_len; | |
char name[25]; | |
uint8_t u8_reserved; | |
uint16_t u16_reserved3; | |
/* total 36 bytes */ | |
} SuperconHeader; | |
union SuperconMeta_U { | |
SuperconHeader header; | |
uint8_t bytes[sizeof(SuperconHeader)]; | |
} typedef SuperconMeta; | |
struct supercon_packet { | |
uint16_t uid; | |
uint16_t blocknum; | |
Point data[16]; | |
}; | |
void stage_data(SuperconMeta *meta, Samples *data_array, uint16_t data_len); | |
bool sample_cache_is_ready(void); | |
void sample_cache_set_ready(bool ready); | |
bool data_available(void); | |
uint8_t get_data(void); | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment