心率传感器移植指南

本章节将实现心率传感器的在Solution Sensor框架下的移植,以VC32S为参考。

1. 原生驱动移植

本节实现传感器芯片最基础的硬件通信功能,包括I2C总线操作、中断处理和电源管理。

1.1 配置与宏定义

在 sdk\customer\peripherals\sensor 目录下创建以vc32s命名的目录,添加原生驱动vc32s.c。 修改sdk\customer\peripherals\sensor对应心率传感器类型的目录下kconfig文件,新建宏来实现驱动的控制

宏名称

类型

说明

VC32S_I2C_BUS

必需

I2C总线设备名称(如"i2c0")

HR_USING_VC32S

必需

启用VC32S心率血氧传感器

HR_MODEL_NAME

必需

传感器设备模型名称(如"vc32s")

PMIC_CONTROL_SERVICE

可选

启用PMIC电源管理

HR_USR_INT_MODE

可选

启用中断模式

VC32S_POW_PIN

可选

电源控制GPIO引脚号

VC32S_RST_PIN

可选

复位控制GPIO引脚号

VC32S_INT_BIT

可选

中断输入GPIO引脚号

1.2 I2C总线初始化

初始化需要通过宏定义找到对应的总线设备打开并对总线进行对应的参数设置。

rt_err_t vc32s_hw_init(void)
{
    rt_uint16_t flag_open = RT_DEVICE_FLAG_RDWR
#ifdef VC32S_USE_DMA
                            | RT_DEVICE_FLAG_DMA_RX | RT_DEVICE_FLAG_DMA_TX
#endif
                           ;

    vc32s_i2cbus = (struct rt_i2c_bus_device *)rt_device_find(VC32S_I2C_BUS);
    if (RT_Device_Class_I2CBUS != vc32s_i2cbus->parent.type)
    {
        vc32s_i2cbus = NULL;
    }
    if (vc32s_i2cbus)
    {
        rt_device_open((rt_device_t)vc32s_i2cbus, flag_open);
#ifdef VC32S_USE_DMA
        LOG_I("HR vc32s i2c use DMA ! \n");
#endif
        LOG_I("find bus %s;\n", VC32S_I2C_BUS);
    }
    else
    {
        LOG_I("bus not find\n");
        return RT_ERROR;
    }
    {
        struct rt_i2c_configuration configuration =
        {
            .mode = 0,
            .addr = 0,
            .timeout = 200,
            .max_hz = 400000,
        };

        rt_i2c_configure(vc32s_i2cbus, &configuration);
    }

    if (RT_EOK != vc32s_init())
    {
        vc32s_hw_deinit();
        return RT_ERROR;
    }

    LOG_I("hr_hw_init ok\n");

    return RT_EOK;
}
static rt_err_t vc32s_init(void)
{
    uint8_t chip_id = 0;

    vc32sReadRegisters_ex(0x00, &chip_id, 1);
    LOG_I("hr_vc32s_init chip_id =0x%x(should be 0x21)\n", chip_id);

    if (chip_id == VC32S_ID)
    {
        vc32s_id = chip_id;
        return RT_EOK;
    }

    return RT_ERROR;
}

1.3 I2C读写接口

总线读写函数供原生驱动调用,实现寄存器的数据交互。

rt_err_t vc32s_i2c_read(unsigned char regAddr,
                        unsigned char len,
                        unsigned char *data)
{
    rt_int8_t res = 0;
    rt_uint8_t buf[2];
    buf[0] = regAddr;  //cmd
    struct rt_i2c_msg msgs[2];
    msgs[0].addr  = VC32S_SLAVE_ADDRESS;    /* Slave address */
    msgs[0].flags = RT_I2C_WR;              /* Read flag */
    msgs[0].buf   = buf;                    /* Read data pointer */
    msgs[0].len   = 1;                      /* Number of bytes read */

    msgs[1].addr  = VC32S_SLAVE_ADDRESS;    /* Slave address */
    msgs[1].flags = RT_I2C_RD;              /* Read flag */
    msgs[1].buf   = data;                   /* Read data pointer */
    msgs[1].len   = len;                    /* Number of bytes read */

    //LOG_I("vc32s_i2c_read \n");

    if (rt_i2c_transfer(vc32s_i2cbus, msgs, 2) == 2)
    {
        res = RT_EOK;
    }
    else
    {
        res = -RT_ERROR;
    }
    return res;
}
static rt_err_t vc32_i2c_base_write(rt_uint8_t *buf, rt_uint16_t len)
{
    rt_int8_t res = 0;
    struct rt_i2c_msg msgs;

    msgs.addr  = VC32S_SLAVE_ADDRESS;    /* slave address */
    msgs.flags = RT_I2C_WR;              /* write flag */
    msgs.buf   = buf;                    /* Send data pointer */
    msgs.len   = len;

    if (rt_i2c_transfer(vc32s_i2cbus, &msgs, 1) == 1)
    {
        res = RT_EOK;
    }
    else
    {
        res = -RT_ERROR;
    }
    return res;
}
unsigned char vc32s_i2c_write(unsigned char regAddr,
                              unsigned char len,
                              unsigned char *data)
{
    rt_uint8_t buf[32];

    buf[0] = regAddr;   //cmd
    rt_memcpy((buf + 1), data, len);

    /* rt_hw_us_delay(20); */

    if (RT_EOK == vc32_i2c_base_write(buf, (len + 1)))
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}

1.4 中断处理

当启用HR_USR_INT_MODE时,需实现中断初始化、处理函数及线程任务。

