如何为编程爱好者设计一款好玩的智能硬件(五)——初尝试·把温湿度给收集了(中)!
一、我的构想:如何为编程爱好者设计一款好玩的智能硬件(一)——即插即用、积木化、功能重组的智能硬件模块构想
二、别人家的孩子:如何为编程爱好者设计一款好玩的智能硬件(二)——别人是如何设计硬件积木的!
三、MCU选型:如何为编程爱好者设计一款好玩的智能硬件(三)——该选什么样的MCU呢?
四、温湿度传感器DHT11驱动封装(上):如何为编程爱好者设计一款好玩的智能硬件(四)——初尝试·把温湿度给收集了(上)!
五、温湿度传感器DHT11驱动封装(中):
先打个预防针——本篇可能比较枯燥!与上一篇图文代码并茂挖空心思讲DHT11的驱动程序的写法不同,本篇将到处都是代码!更坑的是会出现基于AT89C52平台的DHT11驱动C语言代码、基于CC2541平台的DHT11的C语言驱动代码、以及基于STM32平台的DHT11的C语言驱动代码。然后最终根据这三个平台的不同情况抽象出一个尽量和平台无关、方便移植的DHT11底层驱动代码,便于我们今后编写”X-积木OS”。
认个怂:
之所以用这种从普遍情况中找共性的笨方法写DHT11平台无关的驱动,是因为我水平有限,感觉直接从上(datasheet中的通信协议)到下(和平台无关的驱动底层封装)力不从心——不过我也正在努力学习linux系统的c程序架构方式,只是现在还不敢拿出来试手。
开始正文:
为了方便展示DHT11底层驱动在各个平台上的共性(驱动协议)和平台特性,我将用不同的颜色表示平台共性中通信协议的不同过程:
※ 红色表示:协议中启动读数据部分
※ 绿色表示:协议中DHT11应答MCU部分
※ 蓝色表示:MCU读取DHT11的40bit数据部分
※ 黑色表示:协议中终止读取数据部分
接着会在代码后面谈其平台特性,并提出针对这些平台特性的解决方法。
CC2541平台上DHT11驱动底层C语言封装:
1 #include <ioCC2540.h> 2 3 typedef unsigned char uchar; 4 typedef unsigned int uint; 5 6 #define DATA_PIN P0_0 7 8 //温湿度定义 9 uchar ucharFLAG, uchartemp; 10 uchar shidu_shi, shidu_ge, wendu_shi, wendu_ge = 4; 11 uchar ucharT_data_H, ucharT_data_L, ucharRH_data_H, ucharRH_data_L, ucharcheckdata; 12 uchar ucharT_data_H_temp, ucharT_data_L_temp, ucharRH_data_H_temp, ucharRH_data_L_temp, ucharcheckdata_temp; 13 uchar ucharcomdata; 14 15 //延时函数 16 void Delay_us() //1 us延时 17 { 18 asm("nop"); 19 asm("nop"); 20 asm("nop"); 21 asm("nop"); 22 asm("nop"); 23 asm("nop"); 24 asm("nop"); 25 asm("nop"); 26 asm("nop"); 27 } 28 29 void Delay_10us() //10 us延时 30 { 31 Delay_us(); 32 Delay_us(); 33 Delay_us(); 34 Delay_us(); 35 Delay_us(); 36 Delay_us(); 37 Delay_us(); 38 Delay_us(); 39 Delay_us(); 40 Delay_us(); 41 } 42 43 void Delay_ms(uint Time)//n ms延时 44 { 45 unsigned char i; 46 while(Time--) 47 { 48 for(i = 0; i < 100; i++) 49 Delay_10us(); 50 } 51 } 52 53 //温湿度传感 54 void COM(void) // 温湿写入 55 { 56 uchar i; 57 for(i = 0; i < 8; i++) 58 { 59 ucharFLAG = 2; 60 while((!DATA_PIN) && ucharFLAG++); 61 Delay_10us(); 62 Delay_10us(); 63 Delay_10us(); 64 uchartemp = 0; 65 if(DATA_PIN)uchartemp = 1; 66 ucharFLAG = 2; 67 while((DATA_PIN) && ucharFLAG++); 68 if(ucharFLAG == 1)break; 69 ucharcomdata <<= 1; 70 ucharcomdata |= uchartemp; 71 } 72 } 73 74 void DHT11(void) //温湿传感启动 75 { 76 DATA_PIN = 0; //1、启动 77 Delay_ms(19); //>18MS 78 DATA_PIN = 1; 79 P0DIR &= ~0x01; //重新配置IO口方向 80 Delay_10us(); 81 Delay_10us(); 82 Delay_10us(); 83 Delay_10us(); 84 if(!DATA_PIN) 85 { 86 ucharFLAG = 2; //2、等待 87 while((!DATA_PIN) && ucharFLAG++); 88 ucharFLAG = 2; 89 while((DATA_PIN) && ucharFLAG++); 90 COM(); //3、读取 91 ucharRH_data_H_temp = ucharcomdata; 92 COM(); 93 ucharRH_data_L_temp = ucharcomdata; 94 COM(); 95 ucharT_data_H_temp = ucharcomdata; 96 COM(); 97 ucharT_data_L_temp = ucharcomdata; 98 COM(); 99 ucharcheckdata_temp = ucharcomdata; 100 DATA_PIN = 1; //4、终止 101 uchartemp = (ucharT_data_H_temp + ucharT_data_L_temp + ucharRH_data_H_temp + ucharRH_data_L_temp); 102 if(uchartemp == ucharcheckdata_temp) 103 { 104 ucharRH_data_H = ucharRH_data_H_temp; 105 ucharRH_data_L = ucharRH_data_L_temp; 106 ucharT_data_H = ucharT_data_H_temp; 107 ucharT_data_L = ucharT_data_L_temp; 108 ucharcheckdata = ucharcheckdata_temp; 109 } 110 wendu_shi = ucharT_data_H / 10; 111 wendu_ge = ucharT_data_H % 10; 112 113 shidu_shi = ucharRH_data_H / 10; 114 shidu_ge = ucharRH_data_H % 10; 115 } 116 else //没用成功读取,返回0 117 { 118 wendu_shi = 0; 119 wendu_ge = 0; 120 121 shidu_shi = 0; 122 shidu_ge = 0; 123 } 124 125 P0DIR |= 0x01; //IO口需要重新配置 126 }
这里具有平台特性的是:
① 延时函数,不同平台延时函数必定不一样
——> 采用宏定义延时函数,这样只需修改宏而整个驱动程序内部不用修改
② CC2541的引脚的输出和输入特性需要配置,由于单线通信,所以MCU端的引脚就必须在输入属性和输出属性直接进行适时的切换:如上面代码中第79行当MCU发送启动信号之后便将该引脚置为输入属性,在125行,当本次数据传输结束时,又重新将该引脚置为输入等待下次启动
——> 待解决
③ 不同平台引脚定义方式不同,同一平台因为"X-积木"组合方式不同所以每个引脚都可能成为该引脚(不能仅仅用宏定义写死)
——> 采用宏函数形式,做个动态的宏,以达到能表示全部引脚的功能。
AT89C52平台上DHT11驱动底层C语言封装:
1 //****************************************************************// 2 // DHT11使用范例 3 //单片机 : AT89S52 或 STC89C52RC 4 // 功能 :串口发送温湿度数据 晶振 11.0592M 波特率 9600 5 //硬件连接: P2.0口为通讯口连接DHT11,DHT11的电源和地连接单片机的电源和地,单片机串口加MAX232连接电脑 6 //****************************************************************// 7 8 #include <reg51.h> 9 #include <intrins.h> 10 // 11 typedef unsigned char U8; /* defined for unsigned 8-bits integer variable 无符号8位整型变量 */ 12 typedef signed char S8; /* defined for signed 8-bits integer variable 有符号8位整型变量 */ 13 typedef unsigned int U16; /* defined for unsigned 16-bits integer variable 无符号16位整型变量 */ 14 typedef signed int S16; /* defined for signed 16-bits integer variable 有符号16位整型变量 */ 15 typedef unsigned long U32; /* defined for unsigned 32-bits integer variable 无符号32位整型变量 */ 16 typedef signed long S32; /* defined for signed 32-bits integer variable 有符号32位整型变量 */ 17 typedef float F32; /* single precision floating point variable (32bits) 单精度浮点数(32位长度) */ 18 typedef double F64; /* double precision floating point variable (64bits) 双精度浮点数(64位长度) */ 19 // 20 #define uchar unsigned char 21 #define uint unsigned int 22 #define Data_0_time 4 23 24 //----------------------------------------------// 25 //----------------IO口定义区--------------------// 26 //----------------------------------------------// 27 sbit P2_0 = P2 ^ 0 ; 28 29 //----------------------------------------------// 30 //----------------定义区--------------------// 31 //----------------------------------------------// 32 U8 U8FLAG, k; 33 U8 U8count, U8temp; 34 U8 U8T_data_H, U8T_data_L, U8RH_data_H, U8RH_data_L, U8checkdata; 35 U8 U8T_data_H_temp, U8T_data_L_temp, U8RH_data_H_temp, U8RH_data_L_temp, U8checkdata_temp; 36 U8 U8comdata; 37 U8 outdata[5]; //定义发送的字节数 38 U8 indata[5]; 39 U8 count, count_r = 0; 40 U8 str[5] = {"RS232"}; 41 U16 U16temp1, U16temp2; 42 SendData(U8 *a) 43 { 44 outdata[0] = a[0]; 45 outdata[1] = a[1]; 46 outdata[2] = a[2]; 47 outdata[3] = a[3]; 48 outdata[4] = a[4]; 49 count = 1; 50 SBUF = outdata[0]; 51 } 52 53 void Delay(U16 j) 54 { 55 U8 i; 56 for(; j > 0; j--) 57 { 58 for(i = 0; i < 27; i++); 59 60 } 61 } 62 void Delay_10us(void) 63 { 64 U8 i; 65 i--; 66 i--; 67 i--; 68 i--; 69 i--; 70 i--; 71 } 72 73 void COM(void) 74 { 75 76 U8 i; 77 78 for(i = 0; i < 8; i++) 79 { 80 81 U8FLAG = 2; 82 while((!P2_0) && U8FLAG++); 83 Delay_10us(); 84 Delay_10us(); 85 Delay_10us(); 86 U8temp = 0; 87 if(P2_0)U8temp = 1; 88 U8FLAG = 2; 89 while((P2_0) && U8FLAG++); 90 //超时则跳出for循环 91 if(U8FLAG == 1)break; 92 //判断数据位是0还是1 93 94 // 如果高电平高过预定0高电平值则数据位为 1 95 96 U8comdata <<= 1; 97 U8comdata |= U8temp; //0 98 }//rof 99 100 } 101 102 //-------------------------------- 103 //-----湿度读取子程序 ------------ 104 //-------------------------------- 105 //----以下变量均为全局变量-------- 106 //----温度高8位== U8T_data_H------ 107 //----温度低8位== U8T_data_L------ 108 //----湿度高8位== U8RH_data_H----- 109 //----湿度低8位== U8RH_data_L----- 110 //----校验 8位 == U8checkdata----- 111 //----调用相关子程序如下---------- 112 //---- Delay();, Delay_10us();,COM(); 113 //-------------------------------- 114 void RH(void) 115 { 116 //主机拉低18ms //1、启动 117 P2_0 = 0; 118 Delay(180); 119 P2_0 = 1; 120 //总线由上拉电阻拉高 主机延时20us 121 Delay_10us(); 122 Delay_10us(); 123 Delay_10us(); 124 Delay_10us(); 125 //主机设为输入 判断从机响应信号 126 P2_0 = 1; 127 //判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行 128 if(!P2_0) //T ! 129 { 130 U8FLAG = 2; //2、等待 131 //判断从机是否发出 80us 的低电平响应信号是否结束 132 while((!P2_0) && U8FLAG++); 133 U8FLAG = 2; 134 //判断从机是否发出 80us 的高电平,如发出则进入数据接收状态 135 while((P2_0) && U8FLAG++); 136 //数据接收状态 //3、接收数据 137 COM(); 138 U8RH_data_H_temp = U8comdata; 139 COM(); 140 U8RH_data_L_temp = U8comdata; 141 COM(); 142 U8T_data_H_temp = U8comdata; 143 COM(); 144 U8T_data_L_temp = U8comdata; 145 COM(); 146 U8checkdata_temp = U8comdata; 147 P2_0 = 1; //4、停止 148 //数据校验 149 150 U8temp = (U8T_data_H_temp + U8T_data_L_temp + U8RH_data_H_temp + U8RH_data_L_temp); 151 if(U8temp == U8checkdata_temp) 152 { 153 U8RH_data_H = U8RH_data_H_temp; 154 U8RH_data_L = U8RH_data_L_temp; 155 U8T_data_H = U8T_data_H_temp; 156 U8T_data_L = U8T_data_L_temp; 157 U8checkdata = U8checkdata_temp; 158 }//fi 159 }//fi 160 161 } 162 163 //---------------------------------------------- 164 //main()功能描述: AT89C51 11.0592MHz 串口发 165 //送温湿度数据,波特率 9600 166 //---------------------------------------------- 167 void main() 168 { 169 U8 i, j; 170 171 //uchar str[6]={"RS232"}; 172 /* 系统初始化 */ 173 TMOD = 0x20; //定时器T1使用工作方式2 174 TH1 = 253; // 设置初值 175 TL1 = 253; 176 TR1 = 1; // 开始计时 177 SCON = 0x50; //工作方式1,波特率9600bps,允许接收 178 ES = 1; 179 EA = 1; // 打开所以中断 180 TI = 0; 181 RI = 0; 182 SendData(str) ; //发送到串口 183 Delay(1); //延时100US(12M晶振) 184 while(1) 185 { 186 187 //------------------------ 188 //调用温湿度读取子程序 189 RH(); 190 //串口显示程序 191 //-------------------------- 192 193 str[0] = U8RH_data_H; 194 str[1] = U8RH_data_L; 195 str[2] = U8T_data_H; 196 str[3] = U8T_data_L; 197 str[4] = U8checkdata; 198 SendData(str) ; //发送到串口 199 //读取模块数据周期不易小于 2S 200 Delay(20000); 201 }//elihw 202 203 }// main 204 205 void RSINTR() interrupt 4 using 2 206 { 207 U8 InPut3; 208 if(TI == 1) //发送中断 209 { 210 TI = 0; 211 if(count != 5) //发送完5位数据 212 { 213 SBUF = outdata[count]; 214 count++; 215 } 216 } 217 218 if(RI == 1) //接收中断 219 { 220 InPut3 = SBUF; 221 indata[count_r] = InPut3; 222 count_r++; 223 RI = 0; 224 if (count_r == 5) //接收完4位数据 225 { 226 //数据接收完毕处理。 227 count_r = 0; 228 str[0] = indata[0]; 229 str[1] = indata[1]; 230 str[2] = indata[2]; 231 str[3] = indata[3]; 232 str[4] = indata[4]; 233 P0 = 0; 234 } 235 } 236 }
这里具有平台特性的是:
① 延时函数
——> 针对CC2541的解决方案适用
② 与CC2541的引脚的输出和输入特性需要配置不同,AT89C52的引脚不用配置,直接具有输入输出属性,因此不必切换
——> 有的平台要切换,有的平台不用切换,因此需要用相应的宏判断条件来搞了
③ 不同平台引脚定义方式不同,同一平台因为"X-积木"组合方式不同所以每个引脚都可能成为该引脚(不能仅仅用宏定义写死)
——> 采用既定解决方案(同上)
④ 不同平台数据类型不同
——> 完全自定义一套数据类型,通过宏配置到不同平台(该宏一定不要和某些平台上的语言片段重合,一定要特殊并且能表达一定意思)
STM32平台上DHT11驱动底层C语言封装:
1 /** 2 **文件名称:DHT11.c 3 **文件说明:文件为温湿度传感器DHT11的驱动程序 4 **/ 5 #include "../drive/drive.h" 6 7 void GPIO_DHT_Out_Mode(void) 8 { 9 GPIO_InitTypeDef GPIO_InitStructure; 10 11 GPIO_InitStructure.GPIO_Pin = DHT11_PORT; 12 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出 14 GPIO_Init(GPIOA, &GPIO_InitStructure); 15 } 16 void GPIO_DHT_Input_Mode(void) 17 { 18 GPIO_InitTypeDef GPIO_InitStructure; 19 20 GPIO_InitStructure.GPIO_Pin = DHT11_PORT; 21 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 22 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 23 GPIO_Init(GPIOA, &GPIO_InitStructure); 24 } 25 //---------------------------------------------------------------------------------------------- 26 //--- name : DHT11WriteStart 27 //--- 功能 : 向DHT11写入一个读取数据的引导码 28 //---------------------------------------------------------------------------------------------- 29 void DHT11WriteStart() 30 { 31 GPIO_DHT_Out_Mode(); 32 P10 = 1; 33 P10 = 0; 34 Delay_ms(25);//拉低电平至少18ms 35 P10 = 1; 36 Delay_us(30); 37 } 38 //---------------------------------------------------------------------------------------------- 39 //--- name : DHT11ReadByte 40 //--- 功能 : 从DHT11中读取到一个字节 41 //--- 返回值 : 读取到一个字节的数据 42 //---------------------------------------------------------------------------------------------- 43 u8 DHT11ReadByte(void) 44 { 45 u8 temp = 0, i, j = 0; 46 for(i = 0; i < 8; i++) 47 { 48 temp <<= 1; 49 while(0 == DHT11);//等待变高电平 50 while(1 == DHT11)//计算高电平时长 51 { 52 Delay_us(1); 53 j++; 54 } 55 if(j >= 30) //超过30us确认为1 56 { 57 temp = temp | 0x01; 58 j = 0; 59 } 60 j = 0; 61 } 62 return temp; 63 } 64 //---------------------------------------------------------------------------------------------- 65 //--- name : DHT11Read(u8 *RH_temp,u8 *RL_temp,u8 *TH_temp,u8 *TL_temp,u8 *CK_temp) 66 //--- 功能 : 从DHT11中读取数据 67 //--- 说明 : 测试过程中发现温度数值不变,小数值都是零,此模块未测试成功! 68 //---------------------------------------------------------------------------------------------- 69 void DHT11Read(u8 *RH_temp, u8 *RL_temp, u8 *TH_temp, u8 *TL_temp, u8 *CK_temp) 70 { 71 //uchar TH_temp,TL_temp,RH_temp,RL_temp,CK_temp; 72 //uchar TL_temp,RL_temp,CK_temp; 73 //u8 untemp; 74 while(1) 75 { 76 DHT11WriteStart();//给读取前导信号 //1、启动 77 GPIO_DHT_Input_Mode();//设置端口为输入状态 78 if(!DHT11) 79 { 80 while(0 == DHT11);//低电平的响应信号,80us //2、等待 81 while(1 == DHT11);//紧接着是80us的高电平数据准备信号 82 // *CK_temp = DHT11ReadByte(); 83 // *TL_temp = DHT11ReadByte(); 84 // *TH_temp = DHT11ReadByte(); 85 // *RL_temp = DHT11ReadByte(); 86 // *RH_temp = DHT11ReadByte(); 87 88 *RH_temp = DHT11ReadByte();//湿度高8位 //3、读取数据 89 *RL_temp = DHT11ReadByte();//湿度低8位 90 *TH_temp = DHT11ReadByte();//温度高8位 91 *TL_temp = DHT11ReadByte();//温度低8位 92 *CK_temp = DHT11ReadByte();//校验和 93 GPIO_DHT_Out_Mode(); 94 P10 = 1; //4、END 95 //数据校验 96 //untemp= *RH_temp+RL_temp+*TH_temp+TL_temp; 97 return; 98 } 99 DriveDelay(0x3ff); 100 } 101 } 102 /********************************************************************************************* 103 *** 代码段 : DHT11__DEBUG 104 *** 说明 : 该代码段是用于测试温湿度传感器DHT11所用,正常情况下不加入编译。 105 *** 如果用户要测试该模块,可以将#undef改为#define(在文件头处) 106 *********************************************************************************************/ 107 #undef DHT11__DEBUG 108 109 #define DHT11__DEBUG 110 111 #ifdef DHT11__DEBUG 112 113 int main(void) 114 { 115 u8 TH_temp, TL_temp, RH_temp, RL_temp, CK_temp; 116 char DisBuf[20]; 117 #ifdef DEBUG 118 debug(); 119 #endif 120 SysInit(); 121 // SysTickInit(); 122 InitLcd(); 123 LcdDisText(0x80, "hello world!!!"); 124 125 while(1) 126 { 127 DHT11Read(&RH_temp, &RL_temp, &TH_temp, &TL_temp, &CK_temp); 128 sprintf(DisBuf, "%d-%d-%d-%d-%d", RH_temp, RL_temp, TH_temp, TL_temp, CK_temp); 129 // Delay_ms(500); 130 LcdDisText(0x80 + 0x40, (u8 *)DisBuf); 131 LcdDisText(0x80 + 0x40 + 15, "h"); 132 } 133 } 134 #endif
平台特性补充说明:
① STM32和CC2541类似引脚有输入输出两种模式,因此采用函数GPIO_DHT_Out_Mode和函数GPIO_DHT_Input_Mode来切换不同模式
② 虽然STM32是32位单片机,但是其单总线数据传输时读一个字节的数据还是和52、CC2541类似,并没有特殊情况
小结&接下来计划:
从上面DHT11在CC2541、AT89C52和STM32三种不同平台上的实现可以看出:所有驱动程序万变不离其宗,部分变化只是在系统上微调。而如果想封装一个和平台尽量无关的DHT11底层驱动函数,就需要充分发挥宏定义的作用,将所有平台特性元素全部采用宏定义,并抽出平台共性模型建立底层驱动函数。因此,明天同一时间、同一地点我将详细介绍C语言中宏定义的知识,并最终封装成我们想要的平台无关的温湿度传感器底的层驱动文件。
链接: http://pan.baidu.com/s/1dDlUyyd
[三个关键文件链接]
CC2541:http://pan.baidu.com/s/1o6vf2Fw
AT89C52: http://pan.baidu.com/s/1gdrpYmB
STM32:http://pan.baidu.com/s/1bnz5Czt
@beautifulzzzz
2015-9-9 持续更新中~