RT-Thread学习—Sensor框架
1.对接底层驱动
Sensor 驱动框架的作用是:为上层提供统一的操作接口,提高上层代码的可重用性;简化底层驱动开发的难度,只要实现简单的 ops(operations: 操作命令) 就可以将传感器注册到系统上。
如图所示,sensor为上层应用提供统一的标准接口open/close/read/control,sensor框架底层提供ops接口;featch_data/control,实际上使用rt-thread的sensor框架,只需要对接这个接口就行,然后注册设备,应用层就可以访问了。ops接口的定义在sensor.h中,如下所示:
struct rt_sensor_ops
{
rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len);
rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg);
};
对接设备实际上就是实现fetch_data和control两个函数,以ds18b20做实,ds18b20不支持control,所以只需要实现数据获取函数就行。使用RT-Thread添加ds18b20组件包后,在package工程文件夹下的ds18b20-latest文件夹下的src中的文件中实现了fetch_data。
static rt_size_t ds18b20_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len)
{
RT_ASSERT(buf);
if (sensor->config.mode == RT_SENSOR_MODE_POLLING)
{
return _ds18b20_polling_get_data(sensor, buf);
}
else
return 0;
}
其中sensor工作模式类型宏定义在sensor.h中,各个区别见宏定义注释:
/* Sensor work mode types */
#define RT_SENSOR_MODE_NONE (0)
#define RT_SENSOR_MODE_POLLING (1) /* One shot only read a data */
#define RT_SENSOR_MODE_INT (2) /* TODO: One shot interrupt only read a data */
#define RT_SENSOR_MODE_FIFO (3) /* TODO: One shot interrupt read all fifo data */
Sensor框架支持三种打开方式轮询、中断、FIFO,注册设备时,需要选择参数,三种方式宏定义在rtdef.h头文件中,如下:
#define RT_DEVICE_FLAG_RDONLY 0x001 /**< read only */
#define RT_DEVICE_FLAG_WRONLY 0x002 /**< write only */
#define RT_DEVICE_FLAG_RDWR 0x003 /**< read and write */
回到刚刚,_ds18b20_polling_get_data()函数实际上就是ds18b20底层驱动,用来获取温度。
static rt_size_t _ds18b20_polling_get_data(rt_sensor_t sensor, struct rt_sensor_data *data)
{
rt_int32_t temperature_x10;
if (sensor->info.type == RT_SENSOR_CLASS_TEMP)
{
temperature_x10 = ds18b20_get_temperature((rt_base_t)sensor->config.intf.user_data);
data->data.temp = temperature_x10;
data->timestamp = rt_sensor_get_ts();
}
return 1;
}
其中传感器的信息类型定义在sensor.h中,我们只用到温度:
/* Sensor types */
#define RT_SENSOR_CLASS_NONE (0)
#define RT_SENSOR_CLASS_ACCE (1) /* Accelerometer */
#define RT_SENSOR_CLASS_GYRO (2) /* Gyroscope */
#define RT_SENSOR_CLASS_MAG (3) /* Magnetometer */
#define RT_SENSOR_CLASS_TEMP (4) /* Temperature */
#define RT_SENSOR_CLASS_HUMI (5) /* Relative Humidity */
#define RT_SENSOR_CLASS_BARO (6) /* Barometer */
#define RT_SENSOR_CLASS_LIGHT (7) /* Ambient light */
#define RT_SENSOR_CLASS_PROXIMITY (8) /* Proximity */
#define RT_SENSOR_CLASS_HR (9) /* Heart Rate */
#define RT_SENSOR_CLASS_TVOC (10) /* TVOC Level */
#define RT_SENSOR_CLASS_NOISE (11) /* Noise Loudness */
#define RT_SENSOR_CLASS_STEP (12) /* Step sensor */
#define RT_SENSOR_CLASS_FORCE (13) /* Force sensor */
继续往下探,点开ds18b20_get_temperature()函数,就很清晰了:
int32_t ds18b20_get_temperature(rt_base_t pin)
{
uint8_t TL, TH;
int32_t tem;
ds18b20_start(pin);
ds18b20_init(pin);
ds18b20_write_byte(pin, 0xcc);
ds18b20_write_byte(pin, 0xbe);
TL = ds18b20_read_byte(pin); /* LSB first */
TH = ds18b20_read_byte(pin);
if (TH > 7)
{
TH =~ TH;
TL =~ TL;
tem = TH;
tem <<= 8;
tem += TL;
tem = (int32_t)(tem * 0.0625 * 10 + 0.5);
return -tem;
}
else
{
tem = TH;
tem <<= 8;
tem += TL;
tem = (int32_t)(tem * 0.0625 * 10 + 0.5);
return tem;
}
}
这个就跟我们裸机编程ds18b20一样了,就是初始化存在检测、但设备跳过ROM等步骤,参考DS18B20的数据手册,按照时序图操作就行。其中稍有不同的是延时函数,因为DS18B20对延时很敏感,涉及到微妙级别的延时,而rt-thread延时一般只到毫秒级别,RT_TICK_PER_SECOND 1000,那么最小单位为1ms。
#define RT_TICK_PER_SECOND 1000
所以要利用滴答定时器达到微妙延时,然而有操作系统时,滴答定时器提供系统的时基,不能再像裸机那样随意改写,所以采取“时钟摘取法”,不改写滴答定时器,而获得定时,大概方法为:定时开始时获取当时的值,然后实时获取SysTick->value的值,两者做差,差值即为已经延时的滴答数,与目标延时数对比判断即可完成定时。
最后实现一个ops结构体存储以上两个函数(其中ds18b20不需要控制,就没说control函数实现)。
static struct rt_sensor_ops sensor_ops =
{
ds18b20_fetch_data,
ds18b20_control
};
2.传感器设备注册
完成Sensor的ops对接后,还需要注册Sensor设备,注册完后上层应用就可以找到设备从而使用上层接口使用设备。
注册3步骤:
a.创建一个 rt_sensor_t 的结构体指针 (赋值后传给注册函数)
b.为结构体分配内存
c.完成相关初始化。示例程序如下:
int rt_hw_ds18b20_init(const char *name, struct rt_sensor_config *cfg)
{
rt_int8_t result;
rt_sensor_t sensor_temp = RT_NULL;
if (!ds18b20_init((rt_base_t)cfg->intf.user_data))
{
/* temperature sensor register */
sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
if (sensor_temp == RT_NULL)
return -1;
sensor_temp->info.type = RT_SENSOR_CLASS_TEMP;
sensor_temp->info.vendor = RT_SENSOR_VENDOR_DALLAS;
sensor_temp->info.model = "ds18b20";
sensor_temp->info.unit = RT_SENSOR_UNIT_DCELSIUS;
sensor_temp->info.intf_type = RT_SENSOR_INTF_ONEWIRE;
sensor_temp->info.range_max = SENSOR_TEMP_RANGE_MAX;
sensor_temp->info.range_min = SENSOR_TEMP_RANGE_MIN;
sensor_temp->info.period_min = 5;
rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config));
sensor_temp->ops = &sensor_ops;
result = rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
if (result != RT_EOK)
{
LOG_E("device register err code: %d", result);
goto __exit;
}
}
else
{
LOG_E("DS18B20 init failed! Please check the connection!");
goto __exit;
}
return RT_EOK;
__exit:
if (sensor_temp)
rt_free(sensor_temp);
return -RT_ERROR;
}
注册完毕后,下载到板子,使用list_device,即可看到注册的设备:
可看到注册的设备temp_ds1,那么注册API的传参name是rt_hw_ds18b20_init()函数传入,在示例中,rt_hw_ds18b20_init()由下main函数调用:
static int rt_hw_ds18b20_port(void)
{
struct rt_sensor_config cfg;
cfg.intf.user_data = (void *)DS18B20_DATA_PIN;
rt_hw_ds18b20_init("ds18b20", &cfg);
return RT_EOK;
}
此处可见设备名传入的为“ds18b20”,为何控制台显示的却是 “temp_ds1”呢?
原因是因为rt_hw_sensor_register()会为传入的 name 自动添加前缀,比如加速度前缀acce_和此处我们使用的温度传感器,则加入temp_前缀,后面继续跟上传参传入的名字,同时因为系统会截取最大名字长度,超过最大名字长度的部分会被截掉,最大名字定义rtconfig.h中:
#define RT_NAME_MAX 8
我这里设置的为8,所以名字就变成了“temp_ds1”一共8个字符。