diff --git a/Documentation/index.rst b/Documentation/index.rst index 77f03db287cfdc576d6af03ad153cdf03e8cae8c..561fb9bddf931dc6492f05bbbc94654fc4c8c8d0 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -22,6 +22,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the pycardium/overview pycardium/stdlib + pycardium/bhi160 pycardium/bme680 pycardium/buttons pycardium/color @@ -32,10 +33,10 @@ Last but not least, if you want to start hacking the lower-level firmware, the pycardium/os pycardium/personal_state pycardium/power + pycardium/pride pycardium/simple_menu pycardium/utime pycardium/vibra - pycardium/pride .. toctree:: :maxdepth: 1 diff --git a/Documentation/pycardium/bhi160.rst b/Documentation/pycardium/bhi160.rst new file mode 100644 index 0000000000000000000000000000000000000000..6905f779c29d0d92b5a73a0c0020bdeace2c1df1 --- /dev/null +++ b/Documentation/pycardium/bhi160.rst @@ -0,0 +1,102 @@ +.. py:module:: bhi160 + +``bhi160`` - Sensor Fusion +========================== +.. versionadded:: 1.4 + +Supports the BHI160 sensor on the card10 for accelerometer, gyroscope... + +**Example**: + +.. code-block:: python + + import bhi160 + import utime + + bhi = bhi160.BHI160Orientation() + + while True: + samples = bhi.read() + print(samples) + utime.sleep(0.5) + + +.. class:: bhi160.BHI160Orientation(sample_rate,dynamic_range,callback,sample_buffer_len) + + Orientation of the BHI160 + + Parameters: + sample_rate: int, optional + Sample rate (default is 4) + dynamic_range: int, optional + Dynamic range (default is 2) + callback: callable, optional + Call this callback when enough data is collected (default is None) + + .. todo:: The callback functionality is untested, so do not be confused if it does not work. + sample_buffer_len: int, optional + Length of sample buffer (default is 200) + + .. py:method:: read(): + + Read sensor values + + :returns: Collected sensor values as list + + .. py:method:: close(): + + Close the connection to the sensor + + +.. class:: bhi160.BHI160Accelerometer + + Accelerometer of the BHI160 + + Parameters: + sample_rate: int, optional + Sample rate (default is 4) + dynamic_range: int, optional + Dynamic range (default is 2) + callback: callable, optional + Call this callback when enough data is collected (default is None) + + .. todo:: The callback functionality is untested, so do not be confused if it does not work. + sample_buffer_len: int, optional + Length of sample buffer (default is 200) + + .. py:method:: read(): + + Read sensor values + + :returns: Collected sensor values as list + + .. py:method:: close(): + + Close the connection to the sensor + +.. class:: bhi160.BHI160Gyroscope + + Gyroscope of the BHI160 + + Parameters: + sample_rate: int, optional + Sample rate (default is 4) + dynamic_range: int, optional + Dynamic range (default is 2) + callback: callable, optional + Call this callback when enough data is collected (default is None) + + .. todo:: The callback functionality is untested, so do not be confused if it does not work. + sample_buffer_len: int, optional + Length of sample buffer (default is 200) + + .. py:method:: read(): + + Read sensor values + + :returns: Collected sensor values as list + + .. py:method:: close(): + + Close the connection to the sensor + diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 54201a181a5dc9b7b85ec4c2a3ea4f36402a11ab..f701dc4f17d652913e02536582bec11fdca54fee 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -124,9 +124,14 @@ typedef _Bool bool; #define API_BHI160_DISABLE 0xe1 #define API_BHI160_DISABLE_ALL 0xe2 -#define API_MAX86150_INIT 0xf0 -#define API_MAX86150_GET_DATA 0xf1 -#define API_MAX86150_SET_LED_AMPLITUDE 0xf2 +#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 + +>>>>>>> 4f32a5ceaf749a0f8bc8b2b6ec706e118dd368fd /* clang-format on */ @@ -175,9 +180,11 @@ API_ISR(EPIC_INT_BHI160_ACCELEROMETER, epic_isr_bhi160_accelerometer); API_ISR(EPIC_INT_BHI160_ORIENTATION, epic_isr_bhi160_orientation); #define EPIC_INT_BHI160_GYROSCOPE 6 API_ISR(EPIC_INT_BHI160_GYROSCOPE, epic_isr_bhi160_gyroscope); +#define EPIC_INT_MAX30001_ECG 7 +API_ISR(EPIC_INT_MAX30001_ECG, epic_isr_max30001_ecg); /* Number of defined interrupts. */ -#define EPIC_INT_NUM 7 +#define EPIC_INT_NUM 8 /* clang-format on */ /* @@ -820,9 +827,9 @@ enum personal_state { STATE_NO_CONTACT = 1, /** ``2``, "chaos" - Adventure time - blue led, short blink, long blink. */ STATE_CHAOS = 2, - /** ``3``, "communication" - want to learn something or have a nice conversation - green led, long blinks. */ + /** ``3``, "communication" - want to learn something or have a nice conversation - yellow led, long blinks. */ STATE_COMMUNICATION = 3, - /** ``4``, "camp" - I am focussed on self-, camp-, or community maintenance - yellow led, fade on and off. */ + /** ``4``, "camp" - I am focussed on self-, camp-, or community maintenance - green led, fade on and off. */ STATE_CAMP = 4, /** STATE_MAX gives latest value and count of possible STATEs**/ STATE_MAX = 5, @@ -1632,4 +1639,68 @@ API_ISR(EPIC_INT_RTC_ALARM, epic_isr_rtc_alarm); */ API(API_TRNG_READ, int epic_trng_read(uint8_t *dest, size_t size)); +/** + * MAX30001 API + * ---------- + */ + +/** + * Configuration for a MAX30001 sensor. + * + * This struct is used when enabling the sensor using + * :c:func:`epic_max30001_enable_sensor`. + */ +struct max30001_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 the sensor in Hz. + */ + uint16_t sample_rate; + + /** + * Set to true if the second lead comes from USB-C + */ + bool usb; + + /** + * Set to true if the interal lead bias of the MAX30001 is to be used. + */ + bool bias; + + /** Always zero. Reserved for future parameters. */ + uint8_t _padding[8]; +}; + +/** + * Enable a MAX30001 ecg sensor. Calling this funciton 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. + * :returns: A sensor descriptor which can be used with + * :c:func:`epic_stream_read` or a negative error value: + * + * - ``-EBUSY``: The MAX30001 driver is currently busy with other tasks and + * could not be acquired for enabling a sensor. + * + * .. versionadded:: 1.6 + */ +API(API_MAX30001_ENABLE, int epic_max30001_enable_sensor( + struct max30001_sensor_config *config +)); + +/** + * Disable MAX30001 + * + * .. versionadded:: 1.6 + */ +API(API_MAX30001_DISABLE, int epic_max30001_disable_sensor( +void +)); + + #endif /* _EPICARDIUM_H */ diff --git a/epicardium/main.c b/epicardium/main.c index 8c260528f12a535501a22bbde882b8d4f5d0fa88..c7f58a05c889fc0c47eafb97d1f2995a8afce881 100644 --- a/epicardium/main.c +++ b/epicardium/main.c @@ -62,6 +62,17 @@ int main(void) abort(); } + /* MAX30001 */ + if (xTaskCreate( + vMAX30001Task, + (const char *)"MAX30001 Driver", + configMINIMAL_STACK_SIZE * 2, + NULL, + tskIDLE_PRIORITY + 1, + NULL) != pdPASS) { + LOG_CRIT("startup", "Failed to create %s task!", "MAX30001"); + abort(); + } /* API */ if (xTaskCreate( vApiDispatcher, diff --git a/epicardium/modules/MAX30003.h b/epicardium/modules/MAX30003.h new file mode 100644 index 0000000000000000000000000000000000000000..24c5d97f3c33ab14441f87a7221a0609d640205e --- /dev/null +++ b/epicardium/modules/MAX30003.h @@ -0,0 +1,278 @@ +/* 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 */ diff --git a/epicardium/modules/hardware.c b/epicardium/modules/hardware.c index 3b2c0696e127e9681344404ca0dba19d95c5913c..6148513eb03014b19ff01873701ae3aedefb582f 100644 --- a/epicardium/modules/hardware.c +++ b/epicardium/modules/hardware.c @@ -269,5 +269,7 @@ int hardware_reset(void) */ epic_bme680_deinit(); + epic_max30001_disable_sensor(); + return 0; } diff --git a/epicardium/modules/max30001.c b/epicardium/modules/max30001.c new file mode 100644 index 0000000000000000000000000000000000000000..5cc3ef3654e19b6ea298ab7ba8e019676dfc4332 --- /dev/null +++ b/epicardium/modules/max30001.c @@ -0,0 +1,434 @@ +#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 "semphr.h" +#include "queue.h" + +#include "api/interrupt-sender.h" +#include "epicardium.h" +#include "modules/log.h" +#include "modules/modules.h" +#include "modules/stream.h" + +/* Ticks to wait when trying to acquire lock */ +#define LOCK_WAIT pdMS_TO_TICKS(MAX30001_MUTEX_WAIT_MS) + +/* 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 StaticSemaphore_t max30001_mutex_data; +static SemaphoreHandle_t max30001_mutex = NULL; + +/* 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; + + result = hwlock_acquire(HWLOCK_SPI_ECG, pdMS_TO_TICKS(100)); + if (result < 0) { + return result; + } + + if (xSemaphoreTake(max30001_mutex, LOCK_WAIT) != pdTRUE) { + result = -EBUSY; + goto out_free_spi; + } + + 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: + xSemaphoreGive(max30001_mutex); +out_free_spi: + hwlock_release(HWLOCK_SPI_ECG); + return result; +} + +int epic_max30001_disable_sensor(void) +{ + int result = 0; + + result = hwlock_acquire(HWLOCK_SPI_ECG, pdMS_TO_TICKS(100)); + if (result < 0) { + return result; + } + + if (xSemaphoreTake(max30001_mutex, LOCK_WAIT) != pdTRUE) { + result = -EBUSY; + goto out_free_spi; + } + + 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: + xSemaphoreGive(max30001_mutex); +out_free_spi: + hwlock_release(HWLOCK_SPI_ECG); + 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++; + if (xQueueSend( + max30001_stream.queue, + &data, + MAX30001_MUTEX_WAIT_MS) != pdTRUE) { + LOG_WARN( + "max30001", + "queue full"); // TODO; handle queue full + } + } + api_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; + + result = hwlock_acquire(HWLOCK_SPI_ECG, pdMS_TO_TICKS(100)); + if (result < 0) { + return result; + } + + if (xSemaphoreTake(max30001_mutex, LOCK_WAIT) != pdTRUE) { + result = -EBUSY; + goto out_free_spi; + } + + uint32_t ecgFIFO, readECGSamples, ETAG[32], status; + int16_t ecgSample[32]; + const int EINT_STATUS_MASK = 1 << 23; + const int FIFO_OVF_MASK = 0x7; + const int FIFO_VALID_SAMPLE_MASK = 0x0; + const int FIFO_FAST_SAMPLE_MASK = 0x1; + const int 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); + } + + xSemaphoreGive(max30001_mutex); +out_free_spi: + hwlock_release(HWLOCK_SPI_ECG); + 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 vMAX30001Task(void *pvParameters) +{ + max30001_task_id = xTaskGetCurrentTaskHandle(); + max30001_mutex = xSemaphoreCreateMutexStatic(&max30001_mutex_data); + + int lockret = hwlock_acquire(HWLOCK_SPI_ECG, pdMS_TO_TICKS(100)); + if (lockret < 0) { + LOG_CRIT("max30001", "Failed to acquire SPI lock!"); + vTaskDelay(portMAX_DELAY); + } + + /* Take Mutex during initialization, just in case */ + if (xSemaphoreTake(max30001_mutex, 0) != pdTRUE) { + LOG_CRIT("max30001", "Failed to acquire MAX30001 mutex!"); + vTaskDelay(portMAX_DELAY); + } + + /* 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 + + xSemaphoreGive(max30001_mutex); + hwlock_release(HWLOCK_SPI_ECG); + + /* ----------------------------------------- */ + + while (1) { + if (max30001_sensor_active) { + int ret = max30001_fetch_fifo(); + if (ret == -EBUSY) { + LOG_WARN( + "max30001", "Could not acquire mutex?" + ); + continue; + } else 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)); + } +} diff --git a/epicardium/modules/meson.build b/epicardium/modules/meson.build index 736b3efec72ee9462833e574c8b1c7a5ae32ea01..1aa7494ea0eff9003b48f0aa41a63257b1ab3833 100644 --- a/epicardium/modules/meson.build +++ b/epicardium/modules/meson.build @@ -12,6 +12,7 @@ module_sources = files( 'lifecycle.c', 'light_sensor.c', 'log.c', + 'max30001.c', 'personal_state.c', 'pmic.c', 'rtc.c', diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index d6d045f2e8cc4e45a59e7a9a10495fa0373f9333..144b18fb765a78580d66a8e8b59a9593d8127a77 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -73,7 +73,8 @@ void hwlock_init(void); enum hwlock_periph { HWLOCK_I2C = 0, HWLOCK_ADC, - HWLOCK_LED, + HWLOCK_LED, + HWLOCK_SPI_ECG, _HWLOCK_MAX, }; @@ -89,5 +90,7 @@ void disp_forcelock(); #define BHI160_MUTEX_WAIT_MS 50 void vBhi160Task(void *pvParameters); +#define MAX30001_MUTEX_WAIT_MS 50 +void vMAX30001Task(void *pvParameters); #endif /* MODULES_H */ diff --git a/epicardium/modules/stream.c b/epicardium/modules/stream.c index 360e0357b79944c683a9017a936e613d9c3e306a..7453da118ea364db3f510538a617d34762a30dbd 100644 --- a/epicardium/modules/stream.c +++ b/epicardium/modules/stream.c @@ -24,50 +24,63 @@ int stream_init() int stream_register(int sd, struct stream_info *stream) { + int ret = 0; if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) { LOG_WARN("stream", "Lock contention error"); - return -EBUSY; + ret = -EBUSY; + goto out; } if (sd < 0 || sd >= SD_MAX) { - return -EINVAL; + ret = -EINVAL; + goto out_release; } if (stream_table[sd] != NULL) { /* Stream already registered */ - return -EACCES; + ret = -EACCES; + goto out_release; } stream_table[sd] = stream; +out_release: xSemaphoreGive(stream_table_lock); - return 0; +out: + return ret; } int stream_deregister(int sd, struct stream_info *stream) { + int ret = 0; if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) { LOG_WARN("stream", "Lock contention error"); - return -EBUSY; + ret = -EBUSY; + goto out; } if (sd < 0 || sd >= SD_MAX) { - return -EINVAL; + ret = -EINVAL; + goto out_release; } if (stream_table[sd] != stream) { /* Stream registered by someone else */ - return -EACCES; + ret = -EACCES; + goto out_release; } stream_table[sd] = NULL; +out_release: xSemaphoreGive(stream_table_lock); - return 0; +out: + return ret; } int epic_stream_read(int sd, void *buf, size_t count) { + int ret = 0; /* * TODO: In theory, multiple reads on different streams can happen * simulaneously. I don't know what the most efficient implementation @@ -75,29 +88,33 @@ int epic_stream_read(int sd, void *buf, size_t count) */ if (xSemaphoreTake(stream_table_lock, STREAM_MUTEX_WAIT) != pdTRUE) { LOG_WARN("stream", "Lock contention error"); - return -EBUSY; + ret = -EBUSY; + goto out; } if (sd < 0 || sd >= SD_MAX) { - return -EBADF; + ret = -EBADF; + goto out_release; } struct stream_info *stream = stream_table[sd]; if (stream == NULL) { - return -ENODEV; + ret = -ENODEV; + goto out_release; } /* Poll the stream, if a poll_stream function exists */ if (stream->poll_stream != NULL) { int ret = stream->poll_stream(); if (ret < 0) { - return ret; + goto out_release; } } /* Check buffer size is a multiple of the data packet size */ if (count % stream->item_size != 0) { - return -EINVAL; + ret = -EINVAL; + goto out_release; } size_t i; @@ -107,6 +124,10 @@ int epic_stream_read(int sd, void *buf, size_t count) } } + ret = i / stream->item_size; + +out_release: xSemaphoreGive(stream_table_lock); - return i / stream->item_size; +out: + return ret; } diff --git a/epicardium/modules/stream.h b/epicardium/modules/stream.h index 41064bd5d716bc340721bd7b36267d7a648ec45f..1b4cba074035c6b8d6e276f9b01b5c0fecbe19f3 100644 --- a/epicardium/modules/stream.h +++ b/epicardium/modules/stream.h @@ -29,6 +29,7 @@ enum stream_descriptor { SD_BHI160_ACCELEROMETER, SD_BHI160_ORIENTATION, SD_BHI160_GYROSCOPE, + SD_MAX30001_ECG, /** Highest descriptor must always be ``SD_MAX``. */ SD_MAX, };