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
  • add_menu_vibration
  • blinkisync-as-preload
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • ch3/splashscreen
  • dualcore
  • dx/flatten-config-module
  • dx/meh-bdf-to-stm
  • freertos-btle
  • genofire/ble-follow-py
  • koalo/bhi160-works-but-dirty
  • koalo/factory-reset
  • koalo/wip/i2c-for-python
  • master
  • msgctl/faultscreen
  • msgctl/textbuffer_api
  • plaetzchen/ios-workaround
  • rahix/bhi
  • rahix/bluetooth-app-favorite
  • rahix/bma
  • rahix/user-space-ctx
  • renze/hatchery_apps
  • renze/safe_mode
  • schleicher-test
  • schneider/212-reset-hardware-when-entering-repl
  • schneider/ancs
  • schneider/ble-buffers
  • schneider/ble-central
  • schneider/ble-ecg-stream-visu
  • schneider/ble-fixes-2020-3
  • schneider/ble-mini-demo
  • schneider/ble-stability
  • schneider/ble-stability-new-phy
  • schneider/bonding
  • schneider/bonding-fail-if-full
  • schneider/bootloader-update-9a0d158
  • schneider/deepsleep
  • schneider/deepsleep2
  • schneider/deepsleep4
  • schneider/default-main
  • schneider/freertos-list-debug
  • schneider/fundamental-test
  • schneider/iaq-python
  • schneider/ir
  • schneider/max30001
  • schneider/max30001-epicaridum
  • schneider/max30001-pycardium
  • schneider/maxim-sdk-update
  • schneider/mp-exception-print
  • schneider/mp-for-old-bl
  • schneider/png
  • schneider/schleicher-test
  • schneider/sdk-0.2.1-11
  • schneider/sdk-0.2.1-7
  • schneider/sleep-display
  • schneider/spo2-playground
  • schneider/stream-locks
  • schneider/v1.17-changelog
  • bootloader-v1
  • release-1
  • v0.0
  • v1.0
  • v1.1
  • v1.10
  • v1.11
  • v1.12
  • v1.13
  • v1.14
  • v1.15
  • v1.16
  • v1.17
  • v1.18
  • v1.2
  • v1.3
  • v1.4
  • v1.5
  • v1.6
  • v1.7
  • v1.8
  • v1.9
82 results

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
  • dualcore
  • fix-nix
  • master
  • rahix/freertos10
  • rahix/new-build
  • unjailbreak
