Sensor应用指南

备注

文档内容基于solution release v2.3及之后版本,如果遇到接口或功能描述和实际code不符,请及时反馈给我们,我们会及时更改或更新相关内容。

内容概述

章节描述了sensor service的架构,功能,以及不同功能模块间的通讯机制。特别强调了sensor放在HCPU和LCPU的区别,以及sensor servcie 如何调用算法。

如果sensor service已经了解,可以进一步了解具体的sensor器件:

心率传感器,全称Heart Rate,简称HR;跳转到

加速度传感器Accelerometer和陀螺仪Gyroscope,二者统一称呼为Gsensor,简称GS;跳转到

地磁传感器,全称Magnetometer,简称MAG;跳转到

Sensor介绍

Sensor指传感器,包括6轴(加速度、角速度)心率血氧血压,温度,气压,地磁,感光,GPS, 马达等。

MCU和传感器通讯,一般采用的接口包括但不限于I2CSPIUART

solution 对sensor进行了良好的支持,确保了代码的可读性和鲁棒性,扩展性,对传感器代码采用分层设计。


Sensor框架

Sensor 框架如下:

fishy

从框架图描述三层结构的功能如下:

驱动层 直接对硬件处理,以及算法处理;

服务层 中间层,包括:对APP的指令的上传下达;调度对硬件的访问,以及算法的访问,算法结果的处理;

应用层 直观显示sesor的结果,和用户的交互;


Sensor 驱动层

Sensor 驱动层属于物理层,对硬件直接操作,实现对硬件的直接控制;

Sensor 算法也抽象成物理设备,放到驱动层;

Sensor 驱动层 一般会注册成sensor device,用于sensor驱动层或sensor服务层访问;

如果对sensor device 不熟悉的话,可以参考链接rtthread sensor device

注册 sensor device ,参考示例,如下:

static rt_sensor_t sensor = RT_NULL;
sensor = rt_calloc(1, sizeof(struct rt_sensor_device));

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

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

rt_err_t result = rt_hw_sensor_register(sensor, "hr", RT_DEVICE_FLAG_RDWR, RT_NULL);

驱动层代码实现,包括两个部分:

器件驱动本身,代码位于目录:

solution2_0\sdk\customer\peripherals\sensor

注册rt_device设备,代码位于目录:

D:\10_solution\solution_0\solution2_0\solution\components\sensor\sensor_algo

以GS器件stk8321为例:

stk8321器件驱动stk8321.c,包括对寄存器的访问,初始化等,实现在目录:\nsolution2_0\sdk\customer\peripherals\sensor\acc\stk8321

stk8321注册rt_device设备stk8321_gsensor_service.c,实现在目录:

solution2_0\solution\components\sensor\sensor_algo\acc\stk8321

小技巧

rt_device 支持同时注册多个同类型的sensor设备;

Sensor 算法位于驱动层,可以注册成单独的sensor 设备,也可以直接作为回调接口传给服务层。 例如: 心率vc32s 需要算法处理从器件采样的原始值,并输出心率结果; 算法实现位于solution2.0\solution\components\sensor\sensor_algo\hr\vc32s\vc32s_algo_process.c

Sensor 服务层

Sensor 服务层 属于中间层,作用包括:

  • 调度sensor 的采样,算法,以及不同sensor之间的访问;

  • 接收应用层的指令,上报各种sensor数据给到应用层;

Sensor 服务层 独立于 Sensor驱动层,每次更换Sensor器件都不需要修改Sensor 服务层;

Sensor 应用层

Sensor 应用层 属于和用户的交互层,展示实时数据,历史数据,事件;

Sensor 应用层的数据展示一般有两个渠道,硬件自身的UI界面和手机APP展示;

Sensor 服务层 和 驱动层的交互

由于Sensor 服务层 和 驱动层的独立性,每次添加新的器件一般都不需要修改服务层;

Sensor 服务层 通过sensor device 接口来控制驱动层,进而实现对硬件的驱动;

常见接口

rt_device_find()	根据传感器设备设备名称查找设备获取设备句柄
rt_device_open()	打开传感器设备
rt_device_read()	读取数据
rt_device_control()	控制传感器设备
rt_device_set_rx_indicate()	设置接收回调函数
rt_device_close()	关闭传感器设备

Sensor 服务层 和 驱动层的交互特别说明的几个地方;

Sensor 服务层 和 驱动层的交互主要包括三个过程:

  • 初始化sensor

  • sensor采样

  • sensor算法(如果需要的话)

服务层初始化sensor

Sensor 服务层 初始化senso也可以细化成两个过程:

过程1:rt_device_open sensor

open sensor 的内容一般包括:初始化总线,初始化寄存器,获取id等;服务层无法在open sensor的时候得到sensor的id,需要通过信息交互时获取; 过程2:信息交互

