STM32通过I2C接口采集温湿度
目錄
- 一、I2C總線協議
- 1. I2C總線簡介
- 1.1 物理接口
- 1.2 通訊特征
- 1.3 I2C總線狀態
 
- 2. I2C總線通信協議
- 2.1 起始位和結束位
- 2.2 數據格式與應答
- 2.3 數據傳輸通訊
 
- 3.硬件I2C和軟件I2C區別
 
- 二、溫濕度采集代碼編寫
- 1. 了解AHT20芯片的相關信息
- 2. 具體代碼添加過程
- 3. 主要代碼
 
- 三、總結
- 參考資料
一、I2C總線協議
1. I2C總線簡介
I2C是Inter-Integrated Circuit的簡稱,讀作:I-squared-C。由飛利浦公司于1980年代提出,為了讓主板、嵌入式系統或手機用以連接低速周邊外部設備而發展。
 主要用途:
 SOC和周邊外設間的通信(如:EEPROM,電容觸摸芯片,各種Sensor等)。
1.1 物理接口
I2C總線只使用兩條雙向漏極開路的信號線(串行數據線:SDA,及串行時鐘線:SCL),并利用電阻上拉。I2C總線僅僅使用SCL、SDA兩根信號線,就實現了設備間的數據交互,極大地簡化了對硬件資源和PCB板布線空間的占用。I2C總線廣泛應用在EEPROM、實時時鐘、LCD、及其他芯片的接口。I2C允許相當大的工作電壓范圍,典型的電壓基準為:+3.3V或+5V。
SCL(Serial Clock):串行時鐘線,傳輸CLK信號,一般是主設備向從設備提供
 SDA(Serial Data):串行數據線,傳輸通信數據
I2C總線接口內部結構如下圖所示:
 
I2C使用一個7bit的設備地址,一組總線最多和112個節點通信。最大通信數量受限于地址空間及400pF的總線電容。
常見的I2C總線以傳輸速率的不同分為不同的模式:標準模式(100Kbit/s)、低速模式(10Kbit/s)、快速模式(400Kbit/s)、高速模式(3.4Mbit/s),時鐘頻率可以被下降到零,即暫停通信。
該總線是一種多主控總線,即可以在總線上放置多個主設備節點,在停止位(P)發出后,即通訊結束后,主設備節點可以成為從設備節點。
主設備節點:產生時鐘并發起通信的設備節點
 從設備節點:接收時鐘并響應主設備節點尋址的設備節點
1)I2C通信雙方地位不對等,通信由主設備發起,并主導傳輸過程,從設備按I2C協議接收主設備發送的數據,并及時給出響應。
 2)主設備、從設備由通信雙方決定(I2C協議本身無規定),既能當主設備,也能當從設備(需要軟件進行配置)。
 3)主設備負責調度總線,決定某一時刻和哪個從設備通信。同一時刻,I2C總線上只能有一對主設備、從設備通信。
 4)每個I2C從設備在I2C總線通訊中有一個I2C從設備地址,該地址唯一,是從設備的固有屬性,通信中主設備通過從設備地址來找到從設備。
I2C總線多主設備結構如下圖所示:
 
1.2 通訊特征
串行、同步、非差分、低速率
1)串行通信,所有的數據以位為單位在SDA線上串行傳輸
 2)同步通信,即雙方工作在同一個時鐘下,一般是通信的A方通過一根CLK信號線,將A設備的時鐘傳輸到B設備,B設備在A設備傳輸的時鐘下工作。同步通信的特征是:通信線中有CLK。
 3)非差分,I2C通信速率不高,且通信距離近,使用電平信號通信。
 4)低速率,I2C一般是同一個板子上的兩個IC芯片間通信,數據量不大,速率低。速率:幾百KHz,速率可能不同,不能超過IC的最高速率。
1.3 I2C總線狀態
I2C總線上有兩種狀態:
空閑態:沒有設備發生通信。
 忙態:其中一個從設備和主設備通信,I2C總線被占用,其他從設備處于等待狀態。