static void vc32s_irq_handler(void *arg)
{
    static uint32_t last_time;
    // rt_pin_detach_irq(VC32S_INT_BIT);
    uint32_t cur_time = rt_tick_get();
    uint32_t tick = (cur_time - last_time + UINT32_MAX + 1) & UINT32_MAX;
    if (tick > 10)
    {
        rt_pin_irq_enable(VC32S_INT_BIT, 0);
        //VC32S_LOG_I("vc32s_irq_handler:tick:%d\n", tick);
        rt_sem_release(&vc32_int_sem);
    }
    last_time = cur_time;

}
static void vc32s_sensor_task(void *params)
{
    int32_t ret;
    uint8_t status = 0;

    while (1)
    {
        rt_sem_take(&vc32_int_sem, RT_WAITING_FOREVER);

        //vcHr02ReadRegisters(0x00, &status, 1);
        vc32s_get_data();
        rt_pin_attach_irq(VC32S_INT_BIT, PIN_IRQ_MODE_RISING, vc32s_irq_handler, (void *)(rt_uint32_t)VC32S_INT_BIT);
        rt_pin_irq_enable(VC32S_INT_BIT, 1);
    }
}
static int vc32s_thread_init(void)
{
    rt_sem_init(&vc32_int_sem, "hr_vc32s_sem", 0, RT_IPC_FLAG_FIFO);
    rt_err_t ret = rt_thread_init(&vc32_thread, "vc32s_thread", vc32s_sensor_task, RT_NULL, vc32_thread_stack, VC32S_THRED_STACK_SIZE, RT_THREAD_PRIORITY_HIGH, 20);
    RT_ASSERT(RT_EOK == ret);
    return 0;
}
static void vc32s_thread_deinit(void)
{

    rt_err_t ret =  rt_thread_detach(&vc32_thread);
    RT_ASSERT(RT_EOK == ret);
    rt_sem_detach(&vc32_int_sem);
    VC32S_LOG_I("vc32s_thread_deinit\n");
}
static void vc32s_irq_init(void)
{
    rt_pin_mode(VC32S_INT_BIT, PIN_MODE_INPUT);
    rt_pin_irq_enable(VC32S_INT_BIT, 0);

#ifdef BSP_USING_PM // add v32s INT WAKE
    GPIO_TypeDef *gpio = GET_GPIO_INSTANCE(VC32S_INT_BIT);
    uint16_t gpio_pin = GET_GPIOx_PIN(VC32S_INT_BIT);

    int8_t wakeup_pin = HAL_LPAON_QueryWakeupPin(gpio, gpio_pin);
    RT_ASSERT(wakeup_pin >= 0);
    pm_enable_pin_wakeup(wakeup_pin, AON_PIN_MODE_POS_EDGE);
#endif /* BSP_USING_PM */
}
static void vc32s_irq_deinit(void)
{
#ifdef BSP_USING_PM
    GPIO_TypeDef *gpio = GET_GPIO_INSTANCE(VC32S_INT_BIT);
    int8_t gpio_pin = GET_GPIOx_PIN(VC32S_INT_BIT);

    int8_t wakeup_pin = HAL_LPAON_QueryWakeupPin(gpio, gpio_pin);
    RT_ASSERT(wakeup_pin >= 0);
    pm_disable_pin_wakeup(wakeup_pin);

#endif /* BSP_USING_PM */
    rt_pin_irq_enable(VC32S_INT_BIT, 0);
    rt_pin_detach_irq(VC32S_INT_BIT);
}

1.5 电源管理

void vc32s_hw_power_onoff(bool  onoff)
{
    if (onoff)
    {
#if defined(PMIC_CONTROL_SERVICE)
        pmic_service_control(PMIC_CONTROL_HR, 1);
#endif
#if defined(VC32S_RST_PIN)&&(VC32S_RST_PIN >= 0)
        rt_pin_mode(VC32S_RST_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_RST_PIN, PIN_HIGH);
#endif
#if defined(VC32S_POW_PIN)&&(VC32S_POW_PIN >= 0)
        rt_pin_mode(VC32S_POW_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_POW_PIN, PIN_HIGH);
#endif
#if defined(VC32S_VDDIO_POW_PIN) && (VC32S_VDDIO_POW_PIN>=0)
        rt_pin_mode(VC32S_VDDIO_POW_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_VDDIO_POW_PIN, PIN_HIGH);
#endif
        LOG_I("hr_power_on\n");
    }
    else
    {
#if defined(PMIC_CONTROL_SERVICE)
        pmic_service_control(PMIC_CONTROL_HR, 0);
#endif
#if defined(VC32S_POW_PIN)&&(VC32S_POW_PIN >= 0)
        rt_pin_mode(VC32S_POW_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_POW_PIN, PIN_LOW);
#endif
#if defined(VC32S_VDDIO_POW_PIN) && (VC32S_VDDIO_POW_PIN>=0)
        rt_pin_mode(VC32S_VDDIO_POW_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_VDDIO_POW_PIN, PIN_LOW);
#endif
#if defined(PMIC_CONTROL_SERVICE) || (defined(VC32S_RST_PIN)&&(VC32S_RST_PIN >= 0))
#if defined(VC32S_RST_PIN)&&(VC32S_RST_PIN >= 0)
        rt_pin_mode(VC32S_RST_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_RST_PIN, PIN_LOW);
#endif
#endif
        LOG_I("hr_power_off\n");
    }
}

2.算法移植

本节实现通过算法处理原始数据,将I2C总线上读到的数据转化为可用的处理数据。

2.1 配置

在solution\components\sensor\sensor_algo\hr\vc32s 添加算法驱动vc32s_hr_service.c

2.2 算法处理函数

该函数使用VC32S的原始PPG数据和加速度计数据,通过算法计算出心率、血氧等健康指标。

