From d92965df5a93bf4a5b0e62a9eedd52f9eeaea2e9 Mon Sep 17 00:00:00 2001 From: "Jakob (XDjackieXD) Riepler" <jakob.riepler@chaosfield.at> Date: Fri, 31 Jan 2020 22:08:59 +0100 Subject: [PATCH] feat(max86150): MAX86150 Epicardium API Epicardium API for max86150 aligned with latest state of sensor development on card10. Co-Authored-by: Arist <aristkojevnikov@gmail.com> --- epicardium/epicardium.h | 101 ++++++++++++-- epicardium/main.c | 10 ++ epicardium/modules/hardware.c | 7 + epicardium/modules/max86150.c | 236 +++++++++++++++++++++++++++++++++ epicardium/modules/meson.build | 1 + epicardium/modules/modules.h | 6 + epicardium/modules/sleep.c | 6 +- epicardium/modules/stream.h | 1 + 8 files changed, 354 insertions(+), 14 deletions(-) create mode 100644 epicardium/modules/max86150.c diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 01709785..84071d04 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -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. diff --git a/epicardium/main.c b/epicardium/main.c index 831a3e12..c5e2768c 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -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, diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c index afc44f64..35815433 100644 --- a/epicardium/modules/hardware.c +++ b/epicardium/modules/hardware.c @@ -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; } diff --git a/epicardium/modules/max86150.c b/epicardium/modules/max86150.c new file mode 100644 index 00000000..428d0caf --- /dev/null +++ b/epicardium/modules/max86150.c @@ -0,0 +1,236 @@ +#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)); + } +} diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 8ead5e72..474b3293 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -13,6 +13,7 @@ module_sources = files( 'lifecycle.c', 'light_sensor.c', 'log.c', + 'max86150.c', 'max30001.c', 'mutex.c', 'panic.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 567a2a82..0444b688 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -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); diff --git a/epicardium/modules/sleep.c b/epicardium/modules/sleep.c index 4b94fa79..c7c060e1 100644 --- a/epicardium/modules/sleep.c +++ b/epicardium/modules/sleep.c @@ -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(); diff --git a/epicardium/modules/stream.h b/epicardium/modules/stream.h index f6f93970..a953537a 100644 --- a/epicardium/modules/stream.h +++ b/epicardium/modules/stream.h @@ -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, }; -- GitLab