Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • card10/firmware
  • annejan/firmware
  • astro/firmware
  • fpletz/firmware
  • gerd/firmware
  • fleur/firmware
  • swym/firmware
  • l/firmware
  • uberardy/firmware
  • wink/firmware
  • madonius/firmware
  • mot/firmware
  • filid/firmware
  • q3k/firmware
  • hauke/firmware
  • Woazboat/firmware
  • pink/firmware
  • mossmann/firmware
  • omniskop/firmware
  • zenox/firmware
  • trilader/firmware
  • Danukeru/firmware
  • shoragan/firmware
  • zlatko/firmware
  • sistason/firmware
  • datenwolf/firmware
  • bene/firmware
  • amedee/firmware
  • martinling/firmware
  • griffon/firmware
  • chris007/firmware
  • adisbladis/firmware
  • dbrgn/firmware
  • jelly/firmware
  • rnestler/firmware
  • mh/firmware
  • ln/firmware
  • penguineer/firmware
  • monkeydom/firmware
  • jens/firmware
  • jnaulty/firmware
  • jeffmakes/firmware
  • marekventur/firmware
  • pete/firmware
  • h2obrain/firmware
  • DooMMasteR/firmware
  • jackie/firmware
  • prof_r/firmware
  • Draradech/firmware
  • Kartoffel/firmware
  • hinerk/firmware
  • abbradar/firmware
  • JustTB/firmware
  • LuKaRo/firmware
  • iggy/firmware
  • ente/firmware
  • flgr/firmware
  • Lorphos/firmware
  • matejo/firmware
  • ceddral7/firmware
  • danb/firmware
  • joshi/firmware
  • melle/firmware
  • fitch/firmware
  • deurknop/firmware
  • sargon/firmware
  • markus/firmware
  • kloenk/firmware
  • lucaswerkmeister/firmware
  • derf/firmware
  • meh/firmware
  • dx/card10-firmware
  • torben/firmware
  • yuvadm/firmware
  • AndyBS/firmware
  • klausdieter1/firmware
  • katzenparadoxon/firmware
  • xiretza/firmware
  • ole/firmware
  • techy/firmware
  • thor77/firmware
  • TilCreator/firmware
  • fuchsi/firmware
  • dos/firmware
  • yrlf/firmware
  • PetePriority/firmware
  • SuperVirus/firmware
  • sur5r/firmware
  • tazz/firmware
  • Alienmaster/firmware
  • flo_h/firmware
  • baldo/firmware
  • mmu_man/firmware
  • Foaly/firmware
  • sodoku/firmware
  • Guinness/firmware
  • ssp/firmware
  • led02/firmware
  • Stormwind/firmware
  • arist/firmware
  • coon/firmware
  • mdik/firmware
  • pippin/firmware
  • royrobotiks/firmware
  • zigot83/firmware
  • mo_k/firmware
