5.10 Sensors 设备
5.10 Sensors 设备
📚 本节导读
学习时长:约 35 分钟
难度级别:⭐⭐⭐⭐☆
前置知识:第 5.1 节设备驱动框架、第 5.3 节 I2C 设备
本节讲解 OneOS 的 Sensors 传感器框架。它位于 drivers/sensors/,为加速度计、陀螺仪、温度、湿度、气压、光照等各类传感器提供统一的抽象接口,屏蔽不同厂商芯片的差异。应用通过标准设备接口(os_device_read / os_device_control)读写传感器,无需关心底层是 I2C 还是 SPI。
本节会带你:
- 理解传感器框架的类型/单位/模式枚举(
OS_SENSOR_CLASS_*/OS_SENSOR_UNIT_*/OS_SENSOR_MODE_*); - 掌握真实数据结构
os_sensor_device(含parent继承机制、data_buf/data_len配合机制)与os_sensor_data(联合体存储各类数据); - 掌握整数缩放单位换算成实际物理量的方法(
mG÷1000、dCelsius÷10 等); - 逐个拆解
os_sensor_read/os_sensor_control/os_sensor_init/os_sensor_cb/os_hw_sensor_register的完整源码; - 掌握传感器通过标准设备接口读取的正确用法;
- 理解如何新增一颗传感器芯片驱动(
os_sensor_ops契约 +os_hw_sensor_register注册)的适配方法。
说明:本节所有源码均取自项目
drivers/sensors/sensor.h、drivers/sensors/sensor.c、drivers/sensors/adxl345.c,与项目源码逐字一致(含原始英文注释)。
🎯 学习目标
- 理解传感器框架的类型/单位/模式/控制命令枚举
- 掌握真实数据结构
os_sensor_device与os_sensor_data(联合体) - 掌握整数缩放单位(
mG/dCelsius/permillage)的换算方法 - 逐个拆解
os_sensor_read/os_sensor_control/os_sensor_init/os_sensor_cb/os_hw_sensor_register的完整源码 - 掌握传感器通过标准设备接口读取的正确用法
- 理解传感器设备自动加类型前缀的命名规则
- 理解传感器芯片驱动的
os_sensor_ops契约,掌握新增一颗传感器的适配流程
一、概述
1.1 传感器框架解决什么问题
一句话核心:传感器框架解决"不同厂商传感器差异大、应用难以统一访问"的问题。它让你"用同一套标准设备接口读写加速度计、温度计、气压计等所有传感器"。
为什么需要统一框架:
- 不同传感器用不同通信接口(I2C/SPI/UART/单总线/ADC)、不同数据格式、不同单位。
- 若应用为每颗芯片各写一套代码,移植和扩展成本极高。
- 框架用统一的数据结构
os_sensor_data和枚举(类型/单位/模式),把差异封装在驱动层,应用只面对标准接口。
1.2 传感器类型与单位枚举
来源:drivers/sensors/sensor.h
传感器类型(OS_SENSOR_CLASS_*):
| 类型宏 | 值 | 含义 | 设备名前缀 |
|---|---|---|---|
OS_SENSOR_CLASS_NONE | 0 | 无 | none |
OS_SENSOR_CLASS_ACCE | 1 | 加速度计 | acce_ |
OS_SENSOR_CLASS_GYRO | 2 | 陀螺仪 | gyro_ |
OS_SENSOR_CLASS_MAG | 3 | 磁力计 | mag_ |
OS_SENSOR_CLASS_TEMP | 4 | 温度 | temp_ |
OS_SENSOR_CLASS_HUMI | 5 | 湿度 | humi_ |
OS_SENSOR_CLASS_BARO | 6 | 气压 | baro_ |
OS_SENSOR_CLASS_LIGHT | 7 | 光照 | li_ |
OS_SENSOR_CLASS_PROXIMITY | 8 | 接近 | pr_ |
OS_SENSOR_CLASS_HR | 9 | 心率 | hr_ |
OS_SENSOR_CLASS_TVOC | 10 | TVOC | tvoc_ |
OS_SENSOR_CLASS_NOISE | 11 | 噪声 | noi_ |
OS_SENSOR_CLASS_STEP | 12 | 计步 | step_ |
OS_SENSOR_CLASS_FORCE | 13 | 力 | forc_ |
OS_SENSOR_CLASS_ALTI | 14 | 高度 | alti_ |
OS_SENSOR_CLASS_IR | 15 | 红外强度 | ir_ |
关键理解 —— 设备命名规则:os_hw_sensor_register(sensor, "mpu6050", ...) 注册时,框架会自动在名字前加类型前缀(见 3.5 节源码)。所以:
- 加速度计注册为
"mpu6050"→ 设备名"acce_mpu6050" - 温度计注册为
"sht30"→ 设备名"temp_sht30"
数据单位(OS_SENSOR_UNIT_*,注意是整数缩放单位):
| 单位宏 | 值 | 含义 | 说明 |
|---|---|---|---|
OS_SENSOR_UNIT_MG | 1 | 加速度 | mG(0.001g) |
OS_SENSOR_UNIT_MDPS | 2 | 角速度 | mdps(0.001°/s) |
OS_SENSOR_UNIT_MGAUSS | 3 | 磁力 | mGauss |
OS_SENSOR_UNIT_LUX | 5 | 光照 | lux |
OS_SENSOR_UNIT_PA | 9 | 气压 | Pa |
OS_SENSOR_UNIT_MPERMILLAGE | 10 | 湿度 | mpermillage(0.1%) |
OS_SENSOR_UNIT_MDCELSIUS | 12 | 温度 | mdCelsius(0.1℃) |
OS_SENSOR_UNIT_DCELSIUS | 13 | 温度 | dCelsius(0.1℃) |
OS_SENSOR_UNIT_HZ | 14 | 频率 | Hz |
OS_SENSOR_UNIT_BPM | 16 | 心率 | bpm |
OS_SENSOR_UNIT_MN | 17 | 力 | mN |
OS_SENSOR_UNIT_RAW | 18 | 原始值 | count |
关键理解:传感器数据大多是整数缩放值。例如温度单位
dCelsius表示数值是实际温度的 10 倍(253 = 25.3℃),湿度permillage表示1000 = 100%。展示给用户时需要除以缩放因子。
1.3 工作模式与控制命令
工作模式(OS_SENSOR_MODE_*):
#define OS_SENSOR_MODE_NONE (0)
#define OS_SENSOR_MODE_POLLING (1) /* One shot only read a data */
#define OS_SENSOR_MODE_INT (2) /* One shot interrupt only read a data */
#define OS_SENSOR_MODE_FIFO (3) /* One shot interrupt read all fifo data */| 模式 | 含义 |
|---|---|
OS_SENSOR_MODE_POLLING | 轮询:每次读取一个数据 |
OS_SENSOR_MODE_INT | 中断:数据就绪时触发中断读取一个数据 |
OS_SENSOR_MODE_FIFO | FIFO:中断读取全部 FIFO 数据 |
控制命令(OS_SENSOR_CTRL_*):
| 命令宏 | 值 | 作用 |
|---|---|---|
OS_SENSOR_CTRL_GET_ID | 0 | 获取设备 ID |
OS_SENSOR_CTRL_GET_INFO | 1 | 获取传感器信息(os_sensor_info) |
OS_SENSOR_CTRL_SET_RANGE | 2 | 设置量程(单位与 info 相同) |
OS_SENSOR_CTRL_SET_ODR | 3 | 设置输出数据速率(Hz) |
OS_SENSOR_CTRL_SET_MODE | 4 | 设置工作模式 |
OS_SENSOR_CTRL_SET_POWER | 5 | 设置功耗模式 |
OS_SENSOR_CTRL_SELF_TEST | 6 | 执行自检 |
1.4 传感器使用场景
1.4.1 一句话核心
传感器框架适用于需要读取加速度、陀螺仪、温湿度、气压、光照等物理量的场景。应用用标准设备接口打开带类型前缀的设备并读写。
1.4.2 典型应用场景
场景一:读取温湿度(轮询)
打开 "temp_sht30"(温度)/ "humi_sht30"(湿度)
循环 os_device_read 读取数据为什么用框架:应用无需知道 SHT30 是 I2C 还是 SPI、怎么换算,直接读标准 os_sensor_data。
场景二:读取加速度计(配置量程 + 轮询)
打开 "acce_mpu6050"
os_device_control(dev, OS_SENSOR_CTRL_SET_RANGE, &range)
循环 os_device_read为什么用框架:通过标准控制命令设置量程,读取三轴 acce 数据。
1.4.3 与"直接读 I2C/专用库"的对比(为什么用框架)
用框架 vs 绕过框架直接操作传感器芯片,差别在于抽象成本:
| 维度 | 用 Sensors 框架 | 直接读 I2C/SPI 操作芯片 |
|---|---|---|
| 统一接口 | ✅ 所有传感器都 os_device_read/control | ❌ 每颗芯片一套寄存器操作 |
| 换芯片成本 | 只改 ops 驱动,应用不动 | 应用要跟着改寄存器时序 |
| 中断/FIFO 支持 | ✅ 内置 data_buf/os_sensor_cb | 需自己实现 |
| 额外开销 | 一层间接调用(可忽略) | 无 |
| 适用场景 | 多传感器、需统一管理 | 单颗简单传感器、极致精简 |
结论:产品里传感器通常不止一颗(IMU + 温湿度 + 气压),用框架统一管理收益远大于开销;只有"就一颗、极简"时才值得绕过框架。
1.4.4 选型边界表(何时不该用它)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 只用单颗简单传感器且不想引入框架 | 直接用 I2C(5.3)/ ADC(5.11) | 传感器框架适合多传感器统一管理 |
| 需要高频连续采样 | 直接读底层或硬件 DMA | 框架轮询模式有开销,超高频场景用驱动自定义接口 |
二、核心数据结构
2.1 传感器设备 os_sensor_device
来源:drivers/sensors/sensor.h
typedef struct os_sensor_device *os_sensor_t;
struct os_sensor_device
{
struct os_device parent; /* The standard device */
struct os_sensor_info info; /* The sensor info data */
struct os_sensor_config config; /* The sensor config data */
void *data_buf; /* The buf of the data received */
os_size_t data_len; /* The size of the data received */
const struct os_sensor_ops *ops; /* The sensor ops */
os_err_t (*irq_handle)(os_sensor_t sensor); /* Called when an interrupt is generated */
};| 字段 | 类型 | 含义 |
|---|---|---|
parent | struct os_device(内嵌在开头) | 标准设备基类,让这个结构体"是一个设备",可被 os_device_register 注册、被 os_device_open 打开 |
info | struct os_sensor_info | 传感器信息(类型/厂商/单位/量程),注册时由驱动填入,GET_INFO 命令直接拷贝给应用 |
config | struct os_sensor_config | 传感器配置(接口/引脚/模式/功耗/ODR/量程),SET_* 命令成功后同步更新 |
data_buf/data_len | void * / os_size_t | 中断/FIFO 模式下驱动填充的数据缓冲与条数(字节数),os_sensor_read 优先读这里 |
ops | const struct os_sensor_ops * | 传感器操作接口(fetch_data/control),HAL 层驱动提供 |
irq_handle | os_err_t (*)(os_sensor_t) | 中断处理回调(驱动注册),os_sensor_cb 里最先调用 |
关键理解 —— 为什么 parent(os_device)内嵌在最开头:与 5.2 GPIO、5.8 RTC 同理,parent 是第一个成员,所以"设备指针 os_device_t *dev"和"传感器指针 os_sensor_t sensor"数值相等。os_sensor_read/os_sensor_control 里 (os_sensor_t)dev 只是用更大视角解读 dev 指向的内存,从而访问 parent 后面的 info/config/data_buf/ops。这是 OneOS 用"C 语言继承"实现设备多态的标准手法。
关键理解 —— data_buf/data_len 与 os_sensor_read 的配合机制:
- 轮询模式:驱动在
fetch_data里直接从芯片读数据,data_buf/data_len不参与,os_sensor_read走else分支。 - 中断/FIFO 模式:驱动在中断服务(
irq_handle)里把读到的数据填入data_buf、更新data_len(字节数),然后调os_sensor_cb。os_sensor_read检测到data_len > 0就优先从缓冲拷贝,避免重复读芯片。 - 所以
data_len的单位是字节数,而os_sensor_read里用data_len / sizeof(struct os_sensor_data)换算成数据条数,再与请求数len取较小值拷贝。
数值演算 —— data_len 字节数与数据条数换算:若驱动在 FIFO 模式下往 data_buf 填了 2 条 os_sensor_data:
| 计算项 | 代入计算 | 结果 |
|---|---|---|
data_len(字节数) | 2 × sizeof(struct os_sensor_data) | 2 × 32 = 64 字节 |
data_len / sizeof(...) | 64 / 32 | 2 条 |
os_sensor_read(dev, 0, buf, 64) 的 len | min(64, 2) = 2 | 拷 2 条给 buf |
拷贝后 data_len | 清空 | 0(下次走 fetch_data) |
2.2 传感器信息 os_sensor_info
struct os_sensor_info
{
uint8_t type; /* The sensor type */
uint8_t vendor; /* Vendor of sensors */
const char *model; /* model name of sensor */
uint8_t unit; /* unit of measurement */
uint8_t intf_type; /* Communication interface type */
int32_t range_max; /* maximum range of this sensor's value. unit is 'unit' */
int32_t range_min; /* minimum range of this sensor's value. unit is 'unit' */
uint32_t period_min; /* Minimum measurement period,unit:ms. zero = not a constant rate */
uint8_t fifo_max;
};2.3 传感器数据 os_sensor_data(联合体)
struct sensor_3_axis
{
int32_t x;
int32_t y;
int32_t z;
};
struct os_sensor_data
{
uint32_t timestamp; /* The timestamp when the data was received */
uint8_t type; /* The sensor type of the data */
union
{
struct sensor_3_axis acce; /* Accelerometer. unit: mG */
struct sensor_3_axis gyro; /* Gyroscope. unit: mdps */
struct sensor_3_axis mag; /* Magnetometer. unit: mGauss */
int32_t temp; /* Temperature. unit: dCelsius */
int32_t humi; /* Relative humidity. unit: permillage */
int32_t baro; /* Pressure. unit: pascal (Pa) */
int32_t light; /* Light. unit: lux */
int32_t proximity; /* Distance. unit: centimeters */
int32_t hr; /* Heart rate. unit: bpm */
int32_t tvoc; /* TVOC. unit: permillage */
int32_t noise; /* Noise Loudness. unit: HZ */
uint32_t step; /* Step sensor. unit: 1 */
int32_t force; /* Force sensor. unit: mN */
int32_t alti; /* Altitude sensor. unit: m */
uint32_t raw; /* Raw Data. unit: 1 */
} data;
};关键理解 —— 联合体的重要性:
data是一个union,一次只能表示一种数据。因为传感器一次只产生一种类型的读数。- 三轴数据(
acce/gyro/mag)是int32_t的x/y/z,不是 float。 - 温度
temp单位是dCelsius(0.1℃),湿度humi单位是permillage(0.1%),都是整数缩放,不是 float。 - 每次读取后,用
type字段判断这份数据是哪种类型,再访问对应的联合体成员。
数值演算 —— 整数缩放单位换算成实际物理量(这是最容易错的地方,务必掌握):
| 成员 | 单位宏 | 缩放因子 | 原始整数值 | 实际物理量 |
|---|---|---|---|---|
temp | dCelsius | ÷10 | 253 | 25.3℃ |
humi | permillage | ÷10 | 540 | 54.0%(注:permillage=千分之,540 = 54.0%) |
acce.x/y/z | mG | ÷1000 | x = 981 | 0.981 g |
gyro.x/y/z | mdps | ÷1000 | z = -500 | -0.500 °/s |
baro | Pa | ÷1(无需) | 101325 | 101325 Pa |
light | lux | ÷1(无需) | 430 | 430 lux |
alti | m | ÷1(无需) | 123 | 123 m |
force | mN | ÷1000 | 500 | 0.5 N |
换算公式:实际物理量 = 原始整数值 ÷ 缩放因子。温度
253 ÷ 10 = 25.3℃,加速度981 ÷ 1000 = 0.981 g。dCelsius(0.1℃)与mG(0.001g)是同一缩放思想的不同因子。代码里展示时就是data.data.temp / 10.0f、data.data.acce.x / 1000.0f(见四、完整使用示例)。
2.4 传感器操作接口 os_sensor_ops
struct os_sensor_ops
{
os_size_t (*fetch_data)(struct os_sensor_device *sensor, void *buf, os_size_t len);
os_err_t (*control)(struct os_sensor_device *sensor, int cmd, void *arg);
};| 函数 | 说明 |
|---|---|
fetch_data | 驱动层从芯片读取数据到 buf |
control | 驱动层处理控制命令 |
三、核心源码详解
说明:以下源码均取自
drivers/sensors/sensor.c,与项目源码逐字一致(含原始英文注释)。
3.1 os_sensor_read —— 读取传感器数据
一句话定位:标准设备 read 操作。优先读缓冲(data_len > 0),否则直接调驱动 fetch_data。
完整源码(drivers/sensors/sensor.c,与项目一致):
static os_ssize_t os_sensor_read(os_device_t *dev, os_off_t pos, void *buf, os_size_t len)
{
os_sensor_t sensor = (os_sensor_t)dev;
os_size_t result = 0;
OS_ASSERT(dev != OS_NULL);
if (buf == NULL || len == 0)
{
return 0;
}
/* The buffer is not empty. Read the data in the buffer first */
if (sensor->data_len > 0)
{
if (len > sensor->data_len / sizeof(struct os_sensor_data))
{
len = sensor->data_len / sizeof(struct os_sensor_data);
}
memcpy(buf, sensor->data_buf, len * sizeof(struct os_sensor_data));
/* Clear the buffer */
sensor->data_len = 0;
result = len;
}
else
{
/* If the buffer is empty read the data */
result = sensor->ops->fetch_data(sensor, buf, len);
}
return result;
}分步详解:
第 1 步:参数校验(条件 buf == NULL 或 len == 0)
OS_ASSERT(dev != OS_NULL);
if (buf == NULL || len == 0)
{
return 0;
}详细解释:缓冲区为空或长度为 0 时返回 0(读不到数据)。
第 2 步:缓冲非空则读缓冲(条件 data_len > 0)
if (sensor->data_len > 0)
{
if (len > sensor->data_len / sizeof(struct os_sensor_data))
{
len = sensor->data_len / sizeof(struct os_sensor_data);
}
memcpy(buf, sensor->data_buf, len * sizeof(struct os_sensor_data));
sensor->data_len = 0;
result = len;
}详细解释:关键理解:data_len > 0 说明有中断/FIFO 模式下驱动填入缓冲的数据(见 3.4 节 os_sensor_cb)。此时优先从缓冲拷贝,len 取"请求数"和"缓冲内数据条数"的较小值。拷贝后清空 data_len。
第 3 步:缓冲空则直接读芯片(条件 缓冲空)
else
{
result = sensor->ops->fetch_data(sensor, buf, len);
}
return result;
}详细解释:缓冲空(轮询模式)时,直接调驱动层 ops->fetch_data(sensor, buf, len) 从芯片读数据,返回实际读到的条数。
os_sensor_read() 条件走向总结:
os_sensor_read() 条件走向总结:
├── buf == NULL 或 len == 0 → 返回 0
├── data_len > 0(缓冲有数据)
│ ├── len = min(len, data_len / sizeof(os_sensor_data))
│ ├── memcpy(buf, data_buf, len * sizeof(os_sensor_data))
│ ├── data_len = 0(清空)
│ └── result = len
└── data_len == 0(轮询模式)→ result = ops->fetch_data(sensor, buf, len)
└── 返回 result3.2 os_sensor_control —— 控制命令分发
一句话定位:标准设备 control 操作,按命令分发到驱动 ops->control,并同步更新 config。
完整源码(drivers/sensors/sensor.c,与项目一致):
static os_err_t os_sensor_control(os_device_t *dev, int cmd, void *args)
{
os_sensor_t sensor = (os_sensor_t)dev;
os_err_t result = OS_SUCCESS;
OS_ASSERT(dev != OS_NULL);
switch (cmd)
{
case OS_SENSOR_CTRL_GET_ID:
if (args)
{
sensor->ops->control(sensor, OS_SENSOR_CTRL_GET_ID, args);
}
break;
case OS_SENSOR_CTRL_GET_INFO:
if (args)
{
memcpy(args, &sensor->info, sizeof(struct os_sensor_info));
}
break;
case OS_SENSOR_CTRL_SET_RANGE:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_RANGE, args);
if (result == OS_SUCCESS)
{
sensor->config.range = (int32_t)args;
}
break;
case OS_SENSOR_CTRL_SET_ODR:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_ODR, args);
if (result == OS_SUCCESS)
{
sensor->config.odr = (uint32_t)args & 0xFFFF;
}
break;
case OS_SENSOR_CTRL_SET_POWER:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_POWER, args);
if (result == OS_SUCCESS)
{
sensor->config.power = (uint32_t)args & 0xFF;
}
break;
case OS_SENSOR_CTRL_SELF_TEST:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SELF_TEST, args);
break;
default:
return OS_FAILURE;
}
return result;
}分步详解:
第 1 步:按命令分支处理(条件 cmd 值)
switch (cmd)
{
case OS_SENSOR_CTRL_GET_ID:
if (args)
{
sensor->ops->control(sensor, OS_SENSOR_CTRL_GET_ID, args);
}
break;
case OS_SENSOR_CTRL_GET_INFO:
if (args)
{
memcpy(args, &sensor->info, sizeof(struct os_sensor_info));
}
break;
case OS_SENSOR_CTRL_SET_RANGE:
case OS_SENSOR_CTRL_SET_ODR:
case OS_SENSOR_CTRL_SET_POWER:
case OS_SENSOR_CTRL_SELF_TEST:
case OS_SENSOR_CTRL_SET_MODE:
break; /* 这 5 个 SET_* 命令走第 2 步的透传+同步逻辑 */
default:
return OS_FAILURE;
}详细解释:GET_ID 透传给驱动;GET_INFO 直接把 sensor->info 拷贝给 args(无需驱动参与,因为 info 在 os_hw_sensor_register 注册时由驱动填入);SET_RANGE/SET_ODR/SET_POWER/SELF_TEST/SET_MODE 透传给驱动(见第 2 步)。未知命令走 default 返回 OS_FAILURE。
第 2 步:同步更新 config(条件 设置成功)
case OS_SENSOR_CTRL_SET_RANGE:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_RANGE, args);
if (result == OS_SUCCESS)
{
sensor->config.range = (int32_t)args;
}
break;
case OS_SENSOR_CTRL_SET_ODR:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_ODR, args);
if (result == OS_SUCCESS)
{
sensor->config.odr = (uint32_t)args & 0xFFFF;
}
break;
case OS_SENSOR_CTRL_SET_POWER:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_POWER, args);
if (result == OS_SUCCESS)
{
sensor->config.power = (uint32_t)args & 0xFF;
}
break;
case OS_SENSOR_CTRL_SELF_TEST:
result = sensor->ops->control(sensor, OS_SENSOR_CTRL_SELF_TEST, args);
break;
default:
return OS_FAILURE;
}
return result;
}详细解释:关键理解:SET_* 命令把命令下发给驱动后,若驱动返回成功,同步更新 sensor->config(range/odr/power),保证 config 反映真实硬件状态。注意 odr/power 回写时用 & 0xFFFF/& 0xFF 做位宽截断(odr 只存低 16 位、power 只存低 8 位);SELF_TEST 不回写 config;未知命令返回 OS_FAILURE。
os_sensor_control() 条件走向总结:
os_sensor_control(dev, cmd, args) 条件走向总结:
├── GET_ID → args 非空则透传驱动(否则跳过);break
├── GET_INFO → args 非空则 memcpy(args, &sensor->info);break(无需驱动)
├── SET_RANGE → 透传驱动;成功则 config.range = (int32_t)args;break
├── SET_ODR → 透传驱动;成功则 config.odr = (uint32_t)args & 0xFFFF;break
├── SET_POWER → 透传驱动;成功则 config.power = (uint32_t)args & 0xFF;break
├── SELF_TEST → 透传驱动(成功与否都 break,不回写 config);break
├── SET_MODE → 透传驱动(见 3.3 init 中调用)
└── default → 返回 OS_FAILURE(未知命令)
└── 返回 result3.3 os_sensor_init —— 传感器初始化(打开时自动执行)
一句话定位:设备打开时把传感器配置为轮询模式 + 正常功耗。
完整源码(drivers/sensors/sensor.c,与项目一致):
static os_err_t os_sensor_init(os_device_t *dev)
{
os_sensor_t sensor = (os_sensor_t)dev;
OS_ASSERT(dev != OS_NULL);
os_err_t res = OS_SUCCESS;
if (sensor->ops->control != OS_NULL)
{
/* If polling mode is supported, configure it to polling mode */
sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_MODE, (void *)OS_SENSOR_MODE_POLLING);
}
sensor->config.mode = OS_SENSOR_MODE_POLLING;
/* Configure power mode to normal mode */
if (sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_POWER, (void *)OS_SENSOR_POWER_NORMAL) == OS_SUCCESS)
{
sensor->config.power = OS_SENSOR_POWER_NORMAL;
}
return res;
}分步详解:
第 1 步:配置为轮询模式(条件 ops->control 存在)
if (sensor->ops->control != OS_NULL)
{
sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_MODE, (void *)OS_SENSOR_MODE_POLLING);
}
sensor->config.mode = OS_SENSOR_MODE_POLLING;详细解释:打开设备时,若驱动实现了 control,发 SET_MODE 命令把传感器设为轮询模式(OS_SENSOR_MODE_POLLING),并同步 config.mode。为什么默认轮询:轮询模式最简单通用,打开设备即可读取。
第 2 步:配置正常功耗(条件 设置成功)
if (sensor->ops->control(sensor, OS_SENSOR_CTRL_SET_POWER, (void *)OS_SENSOR_POWER_NORMAL) == OS_SUCCESS)
{
sensor->config.power = OS_SENSOR_POWER_NORMAL;
}
return res;
}详细解释:设置 SET_POWER 为 OS_SENSOR_POWER_NORMAL(正常功耗),成功则同步 config.power。
os_sensor_init() 条件走向总结:
os_sensor_init(dev) 条件走向总结:
├── sensor = (os_sensor_t)dev;res = OS_SUCCESS;OS_ASSERT(dev)
├── ops->control 非空
│ └── control(SET_MODE, POLLING)(配置为轮询模式,忽略返回值)
├── config.mode = OS_SENSOR_MODE_POLLING(无条件同步)
├── control(SET_POWER, POWER_NORMAL) == OS_SUCCESS(能设置正常功耗)
│ └── config.power = OS_SENSOR_POWER_NORMAL(同步 config)
└── 返回 res(OS_SUCCESS)3.4 数据就绪回调 os_sensor_cb
一句话定位:驱动在数据就绪(中断/FIFO)时调用,更新 rx_count 并通知应用可读。
完整源码(drivers/sensors/sensor.c,与项目一致):
void os_sensor_cb(os_sensor_t sen)
{
if (sen->irq_handle != OS_NULL)
{
sen->irq_handle(sen);
}
if (sen->data_len > 0)
{
sen->parent.rx_count = sen->data_len / sizeof(struct os_sensor_data);
os_device_recv_notify(&sen->parent);
}
else if (sen->config.mode == OS_SENSOR_MODE_INT)
{
sen->parent.rx_count = 1;
os_device_recv_notify(&sen->parent);
}
else if (sen->config.mode == OS_SENSOR_MODE_FIFO)
{
sen->parent.rx_count = sen->info.fifo_max;
os_device_recv_notify(&sen->parent);
}
}分步详解:
第 1 步:调用驱动中断处理(条件 irq_handle 存在)
if (sen->irq_handle != OS_NULL)
{
sen->irq_handle(sen);
}详细解释:数据就绪时先调驱动注册的 irq_handle(驱动可在这里填充 data_buf/data_len 或做额外处理)。
第 2 步:按模式更新 rx_count 并通知(条件 模式)
if (sen->data_len > 0)
{
sen->parent.rx_count = sen->data_len / sizeof(struct os_sensor_data);
os_device_recv_notify(&sen->parent);
}
else if (sen->config.mode == OS_SENSOR_MODE_INT)
{
sen->parent.rx_count = 1;
os_device_recv_notify(&sen->parent);
}
else if (sen->config.mode == OS_SENSOR_MODE_FIFO)
{
sen->parent.rx_count = sen->info.fifo_max;
os_device_recv_notify(&sen->parent);
}
}详细解释:根据数据来源更新 rx_count 并 os_device_recv_notify 唤醒等待读取的任务:
- 有
data_buf(驱动已填充):rx_count = data_len / sizeof(os_sensor_data)。 INT模式:rx_count = 1(一个数据)。FIFO模式:rx_count = fifo_max(全部 FIFO 数据)。
os_sensor_cb() 条件走向总结:
os_sensor_cb(sen) 条件走向总结:
├── irq_handle 非空 → 调用 sen->irq_handle(sen)(驱动可填 data_buf/data_len)
├── data_len > 0(驱动已在缓冲填数据)
│ ├── rx_count = data_len / sizeof(os_sensor_data)
│ └── os_device_recv_notify()(唤醒读取任务)
├── 否则 config.mode == INT → rx_count = 1;os_device_recv_notify()
└── 否则 config.mode == FIFO → rx_count = info.fifo_max;os_device_recv_notify()
(以上分支互斥:优先 data_buf,再 INT,最后 FIFO)3.5 os_hw_sensor_register —— 注册传感器设备
一句话定位:驱动调用,给设备自动加类型前缀命名,并注册到设备框架。
完整源码(drivers/sensors/sensor.c,与项目一致):
int os_hw_sensor_register(os_sensor_t sensor, const char *name, void *data)
{
int8_t result;
os_device_t *device;
OS_ASSERT(sensor != OS_NULL);
char *sensor_name = OS_NULL, *device_name = OS_NULL;
/* Add a type name for the sensor device */
sensor_name = sensor_name_str[sensor->info.type];
device_name = (char *)os_calloc(1, strlen(sensor_name) + 1 + strlen(name));
if (device_name == OS_NULL)
{
os_kprintf("device_name calloc failed!\r\n");
return OS_FAILURE;
}
memcpy(device_name, sensor_name, strlen(sensor_name) + 1);
strcat(device_name, name);
device = &sensor->parent;
device->ops = &os_sensor_ops;
device->type = OS_DEVICE_TYPE_SENSOR;
device->user_data = data;
result = os_device_register(device, device_name);
if (result != OS_SUCCESS)
{
os_kprintf("sensor %s register err code: %d\r\n", device_name, result);
os_free(device_name);
return result;
}
os_kprintf("sensor %s init success\r\n", device_name);
os_free(device_name);
return OS_SUCCESS;
}分步详解:
第 1 步:生成带类型前缀的设备名(条件 分配失败)
sensor_name = sensor_name_str[sensor->info.type];
device_name = (char *)os_calloc(1, strlen(sensor_name) + 1 + strlen(name));
if (device_name == OS_NULL)
{
os_kprintf("device_name calloc failed!\r\n");
return OS_FAILURE;
}
memcpy(device_name, sensor_name, strlen(sensor_name) + 1);
strcat(device_name, name);详细解释:关键理解:sensor_name_str[type] 是类型前缀(如 "acce_"/"temp_")。分配缓冲,把前缀和名字拼接,得到完整设备名。
数值演算(info.type = TEMP(4),name = "sht30"):
| 计算项 | 值 |
|---|---|
sensor_name | sensor_name_str[4] = "temp_" |
device_name | "temp_" + "sht30" = "temp_sht30" |
第 2 步:设置操作表与类型并注册(无条件执行)
device = &sensor->parent;
device->ops = &os_sensor_ops;
device->type = OS_DEVICE_TYPE_SENSOR;
device->user_data = data;
result = os_device_register(device, device_name);
if (result != OS_SUCCESS)
{
os_kprintf("sensor %s register err code: %d\r\n", device_name, result);
os_free(device_name);
return result;
}
os_kprintf("sensor %s init success\r\n", device_name);
os_free(device_name);
return OS_SUCCESS;
}详细解释:设置操作表 os_sensor_ops、类型 OS_DEVICE_TYPE_SENSOR、用户数据 data,用拼接名注册。注册失败则打印错误码、释放 device_name 并返回该错误码;成功则打印 init success、释放 device_name 并返回 OS_SUCCESS。注意两种分支都会 os_free(device_name),避免内存泄漏。
os_hw_sensor_register() 条件走向总结:
os_hw_sensor_register() 条件走向总结:
├── device_name 分配失败 → 打印 + 返回 OS_FAILURE
├── device_name = 类型前缀 + name
├── device->ops = &os_sensor_ops;type = SENSOR;user_data = data
├── os_device_register
│ ├── 失败 → 打印 + free + 返回错误码
│ └── 成功 → 打印 "init success" + free
└── 返回 OS_SUCCESS四、完整使用示例
示例:读取温湿度与加速度计(标准设备接口)
#include <sensor.h>
#include <device.h>
#include <os_stddef.h>
#include <os_util.h>
void sensor_temp_demo(void)
{
os_device_t *temp_sensor;
struct os_sensor_data data;
/* 1. 打开温湿度传感器(注意:注册时自动加类型前缀 temp_/humi_) */
temp_sensor = os_device_open("temp_sht30");
if (temp_sensor == OS_NULL)
{
os_kprintf("open temp_sht30 failed\r\n");
return;
}
/* 2. 轮询读取温度数据(os_sensor_data.data.temp,单位 dCelsius=0.1℃) */
os_size_t n = os_device_read(temp_sensor, 0, &data, sizeof(data));
if (n == sizeof(data))
{
os_kprintf("Temp: %.1f C\r\n", data.data.temp / 10.0f);
}
}
void sensor_accel_demo(void)
{
os_device_t *accel;
struct os_sensor_data data;
int range = 8000; /* 量程 ±8000 mG = ±8g */
os_err_t ret;
/* 1. 打开加速度计(类型前缀 acce_) */
accel = os_device_open("acce_mpu6050");
if (accel == OS_NULL)
{
os_kprintf("open acce_mpu6050 failed\r\n");
return;
}
/* 2. 设置量程为 ±8g(通过标准控制命令,单位 mG) */
ret = os_device_control(accel, OS_SENSOR_CTRL_SET_RANGE, (void *)range);
if (ret != OS_SUCCESS)
{
os_kprintf("set range failed\r\n");
return;
}
/* 3. 读取三轴加速度数据(data.data.acce,单位 mG) */
os_size_t n = os_device_read(accel, 0, &data, sizeof(data));
if (n == sizeof(data))
{
os_kprintf("Accel: X=%.2f Y=%.2f Z=%.2f g\r\n",
data.data.acce.x / 1000.0f,
data.data.acce.y / 1000.0f,
data.data.acce.z / 1000.0f);
}
}说明:示例展示传感器标准用法:
os_device_open(注意类型前缀)→os_device_control配置 →os_device_read读取。实际效果:温度数据在data.data.temp(dCelsius除以 10 得 ℃),加速度在data.data.acce.x/y/z(mG除以 1000 得 g)。
注意:
os_sensor_read/os_sensor_control是 static 函数,不能直接调用!必须通过os_device_read/os_device_control标准设备接口访问。设备名带类型前缀(temp_/humi_/acce_/gyro_等)。数据是整数缩放(dCelsius/mG/permillage),展示时要除以缩放因子。
五、传感器芯片驱动的适配(新增一颗传感器)
5.1 先认识驱动层:传感器芯片驱动怎么组织的
回顾 1.1 节,框架把差异封装在"驱动层"。驱动层就是 drivers/sensors/ 下一个个按芯片型号命名的文件,每个文件实现一颗传感器芯片的 os_sensor_ops:
| 芯片文件 | 传感器 | 类型 | 接口 |
|---|---|---|---|
drivers/sensors/adxl345.c | ADI 加速度计 | ACCE | I2C |
drivers/sensors/aht20.c | AHT20 温湿度 | TEMP/HUMI | I2C |
drivers/sensors/sht20.c | SHT20 温湿度 | TEMP/HUMI | I2C |
drivers/sensors/bmp180.c | BMP180 气压 | BARO | I2C |
drivers/sensors/bh1750.c | BH1750 光照 | LIGHT | I2C |
drivers/sensors/lsm6dso.c | ST 六轴 IMU | ACCE/GYRO | I2C/SPI |
drivers/sensors/qmi8658.c | QMI8658 六轴 | ACCE/GYRO | I2C/SPI |
关键认知:框架层(
sensor.c)是跨平台统一的,传感器芯片驱动则按芯片型号新增。新增一颗传感器 = 在drivers/sensors/写一个新文件,实现os_sensor_ops+ 注册,框架层与应用层都不用改。这与 5.8 RTC、5.9 Watchdog"换 MCU 换 HAL 厂商目录"不同——传感器驱动挂在具体总线上(I2C/SPI),不随 MCU 厂商变,随外接芯片变。
5.2 移植的全景图:新增芯片驱动需要做三件事
┌──────────────────────────────────────────────────────────┐
│ 应用层(Application) │
│ os_device_open("acce_adxl345") → read / control │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ Sensors 框架层(drivers/sensors/sensor.c) │
│ os_sensor_read/control/init/cb/register(不用改) │
└──────────────────────────────┬───────────────────────────┘
│ os_sensor_ops(fetch_data/control)
▼
┌──────────────────────────────────────────────────────────┐
│ 芯片驱动层(drivers/sensors/adxl345.c 等) ★需新增★ │
│ ①实现 os_sensor_ops ②填 info ③os_hw_sensor_register │
└──────────────────────────────┬───────────────────────────┘
│ os_i2c_client / os_spi_*
▼
┌──────────────────────────────────────────────────────────┐
│ 通信总线(I2C/SPI/单总线) ──▶ 传感器芯片硬件 │
└──────────────────────────────────────────────────────────┘新增一颗芯片驱动,本质是做三件事:
- 实现
os_sensor_ops:fetch_data(读芯片数据)+control(处理命令)。 - 填
info:类型/厂商/型号/单位/接口/量程/周期。 - 调
os_hw_sensor_register注册(框架自动加类型前缀)。
5.3 接口契约:os_sensor_ops 的两个函数
源码参考——ADXL345 的 os_sensor_ops(drivers/sensors/adxl345.c,与项目逐字一致):
static os_size_t adxl345_fetch_data(struct os_sensor_device *sensor, void *buf, os_size_t len)
{
adxl345_info_t *adxl345 = NULL;
struct os_sensor_data *data = NULL;
OS_ASSERT(sensor);
OS_ASSERT(sensor->info.type == OS_SENSOR_CLASS_ACCE);
OS_ASSERT(buf);
adxl345 = (adxl345_info_t *)sensor;
data = (struct os_sensor_data *)buf;
adxl345_get_value(adxl345);
data->type = sensor->info.type;
data->data.acce.x = adxl345->incidence_X;
data->data.acce.y = adxl345->incidence_Y;
data->data.acce.z = adxl345->incidence_Z;
data->timestamp = os_sensor_get_ts();
return len;
}static os_err_t adxl345_control(struct os_sensor_device *sensor, int cmd, void *args)
{
os_err_t result = OS_SUCCESS;
adxl345_info_t *adxl345 = (adxl345_info_t *)sensor;
switch (cmd)
{
case OS_SENSOR_CTRL_GET_ID:
*(uint8_t *)args = adxl345->id;
break;
default:
return OS_FAILURE;
}
return result;
}接口契约要点:
ops 函数 | 职责 | 关键点 |
|---|---|---|
fetch_data | 从芯片读数据,填一个 os_sensor_data | ①填 data->type(用 sensor->info.type);②按类型填 data.data 联合体成员;③填 data->timestamp(用 os_sensor_get_ts());④返回 len |
control | 处理 GET_ID/SET_*/SELF_TEST 等命令 | 用 switch(cmd) 分发,未知命令返回 OS_FAILURE;GET_ID 把芯片 ID 写回 *(uint8_t*)args |
关键理解 ——
fetch_data里的OS_ASSERT(sensor->info.type == OS_SENSOR_CLASS_ACCE):fetch_data只处理一种类型(如加速度),所以它断言info.type是自己对应的类型,防止被错误复用。
5.4 移植的标准流程(5 步,以新增 ADXL345 为例)
第 1 步:定义设备信息结构体——内嵌 os_sensor_device + 通信接口客户端(如 os_i2c_client)+ 私有数据:
typedef struct
{
struct os_sensor_device sensor; /* 内嵌传感器设备 */
struct os_i2c_client i2c; /* I2C 客户端 */
uint8_t id; /* 芯片 ID */
int32_t incidence_X;
int32_t incidence_Y;
int32_t incidence_Z;
} adxl345_info_t;第 2 步:实现 os_sensor_ops——按 5.3 契约实现 fetch_data/control,组装:
/* clang-format off */
static struct os_sensor_ops adxl345_ops =
{
adxl345_fetch_data,
adxl345_control
};
/* clang-format on */第 3 步:填 info 并挂 ops——注册前把传感器信息填好(类型/单位/量程等),并让 sensor.ops 指向操作表:
adxl345->sensor.info.type = OS_SENSOR_CLASS_ACCE;
adxl345->sensor.info.vendor = OS_SENSOR_VENDOR_ADI;
adxl345->sensor.info.model = "adxl345";
adxl345->sensor.info.unit = OS_SENSOR_UNIT_MG;
adxl345->sensor.info.intf_type = OS_SENSOR_INTF_I2C;
adxl345->sensor.info.range_max = 16000;
adxl345->sensor.info.range_min = -16000;
adxl345->sensor.info.period_min = 5;
adxl345->sensor.ops = &adxl345_ops;关键理解 ——
unit决定单位、range决定量程、type决定前缀:info.unit = OS_SENSOR_UNIT_MG告诉应用"读到的acce是mG单位";info.type = OS_SENSOR_CLASS_ACCE让os_hw_sensor_register自动加acce_前缀。
第 4 步:注册——调 os_hw_sensor_register(&sensor, "adxl345", OS_NULL),成功后应用用 "acce_adxl345" 打开:
result = os_hw_sensor_register(&adxl345->sensor, "adxl345", OS_NULL);
if (result != OS_SUCCESS)
{
os_kprintf("device register err code: %d\r\n", result);
goto __exit;
}第 5 步:OS_INIT_CALL 自动初始化——用 OS_INIT_CALL 让驱动在设备初始化阶段自动探测并注册芯片:
OS_INIT_CALL(os_hw_adxl345_init, OS_INIT_LEVEL_DEVICE, OS_INIT_SUBLEVEL_LOW);适配 vs 换 MCU 的区别:新增芯片驱动挂在总线(I2C/SPI)上,通常一个 MCU 工程里会同时挂多颗传感器(如 IMU + 温湿度 + 气压),所以是增量新增文件而非替换;而换 MCU 只需确认
os_i2c_client/os_spi_*底层驱动(5.3/5.4 节)已就绪,传感器芯片驱动本身无需改动。
5.5 移植检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
fetch_data 填了 type/data.data/timestamp? | 缺 type 应用无法判断数据种类,缺 timestamp 无法获知采样时刻 |
info.unit 填对? | 决定应用怎么换算(mG÷1000、dCelsius÷10) |
info.type 填对? | 决定类型前缀(acce_/temp_/baro_…)与联合体成员 |
ops 已赋值? | os_hw_sensor_register 会用 device->ops = &os_sensor_ops 覆盖 parent.ops,但 sensor.ops(私有)必须自己挂 |
是否 OS_INIT_CALL 初始化? | 不初始化则芯片不被探测、设备不注册 |
| 三轴换算是否保留整数缩放? | fetch_data 里返回的应是原始整数缩放值(如 mG),由应用换算,不要在驱动里除以因子变 float |
📝 本节小结
核心要点回顾
本节围绕 Sensors 传感器框架展开,核心要点如下:
1. 统一抽象
- 框架把不同类型/接口/单位的传感器统一为
os_sensor_device。 - 应用通过标准设备接口(
os_device_read/os_device_control)访问。
2. 核心数据结构
os_sensor_device:info(类型/单位/量程)+config(接口/模式/功耗)+ops。os_sensor_data:timestamp + type + union data,联合体一次存一种数据,整数缩放单位。
3. 设备命名规则
os_hw_sensor_register自动加类型前缀:"mpu6050"→"acce_mpu6050"。
4. 核心函数
| 函数 | 作用 |
|---|---|
os_sensor_read | 优先读缓冲,否则 fetch_data(static,经设备接口调用) |
os_sensor_control | 命令分发 + 同步 config |
os_sensor_init | 打开时设轮询模式 + 正常功耗 |
os_sensor_cb | 数据就绪时更新 rx_count 并通知 |
os_hw_sensor_register | 加前缀命名并注册 |
5. 整数缩放单位
- 温度
dCelsius(÷10 得 ℃)、湿度permillage(÷10 得 %)、加速度mG(÷1000 得 g)。
6. 结构体与机制
os_sensor_device:parent(os_device)首成员继承,保证dev与sensor指针数值相等。data_buf/data_len(字节数):中断/FIFO 模式驱动填缓冲,os_sensor_read优先读缓冲,用data_len / sizeof(os_sensor_data)换算条数。
7. 新增芯片驱动的适配要点
- 新增一颗传感器 = 在
drivers/sensors/写新文件:实现os_sensor_ops(fetch_data/control)+ 填info+os_hw_sensor_register注册。 info.type决定类型前缀,info.unit决定应用换算,fetch_data要填type/data.data/timestamp。- 框架层与应用层不用改,驱动挂总线(I2C/SPI)上,可同时挂多颗。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
直接调用 os_sensor_read/os_sensor_control | 它们是 static,必须用 os_device_read/os_device_control 标准接口 |
| 忘记设备名带类型前缀 | 注册时自动加 acce_/temp_ 等前缀,打开用 "temp_sht30" 而非 "sht30" |
| 把数据当 float | 传感器数据是 int32_t 整数缩放(dCelsius/mG/permillage),需除以缩放因子 |
忽略 type 字段 | os_sensor_data.data 是联合体,需按 type 访问对应成员,否则读到错误数据 |
| 混淆三轴字段名 | 三轴在 data.data.acce/gyro/mag,不是 accel/data.accel.x |
| 在中断回调里做耗时操作 | os_sensor_cb/irq_handle 在中断上下文执行,只做标志/搬运 |
混淆 data_len 单位 | data_len 是字节数,os_sensor_read 里用 data_len / sizeof(os_sensor_data) 换算条数 |
fetch_data 忘填 timestamp/type | 应用靠 type 判断数据种类、靠 timestamp 获知采样时刻,漏填会导致数据无法使用 |
| 在驱动里把缩放值转 float | 驱动应保留整数缩放值(如 mG),换算成物理量的工作交给应用 |
下一步
掌握了 Sensors 传感器框架后,接下来学习 ADC 模数转换设备:
- 5.11 ADC设备:了解 OneOS ADC 设备的多通道读取与电压换算。