Commit 36a774a8 authored by schneider's avatar schneider

Merge branch 'max86150' into 'master'

Stable max86150 epicardium API

See merge request !371
parents f17417e1 d92965df
Pipeline #4492 passed with stages
in 2 minutes and 33 seconds
......@@ -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,
};
......
This diff is collapsed.
......@@ -18,86 +18,115 @@
#include <stdint.h>
#include <stdbool.h>
typedef uint8_t byte;
bool max86150_begin(void);
uint32_t max86150_getRed(void); //Returns immediate red value
uint32_t max86150_getIR(void); //Returns immediate IR value
int32_t max86150_getECG(void); //Returns immediate ECG value
bool max86150_safeCheck(uint8_t maxTimeToCheck); //Given a max amount of time, check for new data
// Configuration
void max86150_softReset();
void max86150_shutDown();
void max86150_wakeUp();
static const uint8_t MAX86150_SAMPLEAVG_1 = 0b000;
static const uint8_t MAX86150_SAMPLEAVG_2 = 0b001;
static const uint8_t MAX86150_SAMPLEAVG_4 = 0b010;
static const uint8_t MAX86150_SAMPLEAVG_8 = 0b011;
static const uint8_t MAX86150_SAMPLEAVG_16 = 0b100;
static const uint8_t MAX86150_SAMPLEAVG_32 = 0b101;
static const uint8_t MAX86150_ADCRANGE_4096 = 0b00000000;
static const uint8_t MAX86150_ADCRANGE_8192 = 0b01000000;
static const uint8_t MAX86150_ADCRANGE_16384 = 0b10000000;
static const uint8_t MAX86150_ADCRANGE_32768 = 0b11000000;
static const uint8_t MAX86150_PPG_SAMPLERATE_10 = 0b00000000;
static const uint8_t MAX86150_PPG_SAMPLERATE_20 = 0b00000100;
static const uint8_t MAX86150_PPG_SAMPLERATE_50 = 0b00001000;
static const uint8_t MAX86150_PPG_SAMPLERATE_84 = 0b00001100;
static const uint8_t MAX86150_PPG_SAMPLERATE_100 = 0b00010000;
static const uint8_t MAX86150_PPG_SAMPLERATE_200 = 0b00010100;
static const uint8_t MAX86150_PPG_SAMPLERATE_400 = 0b00011000;
static const uint8_t MAX86150_PPG_SAMPLERATE_800 = 0b00011100;
static const uint8_t MAX86150_PPG_SAMPLERATE_1000 = 0b00100000;
static const uint8_t MAX86150_PPG_SAMPLERATE_1600 = 0b00100100;
static const uint8_t MAX86150_PPG_SAMPLERATE_3200 = 0b00101000;
static const uint8_t MAX86150_PPG_PULSEWIDTH_50 = 0b00;
static const uint8_t MAX86150_PPG_PULSEWIDTH_100 = 0b01;
static const uint8_t MAX86150_PPG_PULSEWIDTH_200 = 0b10;
static const uint8_t MAX86150_PPG_PULSEWIDTH_400 = 0b11;
static const uint8_t MAX86150_SLOT_NONE = 0b0000;
static const uint8_t MAX86150_SLOT_RED_LED = 0b0010;
static const uint8_t MAX86150_SLOT_IR_LED = 0b0001;
static const uint8_t MAX86150_SLOT_RED_PILOT = 0b0110;
static const uint8_t MAX86150_SLOT_IR_PILOT = 0b0101;
static const uint8_t MAX86150_SLOT_ECG = 0b1001;
static const uint8_t MAX86150_LED1_RANGE_50 = 0b00;
static const uint8_t MAX86150_LED1_RANGE_100 = 0b01;
static const uint8_t MAX86150_LED2_RANGE_50 = 0b0000;
static const uint8_t MAX86150_LED2_RANGE_100 = 0b0100;
static const uint8_t MAX86150_ECG_SAMPLERATE_200 = 0b011;
static const uint8_t MAX86150_ECG_SAMPLERATE_400 = 0b010;
static const uint8_t MAX86150_ECG_SAMPLERATE_800 = 0b001;
static const uint8_t MAX86150_ECG_SAMPLERATE_1600 = 0b000;
static const uint8_t MAX86150_ECG_SAMPLERATE_3200 = 0b100;
static const uint8_t MAX86150_ECG_PGA_GAIN_1 = 0b0000;
static const uint8_t MAX86150_ECG_PGA_GAIN_2 = 0b0100;
static const uint8_t MAX86150_ECG_PGA_GAIN_4 = 0b1000;
static const uint8_t MAX86150_ECG_PGA_GAIN_8 = 0b1100;
static const uint8_t MAX86150_ECG_IA_GAIN_5 = 0b00;
static const uint8_t MAX86150_ECG_IA_GAIN_9_5 = 0b01;
static const uint8_t MAX86150_ECG_IA_GAIN_20 = 0b10;
static const uint8_t MAX86150_ECG_IA_GAIN_50 = 0b11;
void max86150_setLEDMode(uint8_t mode);
void max86150_setADCRange(uint8_t adcRange);
void max86150_setSampleRate(uint8_t sampleRate);
void max86150_setPulseWidth(uint8_t pulseWidth);
void max86150_setPulseAmplitudeRed(uint8_t value);
void max86150_setPulseAmplitudeIR(uint8_t value);
void max86150_setPulseAmplitudeProximity(uint8_t value);
void max86150_setProximityThreshold(uint8_t threshMSB);
bool max86150_begin(void);
//Multi-led configuration mode (page 22)
void max86150_enableSlot(uint8_t slotNumber, uint8_t device); //Given slot number, assign a device to slot
uint8_t max86150_get_int1(void);
uint8_t max86150_get_int2(void);
void max86150_set_int_full(bool enabled);
void max86150_set_int_datardy(bool enabled);
void max86150_set_int_ambient_light_overflow(bool enabled);
void max86150_set_int_proximity(bool enabled);
void max86150_soft_reset(void);
void max86150_shut_down(void);
void max86150_wake_up(void);
void max86150_set_ppg_adc_range(uint8_t adcRange);
void max86150_set_ppg_sample_rate(uint8_t sampleRate);
void max86150_set_ppg_pulse_width(uint8_t pulseWidth);
void max86150_set_led_red_amplitude(uint8_t amplitude);
void max86150_set_led_ir_amplitude(uint8_t amplitude);
void max86150_set_led_proximity_amplitude(uint8_t amplitude);
void max86150_set_proximity_threshold(uint8_t threshMSB);
void max86150_fifo_enable_slot(uint8_t slotNumber, uint8_t device);
void max86150_disableSlots(void);
// Data Collection
//Interrupts (page 13, 14)
uint8_t max86150_getINT1(void); //Returns the main interrupt group
uint8_t max86150_getINT2(void); //Returns the temp ready interrupt
void max86150_enableAFULL(void); //Enable/disable individual interrupts
void max86150_disableAFULL(void);
void max86150_enableDATARDY(void);
void max86150_disableDATARDY(void);
void max86150_enableALCOVF(void);
void max86150_disableALCOVF(void);
void max86150_enablePROXINT(void);
void max86150_disablePROXINT(void);
void max86150_enableDIETEMPRDY(void);
void max86150_disableDIETEMPRDY(void);
//FIFO Configuration (page 18)
void max86150_setFIFOAverage(uint8_t samples);
void max86150_enableFIFORollover();
void max86150_disableFIFORollover();
void max86150_setFIFOAlmostFull(uint8_t samples);
//FIFO Reading
uint16_t max86150_check(void); //Checks for new data and fills FIFO
uint8_t max86150_available(void); //Tells caller how many new samples are available (head - tail)
void max86150_nextSample(void); //Advances the tail of the sense array
uint32_t max86150_getFIFORed(void); //Returns the FIFO sample pointed to by tail
uint32_t max86150_getFIFOIR(void); //Returns the FIFO sample pointed to by tail
int32_t max86150_getFIFOECG(void); //Returns the FIFO sample pointed to by tail
uint8_t max86150_getWritePointer(void);
uint8_t max86150_getReadPointer(void);
void max86150_clearFIFO(void); //Sets the read/write pointers to zero
//Proximity Mode Interrupt Threshold
void max86150_setPROXINTTHRESH(uint8_t val);
// Die Temperature
float max86150_readTemperature();
float max86150_readTemperatureF();
// Detecting ID/Revision
uint8_t max86150_getRevisionID();
uint8_t max86150_readPartID();
uint8_t max86150_readRegLED();
// Setup the IC with user selectable settings
//void max86150_setup(byte powerLevel = 0x1F, byte sampleAverage = 4, byte ledMode = 3, int sampleRate = 400, int pulseWidth = 411, int adcRange = 4096);
void max86150_setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange);
// Low-level I2C communication
uint8_t max86150_readRegister8(uint8_t address, uint8_t reg);
void max86150_writeRegister8(uint8_t address, uint8_t reg, uint8_t value);
void max86150_set_ppg_averaging(uint8_t numberOfSamples);
void max86150_clear_fifo(void);
void max86150_set_fifo_rollover(bool enabled);
void max86150_set_fifo_almost_full_clear(bool enabled);
void max86150_set_fifo_almost_full_repeat(bool enabled);
void max86150_set_fifo_almost_full(uint8_t numberOfSamples);
uint8_t max86150_get_fifo_write_pointer(void);
uint8_t max86150_get_fifo_read_pointer(void);
uint8_t max86150_read_part_id();
void max86150_set_ecg_sample_rate(uint8_t sampleRate);
void max86150_set_ecg_pga_gain(uint8_t gain);
void max86150_set_ecg_instrumentation_amplifier_gain(uint8_t gain);
void max86150_setup(const uint8_t ppg_sample_rate);
uint8_t max86150_available(void);
uint32_t max86150_get_red(void);
uint32_t max86150_get_ir(void);
int32_t max86150_get_ecg(void);
uint32_t max86150_get_fifo_red(void);
uint32_t max86150_get_fifo_ir(void);
int32_t max86150_get_fifo_ecg(void);
void max86150_next_sample(void);
uint8_t max86150_get_sample(uint32_t *red, uint32_t *ir, int32_t *ecg);
uint16_t max86150_check(void);
bool max86150_safe_check(uint8_t max_tries);
void max86150_bit_mask(uint8_t reg, uint8_t mask, uint8_t thing);
uint8_t max86150_read_register(uint8_t address, uint8_t reg);
void max86150_write_register(uint8_t address, uint8_t reg, uint8_t value);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment