OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 6889|回复: 11

RT-Thread智能家居DIY教程连载(1)——如何正确使用Sensor框架

[复制链接]

2

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
154
金钱
154
注册时间
2018-11-27
在线时间
30 小时
发表于 2019-8-1 11:34:48 | 显示全部楼层 |阅读模式
本帖最后由 WillianChan 于 2019-8-14 18:17 编辑

智能家居DIY教程连载(1)
如何正确使用 Sensor 框架
Hi~各位小伙伴们,距离 DIY 项目的发布已经有一周的时间了,第一周的任务已经完成了吗?会不会遇到了问题无从下手?没关系,这篇文章很好的解决了大家的困惑,一起来看看吧。
1. 第一周任务回顾
我们来回顾一下第一周安排的任务:
  • 正确读取 ds18b20 温度数据
  • 了解 RT-Thread 的 Sensor 框架,并将 ds18b20 对接到 Sensor 框架上
  • 了解线程的使用,创建一个线程,在线程中读取温度数据,并通过FinSH控制台实时打印出来

上述任务的重点,其实不在于把温度读取到就好了的,重点在于如何将 ds18b20 对接到 RT-Thread 的 Sensor 框架上去。
Sensor 是物联网重要的一部分,“Sensor 之于物联网”相当于“眼睛之于人类”。人没有眼睛就看不到这大千的花花世界,物联网没有了 Sensor 更是不能感知这变化万千的世界。
现在,为物联网开发的 Sensor 已经很多了,不同的传感器厂商、不同的传感器都需要配套自己独有的驱动才能运转起来,这样在开发应用程序的时候就需要针对不同的传感器做适配,自然加大了开发难度。为了降低应用开发的难度,增加传感器驱动的可复用性,我们设计了 Sensor 驱动框架。
Sensor 驱动框架的作用是:为上层提供统一的操作接口,提高上层代码的可重用性;简化底层驱动开发的难度,只要实现简单的 ops(operations: 操作命令) 就可以将传感器注册到系统上。
本次 DIY 活动,以 ds18b20 温度传感器为例子,教大家如何正确使用 Sensor 框架。
2. Sensor 框架介绍
sensor整体框架图.png
Sensor 框架的整体框架图如上。它为上层提供的是标准 device 接口 open/close/read/write/control,这些接口与上层用户程序对接,为底层驱动提供的是简单的 ops(operations: 操作命令)接口:fetch_data/control,这两个接口对接具体硬件的底层驱动。除此之外,Sensor 框架还支持 module(模块),为底层存在耦合的传感器设备提供服务,如果,ds18b20 的底层不存在耦合,此处不需要用到 module。
Sensor 框架更多的介绍在 RT-Thread 的文档中心已有详细说明,这里不过多赘述,详情请查看
3. Sensor 框架的使用
看完文档中心的 Sensor 介绍后,相信大伙儿已经对这个框架有了一定的了解。有的小伙伴是不是早就按耐不住想要跃跃欲试将传感器对接到 Sensor 框架上?这里以 ds18b20 温度传感器为例子。
Sensor 框架的使用分两个步骤:ops 接口对接、传感器设备注册。
3.1 ops 接口对接
我们知道,Sensor 框架的接口分为上层接口和底层接口两种。将 ds18b20 的底层驱动对接到框架上,其实对接就是 Sensor 框架的底层接口,具体的,是底层的 ops 接口。
我们在 RT-Thread 源码中可以找到 Sensor 框架的源码,源码路径为:rt-thread\components\drivers\sensors,在 sensor.h 文件中,我们可以找到对 ops 接口的定义,有两个函数指针,fetch_data 和 contorl。
  1. struct rt_sensor_ops
  2. {
  3.     rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len);
  4.     rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg);
  5. };
复制代码
fetch_data 作用是获取传感器数据,control 作用是通过控制命令控制传感器,ds18b20 并不支持 control,我们只需要实现 fetch_data 就好了。
Sensor 框架当前默认支持轮询(RT_DEVICE_FLAG_RDONLY)、中断(RT_DEVICE_FLAG_INT_RX)、FIFO(RT_DEVICE_FLAG_FIFO_RX) 这三种打开方式。需要在这里判断传感器的工作模式,然后再根据不同的模式返回传感器数据。我们以轮询的方式读取 ds18b20 的温度数据,那么 fetch_data 的实现如下:
  1. static rt_size_t ds18b20_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len)
  2. {
  3.     RT_ASSERT(buf);
  4. ​
  5.     if (sensor->config.mode == RT_SENSOR_MODE_POLLING)
  6.     {
  7.         return _ds18b20_polling_get_data(sensor, buf);
  8.     }
  9.     else
  10.         return 0;
  11. }
