ah20温度采集
文章目錄
- 實驗目的
- 實驗材料
- 軟件
- 硬件
- AHT20
- 實驗原理
- 什么是I2C
- 物理層
- 邏輯層
- 硬件I2C與軟件I2C
- 項目制作
- 制作一個標準庫項目
- 導入AHT20模塊與USART配置
- 修改main函數
- 實驗現象
- 總結
- 參考資料
實驗目的
學習I2C總線通信協議,使用STM32F103完成基于I2C協議的AHT20溫濕度傳感器的數據采集,并將采集的溫度-濕度值通過串口輸出。
具體任務:
1)解釋什么是“軟件I2C”和“硬件I2C”? (閱讀野火配套教材的第23章“I2C–讀寫EEPROM”原理章節)
2)閱讀AHT20數據手冊,編程實現:每隔2秒鐘采集一次溫濕度數據,并通過串口發送到上位機(win10)。
實驗材料
軟件
- KEIL5
- FlyMcu
- FireTools串口助手
硬件
- STM32F103C8T6最小開發板
- AHT20溫濕度傳感器
- CH340模塊
- 面包板一塊
- 杜邦線若干
AHT20
AHT20是奧松電子生產的,一種基于IIC協議的溫濕度傳感器。單片機通過給AHT20通過IIC協議發送指令,可以從AHT20讀取溫濕度數據到單片機。
我在本實驗中使用的芯片的引腳圖如下:
根據引腳定義進行連接,按規定編寫代碼給傳感器發送指令,讀取溫濕度數據。
實驗原理
什么是I2C
I2C 通訊協議(Inter-Integrated Circuit)是由 Phiilps 公司開發的,由于它引腳少,硬件實現簡單,可擴展性強,不需要 USART、CAN 等通訊協議的外部收發設備,現在被廣泛地使用在系統內多個集成電路(IC)間的通訊。
I2C可以分成物理層與協議層。
物理層
I2C是一個支持設備的總線。可連接多個 I2C 通訊設備,支持多個通訊主機及多個通訊從機。對于I2C 總線,只使用兩條總線線路,一條雙向串行數據線(SDA) ,一條串行時鐘線(SCL)。
連接到總線上的設備在輸出時使用開漏輸出的模式。當總線上沒有設備發數據時,大家輸出高阻態,總線被上拉電阻設置為高電平。當有設備發送數據時,如果發送1,設備輸出高阻態,接收設備與上拉電阻相連得到1;如果輸出0,由于上拉電阻把總線與電源隔開,整條總線被設置成低電平。
邏輯層
首先主機向總線發送開始信號,發送之后再發送從機地址(7位或10位)+讀或寫(0或1),對應的從機再給主機發送確認信號。
如果是主機發數據,主機會發送8位數據到從機,從機發送確認信號,主機再接著發下8位數據。發送完畢后,主機發送停止信號,傳輸結束。
如果是主機讀數據,從機會發送8位數據給主機,主機收到后發送確認信號,接著收下8位數據。主機可以發送非確認信號停止接收。發送非停止信號后,主機再發送結束信號。
I2C常使用復合模式,有兩次起始信號。主機找到從機后,再發送傳感器里面的寄存器地址,再進行讀寫。
野火的資料里面有圖片讀寫過程的示意圖:
硬件I2C與軟件I2C
硬件I2C是直接利用 STM32 芯片中的硬件 I2C 外設,在初始化好 I2C 外設后,只需要把某寄存器位置 1,此時外設就會控制對應的 SCL 及 SDA 線自動產生 I2C 起始信號,不需要內核直接控制引腳的電平。
軟件I2C直接使用 CPU 內核按照 I2C 協議的要求控制 GPIO 輸出高低電平,從而模擬I2C。
本實驗中,使用I2C讀寫寄存器的具體實現已經被官方寫成函數給我們使用,我們僅需要根據示例代碼,按自己需要進行調用即可。但是想要分析它的源碼,就必須先了解I2C協議。
項目制作
制作一個標準庫項目
制作標準庫項目過程較為復雜,野火的資料庫提供了項目模板。我也寫好了一個模板在這個鏈接里面,可以下載好后根據文本文件里面的提示進行構建。
導入AHT20模塊與USART配置
構建好標準庫項目后,使用空main函數編譯無誤后,進行這一步。
由于本實驗實現的需求是使用AHT20模塊采集溫濕度數據,并將采集到的數據發送給上位機顯示。因此要使用AHT20相關的代碼以及配置串口。
對于AHT20的代碼,可以下載官方的文檔。點擊這里下載。
注意,下載到的代碼有可能會有bug,下載好代碼后,進入.c文件查看Init_I2C_Sensor_Port(void)函數,看SDA對應的GPIO口以及是否為開漏輸出。
檢查IIC的GPIO口配置正確后,剪切AHT20-21_DEMO_V1_3.c文件中的示例主函數到main.c中。
接著進行USART配置。具體的配置過程不再贅述,我貼出自己寫好的模塊化代碼,可以分別復制到頭文件與源文件中導入項目。
我的代碼僅做了對USART1的配置:
//usart.h #ifndef __PSD_USART_H__ #define __PSD_USART_H__ #include "stm32f10x.h" void Usart_1_Config();//usart1初始配置 void Usart_SendByte(USART_TypeDef *pUSARTx, uint8_t ch);//發送字符 void Usart_SendString(USART_TypeDef *pUSARTx, char *ch);//發送字符串 void Usart_SendHalfWord(USART_TypeDef *pUSARTx, uint16_t ch);//發送無符號16位數據 void Usart_SendWord(USART_TypeDef *pUSARTx, uint32_t ch);//發送無符號32位數據 void Usart_SendNum_Uint8(USART_TypeDef *pUSARTx, uint8_t num);//發送Uint8類型數字 void Usart_SendNum_Uint16(USART_TypeDef *pUSARTx, uint16_t num);//發送Uint16類型數字 void Usart_SendNum_Uint32(USART_TypeDef *pUSARTx, uint32_t num);//發送Uint32類型數字 void Usart_SendNum_Int8(USART_TypeDef *pUSARTx, int8_t num);//發送int8類型數字 void Usart_SendNum_Int16(USART_TypeDef *pUSARTx, int16_t num);//發送int16類型數字 void Usart_SendNum_Int32(USART_TypeDef *pUSARTx, int32_t num);//發送int32類型數字 void Usart_SendNum_Double(USART_TypeDef *pUSARTx, double num);//發送double類型數字 #endif //usart.c #include "psd_usart.h" #include "math.h" static void NVIC_Configuration(){NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;NVIC_Init(&NVIC_InitStruct); } void Usart_1_Config(){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//打開串口GPIO時鐘RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//打開串口時鐘//配置RX,TX的GPIO口GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; //將TX配置為浮空輸入模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA,&GPIO_InitStruct);GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; //將RX配置為復用推挽輸出模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);//配置USART1USART_InitTypeDef USART_InitStruct;USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_InitStruct.USART_StopBits = USART_StopBits_1;USART_InitStruct.USART_BaudRate = 115200;USART_InitStruct.USART_Parity = USART_Parity_No;USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStruct.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;USART_Init(USART1,&USART_InitStruct);USART_Cmd(USART1,ENABLE);NVIC_Configuration();USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); }void Usart_SendByte(USART_TypeDef *pUSARTx, uint8_t ch){USART_SendData(pUSARTx,ch);while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET); }void Usart_SendString(USART_TypeDef *pUSARTx, char *ch){unsigned int k = 0;do{Usart_SendByte(pUSARTx,*(ch+k));k++;}while(*(ch+k)!='\0');while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET); }void Usart_SendHalfWord(USART_TypeDef *pUSARTx, uint16_t ch){uint8_t temp_h, temp_l;temp_h = (ch&0xFF00)>>8;temp_l = ch&0xff;Usart_SendByte(pUSARTx, temp_h);Usart_SendByte(pUSARTx, temp_l);}void Usart_SendWord(USART_TypeDef *pUSARTx, uint32_t ch){uint8_t temp_h, temp_l;temp_h = (ch&0xFFFF0000)>>16;temp_l = ch&0xffff;Usart_SendHalfWord(pUSARTx, temp_h);Usart_SendHalfWord(pUSARTx, temp_l);}void Usart_SendNum_Uint8(USART_TypeDef *pUSARTx, uint8_t num){if (num == 0){Usart_SendByte(pUSARTx,'0');return;}char dat[4];dat[3] = '\0';uint8_t i = 0;while(num>0){uint8_t n = num%10;if(n==0) dat[2-i] = '0';else if(n==1) dat[2-i] = '1';else if(n==2) dat[2-i] = '2';else if(n==3) dat[2-i] = '3';else if(n==4) dat[2-i] = '4';else if(n==5) dat[2-i] = '5';else if(n==6) dat[2-i] = '6';else if(n==7) dat[2-i] = '7';else if(n==8) dat[2-i] = '8';else if(n==9) dat[2-i] = '9';i++;num/=10;}uint8_t a = 3-i;Usart_SendString(pUSARTx,&dat[a]); } void Usart_SendNum_Uint16(USART_TypeDef *pUSARTx, uint16_t num){if (num == 0){Usart_SendByte(pUSARTx,'0');return;}char dat[6];dat[5] = '\0';uint8_t i = 0;while(num>0){uint8_t n = num%10;if(n==0) dat[4-i] = '0';else if(n==1) dat[4-i] = '1';else if(n==2) dat[4-i] = '2';else if(n==3) dat[4-i] = '3';else if(n==4) dat[4-i] = '4';else if(n==5) dat[4-i] = '5';else if(n==6) dat[4-i] = '6';else if(n==7) dat[4-i] = '7';else if(n==8) dat[4-i] = '8';else if(n==9) dat[4-i] = '9';i++;num/=10;}uint8_t a = 5-i;Usart_SendString(pUSARTx,&dat[a]); } void Usart_SendNum_Uint32(USART_TypeDef *pUSARTx, uint32_t num){if (num == 0){Usart_SendByte(pUSARTx,'0');return;}char dat[11];dat[10] = '\0';uint8_t i = 0;while(num>0){uint8_t n = num%10;if(n==0) dat[9-i] = '0';else if(n==1) dat[9-i] = '1';else if(n==2) dat[9-i] = '2';else if(n==3) dat[9-i] = '3';else if(n==4) dat[9-i] = '4';else if(n==5) dat[9-i] = '5';else if(n==6) dat[9-i] = '6';else if(n==7) dat[9-i] = '7';else if(n==8) dat[9-i] = '8';else if(n==9) dat[9-i] = '9';i++;num/=10;}uint8_t a = 10-i;Usart_SendString(pUSARTx,&dat[a]); } void Usart_SendNum_Double(USART_TypeDef *pUSARTx, double num){int32_t num1 = floor(num);int32_t num2 = floor((num-num1)*1000);Usart_SendNum_Int32(pUSARTx,num1);Usart_SendByte(pUSARTx,'.');Usart_SendNum_Int32(pUSARTx,num2); } void Usart_SendNum_Int8(USART_TypeDef *pUSARTx, int8_t num){if(num>=0) Usart_SendNum_Uint8(pUSARTx,num);else{if(num == -128){Usart_SendString(pUSARTx,"-128");}else{num = 0 - num;Usart_SendByte(pUSARTx,'-');Usart_SendNum_Uint8(pUSARTx,num);}} } void Usart_SendNum_Int16(USART_TypeDef *pUSARTx, int16_t num){if(num>=0) Usart_SendNum_Uint16(pUSARTx,num);else{if(num == -32768){Usart_SendString(pUSARTx,"-32768");}else{num = 0 - num;Usart_SendByte(pUSARTx,'-');Usart_SendNum_Uint16(pUSARTx,num);}} } void Usart_SendNum_Int32(USART_TypeDef *pUSARTx, int32_t num){if(num>=0) Usart_SendNum_Uint32(pUSARTx,num);else{if(num == -2147483648){Usart_SendString(pUSARTx,"-2147483648");}else{num = 0 - num;Usart_SendByte(pUSARTx,'-');Usart_SendNum_Uint32(pUSARTx,num);}} }修改main函數
官方的實例代碼已經讀出了放大10倍的溫濕度數據,在main.c函數中找到對應的位置,在讀到的數據后面使用串口通信代碼,將溫濕度除以10之后將數據發送給串口助手。
代碼如下:
int32_t main(void) {Usart_1_Config();//配置Usart1uint32_t CT_data[2];volatile int c1,t1;/***********************************************************************************//**///上電初始化SDA,SCL的IO口/***********************************************************************************/Init_I2C_Sensor_Port();/***********************************************************************************//**///①剛上電,產品芯片內部就緒需要時間,延時100~500ms,建議500ms/***********************************************************************************/Delay_1ms(500);/***********************************************************************************//**///②上電第一次發0x71讀取狀態字,判斷狀態字是否為0x18,如果不是0x18,進行寄存器初始化/***********************************************************************************/if((AHT20_Read_Status()&0x18)!=0x18){AHT20_Start_Init(); //重新初始化寄存器Delay_1ms(10);}/***********************************************************************************//**///③根據客戶自己需求發測量命令讀取溫濕度數據,當前while(1)循環發測量命令讀取溫濕度數據,僅供參考/***********************************************************************************/while(1){AHT20_Read_CTdata_crc(CT_data);if(CT_data[0]==0xff&&CT_data[1]==0xff){Usart_SendString(USART1,"CRC檢驗未通過!");}else{c1 = CT_data[0]*100*10/1024/1024; //計算得到濕度值c1(放大了10倍)t1 = CT_data[1]*200*10/1024/1024-500;//計算得到溫度值t1(放大了10倍)//下一步客戶處理顯示數據,Usart_SendString(USART1,"溫度:");Usart_SendNum_Double(USART1,t1/10.0,1);Usart_SendByte(USART1,'\n');Usart_SendString(USART1,"濕度:");Usart_SendNum_Double(USART1,c1/10.0,1);Usart_SendByte(USART1,'\n');Usart_SendString(USART1,"數據通過檢驗!\n");Usart_SendByte(USART1,'\n');}Delay_1ms(2000);}}實驗現象
查看SDA,SCL對應的GPIO口,將AHT20芯片的VCC,SDA,GND,SCL四個引腳與芯片對應的引腳在面包板上連接好,連接好硬件后燒錄程序到芯片中,打開串口助手,發現接收到溫度與濕度數據。
根據上圖的函數查看自己下的代碼使用的哪個GPIO口。
總結
本實驗實現了使用AHT20傳感器收集溫濕度數據,并發送到串口助手。涉及到I2C與Usart配置。為了完成這個實驗,我將Usart模塊化,制作了輸出中文,浮點數,數字的函數,鞏固了之前所學習的知識。此外還通過AHT20的官方例子學習了I2C的應用。
參考資料
-
https://blog.csdn.net/qq_43279579/article/details/111597278
-
《零死角玩轉STM32——F103指南者》
-
《AHT20產品手冊》
總結
- 上一篇: 方便面又好卖了!康师傅天猫618狂增10
- 下一篇: 大厂的人自带光环,但光环是从哪儿来的呢?