Zephry传感器模型介绍和bme240测试
Posted 17岁boy想当攻城狮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zephry传感器模型介绍和bme240测试相关的知识,希望对你有一定的参考价值。
一、Sensor模型介绍
Zephry针对传感器这一类设备定义了一套统一传感器驱动接口,如果你的传感器想要在Zephry上实现自己的驱动,那么需要遵守这一套接口模型标准。
因为市面上的传感器大多数所反馈的数据基本上都是差不多的,如水平X、Y轴,温度、气压等一些数据,Zephry为了统一管理实现了一套标准API与宏定义,要求开发者们根据去实现这一套标准API,在结合对应的宏定义来反馈传感器对应的值。
Zephry不关心传感器底层是如何实现的,也不关心如何与传感器进行通讯,它要求开发者们实现这套标准统一API,至于底层怎么写随开发者们,无限制,但要求传递参数与反馈值是统一的。
Zephry要求你的传感器设备需要定义在总线上,即定义在dts文件的总线上,让Zephry知道你的设备是通过什么进行通讯的,是I2C、SPI,基于这些Zephry才能解析并调用对应的API,同时这些通讯方式也要求开发者们自己实现,如果你的传感器支持I2C、SPI,那么你就需要写两套代码给Zephry。
二、统一接口
Zephry提供了五个统一开发接口:
传感器接口模型定义在"include/dirvers/sensor.h"文件中
1. sensor_sample_fetch
1.1 函数介绍
函数原型 | 作用 | 返回值 |
---|---|---|
int sensor_sample_fetch(struct device *dev); | 将传感器的所有类型数据放置内存 | 0成功,非0失败 |
1.2 参数介绍
参数名 | 类型 | 作用 |
---|---|---|
dev | struct device * | 指向实例化传感器设备指针 |
1.3 备注
如果想要从传感器获取数据需要先执行这个函数,目的是将传感器的数据放置到内存里,我们就可以往内存里存取数据了,这里的类型是指气压,温度,湿度或者水平数据
2. sensor_sample_fetch_chan
1.1 函数介绍
函数原型 | 作用 | 返回值 |
---|---|---|
int sensor_sample_fetch_chan(struct device *dev, enum sensor_channel type); | 将传感器指定类型数据放置内存 | 0成功,非0失败 |
1.2 参数介绍
参数名 | 类型 | 作用 |
---|---|---|
dev | struct device * | 指向实例化传感器设备指针 |
3. sensor_channel_get
1.1 函数介绍
函数原型 | 作用 | 返回值 |
---|---|---|
int sensor_channel_get(struct device *dev, enum sensor_channel chan, struct sensor_value *val); | 从内存获取指定类型的数据 | 0成功,非0失败 |
1.2 参数介绍
参数名 | 类型 | 作用 |
---|---|---|
dev | struct device * | 指向实例化传感器设备指针 |
chan | enum sensor_channel chan, | 数据类型 |
val | struct sensor_value * | 存放数据的结构体指针 |
4. sensor_attr_set
1.1 函数介绍
函数原型 | 作用 | 参数 |
---|---|---|
int sensor_attr_set(struct device *dev, enum sensor_channel chan, enum sensor_attribute attr, const struct sensor_value *val); | 设置传感器参数,如采样频率,触发阈值 | 0成功,非0失败 |
1.2 参数介绍
参数名 | 类型 | 作用 |
---|---|---|
dev | struct device * | 指向实例化传感器设备指针 |
chan | enum sensor_channel | 数据类型 |
attr | enum sensor_attribute | 要设定的属性类型 |
val | const struct sensor_value * | 要设定的值 |
5. sensor_trigger_set
1.1 函数介绍
函数原型 | 作用 | 参数 |
---|---|---|
static inline int sensor_trigger_set(const struct device *dev, struct sensor_trigger *trig, sensor_trigger_handler_t handler) | 激活传感器触发器,并设置触发器的处理函数 | 0成功,非0失败 |
1.2 参数介绍
参数名 | 类型 | 作用 |
---|---|---|
dev | struct device * | 指向实例化传感器设备指针 |
trig | struct sensor_trigger * | 触发器激活属性 |
handler | sensor_trigger_handler_t | 触发器的激活函数入口地址 |
三、数据类型
1. 传感器数据类型
枚举值供sensor_sample_fetch_chan、sensor_channel_get函数使用
以下枚举类型为“enum sensor_channel”
枚举 | 作用 | 表示单位 |
---|---|---|
枚举 | 作用 | 表示单位 |
SENSOR_CHAN_ACCEL_X | X轴加速度 | m/s^2(每秒变化多少米) |
SENSOR_CHAN_ACCEL_Y | y轴加速度 | m/s^2(每秒变化多少米) |
SENSOR_CHAN_ACCEL_Z | z轴加速度 | m/s^2(每秒变化多少米) |
SENSOR_CHAN_ACCEL_XYZ | 任意加速度 | NULL |
SENSOR_CHAN_GYRO_X | 绕X轴角速度 | radians/s(每秒变化多少角度) |
SENSOR_CHAN_GYRO_Y | 绕y轴角速度 | radians/s(每秒变化多少角度) |
SENSOR_CHAN_GYRO_Z | 绕z轴角速度 | radians/s(每秒变化多少角度) |
SENSOR_CHAN_GYRO_XYZ | 任意角速度 | NULL |
SENSOR_CHAN_MAGN_X | X轴地磁 | Gauss(高斯) |
SENSOR_CHAN_MAGN_Y | y轴地磁 | Gauss(高斯) |
SENSOR_CHAN_MAGN_Z | z轴地磁 | Gauss(高斯) |
SENSOR_CHAN_MAGN_XYZ | 任意轴地磁 | NULL |
SENSOR_CHAN_TEMP | 温度 | ℃(摄氏度) |
SENSOR_CHAN_DIE_TEMP | 器件温度 | ℃(摄氏度) |
SENSOR_CHAN_AMBIENT_TEMP | 环境温度 | ℃(摄氏度) |
SENSOR_CHAN_PRESS | 大气压 | 1000Pa(千帕) |
SENSOR_CHAN_PROX | 距离(靠近)传感器 | 1表示接近 |
SENSOR_CHAN_HUMIDITY | 湿度 | %(百分比) |
SENSOR_CHAN_LIGHT | 可见光强 | lux(勒克斯) |
SENSOR_CHAN_IR | 红外光强 | lux(勒克斯) |
SENSOR_CHAN_RED | 红色光强 | lux(勒克斯) |
SENSOR_CHAN_GREEN | 绿色光强 | lux(勒克斯) |
SENSOR_CHAN_BLUE | 蓝色光强 | lux(勒克斯) |
SENSOR_CHAN_ALTITUDE | 高度传感器 | m(米) |
SENSOR_CHAN_PM_1_0 | PM1.0传感器 | ug/m^3(当前天气中细颗粒物在空气中是多少微克每立方米) |
SENSOR_CHAN_PM_2_5 | PM2.5传感器 | ug/m^3(当前天气中细颗粒物在空气中是多少微克每立方米) |
SENSOR_CHAN_PM_10 | PM2.5传感器 | ug/m^3(当前天气中细颗粒物在空气中是多少微克每立方米) |
SENSOR_CHAN_DISTANCE | 距离传感器 | m(米) |
SENSOR_CHAN_CO2 | CO2传感器, | ppm(浓度) |
SENSOR_CHAN_VOC | VOC传感器, | ppm(浓度) |
SENSOR_CHAN_VOLTAGE | 电压, | V(电压) |
SENSOR_CHAN_CURRENT | 电流, | A(电流) |
SENSOR_CHAN_ALL | 所有类型数据 | NULL |
1.1 备注
以上的表示单位是要求获取时的返回单位,传感器不一定要返回对应的单位,但是在调用此API时在返回时需要进行一次转换,转换为对应的单位并返回,这是Zephry定义的一个规范,你可以不遵守,没有强制条件,但如果不遵守的话会不符合规范,你的驱动无法提供给别人,只能自己用。
你的驱动要根据以上数据类型去实现对应的数据获取功能,如果里面没有你的数据类型,可以自己定义。
四、数据格式
1. sensor_value
1.1 结构体原型
struct sensor_value {
s32_t val1;
s32_t val2;
};
1.2 成员介绍
成员名 | 类型 | 作用 |
---|---|---|
val1 | s32_t | 整数 |
val2 | s32_t | 小数 |
2. 备注
Zephry为了防止部分机器可能没有FPU或传感器与当前开发板的内存大小不一致,产生浮点数溢出或值不匹配使用一个结构体来表示整数部分与小数部分,这样有效解决了在传递浮点数时出现的精度问题,以及不支持浮点数运算的硬件条件。
现在GLIB C有一套自己的浮点数运算库,不依赖FPU,但是效率较慢,GLIB C是C语言的运行库。
五、工作参数
1. 传感器参数类型
此类型供sensor_attr_set设置时使用
以下枚举类型为“enum sensor_attribute”
一般情况下这些工作参数都是编译期间已经在DTS文件中定义好了,Zephry在编译期间就已经确定它的工作参数,在运行期间也是可以修改的
枚举 | 作用 |
---|---|
SENSOR_ATTR_SAMPLING_FREQUENCY | 传感器采样频率(具体含义由实际驱动实现决定,例如:一秒测量多少次) |
SENSOR_ATTR_LOWER_THRESH | 最低触发阈值 |
SENSOR_ATTR_UPPER_THRESH | 最高触发阈值 |
SENSOR_ATTR_SLOPE_TH | 斜率(任意运动)触发 |
SENSOR_ATTR_SLOPE_DUR | 斜率维持超过一定时间触发 |
SENSOR_ATTR_OVERSAMPLING | 过采样参数 |
SENSOR_ATTR_FULL_SCALE | 传感器量程 |
SENSOR_ATTR_OFFSET | 传感器值校正,sensor_channel_get返回的传感器值将被改值偏置final_value = sensor_value + offset |
SENSOR_ATTR_CALIB_TARGET | 传感器自身校正,用芯片内部的算法校正传感器的某个或者所有轴(传感器内部校正功能) |
六、触发模式
1. 触发类型
对于一些传感器支持中断,根据传感器应用场景通常有不同的触发模式,Zephry提供了一套标准触发类型
这些类型供sensor_trigger_set函数使用
枚举类型:“enum sensor_trigger_type”
枚举 | 作用 |
---|---|
SENSOR_TRIG_TIMER | 定时触发 |
SENSOR_TRIG_DATA_READY | 传感器数据准备好触发 |
SENSOR_TRIG_DELTA | 通道量有连续变化时触发(需设置SENSOR_ATTR_SLOPE_TH和SENSOR_ATTR_SLOPE_DUR) |
SENSOR_TRIG_NEAR_FAR | 接近或者远离触发 |
SENSOR_TRIG_THRESHOLD | 超过配置阈值触发(需设置SENSOR_ATTR_LOWER_THRESH和SENSOR_ATTR_UPPER_THRESH) |
SENSOR_TRIG_TAP | 单击触发 |
SENSOR_TRIG_DOUBLE_TAP | 双击触发 |
注意sensor_attr_set仅仅是设置了工作参数,它并不会开启触发中断,开启触发中断需要sensor_trigger_set,它会根据sensor_attr_set设置的工作参数来触发对应的中断类型
七、驱动实现
开发者们需要根据sensor_driver_api成员接口进行实现,这个结构体就是最开始说的统一API的定义,开发者们的传感器驱动代码需要放到“drivers/sensor/”目录下,这是Zephry下的一个编写规范
开发者们根据传感器实现如下五个API,这些API在最开始就已经介绍过了,这里简单列出来:
- sensor_sample_fetch
- sensor_sample_fetch_chan
- sensor_channel_get
- sensor_attr_set
- sensor_trigger_set
如我要实现sensor_channel_get
只需要将原型声明一致,名称倒无所谓,最后的时候我们会通过sensor_driver_api这个结构体里的函数指针去指向我们的函数就可以了,最后在调用DT_INST_FOREACH_STATUS_OKAY驱动注册宏将我们的驱动注册到Zephry内核中
static int mysensor_channel_get(struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
//这里写你的实现
}
针对SPI和I2C通讯的传感器,也要有一套注册流程,一般情况下我们会先写好传感器数据处理的API,至于通讯的在另外实现,因为I2C和SPI就是数据交互不涉及到业务代码,所以单独实现一个I2C与SPI通讯的模块并通过特定宏注册到Sensor模型,Zephry会根据dts文件里的定义来调用对应的接口去获取数据,然后将获取到的数据在传递给业务API
1. BME280注册分析
1.1 什么是BME280
BME280是一个温度,气压、湿度三合一的传感器,可以通过它获取当前温度与气压还有湿度,支持I2C与SPI通讯接口
1.2 注册流程分析
BME280的驱动代码在“drivers/sensor/bem280”目录下
BME280的示例代码在“samples/sensor/bme280”目录下
这里为了让大家更了解Zephry如何注册我们设备,以及如何知道我们传感器使用的是什么通讯协议,从BME280驱动实现给大家分析一下
BME280实现了业务层的API与SPI、I2C通讯的三个模块。
在注册时会将I2C与SPI也注册到Zephry里
SPI的注册
#define BME280_CONFIG_SPI(inst) \\
{ \\
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \\
.bus_io = &bme280_bus_io_spi, \\
.bus_config.spi_cfg = \\
SPI_CONFIG_DT_INST(inst, \\
BME280_SPI_OPERATION, \\
0), \\
}
在编译期间Zephry会解析DTS里的描述,并将实例化的结构体传递进来,这里可以看到将bus总线的指向,这里可以关注bus_io与bus_config这两个成员
它俩的作用就是告诉Zephry SPI的模块在哪,其中SPI总线是哪根线,驱动名是什么也是在DTS文件中写好的,到时候都会传递进来。
其中bme280_bus_io_spi这个结构体就是bme280下spi模块的实现
原型如下:
const struct bme280_bus_io bme280_bus_io_spi = {
.check = bme280_bus_check_spi,
.read = bme280_reg_read_spi,
.write = bme280_reg_write_spi,
};
然后注册到Zephry里就可以了
I2C的注册
#define BME280_CONFIG_I2C(inst) \\
{ \\
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)), \\
.bus_io = &bme280_bus_io_i2c, \\
.bus_config.i2c_addr = DT_INST_REG_ADDR(inst), \\
}
与SPI注册大同小异
然后在声明一个宏注册所有的模块
#define BME280_DEFINE(inst) \\
static struct bme280_data bme280_data_##inst; \\
static const struct bme280_config bme280_config_##inst = \\
COND_CODE_1(DT_INST_ON_BUS(inst, spi), \\
(BME280_CONFIG_SPI(inst)), \\
(BME280_CONFIG_I2C(inst))); \\
DEVICE_DT_INST_DEFINE(inst, \\
bme280_chip_init, \\
bme280_pm_ctrl, \\
&bme280_data_##inst, \\
&bme280_config_##inst, \\
POST_KERNEL, \\
CONFIG_SENSOR_INIT_PRIORITY, \\
&bme280_api_funcs);
这个宏会将bme280的data以及config、api、io通讯模块全部注册进来了,最后一步会调用DT_INST_FOREACH_STATUS_OKAY这个宏去注册,这个宏会将DTS里状态为OK的节点创建一个结构体,并传递进来
DT_INST_FOREACH_STATUS_OKAY(BME280_DEFINE)
这样BME280_DEFINE宏里的inst就变成dts文件里定义的设备详细描述结构体了,然后去注册就可以了。
这里讲一下Zephry是如何知道我们获取的是BME280这个结构体的,我们拆开DT_INST_FOREACH_STATUS_OKAY宏函数看一下
#define DT_INST_FOREACH_STATUS_OKAY(fn) \\
COND_CODE_1(DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT), \\
(UTIL_CAT(DT_FOREACH_OKAY_INST_, \\
DT_DRV_COMPAT)(fn)), \\
())
可以看到里面用DT_HAS_COMPAT_STATUS_OKAY这个宏去取的,其中DT_DRV_COMPAT这个宏就是要序列化DTS节点的名字
这个宏在bme240.h中有定义
#define DT_DRV_COMPAT bosch_bme280
这个名字是COMPAT的,这里可以看下bme240 dts文件里的compat的定义
bme280@76 {
compatible = "bosch,bme280";
status = "okay";
label = "BME280";
reg = <0x76>;
};
compatible表示的是厂商与设备名,用“,”分割开,但是Zephry编译器在实例化的时会将","替换为“_”,这是因为C语言里面不允许使用特殊符号,所以这也是为什么dts里是"bosch,bme280",而在引用时却是"bosch_bme280"的原因
至于Zephry是如何知道你的传感器是怎么通讯的,然后调用对应接口的,这里给大家参考一下我之前移植stm32f746g_disco的一个写法
&i2c1 {
pinctrl-0 = <&i2c1_scl_pb8 &i2c1_sda_pb9>;
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
bme280@76 {
compatible = "bosch,bme280";
status = "okay";
label = "BME280";
reg = <0x76>;
};
};
可以看到我bme280是写在i2c1下的,到时候实例化的时候这些硬件信息都会传递给我们的驱动注册宏函数里,刚刚分析的注册函数里有一条代码:
.bus = DEVICE_DT_GET(DT_INST_BUS(inst)),
这段代码就是去取当前总线类型,我们当前是i2c1,就会将这个总线取出来,类型为i2c,编号为1的这条线上,然后就是这条线的一些硬件信息,到时候这些硬件信息都会传递给驱动,让驱动知道该去哪个地址找进行控制
这里需要注意status="okay"代表这是一个驱动节点,会将这个节点传递给驱动,如果没有okay不会视为驱动节点,不会进行驱动注册时的传递
八、测试
1. 基于BME280驱动写一个Test
1.1 开发
如果不清楚如何使用Drive以及如何知道Drive注册时的名称可以参考这篇文章:Zephry I2C和SPI驱动器介绍和操作FM24V10闪存
1.1.1 包含基本头文件
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/sensor.h>
注意这里不需要包含我们驱动的头文件,只需要包含sensor的头文件就可以了,因为基于它的模型,我们只需要保证我们的dev是指向我们的驱动就可以了,因为Zephry会根据okay为标志去将Drivers目录下的驱动进行注册实例化,到时候调用的时会读取dev的名称然后去调用对应的实现API,会通过Device Name来找对应的注册好的驱动结构体,然后去调用。
1.1.2 获取我们的设备
const struct device *dev = DEVICE_DT_GET_ANY("BME280");
1.1.3 获取气压,温度,湿度
while (1) {
struct sensor_value temp, press, humidity;
sensor_sample_fetch(dev);
sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(dev, SENSOR_CHAN_PRESS, &press);
sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity);
printk("temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\\n",
temp.val1, temp.val2, press.val1, press.val2,
humidity.val1, humidity.val2);
k_sleep(K_MSEC(1000));
}
1.1.4 完整代码
#include <device.h>
#include <devicetree.h>
#include <drivers/sensor.h>
void main(){
const struct device *dev = DEVICE_DT_GET_ANY("BME280");
if(dev == NULL){
printk("get drive error\\n");
return;
}
while (1) {
//获取设备句柄
struct sensor_value temp, press, humidity;
//将传感器数据读入到内存
sensor_sample_fetch(dev);
//读取数据
sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
sensor_channel_get(dev, SENSOR_CHAN_PRESS, &press);
sensor_channel_get(dev, SENSOR_CHAN_HUMIDITY, &humidity);
//打印
printk("temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\\n",
temp.val1, temp.val2, press.val1, press.val2,
humidity.val1, humidity.val2);
//延迟一秒
k_sleep(K_MSEC(1000));
}
}
1.1.5 运行结果
temp: 24.080000; press: 100.887019; humidity: 55.473632
temp: 24.080000; press: 100.886230; humidity: 55.484375
temp: 24.080000; press: 100.886144; humidity: 55.484375
temp: 24.070000; press: 100.886222; humidity: 55.496093
2. 触发方式获取数据
这个需要传感器支持中断方式,触发方式也比较简单,使用的API以及结构体类型都已经介绍过了
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/sensor.h>
//触发函数
static void trigger_handler(struct device *dev, struct sensor_trigger *trig)
{
//通过触发方式获取temp护具
struct sensor_value temp;
//读入内存
sensor_sample_fetch(dev);
//读取数据
sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp);
//打印
printf("trigger fired, temp %d.%06d\\n", temp.val1, temp.val2);
}
void main(void)
{
//获取传感器
struct device *dev = device_get_binding("Trigger_SenSor");
if (dev == NULL) {
printf("device not found. aborting test.\\n");
return;
}
//触发参数
struct sensor_value val;
//触发类型
struct sensor_trigger trig;
//设置最高阈值触发为26.0度
val.val1 = 26;
val.val2 = 0;
//设置26度为高阈值
sensor_attr_set(dev, SENSOR_CHAN_AMBIENT_TEMP,
SENSOR_ATTR_UPPER_THRESH, &val);
//设置触发类型
trig.type = SENSOR_TRIG_THRESHOLD; //触发类型为超出这个预设阈值
trig.chan = SENSOR_CHAN_AMBIENT_TEMP; //以温度为阈值来源
//设定触发状态,并设置触发处理函数
if (sensor_trigger_set(dev, &trig, trigger_handler)) {
printf("Could not set trigger. aborting test.\\n");
return;
}
//进入循环等待
while (1) {
k_sleep(2000);
}
}
当温度高出26度时会自动跳转到trigger_handler函数进行处理
以上是关于Zephry传感器模型介绍和bme240测试的主要内容,如果未能解决你的问题,请参考以下文章
ESP 保姆级教程 疯狂传感器篇 —— 案例:ESP8266 + BME280 + 串口输出
基于Stm32F746g_disg平台下移植zephry使用TinyML预测模型