void vc32s_algo_process(uint8_t hr_type, void *data, void *g_in)
{
    uint8_t algoCallNum = 0;
    uint8_t vcSportFlag = 0;
    vc32s_get_gsensortypedef gsensor_data = {0};
    hrs_raw_data_t *p_data = (hrs_raw_data_t *)data;
    vc32s_raw_data_t *hr_data = (vc32s_raw_data_t *) p_data->p_rawdata;
    //clear health_value
    rt_memset((void *)&health_value, 0, sizeof(vc32s_algo_result_t));
    if (hr_data == RT_NULL)
    {
        VC32S_LOG_I("hr_algo: input data point is NULL!\n");
        return;
    }
    if (hr_data->SampleRate == 0)
    {
        VC32S_LOG_I("hr_algo: input data  is invalid! \n");
        return;
    }
    //VC32S_LOG_I("vc32s_algo_process vcFifoReadFlag = %d, vcPsFlag = %d, wearstatus = %d, SampleRate = %d, ppgLen = %d;\n", \
    //hr_data->vcFifoReadFlag, hr_data->vcPsFlag, hr_data->wearstatus, hr_data->SampleRate, hr_data->ppgLen);


    //VC32S_LOG_I("wearstatus= %d\n", hr_data->wearstatus);

#if defined (USING_GSENSOR_DATA_STORE_LIST) && defined(SENSOR_USING_6D)
    /* Note: when using this function, make sure that the HR cycle is a multiple of the gsensor cycle, otherwise there will be problems */
    vc32s_get_gsensor_data(&gsensor_data, g_in);
#endif

    if (hr_data->vcFifoReadFlag || hr_data->vcPsFlag)
    {
        if (SENSOR_HR == hr_type)
        {
            algoInputData.envSample = hr_data->envValue[0];
            //VC32S_LOG_I("envSample = %d, \n", algoInputData.envSample);
            for (algoCallNum = 0; algoCallNum < 20; algoCallNum++)
            {
                algoInputData.ppgSample = hr_data->ppgValue[algoCallNum];
                algoInputData.axes.x =  gsensor_data.gsensor_x_axis[algoCallNum];     //The direction vertical with ARM.
                algoInputData.axes.y =  gsensor_data.gsensor_y_axis[algoCallNum];    //The direction parallel with ARM.
                algoInputData.axes.z =  gsensor_data.gsensor_z_axis[algoCallNum];   //The direction upside.
                //VC32S_LOG_I("ggp=%d, x=%d, y=%d, z=%d;\n", algoInputData.ppgSample, algoInputData.axes.x, algoInputData.axes.y, algoInputData.axes.z);

                Algo_Input(&algoInputData, 1000 / hr_data->SampleRate, vcSportMode, 1, 0);
            }
            Algo_Output(&algoOutputData);

            health_value.hr = algoOutputData.hrData;
            HeartRateValue = algoOutputData.hrData;
#ifdef VC32S_DEBUG_LOG_EN
            VC32S_LOG_I("vcHr02_process (hr) = %d\n", algoOutputData.hrData);
#endif

        }
        else if (SENSOR_SPO2 == hr_type)
        {
            for (algoCallNum = 0; algoCallNum < 20; algoCallNum++)  //ppglength = 20
            {
                float vcIrPPG = (float)hr_data->ppgValue[algoCallNum * 2];
                float vcRedPPG = (float)hr_data->ppgValue[algoCallNum * 2 + 1];
                float vcSpo2Value = spo2Algo(vcRedPPG, vcIrPPG, 1);
                vcSportFlag = vcSportMotionCalculate(gsensor_data.gsensor_x_axis[algoCallNum], gsensor_data.gsensor_y_axis[algoCallNum], gsensor_data.gsensor_z_axis[algoCallNum]);
                if ((!vcSportFlag) && (vcSpo2Value > 0) && (vcSpo2Value <= 100))
                {
                    health_value.spo2 = (uint8_t)vcSpo2Value;
#ifdef VC32S_DEBUG_LOG_EN
                    VC32S_LOG_I("health_value.spo2 = %d\n", health_value.spo2);
#endif
                }

            }
        }

    }
    else
    {
        if (SENSOR_HR == hr_type)
        {
            health_value.hr = 0;
            HeartRateValue = 0;
            VC32S_LOG_I("no_process (hr) = %d;\n", HeartRateValue);
        }
        else if (SENSOR_SPO2 == hr_type)
        {
            health_value.spo2 = 0;
            VC32S_LOG_I("no _process (SPO2) = %d;\n", health_value.spo2);
        }
    }
#if defined (USING_GSENSOR_DATA_STORE_LIST) &&  defined(SENSOR_USING_6D) && (SportMotionEn)
    if (!hr_data->vcFifoReadFlag)
        green_led_off_state_gsensor_abs_sum_diff_func(gsensor_data.gsensor_x_axis[0], gsensor_data.gsensor_y_axis[0], gsensor_data.gsensor_z_axis[0]);
#endif

    health_value.status = hr_data->wearstatus;

    return ;

}
uint8_t vc32s_algo_postprocess(uint8_t hr_type, hrs_info_t *info)
{
    uint8_t ret = HR_MEAS_NULL;
    static uint32_t meas_sum = 0;
    static uint16_t  meas_count = 0;
    static uint16_t  meas_ok_count = 0;

    if (SENSOR_HR == hr_type || SENSOR_SPO2 == hr_type)
    {
        ret = vc32s_hr_algo_postprocess(health_value.status);
        meas_count++;
        if (((HR_MEAS_OK == ret) && (health_value.hr == 0) && (SENSOR_HR == hr_type))
                || ((HR_MEAS_OK == ret) && (health_value.spo2 == 0) && (SENSOR_SPO2 == hr_type)))
        {
            ret = HR_MEAS_NULL;
        }


        if ((HR_MEAS_OK == ret) && ((health_value.hr > 0) || (health_value.spo2 > 0)))
        {
            meas_ok_count++;
            if (SENSOR_HR == hr_type)
            {
                meas_sum += health_value.hr;
                if (meas_ok_count >= HR_MEAS_SUM_COUNT)
                {
                    info->hr_info.hr = meas_sum / meas_ok_count;
                    VC32S_LOG_I("hr measure ok,hr = [%d];\n", info->hr_info.hr);
                    ret = HR_MEAS_SUCC;
                }
            }
            else if (SENSOR_SPO2 == hr_type)
            {
                meas_sum += health_value.spo2;
                if (meas_ok_count >= HR_MEAS_SUM_COUNT)
                {
                    info->spo2_info.spo2 = meas_sum / meas_ok_count;
                    VC32S_LOG_I("mease ok, spo2 = [%d];\n", info->spo2_info.spo2);
                    ret = HR_MEAS_SUCC;
                }
            }
        }
        else if (HR_MEAS_ABNORMAL == ret
                 || (HR_MEAS_NULL == ret && meas_count >= HR_MEAS_SUM_COUNT * 10))
        {
            ret = HR_MEAS_TIMEOUT;
        }

        if (HR_MEAS_NO_TOUCH == ret || HR_MEAS_SUCC == ret || HR_MEAS_TIMEOUT == ret)
        {
            meas_sum = 0;
            meas_count = 0;
            meas_ok_count = 0;
        }
    }
#if 0
    else if ((SENSOR_BP == hr_type))
    {
        rt_bp_info_ind_t *bp_info = (rt_spo2_info_ind_t *) value;
        ret = rt_bp_info_ind_t(bp_info);
    }
#endif

    return ret;

}

2.3 运动传感器数据融合

当开启USING_GSENSOR_DATA_STORE_LISTSENSOR_USING_6D宏,可以通过算法融合运动传感器跟心率传感器数据,使得到的心率数据更准确。注意:请确保心率传感器周期是运动传感器周期的整数倍,否则会出现问题。

  1. 通过接收上层传入的数据链表获取可用的运动传感器数据。

static void vc32s_get_gsensor_data(vc32s_get_gsensor typedef *pGsensorData, void *g_in)
{
    uint16_t gs_data_len = 0;
    uint16_t gs_node_num = 0;
    uint8_t count_gs_data = 0, count_gs_data_left = 0;
    uint16_t hr_gs_data_buf_len = VC32S_BUFF_SIZE;
    uint8_t  cash_num[20] = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38};
    rt_list_t *pos, *next;
    gsensors_fifo_t *node;
    if (g_in)
    {
        pGsensorData->sampling_rate = 25;
        pGsensorData->sensing_rate = 4;
        pGsensorData->efective_bits = 12;
        gsensor_data_header = (rt_list_t *)g_in;
        if (!rt_list_len(gsensor_data_header))  //if no gsensor data , return
            return;
        gs_data_len = 0;
        rt_list_for_each_safe(pos, next, gsensor_data_header)
        {
            node = rt_list_entry(pos, gsensors_fifo_t, list);
            if ((gs_data_len + node->num) >= hr_gs_data_buf_len)
                gs_node_num = hr_gs_data_buf_len - gs_data_len;
            else
                gs_node_num = node->num;

            //get gsdata
            for (count_gs_data = 0; count_gs_data < gs_node_num; count_gs_data++)
            {
                pGsensorData->gsensor_x_axis[count_gs_data + gs_data_len] = node -> buf[count_gs_data].acce_data[0];
                pGsensorData->gsensor_y_axis[count_gs_data + gs_data_len] = node -> buf[count_gs_data].acce_data[1];
                pGsensorData->gsensor_z_axis[count_gs_data + gs_data_len] = node -> buf[count_gs_data].acce_data[2];
            }
            gs_data_len += gs_node_num;
            if (gs_data_len == hr_gs_data_buf_len)
                break;
        }
    }
    if (gs_data_len < hr_gs_data_buf_len)
    {
        for (count_gs_data_left = gs_data_len; count_gs_data_left < hr_gs_data_buf_len; count_gs_data_left++)
        {
            pGsensorData->gsensor_x_axis[count_gs_data_left] = pGsensorData->gsensor_x_axis[gs_data_len - 1];
            pGsensorData->gsensor_y_axis[count_gs_data_left] = pGsensorData->gsensor_y_axis[gs_data_len - 1];
            pGsensorData->gsensor_z_axis[count_gs_data_left] = pGsensorData->gsensor_z_axis[gs_data_len - 1];

        }
        gs_data_len = hr_gs_data_buf_len;
    }
    VC32S_LOG_I("[hr_algo]>:gs_data_len = %d;\n");
    for (count_gs_data = 0; count_gs_data < VC32S_BUFF_SIZE / 2; count_gs_data++)
    {
        //cash_num[40]
        pGsensorData->gsensor_x_axis[count_gs_data] = pGsensorData->gsensor_x_axis[cash_num[count_gs_data]];
        pGsensorData->gsensor_y_axis[count_gs_data] = pGsensorData->gsensor_y_axis[cash_num[count_gs_data]];
        pGsensorData->gsensor_z_axis[count_gs_data] = pGsensorData->gsensor_z_axis[cash_num[count_gs_data]];
    }
}
  1. 算法片段:在algoInputData中指定输入的数据并调用Algo_Input进行算法处理,通过Algo_Output得到出后的数据algoOutputData

