本篇我们来点亮iic协议的心率血氧模块(主控MAX30102)。
接线跟OLED类似,模块的iic SCL、SDA线分别对接开发板的SCL(P0.16)、SDA(P0.17),也是开发板的i2c1模块。
涉及到MAX30102的函数接口,都已经定义在MAX30102.c和MAX30102.h中
我们先了解下MAX30102.h中提供了哪些函数
#include <stdint.h> #ifndef MAX30102_H_ #define MAX30102_H_ #endif /* MAX30102_H_ */ // ---------- MAX30102 寄存器 ---------- #define MAX30102_ADDR 0x57 // 7-bit I2C 地址 #define REG_INT_STATUS_1 0x00 #define REG_INT_ENABLE_1 0x02 #define REG_FIFO_WR_PTR 0x04 #define REG_OVF_COUNTER 0x05 #define REG_FIFO_RD_PTR 0x06 #define REG_FIFO_DATA 0x07 #define REG_MODE_CONFIG 0x09 #define REG_SPO2_CONFIG 0x0A #define REG_LED1_PA 0x0C // RED LED #define REG_LED2_PA 0x0D // IR LED #define REG_TEMP_INTEGER 0x1F #define REG_TEMP_FRACTION 0x20 #define REG_PART_ID 0xFF int max30102_write_reg(uint8_t reg, uint8_t value); //写寄存器 int max30102_read_reg(uint8_t reg, uint8_t *value); //读寄存器 int max30102_read_multi(uint8_t reg, uint8_t *buf, int len); //读多个字节 int max30102_init(void); //max30102初始化 int max30102_read_fifo(uint32_t *red, uint32_t *ir); //读max30102的fifo
具体的实现都在MAX30102.c中
其实可以看到写寄存器与读寄存器是同一个函数,
只不过写寄存器的函数
req.rx_buf = NULL;
req.rx_len = 0;
读寄存器的函数,先发送再接收
req.tx_buf = NULL;
req.tx_len = 0;
#include "MAX30102.h" #include "mxc_device.h" #include "mxc_delay.h" #include "i2c.h" #include <stdio.h> #include <string.h> #include "algorithm.h" #include "ssd1306.h" #define MA4_SIZE 4 #define HAMMING_SIZE 5 // I2C 使用 I2C1 #define I2C_INST MXC_I2C1 // ---------------- I2C 基础读写函数 ---------------- int max30102_write_reg(uint8_t reg, uint8_t value) { uint8_t data[2] = {reg, value}; mxc_i2c_req_t req; req.i2c = I2C_INST; req.addr = MAX30102_ADDR; req.tx_buf = data; req.tx_len = 2; req.rx_buf = NULL; req.rx_len = 0; req.restart = 0; req.callback = NULL; return MXC_I2C_MasterTransaction(&req); } int max30102_read_reg(uint8_t reg, uint8_t *value) { mxc_i2c_req_t req; int err; req.i2c = I2C_INST; req.addr = MAX30102_ADDR; req.tx_buf = ® req.tx_len = 1; req.rx_buf = NULL; req.rx_len = 0; req.restart = 1; // Repeated START req.callback = NULL; err = MXC_I2C_MasterTransaction(&req); if (err != E_NO_ERROR) return err; req.tx_buf = NULL; req.tx_len = 0; req.rx_buf = value; req.rx_len = 1; req.restart = 0; req.callback = NULL; return MXC_I2C_MasterTransaction(&req); } int max30102_read_multi(uint8_t reg, uint8_t *buf, int len) { mxc_i2c_req_t req; int err; req.i2c = I2C_INST; req.addr = MAX30102_ADDR; req.tx_buf = ® req.tx_len = 1; req.rx_buf = NULL; req.rx_len = 0; req.restart = 1; req.callback = NULL; err = MXC_I2C_MasterTransaction(&req); if (err != E_NO_ERROR) return err; req.tx_buf = NULL; req.tx_len = 0; req.rx_buf = buf; req.rx_len = len; req.restart = 0; req.callback = NULL; return MXC_I2C_MasterTransaction(&req); } // ---------------- MAX30102 初始化 ---------------- int max30102_init(void) { int ret; uint8_t part_id; MXC_I2C_Shutdown(I2C_INST); MXC_I2C_Init(I2C_INST, 1, 0); // master mode MXC_I2C_SetFrequency(I2C_INST, 400000); // 400kHz ret = max30102_read_reg(REG_PART_ID, &part_id); if (ret != E_NO_ERROR) { printf("I2C read failed\n"); return ret; } if (part_id != 0x15) { printf("MAX30102 not found! Part ID: 0x%02X\n", part_id); return -1; } printf("MAX30102 detected, Part ID: 0x%02X\n", part_id); max30102_write_reg(REG_MODE_CONFIG, 0x40); MXC_Delay(MXC_DELAY_MSEC(10)); max30102_write_reg(REG_MODE_CONFIG, 0x03); // SpO2 模式 max30102_write_reg(REG_SPO2_CONFIG, 0x27); // 18-bit, 100Hz max30102_write_reg(REG_LED1_PA, 0x24); // 红光 ~7mA max30102_write_reg(REG_LED2_PA, 0x24); // IR ~7mA return 0; } // ---------------- 读取 FIFO 数据 ---------------- int max30102_read_fifo(uint32_t *red, uint32_t *ir) { uint8_t buf[6]; int ret = max30102_read_multi(REG_FIFO_DATA, buf, 6); if (ret != E_NO_ERROR) return ret; *red = ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2]; *ir = ((uint32_t)buf[3] << 16) | ((uint32_t)buf[4] << 8) | buf[5]; *red &= 0x3FFFF; // 18-bit *ir &= 0x3FFFF; return 0; }
在main.c函数中,我们看下
int main(void) { uint32_t red, ir; if (max30102_init() != 0) //初始化 { printf("MAX30102 init failed!\n"); return 0; } while (1) { if (max30102_read_fifo(&red, &ir) == 0) //读fifo { red_buffer[buffer_index] = red; ir_buffer[buffer_index] = ir; buffer_index++; if (buffer_index >= BUFFER_SIZE) { int32_t spo2, heart_rate; int8_t spo2_valid, hr_valid; // 调用算法 maxim_heart_rate_and_oxygen_saturation( ir_buffer, BUFFER_SIZE, red_buffer, &spo2, &spo2_valid, &heart_rate, &hr_valid); // 打印心率 if (hr_valid && heart_rate > 40 && heart_rate < 180) //40~180 { printf("心率 = %d BPM, ", heart_rate); } else { printf("心率 = --- , "); } // 打印血氧 if (spo2_valid && spo2 > 70 && spo2 <= 100) { printf("血氧 = %d%%\n", spo2); } else { printf("血氧 = ---\n"); } // 重置 buffer buffer_index = 0; } } // 100Hz 采样 MXC_Delay(MXC_DELAY_MSEC(5)); } }
其实就是
初始化MAX30102
读取MAX30102的fifo,赋值red值及ir值
一直读取到Buffer size(800个)
调用算法,将red值及ir值通过运算转化为心率和血氧,具体算法参考官方示例,这里就不作具体展示了
5. 通过串口打印出心率和血氧
运行结果如下:
模块亮红灯。串口输出心率和血氧数据。
ua