7.1 DHT11温湿度传感器

本小节将讲解如何使用DHT11温湿度传感器来感知环境温湿度,并且把数据在屏幕中显示出来。许多传感器的使用方法都是类似的,通过学习使用DHT11,相信读者可以做到举一反三,能够使用更多种类的传感器。

DHT11 简介

DHT11数字温湿度传感器是一款能够检测温湿度的复合传感器,其内置一个测温元件、一个电阻式感湿元件和一个单片机。以奥松公司生产的DHT11温湿度传感器为例,其有效测量范围为:

  • 温度:0~50℃
  • 湿度:20~95%

DHT11实物如图所示。
第7章:外设实验 - 图1

可以看到,DHT11带有4个引脚,其功能说明见下表。

PIN(引脚) 名称 说明
1 VCC 供电引脚,3~5.5VDC
2 DATA 温湿度数据输出
3 NC 空置引脚
4 GND 地线引脚,接电源负极

可以从DATA引脚来获取温湿度数据,下面简单讲解一下其通信协议。

DATA引脚的通信协议分析

从DHT11中获取温湿度数据的方法较比简单,首先是CC2530与DHT11配对(握手),然后按照特定的协议从DATA引脚接收数据。

配对(握手)
在发送数据前,DHT11需要先和CC2530配对,配对的协议如下:
(1)DATA引脚在初始的默认状态时处于高电平(3.3v)。
(2)CC2530拉低DATA引脚的电平18ms毫秒以上,接着拉高电平20~40us,DHT11就会被激活。
(3)DHT11会主动拉低DATA引脚的电平,并且持续80us,表示已经收到了CC2530的指令并且配对成功。
(4)接着DHT11会再次拉高电平,80us后就开始发送温湿度数据给CC2530了。
这个配对过程如图所示。
第7章:外设实验 - 图2

接收收据
DHT11的温湿度数据是以二进制数据表示的,这些二进制数据是按照一个比特位接着一个比特位这样顺序发送到CC2530的,具体的原理如下:
(1)在发送每个比特位之前,DHT11都会把DATA引脚的电平拉低50us,以此告诉CC2530:“我接着要发送一个比特位了”。
(2)接着,DHT11把DATA引脚的电平拉高,如果持续拉高26~28us,表示发送的是数据0;如果持续拉高70us,表示发送的是数据1,如图所示。
第7章:外设实验 - 图3
第7章:外设实验 - 图4

通过这个方式,温湿度数据就发送给了CC2530了。DHT11的通信协议大致上介绍完毕,但还有多个细节还没讲解到,有兴趣的读者可查阅更多相关的资料或仔细研究一下接下来介绍的API的源代码。

DHT11驱动API简介

基于以上原理,笔者设计了一套DHT11驱动API,使用起来非常方便。打开配套工程中的DHT11文件夹,即可找到DHT11驱动API,如图所示。
第7章:外设实验 - 图5

打开hal_dht11.h文件,可以找到API定义代码:

  1. //2. 51单片机入门/7. 外设实验/7.1 温湿度传感器DHT11/Workspace/code/DHT11_MAIN/hal_dht11.h
  2. /**
  3. * @fn halDHT11Init
  4. *
  5. * @brief 初始化函数,使用DHT11前必须先调用此函数
  6. */
  7. void halDHT11Init(void);
  8. /**
  9. * @fn halDHT11GetData
  10. *
  11. * @brief 获取DHT11的温湿度数据
  12. *
  13. * @return 温湿度数据值
  14. */
  15. halDHT11Data_t halDHT11GetData(void);

其中的halDHT11Data_t是一个结构体,用于保存温湿度数据,其定义代码如下:

  1. 1./** @brief 用于表示DHT11温湿度数据 */
  2. 2.typedef struct {
  3. 3. unsigned char ok; //ok的值非0时温湿度数据才有效
  4. 4. unsigned char temp; //温度值,取值范围:0~50
  5. 5. unsigned char humi; //湿度值,取值范围:20~95
  6. 6.} halDHT11Data_t;

使用DHT11驱动API

引脚配置
调用API前,需要先把DATA引脚与CC2530的IO口配对起来。ZigBee标准板是使用CC2530的P0_6引脚与DHT11的DATA引脚连接,可在hal_dht11.h文件中找到如下配置代码:

  1. 1.#define HAL_DHT11_PORT 0 //Port0.
  2. 2.#define HAL_DHT11_PIN 6 //Pin6.

