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 3979 additions and 0 deletions
#include "epicardium.h"
#include "modules/modules.h"
#include "os/core.h"
#include "drivers/drivers.h"
#include "card10.h"
#include "bme680.h"
#include "bosch.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#define HEATR_TEMP 320
#define HEATR_DUR 150
static bool initialized;
static struct bme680_dev bme;
static int convert_error(int8_t error)
{
switch (error) {
case BME680_OK:
return 0;
case BME680_E_NULL_PTR:
return EFAULT;
case BME680_E_COM_FAIL:
return EIO;
case BME680_E_DEV_NOT_FOUND:
return ENODEV;
case BME680_E_INVALID_LENGTH:
return EINVAL;
default:
return 1;
}
}
static int8_t
i2c_write(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
{
int8_t ret;
hwlock_acquire(HWLOCK_I2C);
ret = card10_bosch_i2c_write(addr, reg, p_buf, size);
hwlock_release(HWLOCK_I2C);
return ret;
}
static int8_t i2c_read(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
{
int8_t ret;
hwlock_acquire(HWLOCK_I2C);
ret = card10_bosch_i2c_read(addr, reg, p_buf, size);
hwlock_release(HWLOCK_I2C);
return ret;
}
static void delay(uint32_t msec)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
card10_bosch_delay(msec);
} else {
vTaskDelay(pdMS_TO_TICKS(msec));
}
}
int epic_bme680_init()
{
int8_t result = BME680_OK;
if (bsec_active()) {
/* If the proprietary Bosch BSEC libary is in use
* we redirect calls to that. It always runs
* in the background
*/
return 0;
}
if (initialized) {
return 0;
}
bme.dev_id = BME680_I2C_ADDR_PRIMARY;
bme.intf = BME680_I2C_INTF;
bme.read = i2c_read;
bme.write = i2c_write;
bme.delay_ms = delay;
/*
* amb_temp can be set to 25 prior to configuring the gas sensor
* or by performing a few temperature readings without operating
* the gas sensor.
*/
bme.amb_temp = 25;
result = bme680_init(&bme);
if (result != BME680_OK) {
LOG_ERR("bme680", "bme680_init error: %d\n", result);
return -convert_error(result);
}
/*
* Select the power mode. Must be set before writing the sensor
* configuration
*/
bme.power_mode = BME680_FORCED_MODE;
/* Set the temperature, pressure and humidity settings */
bme.tph_sett.os_hum = BME680_OS_2X;
bme.tph_sett.os_pres = BME680_OS_4X;
bme.tph_sett.os_temp = BME680_OS_8X;
bme.tph_sett.filter = BME680_FILTER_SIZE_0;
/* Set the remaining gas sensor settings and link the heating profile */
bme.gas_sett.run_gas = BME680_ENABLE_GAS_MEAS;
/* Create a ramp heat waveform in 3 steps */
bme.gas_sett.heatr_temp = HEATR_TEMP; /* degree Celsius */
bme.gas_sett.heatr_dur = HEATR_DUR; /* milliseconds */
/* Set the required sensor settings needed */
uint16_t settings_sel = BME680_OST_SEL | BME680_OSP_SEL |
BME680_OSH_SEL | BME680_FILTER_SEL |
BME680_GAS_SENSOR_SEL;
result = bme680_set_sensor_settings(settings_sel, &bme);
if (result != BME680_OK) {
LOG_ERR("bme680",
"bme680_set_sensor_settings error: %d\n",
result);
return -convert_error(result);
}
initialized = true;
return 0;
}
int epic_bme680_deinit()
{
/* This is an intentional NO OP to keep the BME680 always initialized.
*
* If it configured to foreced mode, there is no energy consumption
* penalty.
*/
if (bsec_active()) {
return 0;
}
#if 0
if (!initialized) {
return 0;
}
int8_t result = bme680_soft_reset(&bme);
if (result != BME680_OK) {
LOG_ERR("bme680", "bme680_soft_reset error: %d\n", result);
}
initialized = false;
#endif
return 0;
}
int epic_bme680_read_sensors(struct bme680_sensor_data *data)
{
int8_t result = BME680_OK;
if (bsec_active()) {
return bsec_read_bme680(data);
}
if (!initialized) {
LOG_ERR("bme680", "bme680 sensor not initialized");
return -EINVAL;
}
if (data == NULL) {
return -EFAULT;
}
uint16_t profile_dur = 0;
bme680_get_profile_dur(&profile_dur, &bme);
result = bme680_set_sensor_mode(&bme); /* Trigger a measurement */
if (result != BME680_OK) {
LOG_ERR("bme680", "bme680_set_sensor_mode error: %d\n", result);
return -convert_error(result);
}
/*
* Wait for the measurement to complete. Release the I2C lock in the
* meantime.
*/
vTaskDelay(pdMS_TO_TICKS(profile_dur));
struct bme680_field_data raw_data;
result = bme680_get_sensor_data(&raw_data, &bme);
if (result != BME680_OK) {
LOG_ERR("bme680", "bme680_get_sensor_data error: %d\n", result);
return -convert_error(result);
}
data->temperature = (float)raw_data.temperature / 100.0f;
data->humidity = raw_data.humidity / 1000.0f;
data->pressure = raw_data.pressure / 100.0f;
data->gas_resistance = raw_data.gas_resistance;
return 0;
}
/* Adapted from bsec_iot_example.c and bsec_iot_ulp_plus_example.c */
#include "card10.h"
#include "bosch.h"
#include "bsec_integration.h"
#include "ble/ess.h"
#include "epicardium.h"
#include "modules/modules.h"
#include "os/config.h"
#include "os/core.h"
#include "FreeRTOS.h"
#include "task.h"
#include "max32665.h"
#include "gcr_regs.h"
#include <string.h>
#include <stdio.h>
TaskHandle_t bsec_task_id;
static int64_t last_bme680_timestamp;
static bool bsec_task_active;
static bool debug;
static struct bsec_sensor_data last_bsec_data;
#define ULP 0
// From generic_18v_3s_4d/bsec_serialized_configurations_iaq.c
static const uint8_t bsec_config_generic_18v_3s_4d[454] = {
0, 8, 4, 1, 61, 0, 0, 0, 0, 0, 0, 0, 174, 1,
0, 0, 48, 0, 1, 0, 0, 192, 168, 71, 64, 49, 119, 76,
0, 0, 225, 68, 137, 65, 0, 191, 205, 204, 204, 190, 0, 0,
64, 191, 225, 122, 148, 190, 0, 0, 0, 0, 216, 85, 0, 100,
0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 2, 0, 0, 244,
1, 225, 0, 25, 0, 0, 128, 64, 0, 0, 32, 65, 144, 1,
0, 0, 112, 65, 0, 0, 0, 63, 16, 0, 3, 0, 10, 215,
163, 60, 10, 215, 35, 59, 10, 215, 35, 59, 9, 0, 5, 0,
0, 0, 0, 0, 1, 88, 0, 9, 0, 7, 240, 150, 61, 0,
0, 0, 0, 0, 0, 0, 0, 28, 124, 225, 61, 52, 128, 215,
63, 0, 0, 160, 64, 0, 0, 0, 0, 0, 0, 0, 0, 205,
204, 12, 62, 103, 213, 39, 62, 230, 63, 76, 192, 0, 0, 0,
0, 0, 0, 0, 0, 145, 237, 60, 191, 251, 58, 64, 63, 177,
80, 131, 64, 0, 0, 0, 0, 0, 0, 0, 0, 93, 254, 227,
62, 54, 60, 133, 191, 0, 0, 64, 64, 12, 0, 10, 0, 0,
0, 0, 0, 0, 0, 0, 0, 229, 0, 254, 0, 2, 1, 5,
48, 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0,
92, 4, 144, 1, 64, 1, 64, 1, 144, 1, 48, 117, 48, 117,
48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117,
48, 117, 100, 0, 100, 0, 48, 117, 48, 117, 100, 0, 100, 0,
100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0, 100, 0,
100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 44, 1, 44, 1,
44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1,
44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 8, 7, 8, 7,
8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7,
8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 112, 23, 112, 23,
112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23,
112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, 255, 255,
255, 255, 255, 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, 255,
255, 255, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 0,
0, 0, 83, 141, 0, 0
};
/*!
* @brief Capture the system time in microseconds
*
* @return system_current_time current system timestamp in microseconds
*/
static int64_t get_timestamp_us()
{
int64_t tick = xTaskGetTickCount();
return tick * 1000;
}
/*!
* @brief Handling of the ready outputs
*
* @param[in] timestamp time in nanoseconds
* @param[in] iaq IAQ signal
* @param[in] iaq_accuracy accuracy of IAQ signal
* @param[in] temperature temperature signal
* @param[in] humidity humidity signal
* @param[in] pressure pressure signal
* @param[in] raw_temperature raw temperature signal
* @param[in] raw_humidity raw humidity signal
* @param[in] gas raw gas sensor signal
* @param[in] bsec_status value returned by the bsec_do_steps() call
*
* @return none
*/
static void output_ready(
int64_t timestamp,
float iaq,
uint8_t iaq_accuracy,
float temperature,
float humidity,
float pressure,
float raw_temperature,
float raw_humidity,
float gas,
bsec_library_return_t bsec_status,
float static_iaq,
float co2_equivalent,
float breath_voc_equivalent
) {
last_bsec_data.temperature = temperature;
last_bsec_data.humidity = humidity;
last_bsec_data.pressure = pressure / 100.;
last_bsec_data.gas_resistance = gas;
last_bsec_data.timestamp = timestamp;
last_bsec_data.accuracy = iaq_accuracy;
last_bsec_data.indoor_air_quality = iaq;
last_bsec_data.static_indoor_air_quality = static_iaq;
last_bsec_data.co2_equivalent = co2_equivalent;
last_bsec_data.breath_voc_equivalent = breath_voc_equivalent;
__sync_synchronize();
last_bme680_timestamp = timestamp;
bleESS_update_from_bsec_data(&last_bsec_data);
if (debug) {
LOG_INFO(
"bsec",
"time[ms]: %u, IAQ: %u, IAQ ACC[0-3]: %u, T[.1C]: %u, Hum[.1%%]: %u, P[Pa]: %u, Raw T[.1C]: %u, Raw Hum[.1%%]: %u, Gas[Ohm]: %u, Static IAQ: %u, CO2[ppm]: %u, Breath VOC[ppb]: %u",
(unsigned int)(timestamp / 1e6),
(unsigned int)(iaq),
(unsigned int)(iaq_accuracy),
(unsigned int)(temperature * 10),
(unsigned int)(humidity * 10),
(unsigned int)(pressure),
(unsigned int)(raw_temperature * 10),
(unsigned int)(raw_humidity * 10),
(unsigned int)(gas),
(unsigned int)(static_iaq),
(unsigned int)(co2_equivalent),
(unsigned int)(breath_voc_equivalent * 1e3)
);
}
}
int epic_bsec_read_sensors(struct bsec_sensor_data *data)
{
if (data == NULL) {
return -EFAULT;
}
if (!bsec_task_active) {
return -ENODEV;
}
/* TODO: could also return -EINVAL */
while (last_bme680_timestamp == 0)
vTaskDelay(pdMS_TO_TICKS(10));
*data = last_bsec_data;
return 0;
}
static uint32_t bsec_load(char *path, uint8_t *buffer, uint32_t n_buffer)
{
uint32_t len = 0;
int fd, res;
LOG_DEBUG("bsec", "load %s %lu", path, n_buffer);
if ((fd = epic_file_open(path, "r")) < 0) {
LOG_DEBUG("bsec", "Open failed");
return 0;
}
uint32_t header;
if ((res = epic_file_read(fd, &header, sizeof(header))) !=
sizeof(header)) {
LOG_WARN("bsec", "Header failed");
goto done;
}
if (header > n_buffer) {
LOG_WARN("bsec", "Too large");
goto done;
}
if (epic_file_read(fd, buffer, header) != (int)header) {
LOG_WARN("bsec", "Read failed");
goto done;
}
len = header;
LOG_DEBUG("bsec", "Success");
done:
epic_file_close(fd);
return len;
}
/*!
* @brief Load previous library state from non-volatile memory
*
* @param[in,out] state_buffer buffer to hold the loaded state string
* @param[in] n_buffer size of the allocated state buffer
*
* @return number of bytes copied to state_buffer
*/
static uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer)
{
return bsec_load("bsec_iaq.state", state_buffer, n_buffer);
}
/*!
* @brief Save library state to non-volatile memory
*
* @param[in] state_buffer buffer holding the state to be stored
* @param[in] length length of the state string to be stored
*
* @return none
*/
static void state_save(const uint8_t *state_buffer, uint32_t length)
{
int fd, res;
LOG_DEBUG("bsec", "state_save %d", (int)length);
if ((fd = epic_file_open("bsec_iaq.state", "w")) < 0) {
LOG_WARN("bsec", "Open failed");
return;
}
uint32_t header = length;
if ((res = epic_file_write(fd, &header, sizeof(header))) !=
sizeof(header)) {
LOG_WARN("bsec", "Header failed");
goto done;
}
if (epic_file_write(fd, state_buffer, header) != (int)header) {
LOG_WARN("bsec", "Write failed");
goto done;
}
LOG_DEBUG("bsec", "stack high: %lu", uxTaskGetStackHighWaterMark(NULL));
done:
epic_file_close(fd);
}
/*!
* @brief Delete the library state from non-volatile memory
*
* @return none
*/
static void state_delete(void)
{
LOG_DEBUG("bsec", "state_delete");
epic_file_unlink("bsec_iaq.state");
}
static int8_t
i2c_write(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
{
int8_t ret;
hwlock_acquire(HWLOCK_I2C);
ret = card10_bosch_i2c_write(addr, reg, p_buf, size);
hwlock_release(HWLOCK_I2C);
return ret;
}
static int8_t i2c_read(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
{
int8_t ret;
hwlock_acquire(HWLOCK_I2C);
ret = card10_bosch_i2c_read(addr, reg, p_buf, size);
hwlock_release(HWLOCK_I2C);
return ret;
}
static void delay(uint32_t msec)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
/* We need to fall back to hardware waits if not running
* in a task context */
card10_bosch_delay(msec);
} else {
vTaskDelay(pdMS_TO_TICKS(msec));
}
}
/*!
* @brief Load library config from non-volatile memory
*
* @param[in,out] config_buffer buffer to hold the loaded state string
* @param[in] n_buffer size of the allocated state buffer
*
* @return number of bytes copied to config_buffer
*/
static uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer)
{
uint32_t len = bsec_load("bsec_iaq.config", config_buffer, n_buffer);
if (len == 0) {
LOG_INFO("bsec", "Using default bsec_config_generic_18v_3s_4d");
len = sizeof(bsec_config_generic_18v_3s_4d);
memcpy(config_buffer, bsec_config_generic_18v_3s_4d, len);
}
return len;
}
#if ULP
void ulp_plus_trigger_iaq()
{
/* We call bsec_update_subscription() in order to instruct BSEC to perform an extra measurement at the next
* possible time slot
*/
bsec_sensor_configuration_t requested_virtual_sensors[1];
uint8_t n_requested_virtual_sensors = 1;
bsec_sensor_configuration_t
required_sensor_settings[BSEC_MAX_PHYSICAL_SENSOR];
uint8_t n_required_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR;
bsec_library_return_t status = BSEC_OK;
/* To trigger a ULP plus, we request the IAQ virtual sensor with a specific sample rate code */
requested_virtual_sensors[0].sensor_id = BSEC_OUTPUT_IAQ;
requested_virtual_sensors[0].sample_rate =
BSEC_SAMPLE_RATE_ULP_MEASUREMENT_ON_DEMAND;
/* Call bsec_update_subscription() to enable/disable the requested virtual sensors */
status = bsec_update_subscription(
requested_virtual_sensors,
n_requested_virtual_sensors,
required_sensor_settings,
&n_required_sensor_settings
);
/* The status code would tell is if the request was accepted. It will be rejected if the sensor is not already in
* ULP mode, or if the time difference between requests is too short, for example. */
}
#endif
bool bsec_active(void)
{
return bsec_task_active;
}
int bsec_read_bme680(struct bme680_sensor_data *data)
{
if (!bsec_task_active) {
return BME680_E_COM_FAIL;
}
if (data == NULL) {
return -EFAULT;
}
while (last_bme680_timestamp == 0)
vTaskDelay(pdMS_TO_TICKS(10));
data->temperature = last_bsec_data.temperature;
data->humidity = last_bsec_data.humidity;
data->pressure = last_bsec_data.pressure;
data->gas_resistance = last_bsec_data.gas_resistance;
return BME680_OK;
}
/**
* Checks config and activates the BSEC libary if requested.
*
* Initializes the BSEC library before starting the task to
* reduce the stack size needed for the task by at least 250 bytes
*/
int bsec_activate(void)
{
return_values_init ret;
#if ULP
float sample_rate = BSEC_SAMPLE_RATE_ULP;
#else
float sample_rate = BSEC_SAMPLE_RATE_LP;
#endif
if (!config_get_boolean_with_default("bsec_enable", false)) {
return -1;
}
debug = config_get_boolean_with_default("bsec_debug", false);
float temperature_offset =
config_get_integer_with_default("bsec_offset", -22) / 10.;
if (temperature_offset != 0.0) {
LOG_INFO(
"bsec",
"BSEC Temp offset %d/10 K",
(int)(temperature_offset * 10)
);
}
/* Puts AT LEAST 2 * #BSEC_MAX_PROPERTY_BLOB_SIZE = 2 * 454 = 908 bytes onto the stack */
ret = bsec_iot_init(
sample_rate,
-temperature_offset,
i2c_write,
i2c_read,
delay,
state_load,
config_load
);
if (ret.bsec_status == BSEC_E_CONFIG_VERSIONMISMATCH) {
/* BSEC version changed and old state is not compatible anymore */
/* If the config is also not valid anymore, the user will have
* to fix that. */
state_delete();
ret = bsec_iot_init(
sample_rate,
-temperature_offset,
i2c_write,
i2c_read,
delay,
state_load,
config_load
);
}
if (ret.bme680_status) {
LOG_WARN("bsec", "bme680 init failed: %d", ret.bme680_status);
/* Could not initialize BME680 */
return -1;
} else if (ret.bsec_status) {
LOG_WARN("bsec", "bsec init failed: %d", ret.bsec_status);
/* Could not initialize BSEC library */
return -1;
}
return 0;
}
void vBSECTask(void *pvParameters)
{
bsec_task_active = true;
bsec_task_id = xTaskGetCurrentTaskHandle();
#if ULP
/* State is saved every 100 samples, which means every 100 * 300 secs = 500 minutes */
const int stat_save_interval = 100;
#else
/* State is saved every 10.000 samples, which means every 10.000 * 3 secs = 500 minutes */
const int stat_save_interval = 10000;
#endif
/* Call to endless loop function which reads and processes data based on sensor settings */
/* Puts AT LEAST 2 * BSEC_MAX_STATE_BLOB_SIZE + 8 * sizeof(bsec_input_t) =
* 2 * 139 + 8 * 20 = 438 bytes onto the stack */
bsec_iot_loop(
delay,
get_timestamp_us,
output_ready,
state_save,
stat_save_interval
);
}
#include "epicardium.h"
#include "modules/modules.h"
#include "os/core.h"
#include "portexpander.h"
#include "MAX77650-Arduino-Library.h"
#include <stdint.h>
static const uint8_t pin_mask[] = {
[BUTTON_LEFT_BOTTOM] = 1 << 5,
[BUTTON_RIGHT_BOTTOM] = 1 << 3,
[BUTTON_RIGHT_TOP] = 1 << 6,
};
uint8_t epic_buttons_read(uint8_t mask)
{
uint8_t ret = 0;
hwlock_acquire(HWLOCK_I2C);
if (portexpander_detected() && (mask & 0x7)) {
/*
* Not using PB_Get() here as that performs one I2C transaction
* per button.
*/
uint8_t pin_status = ~portexpander_in_get(0xFF);
for (uint8_t m = 1; m < 0x8; m <<= 1) {
if (mask & m && pin_status & pin_mask[m]) {
ret |= m;
}
}
}
if (mask & BUTTON_RESET && MAX77650_getDebounceStatusnEN0()) {
ret |= BUTTON_RESET;
}
hwlock_release(HWLOCK_I2C);
return ret;
}
#include "epicardium.h"
#include "drivers/display/lcd.h"
#include "drivers/display/epic_ctx.h"
#include "FreeRTOS.h"
#include "LCD_Driver.h"
#include "gpio.h"
#include "task.h"
#include "tmr.h"
#include "tmr_utils.h"
#include <machine/endian.h>
#include <string.h>
static TaskHandle_t lock = NULL;
static int check_lock()
{
TaskHandle_t task = xTaskGetCurrentTaskHandle();
if (task != lock) {
return -EBUSY;
} else {
return 0;
}
}
static uint16_t rgb888_to_rgb565(uint8_t *bytes)
{
return ((bytes[0] & 0b11111000) << 8) | ((bytes[1] & 0b11111100) << 3) |
(bytes[2] >> 3);
}
static inline void
rgb565_to_rgb888(uint16_t pixel, uint8_t *red, uint8_t *green, uint8_t *blue)
{
*blue = (pixel & 31) << 3;
*green = ((pixel >> 5) & 63) << 2;
*red = ((pixel >> 11) & 31) << 3;
}
int epic_disp_print(
int16_t posx,
int16_t posy,
const char *pString,
uint16_t fg,
uint16_t bg
) {
return epic_disp_print_adv(DISP_FONT20, posx, posy, pString, fg, bg);
}
static const float font_map[] = {
[DISP_FONT8] = 8.0f, [DISP_FONT12] = 12.0f, [DISP_FONT16] = 16.0f,
[DISP_FONT20] = 20.0f, [DISP_FONT24] = 24.0f,
};
int epic_disp_print_adv(
uint8_t font,
int16_t posx,
int16_t posy,
const char *pString,
uint16_t fg,
uint16_t bg
) {
uint8_t r, g, b;
int cl = check_lock();
if (cl < 0) {
return cl;
}
if (font >= (sizeof(font_map) / sizeof(font_map[0]))) {
return -EINVAL;
}
float font_size = font_map[font];
ctx_font_size(epicardium_ctx, font_size);
if (fg != bg) {
/* non-transparent background */
rgb565_to_rgb888(bg, &r, &g, &b);
ctx_rgba8(epicardium_ctx, r, g, b, 255);
float width = ctx_text_width(epicardium_ctx, pString);
ctx_rectangle(epicardium_ctx, posx, posy, width, font_size);
ctx_fill(epicardium_ctx);
}
rgb565_to_rgb888(fg, &r, &g, &b);
ctx_rgba8(epicardium_ctx, r, g, b, 255);
ctx_move_to(epicardium_ctx, posx, (float)posy + font_size * 0.8f);
ctx_text(epicardium_ctx, pString);
return 0;
}
int epic_disp_clear(uint16_t color)
{
int cl = check_lock();
if (cl < 0) {
return cl;
}
/*
* We could use ctx for this but it's much easier to just clear the
* framebuffer directly.
*/
for (size_t i = 0; i < sizeof(epicardium_ctx_fb); i += 2) {
epicardium_ctx_fb[i] = color >> 8;
epicardium_ctx_fb[i + 1] = color & 0xff;
}
return 0;
}
int epic_disp_pixel(int16_t x, int16_t y, uint16_t color)
{
int cl = check_lock();
if (cl < 0) {
return cl;
}
uint8_t r, g, b;
rgb565_to_rgb888(color, &r, &g, &b);
ctx_set_pixel_u8(epicardium_ctx, x, y, r, g, b, 255);
return 0;
}
static uint16_t rgb565_pixel_from_buf(
uint8_t *img,
enum epic_rgb_format format,
int16_t width,
int16_t x,
int16_t y,
uint8_t *alpha
) {
uint16_t tmp16;
uint8_t rgba[4];
switch (format) {
case EPIC_RGB565:
*alpha = 255;
memcpy(&tmp16, &img[y * width * 2 + x * 2], 2);
return tmp16;
case EPIC_RGBA5551:
memcpy(&tmp16, &img[y * width * 2 + x * 2], 2);
*alpha = (tmp16 & 0x01) ? 255 : 0;
return (tmp16 & 0xFFC0) | ((tmp16 & 0x3E) >> 1);
case EPIC_RGB8:
*alpha = 255;
memcpy(rgba, &img[y * width * 3 + x * 3], 3);
return rgb888_to_rgb565(rgba);
case EPIC_RGBA8:
memcpy(rgba, &img[y * width * 4 + x * 4], 4);
*alpha = rgba[3];
return rgb888_to_rgb565(rgba);
default:
return 0xFFFF;
}
}
int epic_disp_blit(
int16_t pos_x,
int16_t pos_y,
int16_t width,
int16_t height,
void *img,
enum epic_rgb_format format
) {
int cl = check_lock();
if (cl < 0) {
return cl;
}
for (int16_t xsrc = 0; xsrc < width; xsrc += 1) {
for (int16_t ysrc = 0; ysrc < height; ysrc += 1) {
int16_t xscreen = pos_x + xsrc;
int16_t yscreen = pos_y + ysrc;
size_t offset = yscreen * 160 * 2 + xscreen * 2;
if (xscreen < 0 || xscreen >= 160 || yscreen < 0 ||
yscreen >= 80) {
continue;
}
uint8_t alpha = 255;
uint16_t pixel = rgb565_pixel_from_buf(
img, format, width, xsrc, ysrc, &alpha
);
if (alpha == 0) {
continue;
}
epicardium_ctx_fb[offset] = (pixel & 0xFF00) >> 8;
epicardium_ctx_fb[offset + 1] = pixel & 0xFF;
}
}
return 0;
}
int epic_disp_line(
int16_t xstart,
int16_t ystart,
int16_t xend,
int16_t yend,
uint16_t color,
enum disp_linestyle linestyle,
uint16_t pixelsize
) {
int cl = check_lock();
if (cl < 0) {
return cl;
}
float xstartf = xstart, ystartf = ystart, xendf = xend, yendf = yend;
/*
* For odd line widths, shift the line by half a pixel so it aligns
* perfectly with the pixel grid.
*/
if (pixelsize % 2 == 1) {
xstartf += 0.5f;
ystartf += 0.5f;
yendf += 0.5f;
xendf += 0.5f;
}
uint8_t r, g, b;
rgb565_to_rgb888(color, &r, &g, &b);
ctx_rgba8_stroke(epicardium_ctx, r, g, b, 255);
ctx_line_width(epicardium_ctx, pixelsize);
ctx_move_to(epicardium_ctx, xstartf, ystartf);
ctx_line_to(epicardium_ctx, xendf, yendf);
ctx_stroke(epicardium_ctx);
return 0;
}
int epic_disp_rect(
int16_t xstart,
int16_t ystart,
int16_t xend,
int16_t yend,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize
) {
int cl = check_lock();
if (cl < 0)
return cl;
uint8_t r, g, b;
rgb565_to_rgb888(color, &r, &g, &b);
ctx_rectangle(
epicardium_ctx,
xstart,
ystart,
xend - xstart + 1,
yend - ystart + 1
);
switch (fillstyle) {
case FILLSTYLE_EMPTY:
ctx_rgba8_stroke(epicardium_ctx, r, g, b, 255);
ctx_line_width(epicardium_ctx, pixelsize);
ctx_stroke(epicardium_ctx);
break;
case FILLSTYLE_FILLED:
ctx_rgba8(epicardium_ctx, r, g, b, 255);
ctx_fill(epicardium_ctx);
break;
}
return 0;
}
int epic_disp_circ(
int16_t x,
int16_t y,
uint16_t rad,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize
) {
int cl = check_lock();
if (cl < 0)
return cl;
uint8_t r, g, b;
rgb565_to_rgb888(color, &r, &g, &b);
ctx_arc(epicardium_ctx, x, y, rad, 0.0f, CTX_PI * 1.95, 0);
switch (fillstyle) {
case FILLSTYLE_EMPTY:
ctx_rgba8_stroke(epicardium_ctx, r, g, b, 255);
ctx_line_width(epicardium_ctx, pixelsize);
ctx_stroke(epicardium_ctx);
break;
case FILLSTYLE_FILLED:
ctx_rgba8(epicardium_ctx, r, g, b, 255);
ctx_fill(epicardium_ctx);
break;
}
return 0;
}
int epic_disp_update()
{
int cl = check_lock();
if (cl < 0) {
return cl;
}
lcd_write_fb(epicardium_ctx_fb);
return 0;
}
int epic_disp_framebuffer(union disp_framebuffer *fb)
{
int cl = check_lock();
if (cl < 0) {
return cl;
}
/*
* Flip the screen because that's what this API call historically
* expects.
*/
lcd_set_screenflip(true);
lcd_write_fb(fb->raw);
lcd_set_screenflip(false);
return 0;
}
int epic_disp_backlight(uint16_t brightness)
{
/* TODO: lock? */
if (brightness == 0) {
lcd_set_sleep(true);
} else {
lcd_set_sleep(false);
}
LCD_SetBacklight(brightness);
return 0;
}
int epic_disp_open()
{
TaskHandle_t task = xTaskGetCurrentTaskHandle();
if (lock == task) {
return 0;
} else if (lock == NULL) {
lock = task;
return 0;
} else {
return -EBUSY;
}
}
int epic_disp_close()
{
if (check_lock() < 0 && lock != NULL) {
return -EBUSY;
} else {
lock = NULL;
return 0;
}
}
void disp_update_backlight_clock(void)
{
LCD_UpdateBacklightClock();
}
void disp_forcelock()
{
TaskHandle_t task = xTaskGetCurrentTaskHandle();
lock = task;
}
#pragma once
#include "ctx.h"
extern Ctx *epicardium_ctx;
extern uint8_t epicardium_ctx_fb[160 * 80 * 2];
#include "drivers/display/lcd.h"
#include "drivers/display/epic_ctx.h"
#include "drivers/drivers.h"
#include "display.h"
#include "ctx.h"
#include <stdint.h>
#include <machine/endian.h>
#if BYTE_ORDER == LITTLE_ENDIAN
#define CARD10_CTX_FORMAT CTX_FORMAT_RGB565_BYTESWAPPED
#else
#define CARD10_CTX_FORMAT CTX_FORMAT_RGB565
#endif
uint8_t epicardium_ctx_fb[160 * 80 * 2] = { 0 };
Ctx *epicardium_ctx = NULL;
void disp_init(void)
{
/*
* The bootloader has already initialized the display, so we only need
* to do the bare minimum here.
*/
lcd_reconfigure();
/*
* Initialize the graphics context.
*/
disp_ctx_reinit();
}
void disp_ctx_reinit(void)
{
if (epicardium_ctx != NULL) {
ctx_free(epicardium_ctx);
}
epicardium_ctx = ctx_new_for_framebuffer(
epicardium_ctx_fb, 160, 80, 160 * 2, CARD10_CTX_FORMAT
);
/* set some defaults */
ctx_rgba(epicardium_ctx, 1.0f, 1.0f, 1.0f, 1.0f);
ctx_rgba_stroke(epicardium_ctx, 1.0f, 1.0f, 1.0f, 1.0f);
ctx_font(epicardium_ctx, "ctx-mono");
}
#include "os/core.h"
#include "MAX77650-Arduino-Library.h"
#include "gpio.h"
#include "mxc_delay.h"
#include "portexpander.h"
#include "spi.h"
#include <machine/endian.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* HAL Interfaces {{{ */
static const gpio_cfg_t GPIO_PIN_DC = {
PORT_1, PIN_6, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
static void lcd_hw_init(void)
{
GPIO_Config(&GPIO_PIN_DC);
/* for the reset pin */
if (!portexpander_detected()) {
/* Open-drain */
MAX77650_setDRV(false);
/* Output */
MAX77650_setDIR(false);
}
}
static void lcd_set_dc(bool state)
{
if (state) {
GPIO_OutSet(&GPIO_PIN_DC);
} else {
GPIO_OutClr(&GPIO_PIN_DC);
}
}
static void lcd_set_rst(bool state)
{
if (!portexpander_detected()) {
MAX77650_setDO(state ? true : false);
} else {
portexpander_out_put(PIN_4, state ? 0xFF : 0);
}
}
/** Bit Rate. Display has 15 MHz limit */
#define SPI_SPEED (15 * 1000 * 1000)
static void lcd_spi_write(const uint8_t *data, size_t count)
{
const sys_cfg_spi_t spi_master_cfg = {
.map = MAP_A,
.ss0 = Enable,
.ss1 = Disable,
.ss2 = Disable,
.num_io = 2,
};
spi_req_t request = {
.ssel = 0,
.deass = 1,
.ssel_pol = SPI17Y_POL_LOW,
.tx_data = data,
.rx_data = NULL,
.width = SPI17Y_WIDTH_1,
.len = count,
.bits = 8,
.rx_num = 0,
.tx_num = 0,
.callback = NULL,
};
if (SPI_Init(SPI2, 0, SPI_SPEED, spi_master_cfg) != 0) {
panic("Error configuring display SPI");
}
SPI_MasterTrans(SPI2, &request);
}
static void lcd_delay(size_t millis)
{
// TODO: Is this what we want?
mxc_delay(millis * 1000);
}
/* HAL Interfaces }}} */
enum lcd_commands {
/** Sleep In */
LCD_SLPIN = 0x10,
/** Sleep Out */
LCD_SLPOUT = 0x11,
/** Display Inversion On */
LCD_INVON = 0x21,
/** Display On */
LCD_DISPON = 0x29,
/** Column Address Set */
LCD_CASET = 0x2A,
/** Row Address Set */
LCD_RASET = 0x2B,
/** Memory Write */
LCD_RAMWR = 0x2C,
/** Memory Data Access Control */
LCD_MADCTL = 0x36,
/** Interface Pixel Format */
LCD_COLMOD = 0x3A,
/** Frame Rate Control (In normal mode/ Full colors) */
LCD_FRMCTR1 = 0xB1,
/** Frame Rate Control (In Idle mode/ 8-colors) */
LCD_FRMCTR2 = 0xB2,
/** Frame Rate Control (In Partial mode/ full colors) */
LCD_FRMCTR3 = 0xB3,
/** Display Inversion Control */
LCD_INVCTR = 0xB4,
/** Power Control 1 */
LCD_PWCTR1 = 0xC0,
/** Power Control 2 */
LCD_PWCTR2 = 0xC1,
/** Power Control 3 (in Normal mode/ Full colors) */
LCD_PWCTR3 = 0xC2,
/** Power Control 4 (in Idle mode/ 8-colors) */
LCD_PWCTR4 = 0xC3,
/** Power Control 5 (in Partial mode/ full-colors) */
LCD_PWCTR5 = 0xC4,
/** VCOM Control 1 */
LCD_VMCTR1 = 0xC5,
/** Gamma (+ polarity) Correction Characteristics Setting */
LCD_GMCTRP1 = 0xE0,
/** Gamma (- polarity) Correction Characteristics Setting */
LCD_GMCTRN1 = 0xE1,
};
enum madctl_bits {
MADCTL_MY = 0x80,
MADCTL_MX = 0x40,
MADCTL_MV = 0x20,
MADCTL_ML = 0x10,
MADCTL_RGB = 0x08,
MADCTL_MH = 0x04,
};
static void
lcd_send_command(enum lcd_commands cmd, const uint8_t *args, size_t count)
{
lcd_set_dc(false);
lcd_spi_write((uint8_t *)&cmd, 1);
if (args != NULL && count != 0) {
lcd_set_dc(true);
lcd_spi_write(args, count);
}
}
static void lcd_hard_reset(void)
{
lcd_delay(20);
lcd_set_rst(false);
lcd_delay(20);
lcd_set_rst(true);
lcd_delay(20);
}
void lcd_set_sleep(bool sleep)
{
static int current_sleep = -1;
if (sleep == current_sleep) {
return;
}
current_sleep = sleep;
if (sleep) {
lcd_send_command(LCD_SLPIN, NULL, 0);
} else {
lcd_send_command(LCD_SLPOUT, NULL, 0);
}
}
/**
* Perform a minimal initialization under the assumption that the bootloader has
* already turned on the display. This is faster and prevents visible
* reinitialization artifacts.
*/
void lcd_reconfigure(void)
{
/* Invert Display (twice for unknown reasons ...). */
lcd_send_command(LCD_INVON, NULL, 0);
lcd_send_command(LCD_INVON, NULL, 0);
/* Set framerate control values for all modes to the same values. */
const uint8_t frmctr[] = { 0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A };
lcd_send_command(LCD_FRMCTR1, frmctr, 3);
lcd_send_command(LCD_FRMCTR2, frmctr, 3);
lcd_send_command(LCD_FRMCTR3, frmctr, 6);
/* Set display inversion control (unsure what this does?). */
const uint8_t invctr[] = { 0x03 };
lcd_send_command(LCD_INVCTR, invctr, sizeof(invctr));
/* Configure GVDD voltage to 4.7V. */
const uint8_t pwctr1[] = { 0x62, 0x02, 0x04 };
lcd_send_command(LCD_PWCTR1, pwctr1, sizeof(pwctr1));
/* Configure only ignored bits? */
const uint8_t pwctr2[] = { 0xC0 };
lcd_send_command(LCD_PWCTR2, pwctr2, sizeof(pwctr2));
/*
* Configure "large" amount of current in operational amplifier and
* booster step-ups for all modes.
*/
const uint8_t pwctr3[] = { 0x0D, 0x00 }, pwctr4[] = { 0x8D, 0x6A },
pwctr5[] = { 0x8D, 0xEE };
lcd_send_command(LCD_PWCTR3, pwctr3, sizeof(pwctr3));
lcd_send_command(LCD_PWCTR4, pwctr4, sizeof(pwctr4));
lcd_send_command(LCD_PWCTR5, pwctr5, sizeof(pwctr5));
/* Configure VCOMH voltage to 2.850V. */
const uint8_t vmctr1[] = { 0x0E };
lcd_send_command(LCD_VMCTR1, vmctr1, sizeof(vmctr1));
/* Write positive and negative gamma correction values. */
const uint8_t gmctrp1[] = {
0x10, 0x0E, 0x02, 0x03, 0x0E, 0x07, 0x02, 0x07,
0x0A, 0x12, 0x27, 0x37, 0x00, 0x0D, 0x0E, 0x10,
};
const uint8_t gmctrn1[] = {
0x10, 0x0E, 0x03, 0x03, 0x0F, 0x06, 0x02, 0x08,
0x0A, 0x13, 0x26, 0x36, 0x00, 0x0D, 0x0E, 0x10,
};
lcd_send_command(LCD_GMCTRP1, gmctrp1, sizeof(gmctrp1));
lcd_send_command(LCD_GMCTRN1, gmctrn1, sizeof(gmctrn1));
/* Configure 16-bit pixel format. */
const uint8_t colmod[] = { 0x05 };
lcd_send_command(LCD_COLMOD, colmod, sizeof(colmod));
/*
* Configure "MADCTL", which defines the pixel and color access order.
*/
const uint8_t madctl[] = { MADCTL_MX | MADCTL_MV | MADCTL_RGB };
/*
* Waveshare Driver:
* const uint8_t madctl[] = { MADCTL_MY | MADCTL_MV | MADCTL_RGB };
*/
lcd_send_command(LCD_MADCTL, madctl, sizeof(madctl));
/* Turn the display on. */
lcd_send_command(LCD_DISPON, NULL, 0);
}
/**
* Perform a full initialization of the display. This will ensure the display
* is in a deterministic state.
*/
void lcd_initialize(void)
{
lcd_hw_init();
lcd_hard_reset();
lcd_send_command(LCD_SLPOUT, NULL, 0);
lcd_delay(120);
lcd_reconfigure();
}
/**
* Write a partial display update.
*
* The rectangle from column ``xstart`` to ``xend`` (inclusive) and row
* ``ystart`` to ``yend`` (inclusive) will be updated with the contents of
* ``fb``.
*
* ``fb`` **must** have a size of
* ``(xend - xstart + 1) * (yend - ystart + 1) * 2`` bytes.
*/
void lcd_write_fb_partial(
uint16_t xstart,
uint16_t ystart,
uint16_t xend,
uint16_t yend,
const uint8_t *fb
) {
uint16_t param_buffer[2];
/* Column start and end are offset by 1. */
param_buffer[0] = __htons(xstart + 1);
param_buffer[1] = __htons(xend + 1);
lcd_send_command(
LCD_CASET, (uint8_t *)param_buffer, sizeof(param_buffer)
);
/* Row start and end are offset by a magic 26. */
param_buffer[0] = __htons(ystart + 26);
param_buffer[1] = __htons(yend + 26);
lcd_send_command(
LCD_RASET, (uint8_t *)param_buffer, sizeof(param_buffer)
);
/* Now write out the actual framebuffer contents. */
size_t fb_size = (xend - xstart + 1) * (yend - ystart + 1) * 2;
lcd_send_command(LCD_RAMWR, fb, fb_size);
}
/**
* Write out a full framebuffer update.
*
* ``fb`` **must** be 160 * 80 * 2 = **25600** bytes in size. The pixels are
* ordered in rows starting at the top left of the screen. Each pixel must have
* its bytes laid out as big endian (while the CPU is little endian!).
*/
void lcd_write_fb(const uint8_t *fb)
{
lcd_write_fb_partial(0, 0, 159, 79, fb);
}
/**
* Flip the screen orientation upside down.
*
* Historically we had software perform a flip of the framebuffer before
* sending it out. This function provides a way to make the hardware accept
* such a flipped framebuffer. This exists mostly to support the
* :c:func:`epic_disp_framebuffer()` API call for legacy l0dables.
*/
void lcd_set_screenflip(bool flipped)
{
const uint8_t madctl_upright[] = { MADCTL_MX | MADCTL_MV | MADCTL_RGB };
const uint8_t madctl_flipped[] = { MADCTL_MY | MADCTL_MV | MADCTL_RGB };
if (flipped) {
lcd_send_command(LCD_MADCTL, madctl_flipped, 1);
} else {
lcd_send_command(LCD_MADCTL, madctl_upright, 1);
}
}
#pragma once
#include <stdbool.h>
#include <stdint.h>
void lcd_set_sleep(bool sleep);
void lcd_reconfigure(void);
void lcd_initialize(void);
void lcd_write_fb_partial(
uint16_t xstart,
uint16_t ystart,
uint16_t xend,
uint16_t yend,
const uint8_t *fb
);
void lcd_write_fb(const uint8_t *fb);
void lcd_set_screenflip(bool flipped);
#ifndef DRIVERS_H
#define DRIVERS_H
#include "FreeRTOS.h"
#include "gpio.h"
#include "os/mutex.h"
#include "epicardium.h"
#include <stdint.h>
#include <stdbool.h>
/* ---------- Serial ------------------------------------------------------- */
#define SERIAL_READ_BUFFER_SIZE 128
#define SERIAL_WRITE_STREAM_BUFFER_SIZE 512
void serial_init();
void vSerialTask(void *pvParameters);
void serial_enqueue_char(char chr);
void serial_flush(void);
extern TaskHandle_t serial_task_id;
/* Turn off the print queue and do prints synchroneous from now on. */
void serial_return_to_synchronous();
// For the eSetBit xTaskNotify task semaphore trigger
enum serial_notify{
SERIAL_WRITE_NOTIFY = 0x01,
SERIAL_READ_NOTIFY = 0x02,
};
/* ---------- PMIC --------------------------------------------------------- */
void vPmicTask(void *pvParameters);
/* ---------- Watchdog ----------------------------------------------------- */
void watchdog_init();
void watchdog_clearer_init();
/* Critical battery voltage */
#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,
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);
/* ---------- Display ------------------------------------------------------ */
/* Do display and graphics initialization/configuration. */
void disp_init(void);
/* Reinitialize the graphics context. */
void disp_ctx_reinit(void);
/* Forces an unlock of the display. Only to be used in Epicardium */
void disp_forcelock();
void disp_update_backlight_clock(void);
/* ---------- BHI160 ------------------------------------------------------- */
#define BHI160_FIFO_SIZE 128
#define BHI160_MUTEX_WAIT_MS 50
void vBhi160Task(void *pvParameters);
/* ---------- BME680 ------------------------------------------------------- */
void bme680_periodic(int period);
/* ---------- MAX86150 ----------------------------------------------------- */
#define MAX86150_MUTEX_WAIT_MS 50
void vMAX86150Task(void *pvParameters);
void max86150_mutex_init(void);
/* ---------- MAX30001 ----------------------------------------------------- */
void vMAX30001Task(void *pvParameters);
void max30001_mutex_init(void);
/* ---------- GPIO --------------------------------------------------------- */
extern gpio_cfg_t gpio_configs[];
/* ---------- BSEC / BME680 ------------------------------------------------ */
int bsec_activate(void);
void vBSECTask(void *pvParameters);
bool bsec_active(void);
struct bme680_sensor_data;
int bsec_read_bme680(struct bme680_sensor_data *data);
/* ---------- Sleep -------------------------------------------------------- */
void sleep_deepsleep(void);
/* ---------- RNG ---------------------------------------------------------- */
void rng_init(void);
#endif /* DRIVERS_H */
#include "epicardium.h"
#include "gpio.h"
#include "max32665.h"
#include "mxc_sys.h"
#include "adc.h"
#include "mxc_errors.h"
#include "os/core.h"
#include "modules/modules.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)
*/
gpio_cfg_t gpio_configs[] = {
[EPIC_GPIO_WRISTBAND_1] = { PORT_0,
PIN_21,
GPIO_FUNC_OUT,
GPIO_PAD_NONE },
[EPIC_GPIO_WRISTBAND_2] = { PORT_0,
PIN_22,
GPIO_FUNC_OUT,
GPIO_PAD_NONE },
[EPIC_GPIO_WRISTBAND_3] = { PORT_0,
PIN_29,
GPIO_FUNC_OUT,
GPIO_PAD_NONE },
[EPIC_GPIO_WRISTBAND_4] = { PORT_0,
PIN_20,
GPIO_FUNC_OUT,
GPIO_PAD_NONE },
};
static int s_adc_channels[] = {
[EPIC_GPIO_WRISTBAND_1] = ADC_CH_5,
[EPIC_GPIO_WRISTBAND_2] = ADC_CH_6,
/* on P0.29, there is no ADC available
* see GPIO matrix in MAX32665-MAX32668.pdf,
* pages 32,33
*/
[EPIC_GPIO_WRISTBAND_3] = -1,
[EPIC_GPIO_WRISTBAND_4] = ADC_CH_4,
};
int epic_gpio_set_pin_mode(uint8_t pin, uint8_t mode)
{
if (pin < EPIC_GPIO_WRISTBAND_1 || pin > EPIC_GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
if (mode & EPIC_GPIO_MODE_ADC) {
if (s_adc_channels[pin] == -1) {
LOG_WARN("gpio", "ADC not available on pin %d", pin);
return -EINVAL;
}
cfg->func = GPIO_FUNC_ALT1;
if (mode & EPIC_GPIO_MODE_OUT) {
return -EINVAL;
}
} else if (mode & EPIC_GPIO_MODE_IN) {
cfg->func = GPIO_FUNC_IN;
if (mode & EPIC_GPIO_MODE_OUT) {
return -EINVAL;
}
} else if (mode & EPIC_GPIO_MODE_OUT) {
cfg->func = GPIO_FUNC_OUT;
if (mode & EPIC_GPIO_MODE_IN) {
return -EINVAL;
}
} else {
return -EINVAL;
}
if (!(mode & EPIC_GPIO_MODE_ADC)) {
if (mode & EPIC_GPIO_PULL_UP) {
cfg->pad = GPIO_PAD_PULL_UP;
} else if (mode & EPIC_GPIO_PULL_DOWN) {
cfg->pad = GPIO_PAD_PULL_DOWN;
} else {
cfg->pad = GPIO_PAD_NONE;
}
} else {
cfg->pad = GPIO_PAD_NONE;
}
if (GPIO_Config(cfg) != E_NO_ERROR)
return -EINVAL;
return 0;
}
int epic_gpio_get_pin_mode(uint8_t pin)
{
if (pin < EPIC_GPIO_WRISTBAND_1 || pin > EPIC_GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
int res = 0;
if (cfg->func == GPIO_FUNC_IN)
res |= EPIC_GPIO_MODE_IN;
else if (cfg->func == GPIO_FUNC_OUT)
res |= EPIC_GPIO_MODE_OUT;
else if (cfg->func == GPIO_FUNC_ALT1)
res |= EPIC_GPIO_MODE_ADC;
if (cfg->pad == GPIO_PAD_PULL_UP)
res |= EPIC_GPIO_PULL_UP;
else if (cfg->pad == GPIO_PAD_PULL_DOWN)
res |= EPIC_GPIO_PULL_DOWN;
return res;
}
int epic_gpio_write_pin(uint8_t pin, bool on)
{
if (pin < EPIC_GPIO_WRISTBAND_1 || pin > EPIC_GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
if (cfg->func == GPIO_FUNC_IN)
return -EINVAL;
if (on)
GPIO_OutSet(cfg);
else
GPIO_OutClr(cfg);
return 0;
}
int epic_gpio_read_pin(uint8_t pin)
{
if (pin < EPIC_GPIO_WRISTBAND_1 || pin > EPIC_GPIO_WRISTBAND_4)
return -EINVAL;
gpio_cfg_t *cfg = &gpio_configs[pin];
if (cfg->func == GPIO_FUNC_OUT) {
return GPIO_OutGet(cfg) != 0;
} else if (cfg->func == GPIO_FUNC_IN) {
return GPIO_InGet(cfg) != 0;
} else if (cfg->func == GPIO_FUNC_ALT1) {
hwlock_acquire(HWLOCK_ADC);
ADC_StartConvert(s_adc_channels[pin], 0, 0);
uint16_t value;
int rc = ADC_GetData(&value);
hwlock_release(HWLOCK_ADC);
if (rc < 0) {
return -EIO;
}
return (int)value;
} else {
return -EINVAL;
}
}
#include "leds.h"
#include "pmic.h"
#include "FreeRTOS.h"
#include "timers.h"
#include "task.h"
#include "epicardium.h"
#include "modules/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(void)
{
hwlock_acquire(HWLOCK_LED);
hwlock_acquire(HWLOCK_I2C);
leds_update_power();
leds_update();
hwlock_release(HWLOCK_I2C);
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);
}
int epic_leds_get_rgb(int led, uint8_t *rgb)
{
if (led == PERSONAL_STATE_LED && personal_state_enabled())
return -EPERM;
if (led < 0 || led >= NUM_LEDS)
return -EINVAL;
leds_get_rgb(led, rgb);
return 0;
}
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) {
do_update();
}
}
void epic_leds_dim_bottom(uint8_t value)
{
leds_set_dim_bottom(value);
if (personal_state_enabled() == 0) {
do_update();
}
}
void epic_leds_set_rocket(int led, uint8_t value)
{
hwlock_acquire(HWLOCK_I2C);
pmic_set_led(led, value > 31 ? 31 : value);
hwlock_release(HWLOCK_I2C);
}
int epic_leds_get_rocket(int led)
{
int ret = 0;
hwlock_acquire(HWLOCK_I2C);
ret = pmic_get_led(led);
hwlock_release(HWLOCK_I2C);
return ret;
}
static StaticTimer_t flash_timer_data[3];
static TimerHandle_t flash_timer[] = { NULL, NULL, NULL };
static void rocket_timer_callback(TimerHandle_t flash_timer)
{
uint32_t id = (uint32_t)pvTimerGetTimerID(flash_timer);
epic_leds_set_rocket(id, 0);
}
void epic_leds_flash_rocket(int led, uint8_t value, int millis)
{
int ticks = millis * (configTICK_RATE_HZ / 1000);
int32_t id = led;
if (flash_timer[id] == NULL) {
flash_timer[id] = xTimerCreateStatic(
"flashtimer",
ticks,
pdFALSE,
(void *)id,
rocket_timer_callback,
&flash_timer_data[id]
);
epic_leds_set_rocket(led, value);
}
epic_leds_set_rocket(led, value);
xTimerChangePeriod(flash_timer[id], ticks, 0);
}
void epic_set_flashlight(bool power)
{
hwlock_acquire(HWLOCK_I2C);
leds_flashlight(power);
hwlock_release(HWLOCK_I2C);
}
void epic_leds_update(void)
{
do_update();
}
void epic_leds_set_powersave(bool eco)
{
hwlock_acquire(HWLOCK_I2C);
leds_powersave(eco);
hwlock_release(HWLOCK_I2C);
}
void epic_leds_set_gamma_table(uint8_t rgb_channel, uint8_t *gamma_table)
{
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();
}
#include "FreeRTOS.h" #include "epicardium.h"
#include "timers.h" #include "os/core.h"
#include "led.h" #include "os/work_queue.h"
#include "modules/modules.h"
#include "mxc_config.h" #include "mxc_config.h"
#include "led.h"
#include "adc.h" #include "adc.h"
#include "gpio.h" #include "gpio.h"
#include <errno.h>
#include "FreeRTOS.h"
#include "timers.h"
#define READ_FREQ pdMS_TO_TICKS(100) #define READ_FREQ pdMS_TO_TICKS(100)
...@@ -12,7 +17,7 @@ static uint16_t last_value; ...@@ -12,7 +17,7 @@ static uint16_t last_value;
static TimerHandle_t poll_timer; static TimerHandle_t poll_timer;
static StaticTimer_t poll_timer_buffer; static StaticTimer_t poll_timer_buffer;
int epic_light_sensor_init() static int light_sensor_init()
{ {
const sys_cfg_adc_t sys_adc_cfg = const sys_cfg_adc_t sys_adc_cfg =
NULL; /* No system specific configuration needed. */ NULL; /* No system specific configuration needed. */
...@@ -23,15 +28,34 @@ int epic_light_sensor_init() ...@@ -23,15 +28,34 @@ int epic_light_sensor_init()
return 0; return 0;
} }
void readAdcCallback() uint16_t epic_light_sensor_read()
{ {
hwlock_acquire(HWLOCK_ADC);
ADC_StartConvert(ADC_CH_7, 0, 0); ADC_StartConvert(ADC_CH_7, 0, 0);
ADC_GetData(&last_value); ADC_GetData(&last_value);
hwlock_release(HWLOCK_ADC);
return last_value;
}
static void workpoll(void *data)
{
epic_light_sensor_read();
}
static void poll(TimerHandle_t xTimer)
{
workqueue_schedule(workpoll, NULL);
} }
int epic_light_sensor_run() int epic_light_sensor_run()
{ {
epic_light_sensor_init(); int ret = 0;
hwlock_acquire(HWLOCK_ADC);
light_sensor_init();
if (!poll_timer) { if (!poll_timer) {
poll_timer = xTimerCreateStatic( poll_timer = xTimerCreateStatic(
...@@ -39,7 +63,7 @@ int epic_light_sensor_run() ...@@ -39,7 +63,7 @@ int epic_light_sensor_run()
READ_FREQ, READ_FREQ,
pdTRUE, pdTRUE,
NULL, NULL,
readAdcCallback, poll,
&poll_timer_buffer &poll_timer_buffer
); );
// since &poll_timer_buffer is not NULL, xTimerCreateStatic should allways succeed, so // since &poll_timer_buffer is not NULL, xTimerCreateStatic should allways succeed, so
...@@ -47,10 +71,12 @@ int epic_light_sensor_run() ...@@ -47,10 +71,12 @@ int epic_light_sensor_run()
} }
if (xTimerIsTimerActive(poll_timer) == pdFALSE) { if (xTimerIsTimerActive(poll_timer) == pdFALSE) {
if (xTimerStart(poll_timer, 0) != pdPASS) { if (xTimerStart(poll_timer, 0) != pdPASS) {
return -EBUSY; ret = -EBUSY;
} }
} }
return 0;
hwlock_release(HWLOCK_ADC);
return ret;
} }
int epic_light_sensor_stop() int epic_light_sensor_stop()
...@@ -63,6 +89,7 @@ int epic_light_sensor_stop() ...@@ -63,6 +89,7 @@ int epic_light_sensor_stop()
if (xTimerStop(poll_timer, 0) != pdPASS) { if (xTimerStop(poll_timer, 0) != pdPASS) {
return -EBUSY; return -EBUSY;
} }
return 0; return 0;
} }
......
#include <stdio.h>
#include <string.h>
#include "gpio.h"
#include "pmic.h"
#include "spi.h"
#include "MAX30003.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "epicardium.h"
#include "os/core.h"
#include "modules/modules.h"
#include "modules/stream.h"
#include "os/mutex.h"
#include "user_core/interrupts.h"
/* Interrupt Pin */
static const gpio_cfg_t max30001_interrupt_pin = {
PORT_1, PIN_12, GPIO_FUNC_IN, GPIO_PAD_PULL_UP
};
static const gpio_cfg_t analog_switch = {
PORT_0, PIN_31, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
/* clang-format on */
/* MAX30001 Task ID */
static TaskHandle_t max30001_task_id = NULL;
/* MAX30001 Mutex */
static struct mutex max30001_mutex = { 0 };
/* Stream */
static struct stream_info max30001_stream;
;
/* Active */
static bool max30001_sensor_active = false;
static int ecg_enable(int sample_rate, bool enable_internal_pull);
static int ecg_disable(void);
/* -- API -------------------------------------------------------------- {{{ */
int epic_max30001_enable_sensor(struct max30001_sensor_config *config)
{
int result = 0;
mutex_lock(&max30001_mutex);
hwlock_acquire(HWLOCK_SPI_ECG);
struct stream_info *stream = &max30001_stream;
;
stream->item_size = sizeof(uint16_t);
stream->queue =
xQueueCreate(config->sample_buffer_len, stream->item_size);
if (stream->queue == NULL) {
result = -ENOMEM;
goto out_free_both;
}
result = stream_register(SD_MAX30001_ECG, stream);
if (result < 0) {
vQueueDelete(stream->queue);
goto out_free_both;
}
result = ecg_enable(config->sample_rate, config->bias);
if (result < 0) {
vQueueDelete(stream->queue);
goto out_free_both;
}
if (config->usb) {
GPIO_OutSet(&analog_switch); // USB
} else {
GPIO_OutClr(&analog_switch); // Wrist
}
max30001_sensor_active = true;
result = SD_MAX30001_ECG;
out_free_both:
hwlock_release(HWLOCK_SPI_ECG);
mutex_unlock(&max30001_mutex);
return result;
}
int epic_max30001_disable_sensor(void)
{
int result = 0;
mutex_lock(&max30001_mutex);
hwlock_acquire(HWLOCK_SPI_ECG);
struct stream_info *stream = &max30001_stream;
result = stream_deregister(SD_MAX30001_ECG, stream);
if (result < 0) {
goto out_free_both;
}
vQueueDelete(stream->queue);
stream->queue = NULL;
result = ecg_disable();
if (result < 0) {
goto out_free_both;
}
max30001_sensor_active = false;
result = 0;
out_free_both:
hwlock_release(HWLOCK_SPI_ECG);
mutex_unlock(&max30001_mutex);
return result;
}
/* }}} */
/* -- Driver ----------------------------------------------------------- {{{ */
/*
* Handle a single packet from the FIFO. For most sensors this means pushing
* the sample into its sample queue.
*/
static void max30001_handle_samples(int16_t *sensor_data, int16_t n)
{
if (max30001_stream.queue == NULL) {
return;
}
while (n--) {
uint16_t data = -*sensor_data++;
/* Discard overflow. See discussion in !316. */
if (xQueueSend(max30001_stream.queue, &data, 0) != pdTRUE) {
if (!max30001_stream.was_full) {
LOG_WARN("max30001", "queue full");
}
max30001_stream.was_full = true;
} else {
max30001_stream.was_full = false;
}
}
interrupt_trigger(EPIC_INT_MAX30001_ECG);
}
/***** Functions *****/
static uint32_t ecg_read_reg(uint8_t reg)
{
spi_req_t req;
uint8_t tx_data[] = { (reg << 1) | 1, 0, 0, 0 };
uint8_t rx_data[] = { 0, 0, 0, 0 };
req.tx_data = tx_data;
req.rx_data = rx_data;
req.len = 4;
req.bits = 8;
req.width = SPI17Y_WIDTH_1;
req.ssel = 0;
req.deass = 1;
req.ssel_pol = SPI17Y_POL_LOW;
req.tx_num = 0;
req.rx_num = 0;
SPI_MasterTrans(SPI0, &req);
return (rx_data[1] << 16) | (rx_data[2] << 8) | rx_data[3];
}
static void ecg_write_reg(uint8_t reg, uint32_t data)
{
//printf("write %02x %06lx\n", reg, data);
spi_req_t req;
uint8_t tx_data[] = {
(reg << 1) | 0, data >> 16, (data >> 8) & 0xFF, data & 0xFF
};
uint8_t rx_data[] = { 0, 0, 0, 0 };
req.tx_data = tx_data;
req.rx_data = rx_data;
req.len = 4;
req.bits = 8;
req.width = SPI17Y_WIDTH_1;
req.ssel = 0;
req.deass = 1;
req.ssel_pol = SPI17Y_POL_LOW;
req.tx_num = 0;
req.rx_num = 0;
SPI_MasterTrans(SPI0, &req);
}
static int ecg_enable(int sample_rate, bool enable_internal_pull)
{
// Reset ECG to clear registers
ecg_write_reg(SW_RST, 0);
// General config register setting
union GeneralConfiguration_u CNFG_GEN_r;
CNFG_GEN_r.bits.en_ecg = 1; // Enable ECG channel
if (enable_internal_pull) {
CNFG_GEN_r.bits.rbiasn =
1; // Enable resistive bias on negative input
CNFG_GEN_r.bits.rbiasp =
1; // Enable resistive bias on positive input
CNFG_GEN_r.bits.en_rbias = 1; // Enable resistive bias
} else {
CNFG_GEN_r.bits.rbiasn =
0; // Enable resistive bias on negative input
CNFG_GEN_r.bits.rbiasp =
0; // Enable resistive bias on positive input
CNFG_GEN_r.bits.en_rbias = 0; // Enable resistive bias
}
CNFG_GEN_r.bits.imag = 2; // Current magnitude = 10nA
CNFG_GEN_r.bits.en_dcloff = 1; // Enable DC lead-off detection
ecg_write_reg(CNFG_GEN, CNFG_GEN_r.all);
// ECG Config register setting
union ECGConfiguration_u CNFG_ECG_r;
CNFG_ECG_r.bits.dlpf = 1; // Digital LPF cutoff = 40Hz
CNFG_ECG_r.bits.dhpf = 1; // Digital HPF cutoff = 0.5Hz
//CNFG_ECG_r.bits.gain = 3; // ECG gain = 160V/V
CNFG_ECG_r.bits.gain = 0;
if (sample_rate == 128) {
CNFG_ECG_r.bits.rate = 2; // Sample rate = 128 sps
} else if (sample_rate == 256) {
CNFG_ECG_r.bits.rate = 1; // Sample rate = 256 sps
} else {
return -EINVAL;
}
ecg_write_reg(CNFG_ECG, CNFG_ECG_r.all);
//R-to-R configuration
union RtoR1Configuration_u CNFG_RTOR_r;
CNFG_RTOR_r.bits.en_rtor = 1; // Enable R-to-R detection
ecg_write_reg(CNFG_RTOR1, CNFG_RTOR_r.all);
//Manage interrupts register setting
union ManageInterrupts_u MNG_INT_r;
MNG_INT_r.bits.efit = 0b00011; // Assert EINT w/ 4 unread samples
MNG_INT_r.bits.clr_rrint = 0b01; // Clear R-to-R on RTOR reg. read back
ecg_write_reg(MNGR_INT, MNG_INT_r.all);
//Enable interrupts register setting
union EnableInterrupts_u EN_INT_r;
EN_INT_r.all = 0;
EN_INT_r.bits.en_eint = 1; // Enable EINT interrupt
EN_INT_r.bits.en_rrint = 0; // Disable R-to-R interrupt
EN_INT_r.bits.intb_type = 3; // Open-drain NMOS with internal pullup
ecg_write_reg(EN_INT, EN_INT_r.all);
//Dyanmic modes config
union ManageDynamicModes_u MNG_DYN_r;
MNG_DYN_r.bits.fast = 0; // Fast recovery mode disabled
ecg_write_reg(MNGR_DYN, MNG_DYN_r.all);
// MUX Config
union MuxConfiguration_u CNFG_MUX_r;
CNFG_MUX_r.bits.openn = 0; // Connect ECGN to AFE channel
CNFG_MUX_r.bits.openp = 0; // Connect ECGP to AFE channel
ecg_write_reg(CNFG_EMUX, CNFG_MUX_r.all);
ecg_write_reg(SYNCH, 0);
return 0;
}
static int ecg_disable(void)
{
// TODO
return 0;
}
/*
* Fetch all data available from FIFO buffer and handle all data
* contained in it.
*/
static int max30001_fetch_fifo(void)
{
int result = 0;
mutex_lock(&max30001_mutex);
hwlock_acquire(HWLOCK_SPI_ECG);
uint32_t ecgFIFO, readECGSamples, ETAG[32], status;
int16_t ecgSample[32];
const uint32_t EINT_STATUS_MASK = 1 << 23;
const uint32_t FIFO_OVF_MASK = 0x7;
const uint32_t FIFO_VALID_SAMPLE_MASK = 0x0;
const uint32_t FIFO_FAST_SAMPLE_MASK = 0x1;
const uint32_t ETAG_BITS_MASK = 0x7;
status = ecg_read_reg(STATUS); // Read the STATUS register
// Check if EINT interrupt asserted
if ((status & EINT_STATUS_MASK) == EINT_STATUS_MASK) {
readECGSamples = 0; // Reset sample counter
do {
ecgFIFO = ecg_read_reg(ECG_FIFO); // Read FIFO
ecgSample[readECGSamples] =
ecgFIFO >> 8; // Isolate voltage data
ETAG[readECGSamples] =
(ecgFIFO >> 3) & ETAG_BITS_MASK; // Isolate ETAG
readECGSamples++; // Increment sample counter
// Check that sample is not last sample in FIFO
} while (ETAG[readECGSamples - 1] == FIFO_VALID_SAMPLE_MASK ||
ETAG[readECGSamples - 1] == FIFO_FAST_SAMPLE_MASK);
// Check if FIFO has overflowed
if (ETAG[readECGSamples - 1] == FIFO_OVF_MASK) {
ecg_write_reg(FIFO_RST, 0); // Reset FIFO
LOG_WARN(
"max30001",
"fifo overflow"); // TODO; handle fifo full
}
max30001_handle_samples(ecgSample, readECGSamples);
}
hwlock_release(HWLOCK_SPI_ECG);
mutex_unlock(&max30001_mutex);
return result;
}
/*
* Callback for the MAX30001 interrupt pin. This callback is called from the
* SDK's GPIO interrupt driver, in interrupt context.
*/
static void max300001_interrupt_callback(void *_)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (max30001_task_id != NULL) {
vTaskNotifyGiveFromISR(
max30001_task_id, &xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
/* }}} */
void max30001_mutex_init(void)
{
mutex_create(&max30001_mutex);
}
void vMAX30001Task(void *pvParameters)
{
max30001_task_id = xTaskGetCurrentTaskHandle();
mutex_lock(&max30001_mutex);
hwlock_acquire(HWLOCK_SPI_ECG);
/* Install interrupt callback */
GPIO_Config(&max30001_interrupt_pin);
GPIO_RegisterCallback(
&max30001_interrupt_pin, max300001_interrupt_callback, NULL
);
GPIO_IntConfig(
&max30001_interrupt_pin, GPIO_INT_EDGE, GPIO_INT_FALLING
);
GPIO_IntEnable(&max30001_interrupt_pin);
NVIC_SetPriority(
(IRQn_Type)MXC_GPIO_GET_IRQ(max30001_interrupt_pin.port), 2
);
NVIC_EnableIRQ(
(IRQn_Type)MXC_GPIO_GET_IRQ(max30001_interrupt_pin.port)
);
GPIO_Config(&analog_switch);
GPIO_OutClr(&analog_switch); // Wrist
hwlock_release(HWLOCK_SPI_ECG);
mutex_unlock(&max30001_mutex);
/* ----------------------------------------- */
while (1) {
if (max30001_sensor_active) {
int ret = max30001_fetch_fifo();
if (ret < 0) {
LOG_ERR("max30001", "Unknown error: %d", -ret);
}
}
/*
* Wait for interrupt. After two seconds, fetch FIFO anyway
*
* In the future, reads using epic_stream_read() might also
* trigger a FIFO fetch, from outside this task.
*/
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2000));
}
}
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include "max86150.h"
#include "epicardium.h"
#include "os/core.h"
#include "modules/stream.h"
#include "gpio.h"
#include "pmic.h"
#include "user_core/interrupts.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "modules/modules.h"
static const gpio_cfg_t max86150_interrupt_pin = {
PORT_1, PIN_13, GPIO_FUNC_IN, GPIO_PAD_PULL_UP
};
/* MAX86150 Task ID */
static TaskHandle_t max86150_task_id = NULL;
/* MAX86150 Mutex */
static struct mutex max86150_mutex = { 0 };
/* Stream */
static struct stream_info max86150_stream;
/* Active */
static bool max86150_sensor_active = false;
int epic_max86150_enable_sensor(
struct max86150_sensor_config *config, size_t config_size
) {
int result = 0;
if (sizeof(struct max86150_sensor_config) != config_size) {
return -EINVAL;
}
mutex_lock(&max86150_mutex);
hwlock_acquire(HWLOCK_I2C);
struct stream_info *stream = &max86150_stream;
stream->item_size = sizeof(struct max86150_sensor_data);
stream->queue =
xQueueCreate(config->sample_buffer_len, stream->item_size);
if (stream->queue == NULL) {
result = -ENOMEM;
goto out_free;
}
uint8_t ppg_sample_rate;
if (config->ppg_sample_rate == 10) {
ppg_sample_rate = MAX86150_PPG_SAMPLERATE_10;
} else if (config->ppg_sample_rate == 20) {
ppg_sample_rate = MAX86150_PPG_SAMPLERATE_20;
} else if (config->ppg_sample_rate == 50) {
ppg_sample_rate = MAX86150_PPG_SAMPLERATE_50;
} else if (config->ppg_sample_rate == 84) {
ppg_sample_rate = MAX86150_PPG_SAMPLERATE_84;
} else if (config->ppg_sample_rate == 100) {
ppg_sample_rate = MAX86150_PPG_SAMPLERATE_100;
} else if (config->ppg_sample_rate == 200) {
ppg_sample_rate = MAX86150_PPG_SAMPLERATE_200;
} else if (config->ppg_sample_rate == 400) {
ppg_sample_rate = MAX86150_PPG_SAMPLERATE_400;
} else {
result = -EINVAL;
goto out_free;
}
result = stream_register(SD_MAX86150, stream);
if (result < 0) {
vQueueDelete(stream->queue);
goto out_free;
}
bool begin_result = max86150_begin();
if (!begin_result) {
result = -ENODEV;
vQueueDelete(stream->queue);
goto out_free;
}
max86150_setup(ppg_sample_rate);
max86150_get_int1();
max86150_get_int2();
max86150_sensor_active = true;
result = SD_MAX86150;
out_free:
hwlock_release(HWLOCK_I2C);
mutex_unlock(&max86150_mutex);
return result;
}
int epic_max86150_disable_sensor(void)
{
int result = 0;
mutex_lock(&max86150_mutex);
hwlock_acquire(HWLOCK_I2C);
max86150_shut_down();
max86150_sensor_active = false;
struct stream_info *stream = &max86150_stream;
result = stream_deregister(SD_MAX86150, stream);
if (result == 0) {
vQueueDelete(stream->queue);
stream->queue = NULL;
}
hwlock_release(HWLOCK_I2C);
mutex_unlock(&max86150_mutex);
return result;
}
static int max86150_handle_sample(struct max86150_sensor_data *data)
{
//LOG_INFO("max86150", "Sample! %ld, %ld, %ld", data->red, data->ir, data->ecg);
if (max86150_stream.queue == NULL) {
return -ESRCH;
}
/* Discard overflow. See discussion in !316. */
if (xQueueSend(max86150_stream.queue, data, 0) != pdTRUE) {
if (!max86150_stream.was_full) {
LOG_WARN("max86150", "queue full");
}
max86150_stream.was_full = true;
} else {
max86150_stream.was_full = false;
}
return 0;
}
static int max86150_fetch_fifo(void)
{
int result = 0;
mutex_lock(&max86150_mutex);
hwlock_acquire(HWLOCK_I2C);
struct max86150_sensor_data sample;
// There is a recommendation from Maxim not to read the entire FIFO, but rather a fixed number of samples.
// See https://os.mbed.com/users/laserdad/code/MAX86150_ECG_PPG//file/3c728f3d1f10/main.cpp/
// So we should not use max86150_check() but max86150_get_sample().
int n = 0;
while (max86150_get_sample(&sample.red, &sample.ir, &sample.ecg) > 0) {
n++;
result = max86150_handle_sample(&sample);
// stop in case of errors
if (result < 0) {
n = 0;
break;
}
}
hwlock_release(HWLOCK_I2C);
mutex_unlock(&max86150_mutex);
if (n > 0) {
interrupt_trigger(EPIC_INT_MAX86150);
}
//LOG_INFO("max86150", "%d", n);
return result;
}
/*
* Callback for the MAX86150 interrupt pin. This callback is called from the
* SDK's GPIO interrupt driver, in interrupt context.
*/
static void max86150_interrupt_callback(void *_)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (max86150_task_id != NULL) {
vTaskNotifyGiveFromISR(
max86150_task_id, &xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
/* }}} */
void max86150_mutex_init(void)
{
mutex_create(&max86150_mutex);
}
void vMAX86150Task(void *pvParameters)
{
max86150_task_id = xTaskGetCurrentTaskHandle();
mutex_lock(&max86150_mutex);
hwlock_acquire(HWLOCK_I2C);
/* Install interrupt callback */
GPIO_Config(&max86150_interrupt_pin);
GPIO_RegisterCallback(
&max86150_interrupt_pin, max86150_interrupt_callback, NULL
);
GPIO_IntConfig(
&max86150_interrupt_pin, GPIO_INT_EDGE, GPIO_INT_FALLING
);
GPIO_IntEnable(&max86150_interrupt_pin);
NVIC_SetPriority(
(IRQn_Type)MXC_GPIO_GET_IRQ(max86150_interrupt_pin.port), 2
);
NVIC_EnableIRQ(
(IRQn_Type)MXC_GPIO_GET_IRQ(max86150_interrupt_pin.port)
);
hwlock_release(HWLOCK_I2C);
mutex_unlock(&max86150_mutex);
/* ----------------------------------------- */
while (1) {
if (max86150_sensor_active) {
//LOG_INFO("max86150", "Interrupt!");
mutex_lock(&max86150_mutex);
hwlock_acquire(HWLOCK_I2C);
int i1 = max86150_get_int1();
hwlock_release(HWLOCK_I2C);
mutex_unlock(&max86150_mutex);
//LOG_INFO("max86150", "%d", i1);
if (i1 & 16) {
interrupt_trigger(EPIC_INT_MAX86150_PROX);
}
int ret = max86150_fetch_fifo();
if (ret < 0) {
LOG_ERR("max86150", "Unknown error: %d", -ret);
}
}
/*
* Wait for interrupt. After two seconds, fetch FIFO anyway
*
* In the future, reads using epic_stream_read() might also
* trigger a FIFO fetch, from outside this task.
*/
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2000));
}
}
driver_sources = files(
'bhi.c',
'bsec.c',
'bme680.c',
'buttons.c',
'gpio.c',
'leds.c',
'light_sensor.c',
'max86150.c',
'max30001.c',
'pmic.c',
'rtc.c',
'serial.c',
'sleep.c',
'rng.c',
'usb.c',
'vibra.c',
'watchdog.c',
'ws2812.c',
'display/lcd.c',
'display/api.c',
'display/init.c',
)
#include "epicardium.h"
#include "modules/modules.h"
#include "drivers/drivers.h"
#include "os/core.h"
#include "os/config.h"
#include "user_core/user_core.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>
#include <string.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;
}
hwlock_acquire(HWLOCK_ADC);
hwlock_acquire(HWLOCK_I2C);
/* 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));
hwlock_acquire(HWLOCK_I2C);
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(PMIC_AMUX_DISABLED);
/* 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 uint8_t pmic_poll_interrupts(void)
{
hwlock_acquire(HWLOCK_I2C);
uint8_t int_flag = MAX77650_getINT_GLBL();
hwlock_release(HWLOCK_I2C);
/* 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);
}
return int_flag;
}
__attribute__((noreturn)) static void pmic_die(float u_batt)
{
/* Stop core 1 */
core1_stop();
/* Grab the screen */
disp_forcelock();
/* Turn it on in case it was off */
epic_disp_backlight(100);
/* 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();
/* We have some of headroom to keep the RTC going.
* The battery protection circuit will shut down
* the system at 3.0 V */
/* TODO: Wake-up when USB is attached again */
sleep_deepsleep();
card10_reset();
}
/*
* Check the battery voltage. If it drops too low, turn card10 off.
*/
static void pmic_check_battery()
{
float u_batt;
int res;
/**
* 0 = uncertain, ask config
* 1 = disabled
* 2 = enabled
*/
static int pmic_do_battery_check = 0;
if (pmic_do_battery_check == 0) {
if (config_get_boolean_with_default("battery_check", true)) {
pmic_do_battery_check = 2;
} else {
pmic_do_battery_check = 1;
LOG_WARN(
"pmic",
"Battery check was disabled by config!"
);
}
}
if (pmic_do_battery_check == 1) {
/* Disabled, ignore */
return;
}
res = pmic_read_amux(PMIC_AMUX_BATT_U, &u_batt);
if (res < 0) {
LOG_ERR("pmic",
"Failed reading battery voltage: %s (%d)",
strerror(-res),
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);
}
/*
* API-call for battery current
*/
int epic_read_battery_current(float *result)
{
return pmic_read_amux(PMIC_AMUX_BATT_CHG_I, result);
}
/*
* API-call for charge voltage
*/
int epic_read_chargein_voltage(float *result)
{
return pmic_read_amux(PMIC_AMUX_CHGIN_U, result);
}
/*
* API-call for charge voltage
*/
int epic_read_chargein_current(float *result)
{
return pmic_read_amux(PMIC_AMUX_BATT_CHG_I, result);
}
/*
* API-call for system voltage
*/
int epic_read_system_voltage(float *result)
{
return pmic_read_amux(PMIC_AMUX_SYS_U, result);
}
/*
* API-call for thermistor voltage
*
* Thermistor is as 10k at room temperature,
* voltage divided with another 10k.
* (50% V_bias at room temperature)
*/
int epic_read_thermistor_voltage(float *result)
{
return pmic_read_amux(PMIC_AMUX_THM_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();
uint8_t interrupts = 0;
uint32_t reason = 0;
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);
/* Clear all pending interrupts. */
pmic_poll_interrupts();
while (1) {
interrupts |= pmic_poll_interrupts();
if (interrupts == 0) {
reason |= ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
/* New interrupts */
if (reason & PMIC_NOTIFY_IRQ) {
reason ^= PMIC_NOTIFY_IRQ;
interrupts |= pmic_poll_interrupts();
}
if (interrupts & MAX77650_INT_nEN_R) {
/* Ignored in this state */
/* This can happen if the button is pressed
* during boot and released now. */
interrupts ^= MAX77650_INT_nEN_R; /* Mark as handled. */
}
if (interrupts & MAX77650_INT_nEN_F) {
/* Button was pressed */
interrupts ^= MAX77650_INT_nEN_F; /* Mark as handled. */
button_start_tick = xTaskGetTickCount();
while (true) {
TickType_t duration =
xTaskGetTickCount() - button_start_tick;
if (duration > pdMS_TO_TICKS(1000)) {
disp_forcelock();
disp_ctx_reinit();
/* Turn it on in case it was off */
epic_disp_backlight(100);
epic_disp_clear(0x0000);
char buf[20];
sprintf(buf,
"Off in %d",
7 - (int)(duration + 500) /
1000);
epic_disp_print(
0,
0,
"Sleep zZz..",
0xffff,
0x0000
);
epic_disp_print(
0, 25, buf, 0xf000, 0x0000
);
epic_disp_print(
0,
50,
" Reset ->",
0xffff,
0x0000
);
epic_disp_update();
}
if (duration >= pdMS_TO_TICKS(1000)) {
if (epic_buttons_read(
BUTTON_RIGHT_TOP)) {
disp_forcelock();
disp_ctx_reinit();
/* Turn it on in case it was off */
epic_disp_backlight(100);
epic_disp_clear(0x0000);
epic_disp_print(
55,
30,
"Reset!",
0xf800,
0x0000
);
epic_disp_update();
serial_return_to_synchronous();
LOG_WARN(
"pmic",
"Resetting ..."
);
card10_reset();
}
}
if (interrupts & MAX77650_INT_nEN_R) {
/* Button is released */
interrupts ^=
MAX77650_INT_nEN_R; /* Mark as handled. */
if (duration < pdMS_TO_TICKS(1000)) {
return_to_menu();
}
if (duration > pdMS_TO_TICKS(1000)) {
serial_return_to_synchronous();
LOG_WARN("pmic", "Poweroff");
sleep_deepsleep();
card10_reset();
}
break;
}
reason |= ulTaskNotifyTake(
pdTRUE, pdMS_TO_TICKS(200)
);
if (reason & PMIC_NOTIFY_IRQ) {
/* New interrupts */
reason ^= PMIC_NOTIFY_IRQ;
interrupts |= pmic_poll_interrupts();
}
}
}
if (reason & PMIC_NOTIFY_MONITOR) {
reason ^= PMIC_NOTIFY_MONITOR;
pmic_check_battery();
}
}
}
#include "epicardium.h"
#include "modules/modules.h"
#include "drivers/drivers.h"
#include "MAX77650-Arduino-Library.h"
#include "tiny-AES-c/aes.h"
#include "SHA256/mark2/sha256.h"
#include "mxc_sys.h"
#include "adc.h"
#include "mxc_delay.h"
#include "rtc.h"
#include "trng.h"
#include <string.h>
static struct AES_ctx aes_ctx;
int epic_trng_read(uint8_t *dest, size_t size)
{
if (dest == NULL)
return -EFAULT;
TRNG_Init(NULL);
TRNG_Read(MXC_TRNG, dest, size);
return 0;
}
int epic_csprng_read(uint8_t *dest, size_t size)
{
if (size >= AES_BLOCKLEN) {
int block_count = size / AES_BLOCKLEN;
AES_CTR_xcrypt_buffer(
&aes_ctx, dest, block_count * AES_BLOCKLEN
);
size -= block_count * AES_BLOCKLEN;
dest += block_count * AES_BLOCKLEN;
}
if (size > 0) {
uint8_t out[AES_BLOCKLEN];
AES_CTR_xcrypt_buffer(&aes_ctx, out, sizeof(out));
memcpy(dest, out, size);
}
return 0;
}
void rng_init(void)
{
uint8_t key[AES_BLOCKLEN];
uint8_t iv[AES_BLOCKLEN];
uint8_t hash[32];
sha256_context ctx;
int i;
sha256_init(&ctx);
/* Seed from TRNG.
* Takes about 10 ms. */
for (i = 0; i < 256; i++) {
uint8_t entropy[AES_BLOCKLEN];
epic_trng_read(entropy, AES_BLOCKLEN);
sha256_hash(&ctx, entropy, AES_BLOCKLEN);
}
// Seed from RTC
uint32_t sec, subsec;
while (RTC_GetTime(&sec, &subsec) == E_BUSY) {
mxc_delay(4000);
}
sha256_hash(&ctx, &sec, sizeof(sec));
sha256_hash(&ctx, &subsec, sizeof(subsec));
// Seed from SysTick
uint32_t systick = SysTick->VAL;
sha256_hash(&ctx, &systick, sizeof(systick));
/* Seed from ADC.
* Takes about 50 ms */
ADC_Init(0x9, NULL);
GPIO_Config(&gpio_cfg_adc0);
MAX77650_setMUX_SEL(PMIC_AMUX_BATT_U);
for (i = 0; i < 256; i++) {
uint16_t adc_data;
ADC_StartConvert(ADC_CH_0, 0, 0);
ADC_GetData(&adc_data);
sha256_hash(&ctx, &adc_data, sizeof(adc_data));
}
MAX77650_setMUX_SEL(PMIC_AMUX_DISABLED);
sha256_done(&ctx, hash);
memcpy(key, hash, AES_BLOCKLEN);
memcpy(iv, hash + AES_BLOCKLEN, AES_BLOCKLEN);
AES_init_ctx_iv(&aes_ctx, key, iv);
}
#include "epicardium.h"
#include "os/core.h"
#include "modules/modules.h"
#include "user_core/interrupts.h"
#include "FreeRTOS.h"
#include "task.h"
#include "rtc.h"
#include <stdint.h>
uint64_t monotonic_offset = 0;
uint32_t epic_rtc_get_monotonic_seconds(void)
{
return epic_rtc_get_seconds() + monotonic_offset / 1000ULL;
}
uint64_t epic_rtc_get_monotonic_milliseconds(void)
{
return epic_rtc_get_milliseconds() + monotonic_offset;
}
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;
}
uint64_t epic_rtc_get_milliseconds(void)
{
uint32_t sec, subsec;
while (RTC_GetTime(&sec, &subsec) == E_BUSY) {
vTaskDelay(pdMS_TO_TICKS(4));
}
// Without the bias of 999 (0.24 milliseconds), this decoding function is
// numerically unstable:
//
// Encoding 5 milliseconds into 20 subsecs (using the encoding function in
// epic_rtc_set_milliseconds) and decoding it without the bias of 999 yields
// 4 milliseconds.
//
// The following invariants should hold when encoding / decoding from and to
// milliseconds / subseconds:
//
// - 0 <= encode(ms) < 4096 for 0 <= ms < 1000
// - decode(encode(ms)) == ms for 0 <= ms < 1000
// - 0 <= decode(subsec) < 1000 for 0 <= subsec < 4096
//
// These invariants were proven experimentally.
return (subsec * 1000ULL + 999ULL) / 4096 + sec * 1000ULL;
}
void epic_rtc_set_milliseconds(uint64_t milliseconds)
{
uint32_t sec, subsec;
uint64_t old_milliseconds, diff;
old_milliseconds = epic_rtc_get_milliseconds();
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)
;
diff = old_milliseconds - milliseconds;
monotonic_offset += diff;
}
/* We need to use interrupt_trigger_unsafe() here */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
void RTC_IRQHandler(void)
{
int flags = RTC_GetFlags();
if (flags & MXC_F_RTC_CTRL_ALDF) {
RTC_ClearFlags(MXC_F_RTC_CTRL_ALDF);
interrupt_trigger_unsafe(EPIC_INT_RTC_ALARM);
} else {
LOG_WARN("rtc", "Unknown IRQ caught!");
/* Disable IRQ so it does not retrigger */
NVIC_DisableIRQ(RTC_IRQn);
}
}
#pragma GCC diagnostic pop
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) {
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 "os/core.h"
#include "modules/modules.h"
#include "drivers/drivers.h"
#include "user_core/interrupts.h"
#include "max32665.h"
#include "usb/cdcacm.h"
#include "uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "stream_buffer.h"
#include <stdint.h>
#include <stdio.h>
/* The serial console in use (UART0) */
extern mxc_uart_regs_t *ConsoleUart;
/* Task ID for the serial handler */
TaskHandle_t serial_task_id = NULL;
/* Read queue, filled by both UART and CDCACM */
static QueueHandle_t read_queue;
/* Stream Buffer for handling all writes to serial */
static StreamBufferHandle_t write_stream_buffer = NULL;
void serial_init()
{
/* Setup read queue */
static uint8_t buffer[sizeof(char) * SERIAL_READ_BUFFER_SIZE];
static StaticQueue_t read_queue_data;
read_queue = xQueueCreateStatic(
SERIAL_READ_BUFFER_SIZE, sizeof(char), buffer, &read_queue_data
);
/* Setup write queue */
static uint8_t ucWrite_stream_buffer[SERIAL_WRITE_STREAM_BUFFER_SIZE];
static StaticStreamBuffer_t xStream_buffer_struct;
write_stream_buffer = xStreamBufferCreateStatic(
sizeof(ucWrite_stream_buffer),
1,
ucWrite_stream_buffer,
&xStream_buffer_struct
);
}
void serial_return_to_synchronous()
{
write_stream_buffer = NULL;
}
static void write_str_(const char *str, size_t length)
{
if (length == 0) {
return;
}
/*
* Check if the stream buffer is even initialized yet
*/
if (write_stream_buffer == NULL) {
UART_Write(ConsoleUart, (uint8_t *)str, length);
cdcacm_write((uint8_t *)str, length);
return;
}
if (xPortIsInsideInterrupt()) {
BaseType_t resched1 = pdFALSE;
BaseType_t resched2 = pdFALSE;
/*
* Enter a critial section so no other task can write to the
* stream buffer.
*/
uint32_t basepri = __get_BASEPRI();
taskENTER_CRITICAL_FROM_ISR();
xStreamBufferSendFromISR(
write_stream_buffer, str, length, &resched1
);
taskEXIT_CRITICAL_FROM_ISR(basepri);
if (serial_task_id != NULL) {
xTaskNotifyFromISR(
serial_task_id,
SERIAL_WRITE_NOTIFY,
eSetBits,
&resched2
);
}
/* Yield if this write woke up a higher priority task */
portYIELD_FROM_ISR(resched1 || resched2);
} else {
size_t bytes_sent = 0;
size_t index = 0;
do {
taskENTER_CRITICAL();
/*
* Wait time needs to be zero, because we are in a
* critical section.
*/
bytes_sent = xStreamBufferSend(
write_stream_buffer,
&str[index],
length - index,
0
);
index += bytes_sent;
taskEXIT_CRITICAL();
if (serial_task_id != NULL) {
xTaskNotify(
serial_task_id,
SERIAL_WRITE_NOTIFY,
eSetBits
);
if (bytes_sent == 0) {
vTaskDelay(1);
} else {
portYIELD();
}
}
} while (index < length);
}
}
/*
* API-call to write a string. Output goes to CDCACM, UART and BLE
*
* This is user data from core 1.
*/
void epic_uart_write_str(const char *str, size_t length)
{
/* Make sure that we are not in an interrupt when talking to BLE.
* Should not be the case if this is called from core 1
* anyways. */
if (!xPortIsInsideInterrupt()) {
ble_uart_write((uint8_t *)str, length);
}
write_str_(str, length);
}
static void serial_flush_from_isr(void)
{
uint8_t rx_data[32];
size_t received_bytes;
BaseType_t resched = pdFALSE;
BaseType_t woken = pdFALSE;
uint32_t basepri = __get_BASEPRI();
taskENTER_CRITICAL_FROM_ISR();
do {
received_bytes = xStreamBufferReceiveFromISR(
write_stream_buffer,
(void *)rx_data,
sizeof(rx_data),
&woken
);
resched |= woken;
if (received_bytes == 0) {
break;
}
/*
* The SDK-driver for UART is not reentrant
* which means we need to perform UART writes
* in a critical section.
*/
UART_Write(ConsoleUart, (uint8_t *)&rx_data, received_bytes);
} while (received_bytes > 0);
taskEXIT_CRITICAL_FROM_ISR(basepri);
portYIELD_FROM_ISR(resched);
}
static void serial_flush_from_thread(void)
{
uint8_t rx_data[32];
size_t received_bytes;
do {
taskENTER_CRITICAL();
received_bytes = xStreamBufferReceive(
write_stream_buffer,
(void *)rx_data,
sizeof(rx_data),
0
);
taskEXIT_CRITICAL();
if (received_bytes == 0) {
break;
}
/*
* The SDK-driver for UART is not reentrant
* which means we need to perform UART writes
* in a critical section.
*/
taskENTER_CRITICAL();
UART_Write(ConsoleUart, (uint8_t *)&rx_data, received_bytes);
taskEXIT_CRITICAL();
cdcacm_write((uint8_t *)&rx_data, received_bytes);
} while (received_bytes > 0);
}
/*
* Flush all characters which are currently waiting to be printed.
*
* If this function is called from an ISR, it will only flush to hardware UART
* while a call from thread mode will flush to UART, CDC-ACM, and BLE Serial.
*/
void serial_flush(void)
{
if (xPortIsInsideInterrupt()) {
serial_flush_from_isr();
} else {
serial_flush_from_thread();
}
}
/*
* 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;
}
/*
* Write a string from epicardium. Output goes to CDCACM and UART
*
* This mainly log data from epicardium, not user date from core 1.
*/
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') {
write_str_(&buf[last], i - last);
write_str_("\r", 1);
last = i;
}
}
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;
xTaskNotifyFromISR(
serial_task_id,
SERIAL_READ_NOTIFY,
eSetBits,
&xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void serial_enqueue_char(char chr)
{
if (chr == 0x3) {
/* Control-C */
interrupt_trigger(EPIC_INT_CTRL_C);
}
if (xQueueSend(read_queue, &chr, 100) == errQUEUE_FULL) {
/* Queue overran, wait a bit */
vTaskDelay(portTICK_PERIOD_MS * 50);
}
interrupt_trigger(EPIC_INT_UART_RX);
}
void vSerialTask(void *pvParameters)
{
serial_task_id = xTaskGetCurrentTaskHandle();
/* 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);
}
ret = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (ret & SERIAL_WRITE_NOTIFY) {
serial_flush_from_thread();
}
if (ret & SERIAL_READ_NOTIFY) {
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 "epicardium.h"
#include "modules/modules.h"
#include "os/core.h"
#include "card10.h"
#include "simo.h"
#include "lp.h"
#include "max86150.h"
#include "MAX77650-Arduino-Library.h"
#include "bb_drv.h"
#include "max32665.h"
#include "mxc_sys.h"
#include "mxc_pins.h"
#include <stdint.h>
#include <limits.h>
/* Most code is taken and adapted rom EvKitExamples/LP/main.c */
static uint32_t old_clkcn;
static uint32_t old_perckcn0, old_perckcn1;
void GPIOWAKE_IRQHandler(void)
{
/* Nothing to do here */
}
static void switchToHIRC(void)
{
MXC_GCR->clkcn &= ~(MXC_S_GCR_CLKCN_PSC_DIV128);
MXC_GCR->clkcn |= MXC_S_GCR_CLKCN_PSC_DIV4;
MXC_GCR->clkcn |= MXC_F_GCR_CLKCN_HIRC_EN;
MXC_SETFIELD(
MXC_GCR->clkcn,
MXC_F_GCR_CLKCN_CLKSEL,
MXC_S_GCR_CLKCN_CLKSEL_HIRC
);
while (!(MXC_GCR->clkcn & MXC_F_GCR_CLKCN_CKRDY))
; /* Wait for the clock switch to occur */
/* Disable the now unused 96 MHz clock */
MXC_GCR->clkcn &= ~(MXC_F_GCR_CLKCN_HIRC96M_EN);
SystemCoreClockUpdate();
}
static void deepsleep(void)
{
SIMO_setVregO_B(810); /* Reduce VCOREB to 0.81 V */
LP_FastWakeupEnable();
LP_EnterDeepSleepMode();
SIMO_setVregO_B(1000); /* Restore VCOREB to 1 V */
}
static void turnOffClocks(void)
{
old_perckcn0 = MXC_GCR->perckcn0;
old_perckcn1 = MXC_GCR->perckcn1;
/* Allow the USB Switch to be turned off in deepsleep and backup modes. */
/* TODO: should this be the default setting? */
LP_USBSWLPDisable();
/* Disable all peripheral clocks except GPIO0 (needed for interrupt wakeup) */
MXC_GCR->perckcn0 = 0xFFFFFFFE;
MXC_GCR->perckcn1 = 0xFFFFFFFF;
}
static void turnOnClocks(void)
{
MXC_GCR->perckcn0 = old_perckcn0;
MXC_GCR->perckcn1 = old_perckcn1;
}
/*
* Move most GPIOs into a special low power state with the fact
* in mind that the external 3.3 V are switched off.
*
* E.g. this means that the SD card pins need to be pulled low
* to preven them from backfeeding into the 3.3 V rail via their
* external pull-up resistors.
*
* Pins needed to talk to the PMIC are left untouched.
* ECG AOUT and 32 kHz out as well.
*/
static void gpio_low_power(void)
{
/* clang-format off */
const gpio_cfg_t pins_low_power[] = {
{ PORT_0, PIN_0, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // FLash
{ PORT_0, PIN_1, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash
{ PORT_0, PIN_2, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash
{ PORT_0, PIN_3, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash
{ PORT_0, PIN_4, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash
{ PORT_0, PIN_5, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Flash
{ PORT_0, PIN_6, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // I2C 3.3V
{ PORT_0, PIN_7, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // I2C 3.3V
{ PORT_0, PIN_8, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // Motor
{ PORT_0, PIN_9, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // UART TX
{ PORT_0, PIN_10, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // UART RX
{ PORT_0, PIN_11, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // BMA400 interrupt
// 0, 12: PMIC IRQ
{ PORT_0, PIN_13, GPIO_FUNC_IN, GPIO_PAD_NONE }, // BHI160 interrupt, not sure if PP
// 0, 14: PMIC I2C
// 0, 15: PMIC I2C
{ PORT_0, PIN_16, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // PMIC AMUX
{ PORT_0, PIN_17, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // SOA GPIO
// 0, 18: ECG AOUT
// 0, 19: 32 kHz
{ PORT_0, PIN_20, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 1
{ PORT_0, PIN_21, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 2
{ PORT_0, PIN_22, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 3
{ PORT_0, PIN_23, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // IR-LED
{ PORT_0, PIN_24, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display SS
{ PORT_0, PIN_25, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display MOSI
{ PORT_0, PIN_26, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // SOA GPIO
{ PORT_0, PIN_27, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display SCK
{ PORT_0, PIN_28, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // Backlight
{ PORT_0, PIN_29, GPIO_FUNC_IN, GPIO_PAD_PULL_DOWN }, // Wristband 4
// 0, 30: PMIC power hold
{ PORT_0, PIN_31, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // ECG switch
{ PORT_1, PIN_0, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC
{ PORT_1, PIN_1, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC
{ PORT_1, PIN_2, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC
{ PORT_1, PIN_3, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC
{ PORT_1, PIN_4, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC
{ PORT_1, PIN_5, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // SDHC
{ PORT_1, PIN_6, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // display RS
{ PORT_1, PIN_7, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // Portexpander interrupt
{ PORT_1, PIN_8, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG CS TODO: better OUT high
{ PORT_1, PIN_9, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // ECG SDI
{ PORT_1, PIN_10, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG SDO
{ PORT_1, PIN_11, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG SCK
{ PORT_1, PIN_11, GPIO_FUNC_IN, GPIO_PAD_PULL_UP }, // ECG INT
{ PORT_1, PIN_13, GPIO_FUNC_IN, GPIO_PAD_NONE }, // PPG Interrupt
{ PORT_1, PIN_14, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // RGB LEDs
{ PORT_1, PIN_15, GPIO_FUNC_OUT, GPIO_PAD_NONE }, // RGB LEDs
};
/* clang-format on */
const unsigned int num_pins =
(sizeof(pins_low_power) / sizeof(gpio_cfg_t));
for (size_t i = 0; i < num_pins; i++) {
GPIO_OutClr(&pins_low_power[i]);
GPIO_Config(&pins_low_power[i]);
}
}
/*
* Go to deepsleep, turning off peripherals.
*
* This functions returns after waking up again.
* Currently the only wakeup source is an interrupt
* from the PMIC.
*
* Some peripherals and GPIOs are moved into a low
* power state before going to sleep. There is no guarantee
* that all peripherals will be moved to a low power state
*
* TODO: Move BHI160, BMA400, BME680, MAX30001 into a low
* power state.
*
* The state of the GPIOs and generally all peripherials
* on board is not restored after a wakeup.
*/
void sleep_deepsleep(void)
{
LOG_WARN("pmic", "Powersave");
epic_disp_backlight(0);
epic_leds_set_rocket(0, 0);
epic_leds_set_rocket(1, 0);
epic_leds_set_rocket(2, 0);
#if 1
/* This will fail if there is no
* harmonic board attached */
max86150_begin();
max86150_get_int1();
max86150_get_int2();
max86150_shut_down();
#endif
epic_bhi160_disable_all_sensors();
epic_bme680_deinit();
epic_max30001_disable_sensor();
MAX77650_setEN_SBB2(0b100);
MAX77650_setSBIA_LPM(true);
core1_stop();
MAX77650_getINT_GLBL();
gpio_low_power();
if (ble_is_enabled()) {
BbDrvDisable();
}
turnOffClocks();
SYS_ClockSourceEnable(SYS_CLOCK_HIRC);
old_clkcn = MXC_GCR->clkcn;
switchToHIRC();
deepsleep();
/* Now wait for an interrupt to wake us up */
turnOnClocks();
MXC_GCR->clkcn = old_clkcn;
while (!(MXC_GCR->clkcn & MXC_F_GCR_CLKCN_CKRDY))
; /* Wait for the clock switch to occur */
SystemCoreClockUpdate();
MAX77650_setEN_SBB2(0b110);
}
int epic_sleep(uint32_t ms)
{
/* Allow the interrupt module to break us out of a call to
* epic_sleep() */
uint32_t count = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(ms));
if (count == 0) {
return 0;
} else {
return INT_MAX;
}
}