...
 
Commits (17)
......@@ -5,12 +5,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- **ws2812**: Connect Neopixels to the wristband GPIOs and make your card10
even more colorful!
- `micropython.mem_use()` function.
- High-pass filter and pulse detection in default ECG app.
- `leds.get_rgb()`: Get the current color of an LED.
- `leds.get_rocket()`: Get the current brightness of one of the rockets.
### Changed
- **Pycardium**: Switched from `long-long` to `mpz` integer representation.
This should resolve any issues with large numbers which had popped up so far.
### Fixed
- Backlight and Vibration motor were not reset when switching apps.
- Mismatch in default settings of the *Card10 Nickname* app.
- Fixed the PMIC ADC muxer not being properly reset to neutral after a
measurement.
## [v1.9] - 2019-08-28 23:23 - [IcebergLettuce]
......
......@@ -28,7 +28,7 @@ The current draft uses following service specification:
- Rockets characteristic:
UUID: ``42230210-2342-2342-2342-234223422342``
write no response
read and write no response
- Background LED Bottom Left characteristic:
......@@ -104,6 +104,9 @@ Rockets characteristic
The Rockets characteristic makes it possible to address every three rockets.
Just write there three byte array, one for evey rocket.
On read you get the current value of all three rockets.
Range is between 0 and 31 (``0x1f`) if send higher value it will set to max of 31.
Dataformat:
......@@ -113,8 +116,8 @@ Dataformat:
Rocket0 Rocket1 Rocket2
======= ======= =======
- Enable only Rocket0: ``0xff0000``
- Enable all rockets with 50% brightness: ``0x7f7f7f``
- Enable only Rocket0: ``0x1f0000``
- Enable all rockets with 50% brightness: ``0x0f0f0f``
Background LED <Position> characteristic
----------------------------------------
......
......@@ -38,6 +38,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the
pycardium/simple_menu
pycardium/utime
pycardium/vibra
pycardium/ws2812
.. toctree::
:maxdepth: 1
......
.. py:module:: ws2812
``ws2812`` - Neopixel LEDs
==========================
The ``ws2812`` module controls LEDs of the WS2812 type. Just as the ``leds`` module, it exposes a function :py:func:`ws2812.set_all`, which works a similar fashion.
.. py:function:: set_all(pin, colors)
Set multiple of the LEDs to RGB values.
Filling starts at the LED connected to the specified gpio pin.
:param int pin: ID of the pin to use for sending the data.
:param colors: List of RGB triplets.
**Example**
.. code-block:: python
import color, utime, ws2812, gpio
i = 0
while True:
col1 = color.from_hsv(i % 360, 1.0, 0.1)
col2 = color.from_hsv((i + 20) % 360, 1.0, 0.1)
col3 = color.from_hsv((i + 40) % 360, 1.0, 0.1)
ws2812.set_all(gpio.WRISTBAND_2, [col1, col2, col3])
i += 1
utime.sleep_ms(10)
......@@ -114,7 +114,7 @@ static const uint8_t UUID_attChar_vibra[] = {
/* BLE UUID for card10 char rockets */
static const uint8_t UUID_char_rockets[] = {
ATT_PROP_WRITE_NO_RSP,
ATT_PROP_READ | ATT_PROP_WRITE_NO_RSP,
UINT16_TO_BYTES(CARD10_ROCKETS_VAL_HDL),
CARD10_UUID_SUFFIX, 0x10, CARD10_UUID_PREFIX
};
......@@ -123,6 +123,9 @@ static const uint8_t UUID_attChar_rockets[] = {
CARD10_UUID_SUFFIX, 0x10, CARD10_UUID_PREFIX
};
static uint8_t rocketsValue[] = { 0, 0, 0 };
static uint16_t rocketsLen = sizeof(rocketsValue);
/* BLE UUID for card10 led background bottom left */
static const uint8_t UUID_char_led_bg_bottom_left[] = {
ATT_PROP_READ | ATT_PROP_WRITE_NO_RSP,
......@@ -338,11 +341,13 @@ static const attsAttr_t card10SvcAttrList[] = {
},
{
.pUuid = UUID_attChar_rockets,
.pValue = NULL,
.pValue = rocketsValue,
.pLen = &rocketsLen,
.maxLen = 3 * sizeof(uint8_t),
.settings = ATTS_SET_WRITE_CBACK,
.settings = ATTS_SET_WRITE_CBACK | ATTS_SET_READ_CBACK,
.permissions = ATTS_PERMIT_WRITE | ATTS_PERMIT_WRITE_ENC |
ATTS_PERMIT_WRITE_AUTH,
ATTS_PERMIT_WRITE_AUTH | ATTS_PERMIT_READ |
ATTS_PERMIT_READ_ENC | ATTS_PERMIT_READ_AUTH,
},
// BG LED Bottom left
......@@ -820,6 +825,17 @@ static uint8_t readCard10CB(
APP_TRACE_INFO0("ble-card10: read time\n");
return ATT_SUCCESS;
case CARD10_ROCKETS_VAL_HDL:
pAttr->pValue[0] = epic_leds_get_rocket(0);
pAttr->pValue[1] = epic_leds_get_rocket(1);
pAttr->pValue[2] = epic_leds_get_rocket(2);
APP_TRACE_INFO3(
"ble-card10: get rockets 0:%d, 1:%d, 2:%d\n",
pAttr->pValue[0],
pAttr->pValue[1],
pAttr->pValue[2]
);
return ATT_SUCCESS;
// background leds
case CARD10_LED_BG_BOTTOM_LEFT_VAL_HDL:
return getRGBLed(11, pAttr);
......
......@@ -96,6 +96,7 @@ typedef _Bool bool;
#define API_LEDS_SET_ALL_HSV 0x6b
#define API_LEDS_SET_GAMMA_TABLE 0x6c
#define API_LEDS_CLEAR_ALL 0x6d
#define API_LEDS_GET_ROCKET 0x6e
#define API_LEDS_GET 0x6f
#define API_VIBRA_SET 0x70
......@@ -138,6 +139,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;
......@@ -737,6 +740,24 @@ API(API_LEDS_UPDATE, void epic_leds_update(void));
*/
API(API_LEDS_SET_ROCKET, void epic_leds_set_rocket(int led, uint8_t value));
/**
* Get the brightness of one of the rocket LEDs.
*
* :param int led: Which LED to get.
*
* +-------+--------+----------+
* | ID | Color | Location |
* +=======+========+==========+
* | ``0`` | Blue | Left |
* +-------+--------+----------+
* | ``1`` | Yellow | Top |
* +-------+--------+----------+
* | ``2`` | Green | Right |
* +-------+--------+----------+
* :returns value: Brightness of LED (value between 0 and 31) or ``-EINVAL`` if the LED/rocket does not exists.
*/
API(API_LEDS_GET_ROCKET, int epic_leds_get_rocket(int led));
/**
* Turn on the bright side LED which can serve as a flashlight if worn on the left wrist or as a rad tattoo illuminator if worn on the right wrist.
*
......@@ -1843,4 +1864,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 */
......@@ -31,17 +31,17 @@ struct config_option {
static struct config_option s_options[_EpicOptionCount] = {
/* clang-format off */
#define INIT_Boolean(v) { .boolean = (v) }
#define INIT_Int(v) { .integer = (v) }
#define INIT_Float(v) { .floating_point = (v) }
#define INIT_String(v) { .string = (v) }
#define INIT_(tp, v) INIT_ ## tp (v)
#define INIT(tp, v) INIT_ (tp, v)
#define INIT_Boolean(v) { .boolean = (v) }
#define INIT_Int(v) { .integer = (v) }
#define INIT_Float(v) { .floating_point = (v) }
#define INIT_String(v) { .string = (v) }
#define INIT_(tp, v) INIT_ ## tp (v)
#define INIT(tp, v) INIT_ (tp, v)
#define CARD10_SETTING(identifier, spelling, tp, default_value) \
#define CARD10_SETTING(identifier, spelling, tp, default_value) \
[Option ## identifier] = { .name = (spelling), \
.type = OptionType_ ## tp, \
.value = INIT(tp, (default_value)) },
.type = OptionType_ ## tp, \
.value = INIT(tp, (default_value)) },
#include "modules/config.def"
/* clang-format on */
......@@ -175,7 +175,7 @@ static void configure(const char *key, const char *value, int lineNumber)
static void doline(char *line, char *eol, int lineNumber)
{
//skip leading whitespace
while (*line && isspace(*line))
while (*line && isspace((int)*line))
++line;
char *key = line;
......@@ -199,7 +199,7 @@ static void doline(char *line, char *eol, int lineNumber)
char *e_key = eq - 1;
//skip trailing whitespace in key
while (e_key > key && isspace(*e_key))
while (e_key > key && isspace((int)*e_key))
--e_key;
e_key[1] = '\0';
if (*key == '\0') {
......@@ -209,12 +209,12 @@ static void doline(char *line, char *eol, int lineNumber)
char *value = eq + 1;
//skip leading whitespace
while (*value && isspace(*value))
while (*value && isspace((int)*value))
++value;
char *e_val = eol - 1;
//skip trailing whitespace
while (e_val > value && isspace(*e_val))
while (e_val > value && isspace((int)*e_val))
--e_val;
if (*value == '\0') {
LOG_WARN(
......
......@@ -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,
......
......@@ -112,6 +112,18 @@ void epic_leds_set_rocket(int led, uint8_t value)
pmic_set_led(led, value);
}
int epic_leds_get_rocket(int led)
{
int ret = 0;
while (hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(1)) < 0) {
vTaskDelay(pdMS_TO_TICKS(1));
}
ret = pmic_get_led(led);
hwlock_release(HWLOCK_I2C);
return ret;
}
void epic_set_flashlight(bool power)
{
leds_flashlight(power);
......
......@@ -23,4 +23,5 @@ module_sources = files(
'watchdog.c',
'usb.c',
'config.c',
'ws2812.c'
)
......@@ -3,6 +3,7 @@
#include "FreeRTOS.h"
#include "semphr.h"
#include "gpio.h"
#include <stdint.h>
#include <stdbool.h>
......@@ -52,6 +53,7 @@ void watchdog_clearer_init();
#define BATTERY_CRITICAL 3.40f
enum pmic_amux_signal {
PMIC_AMUX_DISABLED = 0x0,
PMIC_AMUX_CHGIN_U = 0x1,
PMIC_AMUX_CHGIN_I = 0x2,
PMIC_AMUX_BATT_U = 0x3,
......@@ -105,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 */
......@@ -86,7 +86,7 @@ int pmic_read_amux(enum pmic_amux_signal sig, float *result)
ADC_GetData(&adc_data);
/* Turn MUX back to neutral so it does not waste power. */
MAX77650_setMUX_SEL(sig);
MAX77650_setMUX_SEL(PMIC_AMUX_DISABLED);
/* Convert ADC measurement to SI Volts */
float adc_voltage = (float)adc_data / 1023.0f * 1.22f;
......
#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(gpio_cfg_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(gpio_cfg_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_cfg);
epic_ws2812_delay_ticks(EPIC_WS2812_RESET_TCKS);
taskEXIT_CRITICAL();
}
......@@ -3,6 +3,7 @@
#include "MAX77650-Arduino-Library.h"
#include <stdint.h>
#include <stdio.h>
#include <errno.h>
static const gpio_cfg_t pmic_interrupt_pin = {
PORT_0, PIN_12, GPIO_FUNC_IN, GPIO_PAD_PULL_UP
......@@ -123,6 +124,20 @@ void pmic_set_button_callback(pmic_button_callback_fn cb)
pmic_button_callback = cb;
}
int pmic_get_led(uint8_t led)
{
if (led == 0) {
return MAX77650_getBRT_LED0();
}
if (led == 1) {
return MAX77650_getBRT_LED1();
}
if (led == 2) {
return MAX77650_getBRT_LED2();
}
return -EINVAL;
}
void pmic_set_led(uint8_t led, uint8_t val)
{
if (led == 0) {
......
......@@ -21,6 +21,7 @@
void pmic_init(void);
void pmic_set_led(uint8_t led, uint8_t val);
int pmic_get_led(uint8_t led);
void pmic_poll(void);
/* weak, so it can be overwritten by applications */
......
......@@ -14,5 +14,5 @@ QSTR_HEADER="$(dirname "$QSTR_HEADER")/qstrdefs.preprocessed.h"
"$PYTHON" "$SOURCE_DIR"/micropython/tools/mpy-tool.py \
--freeze \
--qstr-header "$QSTR_HEADER" \
-mlongint-impl longlong \
-mlongint-impl mpz \
"$@" >"$OUTPUT"
......@@ -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'
)
#################################
......
../../../lib/micropython/micropython-lib/datetime/datetime.py
\ No newline at end of file
......@@ -72,6 +72,27 @@ def set_rocket(led, value):
sys_leds.set_rocket(led, value)
def get_rocket(led):
"""
Get brightness of one of the rocket LEDs.
:param int led: Choose your rocket!
+-------+--------+----------+
| ID | Color | Location |
+=======+========+==========+
| ``0`` | Blue | Left |
+-------+--------+----------+
| ``1`` | Yellow | Top |
+-------+--------+----------+
| ``2`` | Green | Right |
+-------+--------+----------+
:rtype: int
:returns: Brightness of LED (value between 0 and 31).
"""
return sys_leds.get_rocket(led)
def dim_top(value):
"""
Set global brightness for top RGB LEDs.
......
......@@ -19,6 +19,8 @@ python_modules = files(
'string.py',
'struct.py',
'ucontextlib.py',
'datetime.py',
'time.py',
)
frozen_modules = mpy_cross.process(python_modules)
../../../lib/micropython/micropython-lib/time/time.py
\ No newline at end of file
......@@ -16,6 +16,7 @@ Q(set_all)
Q(set_all_hsv)
Q(set_flashlight)
Q(set_rocket)
Q(get_rocket)
Q(set_powersave)
Q(set_gamma)
Q(dim_top)
......@@ -170,3 +171,7 @@ Q(COMMUNICATION)
Q(CAMP)
Q(MAX30001_ECG)
/* ws2812 */
Q(ws2812)
Q(set_all)
......@@ -200,6 +200,19 @@ static mp_obj_t mp_leds_set_rocket(mp_obj_t led_in, mp_obj_t value_in)
}
static MP_DEFINE_CONST_FUN_OBJ_2(leds_set_rocket_obj, mp_leds_set_rocket);
static mp_obj_t mp_leds_get_rocket(mp_obj_t led_in)
{
int led = mp_obj_get_int(led_in);
uint8_t ret = epic_leds_get_rocket(led);
if (ret == -EINVAL) {
mp_raise_ValueError(
"invalid value: maybe the led does not exists"
);
}
return MP_OBJ_NEW_SMALL_INT(ret);
}
static MP_DEFINE_CONST_FUN_OBJ_1(leds_get_rocket_obj, mp_leds_get_rocket);
static mp_obj_t mp_leds_dim_top(mp_obj_t dim_in)
{
int dim = mp_obj_get_int(dim_in);
......@@ -269,6 +282,7 @@ static const mp_rom_map_elem_t leds_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_set_all), MP_ROM_PTR(&leds_set_all_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_all_hsv), MP_ROM_PTR(&leds_set_all_hsv_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_rocket), MP_ROM_PTR(&leds_set_rocket_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_rocket), MP_ROM_PTR(&leds_get_rocket_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_flashlight),
MP_ROM_PTR(&leds_set_flashlight_obj) },
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&leds_update_obj) },
......
......@@ -45,8 +45,8 @@ MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time);
static mp_obj_t time_time_ms(void)
{
uint64_t milliseconds;
milliseconds = epic_rtc_get_milliseconds() -
(EPOCH_OFFSET + TZONE_OFFSET) * 1000ULL;
milliseconds = epic_rtc_get_milliseconds() - EPOCH_OFFSET * 1000ULL +
TZONE_OFFSET * 1000ULL;
return mp_obj_new_int_from_ull(milliseconds);
}
MP_DEFINE_CONST_FUN_OBJ_0(time_time_ms_obj, time_time_ms);
......
#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);
......@@ -18,7 +18,7 @@
#define MICROPY_ENABLE_GC (1)
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
#define MICROPY_HELPER_REPL (1)
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG)
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ)
#define MICROPY_ENABLE_SCHEDULER (1)
......@@ -44,6 +44,7 @@ int mp_hal_trng_read_int(void);
#define MICROPY_PY_UTIME_MP_HAL (1)
#define MICROPY_PY_IO_FILEIO (1)
#define MICROPY_PY_UERRNO (1)
#define MICROPY_PY_UCTYPES (1)
/* Modules */
#define MODULE_BHI160_ENABLED (1)
......@@ -60,6 +61,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
......