Commit 36a774a8 authored by schneider's avatar schneider
Browse files

Merge branch 'max86150' into 'master'

Stable max86150 epicardium API

See merge request card10/firmware!371
parents f17417e1 d92965df
......@@ -133,9 +133,8 @@ typedef _Bool bool;
#define API_MAX30001_ENABLE 0xf0
#define API_MAX30001_DISABLE 0xf1
#define API_MAX86150_INIT 0x0100
#define API_MAX86150_GET_DATA 0x0101
#define API_MAX86150_SET_LED_AMPLITUDE 0x0102
#define API_MAX86150_ENABLE 0x0100
#define API_MAX86150_DISABLE 0x0101
#define API_USB_SHUTDOWN 0x110
#define API_USB_STORAGE 0x111
......@@ -194,18 +193,20 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id));
/** RTC Alarm interrupt. See :c:func:`epic_isr_rtc_alarm`. */
#define EPIC_INT_RTC_ALARM 3
/** BHI160 Accelerometer. See :c:func:`epic_isr_bhi160_accelerometer`. */
#define EPIC_INT_BHI160_ACCELEROMETER 4
#define EPIC_INT_BHI160_ACCELEROMETER 4
/** BHI160 Orientation Sensor. See :c:func:`epic_isr_bhi160_orientation`. */
#define EPIC_INT_BHI160_ORIENTATION 5
#define EPIC_INT_BHI160_ORIENTATION 5
/** BHI160 Gyroscope. See :c:func:`epic_isr_bhi160_gyroscope`. */
#define EPIC_INT_BHI160_GYROSCOPE 6
#define EPIC_INT_BHI160_GYROSCOPE 6
/** MAX30001 ECG. See :c:func:`epic_isr_max30001_ecg`. */
#define EPIC_INT_MAX30001_ECG 7
#define EPIC_INT_MAX30001_ECG 7
/** BHI160 Magnetometer. See :c:func:`epic_isr_bhi160_magnetometer`. */
#define EPIC_INT_BHI160_MAGNETOMETER 8
/** MAX86150 ECG and PPG sensor. See :c:func:`epic_isr_max86150`. */
#define EPIC_INT_MAX86150 9
/* Number of defined interrupts. */
#define EPIC_INT_NUM 9
#define EPIC_INT_NUM 10
/* clang-format on */
/*
......@@ -871,6 +872,84 @@ API(API_BME680_GET_DATA, int epic_bme680_read_sensors(
struct bme680_sensor_data *data
));
/**
* MAX86150
* ======
*/
/**
* Configuration for a MAX86150 sensor.
*
* This struct is used when enabling a sensor using
* :c:func:`epic_max86150_enable_sensor`.
*/
struct max86150_sensor_config {
/**
* Number of samples Epicardium should keep for this sensor. Do not set
* this number too high as the sample buffer will eat RAM.
*/
size_t sample_buffer_len;
/**
* Sample rate for PPG from the sensor in Hz. Maximum data rate is limited
* to 200 Hz for all sensors though some might be limited at a lower
* rate.
*
* Possible values are 10, 20, 50, 84, 100, 200.
*/
uint16_t ppg_sample_rate;
};
/**
* MAX86150 Sensor Data
*/
struct max86150_sensor_data {
/** Red LED data */
uint32_t red;
/** IR LED data */
uint32_t ir;
/** ECG data */
int32_t ecg;
};
/**
* Enable a MAX86150 PPG and ECG sensor.
*
* Calling this function will instruct the MAX86150 to collect a
* data from the sensor. You can then retrieve the samples using
* :c:func:`epic_stream_read`.
*
* :param max86150_sensor_config* config: Configuration for this sensor.
* :param size_t config_size: Size of ``config``.
* :returns: A sensor descriptor which can be used with
* :c:func:`epic_stream_read` or a negative error value:
*
* - ``-ENOMEM``: The MAX86150 driver failed to create a stream queue.
* - ``-ENODEV``: The MAX86150 driver failed due to physical connectivity problem
* (broken wire, unpowered, etc).
* - ``-EINVAL``: config->ppg_sample_rate is not one of 10, 20, 50, 84, 100, 200
* or config_size is not size of config.
*
* .. versionadded:: 1.13
*/
API(API_MAX86150_ENABLE, int epic_max86150_enable_sensor(struct max86150_sensor_config *config, size_t config_size));
/**
* Disable the MAX86150 sensor.
*
* :returns: 0 in case of success or forward negative error value from stream_deregister.
*
* .. versionadded:: 1.13
*/
API(API_MAX86150_DISABLE, int epic_max86150_disable_sensor());
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_MAX86150`
*
* :c:func:`epic_isr_max86150` is called whenever the MAX86150
* PPG sensor has new data available.
*/
API_ISR(EPIC_INT_MAX86150, epic_isr_max86150);
/**
* Personal State
* ==============
......@@ -1139,7 +1218,7 @@ struct bhi160_sensor_config {
};
/**
* Enable a BHI160 virtual sensor. Calling this funciton will instruct the
* Enable a BHI160 virtual sensor. Calling this function will instruct the
* BHI160 to collect data for this specific virtual sensor. You can then
* retrieve the samples using :c:func:`epic_stream_read`.
*
......@@ -1854,9 +1933,9 @@ struct max30001_sensor_config {
};
/**
* Enable a MAX30001 ecg sensor.
* Enable a MAX30001 ECG sensor.
*
* Calling this funciton will instruct the MAX30001 to collect data for this
* Calling this function will instruct the MAX30001 to collect data for this
* sensor. You can then retrieve the samples using :c:func:`epic_stream_read`.
*
* :param max30001_sensor_config* config: Configuration for this sensor.
......
......@@ -113,6 +113,16 @@ int main(void)
NULL) != pdPASS) {
panic("Failed to create %s task!", "MAX30001");
}
/* MAX86150 */
if (xTaskCreate(
vMAX86150Task,
(const char *)"MAX86150 Driver",
configMINIMAL_STACK_SIZE * 2,
NULL,
tskIDLE_PRIORITY + 1,
NULL) != pdPASS) {
panic("Failed to create %s task!", "MAX86150");
}
/* API */
if (xTaskCreate(
vApiDispatcher,
......
......@@ -194,6 +194,11 @@ int hardware_early_init(void)
*/
max30001_mutex_init();
/*
* max86150 mutex init
*/
max86150_mutex_init();
/* Allow user space to trigger interrupts.
* Used for BLE, not sure if needed. */
SCB->CCR |= SCB_CCR_USERSETMPEND_Msk;
......@@ -283,5 +288,7 @@ int hardware_reset(void)
epic_max30001_disable_sensor();
epic_max86150_disable_sensor();
return 0;
}
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include "max86150.h"
#include "epicardium.h"
#include "modules.h"
#include "modules/log.h"
#include "modules/stream.h"
#include "gpio.h"
#include "pmic.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "api/interrupt-sender.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 {
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);
struct stream_info *stream = &max86150_stream;
result = stream_deregister(SD_MAX86150, stream);
if (result < 0) {
goto out_free;
}
vQueueDelete(stream->queue);
stream->queue = NULL;
// disable max86150 leds
max86150_set_led_red_amplitude(0);
max86150_set_led_ir_amplitude(0);
max86150_sensor_active = false;
result = 0;
out_free:
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) {
LOG_WARN("max86150", "queue full");
return -EIO;
}
return api_interrupt_trigger(EPIC_INT_MAX86150);
}
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().
while (max86150_get_sample(&sample.red, &sample.ir, &sample.ecg) > 0) {
result = max86150_handle_sample(&sample);
// stop in case of errors
if (result < 0) {
break;
}
}
hwlock_release(HWLOCK_I2C);
mutex_unlock(&max86150_mutex);
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!");
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));
}
}
......@@ -13,6 +13,7 @@ module_sources = files(
'lifecycle.c',
'light_sensor.c',
'log.c',
'max86150.c',
'max30001.c',
'mutex.c',
'panic.c',
......
......@@ -111,6 +111,12 @@ void disp_forcelock();
#define BHI160_MUTEX_WAIT_MS 50
void vBhi160Task(void *pvParameters);
/* ---------- 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);
......
......@@ -176,9 +176,9 @@ void sleep_deepsleep(void)
/* This will fail if there is no
* harmonic board attached */
max86150_begin();
max86150_getINT1();
max86150_getINT2();
max86150_shutDown();
max86150_get_int1();
max86150_get_int2();
max86150_shut_down();
#endif
epic_bhi160_disable_all_sensors();
epic_bme680_deinit();
......
......@@ -36,6 +36,7 @@ enum stream_descriptor {
SD_BHI160_GYROSCOPE,
/** MAX30001 ECG */
SD_MAX30001_ECG,
SD_MAX86150,
/** Highest descriptor must always be ``SD_MAX``. */
SD_MAX,
};
......
......@@ -18,151 +18,132 @@
typedef uint8_t byte;
static const uint8_t MAX86150_INTSTAT1 = 0x00;
static const uint8_t MAX86150_INTSTAT2 = 0x01;
static const uint8_t MAX86150_INTENABLE1 = 0x02;
static const uint8_t MAX86150_INTENABLE2 = 0x03;
static const uint8_t MAX86150_INTSTAT1 = 0x00;
static const uint8_t MAX86150_INTSTAT2 = 0x01;
static const uint8_t MAX86150_INTENABLE1 = 0x02;
static const uint8_t MAX86150_INTENABLE2 = 0x03;
static const uint8_t MAX86150_FIFOWRITEPTR = 0x04;
static const uint8_t MAX86150_FIFOOVERFLOW = 0x05;
static const uint8_t MAX86150_FIFOREADPTR = 0x06;
static const uint8_t MAX86150_FIFODATA = 0x07;
static const uint8_t MAX86150_FIFOWRITEPTR = 0x04;
static const uint8_t MAX86150_FIFOOVERFLOW = 0x05;
static const uint8_t MAX86150_FIFOREADPTR = 0x06;
static const uint8_t MAX86150_FIFODATA = 0x07;
static const uint8_t MAX86150_FIFOCONFIG = 0x08;
static const uint8_t MAX86150_FIFOCONTROL1= 0x09;
static const uint8_t MAX86150_FIFOCONTROL2 = 0x0A;
static const uint8_t MAX86150_FIFOCONFIG = 0x08;
static const uint8_t MAX86150_FIFOCONTROL1 = 0x09;
static const uint8_t MAX86150_FIFOCONTROL2 = 0x0A;
static const uint8_t MAX86150_SYSCONTROL = 0x0D;
static const uint8_t MAX86150_PPGCONFIG1 = 0x0E;
static const uint8_t MAX86150_PPGCONFIG2 = 0x0F;
static const uint8_t MAX86150_LED_PROX_AMP = 0x10;
static const uint8_t MAX86150_SYSCONTROL = 0x0D;
static const uint8_t MAX86150_PPGCONFIG1 = 0x0E;
static const uint8_t MAX86150_PPGCONFIG2 = 0x0F;
static const uint8_t MAX86150_LED_PROX_AMP = 0x10;
static const uint8_t MAX86150_LED1_PULSEAMP = 0x11;
static const uint8_t MAX86150_LED2_PULSEAMP = 0x12;
static const uint8_t MAX86150_LED_RANGE = 0x14;
static const uint8_t MAX86150_LED_PILOT_PA = 0x15;
static const uint8_t MAX86150_LED1_PULSEAMP = 0x11;
static const uint8_t MAX86150_LED2_PULSEAMP = 0x12;
static const uint8_t MAX86150_LED_RANGE = 0x14;
static const uint8_t MAX86150_LED_PILOT_PA = 0x15;
static const uint8_t MAX86150_ECG_CONFIG1 = 0x3C;
static const uint8_t MAX86150_ECG_CONFIG3 = 0x3E;
static const uint8_t MAX86150_PROXINTTHRESH = 0x10;
static const uint8_t MAX86150_ECG_CONFIG1 = 0x3C;
static const uint8_t MAX86150_ECG_CONFIG3 = 0x3E;
static const uint8_t MAX86150_PROXINTTHRESH = 0x10;
static const uint8_t MAX86150_PARTID = 0xFF;
static const uint8_t MAX86150_PARTID = 0xFF;
// MAX86150 Commands
static const uint8_t MAX86150_INT_A_FULL_MASK = (byte)~0b10000000;
static const uint8_t MAX86150_INT_A_FULL_ENABLE = 0x80;
static const uint8_t MAX86150_INT_A_FULL_DISABLE = 0x00;
static const uint8_t MAX86150_INT_A_FULL_MASK = (byte)~0b10000000;
static const uint8_t MAX86150_INT_A_FULL_ENABLE = 0x80;
static const uint8_t MAX86150_INT_A_FULL_DISABLE = 0x00;
static const uint8_t MAX86150_INT_DATA_RDY_MASK = (byte)~0b01000000;
static const uint8_t MAX86150_INT_DATA_RDY_ENABLE = 0x40;
static const uint8_t MAX86150_INT_DATA_RDY_MASK = (byte)~0b01000000;
static const uint8_t MAX86150_INT_DATA_RDY_ENABLE = 0x40;
static const uint8_t MAX86150_INT_DATA_RDY_DISABLE = 0x00;
static const uint8_t MAX86150_INT_ALC_OVF_MASK = (byte)~0b00100000;
static const uint8_t MAX86150_INT_ALC_OVF_ENABLE = 0x20;
static const uint8_t MAX86150_INT_ALC_OVF_MASK = (byte)~0b00100000;
static const uint8_t MAX86150_INT_ALC_OVF_ENABLE = 0x20;
static const uint8_t MAX86150_INT_ALC_OVF_DISABLE = 0x00;
static const uint8_t MAX86150_INT_PROX_INT_MASK = (byte)~0b00010000;
static const uint8_t MAX86150_INT_PROX_INT_ENABLE = 0x10;
static const uint8_t MAX86150_INT_PROX_INT_MASK = (byte)~0b00010000;
static const uint8_t MAX86150_INT_PROX_INT_ENABLE = 0x10;
static const uint8_t MAX86150_INT_PROX_INT_DISABLE = 0x00;
static const uint8_t MAX86150_SAMPLEAVG_MASK = (byte)~0b11100000;
static const uint8_t MAX86150_SAMPLEAVG_1 = 0x00;
static const uint8_t MAX86150_SAMPLEAVG_2 = 0x20;
static const uint8_t MAX86150_SAMPLEAVG_4 = 0x40;
static const uint8_t MAX86150_SAMPLEAVG_8 = 0x60;
static const uint8_t MAX86150_SAMPLEAVG_16 = 0x80;
static const uint8_t MAX86150_SAMPLEAVG_32 = 0xA0;
static const uint8_t MAX86150_ROLLOVER_MASK = 0xEF;
static const uint8_t MAX86150_ROLLOVER_ENABLE = 0x10;
static const uint8_t MAX86150_ROLLOVER_DISABLE = 0x00;
static const uint8_t MAX86150_A_FULL_MASK = 0xF0;
static const uint8_t MAX86150_SHUTDOWN_MASK = 0x7F;
static const uint8_t MAX86150_SHUTDOWN = 0x80;
static const uint8_t MAX86150_WAKEUP = 0x00;
static const uint8_t MAX86150_RESET_MASK = 0xFE;
static const uint8_t MAX86150_RESET = 0x01;
static const uint8_t MAX86150_MODE_MASK = 0xF8;
static const uint8_t MAX86150_MODE_REDONLY = 0x02;
static const uint8_t MAX86150_MODE_REDIRONLY = 0x03;
static const uint8_t MAX86150_MODE_MULTILED = 0x07;
static const uint8_t MAX86150_ADCRANGE_MASK = 0x9F;
static const uint8_t MAX86150_ADCRANGE_2048 = 0x00;
static const uint8_t MAX86150_ADCRANGE_4096 = 0x20;
static const uint8_t MAX86150_ADCRANGE_8192 = 0x40;
static const uint8_t MAX86150_ADCRANGE_16384 = 0x60;
static const uint8_t MAX86150_SAMPLERATE_MASK = 0xE3;
static const uint8_t MAX86150_SAMPLERATE_50 = 0x00;
static const uint8_t MAX86150_SAMPLERATE_100 = 0x04;
static const uint8_t MAX86150_SAMPLERATE_200 = 0x08;
static const uint8_t MAX86150_SAMPLERATE_400 = 0x0C;
static const uint8_t MAX86150_SAMPLERATE_800 = 0x10;
static const uint8_t MAX86150_SAMPLERATE_1000 = 0x14;
static const uint8_t MAX86150_SAMPLERATE_1600 = 0x18;
static const uint8_t MAX86150_SAMPLERATE_3200 = 0x1C;
static const uint8_t MAX86150_PULSEWIDTH_MASK = 0xFC;
static const uint8_t MAX86150_PULSEWIDTH_69 = 0x00;
static const uint8_t MAX86150_PULSEWIDTH_118 = 0x01;
static const uint8_t MAX86150_PULSEWIDTH_215 = 0x02;
static const uint8_t MAX86150_PULSEWIDTH_411 = 0x03;
static const uint8_t MAX86150_SLOT1_MASK = 0xF0;
static const uint8_t MAX86150_SLOT2_MASK = 0x0F;
static const uint8_t MAX86150_SLOT3_MASK = 0xF0;
static const uint8_t MAX86150_SLOT4_MASK = 0x0F;
static const uint8_t SLOT_NONE = 0x00;
static const uint8_t SLOT_RED_LED = 0x01;
static const uint8_t SLOT_IR_LED = 0x02;
static const uint8_t SLOT_RED_PILOT = 0x09;
static const uint8_t SLOT_IR_PILOT = 0x0A;
static const uint8_t SLOT_ECG = 0x0D;
static const uint8_t MAX_30105_EXPECTEDPARTID = 0x1E;
static uint8_t _i2caddr;
//activeLEDs is the number of channels turned on, and can be 1 to 3. 2 is common for Red+IR.
static byte activeDevices; //Gets set during max86150_setup. Allows max86150_check() to calculate how many bytes to read from FIFO
static void max86150_bitMask(uint8_t reg, uint8_t mask, uint8_t thing);
#define STORAGE_SIZE 128 //Each long is 4 bytes so limit this to fit on your micro
typedef struct Record
{