algoInputData.envSample = hr_data->envValue[0];
for (algoCallNum = 0; algoCallNum < 20; algoCallNum++)
    {        algoInputData.ppgSample = hr_data->ppgValue[algoCallNum];
        algoInputData.axes.x =  gsensor_data.gsensor_x_axis[algoCallNum];     //The direction vertical with ARM.
        algoInputData.axes.y =  gsensor_data.gsensor_y_axis[algoCallNum];    //The direction parallel with ARM.
        algoInputData.axes.z =  gsensor_data.gsensor_z_axis[algoCallNum];   //The direction upside.
        Algo_Input(&algoInputData, 1000 / hr_data->SampleRate, vcSportMode, 1, 0);
    }
Algo_Output(&algoOutputData);

3. RT-Thread 传感器设备移植

本节将原生驱动封装为RT-Thread标准传感器设备,实现与系统框架的无缝对接。

3.1 配置与宏定义

在目录solution\components\sensor\sensor_algo\hr添加对应的器件目录“vc32s”,在“vc32s"目录下创建vc32s_hr_service.c、vc32s_hr_service.h文件,用于实现vc32s的注册为Sensor设备;

宏名称

类型

功能说明

取值/注意事项

VC32S_BUFF_SIZE

宏定义

FIFO数据缓冲区大小,限制单次读取的最大帧数

默认40,单位:帧

VC32S_ALGO_PEROID

宏定义

算法调用周期,决定应用层数据刷新频率

默认800ms,单位:毫秒

VC32S_PERIOD_TIMER

宏定义

数据读取周期,决定应用层数据刷新频率

默认800ms,单位:毫秒

HR_USING_VC32S

条件编译宏

控制整个驱动模块的编译开关

需在menuconfig选项中定义以启用驱动

3.2 关键数据结构

定义设备私有数据结构体和全局变量。

typedef struct
{
    uint8_t status;
    uint8_t hr;
    uint8_t bp_l;
    uint8_t bp_h;
    uint8_t spo2;
} vc32s_algo_result_t;

typedef struct
{
    bool            vcFifoReadFlag;
    bool            vcPsFlag;
    uint8_t         wearstatus;
    uint8_t         envValue[3];
    uint8_t         SampleRate;
    int16_t         ppgValue[VC32S_BUFF_SIZE];
    uint8_t         ppgLen;
} vc32s_raw_data_t;

3.3 设备注册接口

将vc32s注册为RT-Thread标准传感器设备。

static int rt_hw_vc32s_register(const char *name, struct rt_sensor_config *cfg)
{
    /* magnetometer/compass sensor register */
    {
        vc32s_sensor = rt_calloc(1, sizeof(struct rt_sensor_device));
        if (vc32s_sensor == RT_NULL)
            goto __exit;

        vc32s_sensor->info.type       = RT_SENSOR_CLASS_HR;
        vc32s_sensor->info.vendor     = RT_SENSOR_VENDOR_UNKNOWN;
        vc32s_sensor->info.model      = NULL;
        vc32s_sensor->info.unit       = RT_SENSOR_UNIT_BPM;
        vc32s_sensor->info.intf_type  = RT_SENSOR_INTF_I2C;
        vc32s_sensor->info.range_max  = 220;
        vc32s_sensor->info.range_min  = 30;
        vc32s_sensor->info.period_min = VC32S_PERIOD_TIMER;
        vc32s_sensor->info.fifo_max   = VC32S_BUFF_SIZE;
        vc32s_sensor->data_buf          = RT_NULL;
        vc32s_sensor->data_len          = 0;        //must be 0, because don't use data buf

        rt_memcpy(&vc32s_sensor->config, cfg, sizeof(struct rt_sensor_config));
        vc32s_sensor->ops = &sensor_ops;

        rt_err_t result = rt_hw_sensor_register(vc32s_sensor, name, RT_DEVICE_FLAG_RDWR, RT_NULL);
        if (result != RT_EOK)
        {
            VC32S_LOG_I("HR [%s] register err code: %d", name, result);
            goto __exit;
        }
    }
    VC32S_LOG_I("HR [%s] init success!\n", name);

    return RT_EOK;

__exit:
    if (vc32s_sensor != RT_NULL)
    {
        rt_free(vc32s_sensor);
        vc32s_sensor = RT_NULL;
    }
    return -RT_ERROR;

}

int vc32s_register(void)
{
    //all pin will be configed in drv.
    struct rt_sensor_config cfg = {0};
    cfg.irq_pin.pin = RT_PIN_NONE; //disable pin operation of sensor_close
    int ret = rt_hw_vc32s_register("vc32s", &cfg);
    rt_err_t err = rt_mutex_init(&vc32s_service_mutex, "vc32s_service_mutex", RT_IPC_FLAG_FIFO);

    return ret;
}

INIT_COMPONENT_EXPORT(vc32s_register);

INIT_COMPONENT_EXPORT(vc32s_register);是RT-Thread系统组件初始化命令,用于在系统启动时调用vc32s_register()完成设备的注册。

3.4 传感器接口实现

注册为Sensor设备必须完善接口,实现rt_sensor_ops结构体。

static struct rt_sensor_ops sensor_ops =
{
    vc32s_fetch_data,
    vc32s_control
};

3.4.1 数据获取接口

vc32s_fetch_data()用于服务层向驱动层获取数据。

static rt_size_t vc32s_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len)
{
    if (!vc32s_dev || !buf) return 0;

    if (sensor->config.mode == RT_SENSOR_MODE_POLLING)
    {
        return vc32s_polling_get_data(sensor, buf);
    }

    return 0;
}

vc32s_polling_get_data()实现具体的数据读取逻辑:

