【前言】
本篇将使用模拟IIC来驱动MAX30102传感器。
【IO选择以及初始化】
前面我使用了硬件IIC来实现驱动OLED屏,但是在驱动MAX30102时,发现工作一段时间后,recv会报错,因此修改为模拟IIC更加稳定。
从开发板的原理图中,我选择了P2_6、P2_7来做为MAX30102的SCL、SDA来做为IO接口。
1、首先创建xiic.h/xiic.c入入工程中:
2、宏定义端口,以及SDA、SCL的高低电平:
#define MXC_GPIO_PORT_SDA MXC_GPIO2 #define MXC_GPIO_PORT_SCL MXC_GPIO2 #define MXC_GPIO_PIN_SDA MXC_GPIO_PIN_6 #define MXC_GPIO_PIN_SCL MXC_GPIO_PIN_7 #define IIC_SCL_1 MXC_GPIO_OutSet(gpio_scl_out.port, gpio_scl_out.mask) #define IIC_SCL_0 MXC_GPIO_OutClr(gpio_scl_out.port, gpio_scl_out.mask) #define IIC_SDA_1 MXC_GPIO_OutSet(gpio_sda_out.port, gpio_sda_out.mask) #define IIC_SDA_0 MXC_GPIO_OutClr(gpio_sda_out.port, gpio_sda_out.mask) #define IIC_SDA_READ MXC_GPIO_InGet(gpio_sda_out.port, gpio_sda_out.mask)
3、初始化IIC
mxc_gpio_cfg_t gpio_sda_out; mxc_gpio_cfg_t gpio_scl_out; void IIC_GPIO_INIT(void) { gpio_sda_out.port = MXC_GPIO_PORT_SDA; gpio_sda_out.mask = MXC_GPIO_PIN_SDA; gpio_sda_out.pad = MXC_GPIO_PAD_NONE; gpio_sda_out.func = MXC_GPIO_FUNC_OUT; gpio_sda_out.vssel = MXC_GPIO_VSSEL_VDDIOH; gpio_sda_out.drvstr = MXC_GPIO_DRVSTR_0; MXC_GPIO_Config(&gpio_sda_out); gpio_scl_out.port = MXC_GPIO_PORT_SCL; gpio_scl_out.mask = MXC_GPIO_PIN_SCL; gpio_scl_out.pad = MXC_GPIO_PAD_NONE; gpio_scl_out.func = MXC_GPIO_FUNC_OUT; gpio_scl_out.vssel = MXC_GPIO_VSSEL_VDDIOH; gpio_scl_out.drvstr = MXC_GPIO_DRVSTR_0; MXC_GPIO_Config(&gpio_scl_out); }
4、IIC的基础配置到这里就结束,基他的IIC读取详见如下的驱动源码:
/* * xiic.c * * Created on: 2025年6月21日 * Author: liujianhua */ #include "xiic.h" #include <stdint.h> #include "mxc_device.h" #include "mxc_delay.h" #include "nvic_table.h" #include "board.h" #include "gpio.h" uint8_t ack; mxc_gpio_cfg_t gpio_sda_out; mxc_gpio_cfg_t gpio_scl_out; void IIC_GPIO_INIT(void) { gpio_sda_out.port = MXC_GPIO_PORT_SDA; gpio_sda_out.mask = MXC_GPIO_PIN_SDA; gpio_sda_out.pad = MXC_GPIO_PAD_NONE; gpio_sda_out.func = MXC_GPIO_FUNC_OUT; gpio_sda_out.vssel = MXC_GPIO_VSSEL_VDDIOH; gpio_sda_out.drvstr = MXC_GPIO_DRVSTR_0; MXC_GPIO_Config(&gpio_sda_out); gpio_scl_out.port = MXC_GPIO_PORT_SCL; gpio_scl_out.mask = MXC_GPIO_PIN_SCL; gpio_scl_out.pad = MXC_GPIO_PAD_NONE; gpio_scl_out.func = MXC_GPIO_FUNC_OUT; gpio_scl_out.vssel = MXC_GPIO_VSSEL_VDDIOH; gpio_scl_out.drvstr = MXC_GPIO_DRVSTR_0; MXC_GPIO_Config(&gpio_scl_out); } void IIC_Delay(void) { uint8_t i=6; //i=10延时1.5us//这里可以优化速度 ,经测试最低到5还能写入 while(i--); // delay_us(1); } void IIC_Start(void) { IIC_SCL_1; IIC_SDA_1;//启始信号建立时间 0.6us 400KHz IIC_Delay(); IIC_SDA_0; IIC_Delay();//启始信号保持时间0.6us IIC_SCL_0; IIC_Delay();//时钟低电平时间1.3us } void IIC_Stop(void) { IIC_SDA_0; IIC_SCL_1; IIC_Delay();//结束信号建立时间0.6us IIC_SDA_1; IIC_Delay();//总线空闲时间时间1.3us } void IIC_Send_Byte(uint8_t byte) { uint8_t i;//先发送高位 for(i=0;i<8;i++) { if(byte & 0x80) { IIC_SDA_1; } else { IIC_SDA_0; } IIC_Delay(); IIC_SCL_1; IIC_Delay(); IIC_SCL_0; IIC_Delay(); byte<<=1; } IIC_SDA_1; IIC_Delay(); IIC_SCL_1; IIC_Delay(); if(IIC_SDA_READ) { ack=1; } else { ack=0; } IIC_SCL_0; IIC_Delay(); } uint8_t IIC_Receive_Byte(void) { uint8_t receive=0; uint8_t i;//置数据线为输入方式 for(i=0;i<8;i++) { receive<<=1; IIC_SCL_1;//置时钟线为高使数据线上数据有效 IIC_Delay(); if(IIC_SDA_READ) { receive++;//读数据位,接收的数据位放入retc中 } IIC_SCL_0; IIC_Delay(); } return receive; } uint8_t IIC_Write_Byte(uint8_t device_addr,uint8_t register_addr,uint8_t data) { IIC_Start(); IIC_Send_Byte(device_addr+0); if (ack == 1)return 0; IIC_Send_Byte(register_addr); if (ack == 1)return 0; IIC_Send_Byte(data); if (ack == 1)return 0; IIC_Stop(); return 1; } void I2C_Ack(uint8_t a) { if(a) { IIC_SDA_1; //非应答 IIC_Delay(); IIC_SCL_1; IIC_Delay(); IIC_SCL_0; IIC_Delay(); } else { IIC_SDA_0; //应答 IIC_Delay(); IIC_SCL_1; IIC_Delay(); IIC_SCL_0; IIC_Delay(); IIC_SDA_1; } } uint8_t IIC_Read_Byte(uint8_t device_addr,uint8_t register_addr) { uint8_t read_data; IIC_Start(); IIC_Send_Byte(device_addr+0); if (ack == 1)return 0; IIC_Send_Byte(register_addr); if (ack == 1)return 0; IIC_Start(); IIC_Send_Byte(device_addr+1); if (ack == 1)return 0; read_data = IIC_Receive_Byte(); I2C_Ack(1); IIC_Stop(); return read_data; } uint8_t IIC_Write_Array(uint8_t device_addr,uint8_t register_addr,uint8_t *Data,uint16_t Num) { uint16_t i; IIC_Start(); IIC_Send_Byte(device_addr+0); if (ack == 1)return 0; IIC_Send_Byte(register_addr); if (ack == 1)return 0; for(i=0;i<Num;i++) { IIC_Send_Byte(*Data++); if (ack == 1)return 0; } IIC_Stop(); return 1; } uint8_t IIC_Read_Array(uint8_t device_addr,uint8_t register_addr,uint8_t *Data,uint16_t Num) { uint16_t i; IIC_Start(); IIC_Send_Byte(device_addr+0); if (ack == 1)return 0; IIC_Send_Byte(register_addr); if (ack == 1)return 0; IIC_Start(); IIC_Send_Byte(device_addr+1); if (ack == 1)return 0; for(i=0;i<Num;i++) { *Data++ = IIC_Receive_Byte(); if(i==Num-1) I2C_Ack(1); else I2C_Ack(0); } IIC_Stop(); return 1; }
【MAX30102驱动适配】
1、主要需要适配写入与读取函数,代码如下:
uint8_t max30102_Bus_Write(uint8_t Register_Address, uint8_t Word_Data) { uint8_t data[1]; data[0] = Word_Data; return IIC_Write_Array(max30100_WR_address,Register_Address,data,1); } uint8_t max30102_Bus_Read(uint8_t Register_Address) { return IIC_Read_Byte(max30100_WR_address,Register_Address); }
2、初始化配置:
void max30102_init(void) { hardIIC_init(); max30102_reset(); max30102_Bus_Write(REG_INTR_ENABLE_1, 0xc0); // INTR setting max30102_Bus_Write(REG_INTR_ENABLE_2, 0x00); max30102_Bus_Write(REG_FIFO_WR_PTR, 0x00); // FIFO_WR_PTR[4:0] max30102_Bus_Write(REG_OVF_COUNTER, 0x00); // OVF_COUNTER[4:0] max30102_Bus_Write(REG_FIFO_RD_PTR, 0x00); // FIFO_RD_PTR[4:0] max30102_Bus_Write(REG_FIFO_CONFIG, 0x0f); // sample avg = 1, fifo rollover=false, fifo almost full = 17 max30102_Bus_Write(REG_MODE_CONFIG, 0x03); // 0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED max30102_Bus_Write(REG_SPO2_CONFIG, 0x27); // SPO2_ADC range = 4096nA, SPO2 sample rate (100 Hz), LED pulseWidth (400uS) max30102_Bus_Write(REG_LED1_PA, 0x24); // Choose value for ~ 7mA for LED1 max30102_Bus_Write(REG_LED2_PA, 0x24); // Choose value for ~ 7mA for LED2 max30102_Bus_Write(REG_PILOT_PA, 0x7f); // Choose value for ~ 25mA for Pilot LED }
3、最后是读取一个fifo的,即读取0x09的6个bit的数据,其代码如下:
void maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led) { uint32_t un_temp; unsigned char uch_temp; unsigned char ach_i2c_data[6]; *pun_red_led = 0; *pun_ir_led = 0; // read and clear status register max30102_Bus_Read(REG_INTR_STATUS_1); max30102_Bus_Read(REG_INTR_STATUS_2); IIC_Read_Array(max30100_WR_address,REG_FIFO_DATA,ach_i2c_data,6); un_temp = (unsigned char)ach_i2c_data[0]; un_temp <<= 16; *pun_red_led += un_temp; un_temp = (unsigned char)ach_i2c_data[1]; un_temp <<= 8; *pun_red_led += un_temp; un_temp = (unsigned char)ach_i2c_data[2]; *pun_red_led += un_temp; un_temp = (unsigned char)ach_i2c_data[3]; un_temp <<= 16; *pun_ir_led += un_temp; un_temp = (unsigned char)ach_i2c_data[4]; un_temp <<= 8; *pun_ir_led += un_temp; un_temp = (unsigned char)ach_i2c_data[5]; *pun_ir_led += un_temp; *pun_red_led &= 0x03FFFF; // Mask MSB [23:18] *pun_ir_led &= 0x03FFFF; // Mask MSB [23:18] if(*pun_red_led<=10000) { *pun_red_led = 0; } if(*pun_ir_led<=10000) { *pun_ir_led = 0; } }
其余的代码移植官方的FFT变换以及血氧分析,可以参观官方的驱动即可。
【实验效果】
【总结】
我在移植MAX30102驱动,起码是使用硬件IIC的,结果写入参数没有问题,但是读取数据时会出现错误,而且是随机的,因此修改为模拟,当然在FreeRTOS环境中,可以创建多任务调度,也不需要考虑IIC的冲突。