一、我的構想:如何為編程愛好者設計一款好玩的智能硬件(一)——即插即用、積木化、功能重組的智能硬件模塊構想
二、別人家的孩子:如何為編程愛好者設計一款好玩的智能硬件(二)——別人是如何設計硬件積木的!
三、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 持續更新中~