2. I2C總線通信協議
**時序:**在通信中時序是通信線上按時間順序發生的電平變化,及這些電平變化對通信的意義。
每個通信周期都由一個起始位開始通信,由一個結束位結束通信,中間部分是傳遞的數據。
每個通信周期,主設備會先發8位的從設備地址(從設備地址由高7位的實際從設備地址和低1位的讀/寫標志位組成),主設備以廣播的形式發送從設備地址,I2C總線上的所有從設備收到地址后,判斷從設備地址是否匹配,不匹配的從設備繼續等待,匹配的設備發出一個應答信號。
同一時刻,主設備、從設備只能有一個設備發送數據。
2.1 起始位和結束位
I2C總線通訊由起始位開始通訊,由結束位停止通訊,并釋放I2C總線。起始位和結束位都由主設備發出。
 起始位(S):在SCL為高電平時,SDA由高電平變為低電平
 結束位(P):在SCL為高電平時,SDA由低電平變為高電平
如下圖所示:
 
2.2 數據格式與應答
I2C數據以字節(即8bits)為單位傳輸,每個字節傳輸完后都會有一個ACK應答信號。應答信號的時鐘是由主設備產生的。
應答(ACK):拉低SDA線,并在SCL為高電平期間保持SDA線為低電平
 非應答(NOACK):不要拉低SDA線(此時SDA線為高電平),并在SCL為高電平期間保持SDA線為高電平
在傳輸期間,如果從設備來不及處理主設備發送的數據,從設備會保持SCL線為低電平,強迫主設備等待從設備釋放SCL線,直到從設備處理完后,釋放SCL線,接著進行數據傳輸。
如下圖所示:
 
2.3 數據傳輸通訊
寫數據
 開始數據傳輸后,先發送一個起始位(S),主設備發送一個地址數據(由7bit的從設備地址,和最低位的寫標志位組成的8bit字節數據,該讀寫標志位決定數據的傳輸方向),然后,主設備釋放SDA線,并等待從設備的應答信號(ACK)。每一個字節數據的傳輸都要跟一個應答信號位。數據傳輸以停止位(P)結束,并且釋放I2C總線。
讀數據
 開始通訊時,主設備先發送一個起始信號(S),主設備發送一個地址數據(由7bit的從設備地址,和最低位的寫標志位組成的8bit字節數據),然后,主設備釋放SDA線,并等待從設備的應答信號(ACK),從設備應答主設備后,主設備再發送要讀取的寄存器地址,從設備應答主設備(ACK),主設備再次發送起始信號(Sr),主設備發送設備地址(包含讀標志),從設備應答主設備,并將該寄存器的值發送給主設備;
讀取單字節數據:
 主設備要讀取的數據,如果是只有一個字節的數值,就要結束應答,主設備要先發送一個非應答信號(NOACK),再發送結束信號(P);
 讀取多字節數據:
 主設備要讀取的數據,如果是大于一個字節的多個數據,就發送ACK應答信號(ACK),而不是非應答信號(NOACK),然后主設備再次接收從設備發送的數據,依次類推,直到主設備讀取的數值是最后一個字節數據后,需要主設備給從設備發送非應答信號(NOACK),再發送結束信號(P),結束I2C通訊,并釋放I2C總線。
注意:所有的數據傳輸過程中,SDA線的電平變化必須在SCL為低電平時進行,SDA線的電平在SCL線為高電平時要保持穩定不變。如下圖所示:
3.硬件I2C和軟件I2C區別
所謂硬件I2C對應芯片上的I2C外設,有相應I2C驅動電路,其所使用的I2C管腳也是專用的;軟件I2C一般是用GPIO管腳,用軟件控制管腳狀態以模擬I2C通信波形。
硬件I2C的效率要遠高于軟件的,而軟件I2C由于不受管腳限制,接口比較靈活。
模擬I2C 是通過GPIO,軟件模擬寄存器的工作方式,而硬件(固件)I2C是直接調用內部寄存器進行配置。如果要從具體硬件上來看,可以去看下芯片手冊。因為固件I2C的端口是固定的,所以會有所區別。
至于如何區分它們
可以看底層配置,比如IO口配置,如果配置了IO口的功能(I2C功能)那就是固件I2C,否則就是模擬
 可以看I2C寫函數,看里面有木有調用現成的函數或者給某個寄存器賦值,如果有,則肯定是固件I2C功能,沒有的話肯定是數據一個bit一個bit模擬發生送的,肯定用到了循環,則為模擬。
 根據代碼量判斷,模擬的代碼量肯定比固件的要大。