复制代码
具体的,_ds18b20_polling_get_data(sensor, buf) 的实现如下,其中,ds18b20_get_temperature 函数就是 ds18b20 温度传感器底层驱动的获取温度的函数。
  1. static rt_size_t _ds18b20_polling_get_data(rt_sensor_t sensor, struct rt_sensor_data *data)
  2. {
  3.     rt_int32_t temperature_x10;
  4.     if (sensor->info.type == RT_SENSOR_CLASS_TEMP)
  5.     {
  6.         temperature_x10 = ds18b20_get_temperature((rt_base_t)sensor->config.intf.user_data);
  7.         data->data.temp = temperature_x10;
  8.         data->timestamp = rt_sensor_get_ts();
  9.     }   
  10.     return 1;
  11. }
复制代码
因为不需要 control,我们直接让 control 返回 RT_EOK 即可
  1. static rt_err_t ds18b20_control(struct rt_sensor_device *sensor, int cmd, void *args)
  2. {
  3.     rt_err_t result = RT_EOK;
  4. ​
  5.     return result;
  6. }
复制代码
这样,我们的 ops 函数就写好了。然后,需要实现一个设备接口的结构体 ops 存储上面的接口函数:
  1. static struct rt_sensor_ops sensor_ops =
  2. {
  3.     ds18b20_fetch_data,
  4.     ds18b20_control
  5. };
复制代码
这样一来, ops 接口就对接成功了。
3.2 传感器设备注册
完成 Sensor 的 ops 的对接之后还要注册一个 Sensor 设备,这样上层才能找到这个传感器设备,进而进行控制。
设备的注册一共需要下面几步:
  • 创建一个 rt_sensor_t 的结构体指针
  • 为结构体分配内存
  • 完成相关初始化

具体的,放到 ds18b20 上面来,具体实现如下:
  1. int rt_hw_ds18b20_init(const char *name, struct rt_sensor_config *cfg)
  2. {
  3.     rt_int8_t result;
  4.     rt_sensor_t sensor_temp = RT_NULL;
  5.    
  6.     if (!ds18b20_init((rt_base_t)cfg->intf.user_data))
  7.     {
  8.         /* temperature sensor register */
  9.         sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
  10.         if (sensor_temp == RT_NULL)
  11.             return -1;
  12. ​
  13.         sensor_temp->info.type       = RT_SENSOR_CLASS_TEMP;
  14.         sensor_temp->info.vendor     = RT_SENSOR_VENDOR_DALLAS;
  15.         sensor_temp->info.model      = "ds18b20";
  16.         sensor_temp->info.unit       = RT_SENSOR_UNIT_DCELSIUS;
  17.         sensor_temp->info.intf_type  = RT_SENSOR_INTF_ONEWIRE;
  18.         sensor_temp->info.range_max  = SENSOR_TEMP_RANGE_MAX;
  19.         sensor_temp->info.range_min  = SENSOR_TEMP_RANGE_MIN;
  20.         sensor_temp->info.period_min = 5;
  21. ​
  22.         rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config));
  23.         sensor_temp->ops = &sensor_ops;
  24. ​
  25.         result = rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
  26.         if (result != RT_EOK)
  27.         {
  28.             LOG_E("device register err code: %d", result);
  29.             goto __exit;
  30.         }
  31. ​
  32.     }
  33.     return RT_EOK;
  34.    
  35. __exit:
  36.     if (sensor_temp)
  37.         rt_free(sensor_temp);
  38.     return -RT_ERROR;     
  39. }
复制代码

我们来解读一下。
传感器设备注册的第一步:创建一个 rt_sensor_t 的结构体指针,上述代码中是这么实现的:
  1. rt_sensor_t sensor_temp = RT_NULL;