static rt_size_t vc32s_polling_get_data(rt_sensor_t sensor, void *data)
{
    rt_mutex_take(&vc32s_service_mutex, RT_WAITING_FOREVER);    //avoid vc32s_health_raw_data multi thread use erro

    if (sensor->info.type == RT_SENSOR_CLASS_HR)
    {
        rt_memset((void *)&vc32s_algo_raw_data, 0, sizeof(vc32s_raw_data_t));
        hrs_raw_data_t *p_data = (hrs_raw_data_t *)data;
        rt_memcpy((void *)&vc32s_algo_raw_data, (void *) &vc32s_health_raw_data, sizeof(vc32s_raw_data_t));
        p_data->p_rawdata = (void *) &vc32s_algo_raw_data;
    }
    rt_mutex_release(&vc32s_service_mutex);
    return 0;
}

3.4.2 设备控制接口

vc32s_control()用于服务层对驱动层的控制。 通过该函数控制心率传感器不同的工作模式,可指定运行心率模式或血氧模式。

static rt_err_t vc32s_control(struct rt_sensor_device *sensor, int cmd, void *args)
{
    rt_err_t result = RT_EOK;
    switch (cmd)
    {
    case RT_SENSOR_CTRL_GET_ID:
    {

#if defined(BSP_USE_PIN_MULTIPLEX)
        pin_multiplex_lock();
        set_pin_i2c5(MULTIPLEX_PIN);
#endif

        sensor_reg_info_t *info = (sensor_reg_info_t *)args;
        if (vc32s_dev)
        {
            switch (info->type)
            {
            case SENSOR_HR:
            case SENSOR_BP:
                vcMode = VCWORK_MODE_HRWORK;
                VC32S_LOG_I("hr opened ! \n");
                break;
            case SENSOR_SPO2:
                vcMode = VCWORK_MODE_SPO2WORK;
                VC32S_LOG_I("spo2 opened ! \n");
                break;
            default:
                vcMode = VCWORK_MODE_HRWORK;
            }
            vc32sStart(&vcHr02, vcMode);
            info->sensor_id = vc32s_dev->id;
            info->fetch_mode = vc32s_dev->fetch_mode;
            if ((info->sesnor_rd_indicate) && (info->fetch_mode == RT_SENSOR_MODE_INT))
            {
                vc32s_dev->sesnor_rd_indicate = info->sesnor_rd_indicate;
            }

            info->dev_period = VC32S_PERIOD_TIMER;
            info->algo_period = VC32S_ALGO_PEROID;
            info->fifo_len      = VC32S_BUFF_SIZE;
            info->hr_algo_ops = vc32s_algo_process;
            info->hr_algo_post_ops = vc32s_algo_postprocess;
        }
        else
            info->sensor_id = 0;
#if defined(BSP_USE_PIN_MULTIPLEX)
        pin_multiplex_unlock();
#endif
        VC32S_LOG_I("sensor_id	: %d ! \n", info->sensor_id);
        break;
    }
    case RT_SENSOR_CTRL_SET_RANGE:
        if (!vc32s_dev) return RT_ERROR;
        result = vc32s_set_range(sensor, (rt_int32_t)args);
        break;
    case RT_SENSOR_CTRL_SET_ODR:
        result = -RT_EINVAL;
        break;
    case RT_SENSOR_CTRL_SET_MODE:
        result = vc32s_hr_set_mode(sensor, (rt_uint32_t)args & 0xff);
        break;
    case RT_SENSOR_CTRL_SET_POWER:
        result = vc32s_set_power(sensor, (rt_uint32_t)args & 0xff);
        break;
    case RT_SENSOR_CTRL_SELF_TEST:
        if (!vc32s_dev) return RT_ERROR;
        result = vc32s_self_test(sensor, *((rt_uint8_t *)args));
        break;
    default:
        return -RT_ERROR;
    }

    return result;
}

:vc32s的算法处理未走“算法设备”框架,故在RT_SENSOR_CTRL_GET_ID分支里直接把算法入口vc32s_algo_process 与后处理回调vc32s_algo_postprocess通过函数指针一并带给服务层,后者即可直接调用。

3.4.3 电源控制实现

vc32s_set_power()实现对传感器电源状态的控制。

static rt_err_t vc32s_set_power(rt_sensor_t sensor, rt_uint8_t power)
{
    rt_err_t ret = RT_EOK;

    switch (power)
    {
    case RT_SENSOR_POWER_DOWN:
#if defined(BSP_USE_PIN_MULTIPLEX)
        pin_multiplex_lock();
        set_pin_i2c5(MULTIPLEX_PIN);
#endif
        vc32s_hrs_close();
#if defined(BSP_USE_PIN_MULTIPLEX)
        pin_multiplex_unlock();
#endif
#if !defined(BSP_USE_PIN_MULTIPLEX) //if i2c mutltiplex, hr can't power off.
        vc32s_hw_power_onoff(RT_FALSE);//  power off
#endif
        break;
    case RT_SENSOR_POWER_NORMAL:
        vc32s_hw_power_onoff(RT_TRUE);//  power on
#if defined(BSP_USE_PIN_MULTIPLEX)
        pin_multiplex_lock();
        set_pin_i2c5(MULTIPLEX_PIN);
#endif
        ret = vc32s_hrs_open();
#if defined(BSP_USE_PIN_MULTIPLEX)
        pin_multiplex_unlock();
#endif
#if !defined(BSP_USE_PIN_MULTIPLEX) //if i2c mutltiplex, hr can't power off.
        if (ret != RT_EOK)
            vc32s_hw_power_onoff(RT_FALSE);
#endif
        break;
    case RT_SENSOR_POWER_LOW:
        break;
    case RT_SENSOR_POWER_HIGH:
        break;
    default:
        ;
    }
    return ret;
}

3.5 设备初始化函数

vc32s_hrs_open()作为vc32s心率血压传感器设备的初始化入口,完成硬件唤醒、资源分配和参数配置,使传感器进入可工作状态。

static rt_err_t vc32s_hrs_open(void)
{
    rt_err_t result = RT_EOK;
    VC32S_LOG_I("vc32s hw init\n");
    if (!vc32s_dev)
    {
        vc32s_dev = rt_calloc(1, sizeof(struct vc32s_sensor_device));
        if (!vc32s_dev)
        {
            result = RT_ENOMEM;
            goto err;
        }
        if (RT_EOK != vc32s_hw_init())
        {
            vc32s_hw_deinit();
            result = RT_ERROR;
            rt_free(vc32s_dev);
            goto err;
        }
        vc32s_vcHr02Init(&vcHr02);
        vc32s_dev->fetch_mode = RT_SENSOR_MODE_INT;
#if defined (VC32S_USE_INT_MODE)
        vc32s_int_open();
#endif
        vc32s_dev->bus = (rt_device_t)vc32s_get_i2c_handle();
        vc32s_dev->i2c_addr = vc32s_get_dev_addr();
        vc32s_dev->id = vc32s_get_dev_id();
        VC32S_LOG_I("hr(%s) open\n", vc32s_sensor->parent.parent.name);
        return result;
    }
err:
    VC32S_LOG_I("hr(%s)init err code: %d\n", vc32s_sensor->parent.parent.name, result);
    vc32s_dev = RT_NULL;
    return result;
}

