一、什么是I2C
I²C(Inter-Integrated Circuit)是內部整合電路的稱呼,是一種串行通訊總線,使用多主從架構,由飛利浦公司在1980年代為了讓主板、嵌入式系統或手機用以連接低速周邊裝置而發展。I²C(讀作"I-squared-C" ),還有可選的拼寫方式是I2C(讀作I-two-C)以及IIC(讀作I-I-C),在中國則多以"I方C"稱之。
I2C(Inter-Integrated Circuit)總線是由PHILIPS公司開發的兩線式串行總線,用於連接微控制器及其外圍設備。是微電子通信控制領域廣泛采用的一種總線標准。它是同步通信的一種特殊形式,具有接口線少,控制方式簡單,器件封裝形式小,通信速率較高等優點。I2C 總線支持任何IC 生產過程(CMOS、雙極性)。通過串行數據(SDA)線和串行時鍾 (SCL)線在連接到總線的器件間傳遞信息。每個器件都有一個唯一的地址識別(無論是微控制器——MCU、LCD 驅動器、存儲器或鍵盤接口),而且都可以作為一個發送器或接收器(由器件的功能決定)。LCD 驅動器只能作為接收器,而存儲器則既可以接收又可以發送數據。除了發送器和接收器外,器件在執行數據傳輸時也可以被看作是主機或從機(見表1)。主機是初始化總線的數據傳輸並產生允許傳輸的時鍾信號的器件。此時,任何被尋址的器件都被認為是從機。
特征:
- 只要求兩條總線線路:一條串行數據線SDA,一條串行時鍾線SCL;
- 每個連接到總線的器件都可以通過唯一的地址和一直存在的簡單的主機/從機關系軟件設定地址,主機可以作為主機發送器或主機接收器;
- 它是一個真正的多主機總線,如果兩個或更多主機同時初始化,數據傳輸可以通過沖突檢測和仲裁防止數據被破壞;
- 串行的8 位雙向數據傳輸位速率在標准模式下可達100kbit/s,快速模式下可達400kbit/s,高速模式下可達3.4Mbit/s;
- 連接到相同總線的IC 數量只受到總線的最大電容400pF 限制。
1.2 軟件I2C和硬件I2C的區別
所謂硬件I2C對應芯片上的I2C外設,有相應I2C驅動電路,其所使用的I2C管腳也是專用的;軟件I2C一般是用GPIO管腳,用軟件控制管腳狀態以模擬I2C通信波形。
硬件I2C的效率要遠高於軟件的,而軟件I2C由於不受管腳限制,接口比較靈活。
模擬I2C 是通過GPIO,軟件模擬寄存器的工作方式,而硬件(固件)I2C是直接調用內部寄存器進行配置。如果要從具體硬件上來看,可以去看下芯片手冊。因為固件I2C的端口是固定的,所以會有所區別。
至於如何區分它們
可以看底層配置,比如IO口配置,如果配置了IO口的功能(IIC功能)那就是固件IIC,否則就是模擬
可以看IIC寫函數,看里面有木有調用現成的函數或者給某個寄存器賦值,如果有,則肯定是固件IIC功能,沒有的話肯定是數據一個bit一個bit模擬發生送的,肯定用到了循環,則為模擬。
根據代碼量判斷,模擬的代碼量肯定比固件的要大。
- 硬件IIC用法比較復雜,模擬IIC的流程更清楚一些。
- 硬件IIC速度比模擬快,並且可以用DMA
- 模擬IIC可以在任何管腳上,而硬件只能在固定管腳上。
軟件i2c是程序員使用程序控制SCL,SDA線輸出高低電平,模擬i2c協議的時序。一般較硬件i2c穩定,但是程序較為繁瑣,但不難。
硬件i2c程序員只要調用i2c的控制函數即可,不用直接的去控制SCL,SDA高低電平的輸出。但是有些單片機的硬件i2c不太穩定,調試問題較多。
軟硬I2C的比較
通信速度
相對來說,即使兩者的通信速率設置成相等時,硬件I2C的通信速度要比軟件I2C的速度要快。因為硬件I2C是通過片上外設實現的通信,CPU只需要去讀寫寄存器數據就可以進行I2C通信,程序較為簡單並且占用的資源少。而軟件I2C則需要程序模擬I2C時序,程序較為復雜且占用資源較多。
可移植性
對於硬件I2C來說,由於芯片I2C外設的IO口已經確定,無法隨意更改其他IO口,因而可移植性較差;但是由於軟件I2C是通過IO口模擬I2C通信時序實現的通信,因而可移植性比較好,在任何單片機上都可以使用,只需要修改一下通信時間以及配置好IO口就可以實現I2C通信。
穩定性
一般來說,軟件模擬I2C穩定性要比硬件I2C更加穩定,硬件I2C不穩定,容易卡死,想要寫得穩定程序就非常復雜;但是軟件I2C可能會因為中斷的影響造成數據讀取不准確。
二、AHT20傳輸數據
2.1基本要求
- 每隔2秒鍾采集一次溫濕度數據
- 通過串口發送到上位機(windows10)
2.2主要代碼
#include "led.h" #include "delay.h" #include "temhum.h" #include "sys.h" #include "usart.h" int main(void) { u32 CT_data[2]={0}; volatile float hum=0,tem=0; delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級 uart_init(115200); //串口初始化為115200 LED_Init(); //LED端口初始化 temphum_init(); //ATH20初始化 while(1) { AHT20_Read_CTdata(CT_data); //不經過CRC校驗,直接讀取AHT20的溫度和濕度數據 hum = CT_data[0]*100*10/1024/1024; //計算得到濕度值(放大了10倍) tem = CT_data[1]*200*10/1024/1024-500;//計算得到溫度值(放大了10倍) printf("濕度:%.1f%%\r\n",(hum/10)); printf("溫度:%.1f度\r\n",(tem/10)); printf("\r\n"); //延時2s,LED閃爍提示串口發送狀態 LED=0; delay_ms(1000); LED=1; delay_ms(1000); } }
引腳連接的一些細節:
完整項目代碼:https://github.com/LinZJ0423/STM32
完整項目代碼放在STM32_AHT20.zip內。
結果展示:
可以在圖中看出 芯片正在采集數據,在當用手捂住芯片的時候溫度和濕度都在上升,說明芯片在正常工作且沒有出現異常。
三、 基於SPI的OLED通信
3.1SPI的簡介
SPI是串行外設接口(Serial Peripheral Interface)的縮寫,是由 Motorola 公司提出的一種高速的,全雙工,同步的通信總線,被廣泛地使用在 ADC、LCD 等設備與 MCU 間要求通訊速率較高的場合。SPI總線系統可直接與各個廠家生產的多種標准外圍器件連接,該接口一般使用4條線:串行時鍾線(SCK)、主機輸入/從機輸出數據線MISO、主機輸出/從機輸入數據線MOST和低電平有效的從機選擇線C/S(有的SPI接口芯片帶有中斷信號線INT或INT、有的SPI接口芯片沒有主機輸出/從機輸入數據線MOSI)。
3.2SPI時序
上圖中的時序只是 SPI 其中一種通訊模式,SPI 一共有四種通訊模式,它們的主要區別是總線空閑時 SCK 的時鍾狀態以及數據采樣時刻。為方便說明,在此引入“時鍾極性 CPOL”和“時鍾相位 CPHA”的概念。
時鍾極性 CPOL 是指 SPI 通訊設備處於空閑狀態時,SCK 信號線的電平信號(即 SPI 通訊開始前、 NSS 線為高電平時 SCK 的狀態)。CPOL=0 時, SCK 在空閑狀態時為低電平,CPOL=1 時,則相反。
時鍾相位 CPHA 是指數據的采樣的時刻,當 CPHA=0 時,MOSI 或 MISO 數據線上的信號將會在 SCK 時鍾線的“奇數邊沿”被采樣。當 CPHA=1 時,數據線在 SCK 的“偶數邊沿”采樣。
3.3OLED簡介
OLED(OrganicLight-Emitting Diode),又稱為有機電激光顯示、有機發光半導體(OrganicElectroluminesence Display,OLED)。OLED屬於一種電流型的有機發光器件,是通過載流子的注入和復合而致發光的現象,發光強度與注入的電流成正比。OLED在電場的作用下,陽極產生的空穴和陰極產生的電子就會發生移動,分別向空穴傳輸層和電子傳輸層注入,遷移到發光層。當二者在發光層相遇時,產生能量激子,從而激發發光分子最終產生可見光。
3.4實際操作
基本要求:
- 顯示自己的學號和姓名;
- 顯示AHT20的溫度和濕度;
- 上下或左右的滑動顯示(使用硬件刷屏模式)
主要程序
#include "delay.h" #include "sys.h" #include "oled.h" #include "gui.h" #include "test.h" #include "temhum.h" int main(void) { u8 i; delay_init(); //延時函數初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); AHT20_Init(); OLED_Init(); //初始化OLED OLED_Clear(0); //清屏(全黑) GUI_ShowCHinese(0,20,32,"通信二班",1); OLED_Display_scroll(); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); delay_ms(1000); while(1) { TEST_Chinese(); //學號、姓名顯示 OLED_Clear(0); for(i=0;i<5;i++) { TEST_Menu2(); //AHT20溫濕度顯示 delay_ms(500); } OLED_Clear(0); } }
OLED主要函數
//OLED控制用函數 void OLED_WR_Byte(unsigned dat,unsigned cmd); void OLED_Display_On(void); void OLED_Display_Off(void); void OLED_Set_Pos(unsigned char x, unsigned char y); void OLED_Reset(void); void OLED_Init_GPIO(void); void OLED_Init(void); void OLED_Set_Pixel(unsigned char x, unsigned char y,unsigned char color); void OLED_Display(void); void OLED_Clear(unsigned dat); //OLED滾動顯示 void OLED_Display_scroll(void);
接線說明:
void OLED_Display_scroll(void) { OLED_WR_Byte(0x2e,OLED_CMD);//關滾動 OLED_WR_Byte(0x2a,OLED_CMD);//29向右,2a向左(垂直水平滾動) OLED_WR_Byte(0x00,OLED_CMD);//A:空字節 OLED_WR_Byte(0x00,OLED_CMD);//B:水平起始頁 OLED_WR_Byte(0x00,OLED_CMD);//C:水平滾動速度 OLED_WR_Byte(0x07,OLED_CMD);//D:水平結束頁 OLED_WR_Byte(0x01,OLED_CMD);//E:每次垂直滾動位移 OLED_WR_Byte(0x2f,OLED_CMD);//開滾動 }
void TEST_Chinese(void) { OLED_Clear(0); GUI_ShowString(50,50,"***",8,1); //此處顯示學號 GUI_ShowCHinese(10,10,32,"***",1); //此處顯示姓名 delay_ms(1000); OLED_Clear(0); }
顯示溫濕度:
void TEST_Menu2(void) { u32 CT_data[2]; volatile int c1=0,t1=0; srand(123456); delay_ms(40); AHT20_Read_CTdata(CT_data); //不經過CRC校驗,直接讀取AHT20的溫度和濕度數據 推薦每隔大於1S讀一次 c1 = CT_data[0]*100*10/1024/1024; //濕度 t1 = CT_data[1]*200*10/1024/1024-500; //溫度 GUI_DrawLine(0, 10, WIDTH-1, 10,1); GUI_DrawLine(WIDTH/2-1,11,WIDTH/2-1,HEIGHT-1,1); GUI_DrawLine(WIDTH/2-1,10+(HEIGHT-10)/2-1,WIDTH-1,10+(HEIGHT-10)/2-1,1); GUI_ShowString(0,1,"2020-12-23",8,1); GUI_ShowString(74,1,"Wednesday",8,1); GUI_ShowString(14,HEIGHT-1-10,"Cloudy",8,1); GUI_ShowString(WIDTH/2+1,13,"TEMP",8,1); //顯示溫度 GUI_DrawCircle(WIDTH-20, 25, 1,2); GUI_ShowString(WIDTH-15,20,"C",16,1); GUI_ShowNum(WIDTH/2+8,20,t1/10,2,16,1); GUI_ShowString(WIDTH-41,26,".",8,1); GUI_ShowNum(WIDTH-35,20,t1%10,1,16,1); GUI_ShowString(WIDTH/2+1,39,"HUM",8,1); //顯示濕度 GUI_ShowNum(WIDTH/2+8,46,c1/10,2,16,1); GUI_ShowString(WIDTH-41,52,".",8,1); GUI_ShowNum(WIDTH-35,46,c1%10,1,16,1); GUI_ShowString(WIDTH-21,46,"%",16,1); GUI_DrawBMP(6,16,51,32, BMP5, 1); delay_ms(1000); }
滑動顯示字符:
void OLED_Display_scroll(void) { OLED_WR_Byte(0x2e,OLED_CMD);//關滾動 OLED_WR_Byte(0x2a,OLED_CMD);//29向右,2a向左(垂直水平滾動) OLED_WR_Byte(0x00,OLED_CMD);//A:空字節 OLED_WR_Byte(0x00,OLED_CMD);//B:水平起始頁 OLED_WR_Byte(0x00,OLED_CMD);//C:水平滾動速度 OLED_WR_Byte(0x07,OLED_CMD);//D:水平結束頁 OLED_WR_Byte(0x01,OLED_CMD);//E:每次垂直滾動位移 OLED_WR_Byte(0x2f,OLED_CMD);//開滾動 }
字庫取模
由於OLED顯示中使用了中文字符,因此需要將中文進行取模得到中文的點陣編碼並存到oledfont.h中,方便程序調用並顯示到OLED上去。
下面演示如何使用漢字取模軟件進行漢字取模:
我們利用一些工具生成字模,他會在下方顯示字形碼,同時需要注意一些設置
與圖中相同即可。
在gui.c中找到oledfont.h的頭文件,如圖
在上述函數中放入你需要顯示的中文的字形碼,我們的准備工作就完成了。
同時完整的項目代碼在前面的github地址中,名為ISP.zip的壓縮包中。
四、結果展示
顯示了我們需要的溫濕度,滾動屏,以及姓名和學號。
五、心得體會
這是一次非常不容易的實驗,在實驗室做實驗時就做了很多的調試,最后磕磕碰碰還是顯示出來了。課后做這個題目的時候一開始不懂得如何將兩個項目合並,參考了許多的博客,關於代碼的部分還是一知半解,雖然做完了作業但是還是會繼續學習代碼的流程,雖然圖中展示的結果或許十分簡單,但是要做出來還是十分困難。
六、參考地址
https://blog.csdn.net/qq_45237293/article/details/111712565
https://blog.csdn.net/qq_43279579/article/details/111414037