106 results
Select Git revision
Loading items
Show changes
Showing
with 1078 additions and 1138 deletions
#ifndef EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
#define EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
/* ---------- FAT fs ------------------------------------------------------ */
#include <stdbool.h>
#include "epicardium.h"
/**
* module initialization - to be called once at startup before any FreeRTOS tasks
* have been started
*
* calls fatfs_attach
*/
void fatfs_init(void);
/**
* initialize and mount the FLASH storage
*/
int fatfs_attach(void);
/** close all opened FDs, sync and deinitialize FLASH layer */
void fatfs_detach(void);
#endif//EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
#include "epicardium.h"
#include "gpio.h"
#include "max32665.h"
#include "mxc_errors.h"
/*
* 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_WRISTBAND_1] = { PORT_0, PIN_21, GPIO_FUNC_OUT, GPIO_PAD_NONE },
[GPIO_WRISTBAND_2] = { PORT_0, PIN_22, GPIO_FUNC_OUT, GPIO_PAD_NONE },
[GPIO_WRISTBAND_3] = { PORT_0, PIN_29, GPIO_FUNC_OUT, GPIO_PAD_NONE },
[GPIO_WRISTBAND_4] = { PORT_0, PIN_20, GPIO_FUNC_OUT, GPIO_PAD_NONE },
};
int epic_gpio_set_pin_mode(uint8_t pin, uint8_t mode)
{
if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
bool is_input = (mode & GPIO_MODE_IN) == GPIO_MODE_IN;
bool is_output = (mode & GPIO_MODE_OUT) == GPIO_MODE_OUT;
// Pins can't be input and output at the same time.
if (is_input && is_output)
return -EINVAL;
uint32_t func_value = 0;
if (is_input)
func_value |= GPIO_FUNC_IN;
if (is_output)
func_value |= GPIO_FUNC_OUT;
uint32_t pad_value = 0;
if (mode & GPIO_PULL_UP)
pad_value |= GPIO_PAD_PULL_UP;
if (mode & GPIO_PULL_DOWN)
pad_value |= GPIO_PAD_PULL_DOWN;
cfg->func = func_value;
cfg->pad = pad_value;
if (GPIO_Config(cfg) != E_NO_ERROR)
return -EINVAL;
return 0;
}
int epic_gpio_get_pin_mode(uint8_t pin)
{
if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
int res = 0;
if ((cfg->func & GPIO_FUNC_IN) == GPIO_FUNC_IN)
res |= GPIO_MODE_IN;
if ((cfg->func & GPIO_FUNC_OUT) == GPIO_FUNC_OUT)
res |= GPIO_MODE_OUT;
if ((cfg->pad & GPIO_PAD_PULL_UP) == GPIO_PAD_PULL_UP)
res |= GPIO_PULL_UP;
if ((cfg->pad & GPIO_PAD_PULL_DOWN) == GPIO_PAD_PULL_DOWN)
res |= GPIO_PULL_DOWN;
return res;
}
int epic_gpio_write_pin(uint8_t pin, bool on)
{
if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
if ((cfg->func & GPIO_FUNC_IN) == GPIO_FUNC_IN)
return -EINVAL;
if (on)
GPIO_OutSet(cfg);
else
GPIO_OutClr(cfg);
return 0;
}
uint32_t epic_gpio_read_pin(uint8_t pin)
{
if (pin < GPIO_WRISTBAND_1 || pin > GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
if ((cfg->func & GPIO_FUNC_OUT) == GPIO_FUNC_OUT) {
return GPIO_OutGet(cfg);
} else if ((cfg->func & GPIO_FUNC_IN) == GPIO_FUNC_IN) {
return GPIO_InGet(cfg);
} else {
return -EINVAL;
}
}
#include "epicardium.h" #include "epicardium.h"
#include "api/dispatcher.h" #include "api/dispatcher.h"
#include "api/interrupt-sender.h" #include "usb/epc_usb.h"
#include "cdcacm.h" #include "fs/filesystem.h"
#include "modules/filesystem.h" #include "os/core.h"
#include "modules/log.h"
#include "modules/modules.h" #include "modules/modules.h"
#include "modules/stream.h" #include "modules/stream.h"
#include "drivers/drivers.h"
#include "user_core/interrupts.h"
#include "user_core/user_core.h"
#include "card10.h" #include "card10.h"
#include "display.h"
#include "leds.h" #include "leds.h"
#include "pb.h" #include "pb.h"
#include "pmic.h" #include "pmic.h"
#include "portexpander.h" #include "portexpander.h"
#include "max86150.h"
#include "ble/ble_api.h"
#include "gpio.h" #include "gpio.h"
#include "i2c.h" #include "i2c.h"
#include "rtc.h" #include "rtc.h"
#include "spi.h" #include "spi.h"
#include "trng.h" #include "wdt.h"
/* /*
* Early init is called at the very beginning and is meant for modules which * Early init is called at the very beginning and is meant for modules which
...@@ -28,6 +31,11 @@ ...@@ -28,6 +31,11 @@
*/ */
int hardware_early_init(void) int hardware_early_init(void)
{ {
/*
* Watchdog timer
*/
watchdog_init();
/* /*
* I2C bus for onboard peripherals (ie. PMIC, BMA400, BHI160, BME680, * I2C bus for onboard peripherals (ie. PMIC, BMA400, BHI160, BME680,
* ...) * ...)
...@@ -48,6 +56,13 @@ int hardware_early_init(void) ...@@ -48,6 +56,13 @@ int hardware_early_init(void)
*/ */
GPIO_Init(); GPIO_Init();
/* Set the power hold pin, so the PMIC does not turn off again */
const gpio_cfg_t pwr_hold_pin = {
PORT_0, PIN_30, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
GPIO_Config(&pwr_hold_pin);
GPIO_OutSet(&pwr_hold_pin);
/* /*
* PMIC (MAX77650) * PMIC (MAX77650)
*/ */
...@@ -61,40 +76,34 @@ int hardware_early_init(void) ...@@ -61,40 +76,34 @@ int hardware_early_init(void)
*/ */
portexpander_init(); portexpander_init();
/*
* RNG
*/
TRNG_Init(NULL);
/* /*
* Buttons * Buttons
*/ */
PB_Init(); PB_Init();
/* Enable 32 kHz output */ /* Enable 32 kHz output */
while (RTC_SquareWave( while (RTC_SquareWave(MXC_RTC, SQUARE_WAVE_ENABLED, F_32KHZ, NULL) ==
MXC_RTC, E_BUSY)
SQUARE_WAVE_ENABLED,
F_32KHZ,
NOISE_IMMUNE_MODE,
NULL) == E_BUSY
)
; ;
/*
* RNG
*/
rng_init();
/* If we don't have a valid time yet, set it to 2019-01-01 */ /* If we don't have a valid time yet, set it to 2019-01-01 */
if (RTC_GetSecond() < 1546300800UL) { if (RTC_GetSecond() < 1546300800L) {
epic_rtc_set_milliseconds(1546300800UL * 1000); epic_rtc_set_milliseconds(1546300800UL * 1000);
} }
/* /*
* SPI for ECG * SPI for ECG
*/ */
const sys_cfg_spi_t spi17y_master_cfg = { const sys_cfg_spi_t spi17y_master_cfg = { .map = MAP_A,
.map = MAP_A,
.ss0 = Enable, .ss0 = Enable,
.ss1 = Disable, .ss1 = Disable,
.ss2 = Disable, .ss2 = Disable,
}; .num_io = 2 };
if (SPI_Init(SPI0, 0, SPI_SPEED, spi17y_master_cfg) != 0) { if (SPI_Init(SPI0, 0, SPI_SPEED, spi17y_master_cfg) != 0) {
LOG_ERR("init", "Error configuring SPI"); LOG_ERR("init", "Error configuring SPI");
...@@ -103,10 +112,9 @@ int hardware_early_init(void) ...@@ -103,10 +112,9 @@ int hardware_early_init(void)
} }
/* /*
* The bootloader has already initialized the display, so we only need * Display
* to do the bare minimum here (mostly the gfx datastructures).
*/ */
display_init_slim(); disp_init();
/* /*
* RGB LEDs * RGB LEDs
...@@ -146,8 +154,8 @@ int hardware_early_init(void) ...@@ -146,8 +154,8 @@ int hardware_early_init(void)
/* /*
* USB-Serial * USB-Serial
*/ */
if (cdcacm_init() < 0) { if (epic_usb_cdcacm() < 0) {
LOG_ERR("init", "USB-Serial unavailable"); LOG_ERR("startup", "USB-Serial unavailable");
} }
/* /*
...@@ -158,7 +166,7 @@ int hardware_early_init(void) ...@@ -158,7 +166,7 @@ int hardware_early_init(void)
/* /*
* API Dispatcher & API Interrupts * API Dispatcher & API Interrupts
*/ */
api_interrupt_init(); interrupt_init();
api_dispatcher_init(); api_dispatcher_init();
/* /*
...@@ -171,6 +179,31 @@ int hardware_early_init(void) ...@@ -171,6 +179,31 @@ int hardware_early_init(void)
*/ */
hwlock_init(); hwlock_init();
/*
* API Dispatcher Mutex
*/
dispatcher_mutex_init();
/*
* MAX30001 mutex init
*/
max30001_mutex_init();
/*
* max86150 mutex init
*/
max86150_mutex_init();
max86150_shut_down();
/*
* BME680 Sensor
*/
epic_bme680_init();
/* Allow user space to trigger interrupts.
* Used for BLE, not sure if needed. */
SCB->CCR |= SCB_CCR_USERSETMPEND_Msk;
return 0; return 0;
} }
...@@ -184,6 +217,9 @@ int hardware_early_init(void) ...@@ -184,6 +217,9 @@ int hardware_early_init(void)
*/ */
int hardware_init(void) int hardware_init(void)
{ {
/* Watchdog clearer software timer */
watchdog_clearer_init();
/* Light Sensor */ /* Light Sensor */
LOG_DEBUG("init", "Starting light sensor ..."); LOG_DEBUG("init", "Starting light sensor ...");
epic_light_sensor_run(); epic_light_sensor_run();
...@@ -203,9 +239,14 @@ int hardware_reset(void) ...@@ -203,9 +239,14 @@ int hardware_reset(void)
/* /*
* API Dispatcher & API Interrupts * API Dispatcher & API Interrupts
*/ */
api_interrupt_init(); interrupt_init();
api_dispatcher_init(); api_dispatcher_init();
/*
* close all FDs currently owned by core1
*/
fatfs_close_all(EPICARDIUM_COREMASK_1);
/* Personal State */ /* Personal State */
const int personal_state_is_persistent = const int personal_state_is_persistent =
epic_personal_state_is_persistent(); epic_personal_state_is_persistent();
...@@ -229,7 +270,32 @@ int hardware_reset(void) ...@@ -229,7 +270,32 @@ int hardware_reset(void)
/* /*
* Display * Display
*/ */
display_init_slim(); disp_init();
epic_disp_backlight(20);
/*
* Vibration Motor
*/
epic_vibra_set(false);
/*
* BHI160
*/
epic_bhi160_disable_all_sensors();
epic_max30001_disable_sensor();
epic_max86150_disable_sensor();
/*
* BLE
*/
/* Reset advertisement data */
ble_adv_setup();
/* Start advertising again if needed */
epic_ble_set_mode(false, false);
return 0; return 0;
} }
#include "modules/log.h" #include "os/core.h"
#include "modules/modules.h" #include "modules/modules.h"
#include "os/mutex.h"
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "task.h" #include "task.h"
#include "semphr.h"
#include <errno.h> #include <errno.h>
static StaticSemaphore_t hwlock_mutex_data[_HWLOCK_MAX]; static struct mutex hwlock_mutex[_HWLOCK_MAX] = { { 0 } };
static SemaphoreHandle_t hwlock_mutex[_HWLOCK_MAX];
/* Which task is holding the lock */
static TaskHandle_t hwlock_tasks[_HWLOCK_MAX];
void hwlock_init(void) void hwlock_init(void)
{ {
for (int i = 0; i < _HWLOCK_MAX; i++) { for (int i = 0; i < _HWLOCK_MAX; i++) {
hwlock_mutex[i] = /*
xSemaphoreCreateMutexStatic(&hwlock_mutex_data[i]); * TODO: mutex_create() names these all these mutexes
* "&hwlock_mutex[i]" which is not helpful at all. We
* should somehow rename them to the actual hwlock names.
*/
mutex_create(&hwlock_mutex[i]);
} }
} }
int hwlock_acquire(enum hwlock_periph p, TickType_t wait) void hwlock_acquire(enum hwlock_periph p)
{ {
if (p >= _HWLOCK_MAX) { assert(p < _HWLOCK_MAX);
return -EINVAL; mutex_lock(&hwlock_mutex[p]);
} }
if (xSemaphoreTake(hwlock_mutex[p], wait) != pdTRUE) { int hwlock_acquire_nonblock(enum hwlock_periph p)
LOG_WARN("hwlock", "Lock %u is busy.", p); {
assert(p < _HWLOCK_MAX);
if (mutex_trylock(&hwlock_mutex[p])) {
return 0;
} else {
return -EBUSY; return -EBUSY;
} }
hwlock_tasks[p] = xTaskGetCurrentTaskHandle();
return 0;
} }
int hwlock_release(enum hwlock_periph p) void hwlock_release(enum hwlock_periph p)
{ {
int ret = 0; assert(p < _HWLOCK_MAX);
mutex_unlock(&hwlock_mutex[p]);
if (p >= _HWLOCK_MAX) {
return -EINVAL;
}
if (hwlock_tasks[p] != xTaskGetCurrentTaskHandle()) {
LOG_ERR("hwlock",
"Lock %u is released by task \"%s\" while it was acquired by \"%s\"",
p,
pcTaskGetName(NULL),
pcTaskGetName(hwlock_tasks[p]));
ret = -EACCES;
}
if (xSemaphoreGive(hwlock_mutex[p]) != pdTRUE) {
LOG_ERR("hwlock", "Lock %u not released correctly.", p);
ret = -EINVAL;
}
return ret;
} }
#include "leds.h"
#include "pmic.h"
#include "FreeRTOS.h"
#include "task.h"
#include "epicardium.h"
#include "modules.h"
#include <stdbool.h>
//TODO: create smth like vTaskDelay(pdMS_TO_TICKS(//put ms here)) for us, remove blocking delay from /lib/leds.c to avoid process blocking
#define NUM_LEDS 15 /* Take from lib/card10/leds.c */
static void do_update()
{
while (hwlock_acquire(HWLOCK_LED, pdMS_TO_TICKS(1)) < 0) {
vTaskDelay(pdMS_TO_TICKS(1));
}
leds_update_power();
leds_update();
hwlock_release(HWLOCK_LED);
}
void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
return;
leds_prep(led, r, g, b);
do_update();
}
void epic_leds_set_hsv(int led, float h, float s, float v)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
return;
leds_prep_hsv(led, h, s, v);
do_update();
}
void epic_leds_prep(int led, uint8_t r, uint8_t g, uint8_t b)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
return;
leds_prep(led, r, g, b);
}
void epic_leds_prep_hsv(int led, float h, float s, float v)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
return;
leds_prep_hsv(led, h, s, v);
}
void epic_leds_set_all(uint8_t *pattern_ptr, uint8_t len)
{
uint8_t(*pattern)[3] = (uint8_t(*)[3])pattern_ptr;
for (int i = 0; i < len; i++) {
if (i == PERSONAL_STATE_LED && personal_state_enabled())
continue;
leds_prep(i, pattern[i][0], pattern[i][1], pattern[i][2]);
}
do_update();
}
void epic_leds_set_all_hsv(float *pattern_ptr, uint8_t len)
{
float(*pattern)[3] = (float(*)[3])pattern_ptr;
for (int i = 0; i < len; i++) {
if (i == PERSONAL_STATE_LED && personal_state_enabled())
continue;
leds_prep_hsv(i, pattern[i][0], pattern[i][1], pattern[i][2]);
}
do_update();
}
void epic_leds_dim_top(uint8_t value)
{
leds_set_dim_top(value);
if (personal_state_enabled() == 0)
leds_update();
}
void epic_leds_dim_bottom(uint8_t value)
{
leds_set_dim_bottom(value);
if (personal_state_enabled() == 0)
leds_update();
}
void epic_leds_set_rocket(int led, uint8_t value)
{
value = value > 31 ? 31 : value;
pmic_set_led(led, value);
}
void epic_set_flashlight(bool power)
{
leds_flashlight(power);
}
void epic_leds_update(void)
{
do_update();
}
void epic_leds_set_powersave(bool eco)
{
leds_powersave(eco);
}
void epic_leds_set_gamma_table(uint8_t rgb_channel, uint8_t gamma_table[256])
{
leds_set_gamma_table(rgb_channel, gamma_table);
}
void epic_leds_clear_all(uint8_t r, uint8_t g, uint8_t b)
{
for (int i = 0; i < NUM_LEDS; i++) {
if (i == PERSONAL_STATE_LED && personal_state_enabled())
continue;
leds_prep(i, r, g, b);
}
do_update();
}
#pragma once
#define SEV_DEBUG "D"
#define SEV_INFO "I"
#define SEV_WARN "W"
#define SEV_ERR "E"
#define SEV_CRIT "C"
/* Whether to enable logging at all */
#ifndef LOG_ENABLE
#define LOG_ENABLE 1
#endif
/* Whether to enable even more verbose logging */
#ifndef LOG_ENABLE_DEBUG
#define LOG_ENABLE_DEBUG 0
#endif
/* Whether to enable colorful log */
#ifndef LOG_ENABLE_COLOR
#define LOG_ENABLE_COLOR 1
#endif
#if LOG_ENABLE
int log_msg(const char *subsys, const char *format, ...)
__attribute__((format(printf, 2, 3)));
#if LOG_ENABLE_DEBUG
#define LOG_DEBUG(subsys, format, ...) \
log_msg(subsys, SEV_DEBUG format, ##__VA_ARGS__)
#else /* LOG_ENABLE_DEBUG */
#define LOG_DEBUG(subsys, format, ...)
#endif /* LOG_ENABLE_DEBUG */
#define LOG_INFO(subsys, format, ...) \
log_msg(subsys, SEV_INFO format, ##__VA_ARGS__)
#define LOG_WARN(subsys, format, ...) \
log_msg(subsys, SEV_WARN format, ##__VA_ARGS__)
#define LOG_ERR(subsys, format, ...) \
log_msg(subsys, SEV_ERR format, ##__VA_ARGS__)
#define LOG_CRIT(subsys, format, ...) \
log_msg(subsys, SEV_CRIT format, ##__VA_ARGS__)
#else /* LOG_ENABLE_DEBUG */
inline __attribute__((format(printf, 2, 3))) int
log_msg(const char *subsys, const char *format, ...)
{
return 0;
}
#define LOG_DEBUG(subsys, format, ...)
#define LOG_INFO(subsys, format, ...)
#define LOG_WARN(subsys, format, ...)
#define LOG_ERR(subsys, format, ...)
#define LOG_CRIT(subsys, format, ...)
#endif /* LOG_ENABLE_DEBUG */
module_sources = files( module_sources = files(
'buttons.c',
'dispatcher.c',
'display.c',
'fileops.c',
'gpio.c',
'hardware.c', 'hardware.c',
'hw-lock.c', 'hw-lock.c',
'leds.c',
'lifecycle.c',
'light_sensor.c',
'log.c',
'personal_state.c', 'personal_state.c',
'pmic.c',
'rtc.c',
'serial.c',
'stream.c', 'stream.c',
'trng.c',
'vibra.c',
) )
...@@ -2,65 +2,26 @@ ...@@ -2,65 +2,26 @@
#define MODULES_H #define MODULES_H
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "semphr.h" #include "gpio.h"
#include "os/mutex.h"
#include "epicardium.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
/* ---------- Dispatcher --------------------------------------------------- */
void vApiDispatcher(void *pvParameters);
extern SemaphoreHandle_t api_mutex;
extern TaskHandle_t dispatcher_task_id;
/* ---------- Hardware Init & Reset ---------------------------------------- */ /* ---------- Hardware Init & Reset ---------------------------------------- */
int hardware_early_init(void); int hardware_early_init(void);
int hardware_init(void); int hardware_init(void);
int hardware_reset(void); int hardware_reset(void);
/* ---------- Lifecycle ---------------------------------------------------- */
void vLifecycleTask(void *pvParameters);
void return_to_menu(void);
/* ---------- Serial ------------------------------------------------------- */
#define SERIAL_READ_BUFFER_SIZE 128
void vSerialTask(void *pvParameters);
void serial_enqueue_char(char chr);
/* ---------- LED Animation / Personal States ------------------------------ */ /* ---------- LED Animation / Personal States ------------------------------ */
#define PERSONAL_STATE_LED 14 #define PERSONAL_STATE_LED 14
void vLedTask(void *pvParameters); void vLedTask(void *pvParameters);
int personal_state_enabled(); int personal_state_enabled();
/* ---------- PMIC --------------------------------------------------------- */
void vPmicTask(void *pvParameters);
/* Critical battery voltage */
#define BATTERY_CRITICAL 3.40f
enum pmic_amux_signal {
PMIC_AMUX_CHGIN_U = 0x1,
PMIC_AMUX_CHGIN_I = 0x2,
PMIC_AMUX_BATT_U = 0x3,
PMIC_AMUX_BATT_CHG_I = 0x4,
PMIC_AMUX_BATT_DIS_I = 0x5,
PMIC_AMUX_BATT_NULL_I = 0x6,
PMIC_AMUX_THM_U = 0x7,
PMIC_AMUX_TBIAS_U = 0x8,
PMIC_AMUX_AGND_U = 0x9,
PMIC_AMUX_SYS_U = 0xA,
_PMIC_AMUX_MAX,
};
/*
* Read a value from the PMIC's AMUX. The result is already converted into its
* proper unit. See the MAX77650 datasheet for details.
*/
int pmic_read_amux(enum pmic_amux_signal sig, float *result);
/* ---------- BLE ---------------------------------------------------------- */ /* ---------- BLE ---------------------------------------------------------- */
void vBleTask(void *pvParameters); void vBleTask(void *pvParameters);
bool ble_shall_start(void); bool ble_is_enabled(void);
void ble_uart_write(uint8_t *pValue, uint8_t len); void ble_uart_write(uint8_t *pValue, uint8_t len);
/* ---------- Hardware (Peripheral) Locks ---------------------------------- */ /* ---------- Hardware (Peripheral) Locks ---------------------------------- */
...@@ -70,14 +31,12 @@ enum hwlock_periph { ...@@ -70,14 +31,12 @@ enum hwlock_periph {
HWLOCK_I2C = 0, HWLOCK_I2C = 0,
HWLOCK_ADC, HWLOCK_ADC,
HWLOCK_LED, HWLOCK_LED,
HWLOCK_SPI_ECG,
_HWLOCK_MAX, _HWLOCK_MAX,
}; };
int hwlock_acquire(enum hwlock_periph p, TickType_t wait); void hwlock_acquire(enum hwlock_periph p);
int hwlock_release(enum hwlock_periph p); int hwlock_acquire_nonblock(enum hwlock_periph p);
void hwlock_release(enum hwlock_periph p);
/* ---------- Display ------------------------------------------------------ */
/* Forces an unlock of the display. Only to be used in Epicardium */
void disp_forcelock();
#endif /* MODULES_H */ #endif /* MODULES_H */
...@@ -2,23 +2,39 @@ ...@@ -2,23 +2,39 @@
#include "leds.h" #include "leds.h"
#include "modules.h" #include "modules.h"
#include "os/work_queue.h"
#include "FreeRTOS.h"
#include "timers.h"
#include <math.h> #include <math.h>
uint8_t _personal_state_enabled = 0; static uint8_t _personal_state_enabled = 0;
uint8_t personal_state = STATE_NONE; static uint8_t personal_state = STATE_NONE;
uint8_t personal_state_persistent = 0; static uint8_t personal_state_persistent = 0;
static int led_animation_ticks = 0;
static int led_animation_state = 0;
int led_animation_ticks = 0; static TimerHandle_t led_timer;
int led_animation_state = 0; static StaticTimer_t led_timer_buffer;
static void worktick(void *data);
static const int led_animation_rate = 1000 / 25; /* 25Hz -> 40ms*/
int personal_state_enabled() int personal_state_enabled()
{ {
return _personal_state_enabled; return _personal_state_enabled;
} }
static void tick(TimerHandle_t xTimer)
{
workqueue_schedule(worktick, NULL);
}
int epic_personal_state_set(uint8_t state, bool persistent) int epic_personal_state_set(uint8_t state, bool persistent)
{ {
if (state < STATE_NONE || state > STATE_CAMP) if (state > STATE_CAMP)
return -EINVAL; return -EINVAL;
led_animation_state = 0; led_animation_state = 0;
...@@ -30,16 +46,31 @@ int epic_personal_state_set(uint8_t state, bool persistent) ...@@ -30,16 +46,31 @@ int epic_personal_state_set(uint8_t state, bool persistent)
_personal_state_enabled = (state != STATE_NONE); _personal_state_enabled = (state != STATE_NONE);
personal_state_persistent = persistent; personal_state_persistent = persistent;
if (was_enabled && !_personal_state_enabled) { if (!was_enabled && _personal_state_enabled) {
while (hwlock_acquire(HWLOCK_LED, pdMS_TO_TICKS(1)) < 0) { // Activate
vTaskDelay(pdMS_TO_TICKS(1)); if (!led_timer) {
led_timer = xTimerCreateStatic(
"personal state",
led_animation_rate / portTICK_PERIOD_MS,
pdTRUE,
NULL,
tick,
&led_timer_buffer
);
// since &poll_timer_buffer is not NULL, xTimerCreateStatic should allways succeed, so
// we don't need to check for poll_timer being NULL.
} }
leds_prep(PERSONAL_STATE_LED, 0, 0, 0); if (xTimerIsTimerActive(led_timer) == pdFALSE) {
leds_update_power(); xTimerStart(led_timer, 0);
leds_update(); }
hwlock_release(HWLOCK_LED); } else if (was_enabled && !_personal_state_enabled) {
// Deactivate
xTimerStop(led_timer, 0);
// TODO: we might need a lock here to avoid a race condition
leds_prep(PERSONAL_STATE_LED, 0, 0, 0);
epic_leds_update();
} }
return 0; return 0;
...@@ -55,24 +86,15 @@ int epic_personal_state_is_persistent() ...@@ -55,24 +86,15 @@ int epic_personal_state_is_persistent()
return personal_state_persistent; return personal_state_persistent;
} }
void vLedTask(void *pvParameters) static void worktick(void *data)
{ {
const int led_animation_rate = 1000 / 25; /* 25Hz -> 40ms*/
while (1) {
if (_personal_state_enabled) { if (_personal_state_enabled) {
while (hwlock_acquire(HWLOCK_LED, pdMS_TO_TICKS(1)) <
0) {
vTaskDelay(pdMS_TO_TICKS(1));
}
led_animation_ticks++; led_animation_ticks++;
if (personal_state == STATE_NO_CONTACT) { if (personal_state == STATE_NO_CONTACT) {
leds_prep(PERSONAL_STATE_LED, 255, 0, 0); leds_prep(PERSONAL_STATE_LED, 255, 0, 0);
} else if (personal_state == STATE_CHAOS) { } else if (personal_state == STATE_CHAOS) {
if (led_animation_state == 0) { if (led_animation_state == 0) {
leds_prep( leds_prep(PERSONAL_STATE_LED, 0, 0, 255);
PERSONAL_STATE_LED, 0, 0, 255
);
if (led_animation_ticks > if (led_animation_ticks >
(200 / led_animation_rate)) { (200 / led_animation_rate)) {
led_animation_ticks = 0; led_animation_ticks = 0;
...@@ -86,9 +108,7 @@ void vLedTask(void *pvParameters) ...@@ -86,9 +108,7 @@ void vLedTask(void *pvParameters)
led_animation_state = 2; led_animation_state = 2;
} }
} else if (led_animation_state == 2) { } else if (led_animation_state == 2) {
leds_prep( leds_prep(PERSONAL_STATE_LED, 0, 0, 255);
PERSONAL_STATE_LED, 0, 0, 255
);
if (led_animation_ticks > if (led_animation_ticks >
(1000 / led_animation_rate)) { (1000 / led_animation_rate)) {
led_animation_ticks = 0; led_animation_ticks = 0;
...@@ -104,9 +124,7 @@ void vLedTask(void *pvParameters) ...@@ -104,9 +124,7 @@ void vLedTask(void *pvParameters)
} }
} else if (personal_state == STATE_COMMUNICATION) { } else if (personal_state == STATE_COMMUNICATION) {
if (led_animation_state == 0) { if (led_animation_state == 0) {
leds_prep( leds_prep(PERSONAL_STATE_LED, 255, 255, 0);
PERSONAL_STATE_LED, 255, 255, 0
);
if (led_animation_ticks > if (led_animation_ticks >
(1000 / led_animation_rate)) { (1000 / led_animation_rate)) {
led_animation_ticks = 0; led_animation_ticks = 0;
...@@ -127,15 +145,9 @@ void vLedTask(void *pvParameters) ...@@ -127,15 +145,9 @@ void vLedTask(void *pvParameters)
1.0f, 1.0f,
fabs(sin( fabs(sin(
led_animation_ticks / led_animation_ticks /
(float)(1000 / (float)(1000 / led_animation_rate))));
led_animation_rate))));
}
leds_update_power();
leds_update();
hwlock_release(HWLOCK_LED);
} }
vTaskDelay(led_animation_rate / portTICK_PERIOD_MS); epic_leds_update();
} }
} }
#include "epicardium.h"
#include "modules/modules.h"
#include "modules/log.h"
#include "card10.h"
#include "pmic.h"
#include "MAX77650-Arduino-Library.h"
#include "max32665.h"
#include "mxc_sys.h"
#include "mxc_pins.h"
#include "adc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include <stdio.h>
/* Task ID for the pmic handler */
static TaskHandle_t pmic_task_id = NULL;
enum {
/* An irq was received, probably the power button */
PMIC_NOTIFY_IRQ = 1,
/* The timer has ticked and we should check the battery voltage again */
PMIC_NOTIFY_MONITOR = 2,
};
void pmic_interrupt_callback(void *_)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (pmic_task_id != NULL) {
xTaskNotifyFromISR(
pmic_task_id,
PMIC_NOTIFY_IRQ,
eSetBits,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
int pmic_read_amux(enum pmic_amux_signal sig, float *result)
{
int ret = 0;
if (sig > _PMIC_AMUX_MAX) {
return -EINVAL;
}
ret = hwlock_acquire(HWLOCK_ADC, pdMS_TO_TICKS(100));
if (ret < 0) {
return ret;
}
ret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
if (ret < 0) {
return ret;
}
/* Select the correct channel for this measurement. */
MAX77650_setMUX_SEL(sig);
/*
* According to the datasheet, the voltage will stabilize within 0.3us.
* Just to be sure, we'll wait a little longer. In the meantime,
* release the I2C mutex.
*/
hwlock_release(HWLOCK_I2C);
vTaskDelay(pdMS_TO_TICKS(5));
ret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100));
if (ret < 0) {
return ret;
}
uint16_t adc_data;
ADC_StartConvert(ADC_CH_0, 0, 0);
ADC_GetData(&adc_data);
/* Turn MUX back to neutral so it does not waste power. */
MAX77650_setMUX_SEL(sig);
/* Convert ADC measurement to SI Volts */
float adc_voltage = (float)adc_data / 1023.0f * 1.22f;
/*
* Convert value according to PMIC formulas (Table 7)
*/
switch (sig) {
case PMIC_AMUX_CHGIN_U:
*result = adc_voltage / 0.167f;
break;
case PMIC_AMUX_CHGIN_I:
*result = adc_voltage / 2.632f;
break;
case PMIC_AMUX_BATT_U:
*result = adc_voltage / 0.272f;
break;
case PMIC_AMUX_BATT_CHG_I:
*result = adc_voltage / 1.25f;
break;
case PMIC_AMUX_BATT_NULL_I:
case PMIC_AMUX_THM_U:
case PMIC_AMUX_TBIAS_U:
case PMIC_AMUX_AGND_U:
*result = adc_voltage;
break;
case PMIC_AMUX_SYS_U:
*result = adc_voltage / 0.26f;
break;
default:
ret = -EINVAL;
}
hwlock_release(HWLOCK_I2C);
hwlock_release(HWLOCK_ADC);
return ret;
}
/*
* Read the interrupt flag register and handle all interrupts which the PMIC has
* sent. In most cases this will be the buttons.
*/
static void
pmic_poll_interrupts(TickType_t *button_start_tick, TickType_t duration)
{
while (hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(500)) < 0) {
LOG_WARN("pmic", "Failed to acquire I2C. Retrying ...");
xTaskNotify(pmic_task_id, PMIC_NOTIFY_IRQ, eSetBits);
return;
}
uint8_t int_flag = MAX77650_getINT_GLBL();
hwlock_release(HWLOCK_I2C);
if (int_flag & MAX77650_INT_nEN_F) {
/* Button was pressed */
*button_start_tick = xTaskGetTickCount();
}
if (int_flag & MAX77650_INT_nEN_R) {
/* Button was released */
*button_start_tick = 0;
if (duration < pdMS_TO_TICKS(400)) {
return_to_menu();
} else {
LOG_WARN("pmic", "Resetting ...");
card10_reset();
}
}
/* TODO: Remove when all interrupts are handled */
if (int_flag & ~(MAX77650_INT_nEN_F | MAX77650_INT_nEN_R)) {
LOG_WARN("pmic", "Unhandled PMIC Interrupt: %x", int_flag);
}
}
__attribute__((noreturn)) static void pmic_die(float u_batt)
{
/* Stop core 1 */
core1_stop();
/* Grab the screen */
disp_forcelock();
/* Draw an error screen */
epic_disp_clear(0x0000);
epic_disp_print(0, 0, " Battery", 0xffff, 0x0000);
epic_disp_print(0, 20, " critical", 0xffff, 0x0000);
epic_disp_print(0, 40, " !!!!", 0xffff, 0x0000);
epic_disp_update();
/* Vibrate violently */
epic_vibra_set(true);
/* Wait a bit */
for (int i = 0; i < 50000000; i++)
__NOP();
LOG_WARN("pmic", "Poweroff");
MAX77650_setSFT_RST(0x2);
while (1)
__WFI();
}
/*
* Check the battery voltage. If it drops too low, turn card10 off.
*/
static void pmic_check_battery()
{
float u_batt;
int res;
res = pmic_read_amux(PMIC_AMUX_BATT_U, &u_batt);
if (res < 0) {
LOG_ERR("pmic", "Failed reading battery voltage: %d", res);
return;
}
LOG_DEBUG(
"pmic",
"Battery is at %d.%03d V",
(int)u_batt,
(int)(u_batt * 1000.0) % 1000
);
if (u_batt < BATTERY_CRITICAL) {
pmic_die(u_batt);
}
}
/*
* API-call for battery voltage
*/
int epic_read_battery_voltage(float *result)
{
return pmic_read_amux(PMIC_AMUX_BATT_U, result);
}
static StaticTimer_t pmic_timer_data;
static void vPmicTimerCb(TimerHandle_t xTimer)
{
/*
* Tell the PMIC task to check the battery again.
*/
xTaskNotify(pmic_task_id, PMIC_NOTIFY_MONITOR, eSetBits);
}
void vPmicTask(void *pvParameters)
{
pmic_task_id = xTaskGetCurrentTaskHandle();
ADC_Init(0x9, NULL);
GPIO_Config(&gpio_cfg_adc0);
TickType_t button_start_tick = 0;
pmic_check_battery();
TimerHandle_t pmic_timer = xTimerCreateStatic(
"PMIC Timer",
pdMS_TO_TICKS(60 * 1000),
pdTRUE,
NULL,
vPmicTimerCb,
&pmic_timer_data
);
if (pmic_timer == NULL) {
LOG_CRIT("pmic", "Could not create timer.");
vTaskDelay(portMAX_DELAY);
}
xTimerStart(pmic_timer, 0);
while (1) {
uint32_t reason;
if (button_start_tick == 0) {
reason = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
} else {
reason = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100));
}
TickType_t duration = xTaskGetTickCount() - button_start_tick;
if (button_start_tick != 0 && duration > pdMS_TO_TICKS(1000)) {
LOG_WARN("pmic", "Poweroff");
MAX77650_setSFT_RST(0x2);
}
if (reason & PMIC_NOTIFY_IRQ) {
pmic_poll_interrupts(&button_start_tick, duration);
}
if (reason & PMIC_NOTIFY_MONITOR) {
pmic_check_battery();
}
}
}
#include "epicardium.h"
#include "modules/log.h"
#include "api/interrupt-sender.h"
#include "FreeRTOS.h"
#include "task.h"
#include "rtc.h"
#include <stdint.h>
uint32_t epic_rtc_get_seconds(void)
{
uint32_t sec, subsec;
/*
* TODO: Find out what causes the weird behavior of this function. The
* time needed for this call seems to depend on the frequency at
* which it is called.
*/
while (RTC_GetTime(&sec, &subsec) == E_BUSY) {
vTaskDelay(pdMS_TO_TICKS(4));
}
return sec;
}
void epic_rtc_set_milliseconds(uint64_t milliseconds)
{
uint32_t sec, subsec;
sec = milliseconds / 1000;
subsec = (milliseconds % 1000);
subsec *= 4096;
subsec /= 1000;
while (RTC_Init(MXC_RTC, sec, subsec, NULL) == E_BUSY)
;
while (RTC_EnableRTCE(MXC_RTC) == E_BUSY)
;
}
void RTC_IRQHandler(void)
{
int flags = RTC_GetFlags();
if (flags & MXC_F_RTC_CTRL_ALDF) {
RTC_ClearFlags(MXC_F_RTC_CTRL_ALDF);
api_interrupt_trigger(EPIC_INT_RTC_ALARM);
} else {
LOG_WARN("rtc", "Unknown IRQ caught!");
/* Disable IRQ so it does not retrigger */
NVIC_DisableIRQ(RTC_IRQn);
}
}
int epic_rtc_schedule_alarm(uint32_t timestamp)
{
int res;
/*
* Check if the timestamp lies in the past and if so, trigger
* immediately.
*/
if (epic_rtc_get_seconds() >= timestamp) {
api_interrupt_trigger(EPIC_INT_RTC_ALARM);
return 0;
}
NVIC_EnableIRQ(RTC_IRQn);
while ((res = RTC_SetTimeofdayAlarm(MXC_RTC, timestamp)) == E_BUSY)
;
if (res != E_SUCCESS) {
return -EINVAL;
}
return 0;
}
#include "epicardium.h"
#include "api/interrupt-sender.h"
#include "modules/log.h"
#include "modules/modules.h"
#include "max32665.h"
#include "cdcacm.h"
#include "uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include <stdint.h>
#include <stdio.h>
/* Task ID for the serial handler */
TaskHandle_t serial_task_id = NULL;
/* The serial console in use (UART0) */
extern mxc_uart_regs_t *ConsoleUart;
/* Read queue, filled by both UART and CDCACM */
static QueueHandle_t read_queue;
/*
* API-call to write a string. Output goes to both CDCACM and UART
*/
void epic_uart_write_str(const char *str, intptr_t length)
{
UART_Write(ConsoleUart, (uint8_t *)str, length);
cdcacm_write((uint8_t *)str, length);
ble_uart_write((uint8_t *)str, length);
}
/*
* API-call to read a character from the queue.
*/
int epic_uart_read_char(void)
{
char chr;
if (xQueueReceive(read_queue, &chr, 0) == pdTRUE) {
return (int)chr;
}
return (-1);
}
/*
* API-call to read data from the queue.
*/
int epic_uart_read_str(char *buf, size_t cnt)
{
size_t i = 0;
for (i = 0; i < cnt; i++) {
if (xQueueReceive(read_queue, &buf[i], 0) != pdTRUE) {
break;
}
}
return i;
}
long _write_epicardium(int fd, const char *buf, size_t cnt)
{
/*
* Only print one line at a time. Insert `\r` between lines so they are
* properly displayed on the serial console.
*/
size_t i, last = 0;
for (i = 0; i < cnt; i++) {
if (buf[i] == '\n') {
epic_uart_write_str(&buf[last], i - last);
epic_uart_write_str("\r", 1);
last = i;
}
}
epic_uart_write_str(&buf[last], cnt - last);
return cnt;
}
/* Interrupt handler needed for SDK UART implementation */
void UART0_IRQHandler(void)
{
UART_Handler(ConsoleUart);
}
static void uart_callback(uart_req_t *req, int error)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(serial_task_id, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void serial_enqueue_char(char chr)
{
if (chr == 0x3) {
/* Control-C */
api_interrupt_trigger(EPIC_INT_CTRL_C);
}
if (xQueueSend(read_queue, &chr, 100) == errQUEUE_FULL) {
/* Queue overran, wait a bit */
vTaskDelay(portTICK_PERIOD_MS * 50);
}
api_interrupt_trigger(EPIC_INT_UART_RX);
}
void vSerialTask(void *pvParameters)
{
static uint8_t buffer[sizeof(char) * SERIAL_READ_BUFFER_SIZE];
static StaticQueue_t read_queue_data;
serial_task_id = xTaskGetCurrentTaskHandle();
/* Setup read queue */
read_queue = xQueueCreateStatic(
SERIAL_READ_BUFFER_SIZE, sizeof(char), buffer, &read_queue_data
);
/* Setup UART interrupt */
NVIC_ClearPendingIRQ(UART0_IRQn);
NVIC_DisableIRQ(UART0_IRQn);
NVIC_SetPriority(UART0_IRQn, 6);
NVIC_EnableIRQ(UART0_IRQn);
unsigned char data;
uart_req_t read_req = {
.data = &data,
.len = 1,
.callback = uart_callback,
};
while (1) {
int ret = UART_ReadAsync(ConsoleUart, &read_req);
if (ret != E_NO_ERROR && ret != E_BUSY) {
LOG_ERR("serial", "error reading uart: %d", ret);
vTaskDelay(portMAX_DELAY);
}
ulTaskNotifyTake(pdTRUE, portTICK_PERIOD_MS * 1000);
if (read_req.num > 0) {
serial_enqueue_char(*read_req.data);
}
while (UART_NumReadAvail(ConsoleUart) > 0) {
serial_enqueue_char(UART_ReadByte(ConsoleUart));
}
while (cdcacm_num_read_avail() > 0) {
serial_enqueue_char(cdcacm_read());
}
}
}
#include <string.h> #include <string.h>
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "semphr.h" #include "queue.h"
#include "epicardium.h" #include "epicardium.h"
#include "modules/log.h" #include "os/core.h"
#include "modules/stream.h" #include "modules/stream.h"
#include "os/mutex.h"
/* Internal buffer of registered streams */ /* Internal buffer of registered streams */
static struct stream_info *stream_table[SD_MAX]; static struct stream_info *stream_table[SD_MAX];
/* Lock for modifying the stream info table */ /* Lock for modifying the stream info table */
static StaticSemaphore_t stream_table_lock_data; static struct mutex stream_table_lock;
static SemaphoreHandle_t stream_table_lock;
int stream_init() int stream_init()
{ {
memset(stream_table, 0x00, sizeof(stream_table)); memset(stream_table, 0x00, sizeof(stream_table));
stream_table_lock = mutex_create(&stream_table_lock);
xSemaphoreCreateMutexStatic(&stream_table_lock_data);
return 0; return 0;
} }
int stream_register(int sd, struct stream_info *stream) int stream_register(int sd, struct stream_info *stream)
{ {
if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) { int ret = 0;
LOG_WARN("stream", "Lock contention error");
return -EBUSY; mutex_lock(&stream_table_lock);
}
if (sd < 0 || sd >= SD_MAX) { if (sd < 0 || sd >= SD_MAX) {
return -EINVAL; ret = -EINVAL;
goto out;
} }
if (stream_table[sd] != NULL) { if (stream_table[sd] != NULL) {
/* Stream already registered */ /* Stream already registered */
return -EACCES; ret = -EACCES;
goto out;
} }
stream_table[sd] = stream; stream_table[sd] = stream;
xSemaphoreGive(stream_table_lock); out:
return 0; mutex_unlock(&stream_table_lock);
return ret;
} }
int stream_deregister(int sd, struct stream_info *stream) int stream_deregister(int sd, struct stream_info *stream)
{ {
if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) { int ret = 0;
LOG_WARN("stream", "Lock contention error");
return -EBUSY; mutex_lock(&stream_table_lock);
}
if (sd < 0 || sd >= SD_MAX) { if (sd < 0 || sd >= SD_MAX) {
return -EINVAL; ret = -EINVAL;
goto out;
} }
if (stream_table[sd] != stream) { if (stream_table[sd] != stream) {
/* Stream registered by someone else */ /* Stream registered by someone else */
return -EACCES; ret = -EACCES;
goto out;
} }
stream_table[sd] = NULL; stream_table[sd] = NULL;
xSemaphoreGive(stream_table_lock); out:
return 0; mutex_unlock(&stream_table_lock);
return ret;
} }
int epic_stream_read(int sd, void *buf, size_t count) int epic_stream_read(int sd, void *buf, size_t count)
{ {
int ret = 0;
/* /*
* TODO: In theory, multiple reads on different streams can happen * TODO: In theory, multiple reads on different streams can happen
* simulaneously. I don't know what the most efficient implementation * simulaneously. I don't know what the most efficient implementation
* of this would look like. * of this would look like.
*/ */
if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) { mutex_lock(&stream_table_lock);
LOG_WARN("stream", "Lock contention error");
return -EBUSY;
}
if (sd < 0 || sd >= SD_MAX) { if (sd < 0 || sd >= SD_MAX) {
return -EBADF; ret = -EBADF;
goto out;
} }
struct stream_info *stream = stream_table[sd]; struct stream_info *stream = stream_table[sd];
if (stream == NULL) { if (stream == NULL) {
return -ENODEV; ret = -ENODEV;
goto out;
} }
/* Poll the stream, if a poll_stream function exists */ /* Poll the stream, if a poll_stream function exists */
if (stream->poll_stream != NULL) { if (stream->poll_stream != NULL) {
int ret = stream->poll_stream(); ret = stream->poll_stream();
if (ret < 0) { if (ret < 0) {
return ret; goto out;
} }
} }
/* Check buffer size is a multiple of the data packet size */ /* Check buffer size is a multiple of the data packet size */
if (count % stream->item_size != 0) { if (count % stream->item_size != 0) {
return -EINVAL; ret = -EINVAL;
goto out;
} }
size_t i; size_t i;
...@@ -107,6 +111,9 @@ int epic_stream_read(int sd, void *buf, size_t count) ...@@ -107,6 +111,9 @@ int epic_stream_read(int sd, void *buf, size_t count)
} }
} }
xSemaphoreGive(stream_table_lock); ret = i / stream->item_size;
return i / stream->item_size;
out:
mutex_unlock(&stream_table_lock);
return ret;
} }
...@@ -4,14 +4,16 @@ ...@@ -4,14 +4,16 @@
#include <stdint.h> #include <stdint.h>
#ifndef __SPHINX_DOC #ifndef __SPHINX_DOC
/* stddef.h is not recognized by hawkmoth for some odd reason */ /* Some headers are not recognized by hawkmoth for some odd reason */
#include <stddef.h> #include <stddef.h>
#else
typedef unsigned int size_t;
#endif /* __SPHINX_DOC */
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "queue.h" #include "queue.h"
#else
typedef unsigned int size_t;
typedef int bool;
typedef void *QueueHandle_t;
#endif /* __SPHINX_DOC */
/* Time to wait for the descriptor table lock to become available */ /* Time to wait for the descriptor table lock to become available */
#define STREAM_MUTEX_WAIT pdMS_TO_TICKS(100) #define STREAM_MUTEX_WAIT pdMS_TO_TICKS(100)
...@@ -25,6 +27,17 @@ typedef unsigned int size_t; ...@@ -25,6 +27,17 @@ typedef unsigned int size_t;
* Please keep IDs in sequential order. * Please keep IDs in sequential order.
*/ */
enum stream_descriptor { enum stream_descriptor {
/** BHI160 Accelerometer */
SD_BHI160_ACCELEROMETER,
/** BHI160 Magnetometer */
SD_BHI160_MAGNETOMETER,
/** BHI160 Orientation Sensor */
SD_BHI160_ORIENTATION,
/** BHI160 Gyroscope */
SD_BHI160_GYROSCOPE,
/** MAX30001 ECG */
SD_MAX30001_ECG,
SD_MAX86150,
/** Highest descriptor must always be ``SD_MAX``. */ /** Highest descriptor must always be ``SD_MAX``. */
SD_MAX, SD_MAX,
}; };
...@@ -55,6 +68,12 @@ struct stream_info { ...@@ -55,6 +68,12 @@ struct stream_info {
* The function registered here should never block for a longer time. * The function registered here should never block for a longer time.
*/ */
int (*poll_stream)(); int (*poll_stream)();
/**
* Set to true if the last write to ``queue`` failed because
* the queue was full.
*/
bool was_full;
}; };
/** /**
......
#include "epicardium.h"
#include "trng.h"
int epic_trng_read(uint8_t *dest, size_t size)
{
if (dest == NULL)
return -EFAULT;
TRNG_Read(MXC_TRNG, dest, size);
return 0;
}
#include "gpio.h"
#include "FreeRTOS.h"
#include "timers.h"
static const gpio_cfg_t motor_pin = {
PORT_0, PIN_8, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
static TimerHandle_t vibra_timer;
void epic_vibra_set(int status)
{
if (status) {
GPIO_OutSet(&motor_pin);
} else {
GPIO_OutClr(&motor_pin);
}
}
void vTimerCallback()
{
epic_vibra_set(0);
}
void epic_vibra_vibrate(int millis)
{
int ticks = millis * (configTICK_RATE_HZ / 1000);
epic_vibra_set(1);
vibra_timer =
xTimerCreate("vibratimer", ticks, pdFALSE, 0, vTimerCallback);
xTimerStart(vibra_timer, 0);
}
#include "os/core.h"
#include "os/config.h"
#include "epicardium.h"
#include <assert.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#define MAX_LINE_LENGTH 80
#define KEYS_PER_BLOCK 16
#define KEY_LENGTH 16
#define NOT_INT_MAGIC ((int)0x80000000)
// one key-value pair representing a line in the config
typedef struct {
char key[KEY_LENGTH];
// the value in the config file, if it's an integer.
// for strings it's set to NOT_INT_MAGIC
int value;
// the byte offset in the config file to read the value string
size_t value_offset;
} config_slot;
// a block of 16 config slots
// if more are needed, this becomes a linked list
typedef struct {
config_slot slots[KEYS_PER_BLOCK];
void *next;
} config_block;
static config_block *config_data = NULL;
// returns the config slot for a key name
static config_slot *find_config_slot(const char *key)
{
config_block *current = config_data;
while (current) {
for (int i = 0; i < KEYS_PER_BLOCK; i++) {
config_slot *k = &current->slots[i];
if (strcmp(k->key, key) == 0) {
// found what we're looking for
return k;
} else if (*k->key == '\0') {
// found the first empty key
return NULL;
}
}
current = current->next;
}
return NULL;
}
// returns the next available config slot, or allocates a new block if needed
static config_slot *allocate_config_slot()
{
config_block *current;
if (config_data == NULL) {
config_data = malloc(sizeof(config_block));
assert(config_data != NULL);
memset(config_data, 0, sizeof(config_block));
}
current = config_data;
while (true) {
for (int i = 0; i < KEYS_PER_BLOCK; i++) {
config_slot *k = &current->slots[i];
if (*k->key == '\0') {
return k;
}
}
// this block is full and there's no next allocated block
if (current->next == NULL) {
current->next = malloc(sizeof(config_block));
assert(current->next != NULL);
memset(current->next, 0, sizeof(config_block));
}
current = current->next;
}
}
// parses an int out of 'value' or returns NOT_INT_MAGIC
static int try_parse_int(const char *value)
{
char *endptr;
size_t len = strlen(value);
int v = strtol(value, &endptr, 0);
if (endptr != (value + len)) {
return NOT_INT_MAGIC;
}
return v;
}
// loads a key/value pair into a new config slot
static void add_config_pair(
const char *key, const char *value, int line_number, size_t value_offset
) {
if (strlen(key) > KEY_LENGTH - 1) {
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
line_number
);
return;
}
config_slot *slot = allocate_config_slot();
strncpy(slot->key, key, KEY_LENGTH);
slot->value = try_parse_int(value);
slot->value_offset = value_offset;
}
static void trim(char *str)
{
char *start = str;
while (*start && !isgraph(*start))
start++;
if (strlen(start) > 0) {
char *end = start + strlen(start) - 1;
while (*end && !isgraph(*end))
end--;
end[1] = 0;
}
memmove(str, start, strlen(start) + 1);
}
// parses one line of the config file
static void parse_line(char *line, int line_number, size_t line_offset)
{
char *line_start = line;
char *value_start = strchr(line, '=') + 1;
trim(line);
//printf(line);
if (*line == '#') {
//skip comments
return;
}
char *eq = strchr(line, '=');
if (!eq) {
if (*line) {
LOG_WARN(
"card10.cfg",
"line %d: syntax error",
line_number
);
}
return;
}
*eq = 0;
char *key = line;
trim(key);
if (*key == '\0') {
LOG_WARN("card10.cfg", "line %d: empty key", line_number);
return;
}
char *value = eq + 1;
trim(value);
if (*value == '\0') {
LOG_WARN(
"card10.cfg",
"line %d: empty value for option '%s'",
line_number,
key
);
return;
}
size_t value_offset = value_start - line_start + line_offset;
add_config_pair(key, value, line_number, value_offset);
}
typedef struct {
int line_number;
int file_offset;
int line_start;
char line[MAX_LINE_LENGTH + 1];
int line_length;
} parser_state;
int parse_character(char c, parser_state *s)
{
if (c != '\r' && c != '\n') {
if (s->line_length == MAX_LINE_LENGTH) {
LOG_WARN(
"card10.cfg",
"line:%d: too long - aborting",
s->line_number
);
return -1;
}
s->line[s->line_length++] = c;
} else {
s->line[s->line_length] = 0;
//printf("New line: %s (%d %d)\n", s->line, s->line_number, s->line_start);
parse_line(s->line, s->line_number, s->line_start);
s->line_length = 0;
s->line_start = s->file_offset + 1;
if (c == '\n') {
s->line_number++;
}
}
s->file_offset++;
return 0;
}
// parses the entire config file
void load_config(void)
{
LOG_DEBUG("card10.cfg", "loading...");
int fd = epic_file_open("card10.cfg", "r");
if (fd < 0) {
LOG_DEBUG(
"card10.cfg",
"loading failed: %s (%d)",
strerror(-fd),
fd
);
return;
}
/* Clear any existing configuration */
/* Don't free the blocks as we are most likely
* going to re-use all of them. */
config_block *current;
if (config_data != NULL) {
current = config_data;
while (true) {
memset(current->slots, 0, sizeof(current->slots));
if (current->next == NULL) {
break;
}
current = current->next;
}
}
char buf[128];
int nread;
parser_state s;
memset(&s, 0, sizeof(s));
s.line_number = 1;
do {
nread = epic_file_read(fd, buf, sizeof(buf));
int i;
for (i = 0; i < nread; i++) {
parse_character(buf[i], &s);
}
} while (nread == sizeof(buf));
parse_character('\n', &s);
epic_file_close(fd);
}
// opens the config file, seeks to seek_offset and reads buf_len bytes
// used for reading strings without storing them in memory
// since we don't need to optimize for that use case as much
static size_t read_config_offset(size_t seek_offset, char *buf, size_t buf_len)
{
int fd = epic_file_open("card10.cfg", "r");
if (fd < 0) {
LOG_DEBUG(
"card10.cfg",
"opening config failed: %s (%d)",
strerror(-fd),
fd
);
return 0;
}
int rc = epic_file_seek(fd, seek_offset, SEEK_SET);
if (rc < 0) {
LOG_ERR("card10.cfg", "seek2 failed (%d), aborting", rc);
return 0;
}
// one byte less to accommodate the 0 termination
int nread = epic_file_read(fd, buf, buf_len - 1);
buf[nread] = '\0';
epic_file_close(fd);
return nread;
}
// returns error if not found or invalid
int epic_config_get_integer(const char *key, int *value)
{
config_slot *slot = find_config_slot(key);
if (slot && slot->value != NOT_INT_MAGIC) {
*value = slot->value;
return 0;
}
return -ENOENT;
}
// returns default_value if not found or invalid
int config_get_integer_with_default(const char *key, int default_value)
{
int value;
int ret = epic_config_get_integer(key, &value);
if (ret) {
return default_value;
} else {
return value;
}
}
// returns error if not found
int epic_config_get_string(const char *key, char *buf, size_t buf_len)
{
config_slot *slot = find_config_slot(key);
if (!(slot && slot->value_offset)) {
return -ENOENT;
}
size_t nread = read_config_offset(slot->value_offset, buf, buf_len);
if (nread == 0) {
return -ENOENT;
}
char *end = buf;
while (*end && !iscntrl(*end))
end++;
*end = 0;
trim(buf);
return 0;
}
// returns dflt if not found, otherwise same pointer as buf
char *config_get_string_with_default(
const char *key, char *buf, size_t buf_len, char *dflt
) {
int ret = epic_config_get_string(key, buf, buf_len);
if (ret) {
return dflt;
} else {
return buf;
}
}
// returns error if not found or invalid
int epic_config_get_boolean(const char *key, bool *value)
{
int int_value;
int ret = epic_config_get_integer(key, &int_value);
if (ret == 0) {
*value = !!int_value;
return 0;
}
char buf[MAX_LINE_LENGTH];
ret = epic_config_get_string(key, buf, MAX_LINE_LENGTH);
if (ret < 0) {
return ret;
}
if (!strcasecmp(buf, "true")) {
*value = true;
return 0;
} else if (!strcasecmp(buf, "false")) {
*value = false;
return 0;
}
return -ERANGE;
}
// returns default_value if not found or invalid
bool config_get_boolean_with_default(const char *key, bool default_value)
{
bool value;
int ret = epic_config_get_boolean(key, &value);
if (ret) {
return default_value;
} else {
return value;
}
}
int epic_config_set_string(const char *key, const char *value_in)
{
char value[MAX_LINE_LENGTH + 1];
if (strlen(key) > MAX_LINE_LENGTH) {
return -EINVAL;
}
/* TODO: Change interface of trim to take the buffer and size directly */
if (strlen(value_in) > MAX_LINE_LENGTH) {
return -EINVAL;
}
strcpy(value, value_in);
trim(value);
if (snprintf(NULL, 0, "\n%s = %s\n", key, value) > MAX_LINE_LENGTH) {
return -EINVAL;
}
/* Check if key is sane. No control characters, spaces, equal signs or pounds allowed */
for (size_t i = 0; i < strlen(key); i++) {
char c = key[i];
if (!isgraph(c) || c == '=' || c == '#') {
return -EINVAL;
}
}
/* Check if value is sane. No control characters allowed */
for (size_t i = 0; i < strlen(value); i++) {
char c = value[i];
if (!isprint(c)) {
return -EINVAL;
}
}
config_slot *slot = find_config_slot(key);
bool present = slot && slot->value_offset;
int ret = 0;
if (!present) {
/* Easy case: We simply add the new option at the
* end of the file. */
char buf[MAX_LINE_LENGTH];
/* Leading new line because I'm lazy */
ret = snprintf(buf, sizeof(buf), "\n%s = %s\n", key, value);
if (ret < 0 || ret >= (int)sizeof(buf)) {
return -EINVAL;
}
int fd = epic_file_open("card10.cfg", "a");
if (fd < 0) {
LOG_DEBUG(
"card10.cfg",
"open for appending failed: %s (%d)",
strerror(-fd),
fd
);
return fd;
}
int write_ret = epic_file_write(fd, buf, strlen(buf));
if (write_ret < 0) {
LOG_DEBUG(
"card10.cfg",
"writing failed: %s (%d)",
strerror(-write_ret),
write_ret
);
}
if (write_ret < (int)strlen(buf)) {
LOG_DEBUG(
"card10.cfg",
"writing failed to write all bytes (%d of %d)",
write_ret,
strlen(buf)
);
}
ret = epic_file_close(fd);
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"close failed: %s (%d)",
strerror(-ret),
ret
);
}
if (write_ret < 0) {
ret = write_ret;
}
if (ret < 0) {
goto out;
}
if (write_ret < (int)strlen(buf)) {
LOG_DEBUG(
"card10.cfg",
"writing failed to write all bytes (%d of %d)",
write_ret,
strlen(buf)
);
ret = -EIO;
goto out;
}
} else {
/* Complex case: The value is already somewhere in the file.
* We do not want to lose existing formatting or comments.
* Solution: Copy parts of the file, insert new value, copy
* rest, rename.
*/
char buf[MAX_LINE_LENGTH + 1];
int fd1 = -1;
int fd2 = -1;
ret = epic_config_get_string(key, buf, sizeof(buf));
if (ret == 0 && strcmp(buf, value) == 0) {
/* Nothing to do: the values are the same. */
return 0;
}
size_t nread = read_config_offset(
slot->value_offset, buf, sizeof(buf)
);
if (nread == 0) {
LOG_DEBUG("card10.cfg", "could not read old value");
ret = -EIO;
goto complex_out;
}
char *end = buf;
while (*end && (!iscntrl(*end) || isblank(*end)))
end++;
*end = 0;
int old_len = strlen(buf);
fd1 = epic_file_open("card10.cfg", "r");
if (fd1 < 0) {
LOG_DEBUG(
"card10.cfg",
"open for read failed: %s (%d)",
strerror(-fd1),
fd1
);
ret = fd1;
goto complex_out;
}
fd2 = epic_file_open("card10.nfg", "w");
if (fd2 < 0) {
LOG_DEBUG(
"card10.nfg",
"open for writing failed: %s (%d)",
strerror(-fd2),
fd2
);
ret = fd2;
goto complex_out;
}
/* Copy over slot->value_offset bytes */
int i = slot->value_offset;
while (i > 0) {
int n = i > (int)sizeof(buf) ? (int)sizeof(buf) : i;
ret = epic_file_read(fd1, buf, n);
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"read failed: rc: %d",
ret
);
goto complex_out;
}
int ret2 = epic_file_write(fd2, buf, ret);
if (ret2 < 0) {
ret = ret2;
LOG_DEBUG(
"card10.nfg",
"write failed: rc: %d",
ret
);
goto complex_out;
}
i -= ret;
}
/* Insert new value into the new file */
ret = epic_file_write(fd2, value, strlen(value));
if (ret < 0) {
LOG_DEBUG("card10.nfg", "write failed: rc: %d", ret);
goto complex_out;
}
/* Skip the old value inside the old file */
epic_file_seek(fd1, old_len, SEEK_CUR);
/* Copy the rest of the old file to the new file */
while (true) {
int ret = epic_file_read(fd1, buf, sizeof(buf));
if (ret == 0) {
break;
}
if (ret < 0) {
LOG_DEBUG(
"card10.cfg",
"read failed: rc: %d",
ret
);
goto complex_out;
}
int ret2 = epic_file_write(fd2, buf, ret);
if (ret2 < 0) {
ret = ret2;
LOG_DEBUG(
"card10.nfg",
"write failed: rc: %d",
ret
);
goto complex_out;
}
if (ret < (int)sizeof(buf)) {
break;
}
}
complex_out:
if (fd1 >= 0) {
epic_file_close(fd1);
}
if (fd2 >= 0) {
int ret2 = epic_file_close(fd2);
if (ret >= 0) {
ret = ret2;
}
}
if (ret >= 0) {
epic_file_unlink("card10.cfg");
epic_file_rename("card10.nfg", "card10.cfg");
}
}
out:
/* Reload config so the new key or the changed value is available */
load_config();
return ret < 0 ? ret : 0;
}
#ifndef EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#define EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#include <stdbool.h>
#include <stddef.h>
//initialize configuration values and load card10.cfg
void load_config(void);
// returns default_value if not found or invalid
bool config_get_boolean_with_default(const char *key, bool default_value);
int config_get_integer_with_default(const char *key, int default_value);
// returns dflt if not found, otherwise same pointer as buf
char *config_get_string_with_default(const char *key, char *buf, size_t buf_len, char *dflt);
#endif//EPICARDIUM_MODULES_CONFIG_H_INCLUDED
#pragma once
/**
* Panic
* =====
* Panicking should be used in situations where no automatic recovery is
* possible of where a fatal bug was detected. Calling :c:func:`panic()` will
* show a message on the screen and serial console and then reboot the device.
*
* Keep in mind that screen space is limited and thus the message should be as
* concise as possible.
*/
/**
* Trigger a firmware panic.
*
* This function will not return but instead reboot the device. No
* synchronization of e.g. the filesystem is done so this could potentially lead
* to data loss.
*/
void panic(const char *format, ...)
__attribute__((noreturn, format(printf, 1, 2)));
/**
* Logging
* =======
* All logging macros take a "subsystem" and a log message. This subsystem
* argument should be used to uniquely identify where a message came from.
*
* **Example**:
*
* .. code-block:: cpp
*
* LOG_ERR("pmic",
* "Failed reading battery voltage: %s (%d)",
* strerror(-res),
* res);
*/
/* Whether to enable logging at all */
#ifndef LOG_ENABLE
#define LOG_ENABLE 1
#endif
/* Whether to enable even more verbose logging */
#ifndef LOG_ENABLE_DEBUG
#define LOG_ENABLE_DEBUG 0
#endif
/* Whether to enable colorful log */
#ifndef LOG_ENABLE_COLOR
#define LOG_ENABLE_COLOR 1
#endif
#define SEV_DEBUG "D"
#define SEV_INFO "I"
#define SEV_WARN "W"
#define SEV_ERR "E"
#define SEV_CRIT "C"
#if LOG_ENABLE && !defined(__SPHINX_DOC)
int log_msg(const char *subsys, const char *format, ...)
__attribute__((format(printf, 2, 3)));
#if LOG_ENABLE_DEBUG
#define LOG_DEBUG(subsys, format, ...) \
log_msg(subsys, SEV_DEBUG format, ##__VA_ARGS__)
#else /* LOG_ENABLE_DEBUG */
#define LOG_DEBUG(subsys, format, ...)
#endif /* LOG_ENABLE_DEBUG */
#define LOG_INFO(subsys, format, ...) \
log_msg(subsys, SEV_INFO format, ##__VA_ARGS__)
#define LOG_WARN(subsys, format, ...) \
log_msg(subsys, SEV_WARN format, ##__VA_ARGS__)
#define LOG_ERR(subsys, format, ...) \
log_msg(subsys, SEV_ERR format, ##__VA_ARGS__)
#define LOG_CRIT(subsys, format, ...) \
log_msg(subsys, SEV_CRIT format, ##__VA_ARGS__)
#else /* LOG_ENABLE */
/** */
#define LOG_INFO(subsys, format, ...) 0
/** */
#define LOG_WARN(subsys, format, ...) 0
/** */
#define LOG_ERR(subsys, format, ...) 0
/** */
#define LOG_CRIT(subsys, format, ...) 0
/** Only prints when debug logging is enabled in the meson configuration. */
#define LOG_DEBUG(subsys, format, ...) 0
inline __attribute__((format(printf, 2, 3))) int
log_msg(const char *subsys, const char *format, ...)
{
return 0;
}
#endif /* LOG_ENABLE */
#include "modules/log.h" #include "os/core.h"
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "task.h" #include "task.h"
......