【前言】
本篇将使用模拟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的冲突。
我要赚赏金