4.心率传感器调度模块

调度模块是 Solution 传感器系统的核心,负责将器件采集的原始数据(如 PPG 信号、加速度值)转换为业务可用数据(如心率、计步数)。

4.1 器件与算法关联

4.1.1 核心接口说明

sensor_reg_open 是 Solution 基于 RT-Thread 扩展的核心接口,负责在系统初始化阶段“连接”已注册的传感器设备与算法设备,构建“硬件采集→算法处理”的数据链路,是传感器从“注册态”进入“可用态”的关键步骤。 主注册接口

rt_err_t sensor_reg_open(
    const char *sensor_name,     // 传感器设备名(如"acce_vc32s")
    const char *algo_name,       // 算法设备名
    sensor_reg_info_t *sensor_info, // 输出:传感器+算法信息
    rt_device_t *sensor_device   // 输出:传感器设备句柄
);

功能:完成传感器设备与算法模块的查找、验证与绑定。

数据结构定义

// 算法信息载体(在sensor_reg.h中定义)
typedef struct {
    sensor_type_t type;          // 传感器类型
    uint32_t sensor_id;          // 传感器芯片ID
    uint32_t dev_period;         // 采样周期(ms)
    uint8_t fifo_len;            // FIFO深度
    
    // 算法函数指针(由算法设备注入)
    void (* hrs_algo_ops_t)(uint8_t hr_type, void *data, void *g_in);//主算法入口
    uint8_t (* hrs_algo_post_ops_t)(uint8_t hr_type, hrs_info_t *info);//后处理算法入口
    void (* sesnor_rd_indicate_t)(void *param);//外部中断回调
} sensor_reg_info_t;

4.1.2 应用示例

  1. 修改hr_algo.c 中的宏定义,修改HR_SENSOR_NAME为指定的"vc32s",因为vc32s的算法并没有采用算法设备的方式实现,所以不需要指定HR_ALGO_NAME。

fishy
  1. 初始化绑定设备,通过sensor_reg_open()查找并验证传感器设备(acce_vc32s),通过传感器设备向sensor_reg_info_t结构体注入其处理函数指针(hrs_algo_ops_thrs_algo_post_ops_t);此后数据流处理时,调度器直接通过该函数指针调用算法。

static rt_err_t hr_init(uint8_t type)
{
    sensor_info.type = type;    //tell drv hr type
    sensor_info.sesnor_rd_indicate = hr_read_indicate;
    char *hr_name = "hr_"HR_SENSOR_NAME;
    char *hr_algo_name = "hr_"HR_ALGO_NAME;
    if (RT_EOK != sensor_reg_open(hr_name, hr_algo_name, &sensor_info, &hr_device) ||
            !sensor_info.hr_algo_ops || !sensor_info.hr_algo_post_ops)
    {
        LOG_W("Find valid HR device fail!");
        return RT_ERROR;
    }

    hr_id = sensor_info.sensor_id;
    hr_type = type;
    hr_fetch_mode = sensor_info.fetch_mode;
    hr_dev_period = sensor_info.dev_period;
    hr_algo_period = sensor_info.algo_period;
    hr_fifo_len = sensor_info.fifo_len;

    LOG_I("%s: hr %s peroid%d fifo_len %d id 0x%x", __func__, hr_dev_period, hr_fifo_len, hr_id);
    return RT_EOK;
}

4.2 模块调度

4.2.1 基础概念

Solution 基于 RT-Thread 软件定时器 实现传感器的自动化调度,替代原生轮询机制,可灵活配置采样周期,同时适配低功耗场景(支持动态调整周期)。该调度器是 “器件采集→算法处理→数据上报” 全流程的触发核心。

fishy

4.2.2 测量策略

心率传感器数据支持三种获取模式:手动测量、整点自动测量与定时自动测量。

  1. 手动测量:通过hr_setting_req()选择手动测量,通过指定传入hr_setting->mode = HR_MANU_MEAS,开启手动测量,当周期性调用hr_algo_scheduler()时,测量结果实时上发 APP,不写入历史缓存,测量结束不自动关机,等待 APP 再次下发 OFF 命令。

else //HR_MANU_MEAS
{
    if (HR_MEAS_NO_TOUCH == meas_result || HR_MEAS_TIMEOUT == meas_result)
        {
            remind_ind.event = (HR_MEAS_NO_TOUCH == meas_result) ?  REMIND_HR_NO_TOUCH : REMIND_HR_TIMEOUT;
            ipc_send_lcpu_msg_to_hcpu(SENSOR_APP_EVENT_REMIND_IND, &remind_ind, sizeof(remind_ind));
        }
}
  1. 自动测量:通过hr_setting_req()选择自动测量,通过指定传入hr_setting->mode = HR_AUTO_MEAS,开启自动测量。

  • 整点自动测量:在 hr_algo_scheduler() 周期性执行计时整点检查,当计时到达整点(分、秒均为 0)时,调用 hr_auto_meas_process(true)spo2_auto_meas_process(true),依次触发血氧与心率测量:采样 → 算法处理 → 上报APP → 自动关机。

/**
 * @brief  hr auto measure result
 * @param  bool enable   if true, hourly measure; if false, interval measure
*/

static void hr_auto_meas_process(bool enable)
{
    static uint8_t meas_interval = 0; //unit: one minute
    static uint8_t first = 1;
    hr_auto_meas_t *meas = &hr_auto_meas;
    if (! hr_auto_meas.enable)
        return;
    if (!enable)
    {
        meas_interval++;
        LOG_I("hr_auto_meas meas_interval = %d;", meas_interval);
        if (first || meas_interval >= meas->interval_time_min)
        {
            first = 0;
            meas_interval = 0;
            goto out;
        }
        return;
    }

    //if manual measure is in progress, so delay it until manual end.
out:
    {
        struct tm *cur_time;
        time_t cur_time_stamp = service_timestamp_get(); //get cur timestamp
        cur_time = localtime(&cur_time_stamp);
        cur_time->tm_hour = meas->start_hour;
        cur_time->tm_min = meas->start_min;
        uint32_t start_time_stamp = mktime(cur_time);

        cur_time->tm_hour = meas->end_hour;
        cur_time->tm_min = meas->end_min;
        uint32_t end_time_stamp = mktime(cur_time);
        if (SENSOR_OFF == hr_get_state(HR_MANU_MEAS) &&
                (start_time_stamp == end_time_stamp || (cur_time_stamp >= start_time_stamp && cur_time_stamp <= end_time_stamp)))
        {
            hr_setting_t hr_state = {SENSOR_ON, HR_AUTO_MEAS, SENSOR_HR, 0};
            rt_err_t ret = send_msg_to_lcpu_thread(&hr_state, sizeof(hr_state), hr_setting_req_cb, 0);
            LOG_I("hr_auto_meas start!");
        }
    }
}
/**
 * @brief  spo2 auto measure result
 * @param  bool enable   if true, hourly measure; if false, interval measure
*/
static void spo2_auto_meas_process(bool enable)
{
    static uint8_t meas_interval = 0; //unit: one minute
    static uint8_t first = 1;
    hr_auto_meas_t *meas = &spo2_auto_meas;
    if (! spo2_auto_meas.enable)
        return;

    if (!enable)
    {
        meas_interval++;
        if (first || meas_interval >= meas->interval_time_min)
        {
            first = 0;
            meas_interval = 0;
            goto out;
        }
        return;
    }

out:
    {
        struct tm *cur_time;
        time_t cur_time_stamp = service_timestamp_get(); //get cur timestamp
        cur_time = localtime(&cur_time_stamp);

        cur_time->tm_hour = meas->start_hour;
        cur_time->tm_min = meas->start_min;
        uint32_t start_time_stamp = mktime(cur_time);

        cur_time->tm_hour = meas->end_hour;
        cur_time->tm_min = meas->end_min;
        uint32_t end_time_stamp = mktime(cur_time);

        //if manual measure is in progress, so delay it until manual end.
        if (SENSOR_OFF == spo2_get_state(HR_MANU_MEAS) &&
                (start_time_stamp == end_time_stamp || (cur_time_stamp >= start_time_stamp && cur_time_stamp <= end_time_stamp)))
        {
            hr_setting_t hr_state = {SENSOR_ON, HR_AUTO_MEAS, SENSOR_SPO2, 0};
            rt_err_t ret = send_msg_to_lcpu_thread(&hr_state, sizeof(hr_state), hr_setting_req_cb, 0);
            LOG_I("spo2_auto_meas start!");
        }
    }

}
  • 定时自动测量:在整点自动测量的基础上,分钟调用hr_auto_meas_process(false)进行计数,通过修改分钟计数值interval_time_min进行指定时间触发测量,可在hr_set_auto_meas中直接修改传入hr_auto_meas.interval_time_min值指定定时周期。