如果在硬件上DATA引脚需要与CC2530的其他引脚连接的话,只需要在这里修改引脚编号即可。

API调用示例
打开main.c文件,可以看到API调用示例代码:

  1. //2. 51单片机入门/7. 外设实验/7.1 温湿度传感器DHT11/Workspace/code/DHT11_MAIN/main.c
  2. void main(void)
  3. {
  4. halDHT11Data_t dht11Dat;//定义温湿度数据结构体
  5. uint8 tempStr[50], humiStr[50];
  6. setSystemClk32MHZ();//初始化系统时钟频率
  7. //初始化显示器
  8. #ifdef LCD_OLED12864
  9. //初始化OLED12864屏幕
  10. halOLED12864Init()
  11. #else
  12. //初始化TFT屏幕
  13. halTFTInit(HAL_TFT_PIXEL_WHITE);
  14. #endif
  15. halDHT11Init();//初始化DHT11温湿度传感器
  16. while(1)
  17. {
  18. dht11Dat = halDHT11GetData();//获取温湿度数据
  19. if (dht11Dat.ok) {//如果数据正确获取
  20. sprintf((char *)tempStr, "Temp: %d", dht11Dat.temp);//显示温度
  21. sprintf((char *)humiStr, "Humi: %d", dht11Dat.humi);//显示湿度
  22. //把数据显示到屏幕中
  23. #ifdef LCD_OLED12864
  24. //显示到OLED12863屏幕
  25. halOLED12864ShowX16(0,0, tempStr);
  26. halOLED12864ShowX16(1,0, humiStr);
  27. #else
  28. //显示到TFT屏幕
  29. halTFTShowX16(0,0, HAL_TFT_PIXEL_RED, HAL_TFT_PIXEL_WHITE, tempStr);
  30. halTFTShowX16(0,16, HAL_TFT_PIXEL_RED, HAL_TFT_PIXEL_WHITE, humiStr);
  31. #endif
  32. }
  33. delayMs(SYSCLK_32MHZ, 4000);//延迟
  34. } /* while */
  35. }

=

调试仿真

可以运行本实验代码以观察运行结果,操作步骤如下:
(1)编译链接工程代码后,把程序烧录到配套的ZigBee开发板中。

(2)由于本实验需要用到DHT11温湿度传感器,ZigBee标准板中已经集成了DHT11温湿度传感器,并且DATA引脚默认连接着P0_6引脚,如图所示。
第7章:外设实验 - 图6

如果使用ZigBee Mini测试,需要在P0_6引脚处外接DHT11的DATA引脚,并且把该DHT11的VCC和GND引脚分别接入接入3.3v~5.5v的电源和地线。

(3)把OLED屏幕插入到标准板或者Mini板中,如图所示。
第7章:外设实验 - 图7
第7章:外设实验 - 图8
(4)给开发板上电,屏幕中即会显示环境温湿度数据。

7.2 NorFLASH读写实验

硬件准备

本节课需要用到NOR Flash存储器

  • ZigBee标准板带有NOR Flash存储器,如图所示。
    第7章:外设实验 - 图9
  • 由于ZigBee MiNi板默认无NOR Flash存储器,无标准板的读者可以跳过本节课

    Flash存储器简介

    Flash存储器可以用于保存信息,例如系统配置信息、资料文档等,用途用非常广泛。

按照内部存储结构不同,Flash存储器可分为NOR Flash和NAND Flash两种类型。

NOR Flash

NOR Flash存储器的读取速度快、存储可靠性高、支持使用随机地址访问存储空间支持,但是存储容量小、价格贵,多用于保存电子产品的程序。

NAND Flash

相对于NOR Flash,NAND Flash存储器的容量大、可反复读写次数高和价格便宜,但是读取数据速度慢、不支持随机地址访问存储空间,有点类似于光盘或硬盘,多用于存储卡、U盘中。

M25PE80简介

ZigBee标准板中带有一颗M25PE80芯片,这是一款NOR Flash,其容量是1024KB(8M bit)。另外,CC2530F256的内部也带有Flash存储器,其容量是256KB。也就是说,标准板上共有1024KB+256KB的Flash容量。

  • M25PE80实物图:
    第7章:外设实验 - 图10
  • M25PE80引脚图:
    第7章:外设实验 - 图11

    了解 M25PE80 API

    M25PE80的通信协议是SPI。在学习显示器的实验时,已经讲解过SPI驱动API的设计方式了,只需要基于通用SPI驱动API适配出M25PE80的专用SPI驱动API即可

