From c20bdbac9caa9f131a5f9bf467e298fb51b3e454 Mon Sep 17 00:00:00 2001 From: danb <danb@hasi.it> Date: Sat, 31 Aug 2019 09:20:16 +0000 Subject: [PATCH] feat(ws2812): Add WS2812 module (aka Neopixel) --- epicardium/epicardium.h | 18 ++++++++ epicardium/modules/gpio.c | 2 +- epicardium/modules/meson.build | 1 + epicardium/modules/modules.h | 5 +++ epicardium/modules/ws2812.c | 76 ++++++++++++++++++++++++++++++++++ pycardium/meson.build | 3 +- pycardium/modules/qstrdefs.h | 4 ++ pycardium/modules/ws2812.c | 53 ++++++++++++++++++++++++ pycardium/mpconfigport.h | 1 + 9 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 epicardium/modules/ws2812.c create mode 100644 pycardium/modules/ws2812.c diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 92bb5ade..0b440189 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -138,6 +138,8 @@ typedef _Bool bool; #define API_USB_STORAGE 0x111 #define API_USB_CDCACM 0x112 +#define API_WS2812_WRITE 0x0120 + /* clang-format on */ typedef uint32_t api_int_id_t; @@ -1843,4 +1845,20 @@ API(API_USB_STORAGE, int epic_usb_storage(void)); */ API(API_USB_CDCACM, int epic_usb_cdcacm(void)); +/** + * WS2812 + * ====== + */ + +/** + * Takes a gpio pin specified with the gpio module and transmits + * the led data. The format `GG:RR:BB` is expected. + * + * :param uint8_t pin: The gpio pin to be used for data. + * :param uint8_t * pixels: The buffer, in which the pixel data is stored. + * :param uint32_t n_bytes: The size of the buffer. + */ +API(API_WS2812_WRITE, void epic_ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t n_bytes)); + #endif /* _EPICARDIUM_H */ + diff --git a/epicardium/modules/gpio.c b/epicardium/modules/gpio.c index 6b697739..a923812f 100644 --- a/epicardium/modules/gpio.c +++ b/epicardium/modules/gpio.c @@ -11,7 +11,7 @@ * Despite what the schematic (currently, 2019-08-18) says these are the correct * pins for wristband GPIO 1-4 (not 0-3 as the schematic states) */ -static gpio_cfg_t gpio_configs[] = { +gpio_cfg_t gpio_configs[] = { [EPIC_GPIO_WRISTBAND_1] = { PORT_0, PIN_21, GPIO_FUNC_OUT, diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 628249a3..e943af4f 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -23,4 +23,5 @@ module_sources = files( 'watchdog.c', 'usb.c', 'config.c', + 'ws2812.c' ) diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 27e6e28a..330f4f67 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -3,6 +3,7 @@ #include "FreeRTOS.h" #include "semphr.h" +#include "gpio.h" #include <stdint.h> #include <stdbool.h> @@ -106,4 +107,8 @@ void vBhi160Task(void *pvParameters); void vMAX30001Task(void *pvParameters); void max30001_mutex_init(void); +/* ---------- GPIO --------------------------------------------------------- */ +#define MAX30001_MUTEX_WAIT_MS 50 +extern gpio_cfg_t gpio_configs[]; + #endif /* MODULES_H */ diff --git a/epicardium/modules/ws2812.c b/epicardium/modules/ws2812.c new file mode 100644 index 00000000..4d16e5a2 --- /dev/null +++ b/epicardium/modules/ws2812.c @@ -0,0 +1,76 @@ +#include "leds.h" +#include "pmic.h" +#include "FreeRTOS.h" +#include "task.h" +#include "epicardium.h" +#include "max32665.h" +#include "gpio.h" +#include "modules.h" + +#include <stdbool.h> + +#define OVERHEAD 33 + +#define EPIC_WS2812_ZERO_HIGH_TICKS 34 - OVERHEAD +#define EPIC_WS2812_ZERO_LOW_TICKS 86 - OVERHEAD +#define EPIC_WS2812_ONE_HIGH_TICKS 86 - OVERHEAD +#define EPIC_WS2812_ONE_LOW_TICKS 34 - OVERHEAD +#define EPIC_WS2812_RESET_TCKS 4800 - OVERHEAD + +static volatile uint32_t counter = 0; + +static inline __attribute__((always_inline)) void +epic_ws2812_delay_ticks(uint32_t ticks) +{ + counter = ticks; + while (--counter) { + }; +} + +static inline __attribute__((always_inline)) void +epic_ws2812_transmit_bit(uint32_t pin, uint8_t bit) +{ + if (bit != 0) { + GPIO_OutSet(pin); + epic_ws2812_delay_ticks(EPIC_WS2812_ONE_HIGH_TICKS); + GPIO_OutClr(pin); + epic_ws2812_delay_ticks(EPIC_WS2812_ONE_LOW_TICKS); + } else { + GPIO_OutSet(pin); + epic_ws2812_delay_ticks(EPIC_WS2812_ZERO_HIGH_TICKS); + GPIO_OutClr(pin); + epic_ws2812_delay_ticks(EPIC_WS2812_ZERO_LOW_TICKS); + } +} + +static inline __attribute__((always_inline)) void +epic_ws2812_transmit_byte(uint32_t pin, uint8_t byte) +{ + epic_ws2812_transmit_bit(pin, byte & 0b10000000); + epic_ws2812_transmit_bit(pin, byte & 0b01000000); + epic_ws2812_transmit_bit(pin, byte & 0b00100000); + epic_ws2812_transmit_bit(pin, byte & 0b00010000); + epic_ws2812_transmit_bit(pin, byte & 0b00001000); + epic_ws2812_transmit_bit(pin, byte & 0b00000100); + epic_ws2812_transmit_bit(pin, byte & 0b00000010); + epic_ws2812_transmit_bit(pin, byte & 0b00000001); +} + +void epic_ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t n_bytes) +{ + uint8_t *pixels_end = pixels + n_bytes; + gpio_cfg_t *pin_cfg = &gpio_configs[pin]; + + taskENTER_CRITICAL(); + + epic_gpio_set_pin_mode(pin, EPIC_GPIO_MODE_OUT); + + do { + epic_ws2812_transmit_byte(pin_cfg, *pixels); + } while (++pixels != pixels_end); + + GPIO_OutClr(pin); + epic_ws2812_delay_ticks(EPIC_WS2812_RESET_TCKS); + + taskEXIT_CRITICAL(); +} diff --git a/pycardium/meson.build b/pycardium/meson.build index 32973bae..1d45e970 100644 --- a/pycardium/meson.build +++ b/pycardium/meson.build @@ -16,7 +16,8 @@ modsrc = files( 'modules/sys_display.c', 'modules/utime.c', 'modules/vibra.c', - 'modules/bme680.c' + 'modules/bme680.c', + 'modules/ws2812.c' ) ################################# diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index 68c40810..50178cc8 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -170,3 +170,7 @@ Q(COMMUNICATION) Q(CAMP) Q(MAX30001_ECG) + +/* ws2812 */ +Q(ws2812) +Q(set_all) diff --git a/pycardium/modules/ws2812.c b/pycardium/modules/ws2812.c new file mode 100644 index 00000000..4df1d84a --- /dev/null +++ b/pycardium/modules/ws2812.c @@ -0,0 +1,53 @@ +#include "epicardium.h" + +#include "py/obj.h" + +#include <stdlib.h> +#include <stdio.h> + +/* Define the pixel set_all function in this module */ +static mp_obj_t mp_ws2812_set_all(mp_obj_t pin, mp_obj_t color_in) +{ + mp_int_t pin_int = mp_obj_get_int(pin); + mp_int_t len = mp_obj_get_int(mp_obj_len(color_in)); + mp_int_t pixels_len = len * 3; + uint8_t *pixels_arr = alloca(pixels_len * sizeof(uint8_t)); + + for (int i = 0; i < len; i++) { + mp_obj_t color = mp_obj_subscr( + color_in, mp_obj_new_int(i), MP_OBJ_SENTINEL + ); + + pixels_arr[i * 3] = mp_obj_get_int(mp_obj_subscr( + color, mp_obj_new_int(1), MP_OBJ_SENTINEL) + ); + pixels_arr[i * 3 + 1] = mp_obj_get_int(mp_obj_subscr( + color, mp_obj_new_int(0), MP_OBJ_SENTINEL) + ); + pixels_arr[i * 3 + 2] = mp_obj_get_int(mp_obj_subscr( + color, mp_obj_new_int(2), MP_OBJ_SENTINEL) + ); + } + + /* call epicardium to be fast enough */ + epic_ws2812_write(pin_int, pixels_arr, pixels_len); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(ws2812_set_all_obj, mp_ws2812_set_all); + +/* The globals table for this module */ +static const mp_rom_map_elem_t ws2812_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ws2812) }, + { MP_ROM_QSTR(MP_QSTR_set_all), MP_ROM_PTR(&ws2812_set_all_obj) }, +}; +static MP_DEFINE_CONST_DICT(ws2812_module_globals, ws2812_module_globals_table); + +const mp_obj_module_t ws2812_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&ws2812_module_globals, +}; + +/* This is a special macro that will make MicroPython aware of this module */ +/* clang-format off */ +MP_REGISTER_MODULE(MP_QSTR_ws2812, ws2812_module, MODULE_WS2812_ENABLED); diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h index 8ee59352..767a69fb 100644 --- a/pycardium/mpconfigport.h +++ b/pycardium/mpconfigport.h @@ -60,6 +60,7 @@ int mp_hal_trng_read_int(void); #define MODULE_POWER_ENABLED (1) #define MODULE_UTIME_ENABLED (1) #define MODULE_VIBRA_ENABLED (1) +#define MODULE_WS2812_ENABLED (1) /* * This port is intended to be 32-bit, but unfortunately, int32_t for -- GitLab