static void hr_set_auto_meas(void *meas)
{
#ifndef HR_HOURLY_MEASURE
    hr_auto_meas = *((hr_auto_meas_t *)meas);
    if (hr_auto_meas.interval_time_min < 5) hr_auto_meas.interval_time_min = 5; //  interval_time_min can't less than 5;
#endif
}

: 整点自动测量与定时自动测量通过HR_HOURLY_MEASURE宏后进行切换。


4.2.3 中断调度

在心率传感器的调度策略中,除软件定时器的轮询模式外,还引入外部中断驱动机制,实现通过传感器触发调度。

  1. 原生驱动新增中断管理单元,并创建专用于中断处理的任务

static void vc32s_sensor_task(void *params)
{
    int32_t ret;
    uint8_t status = 0;

    while (1)
    {
        rt_sem_take(&vc32_int_sem, RT_WAITING_FOREVER);
        vc32s_get_data();
        rt_pin_attach_irq(VC32S_INT_BIT, PIN_IRQ_MODE_RISING, vc32s_irq_handler, (void *)(rt_uint32_t)VC32S_INT_BIT);
        rt_pin_irq_enable(VC32S_INT_BIT, 1);
    }
}
  1. 在 RT-Thread 传感器设备驱动中,新增 vc32s_get_data() 接口:根据工作模式(心率 / 血氧 /血压 );工作模式可通过vc32s_control()指定 若当前为中断模式且上层已注册回调,则通过sesnor_rd_indicate将原始数据指针立即抛给调度模块。

void vc32s_get_data(void)
{
    uint8_t ppgLength = 0;
#if defined(BSP_USE_PIN_MULTIPLEX)
    pin_multiplex_lock();
    set_pin_i2c5(MULTIPLEX_PIN);
#endif
    rt_mutex_take(&vc32s_service_mutex, RT_WAITING_FOREVER);

    rt_memset((void *)&vc32s_health_raw_data, 0, sizeof(vc32s_raw_data_t));

    if (VCHR02RET_UNWEARTOISWEAR == vc32sGetSampleValues(&vcHr02, &ppgLength))
    {
        if (vcHr02.workMode == VCWORK_MODE_HRWORK)
        {
            VC32S_LOG_I("Algo_Init\n");
            Algo_Init();
        }
        else if (vcHr02.workMode == VCWORK_MODE_SPO2WORK)
        {
            spo2AlgoInit();
        }
    }
#ifdef VC32S_DEBUG_LOG_EN
    VC32S_LOG_I("fetchmode = %d, workmode = %d , ppgLength = %d ; \n", vc32s_dev->fetch_mode, vcHr02.workMode, ppgLength);
#endif
    if ((vcHr02.workMode == VCWORK_MODE_HRWORK) || (vcHr02.workMode == VCWORK_MODE_SPO2WORK))
    {

        vc32s_health_raw_data.vcFifoReadFlag = vcHr02.vcFifoReadFlag;
        if (vcHr02.vcFifoReadFlag)
            vcHr02.vcFifoReadFlag = 0;

        vc32s_health_raw_data.vcPsFlag = vcHr02.vcPsFlag;
        rt_memcpy(vc32s_health_raw_data.envValue, vcHr02.sampleData.envValue, 3);
        if (ppgLength > VC32S_BUFF_SIZE)
            ppgLength = VC32S_BUFF_SIZE;
        rt_memcpy(vc32s_health_raw_data.ppgValue, vcHr02.sampleData.ppgValue, sizeof(uint16_t) * ppgLength);
        vc32s_health_raw_data.ppgLen = ppgLength;
        vc32s_health_raw_data.wearstatus = vcHr02.wearStatus;
        vc32s_health_raw_data.SampleRate = vcHr02.vcSampleRate;
#ifdef VC32S_DEBUG_LOG_EN
        VC32S_LOG_I("vc32s_get_data vcFifoReadFlag = %d, vcPsFlag = %d, wearstatus = %d, SampleRate = %d, ppgLen = %d;\n", \
                    vc32s_health_raw_data.vcFifoReadFlag, vc32s_health_raw_data.vcPsFlag, vc32s_health_raw_data.wearstatus, vc32s_health_raw_data.SampleRate, vc32s_health_raw_data.ppgLen);
#endif
        if ((vc32s_dev->sesnor_rd_indicate) && (vc32s_dev->fetch_mode == RT_SENSOR_MODE_INT))
        {
            vc32s_dev->sesnor_rd_indicate((void *)&vc32s_health_raw_data);
        }
    }

    else if (vcHr02.workMode == VCWORK_MODE_LPDETECTION)
    {
    }
    else if (vcHr02.workMode == VCWORK_MODE_CROSSTALKTEST)
    {

#if 0
        static uint32_t last_send_hr = 0;
        static struct tm cur_time = {0};
        caron_get_current_time(&cur_time);
        if (cur_time.tm_sec < last_send_hr || cur_time.tm_sec - last_send_hr >= 1)
        {
            last_send_hr = cur_time.tm_sec;
            caron_send_hr_crosstalk_ind(vcHr02.sampleData.psValue);
        }
#endif

#if 1
        static uint32_t last_send_hr_timsetap = 0;
        uint32_t current_timsetap = rt_tick_get();

        if (current_timsetap - last_send_hr_timsetap >= 1000)
        {
            last_send_hr_timsetap = current_timsetap;
            vcHr02value[0] = vcHr02.sampleData.maxLedCur;
            vcHr02value[1] = vcHr02.sampleData.preValue[0];
            vcHr02value[2] = vcHr02.sampleData.psValue;
        }
#endif

        /* If Pass: */
        if ((vcHr02.sampleData.maxLedCur >= 100) && (vcHr02.sampleData.preValue[0] <= 2))
        {
        }

    }

    rt_mutex_release(&vc32s_service_mutex);
#if defined(BSP_USE_PIN_MULTIPLEX)
    pin_multiplex_unlock();
#endif

}
  1. hr_read_indicate()在中断驱动的数据读取结束后被调用,把原始数据指针封装成消息,抛给 LCPU 线程;执行 hr_algo_scheduler(),完成心率/血氧算法的后续计算与结果上报。