打开配套工程代码,展开Hal_Flash_Spi,可以看到笔者设计的通用SPI驱动API和M25Exx驱动专用的SPI驱动API,如图所示。

第7章:外设实验 - 图12

借助此API,M25PE80的使用非常简单,只需要学习一下hal_m25pexx.h/c文件中的3个API即可,其定义如下:

  1. /**
  2. * @fn halM25PExxInit
  3. *
  4. * @brief 初始化M25PExx
  5. *
  6. * @return none
  7. */
  8. void halM25PExxInit(void);
  9. /**
  10. * @fn halM25PExxRead
  11. *
  12. * @brief 从M25PExx中读取数据
  13. *
  14. * @param addr - 将要读取的数据所在的存储器地址.
  15. * @param pBuf - 变量指针,用于保存储器中读出来的数据.
  16. * @param len - 指定从存储器中读取多少个字节的数据.
  17. *
  18. * @return 如果读取成功,则返回0
  19. */
  20. int halM25PExxRead(uint32 addr, uint8 *pBuf, uint16 len);
  21. /**
  22. * @fn halM25PExxWrite
  23. *
  24. * @brief 把数据写入到存储器中
  25. *
  26. * @param addr - 说明把数据写入到存储器的哪个地址
  27. * @param pBuf - 变量指针,指向将要写入到存储器的数据
  28. * @param len - 指定把多少个字节的数据写入到存储器中
  29. *
  30. * @return 如果写入成功,则返回0
  31. */
  32. int halM25PExxWrite(uint32 addr, uint8 *pBuf, uint16 len);

使用 M25PE80 API

打开main.c文件,可以使用 M25PE80 API读写数据的示例代码:

  1. void main(void)
  2. {
  3. uint8 writeVal = 0;//此变量的值将会被写入到存储器中
  4. uint8 readVal = 0;//从存储器读取到的值将会存入此变量中
  5. char str[50];
  6. setSystemClk32MHZ();//初始化系统时钟为32MHz
  7. initUart0(USART_BAUDRATE_115200);//初始化串口0
  8. halM25PExxInit();//初始化M25PE80存储器
  9. //进入到循环中
  10. while(1) {
  11. /* 1.写数据到M25PE80存储器中 */
  12. //串口通信
  13. sprintf(str, "Write: %d\r\n", writeVal);
  14. uart0Send((unsigned char *)str, strlen(str));
  15. //把writeVal的值写入到存储器中地址为0x12345的存储空间中;由于writeVal的类型为uint8,也就1个字节,所以传入的数据长度为1
  16. if (halM25PExxWrite(0x12345, &writeVal, 1) != 0) {
  17. uart0Send("Write Error\r\n", 13);//如果函数返回值不等于0,表示写入错误
  18. continue;
  19. }
  20. writeVal++;//把写入值增加1
  21. delayMs(SYSCLK_32MHZ, 1000);//延迟
  22. /* 2.从M25PE80存储器中读取数据 */
  23. //从halM25PExxRead的0x12345处读取1个字节的数据,并将其保存到readVal中
  24. if (halM25PExxRead(0x12345, &readVal, 1) != 0) {
  25. uart0Send("Read Error\r\n", 12);//如果函数返回值不等于0,表示读取错误
  26. continue;
  27. }
  28. //串口通信
  29. sprintf(str, "Read: %d\r\n", readVal);
  30. uart0Send((uint8 *)str, strlen(str));
  31. delayMs(SYSCLK_32MHZ, 1000);//延迟
  32. }
  33. }

调试仿真

在学习本节课前,需要先掌握基本的程序下载及仿真操作,参考:程序下载及仿真
1.使用仿真器连接ZigBee标准板到电脑中,
2.按一下仿真器中的复位按键
3.打开配套工程,编译源代码并烧录程序到ZigBee标准板中
4.断开仿真器连接,用Micro USB线连接开发板到电脑,并且打开串口调试助手,可以看到写入和读取的过程:
第7章:外设实验 - 图13

7.3 继电器控制实验

本小节将讲解如何使用继电器去控制强电设备。

继电器简介

常见的LED小灯、小型电机或小型传感器的工作电压在3.3v左右,故可以让单片机直接控制其开关。但是像家用电灯泡、家用电风扇或电磁锁的工作电压是220v,故单片机无法直接控制其开关。这时候,单片机可以借助继电器来控制高电压用电器的开关。ZigBee标准板集成了继电器,如图所示。
第7章:外设实验 - 图14