利用rt_device_control(*sensor_device, RT_SENSOR_CTRL_GET_ID, sensor_info)交互信息即:传递服务层的信息给驱动层,同时从驱动层获取信息;

交互的信息通过结构体sensor_reg_info_t传递,结构体成员有些是传递给驱动层,有些则是驱动层传给服务层;

typedef struct
{
    uint8_t                     sensor_id;          //服务层从驱动层获取
    uint8_t                     type;               //服务层传递给驱动层
    uint8_t                     fetch_mode;
    sesnor_rd_indicate_t        sesnor_rd_indicate;//服务层传递给驱动层
    const char                 *algo_name;
    rt_uint32_t                 dev_period;         //服务层从驱动层获取
    rt_uint32_t                 algo_period;        //服务层从驱动层获取
    rt_uint8_t                  fifo_len;           //服务层从驱动层获取
    void                       *gs_input;           //服务层传递给驱动层
    float                       gs_acce_para;       //服务层从驱动层获取
    gs_algo_ops_t               gs_algo_ops;        //服务层从驱动层获取
    gs_para_ops_t               gs_para_ops;        //服务层从驱动层获取

    hrs_algo_ops_t              hr_algo_ops;        //服务层从驱动层获取
    hrs_algo_post_ops_t         hr_algo_post_ops;   //服务层从驱动层获取

    mag_algo_ops_t               mag_algo_ops;      //服务层从驱动层获取
    mag_para_ops_t               mag_para_ops;      //服务层从驱动层获取
} sensor_reg_info

小心

服务层不要跳过sensor device框架直接访问驱动层。

Sensor 算法处理

有些Sensor 例如:心率,加速度计,地磁,GPS都存在算法,用于拿到从sensor器件采样的原始数据,处理后得到想要的结果;

心率,血样都是通过算法获取结果;

计步,运动的结果则是从加速度计的算法处理后得到;

指南针, 通过提供地磁和加速度计的采样数据给到地磁算法,处理后得到;

Sensor 框架是把算法处理接口作为sensor 设备的 算法回调,或者单独注册的sensor算法设备的算法回调;

算法处理接口作为sensor 设备的 算法回调参考

Sensor 服务层和驱动层交互信息的时候,允许驱动层返回算法回调;

心率的算法处理回调

    hrs_algo_ops_t              hr_algo_ops;       
    hrs_algo_post_ops_t         hr_algo_post_ops;

计步的算法处理回调

    gs_algo_ops_t               gs_algo_ops;        
    gs_para_ops_t               gs_para_ops;     

地磁的算法处理回调

    mag_algo_ops_t               mag_algo_ops; 
    mag_para_ops_t               mag_para_ops; 

驱动层处理示例:

static rt_err_t sensor_control(struct rt_sensor_device *sensor, int cmd, void *args)
{
    rt_err_t result = RT_EOK;
    switch (cmd)
    {
    case RT_SENSOR_CTRL_GET_ID:
    {
        sensor_reg_info_t *info = (sensor_reg_info_t *)args;
        if (sensor_dev)
        {
 
            info->sensor_id = id;
            info->fetch_mode = fetch_mode;
            if ((info->sesnor_rd_indicate) && (info->fetch_mode == RT_SENSOR_MODE_INT))
            {
                sensor_dev->sesnor_rd_indicate = info->sesnor_rd_indicate;
            }

            info->dev_period = SENSOR_PERIOD_TIMER;
            info->algo_period = SENSOR_ALGO_PEROID;
            info->fifo_len      = SENSOR_BUFF_SIZE;
            info->hr_algo_ops = hr_algo_process;
            info->hr_algo_post_ops = hr_algo_postprocess;
        }
        else
            info->sensor_id = 0;
        break;
    }

单独注册的sensor算法设备

典型的就是cywee算法,参考文件:cwm_motion.c;

fishy

两种方式的区别

单独注册的sensor算法设备,意味着sensor 都可以使用这个算法;

不注册单独的sensor算法设备,可以减少一次find device, 更简单;

举例:

不同厂家的心率算法互补相同,而且都会提供自己的算法,适合不注册单独的sensor算法设备;

计步算法则是可以多家厂商的加速度计都可以适配,注册成单独的算法设备更合适;

Sesnor服务层增重要接口说明

hr_setting_req(void *data)

功能:心率功能开启关闭

参数说明:

void *data 命令消息 一般类型是:hr_setting_t *


hr_setting_t  结构体如下:


typedef sensor_state_t          hr_setting_t


typedef struct

{ uint8_t state; //打开状态,枚举:SENSOR_ON SENSOR_OFF uint8_t mode; //测量方式,枚举:HR_MANU_MEAS, HR_AUTO_MEAS uint8_t type; //sensor类型,心率枚举:SENSOR_HR,SENSOR_SPO2, SENSOR_BP uint16_t interval_time; //测量间隔 } sensor_state_t;

void hr_config_req(uint16_t msg_id, uint8_t *data)

功能:处理APP层发过来的命令;

参数说明:

msg_id  命令id;    增加新的命令需要增加新的id;


uint8_t *data   传递过来的数据;数据类型根据msg_id的不同会有所不同;

void hr_algo_scheduler(uint32_t period)

功能:心率算法调度

参数说明:

   uint32_t period 执行周期; 对于POLLING方式, sensor_timeout_cb()会传入\nsensor_timer的周期;
                            对于INT方式,则直接传入心率周期确保立刻执行算法;

void gsensor_setting_req(uint8_t *data)

功能:加速度计功能开启关闭

参数说明:

void *data 命令消息 一般类型是:gsensor_setting_t *


gsensor_setting_t  结构体如下:


typedef sensor_state_t          gsensor_setting_t


typedef struct

{ uint8_t state; //打开状态,枚举:SENSOR_ON SENSOR_OFF uint8_t mode; //加速度计未使用 uint8_t type; //加速度计未使用 uint16_t interval_time; //加速度计未使用 } sensor_state_t;

void gsensor_config_req(uint16_t msg_id, uint8_t *data)

功能:处理APP层发过来的命令;

参数说明:

msg_id  命令id;    增加新的命令需要增加新的id;


uint8_t *data   传递过来的数据;数据类型根据msg_id的不同会有所不同;

void gsensor_algo_scheduler(uint32_t period)

功能:加速度计算法调度

参数说明:

   uint32_t period 执行周期; 对于POLLING方式, sensor_timeout_cb()会传入\nsensor_timer的周期;

Sensor 服务层 和 应用层的交互

Sensor 服务层 和 应用层通过传递消息来实现交互; Sensor 服务层发送到应用层的消息传递接口

#define  ipc_send_lcpu_msg_to_hcpu(type, data, len)         ipc_send_msg_to_client(lcpu_srv_handle, type, data, len)

Sensor 应用层发送到服务层的消息传递接口

#define  ipc_send_msg_to_lcpu(type, data, len)              ipc_send_msg_to_server(lcpu_app_srv_handle, type, data, len)

消息传递接口的参数统一为type,data,len;

其中:

type 表示sensor相关的消息统一定义在sensor_service.c中枚举sensor_app_t中;

部分消息id如下:

typedef enum
{
    /*---------------APP_SENSOR_BEGIN-------------------------*/
    APP_SENSOR_ID_BEGIN = SENSOR_SERVICE_CUSTOM_ID_BEGIN,

    //send event and config to sensors.
    APP_SENSOR_GSENSOR_SETTING_REQ,
    APP_SENSOR_MAG_SETTING_REQ,
    APP_SENSOR_HR_SETTING_REQ,
    APP_SENSOR_ACTIVE_GET_SLEEP_DATA,
    APP_SENSOR_SPORT_MODE,
    APP_SENSOR_OPEN_ALGO_ON_HCPU,
    APP_SENSOR_CLOSE_ALGO_ON_HCPU,
    APP_SENSOR_ACTIVITY_SETTING,
    ......
       SENSOR_APP_HISTROY_ID_DATA_NOTIFY_END,
    /*---------------SENSOR_APP_HISTROY_ID_DATA_NOTIFY_END-----------------------------*/
} sensor_app_t; 

data 表示消息缓存的指针;

len 表示消息缓存的长度;

Sensor 服务层的消息接收处理,位于sensor_service.c的sensors_msg_process_in_lcpu_thread_cb;

接口部分代码:

int sensors_msg_process_in_lcpu_thread_cb(comm_msg_t *msg)
{
    uint16_t msg_id = (uint16_t) msg->user_data;
    switch (msg_id)
    {
        case LCPU_PWR_ON:
        {
            /*开机时执行*/
            break;
        }
        case LCPU_PWR_OFF:
        {
            /*关机时执行*/
            ....
            break;
        }
        case LCPU_TIME_SET:
        case APP_SENSOR_SETTING_SLEEP_TIME:
        case APP_SENSOR_SETTING_SPORT_TIME:
        case APP_SENSOR_SETTING_UNIT:
        case APP_SENSOR_SETTING_USER_INFO:
        case APP_SENSOR_SETTING_SPORT_TARGET:
        case APP_SENSOR_SETTING_SEDENTARY:
        case APP_SENSOR_SETTING_GESTURE_DISPLAY:

        case APP_SENSOR_SPORT_MODE:
        case APP_SENSOR_GSENSOR_SETTING_REQ:
        {
            /*gsensor消息处理*/
            ....
            break;          
        }
        case APP_SENSOR_SETTING_HR_AUTO_MEAS:
        case APP_SENSOR_SETTING_SPO2_AUTO_MEAS:
        case APP_SENSOR_SETTING_HR_REMIND:

        case APP_SENSOR_HR_SETTING_REQ:
        case APP_SENSOR_TODAY_HR_REQ:
        {
            /*hr消息处理*/
            ...
            break;
        }
               
    }
}

Sensor 应用层的消息接收处理,位于sensor_service.c的sensors_msg_process_in_hcpu_cb

void sensors_msg_process_in_hcpu_cb(uint16_t msg_id, void *data, uint16_t data_len)
{

    if (SENSOR_APP_EVENT_ID_BEGIN < msg_id && msg_id < SENSOR_APP_EVENT_ID_END || SENSOR_APP_TEST_RANDOM_IND == msg_id)
    {
        send_msg_to_gui_thread(data, data_len, sensors_wakeup_msg_process_in_gui_thread_cb, msg_id, NEED_WAKEUP_UI);
    }
    else if (SENSOR_APP_HISTORY_ID_BEGIN < msg_id && msg_id < SENSOR_APP_HISTORY_ID_END)
    {
        send_msg_to_bg_thread(data, data_len, sensors_msg_process_in_bg_thread_cb, msg_id, 0);
    }
    else
    {
        sensors_data_splitting_process_cb(msg_id, data, data_len);
#if defined(BSP_BLE_SIBLES) && defined(PKG_USING_NANOPB)
        sensors_msg_process_in_ble_to_app(msg_id, data, data_len);
#endif
        send_msg_to_gui_thread(data, data_len, sensors_no_wakeup_msg_process_in_gui_thread_cb, msg_id, NEED_WAKEUP_UI);
    }
}

自定义增加Sensor 服务层到应用层的消息的步骤:

枚举sensor_app_t增加消息ID

->sensors_msg_process_in_hcpu_cb() 增加hcpu对消息id的处理

->调用ipc_send_lcpu_msg_to_hcpu发送消息;

自定义增加Sensor 应用层到服务层的消息的步骤:

枚举sensor_app_t增加消息ID

->sensors_msg_process_in_lcpu_thread_cb() 增加lcpu对消息id的处理

->调用ipc_send_lcpu_msg_to_hcpu发送消息;

Sensor 服务层 和驱动层处于HCPU和LCPU的区别

Sensor 框架的应用层是必须在HCPU,但服务层 和驱动层在可以放在HCPU,也可以放在LCPU;

一般而言:52x 系列会服务层 和驱动层放在HCPU,55x/56x/58x系列则是 会服务层 和驱动层放在LCPU;

二者代码通用,传递消息的接口也相同。虽然52x 系列会服务层 和驱动层放在HCPU,但实际运行的也是名字叫做“lcpu_app”的消息服务,和55x/56x/58x一样。

但考虑到52x 的sensor放到HCPU,一些时序有变化,会通过SENSOR_IN_HCPU来区分,52x 的sensor框架代码;

例如:\n下面这段代码:

fishy

消息从Sensor 应用层 到驱动层控制过程

我们以实际计步应用到驱动层的控制过程;

可以从sport_action_gui.c找到这段代码,用于应用打开时告诉驱动层当前的模式

static void on_start(void)
{
    p_sport_action = (sport_action_t *)APP_GET_PAGE_MEM_PTR;
    RT_ASSERT(p_sport_action);

    p_sport_action->app_idx = (uint16_t)APP_GET_PAGE_USERDATA_PTR;

    sport_action_resume_data(p_sport_action);
    sport_action_gui_init();
    if (p_sport_action->sport_pause)
    {
        sport_action_set_sport_status(p_sport_action);
    }
    sport_init_last_data(p_sport_action);

#if defined (BSP_BLE_AMS) && defined (BSP_BLE_SIBLES)
    uint32_t uuid;
    mobile_os_t os_type = ble_noti_attr_info_get(&uuid);
    if (IOS_TYPE == os_type)
        app_ams_config(AMS_SERVICE_ENABLE_CCCD, 1);
#endif

#if defined (RT_USING_SENSOR)
    sport_mode_t sport_mode;
    sport_mode.flag_sporting = 1;
    sport_mode.mode = convert_sport_mode(p_sport_action->app_idx);
    sport_mode.sport_type = p_sport_action->app_idx;

    ipc_send_msg_to_lcpu(APP_SENSOR_SPORT_MODE, &sport_mode, sizeof(sport_mode_t));
#endif

}

大致流程:

fishy

详细描述:

->调用 ipc_send_msg_to_lcpu(APP_SENSOR_SPORT_MODE, &sport_mode, sizeof(sport_mode_t))发送消息

->位于lcpu_service.c的lcpu_service_comm_cb()会收到消息,并根据消息id进行区分处理

对于APP_SENSOR_SPORT_MODE,会转发给sensor_service.c的->sensors_msg_process_in_lcpu_thread_cb()处理

->sensors_msg_process_in_lcpu_thread_cb() 里判断消息id后,转发给gsensor_config_req(msg_id, msg->data);处理

->gsensor_config_req收到后分析消息id,转给sensor_info.gs_para_ops(msg_id, data) sensor_info.gs_para_ops在初始化的时候已经赋值给gsensor_para_process()

->gsensor_para_process()区分消息id进行处理

消息从Sensor 服务层 到应用层控制过程

Sensor的驱动层不会主动发消息给应用层,都通过服务层调度后,由服务层调用驱动层的接口往应用层发送消息。

我们计步消息发送给应用端为例,分析下消息路径:

fishy

详细描述:

->gsensor_algo调度算法获取到合适的计步数据后,通过接口ipc_send_lcpu_msg_to_hcpu(SENSOR_APP_RT_STEP_INFO_IND, step_info, sizeof(step_info_t));发送给应用端;

->位于lcpu_service.c的app_service_comm_cb()会收到消息,并根据消息id进行区分处理,判断是sensor消息,转发给sensors_msg_process_in_hcpu_cb()

->sensors_msg_process_in_hcpu_cb()进一步区分消息类型为实时消息,进行了三项处理: sensors_data_splitting_process_cb(msg_id, data, data_len);//更新消息数据 sensors_msg_process_in_ble_to_app(msg_id, data, data_len);//转发ble处理 sensors_no_wakeup_msg_process_in_gui_thread_cb()//转发进一步的消息处理

Sensor 代码目录结构如下图:


Sensor 代码目录结构

Sensor 代码目录结构如下图:

fishy

Sensor的驱动实现在\sdk\customer\peripherals目录下;

Sensor的服务层则实现在\solution\components\sensor目录下;


快速添加sensor驱动

快速添加一个sensor驱动,包括以下部分:

  • 添加SDK驱动代码;

  • 添加RTOS驱动代码;

  • 修改服务层使用的sensor;

添加心率驱动

以下以vc32s心率为例,实现快速添加驱动如下:

  1. 在目录"\sdk\customer\peripherals",创建对应的目录“vc32s”;

fishy
  1. vc32s相关驱动文件复制到建好的“vc32s”目录下;

fishy
  1. 修改"\sdk\customer\peripherals\kconfig"文件,创建必要的宏来实现驱动的控制,参考如下:

fishy

实际效果如下:

fishy

对Kconfig文件编写比较陌生的朋友,可以参考链接: Kconfig .

  1. 目录”vc32s"增加SConscript文件,以便目录”vc32s"下的文件加入编译:

fishy

对SConscript文件编写比较陌生的朋友,可以参考链接: Scons .

  1. 驱动文件适配电源控制,参考原理图配置menuconfig的电源pin,并实现对应功能;

void vc32s_hw_power_onoff(bool  onoff)
{

    if (onoff)
    {
#if defined(PMIC_CONTROL_SERVICE)
        pmic_service_control(PMIC_CONTROL_HR, 1);
#endif
#if(VC32S_POW_PIN >= 0)
        rt_pin_mode(VC32S_POW_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_POW_PIN, PIN_HIGH);
#endif
#if(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

");
    }
    else
    {
#if defined(PMIC_CONTROL_SERVICE)
        pmic_service_control(PMIC_CONTROL_HR, 0);
#endif
#if(VC32S_POW_PIN >= 0)
        rt_pin_mode(VC32S_POW_PIN, PIN_MODE_OUTPUT);
        rt_pin_write(VC32S_POW_PIN, PIN_LOW);
#endif
#if(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


        LOG_I("hr_power_off

");
    }
}
  1. VC32S要求使用中断,需要配置中断pin,以及线程。

fishy

中断pin除了配置为中断外,还需要配置为唤醒源,

#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 */
  1. 通讯接口配置,vc32s使用的I2C2,需要配置menuconfig使能I2C2 ,并且配置I2C 初始化,id检查, i2c_write, i2c_read等。

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 ! 

");
#endif
        LOG_I("find bus %s;

", VC32S_I2C_BUS);
    }
    else
    {
        LOG_I("bus not find

");
        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;
    }

    return RT_EOK;
}

id检查

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)

", chip_id);

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

    return RT_ERROR;
}

i2c write

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;
}

i2c read

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 

");

    if (rt_i2c_transfer(vc32s_i2cbus, msgs, 2) == 2)
    {
        res = RT_EOK;
    }
    else
    {
        res = -RT_ERROR;
    }
    return res;
}
  1. 校准tick 配置, vc32s 的时钟需要通过系统时钟进行校准;

static uint32_t vcHr02GetRtcCountFromMCU(void)
{
    return HAL_GTIMER_READ();  //get  tick
}
static void vcHr02Init(vcHr02_t *pVcHr02)
{
    mcuOscData = HAL_LPTIM_GetFreq();   //lptime freq
}
  1. 在目录“siflisolution\solution\components\sensor\sensor_algo\hr”添加对应的器件目录“vc32s”,在“vc32s"目录下创建vc32s_hr_service.c/vc32s_hr_service.h文件,用于实现vc32s的注册为rtos设备;

fishy

vc32s_hr_service.c/vc32s_hr_service.h 可以参考范例创建rtos设备;

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      = name;
        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!

", 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);

注册设备名长度不建议超过系统要求。可以通过宏RT_NAME_MAX来查看当前系统支持的最长名字;

注册为RTOS设备,需要完善RTOS设备的接口,必须要实现的内容如下:

static struct rt_sensor_ops sensor_ops =
{
    vc32s_fetch_data,
    vc32s_control
};

vc32s_fetch_data 用于服务层从驱动获取数据;

vc32s_control 用于驱动各种控制功能,必须要实现的部分如下:

fishy fishy
  1. 服务层调度文件hr_algo.c文件,修改使用的心率器件为“vc32s”,这个名字和注册的设备名要对应;

fishy

至此,心率器件的驱动能够正常运行了。

添加加速度计驱动

以下以stk8321 加速度计为例,实现快速添加加速度计驱动如下:

  1. 在目录"\sdk\customer\peripherals",创建对应的目录“stk8321”;

fishy
  1. stk8321相关驱动文件复制到建好的“stk8321”目录下;

fishy
  1. 修改"\sdk\customer\peripherals\kconfig"文件,创建必要的宏来实现驱动的控制,参考如下:

fishy

实际效果如下:

fishy

对Kconfig文件编写比较陌生的朋友,可以参考链接: Kconfig .

  1. 目录”stk8321"增加SConscript文件,以便目录”stk8321"下的文件加入编译:

fishy

对SConscript文件编写比较陌生的朋友,可以参考链接: Scons .

  1. 加速度计驱动需要适配内容包括:通讯总线,延时,中断及中断处理线程等。下面先适配通讯总线;

总线初始化

static int stk8321_i2c_init(void)
{
    /* get i2c bus device */

    gs_content.bus_handle = (struct rt_i2c_bus_device *)rt_device_find(GS_BUS_NAME);
    if (RT_Device_Class_I2CBUS != gs_content.bus_handle->parent.type)
    {
        gs_content.bus_handle = NULL;
    }
    if (gs_content.bus_handle)
    {

        rt_device_open((rt_device_t)gs_content.bus_handle, RT_DEVICE_FLAG_RDWR);
    }
    else
    {
        rt_kprintf("gs bus not find ! 

");
        return -RT_ERROR;
    }

    {
        struct rt_i2c_configuration configuration =
        {
            .mode = 0,
            .addr = 0,
            .timeout = 200,
            .max_hz = 400000,
        };

        rt_i2c_configure(gs_content.bus_handle, &configuration);
    }
    rt_kprintf("gs i2c bus init ok ! 

");

    return 0;
}

总线读

static int32_t gs_i2c_read(GS_CONT_T *ctx, uint8_t reg, uint8_t *data, uint16_t len)
{
    rt_size_t res = 0;
    GS_CONT_T *handle = ctx;

    if (handle && handle->bus_handle && data)
    {
        struct rt_i2c_msg msgs[2];

        msgs[0].addr  = GS_I2C_ADDR;    /* slave address */
        msgs[0].flags = RT_I2C_WR;        /* write flag */
        msgs[0].buf   = &reg;              /* Send data pointer */
        msgs[0].len   = 1;

        msgs[1].addr  = GS_I2C_ADDR;    /* slave address */
        msgs[1].flags = RT_I2C_RD;        /* write flag */
        msgs[1].buf   = data;              /* Send data pointer */
        msgs[1].len   = len;

        if (rt_i2c_transfer(handle->bus_handle, msgs, 2) == 2)
        {
            res = RT_EOK;
        }
        else
        {
            res = -RT_ERROR;
        }
    }

    return res;
}

总线写

static int32_t stk8321_i2c_write(GS_CONT_T *ctx, uint8_t reg, uint8_t *data, uint16_t len)
{
    rt_int8_t res = 0;
    uint8_t value[2];

    GS_CONT_T *handle = ctx;

    if (handle && handle->bus_handle && data)
    {
        struct rt_i2c_msg msgs;

        value[0] = reg;
        value[1] = *data;

        msgs.addr  = GS_I2C_ADDR;   /* slave address */
        msgs.flags = RT_I2C_WR;        /* write flag */
        msgs.buf   = &value[0];             /* Send data pointer */
        msgs.len   = sizeof(value);

        if (rt_i2c_transfer(handle->bus_handle, &msgs, 1) == 1)
        {
            res = RT_EOK;
        }
        else
        {
            struct rt_i2c_configuration configuration =
            {
                .mode = 0,
                .addr = 0,
                .timeout = 5000,
                .max_hz = 400000,
            };

            rt_i2c_configure(gs_content.bus_handle, &configuration);

            res = -RT_ERROR;
        }
    }
    return res;
}

延时

void delay_ms(uint16_t ms)
{
    rt_thread_mdelay(ms);
}

中断 线程

static void stk8321_int_handle(void *args)
{
    rt_kprintf("stk8321_int_handle

");
    rt_pin_detach_irq(GS_INT);
    rt_sem_release(&gs_int_sem);
}


static int stk8321_gpio_int_enable(void)
{
    struct rt_device_pin_mode m;
    rt_pin_mode(GS_INT, PIN_MODE_INPUT);
    rt_pin_attach_irq(GS_INT, PIN_IRQ_MODE_RISING, stk8321_int_handle, (void *)(rt_uint32_t)GS_INT);
    rt_pin_irq_enable(GS_INT, 1);
    return 0;
}

static int stk8321_gpio_int_disable(void)
{
    rt_pin_irq_enable(GS_INT, 0);
    rt_pin_detach_irq(GS_INT);
    return 0;

}

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

    while (1)
    {
        rt_sem_take(&gs_int_sem, RT_WAITING_FOREVER);
        rt_pin_irq_enable(GS_INT, 0);

        //add fetchdata

        rt_pin_attach_irq(GS_INT, PIN_IRQ_MODE_RISING, stk8321_int_handle, (void *)(rt_uint32_t)GS_INT);
        rt_pin_irq_enable(GS_INT, 1);
    }
}


static int stk8321_thread_init(void)
{
    rt_sem_init(&gs_int_sem, "gs_sem", 0, RT_IPC_FLAG_FIFO);

    gs_thread = rt_thread_create("gs_thread_init", stk8321_thread_task, NULL, GS_THRED_STACK_SIZE, 15, 10);   //HR_THRED_STACK_SIZE
    RT_ASSERT(gs_thread);

    return 0;
}

static void stk8321_thread_deinit(void)
{
    if (gs_thread)
    {
        rt_thread_delete(gs_thread);
        gs_thread = NULL;
    }
    rt_sem_detach(&gs_int_sem);
    rt_kprintf("gs_thread_deinit

");
}
  1. 在目录“siflisolution\solution\components\sensor\sensor_algo\acc”添加对应的器件目录“stk8321”,在“stk8321"目录下创建stk8321_gsensor_service.c/stk8321_gsensor_service.h文件,用于实现stk8321的注册为rtos设备;

fishy

stk8321_gsensor_service.c/stk8321_gsensor_service.h可以参考范例创建rtos设备;

static int rt_hw_stk8321_register(const char *name, struct rt_sensor_config *cfg)
{
    int ret;

    /* accelerometer sensor register */
    {
        stk8321_acce = rt_calloc(1, sizeof(struct rt_sensor_device));
        if (stk8321_acce == RT_NULL)
            return -1;

        stk8321_acce->info.type       = RT_SENSOR_CLASS_ACCE;
        stk8321_acce->info.vendor     = RT_SENSOR_VENDOR_STM;
        stk8321_acce->info.model      = name;
        stk8321_acce->info.unit       = RT_SENSOR_UNIT_MG;
        stk8321_acce->info.intf_type  = RT_SENSOR_INTF_I2C;
        stk8321_acce->info.range_max  = 256;
        stk8321_acce->info.range_min  = 0;
        stk8321_acce->info.period_min = 200;
        stk8321_acce->info.fifo_max   = 10;
        stk8321_acce->data_buf          = RT_NULL;
        stk8321_acce->data_len          = 0;

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

        ret = rt_hw_sensor_register(stk8321_acce, name, RT_DEVICE_FLAG_RDWR, RT_NULL);
        if (ret != RT_EOK)
        {
            rt_kprintf("GS [%s] register err code: %d

", name, ret);
            goto __exit;
        }
    }


    rt_kprintf("GS [%s] register success!

", name);
    return RT_EOK;

    __exit:
    if (stk8321_acce)
    {
        rt_free(stk8321_acce);
        stk8321_acce = RT_NULL;
    }

    return -RT_ERROR;
}

    /**
    * @brief  gsensor register, register name need unique, and longth is less than RT_NAME_MAX
 *  /

int stk8321_register(void)
{
    struct rt_sensor_config cfg = {0};
    cfg.intf.dev_name = STK8321_BUS_NAME;
    cfg.irq_pin.pin = STK8321_INT;    //it must be config
    int ret = rt_hw_stk8321_register("stk8321", &cfg);
    return ret;
}

INIT_COMPONENT_EXPORT(stk8321_register);

注册设备名长度不建议超过系统要求。可以通过宏RT_NAME_MAX来查看当前系统支持的最长名字;

注册为RTOS设备,需要完善RTOS设备的接口,必须要实现的内容如下:

   static struct rt_sensor_ops sensor_ops =
{
    stk8321_fetch_data,
    stk8321_control
};

stk8321_fetch_data 用于服务层向驱动层获取数据; stk8321_control 用于服务层对驱动层的控制; 必须实现的内容如下:

fishy
  1. 服务层调度文件gensor_algo.c文件,修改使用的心率器件为“stk8321”,这个名字和注册的设备名要对应;

fishy

至此,加速度计的驱动能够正常运行了。

添加驱动总结

快速添加完心率驱动和加速度计驱动之后,启动起来,能够启动心率和加速度计,并且定时采集数据。但这些数据还是最终能够使用的结果,还需要把这些数据输入到算法中,得到的结果才是APP最终需要的数据。

快速添加sensor算法

sensor的框架中把算法也定义为设备,通过设备名找到对应的算法。

添加心率算法

心率算法一般和心率器件本身绑定的,所以心率的算法是通过心率的器件驱动获取的。 添加算法的步骤如下:

  1. 新建文件“vc32s_algo_process.c”“vc32s_algo_process.h”用于实现算法; 算法需要实现两个接口:vc32s_algo_process()和vc32s_algo_postprocess(); 这连个接口的指针类型定义在结构体sensor_reg_info_t; 算法接口通过结构体成员hr_algo_ops和hr_algo_post_ops来传递;

typedef struct
{
    uint8_t                     sensor_id;
    uint8_t                     type;
    uint8_t                     work_mode;
    sesnor_rd_indicate_t        sesnor_rd_indicate;
    const char                 *algo_name;
    gs_algo_ops_t               gs_algo_ops;
    gs_para_ops_t               gs_para_ops;
#ifdef SENSOR_USING_HR
    rt_uint32_t                 hr_dev_period;
    rt_uint32_t                 hr_algo_period;
    rt_uint8_t                  hr_fifo_len;
    hrs_algo_ops_t              hr_algo_ops;
    hrs_algo_post_ops_t         hr_algo_post_ops;
#endif
} sensor_reg_info_t;
  1. vc32s 初始化的时候,通过get id 的交互来实现服务层从驱动层拿到算法接口;实现如下:

fishy

这样服务层,在初始化的发现驱动层有算法接口,就会在调度时执行,算法接口;

添加加速度计算法

加速度计的算法一般为通用算法,例如:cywee;可以把算法注册为单独的设备,实现调用步骤:

  1. 注册加速度计算法为设备,注意设备名的长度;

static int rt_hw_cywee_algo_register(const char *name, struct rt_sensor_config *cfg)
{
    /* magnetometer/compass sensor register */
    cywee_algo = rt_calloc(1, sizeof(struct rt_sensor_device));
    RT_ASSERT(cywee_algo)

    cywee_algo->info.type       = RT_SENSOR_CLASS_ACCE;
    cywee_algo->info.vendor     = RT_SENSOR_VENDOR_UNKNOWN;
    cywee_algo->info.model      = "cywee_algo";
    cywee_algo->info.unit       = RT_SENSOR_UNIT_NONE;
    cywee_algo->info.intf_type  = 0;
    cywee_algo->info.range_max  = 220;
    cywee_algo->info.range_min  = 1;
    cywee_algo->info.period_min = 1;
    cywee_algo->info.fifo_max     = 1;
    cywee_algo->data_buf        = RT_NULL;
    cywee_algo->data_len            = 0;        //must be 0, because don't use data buf

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

    rt_err_t result = rt_hw_sensor_register(cywee_algo, name, RT_DEVICE_FLAG_RDWR, RT_NULL);
    if (result != RT_EOK)
    {
        rt_kprintf("device register err code: %d", result);
        goto __exit;
    }

    rt_kprintf("GS algo[%s] init success!

", name);

    return RT_EOK;

__exit:
    if (cywee_algo)
    {
        rt_free(cywee_algo);
        cywee_algo = RT_NULL;
    }
    return -RT_ERROR;
}
/**
 * @brief gsensor algo  register, register name need unique, and longth is less than RT_NAME_MAX
*/

static int cyewee_algo_register(void)
{
    //register cywee algo to gsensor algo.
    char name[RT_NAME_MAX] = "a_cywee";
    //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
    rt_hw_cywee_algo_register(name, &cfg);
    return 0;
}
INIT_COMPONENT_EXPORT(cyewee_algo_register);
  1. 在服务层更改算法名称,位于文件gsensor_algo.c,这样加速度计根据算法名称打开设备,获取算法接口;

#define G_SENSOR_NAME       "stk8321"
#define G_ALGO_NAME         "a_cywee"