/**
 * @brief  hr run algo call back
 * @param  comm_msg_t *msg  message info for get point of rawdata
*/
static int hr_algo_run_cb(comm_msg_t *msg)
{
    uint32_t data = 0;
    memcpy((void *)&data, msg->data, msg->data_len);
    hr_algo_raw_data.p_rawdata = (void *)data;
    hr_algo_scheduler(hr_dev_period);
    return 0;
}
/**
 * @brief  hr read finish indicate callback. when in INT mode, drive read raw data completed, can call this function to run algo;
 * @param  void *param  used to passing point of rawdata buff;
*/
void hr_read_indicate(void *param)
{
    uint32_t data = (uint32_t)param;
    send_msg_to_lcpu_thread((void *)(&data), sizeof(uint32_t), hr_algo_run_cb, 0);
}

4.2.4 示例代码

void hr_algo_scheduler(uint32_t period)
{
    static uint16_t hr_peroid_time = 0, hr_algo_time = 0, one_minu = 0;
    uint8_t meas_result = HR_MEAS_NULL;
    hrs_info_t hrs_info = {0};

    void *gsensor_input = NULL;

    one_minu += period;

#ifdef HR_HOURLY_MEASURE
    if (hr_is_hourly_time() && !hr_paral_flag)
    {
        hr_paral_flag = HR_PARALLEL_EVENT_HR | HR_PARALLEL_EVENT_SPO2 |

#if defined(APP_BLOOD_PRESSURE_CFG)
                        HR_PARALLEL_EVENT_BP |
#endif
                        HR_PARALLEL_EVENT_NO;
    }
#endif

    //if HR measurement is not enabled, return
    // rt_kprintf("hr_opened = 0x%x, spo2_opened = 0x%x;\n", hr_opened, spo2_opened);
    if ((SENSOR_OFF == hr_get_state(HR_MANU_MEAS | HR_AUTO_MEAS))
            && (SENSOR_OFF == spo2_get_state(HR_MANU_MEAS | HR_AUTO_MEAS)))
    {
        hr_peroid_time = 0;
        hr_algo_time = 0;
        hr_auto_time = 0;
        spo2_auto_time = 0;
        goto end;
    }

    hr_peroid_time  += period;
    hr_algo_time    += period;

    if (SENSOR_ON == hr_get_state(HR_AUTO_MEAS))
        hr_auto_time += period;
    else
        hr_auto_time = 0;

    if (SENSOR_ON == spo2_get_state(HR_AUTO_MEAS))
        spo2_auto_time += period;
    else
        spo2_auto_time = 0;


    if (hr_peroid_time < hr_dev_period)
    {
        goto end;
    }

    hr_peroid_time = 0;


    if (hr_fetch_mode == RT_SENSOR_MODE_POLLING)
        //get hr raw data from sensor
        hr_data_fetch(0, (void *)&hr_algo_raw_data);

#if defined(USING_GSENSOR_DATA_STORE_LIST) && defined(SENSOR_USING_6D)

    /* Note: when using this function, make sure that the HR cycle is a multiple of the gsensor cycle, otherwise there will be problems */
    //get senor data
    if (gsensor_is_opened())
        gsensor_input = gsensor_get_stored_data();

#endif

    //each HR measurement period, calls the HR algorithm for response processing with raw data inputing
    sensor_info.hr_algo_ops(hr_type, (void *)&hr_algo_raw_data, gsensor_input);
#if defined(USING_GSENSOR_DATA_STORE_LIST) && defined(SENSOR_USING_6D)
    if (gsensor_is_opened())
        gsensor_data_free(true);
#endif

    //considering that the period of HR algorithm is always N times of HR sampling period
    //in other words: hr_algo_time = N * hr_peroid_time
    //each HR algo period, gets algorithm measurement results for processing
    if (hr_algo_time >= hr_algo_period)
    {
        hr_algo_time = 0;

        //obtain the algorithm processing results, filter and average the measured values for 5 times, and input the measurement results
        meas_result = sensor_info.hr_algo_post_ops(hr_type, &hrs_info);

        LOG_I("hr_algo_result: state %d hr_value %d  spo2_value %d  bp_value %d!", meas_result, hrs_info.hr_info.hr, hrs_info.spo2_info.spo2, hrs_info.bp_info.bp_h);

        //if the measurement is completed (successful or failed), save the measured value and check the event that triggered the remind
        if (HR_MEAS_NO_TOUCH == meas_result || HR_MEAS_SUCC == meas_result || HR_MEAS_TIMEOUT == meas_result || HR_MEAS_NULL == meas_result)
        {
            hr_meas_result_postprocess(meas_result, &hrs_info);
        }
    }

end:
    //check whether the automatic measurement cycle times out every minute. if so, start the measurement process
    if (one_minu >= 60 * 1000)
    {
        one_minu = 0;
        //Under the automatic measurement mode, store the heart rate measurement data to history once every 5 minutes
#ifndef HR_HOURLY_MEASURE
        hr_auto_meas_process(false);
#endif
        //check whether it ends on the same day, and if so, send the history of automatic measurement to APP
        hr_send_curday_history_to_app();
    }

#ifdef HR_HOURLY_MEASURE
    // hr hourly measure process, if hr and spo2 all can auto measure, function will run hr auto measure first , hen run spo2 auto measure;
    if (hr_paral_flag > 0)
    {
        if (hr_paral_flag & HR_PARALLEL_EVENT_HR)
        {
            if (! hr_auto_meas.enable)
                hr_paral_flag &= ~HR_PARALLEL_EVENT_HR;
            else
            {
                if (SENSOR_OFF == hr_get_state(HR_AUTO_MEAS))    hr_auto_meas_process(true);
            }
        }
        else if ((hr_paral_flag & HR_PARALLEL_EVENT_SPO2))
        {
            if (! spo2_auto_meas.enable)
                hr_paral_flag &= ~HR_PARALLEL_EVENT_SPO2;
            else
            {
                if (SENSOR_OFF == spo2_get_state(HR_AUTO_MEAS))  spo2_auto_meas_process(true);
            }
        }
    }
#endif
}

:该函数由sensor_timer定时器定时调用或传感器外部中断触发,实现了数据的采集、处理、上报全流程