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