这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 第二期-智能手环DIY活动-心率血氧检测

共2条 1/1 1 跳转至

第二期-智能手环DIY活动-心率血氧检测

助工
2025-09-23 17:25:11     打赏

硬件:ADI评估板-MAX78000FTHR#

            ADI脉搏血氧计传感器-MAXREFDES117#    

            显示屏- oled 128*64

image.png

通过 心率传感器获取数据,经过解析,显示到oled上,oled增加了心率数据的实时曲线。

增加一个回调函数定义,配置传感器的时候,传递回调函数指针,函数用于获取实时心率数据。

其中oled 和心率传感器使用iic 接口,控制板有一个iic接口,所以实现的方案里,这两个设备都挂在iic总线上。

同时实现点亮oled和读取心率传感器的操作。

curve_dp cb_func = NULL;
void MAX30102_Config(curve_dp *cb)
{
    cb_func = cb;
    MAX30102_reset();
   
    enable_temp();
    max30102_write_reg(REG_INTR_ENABLE_1,0xc0); //INTR setting
    max30102_write_reg(REG_INTR_ENABLE_2,0x00);//
    max30102_write_reg(REG_FIFO_WR_PTR,0x00);//FIFO_WR_PTR[4:0]
    max30102_write_reg(REG_OVF_COUNTER,0x00);//OVF_COUNTER[4:0]
    max30102_write_reg(REG_FIFO_RD_PTR,0x00);//FIFO_RD_PTR[4:0]
   
    max30102_write_reg(REG_FIFO_CONFIG,0x0f);//sample avg = 1, fifo rollover=false, fifo almost full = 17
    max30102_write_reg(REG_MODE_CONFIG,0x03);//0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LED
    max30102_write_reg(REG_SPO2_CONFIG,0x27);   // SPO2_ADC range = 4096nA, SPO2 sample rate (50 Hz), LED pulseWidth (400uS)  
    max30102_write_reg(REG_LED1_PA,0x32);//Choose value for ~ 10mA for LED1
    max30102_write_reg(REG_LED2_PA,0x32);// Choose value for ~ 10mA for LED2
    max30102_write_reg(REG_PILOT_PA,0x7f);// Choose value for ~ 25mA for Pilot LED
    printf("REG_PART_ID: %x\r\n",max30102_read_reg(REG_PART_ID));
    printf("REG_REV_ID: %x\r\n",max30102_read_reg(REG_REV_ID));
}

读取fifo数据寄存器,并间隔20个数据上报一次实时心率数据。

#define CAL_UNIT            20
uint16_t cal_num=CAL_UNIT;
uint32_t un_min=0xffffff ,un_max=0,un_prev_data=0;
void max30102_read_fifo(uint32_t *fifo_red,uint32_t *fifo_ir)
{
    uint8_t ach_i2c_data[6] = {0};

    //read and clear status register
    max30102_read_reg(REG_INTR_STATUS_1);
    max30102_read_reg(REG_INTR_STATUS_2);

    max30102_read_data(REG_FIFO_DATA,ach_i2c_data,6);


    *fifo_red = (long)((long)((long)ach_i2c_data[0]&0x03)<<16) | (long)ach_i2c_data[1]<<8 | (long)ach_i2c_data[2];  //将值合并得到实际数字,数组400-500为新读取数据
    *fifo_ir =  (long)((long)((long)ach_i2c_data[3]&0x03)<<16) | (long)ach_i2c_data[4]<<8 | (long)ach_i2c_data[5];   //将值合并得到实际数字,数组400-500为新读取数据
    *fifo_red&=0x03FFFF;  //Mask MSB [23:18]
    *fifo_ir&=0x03FFFF;  //Mask MSB [23:18]

    if(!cal_num){
        if(cb_func) {
            uint32_t temp = min(un_max, *fifo_red);
            temp = max(un_min, *fifo_red);
            float_t bili = (float_t)((temp-un_min)*1.0/(un_max - un_min));
            //printf("bili[%f][%d:%d:%d]\n",bili,un_max,temp,un_min);
            cb_func(bili);
        }
        cal_num = CAL_UNIT;
    }else{
        cal_num--;
    }

显示试试心率曲线

void curve_red_data(float bili)
{
    u8g2_t *u8g2 = &u8g2_st;
    u8g2_SetDrawColor(&u8g2_st,0);
    u8g2_DrawBox(&u8g2_st,0, 40, 128, 24);
    u8g2_SetDrawColor(&u8g2_st,1);
    // u8g2_ClearBuffer(u8g2);
    uint8_t psi;
    uint8_t curve_dt = bili*24 + 40;
    curve_dt = min(curve_dt, 64);
    curve_dt = max(curve_dt, 40);
    data_str[bujin] = curve_dt;
    //log_2("curve_dp [%f]data[%d]curve_dt[%d]bujin[%d]",bili,data_str[bujin],curve_dt,bujin);

    bujin = 128 <= (bujin+1) ?0 : (bujin+1);
    for(int iii = 0; iii < 128; iii++){
        psi = iii+bujin;
        if(psi >= 128) psi -= 128;
        u8g2_DrawPixel(u8g2, iii, data_str[psi]);  //
    }

    // uint8_t data_str[10];
    // u8g2_SetFont(u8g2, u8g2_font_wqy15_t_gb2312); /*字库选择*/
    // sprintf((char*)data_str,"%d",curve_dt);
    // u8g2_DrawUTF8(u8g2, 100, 35, data_str);
    u8g2_SendBuffer(u8g2);
}



解析心率和血氧样本,并使用官方算法进行解析。

图数据有效,则返回有效数据标识。

uint8_t get_data_show(uint32_t *hr_rate,uint32_t *spo2_rate)
{
    // 读取和计算max30102数据,总体用缓存的500组数据分析,实际每读取100组新数据分析一次
    un_min = 0x3FFFF;
    un_max = 0;
    uint32_t count = 500; //采样点数
    uint32_t i;
     float f_temp;
    // 将前100组样本转储到内存中(实际没有),并将后400组样本移到顶部,将100-500缓存数据移位到0-400
    for(i = 100; i < 500; i++){
        aun_red_buffer[i - 100] = aun_red_buffer[i]; // 将100-500缓存数据移位到0-400
        aun_ir_buffer[i - 100] = aun_ir_buffer[i]; // 将100-500缓存数据移位到0-400
        // 更新信号的最小值和最大值
        un_min = min(un_min, aun_red_buffer[i]);
        un_max = max(un_max, aun_red_buffer[i]);
    }
    // 在计算心率前取100组样本,取的数据放在400-500缓存数组中
    for(i = 400; i < 500; i++){
        un_prev_data = aun_red_buffer[i - 1]; // 临时记录上一次读取数据
        while(!(max30102_read_reg(REG_INTR_STATUS_1) & 0x40)){
        }
        max30102_read_fifo(&aun_red_buffer[i], &aun_ir_buffer[i]);  //read from MAX30102 FIFO2  

        if(aun_red_buffer[i] > un_prev_data){ // 用新获取的一个数值与上一个数值对比
            f_temp = aun_red_buffer[i] - un_prev_data;
            f_temp /= (un_max - un_min);
            f_temp *= MAX_BRIGHTNESS; // 公式(心率曲线)=(新数值-旧数值)/(最大值-最小值)*255
            n_brightness -= (int)f_temp;
            if(n_brightness < 0)
                n_brightness = 0;
        }else{
            f_temp = un_prev_data - aun_red_buffer[i];
            f_temp /= (un_max - un_min);
            f_temp *= MAX_BRIGHTNESS; // 公式(心率曲线)=(旧数值-新数值)/(最大值-最小值)*255
            n_brightness += (int)f_temp;
            if(n_brightness > MAX_BRIGHTNESS)
                n_brightness = MAX_BRIGHTNESS;
        }
    }
    maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, 500, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); // 传入500个心率和血氧数据计算传感器检测结论,反馈心率和血氧测试结果

    *hr_rate = n_heart_rate;
    *spo2_rate = n_sp02;
    if((1 == ch_hr_valid) && (1 == ch_spo2_valid) && (n_heart_rate < 120) && (n_sp02 < 101))
    {
        printf("HeartRate=%i, BloodOxyg=%i\r\n", n_heart_rate, n_sp02);
        return 1;
    }
    return 0;
}