硬件I2C用法比較復雜,模擬I2C的流程更清楚一些。
硬件I2C速度比模擬快,并且可以用DMA
模擬I2C可以在任何管腳上,而硬件只能在固定管腳上。
軟件i2c是程序員使用程序控制SCL,SDA線輸出高低電平,模擬i2c協議的時序。一般較硬件i2c穩定,但是程序較為繁瑣,但不難。
硬件i2c程序員只要調用i2c的控制函數即可,不用直接的去控制SCL,SDA高低電平的輸出。但是有些單片機的硬件i2c不太穩定,調試問題較多。
二、溫濕度采集代碼編寫
1. 了解AHT20芯片的相關信息
具體信息請到官方下載對應產品介紹文檔,資料鏈接如下
 http://www.aosong.com/class-36.html
2. 具體代碼添加過程
在野火提供的示例代碼中,打開一個只包含固件庫的空項目。向工程中添加相關代碼,添加代碼的具體內容請參考下面鏈接:
 https://blog.csdn.net/hhhhhh277523/article/details/111397514
3. 主要代碼
main.c:
int main(void) { delay_init(); //初始化 uart_init(115200); //波特率IIC_Init();while(1){printf("溫度濕度顯示");read_AHT20_once();delay_ms(2000);} }bsp_i2c.c:
uint8_t ack_status=0; uint8_t readByte[6]; uint8_t AHT20_status=0;uint32_t H1=0; //Humility uint32_t T1=0; //Temperatureuint8_t AHT20_OutData[4]; uint8_t AHT20sendOutData[10] = {0xFA, 0x06, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF};void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);IIC_SCL=1;IIC_SDA=1;}void IIC_Start(void) {SDA_OUT(); IIC_SDA=1; IIC_SCL=1;delay_us(4);IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4);IIC_SCL=0; } void IIC_Stop(void) {SDA_OUT();//sda??ê?3?IIC_SCL=0;IIC_SDA=0;//STOP:when CLK is high DATA change form low to highdelay_us(4);IIC_SCL=1; IIC_SDA=1;delay_us(4); }u8 IIC_Wait_Ack(void) {u8 ucErrTime=0;SDA_IN(); IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA){ucErrTime++;if(ucErrTime>250){IIC_Stop();return 1;}}IIC_SCL=0;return 0; } void IIC_Ack(void) {IIC_SCL=0;SDA_OUT();IIC_SDA=0;delay_us(2);IIC_SCL=1;delay_us(2);IIC_SCL=0; }void IIC_NAck(void) {IIC_SCL=0;SDA_OUT();IIC_SDA=1;delay_us(2);IIC_SCL=1;delay_us(2);IIC_SCL=0; } void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;for(t=0;t<8;t++){ IIC_SDA=(txd&0x80)>>7;txd<<=1; delay_us(2); IIC_SCL=1;delay_us(2); IIC_SCL=0; delay_us(2);} } u8 IIC_Read_Byte(unsigned char ack) {unsigned char i,receive=0;SDA_IN();//SDAéè???aê?è?for(i=0;i<8;i++ ){IIC_SCL=0; delay_us(2);IIC_SCL=1;receive<<=1;if(READ_SDA)receive++; delay_us(1); } if (!ack)IIC_NAck();elseIIC_Ack(); return receive; }void IIC_WriteByte(uint16_t addr,uint8_t data,uint8_t device_addr) {IIC_Start(); if(device_addr==0xA0)IIC_Send_Byte(0xA0 + ((addr/256)<<1));elseIIC_Send_Byte(device_addr); IIC_Wait_Ack(); IIC_Send_Byte(addr&0xFF); IIC_Wait_Ack(); IIC_Send_Byte(data); IIC_Wait_Ack(); IIC_Stop();if(device_addr==0xA0) //delay_ms(10);elsedelay_us(2); }uint16_t IIC_ReadByte(uint16_t addr,uint8_t device_addr,uint8_t ByteNumToRead) //?á??′??÷?ò?áêy?Y { uint16_t data;IIC_Start(); if(device_addr==0xA0)IIC_Send_Byte(0xA0 + ((addr/256)<<1));elseIIC_Send_Byte(device_addr); IIC_Wait_Ack();IIC_Send_Byte(addr&0xFF); IIC_Wait_Ack(); IIC_Start(); IIC_Send_Byte(device_addr+1); IIC_Wait_Ack();if(ByteNumToRead == 1){data=IIC_Read_Byte(0);}else{data=IIC_Read_Byte(1);data=(data<<8)+IIC_Read_Byte(0);}IIC_Stop(); return data; }//AHT20芯片的使用過程 void read_AHT20_once(void) {delay_ms(10);reset_AHT20();//重置AHT20芯片delay_ms(10);init_AHT20();//初始化AHT20芯片delay_ms(10);startMeasure_AHT20();//開始測試AHT20芯片delay_ms(80);read_AHT20();//讀取AHT20采集的到的數據delay_ms(5); }//重置AHT20芯片 void reset_AHT20(void) {I2C_Start();I2C_WriteByte(0x70);ack_status = Receive_ACK();if(ack_status) printf("1");else printf("1-n-");I2C_WriteByte(0xBA);ack_status = Receive_ACK();if(ack_status) printf("2");else printf("2-n-");I2C_Stop();/*AHT20_OutData[0] = 0;AHT20_OutData[1] = 0;AHT20_OutData[2] = 0;AHT20_OutData[3] = 0;*/ }//初始化AHT20芯片 void init_AHT20(void) {I2C_Start();I2C_WriteByte(0x70);ack_status = Receive_ACK();if(ack_status) printf("3");else printf("3-n-"); I2C_WriteByte(0xE1);ack_status = Receive_ACK();if(ack_status) printf("4");else printf("4-n-");I2C_WriteByte(0x08);ack_status = Receive_ACK();if(ack_status) printf("5");else printf("5-n-");I2C_WriteByte(0x00);ack_status = Receive_ACK();if(ack_status) printf("6");else printf("6-n-");I2C_Stop(); }void startMeasure_AHT20(void) {//------------I2C_Start();I2C_WriteByte(0x70);ack_status = Receive_ACK();if(ack_status) printf("7");else printf("7-n-");I2C_WriteByte(0xAC);ack_status = Receive_ACK();if(ack_status) printf("8");else printf("8-n-");I2C_WriteByte(0x33);ack_status = Receive_ACK();if(ack_status) printf("9");else printf("9-n-");I2C_WriteByte(0x00);ack_status = Receive_ACK();if(ack_status) printf("10");else printf("10-n-");I2C_Stop(); }//AHT20芯片讀取數據 void read_AHT20(void) {uint8_t i;for(i=0; i<6; i++){readByte[i]=0;}I2C_Start();//I2C啟動I2C_WriteByte(0x71);//I2C寫數據ack_status = Receive_ACK();//收到的應答信息readByte[0]= I2C_ReadByte();//I2C讀取數據Send_ACK();//發送應答信息readByte[1]= I2C_ReadByte();Send_ACK();readByte[2]= I2C_ReadByte();Send_ACK();readByte[3]= I2C_ReadByte();Send_ACK();readByte[4]= I2C_ReadByte();Send_ACK();readByte[5]= I2C_ReadByte();SendNot_Ack();//Send_ACK();I2C_Stop();//I2C停止函數//判斷讀取到的第一個字節是不是0x08,0x08是該芯片讀取流程中規定的,如果讀取過程沒有問題,就對讀到的數據進行相應的處理if( (readByte[0] & 0x68) == 0x08 ){H1 = readByte[1];H1 = (H1<<8) | readByte[2];H1 = (H1<<8) | readByte[3];H1 = H1>>4;H1 = (H1*1000)/1024/1024;T1 = readByte[3];T1 = T1 & 0x0000000F;T1 = (T1<<8) | readByte[4];T1 = (T1<<8) | readByte[5];T1 = (T1*2000)/1024/1024 - 500;AHT20_OutData[0] = (H1>>8) & 0x000000FF;AHT20_OutData[1] = H1 & 0x000000FF;AHT20_OutData[2] = (T1>>8) & 0x000000FF;AHT20_OutData[3] = T1 & 0x000000FF;}else{AHT20_OutData[0] = 0xFF;AHT20_OutData[1] = 0xFF;AHT20_OutData[2] = 0xFF;AHT20_OutData[3] = 0xFF;printf("讀取失敗!!!");}printf("\r\n");//根據AHT20芯片中,溫度和濕度的計算公式,得到最終的結果,通過串口顯示printf("溫度:%d%d.%d",T1/100,(T1/10)%10,T1%10);printf("濕度:%d%d.%d",H1/100,(H1/10)%10,H1%10);printf("\r\n"); }uint8_t Receive_ACK(void) {uint8_t result=0;uint8_t cnt=0;IIC_SCL = 0;SDA_IN(); delay_us(4);IIC_SCL = 1;delay_us(4);while(READ_SDA && (cnt<100)){cnt++;}IIC_SCL = 0;delay_us(4);if(cnt<100){result=1;}return result; }void Send_ACK(void) {SDA_OUT();IIC_SCL = 0;delay_us(4);IIC_SDA = 0;delay_us(4);IIC_SCL = 1;delay_us(4);IIC_SCL = 0;delay_us(4);SDA_IN(); }void SendNot_Ack(void) {SDA_OUT();IIC_SCL = 0;delay_us(4);IIC_SDA = 1;delay_us(4);IIC_SCL = 1;delay_us(4);IIC_SCL = 0;delay_us(4);IIC_SDA = 0;delay_us(4); }void I2C_WriteByte(uint8_t input) {uint8_t i;SDA_OUT();for(i=0; i<8; i++){IIC_SCL = 0;delay_ms(5);if(input & 0x80){IIC_SDA = 1;//delaymm(10);}else{IIC_SDA = 0;//delaymm(10);}IIC_SCL = 1;delay_ms(5);input = (input<<1);}IIC_SCL = 0;delay_us(4);SDA_IN();delay_us(4); } uint8_t I2C_ReadByte(void) {uint8_t resultByte=0;uint8_t i=0, a=0;IIC_SCL = 0;SDA_IN();delay_ms(4);for(i=0; i<8; i++){IIC_SCL = 1;delay_ms(3);a=0;if(READ_SDA){a=1;}else{a=0;}//resultByte = resultByte | a;resultByte = (resultByte << 1) | a;IIC_SCL = 0;delay_ms(3);}SDA_IN();delay_ms(10);return resultByte; }void set_AHT20sendOutData(void) {/* --------------------------* 0xFA 0x06 0x0A temperature(2 Bytes) humility(2Bytes) short Address(2 Bytes)* And Check (1 byte)* -------------------------*/AHT20sendOutData[3] = AHT20_OutData[0];AHT20sendOutData[4] = AHT20_OutData[1];AHT20sendOutData[5] = AHT20_OutData[2];AHT20sendOutData[6] = AHT20_OutData[3];// AHT20sendOutData[7] = (drf1609.shortAddress >> 8) & 0x00FF; // AHT20sendOutData[8] = drf1609.shortAddress & 0x00FF;// AHT20sendOutData[9] = getXY(AHT20sendOutData,10); }void I2C_Start(void) {SDA_OUT();IIC_SCL = 1;delay_ms(4);IIC_SDA = 1;delay_ms(4);IIC_SDA = 0;delay_ms(4);IIC_SCL = 0;delay_ms(4); }void I2C_Stop(void) {SDA_OUT();IIC_SDA = 0;delay_ms(4);IIC_SCL = 1;delay_ms(4);IIC_SDA = 1;delay_ms(4); }線路連接:
 由于本程序采用的軟件I2C實現的,采用GPIO引腳是PB6,PB7。具體定義代碼如下
 #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
 #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
 #define IIC_SCL PBout(6) //SCL
 #define IIC_SDA PBout(7) //SDA
 #define READ_SDA PBin(7)
 所以,SCL連接PB6,SDA連接PB7。
boot0置1,boot1置0燒寫代碼,boot0置0,boot1置1 讀取溫濕度數據:
 
三、總結
對于I2C協議進行通信的一個過程有了一個基本的理解。雖然讀出了溫濕度,但與實際的溫度還是有差異,不是很準確,但加溫時能讀出溫度在升高,證明芯片在正確工作中。
參考資料
I2C總線通訊協議
 stm32通過I2C接口實現溫濕度(AHT20)的采集
 
 https://blog.csdn.net/qq_31860135/article/details/88658136
總結
以上是生活随笔為你收集整理的STM32通过I2C接口采集温湿度的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 常用的dns地址分享
- 下一篇: 测功机TN曲线
