目錄
IIC協議簡介
IIC總線系統結構?
IIC總線物理層特點
?IIC總線協議層
空閑狀態
應答信號?
數據的有效性?
數據傳輸
STM32的IIC特性及架構
STM32的IIC結構體?
0.96寸OLED顯示屏
?SSD1306框圖及引腳定義
4針腳I2C接口模塊原理圖?
字節傳輸-I2C?
執行邏輯框圖
命令表
硬件I2C的配置
軟件I2C的配置
溫濕度傳感器
OELD顯示溫濕度項目
?
IIC協議簡介
IIC協議協議簡介
?????????????????????? IIC通訊協議(Inter----Integrted Circuit)是由Phiips飛利浦公司開發的,
由于他引腳少,硬件實現簡單,可拓展性強,不需要UASRT,CAN通訊協議的外部收發設備,現在被廣泛使用在系統內多個集成電路IC(芯片)間的通訊。
半雙工的通訊方式
引腳很少 SDA SCL一個數據一個時鐘,和串口相比要一個芯片轉換
? ? ? ? ? ? ?
IIC總線系統結構?
???????? 他是一個支持多設備的總線。”總線”指多個設備共用的信號線,在一個IIC通訊總線中,可連接多個IIC通訊設備,支持多個通訊主機及多個通訊從機。主機就是MCU從機就是外部設備
????????? 一個IIC總線只使用兩條總線線路,一條雙向串行數據線(SDA),一條串行時鐘線(SCL)。數據線即用來表示數據,時鐘線用于數據收發同步。
??????? 每個連接到總線的設備都有一個獨立的地址,主機可以利用這個地址進行不同設備直接的訪問。
IIC總線物理層特點
總線通過上拉電阻接到電源。當IIC設備空閑時,會輸出高阻態,而當所有設備都空閑,都輸出高阻態,由上拉電阻把總線拉成高電平。
??????? 多個主機同時使用總線時,為了防止數據沖突,會利用仲裁方式決定哪個設備占用總線。仲裁相當于中斷優先級
?????? 具有三種傳輸模式:標準模式傳輸速率為100kbit/s,快速模式為400kbit/s,高速模式下可達3.4M/s,但目前大多IIC設備尚不支持高速模式。
?IIC總線
硬件IIC:對應芯片上的IIC外設,有相對應的IIC驅動電路,其所使用的IIC管教也是專用的;
軟件IIC:一般是用GPIO管教,用軟件控制管腳狀態以及模擬IIC通信波形;
區別:硬件IIC的效率要遠高于軟件的,而軟件IIC不受引腳限制,接口比較靈活。
?????????? 軟件IIC是通過GPIO,軟件模擬寄存器的工作方式,而硬件IIC是直接調用內部寄存器進行配置。如果要從具體硬件上來看,可以去看下芯片手冊。因為固件IIC的端口是固定的,所以會有所區別。
如何區分?
1.硬件IIC用法復雜,模擬IIC流程更加清楚
2.硬件IIC速度比模擬快,并且可以用DMA
3.模擬IIC可以在任何管腳上,硬件IIC在固定管腳上
?IIC總線協議層
IIC協議層
?????????????? IIC的協議定義了通訊的起始和停止信號、數據有效性、響應、仲裁、時鐘同步和地址廣播等環節。
1,IIC基本讀寫過程
主機寫數據到從機
?S:數據由主機傳輸至從機? SLAVE ADDRESS : 從機地址?P:數據傳輸結束???
起始信號產生后,所有從機就開始緊接下來廣播的從機地址信號。IIC總線,每個設備的地址都是唯一的,當主機廣播的地址與某個設備的地址相同時,這個設備就被選中了,沒被選中的設備講會忽略之后的數據信號。根據IIC協議,這個從機地址可以是7位或10位。?
地址位之后,傳輸方向RW選擇位,為0:表示數據傳輸方向是由主機傳輸至從機,即主機向從機寫數據。為1:則相反。?從機接收傳輸方向選擇位后,主機或從機會返回一個應答A(ACK)或非應答A/A(NACK)信號,只有接收到應答信號后,主機才能繼續發送或接收數據。
?主機讀數據到從機
讀數據:
配置方向傳輸位為”讀數據”方向。廣播完地址后,接收到應答信號后,從機開始向主機返回數據(DATA),數據包大小也為8位,從機每發送完一個數,都會等待主機的應答信號(ACK),重復這個過程,可以返回N個數據,N沒有限制大小。當主機希望停止接收數據時,就向從機返回一個非應答信號(NCAK),則從機自動停止數據傳輸。
通訊復合格式
?復合格式,該傳輸過程有兩次起始信號(S),在第一次傳輸過程中,主機通過SLAVE_ADDRESS尋找到從設備后,發送一段”數據”,這段數據通常用于表示從設備內部的寄存器或存儲器地址;第二次傳輸中,對該地址的內容進行讀或寫。也就是說,第一次通訊是告訴從機讀寫地址,第二次則是讀寫的實際內容。
空閑狀態
???? IIC總線的SDA和SCL兩條信號線同時處于高電平時,規定位總線的空閑狀態。
開始信號停止信號?
???? 起始信號:當SCL為高電平期間,SDA有高到低的跳變;啟動信號是一種電平跳變時序信號,而不是一個電平信號。
??? 停止信號:當SCL為高電平期間,SDA由低到高的跳變;停止信號也是一種高電平跳變時序信號,而不是一個電平信號
???? 起始信號和停止信號一般由主機產生
應答信號?
發送器每發送一個字節,就在時鐘脈沖9期間釋放數據線,由接收器反饋一個應答信號。應答信號為低電平時,規定為有效應答位(ACK簡稱應答位)
表示接收器已經成功地接收了該字節;應答信號為高電平時,規定為非應答位(NACK),一般表示接收器接收該字節沒有成功。
???? 對于反饋有效應答位ACK的要求是,接收器在第九個時鐘脈沖之前的低電平期間將SDA線拉低,并且確保在該時鐘的高電平期間為穩定的低電平。
數據的有效性?
IIC總線進行數據傳輸時,時鐘信號為高電平期間,數據線上的數據必須保持穩定,只有在時鐘線上的信號為低電平期間,數據線上的高電平或低電平狀態才允許變化。SDA數據線在SCL的每個時鐘周期傳輸一位數據。
如果數據總線正在發送1然后想變為發送0只能在時鐘總線的低電平的時候發生改變
??? 即:數據在SCL的上升沿到來之前就需準備好。并在下降沿到來之前必須穩定?
數據傳輸
???? 在IIC總線上傳送的每一位數據都有一個時鐘脈沖相對應(或同步控制),即在SCL串行時鐘的配合下,在SDA上逐位地串行傳送每一位數據。數據位的傳輸是邊沿觸發。
?STM32的IIC特性及架構
軟件模擬協議:使用CPU直接控制通訊引腳的電平,產生出符合通訊協議標準的邏輯。
硬件實現協議:由STM32的IIC片上外設專門負責實現IIC通訊協議,只要配置好該外設,它就會自動根據協議要求產生通訊信號,收發數據并緩存起來,CPU只要檢測該外設的狀態和訪問數據寄存器,就能完成數據收發。這種由硬件外設處理IIC協議的方式減輕了CPU的工作,且使軟件設計更加簡單。
??? STM32的IIC外設可用作通訊的主機及從機,支持100Kbit/s和400Kbits/s的速率,支持7位、10位設備地址,支持DMA數據傳輸,并具有數據校驗功能。
?通訊引腳、時鐘控制邏輯、數據控制邏輯、整體控制邏輯
1.通訊引腳
??????????? STM32芯片有多個IIC外設,它們的IIC通訊信號引出到不同的GPIO引腳上,使用時必須配置這些指定的引腳。
?
2.時鐘控制邏輯
???????? SCL線的時鐘信號,由IIC接口根據時鐘控制寄存器(CCR)控制,控制的參數主要位時鐘頻率。
?? 可選擇IIC通訊的“標準/快速”模式,這兩個模式分別對應100/400Kbits/s的通訊速率。
?? 在快速模式下可選擇SCL時鐘的占空比,可選T(low)/T(high) = 2或T(low)/T(high)=16/9模式。
?? CCR寄存器中12位的配置因子CCR,它與IIC外設的輸入時鐘源共用作用,產生SCL時鐘。STM32的IIC外設輸入時鐘源位PCKL1.
如果是標志模式下T(high)=T(low)的,一個周期等于T(high)+T(low),所以說占空比T(high)/T(high)+T(low)等于百分之50
?
計算時鐘頻率
??? 標準模式
?? T high = CCR *T pckl1? T low= CCR*Tpclk1
?? 快速模式中 Tlow/Thigh =2時:
??? Thigh = CCR*Tpckl1??? T low = 2*low*Tpckl1?
?? 快速模式中 Tlow/Thigh =16/9時:
??? Thigh = 9*CCR*Tpckl1??? T low = 16*low*Tpckl1
PCLK1 = 36MHz,想要配置400Kbits/s 方法:
PCLK時鐘周期: TPCLK1 = 1/36 000 000(周期等于頻率的分之一)
目標SCL線時鐘周期:? TSCL = 1/400 000
因為在快速模式下Tlow/Thigh =2,所以Tlow=2*Thigh
SCL時鐘周期內的高電平時間: Thigh = TSCL/3
SCL時鐘周期內的低電平時間: Tlow? = 2*TSCL/3
計算CCR的值 :?? CCR = THIGH/TPCLK1 = 30
計算出來的值寫入到寄存器即可??????????????
3.數據控制邏輯
?????? IIC的SDA信號主要連接到數據移位寄存器上,數據移位寄存器的數據來源及目標是數據寄存器(DR)、地址寄存器(OAR)、PEC寄存器以及SDA數據線。
·當向外發送數據的時候,數據移位寄存器以“數據寄存器”為數據源,把數據一位一位地通過SDA信號線發送出去。(是從低位發送還是從高位發送呢由SMBA控制,一般是高位先行)
·當從外部接收數據的時候,數據移位寄存器把SDA信號線采樣到的數據一位一位地存儲到”數據寄存器”中。
????????? 使用IIC外設通訊時,在通訊的不同階段它會對”狀態寄存器(SR1和SR2)”的不同數據位寫入參數,通過讀取這些寄存器標志來了解通訊狀態。
1.主發送器
控制產生起始信號(S),當發生起始信號后,它產生事件”EV5”,并會對SR1寄存器的 SB 位置1(通過配置我們的寄存器),表示起始信號已經發生。?發生設備地址并等待應答信號,若有從機應答,則產生時間 EV6 及 EV8 ,這時SR1寄存器的 ADDR位及 TXE位被置1,ADDR位1 表示地址已經發送,TEX表示數據寄存器為空。
?
????????·往IIC的數據寄存器DR寫入要發送的數據,這時TXE位會被充值0,表示數據寄存器非空,IIC外設通過SDA信號線一位位把數據發送出去后,又會產生EV8事件,即TXE被置1,重復這個過程,可發送多個字節。
????? ·發送數據完成后,控制IIC設備產生一個停止信號P,這個時候產生EV2事件,SR1的TEX位及BTF位被置1,表示通訊結束。
STM32的IIC結構體?
typedef struct{uint32_t I2C_ClockSpeed; //設置SCL時鐘頻率,此值要低400 000uint16_t I2C_Mode;???????????? //指定工作模式,可選IIC模式及SMBUS模式uint16_t I2C_DutyCycle;?????? //時鐘占空比,可選low/high = 2:0或16:9uint16_t I2C_OwnAddress1;? //自身的IIC設備地址??uint16_t I2C_Ack;??????????????? //使能或者關閉響應,一般是使能uint16_t I2C_AcknowledgedAddress; //指定地址長度,可為7或10}I2C_InitTypeDef;
?uint32_t I2C_ClockSpeed; //設置SCL時鐘頻率,此值要低于400 000?
I2C_ClockSpeed? ? ? ? ? ? ? ? ? ? ? ? ??
?????? 設置IIC的傳輸速率,在調用初始化函數時,函數會根據我們輸入的數值經過運算后把時鐘因子寫入到IIC的時鐘控制寄存器CCR。而我們寫入的這個參數值不得高于400Khz.
??????? 實際上由于CCR寄存器不能寫入小數類型的時鐘因子,影響到SCL的實際頻率可能會低于本成員設置的參數值,這時除了通訊會稍微慢點以外,不會對IIC的標準通訊造成其他影響。
?
??根據頻率比較會把結構體的值寫入相對于的寄存器里相對于的位
? uint16_t I2C_Mode;????? //指定工作模式,可選IIC模式SMBUS模式
選擇IIC的使用方式,有IIC模式(IIC_Mode_IIC)和SMBus主、從模式(IIC_Mode_SMBusHost、IIC_Mode_SMBusDevice)
??????? IIC不需要在此處區分主從模式,直接設置IIC_Mode_IICj即可
?
uint16_t I2C_DutyCycle; //時鐘占空比,可選low/high = 2:0或16:9
·I2C_DutyCycle? ? ? ? ? ? ? ? ? ? ? ?
?????? 設置IIC的SCL線時鐘的占空比。該配置有兩個選擇,分別為低電平時間比高電平時間為2:1(IIC_DutyCycle_2)和16:9(IIC_DutyCycle_16_9).
?????? 其實這兩個模式的比例差別并不大,一般要求都不會如此嚴格,這里隨便選就可以了。
?uint16_t I2C_OwnAddress1;? //自身的IIC設備地址
·I2C_OwnAddress1? ? ? ? ? ?
??????? 配置STM32的IIC設備自己的地址,每個連接到IIC總線上的設備都有一個自己的地址,作為主機也不例外。地址可以設置為7位或10位(受下面IIC_AcknowledgeAddress成員決定),只要該地址是IIC總線上唯一的即可。
?????? STM32的IIC外設可同時使用兩個地址,即同時對兩個地址作出響應,這個結構體成員IIC_OwnAddress1配置的是默認的,OAR1寄存器存儲的地址,若需要設置第二個地址寄存器OAR2,可使用IIC_OwmAddress2Conig函數來配置,OAR2不支持10位地址。主從設備在通信的時候另外也可以進行其他的活動操作,所以使用起來非常靈活
?
?uint16_t I2C_Ack;??????????????? //使能或者關閉響應,一般是使能? ? ?
·I2C_Ack?
????? 配置IIC應答是否使能,設置位使能則可以發送響應信號,一般配置位允許應答(IIC_Ack_Enable),這是絕大多數遵循IIC標準的設備的通訊要求,改為禁止應答(IIC_Ack_Disable)往往會導致通訊錯誤。????
?uint16_t I2C_AcknowledgedAddress; //指定地址長度,可為7或10
??????
·I2C_AcknowledgedAddress ? ?
???? 選擇IIC的尋址模式是7位或者是10位地址,這需要根據實際連接到IIC總線上設備的地址進行選擇,這個成員的配置也影響到IIC_OwnAddress成員,只有這里設置成10位模式時,IIC_OwnAddress1才支持10位地址
??? 配置完這些結構體成員的值,調用庫函數IIC_Init就可以把結構體的配置寫入到對應的寄存器中了。????
?
0.96寸OLED顯示屏
適用器件:
? 0.96寸OLED顯示屏(驅動芯片:SSD1306 / SSD1315)
? 1.3寸OLED顯示屏(驅動芯片:SH1106)
? 4針腳I2C接口
? 7針腳SPI接口
? 128*64像素
? 像素顏色不限
???????????????????????????????????????????????????????
?SSD1306是一款OLED(有機發光二極管)/PLED(高分子發光二極管)點陣顯示屏的控制器,可以嵌入在屏幕中,用于執行接收數據、顯示存儲、掃描刷新等任務
?驅動接口:128個SEG引腳和64個COM引腳,對應128*64像素點陣顯示屏
?內置顯示存儲器(GDDRAM):128*64 bit (128*8 Byte)SRAM
?供電:VDD=1.65~3.3V(IC 邏輯),VCC=7~15V(面板驅動)
?通信接口:8位6800/8080并行接口,3/4線SPI接口,I2C接口
?第一步我們通過引出來的通信引腳把我們想顯示的內容發給驅動芯片,第二步驅動芯片收到數據然后把它存起來也就是芯片內部,自帶的有RAM顯示存儲器,存儲的是屏幕顯示的內容,第三步驅動芯片內部有時鐘和掃描電路,根據顯示存儲器的數據,它會自動對應刷新到屏幕,這樣我們指定的內容就呈現在屏幕上了,這些步驟,我們只需要關心,如何通過通信協議把數據寫入到屏幕里的顯示存儲器即可
?SSD1306框圖及引腳定義
收到一個字節之后首先要在MCU進行分流,發一個字節會指定,這個字節是作為數據還是作為命令,其中命令會進入到下面,命令譯碼器用于內部電路的執行,比如開啟顯示,關閉顯示,設置對比度,設置顯示起始位置,芯片內有一個命令表,參考命令表就知道這個命令是什么功能了
數據會進入到右邊GDDRAM,定義為數據的字節決定的是屏幕上顯示什么內容了,只有我們把想顯示的內容寫入到GDDRAM里的對應位置,在屏幕上就會顯示對應的內容,所以我們操作顯示屏只需要關心如何將數據寫入到GDDRAM的正確位置就行了
顯示控制器Display Counter的任務就是讀取GDDRAM把里面的內容掃描刷新到屏幕上,所以控制器的輸出就是顯示屏點陣的驅動器了,
中間是短驅動器輸出引腳是。。。上下兩端是公共驅動器階段點陣的64行
振蕩器Oscillator用于提供刷新屏幕的時鐘,CL是時鐘輸入腳,CLS是時鐘源選擇腳,這個芯片內部自帶時鐘,但也可以通過CLS選擇,CLS接高電平,選擇內容時鐘,CL引腳不用,CLS接低電平,選擇外部時鐘,CL加一個外部時鐘源,一般用內部時鐘?
電流控制和電壓控制,VCOMEH一般是公共端的電壓輸出,一般在外面接一個電源濾波電容就行了,IREF是驅動電流控制,在外面接個電阻,可以控制驅動屏幕的電流
?
4針腳I2C接口模塊原理圖?
字節傳輸-I2C?
首先S起始條件,起始之后主機需要發送一個字節,并且必須是7位從機地址+1位讀寫位的格式,串行模式只能寫入,所以RW位只給0,用于I2C尋址,用于在多從機的情況下,指定操作哪個從機,發送一個字節后需要接受應答,判斷一下,從機是不是收到了,尋址完后就可以進入命令或數據的發送了,I2C沒有單獨的DC引腳,如何判斷是命令還是數據呢,這里再發送Data byte之前要先發送一個Control byte用于指定后面的有效字節是命令還是數據,DC指定1或0表示是命令還是數據的選擇
Co置1表示在每個Data byte都加一個Control byte, 這樣可以對每一個Data byte 指定是命令還是數據,命令和數據可以來回切換,Co置0表示先來一個Control byte之后的全部都是Data byte,要么是命令要么是數據,不能來回切換,最后p停止,而且每發送一個字節都有應答位(高位先行模式)
執行邏輯框圖
?
1.左上角為原點,向右為X軸坐標是0-127,向下為Y軸坐標是0-63,中間是所有的像素點,目前是單色屏幕,所以每個像素點只有亮滅兩種狀態,用一個bit,1或0就可以控制一個像素點
下圖是驅動芯片內部的顯示存儲器128*64位即128*8字節的GDDRAM,這個GDDRAM和屏幕像素點是一一映射的關系,其X軸坐標也是0-127,但是Y軸有所不同,雖然Y軸總共也是64個坐標,但是Y軸會8個坐標為一組進行分頁,坐標范圍是PAGE0-7總共分為8頁,為什么要進行分頁?因為屏幕上只需要一個bit就可以控制一個像素點了,但是存儲器的組織一般都是以字節為單位的,而且我們使用寫數據的時序,一次性也是發過來一個字節,一個字節有8bit,如果8位只顯示一個像素點太浪費了,所以這里會把字節的8位展開豎向排列,一個位控制一個像素點,是LSB低位在上MSB高位在上,所以說我們每一次寫入一個數據都會控制縱向的8個像素點,既然是一起控制的,那么Y軸就必然會8位為一組,進行分頁,這樣做的好處就是充分利用一個字節8位,一次性寫入8個像素點,效率也比較高,壞處是Y軸坐標只能8個為一組進行指定,不能在0-63任意指定(有解決方法)
2.怎么寫入數據?比如我們先設定頁地址為PAGE0,列地址設定為8,就指定了起始坐標是這個字節的位置,然后我們寫入數據0X55(01010101)寫入完成后驅動芯片會自動根據GDDRAM的數據,掃描點陣屏,所以上面在點陣屏的對應位置會寫入一個小豎條,1的地方點亮,0的地方熄滅,接著寫一個字節后內部的地址指針會向右移動一個單位,所以我們再寫入一個數據比如0XAA(10101010)
3.比如我們想在屏幕的右上角,顯示字符A,操作流程是使用寫命令的函數第一步設置頁地址為PAGE0,第二步設置頁地址為122,這時當前的地址指針指向這個字節,然后使用寫數據的函數寫入0x00當前字節就變成了0x00寫完之后地址指針自動右移繼續寫,連續寫完6個字節而且都豎向排列后,存儲器內部的數據就是這樣的,現在寫入到了最后一個字節的位置了,如果繼續寫的話默認會回到此頁的最開頭,當然也可以配置自動換頁,換到下一頁的開頭,這個可以通過配置尋址模式來實現,這樣對應的屏幕就顯示一個字符A,這串數據可以提前存到數組里,這是6x8點陣,字符A的字模數據,當我們想顯示A時就寫入這一串數據就行了,如果我們想把A 往左移動一個像素,設置列地址時為121即可,往下移很難,如果指定頁地址為PAGE1那么其實是往下移動了8個像素,這時得把A的字模切成兩半,一半寫入PAGE0的下部分,一半寫入PAGE1的上部分,如果想實現Y軸任意指定或者更高級的繪圖功能,那就得有讀取GDDRAM的能力,并行模式可以讀取,串行模式下讀不了(但是可以在程序中定義緩存數組實現,先讀寫緩存數組,最后在一起更新到屏幕的GDDRAM里)
4.運行流程:如何指定坐標如何指定顯示數據呢,首先是通信接口,它的作用是接受一個字節,一個字節進來后通過DC引腳指定這個字節是命令還是數據
命令表
第一個是雙字節命令先寫一個命令0X81,表示設置對比度緊跟著再寫一個字節,這個字節就是設定對比度的值
下一個是單字節命令可以寫入A4或者A5即這個字節的前七位是命令碼,最低位1/0表示開關功能是GDDRAM是否開啟,發送A4即最低位為0表示輸出遵循RAM的內容,發送A5即最低位為1,表示輸出忽略RAM的內容
下一個功能是反色顯示,發送A6表示RAM里的1像素點亮,發送A7表示像素里的0像素點亮
AE表示顯示關閉 AF表示顯示開啟
設置列地址為00~0F,其中高四位0000是命令碼,低4位是列地址的低位,配合下面的命令10~1F,高4位0001命令碼,低4位是列地址的高位,這兩個四位拼在一起構成列地址的8位,因為命令的高位需要有命令碼,防止各個命令沖突,所以指定列地址,需要拆分為高4位和低4位
設置頁地址B0·別,高5位為10110為命令碼,低3位指定頁地址PAGE0-7?
比如發送命令B0 表示設置頁地址位PAGE0,發送命令00再發10表示設置列地址為0?
命令0X81: 設置對比度。包含兩個字節,第一個0X81為命令,隨后方法是的一個字節要設置這個對比度,值越大屏幕越亮。
??? ·命令0XAE/0XAF: 0XAE為關閉顯示命令,0XAF為開啟顯示命令
??? ·0X8D: 包含兩個字節,第一個為命令字,第二個為設置值,第二個字節的BIT2表示電荷泵的開關狀態,該位為1開啟電荷泵,為0則關閉。模塊初始化的時候,這個必須要開啟,否則看不到屏幕顯示。
??? ·命令0XB0~B7:用于設置頁地址,其低三位的值對應GRAM頁地址。
??? ·命令0X00~0X0F:用于設置顯示時的起始列地址低四位。
??? ·命令0X10~0X1F: 用于設置顯示時的起始列地址高四位。
硬件I2C的配置
void I2C_Configuration()
{I2C_InitTypeDef I2C_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);//PB6---SCL,PB7---SDAGPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;//因為I2C空閑的時候是高阻態,而且復用I2CGPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);I2C_DeInit(I2C1);I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;I2C_InitStructure.I2C_ClockSpeed = 400000;I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;I2C_InitStructure.I2C_OwnAddress1 = 0X30;I2C_Init(I2C1, &I2C_InitStructure);I2C_Cmd(I2C1, ENABLE);}
I2C怎么去寫一個字節,需要再寫一個函數,因為作為OLED屏幕的驅動肯定要發送過去
先發送地址,再發送數據
在發送之前要檢查一下總線是否繁忙,然后開啟I2C
開啟之后要有EV5的標志,應答完,要發送地址了(先去.h定義一個OLED的地址)
再發送應答位,現在可以發送數據了
void I2C_WriteByte(uint8_t addr,uint8_t dat)
{while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));//檢查I2C總線是否繁忙//這行代碼的作用是在 I2C 總線處于繁忙狀態時,函數會一直循環等待,直到總線空閑。I2C_GenerateSTART(I2C1, ENABLE);//打開I2Cwhile(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//EV5,主模式//這行代碼會使函數進入循環,直到 I2C 主機成功進入主模式。I2C_Send7bitAddress(I2C1,0x78, I2C_Direction_Transmitter);//發送器件地址(點名)while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//EV6//函數會循環等待,直到主機成功進入主發送模式。I2C_SendData(I2C1, addr); //發送寄存器地址//用于向 I2C 數據寄存器寫入數據。這里將之前傳入的 addr(寄存器地址)發送給從設備。while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));//EV8//函數會循環等待,直到寄存器地址成功發送。I2C_SendData(I2C1,dat); //發送數據//將之前傳入的 dat(要寫入的數據)發送到從設備的指定寄存器地址while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));//EV8I2C_GenerateSTOP(I2C1, ENABLE);//關閉I2C總線}
//寫命令
void WriteCmd(unsigned char I2C_Command)
{I2C_WriteByte(0x00,I2C_Command);該函數的作用是向 I2C 設備寫入一個命令。它調用了I2C_WriteByte函數,將地址0x00和命令I2C_Command作為參數傳遞給I2C_WriteByte函數。這里的0x00代表Control Byte 為0000 0000 也就是DC位置0 表示之后發送的是命令}
//寫數據
void WriteDate(unsigned char I2C_Date)
{I2C_WriteByte(0x40,I2C_Date);該函數的作用是向 I2C 設備寫入一個數據。它調用了I2C_WriteByte函數,將地址0x40和數據I2C_Date作為參數傳遞給I2C_WriteByte函數。這里的0x40代表Control Byte 為0100 0000 也就是DC位置1 表示之后發送的是數據}
?OLED屏幕初始化
void OLED_Init(void)
{delay(100);WriteCmd(0xAE); //display offWriteCmd(0x20); //Set Memory Addressing Mode WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,InvalidWriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7WriteCmd(0xc8); //Set COM Output Scan DirectionWriteCmd(0x00); //---set low column addressWriteCmd(0x10); //---set high column addressWriteCmd(0x40); //--set start line addressWriteCmd(0x81); //--set contrast control registerWriteCmd(0xff); //áá?èμ÷?ú 0x00~0xffWriteCmd(0xa1); //--set segment re-map 0 to 127WriteCmd(0xa6); //--set normal displayWriteCmd(0xa8); //--set multiplex ratio(1 to 64)WriteCmd(0x3F); //WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM contentWriteCmd(0xd3); //-set display offsetWriteCmd(0x00); //-not offsetWriteCmd(0xd5); //--set display clock divide ratio/oscillator frequencyWriteCmd(0xf0); //--set divide ratioWriteCmd(0xd9); //--set pre-charge periodWriteCmd(0x22); //WriteCmd(0xda); //--set com pins hardware configurationWriteCmd(0x12);WriteCmd(0xdb); //--set vcomhWriteCmd(0x20); //0x20,0.77xVccWriteCmd(0x8d); //--set DC-DC enableWriteCmd(0x14); //WriteCmd(0xaf); //--turn on oled panel}
設置起點坐標
void OLED_SetPos(unsigned char x,unsigned char y)
{WriteCmd(0xb0 +y);WriteCmd((x&0xf0)>>4|0x10);WriteCmd((x&0x0f)|0x01);}
OLED_SetPos
?是函數名,用于設置 OLED 屏幕上的位置。x
?和?y
?是兩個無符號字符型(unsigned char
)的參數,分別代表屏幕上的列坐標和頁坐標。在 OLED 顯示屏中,通常采用頁地址和列地址的方式來定位像素點,y
?代表頁地址,x
?代表列地址。WriteCmd
?函數在之前的內容中提到過,它用于向 I2C 設備(這里是 OLED 顯示屏)寫入命令。0xb0
?是 OLED 顯示屏設置頁地址命令的基礎值。在 OLED 顯示屏中,通常將屏幕按頁劃分,每一頁包含若干行像素。不同的頁地址由?0xb0
?加上頁號來表示,這里的?y
?就是頁號(范圍通常是 0 - 7,因為很多 OLED 顯示屏有 8 頁)。例如,當?y = 0
?時,發送的命令是?0xb0
,表示選擇第 0 頁;當?y = 1
?時,發送的命令是?0xb1
,表示選擇第 1 頁,以此類推。(x & 0xf0)
:使用按位與運算符?&
?將?x
?與?0xf0
(二進制為?11110000
)進行運算,目的是提取?x
?的高 4 位。例如,如果?x = 0x25
(二進制?00100101
),那么?x & 0xf0
?的結果是?0x20
(二進制?00100000
)。(x & 0xf0) >> 4
:將提取的高 4 位右移 4 位,使其成為低 4 位。繼續上面的例子,0x20
?右移 4 位后得到?0x02
(二進制?00000010
)。(x & 0xf0) >> 4 | 0x10
:使用按位或運算符?|
?將右移后的結果與?0x10
(二進制?00010000
)進行運算。0x10
?是設置列地址高 4 位命令的基礎值,通過按位或運算將提取的高 4 位組合到命令中。例如,0x02 | 0x10
?的結果是?0x12
,表示列地址的高 4 位為?0010
。(x & 0x0f)
:使用按位與運算符?&
?將?x
?與?0x0f
(二進制為?00001111
)進行運算,目的是提取?x
?的低 4 位。例如,如果?x = 0x25
,那么?x & 0x0f
?的結果是?0x05
(二進制?00000101
)。(x & 0x0f) | 0x01
:使用按位或運算符?|
?將提取的低 4 位與?0x01
(二進制?00000001
)進行運算。0x01
?是設置列地址低 4 位命令的基礎值,通過按位或運算將提取的低 4 位組合到命令中。例如,0x05 | 0x01
?的結果是?0x05
,表示列地址的低 4 位為?0101
。
(x&0xf0)>>4|0x10
:x&0xf0
?會把?x
?的低四位清零,保留高四位。接著?>>4
?把高四位右移到低四位的位置,最后?|0x10
?是按位或操作,可能是為了組合成符合 OLED 指令規范的高四位地址設置指令部分。(x&0x0f)|0x01
:x&0x0f
?會把?x
?的高四位清零,保留低四位,然后?|0x01
?按位或操作,是為了組合成符合 OLED 指令規范的低四位地址設置指令部分。
//全屏填充
void OLED_Fill(unsigned char Fill_Data)
{//共八頁unsigned char m,n;for(m=0;m<8;m++){WriteCmd(0xb0+m);WriteCmd(0x00);WriteCmd(0x10);for(n=0;n<128;n++){WriteDate(Fill_Data);} }}//清屏
void OLED_CLS(void)
{OLED_Fill(0x00);
}//OLED打開
void OLED_ON(void)
{WriteCmd(0X8D); //設置電荷泵WriteCmd(0X14); //開啟電荷泵WriteCmd(0XAF); //OLED喚醒}
//OLED關閉
void OLED_OFF(void)
{WriteCmd(0X8D); //設置電荷泵WriteCmd(0X10); //關閉電荷泵WriteCmd(0XAE); //OLED關閉}
?OLED顯示字符串
//OLED存放格式
//一行128個 一列7個 有8頁
//′?·???ê?è???£o
//[0]0 1 2 3 ... 127
//[1]0 1 2 3 ... 127
//[2]0 1 2 3 ... 127
//[3]0 1 2 3 ... 127
//[4]0 1 2 3 ... 127
//[5]0 1 2 3 ... 127
//[6]0 1 2 3 ... 127
//[7]0 1 2 3 ... 127
//顯示字符串函數
//TextSize表示兩種顯示方式 一種是6X8 一種是8X16
void OLED_ShowStr(unsigned char x,unsigned y,unsigned char ch[],unsigned TextSize)
{ unsigned char c = 0,i = 0,j = 0;//現在選擇用那種表達方式switch(TextSize) { case 1:{while(ch[j] != '\0'} //先判斷字符串是否輸出到'\0'{ c = ch[j] - 32;//將字符的ASCII碼減去32用于索引F6x8 (字庫假設字庫從空格字符開始)if(x>126){x = 0;y++;}//上面是已經找到該輸出什么了//現在需要設置一下從什么地址開始輸出OLED_SetPos(x,y);for(i=0;i<6;i++){WriteDate( F6x8[c][i]);x+=6; }j++;}}break;case 2;{while(ch[j] !='\0'){c = ch[j] - 32;if(x > 120){x = 0;y++;}OLED_SetPos(x,y);for(i = 0;i<8;i++)WriteDate( F8X16[c*16+i]);OLED_SetPos(x,y);for(i = 0;i<8;i++)WriteDate( F8X16[c*16+i+8]);x+=8;}break;}}
int main()
{ I2C_Configuration();OLED_Init();delay(100);OLED_Fill(0xFF); //屏幕全亮delay(100);OLED_Fill(0x00); //屏幕全滅delay(100);OLED_ShowStr(0,3,"Hello World",1);OLED_ShowStr(0,4,"Hello World",2);}
OLED顯示文字
先生成字模
void OLED_ShowCN(unsigned char x,unsigned char y,unsigned char N)
{unsigned char wm=0;unsigned int addr = 32*N;OLED_SetPos(x,y);for(wm=0;wm<16;wm++){WriteDat(F16X16[addr]);addr +=1;}OLED_SetPos(x,y+1);for(wm=0;wm<16;wm++){WriteDat(F16X16[addr]);addr +=1;}
軟件I2C的配置
先配置GPIO引腳
?宏定義輸出高電平和低電平
#define OLEDR_SCLK_Set() GPIO_SetBits(GPIOB, GPIO_Pin_0)//PB0(SCL)輸出高電平
#define OLEDR_SCLK_Clr() GPIO_ResetBits(GPIOB, GPIO_Pin_0)//PB1(SCL)輸出低電平
#define OLEDR_SDIN_Set() GPIO_SetBits(GPIOB, GPIO_Pin_1)//PB1(SDA)輸出高電平
#define OLEDR_SDIN_Clr() GPIO_ResetBits(GPIOB, GPIO_Pin_1)//PB1(SDA)輸出低電平
?根據時序圖,最開始時把SDA和SCL拉高 (開漏換成推挽)
模擬I2C起始信號和停止信號
根據時序圖 模擬I2C起始信號和停止信號
static void OLEDR_IIC_Start(void)
{OLEDR_SCLK_Set();//時鐘總線高電平OLEDR_SDIN_Set();//數據總線高電平delay_u(1);OLEDR_SDIN_Clr();//數據總線先低電平delay_u(1);OLEDR_SCLK_Clr();/時鐘總線再低電平}
static void OLEDR_ICC_Stop(void)
{OLEDR_SDIN_Clr();//數據總線低電平delay_u(1);OLEDR_SCLK_Set();//時鐘總線高電平delay_u(1);OLEDR_SDIN_Set();//數據總線在高電平delay_u(1);
}
?模擬I2C的應答信號
根據時序圖 模擬I2C的應答信號
先宏定義
static unsigned char IIC_Wait_Ack(void)
{unsigned char ack;OLEDR_SCLK_Clr();//時鐘總線置低delay_u(1);OLEDR_SDIN_Set();//數據總線高電平delay_u(1);OLEDR_SCLK_Set();//時鐘總線高電平delay_u(1);if(OLED_READ_SDIN())//如果讀取到電平{ack = IIC_NO_ACK; }else{ack = IIC_ACK; }OLEDR_SCLK_Clr();//時鐘總線置低delay_u(1);return ack; //返回取得應答信息}
?
模擬IIC寫字節?
根據數據的有效性???????,時鐘和數據都高電平的時候才有效
static void Write_IIC_Byte(unsigned char IIC_Byte)
{unsigned char i ;//定義變量for(i = 0;i<8;i++){OLED_SCLK_Clr();delay_u(1);//因為是高位傳輸,所以先讀取高位,如果高位為1,說明有數據進入if(IIC_Byte & 0x80) //循環8次 讀取最高位,與0x80(1000 0000)進行與運算(逢0截止)OLEDR_SDIN_Set(); //最高位為1 數據總線置高電平elseOLEDR_SDIN_Clr(); //最高位位0 數據總線置低電平IIC_Byte <<=1; //數據向左移1位delay_u(1);OLEDR_SCLK_Set();//時鐘線置高,產生上升沿,把數據發出去delay_u(1);}OLEDR_SCLK_Clr();//不循環的時候時鐘線置低電平delay_u(1);IIC_Wait_Ack();//從機等待}
模擬I2C對OLED寫入一個字節
//IIC寫命令
static void Write_IIC_Command(unsigned char IIC_Command)
{void OLEDR_IIC_Start();//發送起始信號Write_IIC_Byte(0x78);//寫入從機(oled屏)地址 SD0 = 0Write_IIC_Byte(0x00);//0x00 用于告訴 OLED 屏幕接下來接收的是命令Write_IIC_Byte(IIC_Command);//命令OLEDR_ICC_Stop();//發送停止信號}
//IIC寫數據
static void Write_IIC_Command(unsigned char IIC_Command)
{void OLEDR_IIC_Start();//發送起始信號Write_IIC_Byte(0x78);//寫入從機(oled屏)地址 SD0 = 0Write_IIC_Byte(0x40);//0x40 用于告訴 OLED 屏幕接下來接收的是數據Write_IIC_Byte(IIC_Command);//數據OLEDR_ICC_Stop();//發送停止信號}
//對OLED寫入一個字節
void OLED_WR_Byte(unsigned char dat,unsigned char cmd)
{if(cmd)//如果cmd為真(1)則是寫數據,否則是寫命令{Write_IIC_Data(dat);//寫入數據}else{Write_IIC_Command(dat);//寫入命令}}
OLED起始坐標
先宏定義用于這個函數
//設置數據寫入的起始行和列
//x:列的起始低地址與高地址
//y:頁的起始地址 0-7
void OLED_Set_Pos(unsigned char x,unsigned char y)
{OLED_WR_Byte(0xb0+y,OLED_CMD); //寫入頁地址(命令)OLED_WR_Byte((x&0x0f),OLED_CMD);//寫入列的低地址(命令)OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);//寫入列的高地址 (命令)}
//開顯示
void OLED_Display_On(void)
{OLED_WR_Byte(0x8D,OLED_CMD);//設置OLED電荷泵OLED_WR_Byte(0x14,OLED_CMD);//使能 開OLED_WR_Byte(0xAF,OLED_CMD);//開顯示}//關顯示
void OLED_Display_Off(void)
{OLED_WR_Byte(0xAE,OLED_CMD);//關顯示OLED_WR_Byte(0x8D,OLED_CMD);//設置OLED電荷泵OLED_WR_Byte(0x10,OLED_CMD);//失能}
//清屏幕
void OLED_Clear(void)
{unsigned char i,n;for(i=0;i<8;i++)//i代表頁數 總共有8頁 所以循環8次{OLED_WR_Byte(0xb0+i,OLED_CMD);//從0-7頁寫入OLED_WR_Byte(0x00,OLED_CMD);//列的低地址OLED_WR_Byte(0x10,OLED_CMD);//列的高地址for(n=0;n<128;n++){OLED_WR_Byte(0,OLED_DATA);//寫入 0 清屏 }}}
顯示字符 字符串 數字 漢字
宏定義一下
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr)
{unsigned char c = 0,i = 0;c = chr -' ';//為獲取字符的偏移量//判斷大小if(x>Max_Coulumn){x = 0;//列超出范圍了 得從下兩頁開始y = y+2;}if(SIZE == 16)//字符大小如果為16 那就是16x8 需要占兩頁 每一頁有8列{OLED_Set_Pos(x,y);//從x列 y頁開始for(i=0;i<8;i++)//先顯示前8位OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);//找出字符C的數組,在第一頁上依次把每一列寫出//因為一列只能顯8位,所以需要再寫一頁OLED_Set_Pos(x,y+1);//從x列 y+1頁開始for(i=0;i<8;i++)//再顯示剩下8位OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);//i+8寫出數組的后8位 }else //6x8的 6位 只需占一頁 每一頁六列{OLED_Set_Pos(x,y);//從x列 y頁開始for(i=0;i<6;i++)OLED_WR_Byte(F6x8[c][i],OLED_DATA);//找出字符C的數組,依次把每列寫出} }
//顯示字符串
void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr)
{unsigned char j = 0;while(chr[j] != '\0')//判斷是不是最后一個字符{OLED_ShowChar(x,y,chr[j]);//顯示字符x+=8; //每顯示一個字符,重新從后八列開始,因為一個字符得用八個字節顯示if(x>=128){x = 0;y =+2;}j++; }
//計算m^n次方
unsigned int oled_pow(unsigned char m,unsigned n)
{unsigned int result = 1;while(n--)result *=m;return result;}
//顯示數字
void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size)
{ unsigned char t,temp; //t用于循環計數 temp用于存儲當前要顯示的數字位unsigned char enshow=0; //用來判斷是否開始顯示非零數字for(t=0;t<len;t++){temp=(num/oled_pow(10,len-t-1))%10;//取出輸入數的每一位,由高到低if(enshow==0&&t<(len-1)) //如果enshow為0而且不是最后一位數字{if(temp==0) //如果該數字位為0{OLED_ShowChar(x+(size/2)*t,y,' ');//顯示0 x+(size/2)*t根據字體大小偏移的列數continue; //跳出剩下語句,繼續重復循環}else enshow=1; }OLED_ShowChar(x+(size/2)*t,y,temp+'0'); //顯示一個位 x+(size/2)*t根據字體大小偏移的列數}
}
void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char num)
{unsigned char t,adder = 0;OLED_Set_Pos(x,y);for(t=0;t<16;t++){ OLED_WR_Byte( Hzk[2*no][t],OLED_DATA);//畫 number在數組位置的第一頁16個列的點adder+=1; //數組地址+1}OLED_Set_Pos(x,y+1);for(t=0;t<16;t++){OLED_WR_Byte( Hzk[2*no+1][t],OLED_DATA);//畫 number在數組位置的第二頁16個列的點adder+=1; //數組地址+1}}
int main()
{ initSysTick();delay_ms(1500);OLED_Init(); OLED_Clear();OLED_ShowChar(30,2,'O');OLED_ShowChar(38,2,'K');OLED_ShowString(30,4,"123");
}
溫濕度傳感器
? ? ? ? ? ? ? ? ? ? ? ?
DHT11數字溫濕度傳感器是一款含有已校準數字信號輸出的溫濕度復合傳感器,應用于專用數字模塊采集技術和溫濕度傳感技術,確保產品具有極高的可靠性和卓越的長期穩定性。
???????傳感器包括一個電阻式感濕元件和一個NTC測溫元件,并與一個高性能八位單片機相連接。采用單線制串口行接口,信號傳輸距離可達20M以上。
應用于通暖空調,汽車,自動控制設備,氣象站家電
濕度調節器,醫療,除濕器等等。?
硬件接線圖:?
供電電壓:3.3 - 5.5V直流電
輸出為單總線數字信號
溫度測量范圍0-50度(精度正負2度,分辨率1度)
濕度測量范圍為20-90%RH(精度為正負5%,分辨率1%)
模塊的+??????? 接單片機的5V
模塊的-???????? 接單片機的GND
模塊的OUT?? 接單片機定義的引腳
vcc和gnd之間可以加一個電容,用于去耦濾波
采用單總線雙向串行通信協議,每次采集都要由單片機發起開始信號,然后DHT11會向單片機發送響應并開始傳輸40位數據幀(5x8),高位在前。
數據格式為:
第一二個字節: 8bit濕度整數數據+8bit濕度小數數據
第三四個字節: 8bit溫度整數數據+8bit溫度小數數據
第五個字節??? : 8bit校驗位(它是前四個數據相加后八位的數值)
溫濕度小數部分默認為0,即單片機采集的數據都是整數,校驗位為4個字節的數據相加取結果的低8位數據作為校驗和;
示例一:
0011 0101???? 0000 0000??? 0011 1000?? 0000 0000????? 0100 1101
濕度高八位??? 濕度低八位?? 溫度高八位? 溫度低八位?????? 檢驗位
計算 :??? 0011 0101?? (相加)??????????
??????????????? 0011 1000
?結果:??? 01001101?????? 濕度為0011 0101 = 35H = 53%RH
?????????????????????????????????????? 溫度為0011 1000 = 18H = 24°
示例二:
0011 0101???? 0000 0000??? 0001 1000?? 0000 0000????? 0100 1001
濕度高八位??? 濕度低八位?? 溫度高八位? 溫度低八位?????? 檢驗位
計算 :??? 0011 0101?? (相加)??????????
??????????????? 0001 1000
?結果:??? 0100 1101??? 不等于 01001001 本次接收數據不正確,重新接收數據
溫濕度傳感器時序介紹?
總線空閑狀態為高電平,主機把總線拉低等待DHT11響應,主機把總線拉低必須大于18毫秒,保證DHT11能檢測起始信號。主機立馬拉高等待,DHT11接收到主機的開始信號后,等待主機開始信號結束,然后發送80us低電平響應信號,主機發送開始信號結束后,延時等待20-40us后,讀取DHT11的響應信號,主機發送開始信號后,可以切換到輸入模式,或者輸出高電平均可,總線由上拉電阻拉高。?
DHT11輸出0時時序?
DHT11輸出1時時序?
?
?因為是采用單總線雙向串行通信協議所以要配兩次GPIO
#include "stm32f10x.h"
#include "DHT11.h"
#include "delay.h"
#include "stdio.h"uint16_t Rxbuff[5];
//先輸出 發送起始信號等待響應后
void DHT11_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_SetBits(GPIOB, GPIO_Pin_11);}
//再轉換為輸入
void DHT11_GPIO_Init1(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_SetBits(GPIOB, GPIO_Pin_11);}static uint8_t DHT11_Back()
{uint8_t i =200;while(read_data && i--); //等待低電平的到來i= 200;while(!read_data && i--); //等待高點平的到來return 0;
}void DHT11_Start(void)
{data0;//將總線拉低delay_ms(20);//主機將總線拉低必須大于18毫秒data1;//再把總線拉高等待延時delay_us(10);DHT11_GPIO_Init1();//這時單片機是由輸出切換成輸入while(DHT11_Back());//同時等待響應信號(響應信號是低電平)
//如果是低電平代表這個值DHT11_Back()是0就會繼續運行下面代碼 如果是高電平就是1就會一直在while里循環
}
//總共接收5個字節 每個字節8位
void DHT11_ReceptionBuff()
{uint8_t y=1;uint16_t i;uint8_t x;for(x=0;x<5;x++)//接收5個字節{i=0;//i代表位for(y=1;y<9;y++){while(read_data){__nop();}delay_us(40);while(!read_data){__nop();}i= i<<1;delay_us(30);//每接收一位中間得隔50微秒if(read_data){i |=1;}while(read_data);}Rxbuff[x] =i;}}void DHT11_UpdateData()
{DHT11_GPIO_Init();//GPIO初始化 這時還是輸出模式DHT11_Start();//開始信號DHT11_ReceptionBuff();//切換成接收信號}
OELD顯示溫濕度項目
int main()
{ initSysTick();delay_ms(1500);OLED_Init(); OLED_Clear();OLED_ShowCHinese(0,2,6); //當OLED_ShowCHinese(16,2,7); //前OLED_ShowCHinese(32,2,8); //溫OLED_ShowCHinese(48,2,9); //度OLED_ShowCHinese(66,2,10); //:OLED_ShowCHinese(90,2,15); //.OLED_ShowCHinese(112,2,11); //COLED_ShowCHinese(0,5,6); //當OLED_ShowCHinese(16,5,7); //前OLED_ShowCHinese(32,5,12); //濕OLED_ShowCHinese(48,5,9); //度OLED_ShowCHinese(66,5,10); //:OLED_ShowCHinese(112,5,13); //% while(1){uint16_t i;uint8_t k;uint8_t kk;uint8_t kkk;DHT11_UpdateData();i = Rxbuff[0]+ Rxbuff[1]+Rxbuff[2]+ Rxbuff[3];if(Rxbuff[4] ==i){k=Rxbuff[2];kk=Rxbuff[0];kkk=Rxbuff[3];OLED_ShowNum(74,2,k/10,3,3);OLED_ShowNum(82,2,k%10,3,3);OLED_ShowNum(98,2,kkk,3,3);OLED_ShowNum(88,5,kk/10,3,3);OLED_ShowNum(98,5,kkk%10,3,3);}delay_ms(2000);}
}
- 溫度顯示
OLED_ShowNum(74,2,k/10,3,3);
:
k/10
:這是一個整數除法運算。例如,若k
的值為 25,那么k/10
的結果就是 2,它代表溫度整數部分的十位數字。OLED_ShowNum(74,2,k/10,3,3);
表示在 OLED 屏幕的x
坐標為 74、y
坐標為 2 的位置,以長度為 3、字體大小為 3 顯示溫度整數部分的十位數字。OLED_ShowNum(82,2,k%10,3,3);
:
k%10
:這是取模運算,用于獲取k
除以 10 的余數。若k
的值為 25,那么k%10
的結果就是 5,它代表溫度整數部分的個位數字。OLED_ShowNum(82,2,k%10,3,3);
表示在 OLED 屏幕的x
坐標為 82、y
坐標為 2 的位置,以長度為 3、字體大小為 3 顯示溫度整數部分的個位數字。OLED_ShowNum(98,2,kkk,3,3);
:
kkk
存儲的是溫度的小數部分。OLED_ShowNum(98,2,kkk,3,3);
表示在 OLED 屏幕的x
坐標為 98、y
坐標為 2 的位置,以長度為 3、字體大小為 3 顯示溫度的小數部分。
?濕度顯示
OLED_ShowNum(88,5,kk/10,3,3);
:
kk/10
:同樣是整數除法運算,用于獲取濕度整數部分的十位數字。OLED_ShowNum(88,5,kk/10,3,3);
表示在 OLED 屏幕的x
坐標為 88、y
坐標為 5 的位置,以長度為 3、字體大小為 3 顯示濕度整數部分的十位數字。OLED_ShowNum(98,55,kkk%10,3,3);
:
- 這里存在一個問題,
kkk
存儲的是溫度的小數部分,而此處本意可能是要顯示濕度的小數部分(按照 DHT11 數據格式,濕度小數部分應從Rxbuff[1]
獲取)。并且y
坐標為 55 可能超出了 OLED 屏幕的有效范圍,會導致顯示異常。kkk%10
獲取kkk
除以 10 的余數。OLED_ShowNum(98,55,kkk%10,3,3);
表示在 OLED 屏幕的x
坐標為 98、y
坐標為 55 的位置,以長度為 3、字體大小為 3 顯示數據,但這里數據和坐標可能都有誤。