6 results
Show changes
Showing
with 4152 additions and 10 deletions
/* clang-format off */
/*******************************************************************************
* Copyright (C) 2017 Maxim Integrated Products, Inc., All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Maxim Integrated
* Products, Inc. shall not be used except as stated in the Maxim Integrated
* Products, Inc. Branding Policy.
*
* The mere transfer of this software does not imply any licenses
* of trade secrets, proprietary technology, copyrights, patents,
* trademarks, maskwork rights, or any other form of intellectual
* property whatsoever. Maxim Integrated Products, Inc. retains all
* ownership rights.
*******************************************************************************
*/
#ifndef _MAX30003_H_
#define _MAX30003_H_
///MAX30003 Registers
enum Registers_e
{
NO_OP = 0x00,
STATUS = 0x01,
EN_INT = 0x02,
EN_INT2 = 0x03,
MNGR_INT = 0x04,
MNGR_DYN = 0x05,
SW_RST = 0x08,
SYNCH = 0x09,
FIFO_RST = 0x0A,
INFO = 0x0F,
CNFG_GEN = 0x10,
CNFG_ALL = 0x12,
CNFG_EMUX = 0x14,
CNFG_ECG = 0x15,
CNFG_RTOR1 = 0x1D,
CNFG_RTOR2 = 0x1E,
ECG_FIFO_BURST = 0x20,
ECG_FIFO = 0x21,
RTOR = 0x25,
NO_OP2 = 0x7F
};
///Status register bits
union Status_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t loff_nl : 1;
uint32_t loff_nh : 1;
uint32_t loff_pl : 1;
uint32_t loff_ph : 1;
uint32_t reserved1 : 4;
uint32_t pllint : 1;
uint32_t samp : 1;
uint32_t rrint : 1;
uint32_t lonint : 1;
uint32_t reserved2 : 8;
uint32_t dcloffint : 1;
uint32_t fstint : 1;
uint32_t eovf : 1;
uint32_t eint : 1;
uint32_t reserved3 : 8;
}bits;
};
///Enable Interrupt registers bits
union EnableInterrupts_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t intb_type : 2;
uint32_t reserved1 : 6;
uint32_t en_pllint : 1;
uint32_t en_samp : 1;
uint32_t en_rrint : 1;
uint32_t en_loint : 1;
uint32_t reserved2 : 8;
uint32_t en_dcloffint : 1;
uint32_t en_fstint : 1;
uint32_t en_eovf : 1;
uint32_t en_eint : 1;
uint32_t reserved3 : 8;
}bits;
};
///Manage Interrupt register bits
union ManageInterrupts_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t samp_it : 4;
uint32_t clr_samp : 1;
uint32_t reserved1 : 1;
uint32_t clr_rrint : 2;
uint32_t clr_fast : 1;
uint32_t reserved2 : 12;
uint32_t efit : 5;
uint32_t reserved3 : 8;
}bits;
};
///Manage Dynamic Modes register bits
union ManageDynamicModes_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t reserved1 : 16;
uint32_t fast_th : 6;
uint32_t fast : 2;
uint32_t reserved2 : 8;
}bits;
};
///General Configuration bits
union GeneralConfiguration_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t rbiasn : 1;
uint32_t rbiasp : 1;
uint32_t rbiasv : 2;
uint32_t en_rbias : 2;
uint32_t vth : 2;
uint32_t imag : 3;
uint32_t ipol : 1;
uint32_t en_dcloff : 2;
uint32_t reserved1 : 5;
uint32_t en_ecg : 1;
uint32_t fmstr : 2;
uint32_t en_ulp_lon : 2;
uint32_t reserved2 : 8;
}bits;
};
///Cal Configuration bits
union CalConfiguration_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t thigh : 11;
uint32_t fifty : 1;
uint32_t fcal : 3;
uint32_t reserved1 : 5;
uint32_t vmag : 1;
uint32_t vmode : 1;
uint32_t en_vcal : 1;
uint32_t reserved2 : 9;
}bits;
};
///Mux Configuration bits
union MuxConfiguration_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t reserved1 : 16;
uint32_t caln_sel : 2;
uint32_t calp_sel : 2;
uint32_t openn : 1;
uint32_t openp : 1;
uint32_t reserved2 : 1;
uint32_t pol : 1;
uint32_t reserved3 : 8;
}bits;
};
///ECG Configuration bits
union ECGConfiguration_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t reserved1 : 12;
uint32_t dlpf : 2;
uint32_t dhpf : 1;
uint32_t reserved2 : 1;
uint32_t gain : 2;
uint32_t reserved3 : 4;
uint32_t rate : 2;
uint32_t reserved4 : 8;
}bits;
};
///RtoR1 Configuration bits
union RtoR1Configuration_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t reserved1 : 8;
uint32_t ptsf : 4;
uint32_t pavg : 2;
uint32_t reserved2 : 1;
uint32_t en_rtor : 1;
uint32_t rgain : 4;
uint32_t wndw : 4;
uint32_t reserved3 : 8;
}bits;
};
///RtoR2 Configuration bits
union RtoR2Configuration_u
{
///Access all bits
uint32_t all;
///Access individual bits
struct
{
uint32_t reserved1 : 8;
uint32_t rhsf : 3;
uint32_t reserved2 : 1;
uint32_t ravg : 2;
uint32_t reserved3 : 2;
uint32_t hoff : 6;
uint32_t reserved4 : 10;
}bits;
};
#endif /* _MAX30003_H_ */
/* clang-format on */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "gpio.h"
#include "bhy_uc_driver.h"
#include "bhy.h"
#include "pmic.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "epicardium.h"
#include "os/core.h"
#include "modules/modules.h"
#include "drivers/drivers.h"
#include "modules/stream.h"
#include "user_core/interrupts.h"
/* BHI160 Firmware Blob. Contents are defined in libcard10. */
extern uint8_t bhy1_fw[];
/* Interrupt Pin */
static const gpio_cfg_t bhi160_interrupt_pin = {
PORT_0, PIN_13, GPIO_FUNC_IN, GPIO_PAD_PULL_UP
};
/* clang-format off */
/* Axis remapping matrices */
static const int8_t bhi160_mapping_matrix[3 * 3] = {
0, -1, 0,
1, 0, 0,
0, 0, 1,
};
static const int8_t bmm150_mapping_matrix[3 * 3] = {
-1, 0, 0,
0, 1, 0,
0, 0, -1,
};
/*
* From the official docs:
*
* The sic matrix should be calculated for customer platform by logging
* uncalibrated magnetometer data. The sic matrix here is only an example
* array (identity matrix). Customer should generate their own matrix. This
* affects magnetometer fusion performance.
*
* TODO: Get data for card10
*/
static const float bhi160_sic_array[3 * 3] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
};
/* clang-format on */
/* BHI160 Task ID */
static TaskHandle_t bhi160_task_id = NULL;
/* BHI160 Mutex */
static struct mutex bhi160_mutex = { 0 };
/* Streams */
static struct stream_info bhi160_streams[10];
/* Active */
static bool bhi160_sensor_active[10] = { 0 };
/*
* Driver State: A flag that is set when an unrecoverable error occurred.
* Effectively, this means the sensor will be disabled until next reboot and any
* API calls will fail immediately.
*/
static bool bhi160_driver_b0rked = false;
/* -- Utilities -------------------------------------------------------- {{{ */
/*
* Retrieve the data size for a sensor. This value is needed for the creation
* of the sensor's sample queue.
*/
static size_t bhi160_lookup_data_size(enum bhi160_sensor_type type)
{
switch (type) {
case BHI160_ACCELEROMETER:
case BHI160_MAGNETOMETER:
case BHI160_ORIENTATION:
case BHI160_GYROSCOPE:
return sizeof(struct bhi160_data_vector);
default:
return 0;
}
}
/*
* Map a sensor type to the virtual sensor ID used by BHy1.
*/
static bhy_virtual_sensor_t bhi160_lookup_vs_id(enum bhi160_sensor_type type)
{
switch (type) {
case BHI160_ACCELEROMETER:
return VS_ID_ACCELEROMETER;
case BHI160_MAGNETOMETER:
return VS_ID_MAGNETOMETER;
case BHI160_ORIENTATION:
return VS_ID_ORIENTATION;
case BHI160_GYROSCOPE:
return VS_ID_GYROSCOPE;
default:
return -1;
}
}
/*
* Map a sensor type to its stream descriptor.
*/
static int bhi160_lookup_sd(enum bhi160_sensor_type type)
{
switch (type) {
case BHI160_ACCELEROMETER:
return SD_BHI160_ACCELEROMETER;
case BHI160_MAGNETOMETER:
return SD_BHI160_MAGNETOMETER;
case BHI160_ORIENTATION:
return SD_BHI160_ORIENTATION;
case BHI160_GYROSCOPE:
return SD_BHI160_GYROSCOPE;
default:
return -1;
}
}
/* }}} */
/* -- API -------------------------------------------------------------- {{{ */
int epic_bhi160_enable_sensor(
enum bhi160_sensor_type sensor_type,
struct bhi160_sensor_config *config
) {
int result = 0;
bhy_virtual_sensor_t vs_id = bhi160_lookup_vs_id(sensor_type);
if (vs_id == (bhy_virtual_sensor_t)-1) {
return -ENODEV;
}
mutex_lock(&bhi160_mutex);
hwlock_acquire(HWLOCK_I2C);
if (bhi160_driver_b0rked) {
result = -ENODEV;
goto out_free;
}
struct stream_info *stream = &bhi160_streams[sensor_type];
stream->item_size = bhi160_lookup_data_size(sensor_type);
/* TODO: Sanity check length */
stream->queue =
xQueueCreate(config->sample_buffer_len, stream->item_size);
if (stream->queue == NULL) {
result = -ENOMEM;
goto out_free;
}
result = stream_register(bhi160_lookup_sd(sensor_type), stream);
if (result < 0) {
goto out_free;
}
result = bhy_enable_virtual_sensor(
vs_id,
VS_WAKEUP,
config->sample_rate,
0,
VS_FLUSH_NONE,
0,
config->dynamic_range /* dynamic range is sensor dependent */
);
if (result != BHY_SUCCESS) {
goto out_free;
}
bhi160_sensor_active[sensor_type] = true;
/* Return the sensor stream descriptor */
result = bhi160_lookup_sd(sensor_type);
out_free:
hwlock_release(HWLOCK_I2C);
mutex_unlock(&bhi160_mutex);
return result;
}
int epic_bhi160_disable_sensor(enum bhi160_sensor_type sensor_type)
{
int result = 0;
bhy_virtual_sensor_t vs_id = bhi160_lookup_vs_id(sensor_type);
if (vs_id == (bhy_virtual_sensor_t)-1) {
return -ENODEV;
}
mutex_lock(&bhi160_mutex);
hwlock_acquire(HWLOCK_I2C);
if (bhi160_driver_b0rked) {
result = -ENODEV;
goto out_free;
}
struct stream_info *stream = &bhi160_streams[sensor_type];
result = stream_deregister(bhi160_lookup_sd(sensor_type), stream);
if (result < 0) {
goto out_free;
}
vQueueDelete(stream->queue);
stream->queue = NULL;
result = bhy_disable_virtual_sensor(vs_id, VS_WAKEUP);
if (result < 0) {
goto out_free;
}
bhi160_sensor_active[sensor_type] = false;
result = 0;
out_free:
hwlock_release(HWLOCK_I2C);
mutex_unlock(&bhi160_mutex);
return result;
}
void epic_bhi160_disable_all_sensors()
{
for (size_t i = 0; i < sizeof(bhi160_sensor_active); i++) {
if (bhi160_sensor_active[i]) {
epic_bhi160_disable_sensor(i);
}
}
}
/* }}} */
/* -- Driver ----------------------------------------------------------- {{{ */
/*
* Handle a single packet from the FIFO. For most sensors this means pushing
* the sample into its sample queue.
*/
static void
bhi160_handle_packet(bhy_data_type_t data_type, bhy_data_generic_t *sensor_data)
{
uint8_t sensor_id = sensor_data->data_vector.sensor_id;
struct bhi160_data_vector data_vector;
/*
* Timestamp of the next samples, counting at 32 kHz.
* Currently unused.
*/
static uint32_t timestamp = 0;
enum bhi160_sensor_type sensor_type = 0;
int epic_int = 0;
bool wakeup = false;
switch (sensor_id) {
case VS_ID_TIMESTAMP_MSW_WAKEUP:
wakeup = true;
/* fall through */
case VS_ID_TIMESTAMP_MSW:
assert(data_type == BHY_DATA_TYPE_SCALAR_U16);
timestamp = sensor_data->data_scalar_u16.data << 16;
break;
case VS_ID_TIMESTAMP_LSW_WAKEUP:
wakeup = true;
/* fall through */
case VS_ID_TIMESTAMP_LSW:
assert(data_type == BHY_DATA_TYPE_SCALAR_U16);
timestamp = (timestamp & 0xFFFF0000) |
sensor_data->data_scalar_u16.data;
break;
case VS_ID_ACCELEROMETER_WAKEUP:
case VS_ID_MAGNETOMETER_WAKEUP:
case VS_ID_ORIENTATION_WAKEUP:
case VS_ID_GYROSCOPE_WAKEUP:
wakeup = true;
/* fall through */
case VS_ID_ACCELEROMETER:
case VS_ID_MAGNETOMETER:
case VS_ID_ORIENTATION:
case VS_ID_GYROSCOPE:
switch (sensor_id) {
case VS_ID_ACCELEROMETER_WAKEUP:
case VS_ID_ACCELEROMETER:
sensor_type = BHI160_ACCELEROMETER;
epic_int = EPIC_INT_BHI160_ACCELEROMETER;
break;
case VS_ID_MAGNETOMETER_WAKEUP:
case VS_ID_MAGNETOMETER:
sensor_type = BHI160_MAGNETOMETER;
epic_int = EPIC_INT_BHI160_MAGNETOMETER;
break;
case VS_ID_ORIENTATION_WAKEUP:
case VS_ID_ORIENTATION:
sensor_type = BHI160_ORIENTATION;
epic_int = EPIC_INT_BHI160_ORIENTATION;
break;
case VS_ID_GYROSCOPE_WAKEUP:
case VS_ID_GYROSCOPE:
sensor_type = BHI160_GYROSCOPE;
epic_int = EPIC_INT_BHI160_GYROSCOPE;
break;
}
assert(data_type == BHY_DATA_TYPE_VECTOR);
if (bhi160_streams[sensor_type].queue == NULL) {
break;
}
data_vector.data_type = BHI160_DATA_TYPE_VECTOR;
data_vector.x = sensor_data->data_vector.x;
data_vector.y = sensor_data->data_vector.y;
data_vector.z = sensor_data->data_vector.z;
data_vector.status = sensor_data->data_vector.status;
/* Discard overflow. See discussion in !316. */
if (xQueueSend(
bhi160_streams[sensor_type].queue,
&data_vector,
0) != pdTRUE) {
if (!bhi160_streams[sensor_type].was_full) {
LOG_WARN(
"bhi160",
"queue full for %d",
sensor_type
);
}
bhi160_streams[sensor_type].was_full = true;
} else {
bhi160_streams[sensor_type].was_full = false;
}
if (wakeup) {
interrupt_trigger(epic_int);
}
break;
default:
break;
}
}
/*
* Fetch all data available from BHI160's FIFO buffer and handle all packets
* contained in it.
*/
static void bhi160_fetch_fifo(void)
{
/*
* Warning: The code from the BHy1 docs has some issues. This
* implementation looks similar, but has a few important differences.
* You'll probably be best of leaving it as it is ...
*/
/*
* FIFO buffer. Access to this static variable is safe because this
* function is guarded by the bhi160_mutex.
*/
static uint8_t bhi160_fifo[BHI160_FIFO_SIZE];
static size_t start_index = 0;
BHY_RETURN_FUNCTION_TYPE result = 0;
/* Number of bytes left in BHI160's FIFO buffer */
uint16_t bytes_left_in_fifo = 1;
mutex_lock(&bhi160_mutex);
hwlock_acquire(HWLOCK_I2C);
while (bytes_left_in_fifo) {
/* Fill local FIFO buffer with as many bytes as possible */
uint16_t bytes_read;
result = bhy_read_fifo(
&bhi160_fifo[start_index],
BHI160_FIFO_SIZE - start_index,
&bytes_read,
&bytes_left_in_fifo
);
if (result != BHY_SUCCESS) {
/*
* Honestly not sure how we should handle these errors.
*
* Needs more digging to find out how bhy_read_fifo()
* behaves in error situations (a quick glance at the
* code shows that the function might have written
* _somethings_ into our buffer so we can't just retry
* blindly ...)
*
* For now, just abort everything and disable the
* sensor. Won't cause havoc at least ...
*/
LOG_ERR("bhi160",
"Error while reading fifo: %d. Disabling.",
result);
bhi160_driver_b0rked = true;
break;
}
/* Add the bytes left from the last transfer on top */
bytes_read += start_index;
/* Handle all full packets received in this transfer */
uint8_t *fifo_ptr = bhi160_fifo;
uint16_t bytes_left = bytes_read;
while (bytes_left > 0) {
bhy_data_generic_t sensor_data;
bhy_data_type_t data_type;
result = bhy_parse_next_fifo_packet(
&fifo_ptr,
&bytes_left,
&sensor_data,
&data_type
);
if (result == BHY_SUCCESS) {
bhi160_handle_packet(data_type, &sensor_data);
} else {
LOG_WARN(
"bhi160",
"Error in fifo packet: %d. Ignoring.",
result
);
break;
}
}
/* Shift the remaining bytes to the beginning */
for (int i = 0; i < bytes_left; i++) {
bhi160_fifo[i] =
bhi160_fifo[bytes_read - bytes_left + i];
}
start_index = bytes_left;
}
hwlock_release(HWLOCK_I2C);
mutex_unlock(&bhi160_mutex);
}
/*
* Callback for the BHI160 interrupt pin. This callback is called from the
* SDK's GPIO interrupt driver, in interrupt context.
*/
static void bhi160_interrupt_callback(void *_)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (bhi160_task_id != NULL && !bhi160_driver_b0rked) {
vTaskNotifyGiveFromISR(
bhi160_task_id, &xHigherPriorityTaskWoken
);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
/* }}} */
void vBhi160Task(void *pvParameters)
{
int ret;
bhi160_task_id = xTaskGetCurrentTaskHandle();
mutex_create(&bhi160_mutex);
mutex_lock(&bhi160_mutex);
hwlock_acquire(HWLOCK_I2C);
memset(bhi160_streams, 0x00, sizeof(bhi160_streams));
/*
* The BHI160, coming out of power-on-reset will hold its interrupt line
* high until a firmware image is loaded. Once that firmware is loaded
* and running, the interrupt line is deasserted and from then on,
* interrupts are signaled using a rising edge.
*
* So, initially we need to configure the IRQ for a falling edge, load
* the firmware and then reconfigure for a rising edge.
*/
GPIO_Config(&bhi160_interrupt_pin);
GPIO_RegisterCallback(
&bhi160_interrupt_pin, bhi160_interrupt_callback, NULL
);
GPIO_IntConfig(&bhi160_interrupt_pin, GPIO_INT_EDGE, GPIO_INT_FALLING);
GPIO_IntEnable(&bhi160_interrupt_pin);
NVIC_SetPriority(
(IRQn_Type)MXC_GPIO_GET_IRQ(bhi160_interrupt_pin.port), 2
);
NVIC_EnableIRQ((IRQn_Type)MXC_GPIO_GET_IRQ(bhi160_interrupt_pin.port));
/* Upload firmware */
ret = bhy_driver_init(bhy1_fw);
if (ret) {
LOG_CRIT("bhi160", "BHy1 init failed! Disabling.");
/* Disable BHI160 until next reboot */
bhi160_driver_b0rked = true;
hwlock_release(HWLOCK_I2C);
mutex_unlock(&bhi160_mutex);
vTaskDelete(NULL);
}
/* Wait for first interrupt, a falling edge */
hwlock_release(HWLOCK_I2C);
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(2000)) == 0) {
LOG_CRIT(
"bhi160",
"Sensor firmware was not loaded correctly. Disabling."
);
/* Disable BHI160 until next reboot */
bhi160_driver_b0rked = true;
mutex_unlock(&bhi160_mutex);
vTaskDelete(NULL);
}
hwlock_acquire(HWLOCK_I2C);
/*
* The firmware is now loaded; as stated above, we now need to
* reconfigure the IRQ for a rising edge.
*/
GPIO_IntConfig(&bhi160_interrupt_pin, GPIO_INT_EDGE, GPIO_INT_RISING);
/*
* Remap axes to match card10 layout.
*
* TODO: We set the matrix for the accelerometer twice because on some
* badges, the axis mapping is not applied properly the first time. We
* should fix this properly at some point.
*/
bhy_mapping_matrix_set(
PHYSICAL_SENSOR_INDEX_ACC, (int8_t *)bhi160_mapping_matrix
);
bhy_mapping_matrix_set(
PHYSICAL_SENSOR_INDEX_ACC, (int8_t *)bhi160_mapping_matrix
);
bhy_mapping_matrix_set(
PHYSICAL_SENSOR_INDEX_MAG, (int8_t *)bmm150_mapping_matrix
);
bhy_mapping_matrix_set(
PHYSICAL_SENSOR_INDEX_GYRO, (int8_t *)bhi160_mapping_matrix
);
/* Set "SIC" matrix. TODO: Find out what this is about */
bhy_set_sic_matrix((float *)bhi160_sic_array);
hwlock_release(HWLOCK_I2C);
mutex_unlock(&bhi160_mutex);
/* ----------------------------------------- */
while (1) {
bhi160_fetch_fifo();
/*
* Wait for interrupt. After two seconds, fetch FIFO anyway in
* case there are any diagnostics or errors.
*
* 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 "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 "modules/log.h"
#include "os/core.h"
#include "portexpander.h"
#include "MAX77650-Arduino-Library.h"
......@@ -16,19 +16,14 @@ static const uint8_t pin_mask[] = {
uint8_t epic_buttons_read(uint8_t mask)
{
uint8_t ret = 0;
if (portexpander_detected() && (mask & 0x7)) {
if (hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100)) < 0) {
LOG_ERR("buttons", "Can't acquire I2C bus");
return 0;
}
hwlock_acquire(HWLOCK_I2C);
if (portexpander_detected() && (mask & 0x7)) {
/*
* Not using PB_Get() here as that performs one I2C transcation
* Not using PB_Get() here as that performs one I2C transaction
* per button.
*/
uint8_t pin_status = ~portexpander_get();
hwlock_release(HWLOCK_I2C);
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]) {
......@@ -41,5 +36,6 @@ uint8_t epic_buttons_read(uint8_t mask)
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 "epicardium.h"
#include "modules/log.h"
#include "os/core.h"
#include "os/work_queue.h"
#include "modules/modules.h"
#include "mxc_config.h"
......@@ -27,26 +28,32 @@ static int light_sensor_init()
return 0;
}
static void readAdcCallback()
uint16_t epic_light_sensor_read()
{
if (hwlock_acquire(HWLOCK_ADC, 0) != 0) {
/* Can't do much about this here ... Retry next time */
return;
}
hwlock_acquire(HWLOCK_ADC);
ADC_StartConvert(ADC_CH_7, 0, 0);
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 ret = 0;
if (hwlock_acquire(HWLOCK_ADC, pdMS_TO_TICKS(500)) != 0) {
return -EBUSY;
}
hwlock_acquire(HWLOCK_ADC);
light_sensor_init();
......@@ -56,7 +63,7 @@ int epic_light_sensor_run()
READ_FREQ,
pdTRUE,
NULL,
readAdcCallback,
poll,
&poll_timer_buffer
);
// since &poll_timer_buffer is not NULL, xTimerCreateStatic should allways succeed, so
......
#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;
}