展示心率和血氧

void showfirstpage(u8g2_t *u8g2)
{
    u8g2_SetFont(u8g2, u8g2_font_wqy12_t_gb2312); /*字库选择*/    
    u8g2_DrawUTF8(u8g2, 20, 15, "脉搏");
    u8g2_DrawUTF8(u8g2, 84, 15, "血氧");
    u8g2_DrawHLine(u8g2, 0, 20, 128);

    u8g2_SendBuffer(u8g2);
    //u8g2_ClearBuffer(u8g2);
    log("showfirstpage done");
}
void showdata(u8g2_t *u8g2,uint32_t hr_rate,uint32_t spo2_rate)
{
    uint8_t data_str[10];
    u8g2_SetFont(u8g2, u8g2_font_wqy15_t_gb2312); /*字库选择*/    //u8g2_font_wqy16_t_gb2312
    sprintf((char*)data_str,"%03d",hr_rate);
    u8g2_DrawUTF8(u8g2, 20, 35, data_str);

    sprintf((char*)data_str,"%03d",spo2_rate);
    u8g2_DrawUTF8(u8g2, 85, 35, data_str);

    u8g2_SendBuffer(u8g2);
}

效果展示

代码

max78000-o2andpuls.zip

-----------------------------------------------------------------------------------------------------------------------------------------

字库自定义

使用github上开源的工具:larryli/u8g2_wqy: 适合 u8g2 的中文字体,采用文泉驿点阵宋体作为源本,提供 12x12、13x13、14x14、15x15 和 16x16 点阵字库。

编译text文件,添加自己需要的字符。

基于源码提供的gb2312.txt,删除不需要的汉字,保留需要的汉字。

可以使用git终端里提供的iconv工具,此工具提供完整的编码。

使用脚本genmaps.sh,生成需要的maps文件,里面是我们需要的字符unicode编码,并且进行了格式化。

再运行脚本 build.sh,生成我们需要的c文件,位置位于src里。

把c文件的数组复制到项目里的fonts.c文件里,就可以使用了。

问题:

1,oled和传感器使用同一个iic链接,数据数据时候,传感器读取一次,立马卡住,死循环在iic驱动代码里,读不到新数据,一直循环。调试堆栈,如下图

image.png

方案:

    因为能够读取一次传感器数据,所以通信问题不大,可能总线锁死,导致无法继续读取,传感器数据也无法发送过来。

在读iic的接口里,增加一个停止接口,主动释放一下总线,问题没了

image.png



院士
2025-09-23 22:11:00     打赏
2楼

楼主的fifo数据设计是不是有点复杂了。



共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]