diff --git a/Documentation/index.rst b/Documentation/index.rst index 561fb9bddf931dc6492f05bbbc94654fc4c8c8d0..0607bdaa5c89d6b1508c25c809375db529a9ee36 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -24,6 +24,7 @@ Last but not least, if you want to start hacking the lower-level firmware, the pycardium/stdlib pycardium/bhi160 pycardium/bme680 + pycardium/max30001 pycardium/buttons pycardium/color pycardium/display diff --git a/Documentation/pycardium/max30001.rst b/Documentation/pycardium/max30001.rst new file mode 100644 index 0000000000000000000000000000000000000000..1fb0ddb954a53dfd04282e16aa549a2de5a9a726 --- /dev/null +++ b/Documentation/pycardium/max30001.rst @@ -0,0 +1,5 @@ +``max30001`` - MAX30001 +===================== + +.. automodule:: max30001 + :members: diff --git a/pycardium/meson.build b/pycardium/meson.build index bb8d03b4fddca90a01be91b8de731abdfd7d3e74..32973bae0c945a0208ba4c1040367dac75b84fb9 100644 --- a/pycardium/meson.build +++ b/pycardium/meson.build @@ -9,6 +9,7 @@ modsrc = files( 'modules/interrupt.c', 'modules/sys_leds.c', 'modules/light_sensor.c', + 'modules/max30001-sys.c', 'modules/os.c', 'modules/personal_state.c', 'modules/power.c', diff --git a/pycardium/modules/interrupt.c b/pycardium/modules/interrupt.c index 927b936b906a8a7a8a952d12bf9940ab76ce8eff..838ef2b3a397887221041804a0477a5d1cfdbce5 100644 --- a/pycardium/modules/interrupt.c +++ b/pycardium/modules/interrupt.c @@ -91,6 +91,9 @@ static const mp_rom_map_elem_t interrupt_module_globals_table[] = { MP_OBJ_NEW_SMALL_INT(EPIC_INT_BHI160_ORIENTATION) }, { MP_ROM_QSTR(MP_QSTR_BHI160_GYROSCOPE), MP_OBJ_NEW_SMALL_INT(EPIC_INT_BHI160_GYROSCOPE) }, + { MP_ROM_QSTR(MP_QSTR_MAX30001_ECG), + MP_OBJ_NEW_SMALL_INT(EPIC_INT_MAX30001_ECG) }, + }; static MP_DEFINE_CONST_DICT( interrupt_module_globals, interrupt_module_globals_table diff --git a/pycardium/modules/max30001-sys.c b/pycardium/modules/max30001-sys.c new file mode 100644 index 0000000000000000000000000000000000000000..08d5bad68fd9bdc4806a875aab6aecab75ba4262 --- /dev/null +++ b/pycardium/modules/max30001-sys.c @@ -0,0 +1,76 @@ +#include "py/obj.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "epicardium.h" +#include "api/common.h" +#include "mphalport.h" + +STATIC mp_obj_t mp_max30001_enable_sensor(size_t n_args, const mp_obj_t *args) +{ + struct max30001_sensor_config cfg = { 0 }; + cfg.usb = mp_obj_is_true(args[0]); + cfg.bias = mp_obj_is_true(args[1]); + cfg.sample_rate = mp_obj_get_int(args[2]); + cfg.sample_buffer_len = mp_obj_get_int(args[3]); + + int stream_id = epic_max30001_enable_sensor(&cfg); + + return MP_OBJ_NEW_SMALL_INT(stream_id); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN( + mp_max30001_enable_sensor_obj, 4, 4, mp_max30001_enable_sensor +); + +STATIC mp_obj_t mp_max30001_read_sensor(mp_obj_t stream_id_in) +{ + int16_t buf[256]; + int stream_id = mp_obj_get_int(stream_id_in); + + int n = epic_stream_read(stream_id, buf, sizeof(buf)); + + mp_obj_list_t *list = mp_obj_new_list(0, NULL); + for (int i = 0; i < n; i++) { + mp_obj_list_append(list, mp_obj_new_int(buf[i])); + } + + return MP_OBJ_FROM_PTR(list); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1( + mp_max30001_read_sensor_obj, mp_max30001_read_sensor +); + +STATIC mp_obj_t mp_max30001_disable_sensor(void) +{ + int ret = epic_max30001_disable_sensor(); + + return MP_OBJ_NEW_SMALL_INT(ret); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_0( + mp_max30001_disable_sensor_obj, mp_max30001_disable_sensor +); + +STATIC const mp_rom_map_elem_t max30001_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_sys_max30001) }, + { MP_ROM_QSTR(MP_QSTR_enable_sensor), + MP_ROM_PTR(&mp_max30001_enable_sensor_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_sensor), + MP_ROM_PTR(&mp_max30001_read_sensor_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_sensor), + MP_ROM_PTR(&mp_max30001_disable_sensor_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT( + max30001_module_globals, max30001_module_globals_table +); + +// Define module object. +const mp_obj_module_t max30001_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&max30001_module_globals, +}; + +/* clang-format off */ +// Register the module to make it available in Python +MP_REGISTER_MODULE(MP_QSTR_sys_max30001, max30001_module, MODULE_MAX30001_ENABLED); diff --git a/pycardium/modules/py/max30001.py b/pycardium/modules/py/max30001.py new file mode 100644 index 0000000000000000000000000000000000000000..67bc5169f4f2ca0020c78054f767bfdae000bab4 --- /dev/null +++ b/pycardium/modules/py/max30001.py @@ -0,0 +1,97 @@ +import sys_max30001 +import interrupt +import ucollections + + +class MAX30001: + """ + The MAX30001 class provides a stram interface to the MAX30001 + ECG and BIO-Z sensor. + + .. code-block:: python + + import max30001 + m = max30001.MAX30001() + m.read() + m.close() + """ + + def __init__( + self, + usb=False, + bias=True, + sample_rate=128, + callback=None, + sample_buffer_len=256, + ): + """ + Initializes the MAX30001 (if it is not already running). + + :param usb: True if all ECG leads should use the USB-C connector + :param bias: True if the internal bias of the ECG should be used. Mandatory if the card10 is not attached to the body and the USB-C connector is used. + :param sample_rate: Selected sample rate in Hz. Supported values: 128 and 256. + :param callback: If not None: A callback which is called with the data when ever new data is available + :param sample_buffer: Length of the internal buffer (in samples) + """ + + self.sample_rate = sample_rate + self.callback = callback + self.sample_buffer_len = sample_buffer_len + self.interrupt_id = interrupt.MAX30001_ECG + self.usb = usb + self.bias = bias + self._callback = callback + self.enable_sensor() + + def enable_sensor(self): + """ + Enables the sensor. Automatically called by __init__. + """ + interrupt.disable_callback(self.interrupt_id) + interrupt.set_callback(self.interrupt_id, self._interrupt) + self.stream_id = sys_max30001.enable_sensor( + self.usb, self.bias, self.sample_rate, self.sample_buffer_len + ) + + if self.stream_id < 0: + raise ValueError("Enable sensor returned %i", self.stream_id) + + self.active = True + + if self._callback: + interrupt.enable_callback(self.interrupt_id) + + def __enter__(self): + return self + + def __exit__(self, _et, _ev, _t): + self.close() + + def close(self): + """ + Close the currently open connection to the sensor. + """ + + if self.active: + self.active = False + ret = sys_max30001.disable_sensor(self.sensor_id) + + if ret < 0: + raise ValueError("Disable sensor returned %i", ret) + + interrupt.disable_callback(self.interrupt_id) + interrupt.set_callback(self.interrupt_id, None) + + def read(self): + """ + Read as many samples (signed integer) as currently available. + """ + if self.active: + return sys_max30001.read_sensor(self.stream_id) + return [] + + def _interrupt(self, _): + if self.active: + data = self.read() + if self._callback: + self._callback(data) diff --git a/pycardium/modules/py/meson.build b/pycardium/modules/py/meson.build index 5548fff7e9fb15c17fec2f6c1c8d9add23a6541d..75bf937c7b2d394d0834542e333bb050328c0823 100644 --- a/pycardium/modules/py/meson.build +++ b/pycardium/modules/py/meson.build @@ -4,6 +4,7 @@ python_modules = files( 'htmlcolor.py', 'display.py', 'leds.py', + 'max30001.py', 'pride.py', 'ledfx.py', 'simple_menu.py', diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index d789c8730c42f68925b4d058fe35e93449b2e4bb..095f989871cbc0c84ee7941a6e21946278928453 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -163,3 +163,5 @@ Q(NO_CONTACT) Q(CHAOS) Q(COMMUNICATION) Q(CAMP) + +Q(MAX30001_ECG) diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h index 43f878706cda83c9d7350f211a9b5ee3686dbea9..aad4320d8516f121552eb0f63d8ebbd91fa841a6 100644 --- a/pycardium/mpconfigport.h +++ b/pycardium/mpconfigport.h @@ -53,6 +53,7 @@ int mp_hal_trng_read_int(void); #define MODULE_INTERRUPT_ENABLED (1) #define MODULE_LEDS_ENABLED (1) #define MODULE_LIGHT_SENSOR_ENABLED (1) +#define MODULE_MAX30001_ENABLED (1) #define MODULE_OS_ENABLED (1) #define MODULE_PERSONAL_STATE_ENABLED (1) #define MODULE_POWER_ENABLED (1)