复制代码
传感器设备注册的第二步:为结构体分配内存,上述代码中是这么实现的:
  1. sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
  2. if (sensor_temp == RT_NULL)
  3.     return -1;
复制代码
传感器设备注册的第三步:完成相关初始化,上述代码中是这么实现的:
  1. sensor_temp->info.type       = RT_SENSOR_CLASS_TEMP;
  2. sensor_temp->info.vendor     = RT_SENSOR_VENDOR_DALLAS;
  3. sensor_temp->info.model      = "ds18b20";
  4. sensor_temp->info.unit       = RT_SENSOR_UNIT_DCELSIUS;
  5. sensor_temp->info.intf_type  = RT_SENSOR_INTF_ONEWIRE;
  6. sensor_temp->info.range_max  = SENSOR_TEMP_RANGE_MAX;
  7. sensor_temp->info.range_min  = SENSOR_TEMP_RANGE_MIN;
  8. sensor_temp->info.period_min = 5;
  9. ​
  10. rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config));
  11. sensor_temp->ops = &sensor_ops;
复制代码
传感器设备注册的三个步骤完成之后,就可以放心大胆地注册传感器设备了,上述代码中是这么实现的:
  1. rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
复制代码
上述的“ops 接口对接”和“传感器设备注册”两个工作完成后,就可以通过 Sensor 框架中的上层接口 open/close/read/write/control,对 ds18b20 进行操作了。
先不着急,我们在 FinSH 中输入 list_device 命令查看 ds18b20 温度传感器是否真的已经被注册上去了:
list_device.jpg
哇!成功将 ds18b20 注册成传感器设备了,可喜可贺!!!
传感器驱动对接 Sensor 框架的操作中的更多细节,请在 RT-Thread 的文档中心中查看
4. 在线程中读取温度数据
我们通过一个线程,去实时获取 ds18b20 的温度数据。
线程的基本操作有:创建/初始化( rt_thread_create/rt_thread_init)、启动(rt_thread_startup)、运行(rt_thread_delay/rt_thread_control)、删除/脱离(rt_thread_delete/rt_thread_detach)。
之前我们已经将 ds18b20 对接到 ops 接口并成功注册成传感器设备了,接下来就可以利用 Sensor 框架的上层接口 open/close/read/write/control 对 ds18b20 进行操作了。
在 main 函数中创建一个读取 ds18b20 温度数据的线程并启动它,线程入口函数是 read_temp_entry:
  1. rt_thread_t ds18b20_thread, led_thread;
  2.    
  3. ds18b20_thread = rt_thread_create("18b20tem",
  4.                      read_temp_entry,
  5.                      "temp_ds18b20",
  6.                      512,
  7.                      RT_THREAD_PRIORITY_MAX / 2,
  8.                      20);
  9. if (ds18b20_thread != RT_NULL)
  10. {
  11.     rt_thread_startup(ds18b20_thread);
  12. }
复制代码
在线程入口函数 read_temp_entry 中,我们通过几个步骤,就可以读取 ds18b20 的温度数据了:
  • 创建一个 rt_sensor_data 的数据结构体
  • 查找传感器设备驱动
  • 打开对应的传感器设备
  • 读取传感器设备数据

上述步骤具体实现如下:
  1. static void read_temp_entry(void *parameter)
  2. {
  3.     rt_device_t dev = RT_NULL;
  4.     struct rt_sensor_data sensor_data;
  5.     rt_size_t res;
  6. ​
  7.     dev = rt_device_find(parameter);
  8.     if (dev == RT_NULL)
  9.     {
  10.         rt_kprintf("Can't find device:%s\n", parameter);
  11.         return;
  12.     }
  13. ​
  14.     if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK)
  15.     {
  16.         rt_kprintf("open device failed!\n");
  17.         return;
  18.     }
  19.     rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)100);
  20. ​
  21.     while (1)
  22.     {
  23.         res = rt_device_read(dev, 0, &sensor_data, 1);
  24.         if (res != 1)
  25.         {
  26.             rt_kprintf("read data failed!size is %d\n", res);
  27.             rt_device_close(dev);
  28.             return;
  29.         }
  30.         else
  31.         {
  32.             rt_kprintf("temp:%3d.%dC, timestamp:%5d\n", sensor_data.data.temp / 10, sensor_data.data.temp % 10, sensor_data.timestamp);
  33.         }
  34.         rt_thread_mdelay(100);
  35.     }
  36. }
