通过OneWire驱动库获取DS18B20/DHT¶
作者:陈之敏 时间:2020年08月15日 关键字:csdk、RDA8910、二次开发、单总线、DS18B20、DHT11 ## 目录
最近听说有的小伙伴看了我的教程后,有一些问题都跑到官方的gitee上面去问去了。导致官方的人没搞懂问的是啥,小伙伴们也没能知道自己想要的答案。给大家造成了困扰,这里我说声抱歉。
既然出现了这个问题,我这里就声明一下,本系列教程所涉及的内容(demo)不是官方的作品。我个人觉得官方的demo内容太多太全,往往都是把一个模块内所有的东西全部放在一起。这样的话对新手不是很友好,阅读起来也比较费劲。我就把官方的部分demo进行相关的简化,并推出教程这样的话可能会对新手朋友们有一定的帮助。
有时候周末闲暇时间我也会加上一些我觉得好玩的模块在里面,这些可能在官方的demo都没有,比如cJSON、PAHO-MQTT、http-client。
这就是官方的代码仓库。
git clone --recursive https://gitee.com/openLuat/Luat_CSDK_Air724U.git
当然各位小伙伴在看本教程时,我建议还是使用我下面提供的仓库比较好,看完之后在迁移到官方的仓库⇧。
git clone --recursive https://gitee.com/chenxiahuaxu/RDA8910_CSDK.git
再看本教程过程中如果遇到了问题,可以在本人的代码仓库下面评论。也可以在本人的博客下面评论。我要是看到的话,并且这个问题在我的能力范围的话我会尽力解答的(非官方,不要对我要求太多哦,要求太多我可能就不管啦)。
一、前言¶
大概在一个月之前吧,群里就有小伙伴问:“咱们的CAT1能不能读取到单总线数据啊?比如说DS18B20这些传感器”。当时我就说这些传感器对时序的要求比较高,都是us级别的。驱动放在lua层还真不一定行,csdk的问题应该不大。
又问:“那小哥哥什么时候安排一波呢?”
答:“别别别,我可是钢铁直男,对男的没兴趣。要不这样吧,我周末看看吧,有时间的话搞一下” 然而这一个周末看看,就鸽了将近一个月。 想了想就这么一直鸽下去也不是个事情。于是乎最近几天抽时间看了下,虽然中间也遇到了一点波折。但是这都不影响结果,好在是搞出来了。既然搞出来了那好东西就要和大家分享。
二、单总线时序¶
虽然DS18B20和DHT11都叫做单总线通讯,但是他们的时序要求却不太一样,包括读时序的位置也不一样。不过好在它们基本的交互逻辑都是差不多的,这里就拿ds18b20的驱动时序随便讲一下。 ## 2.1、初始化时序 在初始化序列期间,总线控制器拉低总线并保持 480us 以发出(TX)一个复位脉冲,然后释放总线,进入接收状态(RX)。单总线由 5K 上拉电阻拉到高电平。当DS18B20 探测到 I/O 引脚上的上升沿后,等待 15-60us,然后发出一个由 60-240us低电平信号构成的存在脉冲。
2.2、写时序¶
ds18b20的写时序又分为写0时序和写1时序,总线控制器通过写 1 时序写逻辑 1 到DS18B20,写 0 时序写逻辑 0 到 DS18B20。所有写时序必须最少持续 60us,包括两个写周期之间至少1us 的恢复时间。当总线控制器(单片机)把数据线从逻辑高电平拉到低电平的时候,写时序开始。
总控制器要生成一个写 0 时序,必须把数据线拉到低电平并持续保持(至少 60us)。然后释放。
总控制器要生成一个写 1时序,必须把数据线拉到低电平,然后在15us内释放总线,并持续保持(至少 60us)。然后释放。
## 2.3、读时序 ds18b20的读时序又分为读0时序和读1时序,总线控制器在发出读暂存器指令[BEh]或读电源模式指令[B4H]后必须立刻开始读时序。所有读时序必须最少 60us,包括两个读周期间至少 1us 的恢复时间。当总线控制器把数据线从高电平拉到低电平时(数据线必须至少保持 1us),读时序开始。
当传输逻辑 0 结束后,总线将被释放,通过上拉电阻回到上升沿状态。从 DS18B20 输出的数据在读时序的下降沿出现后 15us 内有效。因此,总线控制器在读时序开始后必须停止把 I/O 脚驱动为低电平 15us,以读取I/O 脚状态。
三、OneWire驱动库¶
学习完ds18b20的读时序后,介绍下本文开头提到的OneWire库。
在编写DS18B20和DHT11驱动后,发现类似这种的单总线传感器。都有一个共同的特点,想要完成它们的读取过程,那就需要有us级别的延时,需要能够快速切换gpio的输入/输出状态以及读取/设置gpio的状态。单个操作都必须在极短的时间内完成。整个读取过程中除了需要操作gpio就不依赖任何其他的库函数。
3.1、基础库¶
我将这些共同点整理了下,提供了OneWire库,其中包含基础库函数6个,分别是:
/** @description: 输出调试信息 */
OneWire_printf
(fmt,…);
/* *@description: 设置单总线信号线为输入。默认上拉@param :pin{uint8}:要操作的引脚 @return: TRUE:正确 FALSE:引脚不在允许范围/
OneWire_IO_IN
(uint8 pin);
/* *@description: 设置单总线信号线为输出。默认输出高定平@param :pin{uint8}:要操作的引脚 @return: 无/
OneWire_IO_OUT
(uint8 pin);
/* *@description: 单总线输出高低电平@param : pin{uint8}:要操作的引脚 level{uint8}}:要输出的电平。0或者1 @return: 无/
OneWire_DQ_OUT
(uint8 pin, uint8 level);
/* *@description: 读取单总线的高低电平信号@param : pin{uint8}:要操作的引脚 @return: 0或者1/
OneWire_DQ_IN
(uint8 pin);
/* *@description: 单总线1us延时函数@param : us{uint32}:调用的次数。 @return: 无/
OneWire_Delay_1us
(volatile uint32 us);
3.2、扩展库¶
然后又在RDA8910平台上,基于OneWire库开发了DS18B20的驱动以及DHT11的驱动。这两个驱动完全依赖于OneWire库函数,不依赖其他文件,整个驱动基于platform提供的6个函数制作。 移植在不同的平台只需要修改这上图所示的六个函数,即可直接调用扩展库中提供的ds18b20和dht11的驱动。
3.2.1、DS18B20¶
其中DS18B20提供了两个读取函数,分别是:
/* *@description: 从ds18b20得到温度值数字,精确到0.0625。结果被扩大10000倍@param : pin{uint8}:要操作的引脚 TempNum{int }:输出温度值@return 0:正常获取 2:传入的pin不在允许范围 3:未检测到ds18b20 */
uint8
DS18B20_GetTemp_Num
(uint8 pin, int *TempNum);
/* *@description: 从ds18b20得到温度值字符串@param : pin{uint8}:要操作的引脚 TempNum{char }:输出温度字符串@return 0:正常获取 1:TempStr=NULL 2:传入的pin不在允许范围 3:未检测到ds18b20 */
uint8
DS18B20_GetTemp_String
(uint8 pin, char *TempStr);
3.2.2、DHT11¶
其中DHT11提供了两个读取函数,分别是:
/* *@description: 从dht11得到温/湿度值数字@param : pin{uint8}:要操作的引脚 HumNum{uint8 }:输出温度值 TemNum{uint8}:输出湿度值 @return 0:正常获取 2:传入的pin不在允许范围 3:未检测到dht11 4:数据校验错误/
uint8
DHT11_GetData_Num
(uint8 pin, uint8 HumNum, uint8TemNum);
/* *@description: 从dht11得到温/湿度值字符串@param : pin{uint8}:要操作的引脚,可选范围0、1、2、3、7 HumStr{char }:输出温度值字符串 TemStr{char}:输出湿度值字符串 @return 0:正常获取 1:HumStr == NULL || TemStr == NULL 2:传入的pin不在允许范围 3:未检测到dht11 4:数据校验错误/
uint8
DHT11_GetData_String
(uint8 pin, char HumStr, charTemStr);
主要是我手上就只有这两种传感器,只能在初始版本内置两个驱动的具体实现。有兴趣的同学可以一起来参与此仓库的贡献。
上述OneWire操作库,已经在码云开源,获取地址:
git clone https://gitee.com/chenxiahuaxu/onewire_driver_library
四、测试DS18B20驱动¶
4.1、编写程序¶
这一部分是本例程的核心,目的是读取DS18B20的值。
static void ds18b20_task(PVOID pParameter)
{
iot_os_sleep(3000);
while (1)
{
if (DS18B20_GetTemp_Num(7, &TempNum) == 0)
{
iot_debug_print("[ds18b20]DS18B20_GetTemp_Num : %d", TempNum);
}
iot_os_sleep(1000);
if (DS18B20_GetTemp_String(7, &TempStr[0]) == 0)
{
iot_debug_print("[ds18b20]DS18B20_GetTemp_String : %s", TempStr);
}
iot_os_sleep(1000);
}
}
这一部分是本例程的彩蛋,将读取到的值显示在OLED-SSD1306上。
static void oled_task(PVOID pParameter)
{
if (OLED_Init() == FALSE) //初始化OLED
{
while (1)
{
iot_debug_print("[oled]OLED_Init FALSE!");
iot_os_sleep(1000);
}
}
OLED_ShowString(0, 0, "LUAT CSDK", 24);
OLED_ShowString(0, 24, "DEMO DS18B20", 16);
OLED_ShowString(0, 40, "Tempera:", 12);
OLED_ShowString(0, 52, "num:", 12);
while (1)
{
OLED_ShowString(50, 40, TempStr, 12); //显示ASCII字符
OLED_ShowNum(50, 52, TempNum, 6, 12); //显示ASCII字符
OLED_Refresh_Gram(); //更新显示到OLED
iot_os_sleep(1000);
iot_debug_print("[oled]demo_oled run!");
}
}
4.2、完整程序¶
完整的demo在这里,可以复制直接用。
/***************
demo_ds18b20
****************/
#include "iot_debug.h"
#include "iot_os.h"
#include "am_openat.h"
#include "oled.h"
#include "ds18b20.h"
int TempNum = 0;
char TempStr[10] = {0};
static void oled_task(PVOID pParameter)
{
if (OLED_Init() == FALSE) //初始化OLED
{
while (1)
{
iot_debug_print("[oled]OLED_Init FALSE!");
iot_os_sleep(1000);
}
}
OLED_ShowString(0, 0, "LUAT CSDK", 24);
OLED_ShowString(0, 24, "DEMO DS18B20", 16);
OLED_ShowString(0, 40, "Tempera:", 12);
OLED_ShowString(0, 52, "num:", 12);
while (1)
{
OLED_ShowString(50, 40, TempStr, 12); //显示ASCII字符
OLED_ShowNum(50, 52, TempNum, 6, 12); //显示ASCII字符
OLED_Refresh_Gram(); //更新显示到OLED
iot_os_sleep(1000);
iot_debug_print("[oled]demo_oled run!");
}
}
static void ds18b20_task(PVOID pParameter)
{
iot_os_sleep(3000);
while (1)
{
if (DS18B20_GetTemp_Num(7, &TempNum) == 0)
{
iot_debug_print("[ds18b20]DS18B20_GetTemp_Num : %d", TempNum);
}
iot_os_sleep(1000);
if (DS18B20_GetTemp_String(7, &TempStr[0]) == 0)
{
iot_debug_print("[ds18b20]DS18B20_GetTemp_String : %s", TempStr);
}
iot_os_sleep(1000);
}
}
int appimg_enter(void *param)
{
iot_os_sleep(2000);
iot_debug_print("[oled]appimg_enter");
iot_os_create_task(oled_task, NULL, 1024, 1, OPENAT_OS_CREATE_DEFAULT, "oled_task");
iot_os_create_task(ds18b20_task, NULL, 1024, 1, OPENAT_OS_CREATE_DEFAULT, "ds18b20_task");
return 0;
}
void appimg_exit(void)
{
iot_debug_print("[oled]appimg_exit");
}
五、测试DHT11驱动¶
4.1、编写程序¶
这一部分是本例程的核心,目的是读取DHT11的值。
static void dht11_task(PVOID pParameter)
{
iot_os_sleep(3000);
while (1)
{
if (DHT11_GetData_String(7, &HumStr, &TemStr) == 0)
{
iot_debug_print("[dht11]HumStr: %s,TemStr: %s", HumStr, TemStr);
}
iot_os_sleep(3000);
}
}
4.2、完整程序¶
完整的demo在这里,可以复制直接用。
/***************
demo_dht11
****************/
#include "iot_debug.h"
#include "iot_os.h"
#include "am_openat.h"
#include "oled.h"
#include "dht11.h"
char HumStr[10] = {0};
char TemStr[10] = {0};
static void oled_task(PVOID pParameter)
{
if (OLED_Init() == FALSE) //初始化OLED
{
while (1)
{
iot_debug_print("[oled]OLED_Init FALSE!");
iot_os_sleep(1000);
}
}
OLED_ShowString(0, 0, "LUAT CSDK", 24);
OLED_ShowString(0, 24, "DEMO DHT11", 16);
OLED_ShowString(0, 40, "Humidity:", 12);
OLED_ShowString(0, 52, "Tempera:", 12);
uint8 t = 0;
while (1)
{
OLED_ShowString(60, 40, HumStr, 12); //显示ASCII字符
OLED_ShowString(60, 52, TemStr, 12); //显示ASCII字符
OLED_Refresh_Gram(); //更新显示到OLED
iot_os_sleep(1000);
iot_debug_print("[oled]demo_oled run!");
}
}
static void dht11_task(PVOID pParameter)
{
iot_os_sleep(3000);
while (1)
{
if (DHT11_GetData_String(7, &HumStr, &TemStr) == 0)
{
iot_debug_print("[dht11]HumStr: %s,TemStr: %s", HumStr, TemStr);
}
iot_os_sleep(3000);
}
}
int appimg_enter(void *param)
{
iot_os_sleep(2000);
iot_debug_print("[oled]appimg_enter");
iot_os_create_task(oled_task, NULL, 1024, 1, OPENAT_OS_CREATE_DEFAULT, "oled_task");
iot_os_create_task(dht11_task, NULL, 1024, 1, OPENAT_OS_CREATE_DEFAULT, "dht11_task");
return 0;
}
void appimg_exit(void)
{
iot_debug_print("[oled]appimg_exit");
}