其中的绿色部分是接线端子,共有3接线口,每个接线口的上方均有一个螺丝孔。
第7章:外设实验 - 图15

以220v家用灯泡为例简单讲解一下继电器的接线方法,用螺丝刀拧开第1、2号口的螺丝,分别塞入零线后再拧紧螺丝,如图所示。此时,继电器充当了一个开关,可以控制零线的断开或者闭合,从而控制灯泡的开关。也可以把零线分别塞入到第2、3号口,区别在与如果介入第1、2号口,那么在默认状态下是断开的;如果接入第2、3号口,那么在默认状态是闭合的。
第7章:外设实验 - 图16

类似地,使用继电器连接12v电磁锁的示意图如图所示。图中的12v电池是用于给电磁锁供电的。
第7章:外设实验 - 图17

使用继电器

继电器的使用方式非常简单,控制继电器的控制引脚的电平即可控制其开合。

引脚配置
使用继电器前,需要先把继电器的控制引脚与CC2530的IO口配对起来。ZigBee标准板是使用CC2530的P0_5引脚与继电器的控制引脚连接的。打开本实验代码代码,可以找到ioConfig.c文件,如图所示。
第7章:外设实验 - 图18

可在本实验代码的ioConfig.c文件中找到如下配置代码:

  1. #define RELAY_PORT 0
  2. #define RELAY_PIN 5
  3. #define RELAY P0_5

P0_5是由头文件ioCC2530.h所定义的,用于表示P0_5引脚,因此RELAY实际上就表示P0_5引脚。如果继电器控制引脚需要与CC2530的其他引脚连接的话,只需要在这里修改引脚映射即可,例如如果需要在P1_2引脚外接继电器控制引脚,代码如下:

  1. #define RELAY_PORT 1
  2. #define RELAY_PIN 2
  3. #define RELAY P1_2

控制继电器
控制继电器的开合,本质上就是控制P0_5引脚的电平,在ioConfig.c中可找到示例代码:

  1. //2. 51单片机入门/7. 外设实验/7.3 继电器开关控制/Workspace/code/ioConfig/ioConfig.c
  2. #include "cc2530_ioctl.h"
  3. #include <stdio.h>
  4. /** @brief GPIO映射定义 */
  5. #define RELAY_PORT 0
  6. #define RELAY_PIN 5
  7. #define RELAY P0_5
  8. /** @brief 继电器开关状态定义 */
  9. #define RELAY_ON 1
  10. #define RELAY_OFF 0
  11. static void delayMs(uint16_t nMs);
  12. static void initRelay(void);
  13. void main()
  14. {
  15. initRelay();
  16. while(1) {
  17. delayMs(1000);//延迟
  18. //反转RELAY引脚的电平状态
  19. RELAY = (RELAY == RELAY_ON)? RELAY_OFF : RELAY_ON;
  20. } /* while */
  21. }
  22. /*
  23. * 延迟指定的时间
  24. *
  25. * @param nMs - 时间长度,单位为微秒
  26. */
  27. static void delayMs(uint16_t nMs)
  28. {
  29. uint16_t i,j;
  30. for (i = 0; i < nMs; i++)
  31. for (j = 0; j < 535; j++);
  32. }
  33. /*
  34. * 初始化继电器
  35. */
  36. static void initRelay()
  37. {
  38. CC2530_IOCTL(
  39. RELAY_PORT,
  40. RELAY_PIN,
  41. CC2530_OUTPUT);
  42. RELAY = RELAY_OFF;
  43. }

上述代码实现了不断开关继电器的功能。可见,继电器的使用其实非常简单的。

调试仿真

可以运行本实验代码以观察运行结果,操作步骤如下:
(1)编译链接配套的工程代码后,把程序烧录到配套的ZigBee开发板中。
(2)由于本实验需要用到继电器,ZigBee标准板中已经集成了继电器,并且其控制引脚默认连接着P0_5引脚。如果使用ZigBee Mini板测试,需要在P0_5引脚处外接继电器控制引脚,并且把该继电器的VCC和GND引脚分别接入到5v的电源和地线。
(3)通过MicroUSB线给开发板上电,即可观察到继电器不断的开合。
必须要使用Micro USB线给开发板供电才能使用继电器!