复制代码
通过 FinSH 控制台,查看该线程源源不断输出的数据:
result.jpg
温度数据在线程中被正确读取出来了,到此为止,本周任务就算是成功完成了的,因吹斯听~(^_^)
5. 开源代码
为了更进一步便于大家学习,第一周任务的代码已经开源啦~ 请点击这里查看
6. 注意事项
  • 欲使用 Sensor 框架,当然是要在 menuconfig 中将它开启啦:

  1. <font face="宋体">RT-Thread Components  --->
  2.     Device Drivers  --->
  3.        </font><font face="宋体"> Using Sensor device drivers</font>
复制代码
  • 打开之后,需要使用 scons --target=mdk5 更新工程即可。看,Sensor 框架加入到工程当中了:

   sensor_in_project.jpg
  • stm32f407-atk-explorer 文件夹里面的工程就是第一周的任务
  • 是基于正点原子的探索者开发板的 BSP 制作的,手头有这款开发板的可以直接打开工程编译下载即可。使用其他开发板的,如果已经有 BSP 支持的,可以直接使用对应的 BSP 来做,并将 stm32f407-atk-explorer\board\ports 文件夹中的 sensor_dallas_ds18b20.c 文件引入到自己的工程当中即可。其余开发板,可以参考该 Demo 程序,照葫芦画瓢。


正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

2

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
154
金钱
154
注册时间
2018-11-27
在线时间
30 小时
 楼主| 发表于 2019-8-16 16:37:31 | 显示全部楼层
回复 支持 反对

使用道具 举报

17

主题

465

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
2140
金钱
2140
注册时间
2013-1-11
在线时间
504 小时
发表于 2019-8-20 11:42:10 | 显示全部楼层
顶一个
回复 支持 反对

使用道具 举报

2

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
154
金钱
154
注册时间
2018-11-27
在线时间
30 小时
 楼主| 发表于 2019-8-20 18:30:41 | 显示全部楼层

谢谢支持
回复 支持 反对

使用道具 举报

0

主题

5

帖子

0

精华

初级会员

Rank: 2

积分
86
金钱
86
注册时间
2018-12-8
在线时间
16 小时
发表于 2019-8-20 22:29:28 | 显示全部楼层
写的很棒
回复 支持 反对

使用道具 举报

2

主题

16

帖子

0

精华

初级会员

Rank: 2

积分
154
金钱
154
注册时间
2018-11-27
在线时间
30 小时
 楼主| 发表于 2019-8-21 14:17:38 | 显示全部楼层

如有漏洞,欢迎指出
回复 支持 反对

使用道具 举报

5

主题

63

帖子

0

精华

新手入门

积分
12
金钱
12
注册时间
2018-6-15
在线时间
28 小时
发表于 2019-8-31 14:01:56 | 显示全部楼层
写的很棒
回复 支持 反对

使用道具 举报

0

主题

2

帖子

0

精华

初级会员

Rank: 2

积分
75
金钱
75
注册时间
2019-8-9
在线时间
22 小时
发表于 2019-11-10 16:48:31 | 显示全部楼层
老哥,稳
回复 支持 反对

使用道具 举报

3

主题

39

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
308
金钱
308
注册时间
2013-4-5
在线时间
81 小时
发表于 2019-12-4 08:05:50 | 显示全部楼层
很详细啊,入门最爱
回复 支持 反对

使用道具 举报

26

主题

1523

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6308
金钱
6308
注册时间
2015-8-25
在线时间
999 小时
发表于 2019-12-4 13:02:31 | 显示全部楼层
谢谢分享            
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
12
金钱
12
注册时间
2019-9-6
在线时间
2 小时
发表于 2019-12-28 20:00:35 | 显示全部楼层
写的很好,非常棒
回复 支持 反对

使用道具 举报

28

主题

110

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
494
金钱
494
注册时间
2017-10-14
在线时间
76 小时
发表于 2020-1-10 10:40:28 | 显示全部楼层
用menuconfig生成的代码文件不能自动添加到工程目录下,你有没有遇到过这种情况??
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-1 08:02

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表