為了方便之後的實做顯示結果,本篇將在初步瞭解 GPIO 的作法後,使用 I2C 介面,將結果的值顯示在 0.96 吋的 OLED 顯示器上。這個顯示器的型號是 SSD1306,找到了一個生產顯示器廠商提供的驅動程式,獨立產生一個 oled.c 及 oled.h(定義顯示字型的 oledfont.h 及定義圖形的 bmp.h 檔)。
開啟 Keil 5 主程式,在左方樹狀目錄的 Target 按右鍵,出現選單視窗,選擇「Optios for Target...」或上方工具列的魔法棒(如下圖紅色框處),開啟選項設定視窗,如下圖:
點選「C/C++」頁簽,在下方點選 include 路徑的圖示,增加一個 \Hardware 的路徑,如下圖:
再增加一個 Group,名為 Hardware,將未來新增的 .c 檔案,都增加進來,方法請參考前一篇文章,或按下下圖上方紅色處圖示,來增加 Group 及 .c 檔案。
I2C 總線有三種傳輸速率可供選擇:標準模式(可達100 kbit/s)、快速模式(可達400 kbit/s)和高速模式(可達3.4 Mbit/s),STM32 僅使用標準和快速兩種模式。STM32F103x 的 I2C 總線通信接口,其內部結構如下圖:
① 引腳:I2C 的硬體架構只需要兩個引腳(SDA和SCL),SMBA 線用於 SMBUS 的警告訊號,I2C 通訊用不到。
② 時鐘訊號:SCL 的時鐘訊號,透過設定時鐘控制暫存器(ClockControl Register,CCR)控制,設定 SCL 的頻率,可選擇「標準(100 kbit/s)/快速(400 kbit/s)」模式。
③ 資料收發:I2C 的 SDA 信號主要連接到資料移位暫存器(Data Shift Register,DSR)上,DSR的資料來源及目標是資料暫存器(Data Register,DR),傳送資料時,傳送的位元組寫入 DR 暫存器,硬體會把 DR 中的位元組搬到 DSR 中,然後在時鐘訊號的配合下,把 DSR 最高位的資料放到傳輸線 SDA 上,並對 DSR 進行移位元運算。接收資料時,資料控制器(Data Control)根據時鐘訊號,把 SDA 線上的高低電平轉換為「1」或「0」的資料,寫到 DSR 的最低位,同時 DSR 移位元運算,當接收完一個位元組的 8 位元資料後,把 DSR 中的資料搬到DR暫存器中。
④ 控制邏輯:控制邏輯負責協調整個 I2C 外設,控制邏輯可改變「控制暫存器(Control Register 1,CR1)和(Control Register 2,CR2)」來達成觸發起始和停止訊號,做出ACK響應,設定外設時脈頻率,開啟DMA和中斷的功能。外設工作時,控制邏輯會根據外設的工作狀態修改「狀態暫存器(Status Register 1,SR1)和(Status Register 2,SR2)」,只要讀取這些暫存器相關的暫存器位,就可以知道當前匯流排是否被佔用,本機是主裝置還是從裝置,資料是否傳送完畢等 I2C 的工作狀態。
除了上述簡單的說明外,要使用 STM32 開發板控制 I2C 接收/傳送資料,還有很多內容需要瞭解,之後再以實做來補充。
原因是宣告不能在執行語句後面,也就是說變數的宣告需寫在使用語句的前面,否則會導致程序在編譯時找不到這個變數,造成錯誤。解決方法是在選項(下圖左上紅框處圖示)中勾選「C99」(下圖下方方框處),讓編譯時,可以將宣告和語句混用,就像在 C 語言中一樣。如下圖:
[設定include路徑及程式]
上一篇使用 LED 作為交通燈號,僅使用 GPIO 的引腳設定為 on/off,就可以控制 LED 燈亮或滅,這篇開始會將每種硬體(如 OLED、LCD1602...等)的驅動函式放在 Hardware 目錄中。首先在專案的根路徑下,新增一個 Hardware 資料夾,與 System、User 同一層目錄。開啟 Keil 5 主程式,在左方樹狀目錄的 Target 按右鍵,出現選單視窗,選擇「Optios for Target...」或上方工具列的魔法棒(如下圖紅色框處),開啟選項設定視窗,如下圖:
點選「C/C++」頁簽,在下方點選 include 路徑的圖示,增加一個 \Hardware 的路徑,如下圖:
再增加一個 Group,名為 Hardware,將未來新增的 .c 檔案,都增加進來,方法請參考前一篇文章,或按下下圖上方紅色處圖示,來增加 Group 及 .c 檔案。
[STM32F103x的I2C]
I2C 是一種使用多主從架構的串列通訊協議,只需要兩條線:數據線(SDA)和時鐘線(SCL)即可進行通訊。I2C模組接收和發送資料,將資料從串列轉換成並行,或平行轉換成串列。I2C 總線有三種傳輸速率可供選擇:標準模式(可達100 kbit/s)、快速模式(可達400 kbit/s)和高速模式(可達3.4 Mbit/s),STM32 僅使用標準和快速兩種模式。STM32F103x 的 I2C 總線通信接口,其內部結構如下圖:
① 引腳:I2C 的硬體架構只需要兩個引腳(SDA和SCL),SMBA 線用於 SMBUS 的警告訊號,I2C 通訊用不到。
② 時鐘訊號:SCL 的時鐘訊號,透過設定時鐘控制暫存器(ClockControl Register,CCR)控制,設定 SCL 的頻率,可選擇「標準(100 kbit/s)/快速(400 kbit/s)」模式。
③ 資料收發:I2C 的 SDA 信號主要連接到資料移位暫存器(Data Shift Register,DSR)上,DSR的資料來源及目標是資料暫存器(Data Register,DR),傳送資料時,傳送的位元組寫入 DR 暫存器,硬體會把 DR 中的位元組搬到 DSR 中,然後在時鐘訊號的配合下,把 DSR 最高位的資料放到傳輸線 SDA 上,並對 DSR 進行移位元運算。接收資料時,資料控制器(Data Control)根據時鐘訊號,把 SDA 線上的高低電平轉換為「1」或「0」的資料,寫到 DSR 的最低位,同時 DSR 移位元運算,當接收完一個位元組的 8 位元資料後,把 DSR 中的資料搬到DR暫存器中。
④ 控制邏輯:控制邏輯負責協調整個 I2C 外設,控制邏輯可改變「控制暫存器(Control Register 1,CR1)和(Control Register 2,CR2)」來達成觸發起始和停止訊號,做出ACK響應,設定外設時脈頻率,開啟DMA和中斷的功能。外設工作時,控制邏輯會根據外設的工作狀態修改「狀態暫存器(Status Register 1,SR1)和(Status Register 2,SR2)」,只要讀取這些暫存器相關的暫存器位,就可以知道當前匯流排是否被佔用,本機是主裝置還是從裝置,資料是否傳送完畢等 I2C 的工作狀態。
除了上述簡單的說明外,要使用 STM32 開發板控制 I2C 接收/傳送資料,還有很多內容需要瞭解,之後再以實做來補充。
[材料]
- STM32F103C8T6 主板 x 1
- OLED SSD1306 顯示器 x1
- 麵包板 x1
- LED 紅、黃、綠 各 1 個
- STLINK V2 模擬下載器 x 1
- 連接線 x N 條
[接線與電路圖]
LED 正極(長腳)都接到 3.3v,另一腳,分別接在 STM32F103 的 PA0、PA1 及 PA2。STM32F103x 連接 SSD1306 如下表:STM32F103x | SSD1306 OLED |
---|---|
3.3v | VDD |
GND | GND |
B8 | SCK/SCL |
B9 | SDA |
[程式]
主程式一開始先對 OLED 進行初始化,再設定三個引腳 GPIO 連接三個 LED燈,進入迴圈循環時,紅綠燈亮時各等待三秒,黃燈亮時等待一秒,主程式 main.c 如下:/********************************************************** * @file main.c * @date 2022/3/27 * @brief traffic light with OLED * VDD - 3.3V * GND - GND * SCL/SCK - PB8 // IIC時鐘信號 * SDA - PB9 // IIC資料信號 **********************************************************/ #include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" #include "delay.h" #include "sys.h" #include "oled.h" #include "bmp.h" GPIO_InitTypeDef GPIO_InitStruct; // 定義一個GPIO_InitTypeDef類型的結構體 int main (void) { delay_init(); //延時函數初始化 NVIC_Configuration(); //設置 NVIC 中斷分組2:2位搶佔優先順序,2位回應優先順序 OLED_Init(); //初始化OLED OLED_Clear(0); //清除螢幕 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //開啟GPIOA的外設時鐘 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2; //選擇要控制的GPIOA引腳 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; //設置引腳速率為2MHz GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //設置引腳為通用推輓輸出模式 GPIO_Init(GPIOA, &GPIO_InitStruct); //調用庫函數,初始化GPIOA5引腳 // 將引腳設為高電位,此時三個燈熄滅 GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2); OLED_Init(); OLED_ShowString(1, 2, "Welcome... ",16); OLED_ShowString(1, 4, "Stop Light Proj",16); delay_s(3); OLED_Clear(0); while (1) { GPIO_ResetBits(GPIOA, GPIO_Pin_0); // 點亮紅燈 OLED_ShowString(6, 3, "Red ",16); delay_s(3); // 停 3 秒 GPIO_SetBits(GPIOA, GPIO_Pin_0); // 熄滅紅燈 GPIO_ResetBits(GPIOA, GPIO_Pin_1); // 點亮黃燈 OLED_ShowString(6, 3, "Yellow",16); delay_s(1); GPIO_SetBits(GPIOA, GPIO_Pin_1); // 熄滅黃燈 GPIO_ResetBits(GPIOA, GPIO_Pin_2); // 點亮綠燈 OLED_ShowString(6, 3, "Green ",16); delay_s(3); GPIO_SetBits(GPIOA, GPIO_Pin_2); // 熄滅綠燈 } }由於引用的 OLED 等相關程式數量較多,我將程式放在 Github:2. LED Traffic light with OLED,有興趣的朋友可以下載來試試。
[編譯錯誤#268]
程式寫完在第一次編譯時,出現一個錯誤的提示:oled.c(45): error: #268: declaration may not appear after executable statement in block找到網路上的解決方法:ARMCC: Error #268: Declaration may not appear after Exec
原因是宣告不能在執行語句後面,也就是說變數的宣告需寫在使用語句的前面,否則會導致程序在編譯時找不到這個變數,造成錯誤。解決方法是在選項(下圖左上紅框處圖示)中勾選「C99」(下圖下方方框處),讓編譯時,可以將宣告和語句混用,就像在 C 語言中一樣。如下圖:
張貼留言