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和传感器通讯,一般采用的接口包括但不限于I2C,SPI,UART。
solution 对sensor进行了良好的支持,确保了代码的可读性和鲁棒性,扩展性,对传感器代码采用分层设计。
Sensor框架
Sensor 框架如下:

从框架图描述三层结构的功能如下:
驱动层 直接对硬件处理,以及算法处理;
服务层 中间层,包括:对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;

两种方式的区别
单独注册的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下面这段代码:

消息从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
}
大致流程:

详细描述:
->调用 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的驱动层不会主动发消息给应用层,都通过服务层调度后,由服务层调用驱动层的接口往应用层发送消息。
我们计步消息发送给应用端为例,分析下消息路径:

详细描述:
->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 代码目录结构如下图:

Sensor的驱动实现在\sdk\customer\peripherals目录下;
Sensor的服务层则实现在\solution\components\sensor目录下;
快速添加sensor驱动
快速添加一个sensor驱动,包括以下部分:
添加SDK驱动代码;
添加RTOS驱动代码;
修改服务层使用的sensor;
添加心率驱动
以下以vc32s心率为例,实现快速添加驱动如下:
在目录"\sdk\customer\peripherals",创建对应的目录“vc32s”;

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

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

实际效果如下:

对Kconfig文件编写比较陌生的朋友,可以参考链接: Kconfig .
目录”vc32s"增加SConscript文件,以便目录”vc32s"下的文件加入编译:

对SConscript文件编写比较陌生的朋友,可以参考链接: Scons .
驱动文件适配电源控制,参考原理图配置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
");
}
}
VC32S要求使用中断,需要配置中断pin,以及线程。

中断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 */
通讯接口配置,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;
}
校准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
}
在目录“siflisolution\solution\components\sensor\sensor_algo\hr”添加对应的器件目录“vc32s”,在“vc32s"目录下创建vc32s_hr_service.c/vc32s_hr_service.h文件,用于实现vc32s的注册为rtos设备;

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 用于驱动各种控制功能,必须要实现的部分如下:


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

至此,心率器件的驱动能够正常运行了。
添加加速度计驱动
以下以stk8321 加速度计为例,实现快速添加加速度计驱动如下:
在目录"\sdk\customer\peripherals",创建对应的目录“stk8321”;

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

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

实际效果如下:

对Kconfig文件编写比较陌生的朋友,可以参考链接: Kconfig .
目录”stk8321"增加SConscript文件,以便目录”stk8321"下的文件加入编译:

对SConscript文件编写比较陌生的朋友,可以参考链接: Scons .
加速度计驱动需要适配内容包括:通讯总线,延时,中断及中断处理线程等。下面先适配通讯总线;
总线初始化
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 = ® /* 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
");
}
在目录“siflisolution\solution\components\sensor\sensor_algo\acc”添加对应的器件目录“stk8321”,在“stk8321"目录下创建stk8321_gsensor_service.c/stk8321_gsensor_service.h文件,用于实现stk8321的注册为rtos设备;

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 用于服务层对驱动层的控制; 必须实现的内容如下:

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

至此,加速度计的驱动能够正常运行了。
添加驱动总结
快速添加完心率驱动和加速度计驱动之后,启动起来,能够启动心率和加速度计,并且定时采集数据。但这些数据还是最终能够使用的结果,还需要把这些数据输入到算法中,得到的结果才是APP最终需要的数据。
快速添加sensor算法
sensor的框架中把算法也定义为设备,通过设备名找到对应的算法。
添加心率算法
心率算法一般和心率器件本身绑定的,所以心率的算法是通过心率的器件驱动获取的。 添加算法的步骤如下:
新建文件“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;
vc32s 初始化的时候,通过get id 的交互来实现服务层从驱动层拿到算法接口;实现如下:

这样服务层,在初始化的发现驱动层有算法接口,就会在调度时执行,算法接口;
添加加速度计算法
加速度计的算法一般为通用算法,例如:cywee;可以把算法注册为单独的设备,实现调用步骤:
注册加速度计算法为设备,注意设备名的长度;
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);
在服务层更改算法名称,位于文件gsensor_algo.c,这样加速度计根据算法名称打开设备,获取算法接口;
#define G_SENSOR_NAME "stk8321"
#define G_ALGO_NAME "a_cywee"