天天看點

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

第二十六章 OLED實驗​

本章我們來學習使用OLED液晶顯示屏,在開發闆上我們預留了OLED子產品接口,需要準備一個OLED顯示子產品。下面我們一起來點亮OLED,并實作顯示字元和圖檔。​

本章分為如下幾個小節:​

26.1、字元編碼;​

26.2、制作字模;​

26.3、OLED簡介;​

26.4、OLED字元、數字顯示實驗;​

26.5、OLED顯示圖檔實驗;​

26.6、OLED顯示動圖實驗;​

26.7、OLED顯示中文字型;​

26.1 字元編碼​

計算機中存儲的資訊是以二進制的0或1來表示的,我們在螢幕上看到的漢字、英文和數字等資訊是經過二進制轉換後的結果。按照某種規則将字元存儲在計算機中,例如ASCII ​​字元集​​中,字元’a’用十進制的97來表示,字元’A’用十進制的65來表示,我們稱為“編碼”,反之,将計算機中的二進制資料解釋出來,我們稱為“解碼”。​

字元集是各種文字和符号的集合,常見的字元集有ASCII字元集、GB2312字元集、BIG5字元集、Unicode字元集等。下面我們會介紹ASCII字元集,關于其它字元集,如果想深入了解,大家可以參考百度百科詳細說明。計算機要準确的處理各種字元集的文字,就需要進行字元編碼。字元編碼也稱作字集碼,它是一套編碼規則,是資訊處理的一項基本技術,其在符号集與數字系統之間建立對應關系,将符号轉換為計算機可以夠識别和存儲的數字。​

ASCII碼使用7位2進制數表示一個字元,7位2進制數可以表示出27個字元,共128個字元,其中有 96 個可列印字元,包括常用的字母、數字、标點符号等,另外還有 32 個​​控制字元​​,控制字元中,如LF(換行)、CR(回車)、FF(換頁)、DEL(删除)、BS(倒退)、BEL(振鈴)等,如下圖中是ASCII字元代碼表:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.1. 1字元代碼表ASCII字元代碼表​

本章實驗,我們會使用以下ASCII字元集來顯示英文字元和數字(第一個字元是空格): !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ ​

26.2 制作字模​

26.2.1 像素​

圖像顯示是用點陣的方式來顯示的,螢幕上一個個很細小的點就是像素,像素點就類似一個燈(在 OLED 顯示器中,像素點就是一個小燈),通過控制這些小燈的亮和滅可以顯示不同的圖像。像素是圖像最基本的機關,螢幕上像素點越小,像素密度越高的,圖像的像素越高,顯示的效果就越清晰。高分辨率的圖像要比低分辨率的圖像包含更多的像素,是以同樣大小的圖像,使用高分辨率的螢幕比低分辨率的螢幕來顯示更清晰。提起顯示器,我們都會聽到 720P、1080P、2K 或 4K 這樣的字眼,1080P 的意思就是一個螢幕上的像素數量是1920*1080 個,也就是這個螢幕一列 1080 個像素點,一共 1920 列,如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.1. 1像素點和分辨率關系示意圖​

上圖中,X 軸就是1080P顯示器的橫軸,Y 軸就是1080P顯示器的豎軸。圖中的小方塊就是像素點,一共有 1920*1080=2073600 個像素點。左上角的 A 點是第一個像素點,右下角的 C 點就是最後一個像素點。​

26.2.2 字模​

1. 字模簡介​

字模就是字元或者圖像在點陣上顯示時對應的編碼,字模也就是我們要計算機識别的符号或者圖像的資料,以數字來表示,因為計算機隻能識别數字0和1,是以我們要先把圖像或者符号通過字模軟體轉化成字模。如下圖,如果我們要顯示一個英文字母“B”,以8*16個二進制資料位來表示,每個二進制資料位記錄一個像素點的狀态,則一個英文字母需要8*16/8=16個位元組的二進制資料位來表示。字元的寬為1個位元組,高為2個位元組:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 1顯示英文字元​

如果是漢字,漢字字元是英文字元的兩倍,一個漢字則需要16*16/8=32個位元組的二進制資料位來表示,字元的寬為2個位元組,高也為2個位元組,即漢字字元的寬是英文字元的寬的2倍:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 2顯示中文字型​

2. 制作字模​

制作字模,有很多的優秀的軟體,本實驗我們使用“開發闆CD光牒A-基礎資料\3、軟體\PCtoLCD2002完美版”字模軟體來制作字模,該軟體可以提供各種字元包括漢字(字型和大小都可以自己設定)點陣提取,且取模方式可以設定好幾種,常用的取模方式,該軟體都支援。該軟體除了支援字元模式,還支援圖形模式,也就是使用者如果要顯示一張圖檔的話,可以自己定義圖檔的大小,然後手動畫圖,或者可以導入一張已有的圖檔,該軟體根據圖檔提取出點陣資料。下面我們來介紹此字模軟體的使用方法。​

(1)字元模式​

①輕按兩下打開字模軟體PCtoLCD2002完美版:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 3完美版​

②對于要顯示字元,我們先把要顯示的字元輸入字元輸入框中,然後點選設定選項設定取模方式:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 4輸入字元​

③然後我們設定取模方式,如下圖,紅框處可以配置,其它部分我們先保持預設:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 5設定取模方式​

點陣格式可以選擇陰碼或者陽碼,字模也就是像素點的資料,每一個點都需要一個bit位來存儲,該位為0代表該像素點不顯示,為1代表顯示。字模點陣中有筆迹像素位的狀态是“1”(亮),沒有筆記像素位的狀态是“0”(滅)的方式為陰碼;字模點陣中有筆迹像素位的狀态是“0”(滅),沒有筆記像素位的狀态是“1”(亮)的方式為陽碼。本節實驗中選擇陰碼。​

取模走向可以選擇順向或者逆向,也就是螢幕上同列中上行與下行哪個行對應生成位元組的高位還是低位的問題,順向就是螢幕下行屬于高位,逆向就是螢幕上行屬于低位。本節試驗選擇順向。​

每行顯示資料可以自行配置,點陣表示生成的字模中,每行最大可以顯示多少個資料,索引則表示産生的索引中,每行顯示多少個索引值。​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 6點陣資料​

例如配置為“點陣:12 索引:12”,以下字模每行最大為12個資料:​

{0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4},​
{0x00,0x1C,0x00,0x04},/*"A",0*/      

字寬和字高,字寬和字高可以自行配置,會有英文的字寬和字寬以及中文的字寬和字寬,我們前面也說過,漢字的字寬是英文字寬的兩倍,如果配置漢字的字寬和字高都是16,那麼英文的字寬和字高分别是8和16:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 7設定字寬和字高​

自定義格式中,A51按彙編生成,C51按c格式生成,顯然我們是c程式設計,選擇C51即可。自定義格式處如果勾選後,我們可以配置生成的字模資料的格式。例如,如果生成的資料中不想要大括号“{}”,可以自定義去掉大括号:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 8自定義點陣資料的字首和字尾​

生成的點陣資料中就沒有了大括号:​

0x10,0x04,0x1F,0xFC,0x10,0x04,0x00,0x04,0x00,0x04,0x00,0x04,​

0x00,0x0C,0x00,0x00,/*"L",1*/​

取模方式有逐列式、逐行式、列行式和行列式。不同的取模方式需要結合不同的算法。在右上角的取模說明裡面有,即:從第一列開始向下每取8個點作為一個位元組,如果最後不足8個點就補滿8位。取模順序是從高到低,即第一個點作為最高位。如*-------取為10000000。其實就是按如下圖所示路徑的這種方式:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 9取模方式圖解​

從上到下,從左到右,高位在前。我們按這樣的取模方式,然後把ASCII字元集按12*6大小、16*8和24*12大小取模出來(對應漢字大小為12*12、16*16和24*24,字元的隻有漢字的一半大!),每個12*6的字元占用12個位元組,每個16*8的字元占用16個位元組,每個24*12的字元占用36個位元組。 ​

④點選生成字模,再儲存字模,可以選擇儲存生成的字模:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 10生成和儲存字模​

(2)圖形模式​

PCtoLCD2002完美版字模軟體支援BMP格式的圖檔,如果要對某一張點陣圖檔取模,需要将此圖檔轉化成.bmp格式的圖檔才可以。​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 11圖形模式​

如下圖,選擇圖形模式後,點選檔案à打開,選擇打開一張.bmp格式的檔案即可:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 12選擇打開圖形​

或者可以選擇檔案à建立,建立一張大小規定的圖像(如果螢幕很小,建議設定的長和寬要比螢幕小,這樣才可以顯示完全),然後手動畫圖:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.2.2. 13手動畫圖​

26.2.3 根據取模方式控制點陣顯示字元​

下面,我們先以顯示簡單的字元為例子,對字元顯示做一個簡單的講解,為了友善講解,此處講解的代碼先不在開發闆上運作,我們先在以前學習C語言的工具(例如Visual C++ 6.0)上操作實作。此處的代碼非常簡單,如果沒有安裝此軟體的也無關緊要,了解一遍代碼即可。​

1. 顯示英文字元​

(1)字模提取​

如果我們要顯示一個英文字元“A”,如下圖,設定字型為宋體,字寬和字高都為16,這個是漢字格式,那麼對應的英文格式字長就是8,字寬就是16。如果A這個字元按陰碼、順向、逐列式、十六進制方式取模(即從下到上、從左到右、從低位到高位取模),如下圖,因為點陣格式是陰碼,為1的地方表示亮,為0的地方表示滅:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 1設定取模方式​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 2英文字元A​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 3字元A對應的點陣資料​

那麼,取模得到的十六進制資料為:​

0x00,0x04,0x00,0x3C,0x03,0xC4,0x1C,0x40,0x07,0x40,0x00,0xE4,0x00,0x1C,0x00,0x04,/*"A",0*/      

不同的取模方式得到的資料不一樣,如果設定字型為宋體,字寬和字高都為16,我們的取模方式為陰碼、順向、行列式、十六進制方式提取(即從上到下、從右到左、從),則取模得到的十六進制的資料為:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 4設定取模方式​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 5字元A對應的點陣資料​

取模得到的點陣資料如下:​

0x00,0x00,0x00,0x10,0x10,0x18,0x28,0x28,0x24,0x3C,0x44,0x42,0x42,0xE7,0x00,0x00,/*"A",0*/​

(2)程式實作​

以上字模提取過程稱為編碼,不同的取模方式,算法會有些差别,我們使用程式将字元“A”列印出來,使用以上陰碼、順向、行列式、十六進制取模方式的編碼資料,在Visual C++ 6.0下的程式如下:​

1 #include <stdio.h> ​
2 #include <stdlib.h> ​
3 ​
4 unsigned char ch[] = {0x00,0x00,0x00,0x10,0x10,0x18,0x28,0x28,0x24,0x3C,0x44,0x42,0x42,0xE7,0x00,0x00}; ​
5 ​
6 void showA(){ ​
7 int i,j; ​
8 unsigned char t; ​
9 for (i = 0; i < 16; ++i)  /* 總共16個十六進制資料 */ ​
10 { ​
11 t = ch[i];   /* 依次取出以上數組的資料​
12 for (j = 0; j < 8; ++j)  /* 對于某行中的每個點​
13 { ​
14 if (0x80 & t)​
15 { ​
16 printf("*"); 從左到右如果最左位為1,則顯示*号 */​
17 }​
18 else​
19 { ​
20 printf(" "); /* 從左到右如果最左位為0,則顯示空格 */​
21 } ​
22 t <<= 1; /* 将右邊的資料往左移動 */​
23 } ​
24 printf("\n"); ​
25 } ​
26 } ​
27 ​
28 int main(void) { ​
29 showA(); ​
30 return EXIT_SUCCESS; ​
31 }      

我們簡單分析以上代碼的實作邏輯。​

第4行是講取模得到的資料按照順序排列到一個一維數組中;​

第9行,一維數組共16個資料,每個資料代表一行(一行有8位),使用for循環依次取出這16個資料;​

第11行,依次取出的資料存放到變量t中;​

第12行,每個資料是8bit的, j表示這8個位的第幾位,有0~7個位,0表示第0位,每個位表示一個像素點。​

第14~17行,将這8位的每個位進行判斷,如果某位為1,則列印*号;​

第18~21行,如果某位為0,則列印空格;​

第22行,t左移,從第0個資料開始,直到将這16個資料都判斷完畢為止。​

第28~30行是固定格式,使用控制台輸出。​

以上代碼使用for循環嵌套,對資料逐位進行判斷。​

編譯無報錯,執行後控制台列印出A,效果如下:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 6運作結果​

2. 顯示中文字元​

我們前面說過,一個中文字元的字寬是一個英文字元的兩倍,每個漢字每行有16個像素點,即2個位元組的像素點,是以每行需要對2個位元組的二進制資料進行列印,參考前面英文字元的顯示方法,程式中我們将漢字分為左半部分和右半部分來實作,是以要再增加一個for循環。​

(1)漢字取模​

前面我們說了,取模方式的不同,算法會有差别,前面的英文字元顯示我們是采用陰碼、順向、行列式、十六進制取模方式。這裡我們設定字型為宋體,字寬和字高都為16,使用陰碼、順向、逐行式、十六進制取模方式:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 7設定取模方式​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 8字型效果​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 9點陣資料排列​

取模後點陣的資料為:​

0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,​
0x02,0x80,0x02,0x80,0x04,0x40,0x04,0x40,0x08,0x20,0x10,0x10,0x20,0x08,0xC0,0x06,/*"大",0*/      

(2)程式實作​

取模的資料中有兩行十六進制資料,先以第一行的資料為例,第0個資料是左半邊,第1個資料是右半邊,第2個資料是左半邊,第3個資料是右半邊,也就是排序中,偶數對應左半邊,奇數對應右半邊,依此類推,資料交替存放。程式實作的代碼如下:​

1 #include <stdio.h> ​
2 #include <stdlib.h> ​
3 ​
4 unsigned char ch[] = { ​
5 0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,​
6 0x02,0x80,0x02,0x80,0x04,0x40,0x04,0x40,0x08,0x20,0x10,0x10,0x20,0x08,0xC0,0x06​
7 }; ​
8 ​
9 void show() ​
10 { ​
11 int i, j; ​
12 unsigned char f, s; /*表示一個漢字每行的左半邊的8像素和右半邊的8像素 */​
13 for (i = 0; i < 16; ++i) /* 16個組合 */​
14 { ​
15 f = ch[i * 2]; /* 取出左半邊的資料 */​
16 s = ch[i * 2 + 1]; /* 取出右半邊的資料 */​
17​
18 /* 先判斷中文字元的左半邊的資料 */​
19​
20 for (j = 0; j < 8; ++j) /* 每個資料8位 */​
21 { ​
22 if (0x80 & f) ​
23 { ​
24 printf("*"); /* 從左到右,如果某位為1,則顯示*号 */​
25 } ​
26 else ​
27 { ​
28 printf(" "); /* 從左到右,如果某位為0,則顯示空格 */​
29 } ​
30 f <<= 1; /* 将右邊的資料往左移動 */​
31 } ​
32​
33 /* 再判斷中文字元右半邊的資料 */​
34​
35 for (j = 0; j < 8; ++j)/* 每個資料8位 */ ​
36 { ​
37 if (0x80 & s) ​
38 { ​
39 printf("*");/* 從左到右,如果某位為1,則顯示*号 */ ​
40 } ​
41 else ​
42 { ​
43 printf(" "); /* 從左到右,如果某位為0,則顯示空格 */​
44 } ​
45 s <<= 1; /* 将右邊的資料往左移動 */​
46 } ​
47 printf("\n"); ​
48 } ​
49 } ​
50 ​
51 int main(void) { ​
52 show(); ​
53 return EXIT_SUCCESS; ​
54 }      

第12行,定義兩個變量,f用于左半邊的資料計算,f是偶數;s用于右半邊的資料計算,s是奇數;​

第13行,一維數組中有兩行,每行16個十六進制的資料;​

第15行,依次取出左半邊的資料,​

第16行,依次取出右半邊的資料;​

第20~31行,像前面顯示英文字元那樣,将漢字的左半邊顯示出來;​

第35行~46行,像前面顯示英文字元一樣,将漢字的右半邊顯示出來;​

編譯不報錯,運作程式,結果如下:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 10編譯運作效果​

3. 顯示圖檔或者動圖​

關于顯示圖檔和動圖,這裡就不列出代碼了,我們後面會有專門的實驗。​

(1)顯示一張圖檔​

如果隻是想顯示一張圖檔,隻需要将此圖檔轉化成.bmp格式的圖檔,再取模即可。要注意的是顯示螢幕的分辨率,如果螢幕的分辨率比圖檔的分辨率要小,則螢幕上無法顯示完全圖檔,可以修改圖檔的分辨率以後再進行取模。可以使用windows自帶的畫圖工具先打開要修改的.bmp格式的檔案,打開以後再手動修改像素:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 11小改像素​

(2)顯示動圖​

如果要顯示動圖,如果隻有一張.bmp格式的圖檔,可以通過程式将圖檔移動位置,如果是.gif格式的動圖檔案,可以使用gif分離器軟體,例如開發闆CD光牒A-基礎資料\3、軟體下的GIF2BMP軟體,将動圖拆分成一張張的.bmp格式的檔案,然後再對每張圖檔取模。其實動圖也就是由一幀幀的圖檔組合成的,拆分出的每一張.bmp格式的圖檔都是一幀圖。​

輕按兩下打開Gif分離器zhs9.exe,然後選擇要分離的.gif格式檔案以及分離後的檔案儲存路徑,再點選開始分離,軟體則進行分離。​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 12分離圖檔​

為了友善大家,在開發闆CD光牒A-基礎資料\3、軟體\GIF2BMP下有放一張動圖檔案,檔案名為2323.gif,像素為120*60幀。如下圖,将2323.gif的動圖分離後,最後得到58張.bmp格式的檔案,即此動圖有58幀:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

26.2.3. 13分離後的圖檔​

後面的實驗中,我們會選出其中的10張圖檔在OLED上顯示出一個動圖。​

26.3 OLED簡介​

26.3.1 OLED簡介​

OLED,即有機發光二極管(Organic Light-Emitting Diode),又稱為有機電雷射顯示(Organic Electroluminesence Display, OELD)。OLED由于同時具備自發光,不需背光源、對比度高、厚度薄、視角廣、反應速度快、可用于撓曲性面闆、使用溫度範圍廣、構造及制程較簡單等優異之特性,被認為是下一代的平面顯示器新興應用技術。​

LCD都需要背光,而OLED不需要,因為它是自發光的。這樣同樣的顯示,OLED效果要來得好一些。以目前的技術,OLED的尺寸還難以大型化,但是分辨率确可以做到很高。在本章中,我們使用的是ALINETEK的OLED顯示子產品,該子產品有以下特點:​

1)子產品有單色和雙色兩種可選,單色為純藍色,而雙色則為黃藍雙色。​

2)尺寸小,顯示尺寸為0.96寸,而子產品的尺寸僅為27mm*26mm大小。​

3)高分辨率,該子產品的分辨率為128*64。​

4)多種接口方式,該子產品提供了總共4種接口包括:6800、8080兩種并行接口方式、4線SPI接口方式以及IIC接口方式(隻需要2根線就可以控制OLED了!)。​

5)不需要高壓,直接接3.3V就可以工作了。​

26.3.2 OLED的模式簡介​

這裡要提醒大家的是,該子產品不和5.0V接口相容,是以請大家在使用的時候一定要小心,别直接接到5V的系統上去,否則可能燒壞子產品。以下4種模式通過子產品的BS1和BS2設定,BS1和BS2的設定與子產品接口模式的關系如下表所示:​

接口方式​ 4線SPI​ IIC​ 8位6800​ 8位8080​
BS1​ 0​ 1​ 0​ 1​
BS2​ 0​ 0​ 1​ 1​

表26.3.2. 1子產品接口方式設定表​

表中:“1”代表接VCC,而“0”代表接GND。​

該子產品的外觀圖如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 1子產品外觀圖​

ALIENTEK OLED子產品預設設定是:BS1和BS2接VCC ,即使用8080并口方式,如果你想要設定為其他模式,則需要在OLED的背面,用烙鐵修改BS1和BS2的設定。​

子產品的原理圖如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 2子產品原理圖​

該子產品采用8*2的2.54排針與外部連接配接,總共有16個管腳,在16條線中,我們隻用了15條,有一個是懸空的。15條線中,電源和地線占了2條,還剩下13條信号線。​

在不同模式下,我們需要的信号線數量是不同的,在8080模式下,需要全部13條,而在IIC模式下,僅需要2條線就夠了!這其中有一條是共同的,那就是複位線RST(RES),RST上的低電平,将導緻OLED複位,在每次初始化之前,都應該複位一下OLED子產品。​

ALIENTEK OLED子產品的控制器是SSD1306,本章,我們将學習如何通過STM32H750來控制該子產品顯示字元和數字,本章的執行個體代碼将可以支援兩種方式與OLED子產品連接配接,一種是8080的并口方式,另外一種是4線SPI方式。​

1. 8080并行接口方式​

首先我們介紹一下子產品的8080并行接口,8080并行接口的發明者是INTEL,該總線也被廣泛應用于各類液晶顯示器,ALIENTEK OLED子產品也提供了這種接口,使得MCU可以快速的通路OLED。ALIENTEK OLED子產品的8080接口方式需要如下一些信号線:​

CS:OLED片選信号。​

WR:向OLED寫入資料。​

RD:從OLED讀取資料。​

D[7:0]:8位雙向資料線。​

RST(RES):硬複位OLED。​

DC:指令/資料标志(0,讀寫指令;1,讀寫資料)。​

子產品的8080并口讀/寫的過程為:先根據要寫入/讀取的資料的類型,設定DC為高(資料)/低(指令),然後拉低片選,選中SSD1306,接着我們根據是讀資料,還是要寫資料置RD/WR為低,然後:​

在RD的上升沿, 使資料鎖存到資料線(D[7:0])上;​

在WR的上升沿,使資料寫入到SSD1306裡面;​

SSD1306的8080并口寫時序圖如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 3并口寫時序圖​

SSD1306的8080并口讀時序圖如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 4 8080并口讀時序圖​

SSD1306的8080接口方式下,控制腳的信号狀态所對應的功能如下表所示:​

功能​ RD​ WR​ CS​ DC​
寫指令​ H​ ↑​ L​ L​
讀狀态​ ↑​ H​ L​ L​
寫資料​ H​ ↑​ L​ H​
讀資料​ ↑​ H​ L​ H​

表26.3.2. 2控制腳信号狀态功能表​

在8080方式下讀資料操作的時候,我們有時候(例如讀顯存的時候)需要一個假讀命(Dummy Read),以使得微控制器的操作頻率和顯存的操作頻率相比對。在讀取真正的資料之前,由一個的假讀的過程。這裡的假讀,其實就是第一個讀到的位元組丢棄不要,從第二個開始,才是我們真正要讀的資料。​

一個典型的讀顯存的時序圖,如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 5讀顯存時序圖​

可以看到,在發送了列位址之後,開始讀資料,第一個是Dummy Read,也就是假讀,我們從第二個開始,才算是真正有效的資料。​

2. 4線串行(SPI)方式​

我們的代碼同時相容SPI方式的驅動,如果你使用的是這種驅動方式,則應該把代碼中的宏OLED_MODE設定為:​

#define OLED_MODE 0 /* 0: 4線串行模式 */​

接下來介紹一下4線串行(SPI)方式,4順序列槽模式使用的信号線有如下幾條:​

CS:OLED片選信号。​

RST(RES):硬複位OLED。​

DC:指令/資料标志(0,讀寫指令;1,讀寫資料)。​

SCLK:串行時鐘線。在4線串行模式下,D0信号線作為串行時鐘線SCLK。​

SDIN:串行資料線。在4線串行模式下,D1信号線作為串行資料線SDIN。​

子產品的D2需要懸空,其他引腳可以接到GND。在4線串行模式下,隻能往子產品寫資料而不能讀資料。​

在4線SPI模式下,每個資料長度均為8位,在SCLK的上升沿,資料從SDIN移入到SSD1306,并且是高位在前的。DC線還是用作指令/資料的标志線。在4線SPI模式下,寫操作的時序如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 6線SPI寫操作時序圖​

4線串行模式就為大家介紹到這裡。其他還有幾種模式,在SSD1306的資料手冊《SSD1306-Revision 1.1 (Charge Pump)》上都有詳細的介紹,如果要使用這些方式,請大家參考該手冊,手冊位于“開發闆CD光牒A-基礎資料\6、硬體資料\1、晶片資料\【正點原子】0.96寸OLED子產品12864資料”路徑下。 ​

3. SSD1306的顯存​

接下來,我們介紹一下子產品的顯存,SSD1306的顯存總共為128*64bit大小,SSD1306将這些顯存分為了8頁,其對應關系如下表所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 7顯存與螢幕對應關系表​

可以看出,SSD1306的每頁包含了128個位元組,總共8頁,這樣剛好是128*64的點陣大小。當GRAM的寫入模式為頁模式時,需要設定低位元組起始的列位址(0x00~0x0F)和高位元組的起始列位址(0x10~0x1F),晶片手冊中給出了寫入GRAM與顯示的對應關系,寫入列位址在寫完一位元組後自動按列增長,如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 8與顯示的對應關系​

因為每次寫入都是按位元組寫入的,這就存在一個問題,如果我們使用隻寫方式操作子產品,那麼,每次要寫8個點,這樣,我們在畫點的時候,就必須把要設定的點所在的位元組的每個位都搞清楚目前的狀态(0/1?),否則寫入的資料就會覆寫掉之前的狀态,結果就是有些不需要顯示的點,顯示出來了,或者該顯示的沒有顯示了。這個問題在能讀的模式下,我們可以先讀出來要寫入的那個位元組,得到目前狀況,在修改了要改寫的位之後再寫進GRAM,這樣就不會影響到之前的狀況了。但是這樣需要能讀GRAM,對于4線SPI模式/IIC模式,子產品是不支援讀的,而且讀à改à寫的方式速度也比較慢。​

是以我們采用的辦法是在STM32MP157的内部建立一個虛拟的OLED的GRAM(共128*8=1024個位元組),在每次修改的時候,隻是修改STM32MP157上的GRAM(實際上就是SRAM),在修改完了之後,一次性把STM3MP157上的GRAM寫入到OLED的GRAM。當然這個方法也有壞處,一個是對于那些SRAM很小的單片機(比如51系列)不太友好,另一個是每次都寫入全屏,螢幕重新整理率會變低。​

4. SSD1306的指令​

SSD1306的指令比較多,這裡我們僅介紹幾個比較常用的指令,這些指令如下表所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 9常用指令表​

第一個指令為0X81,用于設定對比度的,這個指令包含了兩個位元組,第一個0X81為指令,随後發送的一個位元組為要設定的對比度的值。這個值設定得越大螢幕就越亮。​

第二個指令為0XAE/0XAF。0XAE為關閉顯示指令;0XAF為開啟顯示指令。​

第三個指令為0X8D,該指令也包含2個位元組,第一個為指令字,第二個為設定值,第二個位元組的BIT2表示電荷泵的開關狀态,該位為1,則開啟電荷泵,為0則關閉。在子產品初始化的時候,這個必須要開啟,否則是看不到螢幕顯示的。​

第四個指令為0XB0~B7,該指令用于設定頁位址,其低三位的值對應着GRAM的頁位址。​

第五個指令為0X00~0X0F,該指令用于設定顯示時的起始列位址低四位。​

第六個指令為0X10~0X1F,該指令用于設定顯示時的起始列位址高四位。​

其他指令,我們就不在這裡一一介紹了,大家可以參考SSD1306 datasheet的第28頁。從這頁開始,對SSD1306的指令有詳細的介紹。​

5. OLED初始化過程​

最後,我們再來介紹一下OLED子產品的初始化過程,SSD1306的典型初始化框圖如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.3.2. 10初始化框圖​

驅動IC的初始化代碼,我們直接使用廠家推薦的設定就可以了,隻要對細節部分進行一些修改,使其滿足我們自己的要求即可,其他不需要變動。​

26.4 OLED字元、數字顯示實驗​

本實驗配置好的實驗工程已經放到了開發闆CD光牒中,路徑為:開發闆CD光牒A-基礎資料\1、程式源碼\3、M4裸機驅動例程\庫V1.2\實驗15-1 OLED字元、數字顯示實驗。​

前面我們重點向大家介紹了ALIENTEK OLED子產品的相關知識,接下來我們将使用這個子產品來顯示字元和數字。通過以上介紹,我們可以得出OLED顯示需要的相關設定步驟如下:​

1)設定STM32MP157與OLED子產品相連接配接的IO​

這一步,先将我們與OLED子產品相連的IO口設定為輸出,具體使用哪些IO口,這裡需要根據連接配接電路以及OLED子產品所設定的通訊模式來确定。這些将在硬體設計部分向大家介紹。​

2)初始化OLED子產品​

其實這裡就是上面的初始化框圖的内容,通過對OLED相關寄存器的初始化,來啟動OLED的顯示。為後續顯示字元和數字做準備。​

3)通過函數将字元和數字顯示到OLED子產品上​

這裡就是通過我們設計的程式,将要顯示的字元送到OLED子產品就可以了,這些函數将在軟體設計部分向大家介紹。​

通過以上三步,我們就可以使用ALIENTEK OLED子產品來顯示字元和數字了。​

26.4.1 硬體設計​

1. 例程功能​

使用8080并口模式驅動或者使用4線SPI序列槽模式,驅動OLED子產品,不停的顯示ASCII碼和碼值。LED0閃爍,提示程式運作。​

2. 硬體資源​

1)LED燈​

LED0 - PI0​

2)ALIENTEK 0.96寸OLED子產品,在硬體上,OLED與開發闆的IO口對應關系如下:​

OLED_RS(OLED 子產品上的絲印是DC)對應12C5_SCL,即:PA11;​

OLED子產品上懸空/不接的引腳是DCMI_PIXCLK,即PA6;​

OLED子產品IO​ 開發闆IO​ STM32MP57晶片對應IO​
OLED_CS​ DCMI_VSYNC​ PB7​
OLED_RS​ 12C5_SCL​ PA11​
OLED_WR​ DCMI_HSYNC​ PH8​
OLED_RD​ I2C5_SDA​ PA12​
OLED_RST​ DCMI_RESET​ PA7​
NC​ DCMI_PIXCLK​ PA6​
OLED_D7​ DCMI_D7​ PE6​
OLED_D6​ DCMI_D6​ PB8​
OLED_D5​ DCMI_D5​ PI4​
OLED_D4​ DCMI_D4​ PH14​
OLED_D3​ DCMI_D3​ PH12​
OLED_D2​ DCMI_D2​ PH11​
OLED_D1​ DCMI_D1​ PH10​
OLED_D0​ DCMI_D0​ PH9​

表26.4.1. 1口對應表​

注意,這裡的OLED_D[7:0]因為不是接的連續的IO,是以後面的程式中得用拼湊的方式去組合一下,後續的程式部分會介紹到。​

3. 原理圖​

OLED子產品插在開發闆底闆的CAMERA接口上,接口的原理圖如下,對應的IO口在前面已有詳細說明了:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

表26.4.1. 2接口部分原理圖​

下面我們介紹OLED子產品與我們開發闆的連接配接,開發闆上有一個OLED/CAMERA的接口(P2接口)可以和ALIENTEK OLED子產品直接對插(靠左插!),這些線的連接配接,開發闆的内部已經連接配接好了,我們隻需要将OLED子產品插上去就好了,連接配接如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

表26.4.1. 3子產品連接配接示意圖​

26.4.2 程式設計​

OLED隻是用到HAL庫中GPIO外設的驅動代碼,配置步驟在前面跑馬燈實驗已經介紹了。​

1. 程式流程圖​

下面看看本實驗的程式流程圖:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.4.2. 1程式流程圖​

OLED驅動代碼需要我們手動添加,由三個檔案構成:oled.c、oled.h和oledfont.h。oledfont.h頭檔案存放的是ASCII字元集,oled.h存放的是引腳接口宏定義和函數聲明等,oled.c則是驅動代碼。​

2. 添加oledfont.h檔案代碼​

oledfont.h檔案用于存放ASICII字元集點陣資料,也就是将ASICII字元集取模後得到的資料,這裡我們把 ASCII 字元集按字寬和字高為12*12、16*16和24*24的大小取模出來。這裡我們以按字寬和字高為12*12為例子做講解。設定字型為隸書,字寬和字高都為12,取模方式設定:陰碼+逐列式+順向+C51格式,最後将以下ASICII碼字元拷貝到輸入框,注意,第一個字元是空格,ASCII字元集: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖19.4.2. 2設定取模方式​

最後将資料儲存在一個.tex檔案中,得到的資料如下:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖19.4.2. 3取模得到的點陣資料​

其它字寬和字高為16*16和24*24的資料生成也是同樣的方法。oledfont.h檔案的代碼我們已經在本實驗的工程中給出,大家可以直接使用。​

3. 添加oled.h檔案代碼​

1 #ifndef __OLED_H​
2 #define __OLED_H​
3 ​
4 #include "stdlib.h" ​
5 #include "./SYSTEM/sys/sys.h"​
6 ​
7 /* OLED模式設定​
8線串行模式 (子產品的BS1,BS2均接GND)​
9 并行8080模式 (子產品的BS1,BS2均接VCC)​
10  */​
11 #define OLED_MODE 1 /* 預設使用8080并口模式 */​
12 ​
13 /************************OLED SPI模式引腳定義*********************/​
14 /* ​
15注意:這裡僅定義了 OLED 4線SPI模式驅動時的引腳定義.​
16并口通路,由于引腳太多,就不單獨定義了​
17 */​
18 /* PE1引腳和時鐘使能定義 */​
19 #define OLED_SPI_RST_PORT GPIOE​
20 #define OLED_SPI_RST_PIN GPIO_PIN_1​
21 #define OLED_SPI_RST_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) ​
22 /* PB7引腳和時鐘使能定義 */​
23 #define OLED_SPI_CS_PORT GPIOB​
24 #define OLED_SPI_CS_PIN GPIO_PIN_7​
25 #define OLED_SPI_CS_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) ​
26 /* PA11引腳和時鐘使能定義 */​
27 #define OLED_SPI_RS_PORT GPIOA​
28 #define OLED_SPI_RS_PIN GPIO_PIN_11​
29 #define OLED_SPI_RS_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) ​
30 /* H9引腳和時鐘使能定義 */​
31 #define OLED_SPI_SCLK_PORT GPIOH​
32 #define OLED_SPI_SCLK_PIN GPIO_PIN_9​
33 #define OLED_SPI_SCLK_CLK_ENABLE() do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0) ​
34 /* H10引腳和時鐘使能定義 */​
35 #define OLED_SPI_SDIN_PORT GPIOH​
36 #define OLED_SPI_SDIN_PIN GPIO_PIN_10​
37 #define OLED_SPI_SDIN_CLK_ENABLE() do{ __HAL_RCC_GPIOH_CLK_ENABLE(); }while(0) ​
38 ​
39 /********OLED SPI和8080并口模式相關端口控制函數定義*****************/​
40 /* ​
41注意:OLED_RST/OLED_CS/OLED_RS,這三個是和80并口模式​
42共用的,即80模式也必須實作這3個函數!​
43  */​
44 #define OLED_RST(x) do{ x ? \​
45 HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_SET) : \​
46 HAL_GPIO_WritePin(OLED_SPI_RST_PORT, OLED_SPI_RST_PIN, GPIO_PIN_RESET); \​
47 }while(0) /* 設定RST引腳 */​
48 ​
49 #define OLED_CS(x) do{ x ? \​
50 HAL_GPIO_WritePin(OLED_SPI_CS_PORT, OLED_SPI_CS_PIN, GPIO_PIN_SET) : \​
51 HAL_GPIO_WritePin(OLED_SPI_CS_PORT, OLED_SPI_CS_PIN, GPIO_PIN_RESET); \​
52 }while(0) /* 設定CS引腳 */​
53 ​
54 #define OLED_RS(x) do{ x ? \​
55 HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_SET) : \​
56 HAL_GPIO_WritePin(OLED_SPI_RS_PORT, OLED_SPI_RS_PIN, GPIO_PIN_RESET); \​
57 }while(0) /* 設定RS引腳 */​
58 ​
59 #define OLED_SCLK(x) do{ x ? \​
60 HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_SET) : \​
61 HAL_GPIO_WritePin(OLED_SPI_SCLK_PORT, OLED_SPI_SCLK_PIN, GPIO_PIN_RESET); \​
62 }while(0) /* 設定SCLK引腳 */​
63 ​
64 #define OLED_SDIN(x) do{ x ? \​
65 HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_SET) : \​
66 HAL_GPIO_WritePin(OLED_SPI_SDIN_PORT, OLED_SPI_SDIN_PIN, GPIO_PIN_RESET); \​
67 }while(0) /* 設定SDIN引腳 */​
68 ​
69 /* OLED 80并口模式WR,RD端口控制函數 定義 */​
70 #define OLED_WR(x) do{ x ? \​
71 HAL_GPIO_WritePin(GPIOH, GPIO_PIN_8, GPIO_PIN_SET) : \​
72 HAL_GPIO_WritePin(GPIOH, GPIO_PIN_8, GPIO_PIN_RESET); \​
73 }while(0) /* 設定WR引腳 */​
74 ​
75 #define OLED_RD(x) do{ x ? \​
76 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET) : \​
77 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET); \​
78 }while(0) /* 設定RD引腳 */​
79 ​
80 /***************************指令/資料 定義*************************/ ​
81 ​
82 #define OLED_CMD 0 /* 寫指令 */​
83 #define OLED_DATA 1 /* 寫資料 */​
84 ​
85 /*******************************函數聲明**************************/​
86 ​
87 static void oled_wr_byte(uint8_t data, uint8_t cmd); /* 寫一個位元組到OLED */​
88 static uint32_t oled_pow(uint8_t m, uint8_t n); /* OLED求平方函數 */​
89 void oled_init(void); /* OLED初始化 */​
90 void oled_clear(void); /* OLED清屏 */​
91 void oled_display_on(void); /* 開啟OLED顯示 */​
92 void oled_display_off(void); /* 關閉OLED顯示 */​
93 void oled_refresh_gram(void); /* 更新顯存到OLED */ ​
94 /* OLED畫點函數 */​
95 void oled_draw_point(uint8_t x, uint8_t y, uint8_t dot);​
96 /* OLED區域填充函數 */ ​
97 void oled_fill(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t dot); ​
98 /* OLED顯示字元函數 */ ​
99 void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode);​
100 /* OLED顯示數字函數 */ ​
101 void oled_show_num(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size);​
102 /* OLED顯示字元串函數 */ ​
103 void oled_show_string(uint8_t x, uint8_t y, const char *p, uint8_t size); ​
104​
105 #endif      

第19~37行,為了和8080并口方式差別開來,重新定義了SPI模式的引腳;​

第44~67行,SPI和8080并口模式相關端口控制函數的定義;​

第70~78行,8080并口模式相關端口控制函數定義;​

第82~83,寫指令和寫資料宏定義;​

第87~103行,一些函數的聲明。​

4. 添加oled.c檔案代碼​

下面我們來重點分析oled.c檔案的代碼,此檔案中有很多函數,我們選擇分析幾個重要的函數。所有函數的代碼清檢視工程檔案中的oled.c檔案。​

(1)oled_init初始化函數​

1 /**​
2  * @brief初始化OLED(SSD1306) ​
3 * @param無​
4 * @retval無​
5  */​
6 void oled_init(void)​
7 {​
8 GPIO_InitTypeDef gpio_init_struct;​
9 /* 使能GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOH和GPIOI時鐘 */​
10 __HAL_RCC_GPIOA_CLK_ENABLE();​
11 __HAL_RCC_GPIOB_CLK_ENABLE();​
12 __HAL_RCC_GPIOC_CLK_ENABLE();​
13 __HAL_RCC_GPIOD_CLK_ENABLE();​
14 __HAL_RCC_GPIOE_CLK_ENABLE();​
15 __HAL_RCC_GPIOH_CLK_ENABLE();​
16 __HAL_RCC_GPIOI_CLK_ENABLE();​
17 ​
18 #if OLED_MODE==1    /* 使用8080并口模式 */​
19 /* 設定PA6,11,12 */​
20 gpio_init_struct.Pin=GPIO_PIN_6|GPIO_PIN_11|GPIO_PIN_12; ​
21 gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;  /* 推挽輸出 */​
22 gpio_init_struct.Pull=GPIO_PULLUP;  /* 上拉 */​
23 gpio_init_struct.Speed=GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */​
24 HAL_GPIO_Init(GPIOA,&gpio_init_struct);   /* 初始化GPIOA */​
25 ​
26 /* 設定PB7,8 */​
27 gpio_init_struct.Pin=GPIO_PIN_7|GPIO_PIN_8;​
28 HAL_GPIO_Init(GPIOB,&gpio_init_struct);  /* 初始化GPIOB */​
29 ​
30 /* 設定PE1,6 */​
31 gpio_init_struct.Pin=GPIO_PIN_1 | GPIO_PIN_6;​
32 HAL_GPIO_Init(GPIOE,&gpio_init_struct);  /* 初始化GPIOE */​
33 ​
34 /* 設定PH8,9,10,11,12,14 */​
35 gpio_init_struct.Pin=GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | \​
36 GPIO_PIN_11 |GPIO_PIN_12 | GPIO_PIN_14;​
37 HAL_GPIO_Init(GPIOH,&gpio_init_struct);  /* 初始化GPIOH */​
38 ​
39 /* 設定PHI4 */​
40 gpio_init_struct.Pin=GPIO_PIN_4;​
41 HAL_GPIO_Init(GPIOI,&gpio_init_struct);  /* 初始化GPIOI */​
42 ​
43 OLED_WR(1);/* WR電平拉高 */​
44 OLED_RD(1);/* RD電平拉高 */​
45 ​
46 #else /* 使用4線SPI 序列槽模式 */​
47 gpio_init_struct.Pin=OLED_SPI_RST_PIN;​
48 gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;  /* 推挽輸出 */​
49 gpio_init_struct.Pull=GPIO_PULLUP;   /* 上拉 */​
50 gpio_init_struct.Speed=GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */​
51 /* RST引腳模式設定 */​
52 HAL_GPIO_Init(OLED_SPI_RST_PORT,&gpio_init_struct); ​
53 ​
54 gpio_init_struct.Pin=OLED_SPI_CS_PIN;​
55 /* CS引腳模式設定 */​
56  HAL_GPIO_Init(OLED_SPI_CS_PORT,&gpio_init_struct); ​
57 ​
58 gpio_init_struct.Pin=OLED_SPI_RS_PIN;​
59 /* RS引腳模式設定 */​
60  HAL_GPIO_Init(OLED_SPI_RS_PORT,&gpio_init_struct); ​
61 ​
62 gpio_init_struct.Pin=OLED_SPI_SCLK_PIN;​
63 /* SCLK引腳模式設定 */​
64 HAL_GPIO_Init(OLED_SPI_SCLK_PORT,&gpio_init_struct);​
65 ​
66 gpio_init_struct.Pin=OLED_SPI_SDIN_PIN;​
67 /* SDIN引腳模式設定 */​
68 HAL_GPIO_Init(OLED_SPI_SDIN_PORT,&gpio_init_struct);​
69 ​
70 OLED_SDIN(1); /* 設定SDIN引腳 */​
71  OLED_SCLK(1);  /* 設定SCLK引腳 */​
72 #endif​
73 OLED_CS(1);​
74 OLED_RS(1);​
75 ​
76 OLED_RST(0);​
77 delay_ms(100);​
78 OLED_RST(1);​
79 ​
80 oled_wr_byte(0xAE, OLED_CMD); /* 關閉顯示 */​
81 oled_wr_byte(0xD5, OLED_CMD); /* 設定時鐘分頻因子,震蕩頻率 */​
82 oled_wr_byte(80, OLED_CMD); /* [3:0],分頻因子;[7:4],震蕩頻率 */​
83 oled_wr_byte(0xA8, OLED_CMD); /* 設定驅動路數 */​
84 oled_wr_byte(0X3F, OLED_CMD); /* 預設0X3F(1/64) */​
85 oled_wr_byte(0xD3, OLED_CMD); /* 設定顯示偏移 */​
86 oled_wr_byte(0X00, OLED_CMD); /* 預設為0 */​
87 ​
88 oled_wr_byte(0x40, OLED_CMD); /* 設定顯示開始行 [5:0],行數. */​
89 ​
90 oled_wr_byte(0x8D, OLED_CMD); /* 電荷泵設定 */​
91 oled_wr_byte(0x14, OLED_CMD); /* bit2,開啟/關閉 */​
92 oled_wr_byte(0x20, OLED_CMD); /* 設定記憶體位址模式 */​
93 /* [1:0],00,列位址模式;01,行位址模式;10,頁位址模式;預設10; */​
94 oled_wr_byte(0x02, OLED_CMD);​
95 /* 段重定義設定,bit0:0,0->0;1,0->127; */​
96 oled_wr_byte(0xA1, OLED_CMD); ​
97 /* 設定COM掃描方向;bit3:0,普通模式;1,重定義模式 COM[N-1]->COM0;N:驅動路數 */ ​
98 oled_wr_byte(0xC0, OLED_CMD); ​
99 oled_wr_byte(0xDA, OLED_CMD); /* 設定COM硬體引腳配置 */​
100 oled_wr_byte(0x12, OLED_CMD); /* [5:4]配置 */​
101​
102 oled_wr_byte(0x81, OLED_CMD); /* 對比度設定 */​
103 oled_wr_byte(0xEF, OLED_CMD); /* 1~255;預設0X7F (亮度設定,越大越亮) */​
104 oled_wr_byte(0xD9, OLED_CMD); /* 設定預充電周期 */​
105 oled_wr_byte(0xf1, OLED_CMD); /* [3:0],PHASE 1;[7:4],PHASE 2; */​
106 oled_wr_byte(0xDB, OLED_CMD); /* 設定VCOMH 電壓倍率 */​
107 /* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */​
108 oled_wr_byte(0x30, OLED_CMD); ​
109 /* 全局顯示開啟;bit0:1,開啟;0,關閉;(白屏/黑屏) */​
110 oled_wr_byte(0xA4, OLED_CMD);​
111 /* 設定顯示方式;bit0:1,反相顯示;0,正常顯示 */​
112 oled_wr_byte(0xA6, OLED_CMD); ​
113 oled_wr_byte(0xAF, OLED_CMD); /* 開啟顯示 */​
114 oled_clear();​
115 }      

該函數的結構比較簡單,開始是對GPIO口的初始化,這裡我們用了宏定義OLED_MODE來決定要設定的IO口,預設使用8080并口模式,後面的就是一些初始化序列了,我們按照廠家提供的資料來做就可以。值得注意一點的是,因為OLED是無背光的,在初始化之後,我們把顯存都清空了,是以我們在螢幕上是看不到任何内容的,就像沒通電一樣,不要以為這就是初始化失敗,要寫入資料子產品才會顯示的。​

(2)oled_data_out和oled_wr_byte函數​

oled_data_out函數是通過拼湊的方法向OLED輸出一個8位資料。因為資料引腳使用到的IO口并不是連續的IO口,一個8位的資料要通過這8個不連續的IO口發送出去的話,我們使用拼湊的方式,即:​

對于8080并口方式:​

D0​ PH9​ D4​ PH14​
D1​ PH10​ D5​ PI4​
D2​ PH11​ D6​ PB8​
D3​ PH12​ D7​ PE6​

表26.4.2. 1 8位資料線接口方式​

對于4線SPI序列槽方式,D0信号線作為串行時鐘線SCLK,D1信号線作為串行資料線SDIN,資料通過D1一位一位地發送出去。​

1 #if OLED_MODE == 1 /* 使用8080并口驅動OLED */​
2 /**​
3 * @brief通過拼湊的方法向OLED輸出一個8位資料​
4 * @param要輸出的資料​
5 * @retval無​
6 */​
7 static void oled_data_out(uint8_t data)​
8 {​
9 uint16_t dat = data & 0X0F;​
10 ​
11 GPIOH->ODR &= ~(0XF << 9); /* 清空PH的第12~9位 */​
12 GPIOH->ODR |= dat << 9;  /* 将D[3:0]配置設定給PH[12:9] */​
13 ​
14 GPIOH->ODR &= ~(0X1 << 14); /* 清空PH的第14位 */​
15 GPIOH->ODR |= ((data >> 4) & 0x01) << 14;  /* 将D4給PH14 */​
16 ​
17 GPIOI->ODR &= ~(0X1 << 4);   /* 清空PI4 */​
18 GPIOI->ODR |= ((data >> 5) & 0x01) << 4;  /* 将D5給PI4 */​
19 ​
20 GPIOB->ODR &= ~(0X1 << 8);   /* 清空PB8 */​
21 GPIOB->ODR |= ((data >> 6) & 0x01) << 8;​
22 ​
23 GPIOE->ODR &= ~(0X1 << 6);   /* 清空PE6 */​
24 GPIOE->ODR |= ((data >> 7) & 0x01) << 6;  /* 将D7給PE6 */​
25 #if 0​
26 GPIOC->ODR&=~(0XF<<6);    //清空6~9​
27 GPIOC->ODR|=dat<<6;    //D[3:0]-->PC[9:6]​
28 ​
29 GPIOC->ODR&=~(0X1<<11);    //清空11​
30 GPIOC->ODR|=((data>>4)&0x01)<<11;​
31 ​
32 GPIOD->ODR&=~(0X1<<3);   //清空3​
33 GPIOD->ODR|=((data>>5)&0x01)<<3;​
34 ​
35 GPIOB->ODR&=~(0X3<<8);   //清空8,9​
36 GPIOB->ODR|=((data>>6)&0x01)<<8;​
37 GPIOB->ODR|=((data>>7)&0x01)<<9;​
38 #endif​
39 ​
40 }​
41 /**​
42 * @brief向OLED寫入一個位元組​
43 * @param要輸出的資料​
44  * @param資料/指令标志 0,表示指令;1,表示資料;​
45 * @retval無​
46 */​
47 static void oled_wr_byte(uint8_t data, uint8_t cmd)​
48 {​
49 oled_data_out(data);/* 向OLED輸出一個8位位元組 */​
50 OLED_RS(cmd); /* cmd為0,表示指令;cmd為1,表示資料 */​
51 OLED_CS(0); /* 片選引腳拉低,選中SSD1306 */​
52 OLED_WR(0); /* WR先拉低 */​
53 OLED_WR(1); /* WR再拉高,變成上升沿了,使位元組寫入到資料線D[7:0]上 */​
54 OLED_CS(1); /* 片選引腳拉高,關閉片選 */​
55 OLED_RS(1); /* 即DC設定為1,DC電平恢複至初始态 */​
56 }​
57 #else /* 使用SPI驅動OLED */​
58 /**​
59  * @brief向OLED寫入一個位元組​
60 * @param要輸出的資料​
61 * @param資料/指令标志 0,表示指令;1,表示資料;​
62 * @retval無​
63 */​
64 static void oled_wr_byte(uint8_t data, uint8_t cmd)​
65 {​
66 uint8_t i;​
67  OLED_RS(cmd); /* cmd為0,表示指令;cmd為1,表示資料 */​
68 OLED_CS(0); /* 片選引腳拉低,選中SSD1306 */​
69 ​
70 for (i = 0; i < 8; i++)/* SPI是串行,一位一位地傳輸 */​
71 {​
72 OLED_SCLK(0);  /* SCLK為低電平,低電平開始采樣資料 */​
73 if (data & 0x80) /* 高位在前 */​
74 {​
75  OLED_SDIN(1);  /* 寫1 */​
76  }​
77  else ​
78  {​
79 OLED_SDIN(0); /* 寫0 */​
80  }​
81 OLED_SCLK(1);  /* SCLK為高電平 */​
82 data <<= 1;  /* 左移 */​
83 }​
84 ​
85  OLED_CS(1);  /* 關閉片選 */​
86 OLED_RS(1); /* DC電平恢複至初始态 */​
87 }​
88 #endif      

oled_wr_byte函數的代碼我們已經在上面列出了,通過此函數向OLED寫入一個位元組。8080并口方式和SPI方式接線不同,通信方式也會不同,是以這部分分為兩種情況,如果使用SPI方式,隻需要将宏OLED_MODE定義為0即可。此函數的代碼是根據前文講解的通信時序圖編寫的代碼。我們來分析這段代碼。​

對于8080并口方式:​

第49行,向OLED輸出一個8位位元組;​

第50行,OLED_RS(cmd)中的cmd可選1或0,選1表示對資料進行操作,選0表示對指令進行操作;​

第51行,片選引腳拉低,選中OLED晶片SSD1306;​

第52和53行,先将WR拉低再拉高,這樣就變成一個上升沿了,前面我們分析過,将資料寫入SSD1306時,WR引腳的信号狀态處于是上升沿:​

功能​ RD​ WR​ CS​ DC​
寫指令​ H​ ↑​ L​ L​
讀狀态​ ↑​ H​ L​ L​
寫資料​ H​ ↑​ L​ H​
讀資料​ ↑​ H​ L​ H​

表26.4.2. 2功能引腳的狀态​

第54行,将片選拉高,關閉片選引腳;​

第55行,設定DC引腳為高電平,回複為初始态。初始态下DC是高電平。​

對于4線SPI方式:​

第68行,将片選拉低,選中OLED晶片;​

第70~83行,SPI通信方式是一位一位地進行傳輸,是以分8次傳輸。SPI通信是在SCLK未低電平的時候采樣資料,為高電平的時候停止采樣;​

第85和86行,關閉片選,并将DC恢複至初始狀态。​

(3)oled_refresh_gram和oled_clear函數​

oled_refresh_gram函數是把我們在程式中定義的二維數組g_oled_gram的值一次性重新整理到OLED的顯存GRAM中,該數組值與OLED顯存GRAM值一一對應。在操作的時候我們隻需要先修改該數組的值,然後再通過調用oled_refresh_gram函數把數組的值一次性重新整理到OLED 的GRAM上即可。​

oled_clear函數是清屏函數,g_oled_gram[n][i]的值為0x00,即螢幕的像素點是黑的,沒有點亮。​

1 /*​
2 的顯存​
3每個位元組表示8個像素, 128,表示有128列, 8表示有64行, 高位表示第行數.​
4 比如:g_oled_gram[0][0],包含了第一列,第1~8行的資料.​
5即表示坐标(0,0)​
6類似的: g_oled_gram[1][0].6,表示坐标(1,1), g_oled_gram[10][1].5,​
7 表示坐标(10,10),​
8  *​
9存放格式如下(高位表示低行數).​
10 * [0]0 1 2 3 ... 127​
11  * [1]0 1 2 3 ... 127​
12  * [2]0 1 2 3 ... 127​
13 * [3]0 1 2 3 ... 127​
14  * [4]0 1 2 3 ... 127​
15 * [5]0 1 2 3 ... 127​
16 * [6]0 1 2 3 ... 127​
17 * [7]0 1 2 3 ... 127​
18  */​
19 static uint8_t g_oled_gram[128][8];​
20​
21 /**​
22  * @brief更新顯存到OLED​
23  * @param無​
24 * @retval無​
25 */​
26 void oled_refresh_gram(void)​
27 {​
28 uint8_t i, n;​
29​
30 for (i = 0; i < 8; i++)​
31 {​
32 oled_wr_byte (0xb0 + i, OLED_CMD); /* 設定頁位址(0~7) */​
33 oled_wr_byte (0x00, OLED_CMD); /* 設定顯示位置—列低位址 */​
34 oled_wr_byte (0x10, OLED_CMD); /* 設定顯示位置—列高位址 */​
35​
36 for (n = 0; n < 128; n++)​
37 {​
38 oled_wr_byte(g_oled_gram[n][i], OLED_DATA);​
39 }​
40 }​
41 }​
42 /**​
43 * @brief清屏函數,清完屏,整個螢幕是黑色的!和沒點亮一樣!!!​
44 * @param無​
45 * @retval無​
46 */​
47 void oled_clear(void)​
48 {​
49 uint8_t i, n;​
50​
51 for (i = 0; i < 8; i++)for (n = 0; n < 128; n++)g_oled_gram[n][i] = 0X00;​
52​
53 oled_refresh_gram(); /* 更新顯示 */​
54 }      

g_oled_gram [128][8]二維數組中的128代表列數(x坐标),而8代表的是頁,每頁又包含8行,總共64行(y坐标),從高到低對應行數從小到大,如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.4.2. 2和OLED屏坐标對應關系​

上表中G代表OLED_GRAM,G[0][0]就表示OLED_GRAM[0][0]。比如,我們要在x=3,y=9這個點寫入1,則可以用這個句子實作:​

OLED_GRAM[3][1]|=1<<6;​

一個通用的在點(x,y)置1表達式為:​

OLED_GRAM[x][7-y/8]|=1<<(7-y%8);​

其中x的範圍為:0~127;y的範圍為:0~63。​

(4)oled_draw_point畫點函數​

下面我們介紹重要的畫點函數,函數代碼如下:​

1 /**​
2 * @brief畫點​
3  * @param x : 0~127​
4 * @param y : 0~63​
5  * @param填充 0,清空​
6  * @retval無​
7  */​
8 void oled_draw_point(uint8_t x, uint8_t y, uint8_t dot)​
9 {​
10 uint8_t pos, bx, temp = 0;​
11​
12 if (x > 127 || y > 63) return; /* 超出範圍了. */​
13​
14  pos = 7 - y / 8;/* 計算GRAM裡面的y坐标所在的位元組, 每個位元組可以存儲8個行坐标 */​
15​
16 bx = y % 8; /* 取餘數,友善計算y在對應位元組裡面的位置,及行(y)位置 */​
17 temp = 1 << (7 - bx); /* 高位表示低行号, 得到y對應的bit位置,将該bit先置1 */​
18​
19 if (dot) /* 畫實心點 */​
20 {​
21 g_oled_gram[x][pos] |= temp;​
22 }​
23 else /* 畫空點,即不顯示 */​
24 {​
25 g_oled_gram[x][pos] &= ~temp;​
26 }​
27 }      

該函數有3個形參,前兩個是橫縱坐标,第三個dot為要寫入1還是0。該函數實作了我們在OLED子產品上任意位置畫點的功能。​

第12行,OLED螢幕的分辨率是128*64,x和y的坐标從0開始,x最大為127,y最大為63,如果超出範圍,則螢幕上無法顯示;​

第14行,pos代表的是第幾頁,這行代碼是為了确定頁數。我們面對螢幕,由螢幕從上往下看,最上是第7頁,最下是第0頁;​

第16行,bx = y %是為了确定移位的個數;​

第17行,temp是表示該頁的哪一列;​

如下圖是本實驗程式中硬體的頁排序和程式中的頁分布關系:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

表26.4.2. 3硬體的頁排序示意圖​

上圖是螢幕硬體上GRAM裡頁的排布的關系,下圖是程式中的頁排布關系,正因為這種關系,是以以上的畫點程式作了特殊處理。​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.4.2. 3程式中的頁排布示意圖​

(5)OLED顯示字元、數字函數​

1 /**​
2  * @brief在指定位置顯示一個字元,包括部分字元​
3  * @param x : 0~127​
4  * @param y : 0~63​
5  * @param選擇字型 12/16/24​
6  * @param反白顯示;1,正常顯示​
7 * @retval無​
8 */​
9 void oled_show_char(uint8_t x, uint8_t y, uint8_t chr, uint8_t size, uint8_t mode)​
10 {​
11 uint8_t temp, t, t1;​
12 uint8_t y0 = y;​
13 uint8_t *pfont = 0;​
14 /* 得到字型一個字元對應點陣集所占的位元組數 */​
15 uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); ​
16 chr = chr - ' ';/* 得到偏移後的值,因為字庫是從空格開始存儲的,第一個字元是空格 */​
17 ​
18 if (size == 12) /* 調用1206字型 */​
19 {​
20 pfont = (uint8_t *)oled_asc2_1206[chr];​
21 }​
22 else if (size == 16) /* 調用1608字型 */​
23 {​
24 pfont = (uint8_t *)oled_asc2_1608[chr];​
25 }​
26 else if (size == 24) /* 調用2412字型 */​
27 {​
28 pfont = (uint8_t *)oled_asc2_2412[chr];​
29 }​
30 else /* 沒有的字庫 */​
31 {​
32 return;​
33 }​
34 ​
35 for (t = 0; t < csize; t++)​
36 {​
37 temp = pfont[t];​
38 for (t1 = 0; t1 < 8; t1++)​
39 {​
40 if (temp & 0x80)oled_draw_point(x, y, mode);​
41 else oled_draw_point(x, y, !mode);​
42 ​
43 temp <<= 1;​
44 y++;​
45 ​
46 if ((y - y0) == size)​
47 {​
48 y = y0;​
49 x++;​
50 break;​
51 }​
52 }​
53 }​
54 }      

oled_show_char函數為字元以及字元串顯示的核心部分,函數中chr = chr - ' ';這句是要得到在字元點陣資料裡面的實際位址,因為我們的取模是從空格鍵開始的,例如oled_asc2_1206 [0][0],代表的是空格符開始的點陣碼。在接下來的代碼,我們也是按照從上到小(先y++),從左到右(再x++)的取模方式來編寫的,先得到最高位,然後判斷是寫1還是0,畫點;接着讀第二位,如此循環,直到一個字元的點陣全部取完為止。這其中涉及到列位址和行位址的自增,根據取模方式來了解,就不難了。​

oled.c的内容比較多,其他的函數請大家自行了解,這裡就不一一介紹了。下面開始main.c檔案的介紹。​

5. 修改main.c檔案​

main.c檔案代碼如下:​

int main(void)​
{​
 uint8_t t = 0;​

 HAL_Init();   /* 初始化HAL庫 */​
 /* 初始化M4核心時鐘,209M */​
 if(IS_ENGINEERING_BOOT_MODE())​
 {​
 sys_stm32_clock_init(34, 2, 2, 17, 6826);​
 }​
 delay_init(209);   /* 延時初始化 */​
 led_init(); /* 初始化LED */​
 oled_init();  /* 初始化OLED */​
 oled_show_string(0, 0, "ALIENTEK", 24);​
 oled_show_string(0, 24, "0.96' OLED TEST", 16);​
 oled_show_string(0, 40, "ATOM 2020/5/11", 12);​
 oled_show_string(0, 52, "ASCII:", 12);​
 oled_show_string(64, 52, "CODE:", 12);​
 oled_refresh_gram();  /* 更新顯示到OLED */​

 t = ' '; ​
 while (1)​
 {​
 oled_show_char(36, 52, t, 12, 1); /* 顯示ASCII字元 */​
 oled_show_num(94, 52, t, 3, 12); /*顯示ASCII字元的碼值 */​
 oled_refresh_gram();  /*更新顯示到OLED */​
 t++;​
 if (t > '~')t = ' ';​
 delay_ms(500);​
 LED0_TOGGLE();  /* LED0閃爍 */​
 }​
}      

Main.c主要功能就是在OLED上顯示一些實驗資訊字元,然後開始從空格鍵開始不停的循環顯示ASCII字元集,并顯示該字元的ASCII值。最後LED0閃爍提示程式正在運作。​

26.4.3 編譯和測試​

下載下傳代碼後,LED0不停的閃爍,提示程式已經在運作了。同時OLED子產品顯示ASCII字元集等資訊,如下圖所示(本實驗使用的OLED子產品是雙色的,顯示的顔色有兩種):​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.4.3. 1顯示效果​

OLED顯示了三種尺寸的字元:24*12(ALIENTEK)、16*8(0.96’ OLED TEST)和12*6(剩下的内容)。說明我們的實驗是成功的,實作了三種不同尺寸ASCII字元的顯示,在最後一行不停的顯示ASCII字元以及其碼值。​

通過這一章的學習,我們學會了ALIENTEK OLED子產品的使用,在調試代碼的時候,又多了一種顯示資訊的途徑,在以後的程式編寫中,大家可以好好利用。​

26.5 OLED顯示圖檔實驗​

本實驗是在上一章實驗的基礎上修改的,注意main.c檔案修改的地方。配置好的實驗工程已經放到了開發闆CD光牒中,由于幾個實驗共用一個工程,是以要測試哪個實驗,就在min.c檔案中把其它實驗的代碼注釋掉即可。路徑為:開發闆CD光牒A-基礎資料\1、程式源碼\3、M4裸機驅動例程\庫V1.2\實驗15-2 OLED顯示圖檔實驗。​

26.5.1 取模​

1. 選擇.bmp格式圖檔​

我們在前面實驗的基礎上,實作此實驗,本節實驗需要借助前面實驗的畫點函數。我們選擇前面使用gif分離器分離出的.bmp圖檔中的某一張來操作本實驗,例如使用第一張圖檔,此圖檔像素是120*60::​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.5.1. 1選擇一張bmp格式圖檔​

2. 取模​

我們對上面選擇的第一章圖檔取模如下,取模方式設定:陰碼+逐列式+順向+C51格式+十六進制,字寬和字高均為12:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.5.1. 2圖檔取模​

26.5.2 添加使用者代碼​

1. 添加logo.h檔案代碼​

在工程的Drivers\BSP\OLED目錄下建立一個logo.h檔案,将上面取模的資料拷貝到logo.h檔案的一維數組Image[]中,此一維數組總共有960個十六進制的資料,也就是此.bmp的圖檔有最多960個像素點。​

#ifndef _LOGO_H_​
#define _LOGO_H_​

 uint8_t Image[]={//960​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 /***************省略部分資料 *************/​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
};​
#endif      

2. 修改oled.c​

在oled.h檔案中添加如下代碼:​

void OLED_Show(uint8_t x,uint8_t y,uint16_t bytenumber, uint8_t height, uint8_t* Image, uint8_t mode);      

在前面實驗的oled.c檔案中添加如下代碼:​

/**​
* @brief顯示圖檔​
* @param起始坐标​
* number: 圖檔像素點的個數​
* height:圖檔像素點的高度,即每一列總共有幾行​
* Image : 包含圖像資料的一維數組首位址​
* mode :1點亮資料點,0不點亮資料點​
* @retval無​
*/​
void OLED_Show(uint8_t x,uint8_t y,uint16_t number, uint8_t height, uint8_t* Image, uint8_t mode)​
{​
 uint16_t i=0;​
 uint8_t y0=y, temp=0, j=0;​

 for(i=0; i<number; i++)   /* number個像素點就要循環number次 */​
 {​
 temp=Image[i];​
 for(j=0; j<8; j++)   /* 對于某行中的每個點進行判斷 */​
 {​
 if(temp&0x80)    /* 如果像素點是1,則點亮資料點 */​
 oled_draw_point(x,y,mode);​
 else     /* 如果像素點是0,則不點亮資料點 */​
 oled_draw_point(x,y,!mode);​
 temp <<= 1;    /* 将右邊的資料往左移動 */​
 y++; ​
 if(y-y0 == height)​
 {​
 y=y0;​
 x++;​
 break;​
 }​
 }​
 }​
}      

3. 修改main.c​

在main.c檔案中添加如下代碼:​

#include "./SYSTEM/sys/sys.h"​
#include "./SYSTEM/delay/delay.h"​
#include "./SYSTEM/usart/usart.h"​
#include "./BSP/LED/led.h"​
#include "./BSP/BEEP/beep.h"​
#include "./BSP/KEY/key.h"​
#include "./BSP/OLED/oled.h"​
#include "./BSP/OLED/logo.h"​
/**​
 * @brief主函數​
 * @param無​
 * @retval無​
 */​
int main(void)​
{ ​
 HAL_Init();  /* 初始化HAL庫 */​
 /* 初始化M4核心時鐘,209M */​
 if(IS_ENGINEERING_BOOT_MODE())​
 {​
 sys_stm32_clock_init(34, 2, 2, 17, 6826);​
 }​
 delay_init(209); /* 延時初始化 */​
 led_init(); /* 初始化LED */​
 oled_init();  /* 初始化OLED */​
 while (1)​
 {​
 OLED_Show(0,0,960, 60, Image, 1); /* 顯示一張圖檔 */​
 oled_refresh_gram();     /*更新顯存到OLED */​
 delay_ms(500);       延時500ms */​
 LED0_TOGGLE(); /* LED0閃爍 */​
 }​
}      

注意OLED_Show(0,0,960, 60, Image, 1)這一行,x和y坐标均是0,像素點個數是960(前面取模後我們計算出來.bmp的圖檔有最多960個像素點),像素點的高度是60,因為我們使用的圖檔像素是120*60的,高度則為60。​

26.5.3. 編譯運作​

編譯無報錯,運作後的效果如下圖所示:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.5.3. 1編譯運作結果​

26.6 OLED顯示動圖實驗​

本實驗是在上一章實驗的基礎上修改的,注意main.c檔案修改的地方。配置好的實驗工程已經放到了開發闆CD光牒中,由于幾個實驗共用一個工程,是以要測試哪個實驗,就在min.c檔案中把其它實驗的代碼注釋掉即可。路徑為:開發闆CD光牒A-基礎資料\1、程式源碼\3、M4裸機驅動例程\庫V1.2\實驗15-3 OLED顯示動圖實驗。​

26.6.1 取模​

1. 選擇.bmp格式檔案​

每一張動圖是由一幀幀的圖檔組合成的,前面我們使用gif圖檔分離器分離出了58張的.bmp格式的檔案,我們可以考慮使用其中幾張的圖檔來生成點陣資料,再将每張圖檔顯示出來,顯示的每張圖檔延時400ms的時間,這樣看上去就像是一個動圖了。如下,我們選擇這58張圖檔中的10張(0/2/6/13/16/22/34/43/48/57):​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.6.1. 1選取圖檔​

2. 取模​

我們依次對這10張圖檔進行取模,儲存每張圖檔取模後的資料,取模方式和前面的一樣,即陰碼+逐列式+順向+C51格式+十六進制,字寬和字高均為12:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.6.1. 2取模配置​

觀察取模後的資料,每張圖檔的資料的個數都是960個(即每張圖檔的像素點個數是960)。​

26.6.2 添加使用者代碼​

1. 修改logo.h​

我們可以直接在上一章節的logo.h檔案中建立10個一維數組,每個數組存放一張圖檔的資料。由于代碼太多,我們這裡隻截圖一部分,具體可以參考本實驗的工程代碼:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.6.2. 1中添加的代碼​

2. 修改main.c​

然後在main.c檔案中直接添加如下代碼:​

OLED_Show(0,0,960, 60, Image1, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image2, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image3, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image4, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image5, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image6, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image7, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image8, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image9, 1);​
oled_refresh_gram();​
HAL_Delay(400);​
OLED_Show(0,0,960, 60, Image10, 1);​
oled_refresh_gram();​
HAL_Delay(400);      

以上是建立了10個一維的數組,然後分10次進行顯示,每次顯示一張圖檔,每張圖檔顯示400ms。編譯後測試,就可以看到一個動圖了。​

以上代碼比較繁瑣,下面我們将以上代碼進行改進:​

3. 修改image.h​

下面我們在工程的Drivers\BSP\OLED下建立一個image.h頭檔案,在此頭檔案中建立一個二維數組,數組中的常量表達式1是圖檔的索引值,共有10張圖檔,是以值為10,每張圖檔的像素點個數為960,是以數組中的常量表達式2的值為960:​

uint8_t BMP[10][960]={​
 {//IMG00000.bmp​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 /***********省略部分代碼**********************************/​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 },​
 {//IMG00002.bmp​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 /***********省略部分代碼**********************************/​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 },​
 .​
 .​
 .​
 {//IMG00057.bmp​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 /***********省略部分代碼**********************************/​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,​
 },​
};      

4. 修改oled.c​

在oled.c檔案中添加代碼如下:​

#include "./BSP/OLED/image.h"​
/**​
* @brief顯示圖檔​
* @param起始坐标​
* px,py: 圖檔的寬度和高度,本實驗所使用的圖檔​
* 寬是120像素,高是60像素​
* index : 圖檔的索引,為0~9,共10張圖​
* mode :1點亮資料點,0不點亮資料點​
* @retval無​
*/​
void OLED_ShowBMP(uint8_t x, uint8_t y, uint8_t px, uint8_t py, uint8_t index, uint8_t mode)​
{​
 uint8_t temp,t1;​
 uint16_t j,i;​
 uint8_t y0=y;​

 i=960;​
 // i = (px/2)*(py/4); /* 或者使用此函數 */​

 for(j = 0; j < i;j++)​
 {​
 temp = BMP[index][j]; /* 調用圖檔 */​
 for(t1=0;t1<8;t1++) /* 對于某行中的每個點進行判斷 */​
 {​
 if(temp&0x80)/* 如果像素點是1,則點亮資料點 */​
 oled_draw_point(x,y,mode);​
 else /* 如果像素點是0,則不點亮資料點 */​
 oled_draw_point(x,y,!mode);​
 temp<<= 1; /* 将右邊的資料往左移動 */​
 y++;​
 if((y-y0) == py)​
 {​
 y=y0;​
 x++;​
 break;​
 }​
 }​
 }​
}      

以上代碼中,index是索引值,取值為0~9。​

5. 修改main.c​

main.c檔案代碼如下:​

#include "./SYSTEM/sys/sys.h"​
#include "./SYSTEM/delay/delay.h"​
#include "./SYSTEM/usart/usart.h"​
#include "./BSP/LED/led.h"​
#include "./BSP/BEEP/beep.h"​
#include "./BSP/KEY/key.h"​
#include "./BSP/OLED/oled.h"​
#include "./BSP/OLED/logo.h"​
/**​
 * @brief主函數​
 * @param無​
 * @retval無​
 */​
int main(void)​
{ ​
 HAL_Init(); /* 初始化HAL庫 */​
 /* 初始化M4核心時鐘,209M */​
 if(IS_ENGINEERING_BOOT_MODE())​
 {​
 sys_stm32_clock_init(34, 2, 2, 17, 6826);​
 }​
 delay_init(209); /* 延時初始化 */​
 led_init(); /* 初始化LED */​
 oled_init(); /* 初始化OLED */​
 while (1)​
 {​
 int i;​
 for(i = 0;i < 10;i ++)    /* 共10張圖檔 */​
 {​
 OLED_ShowBMP(0,0,120, 60, i, 1); /* 顯示一張圖檔 */​
 oled_refresh_gram();  /*更新顯存到OLED */​
 HAL_Delay(400);  /* 延時500ms */​
 }​

 delay_ms(500);  /* 延時500ms */​
 LED0_TOGGLE();  /* LED0閃爍 */​
 }​
}      

以上代碼中,定義i,取i依次為0~9,每隔400ms顯示一張圖檔,效果和前面的實驗一樣。​

26.6.3 編譯測試​

編譯運作,可以看到一個動圖效果:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.6.3. 1實驗效果​

26.7 OLED顯示一個漢字​

本實驗是在上一章實驗的基礎上修改的,注意main.c檔案修改的地方。配置好的實驗工程已經放到了開發闆CD光牒中,由于幾個實驗共用一個工程,是以要測試哪個實驗,就在min.c檔案中把其它實驗的代碼注釋掉即可。路徑為:開發闆CD光牒A-基礎資料\1、程式源碼\3、M4裸機驅動例程\庫V1.2\實驗15-3 OLED顯示一個漢字。 ​

本小節實驗我們按照26.2.3小節講解中文字型顯示的思路來顯示一個漢字,本節的實驗代碼就不去深究GB2312這些編碼了。下面以一個簡單的例子來講解,大家可以編寫适合自己的代碼顯示一句話。​

26.7.1 取模​

我們選擇顯示一個宋體“正”字。這裡我們設定字型為宋體,字寬和字高可以随意,我們後面的程式會對字寬和字高做處理,使用陰碼、順向、逐列式、十六進制取模方式:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.7.1. 1取模設定​

取模後的點陣資料如下,共有128個十六進制資料(每個資料8位,相當于1個位元組),如果将其分為兩組,每組為64個:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.7.1. 2點陣資料​

26.7.2 添加使用者代碼​

1. 修改logo.h​

在logo.h後面添加一個一維數組Chinese[],數組的成員就是上面取模後的點陣資料:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗

圖26.7.2. 1添加的一維數組​

2. 修改oled.c​

在oled.c檔案中添加顯示中文函數OLED_Show_Chinese如下:​

1 /**​
2 * @brief顯示一個漢字​
3 * @param起始坐标​
4分别是中文的字寬和字高​
5包含漢字資料的一維數組首位址​
6 點亮資料點,0不點亮資料點​
7 * @retval無​
8  */​
9 void OLED_Show_Chinese(uint8_t x,uint8_t y, uint8_t px, uint8_t              py,uint8_t* ch, uint8_t mode)​
10 {​
11 uint8_t j,y0=y;​
12 uint16_t i,number;​
13 unsigned char f, s;​
14 number=px*py/16; /* number是字型所占用的位元組數除以2 */​
15 或者直接寫等于64 */​
16 for (i = 0; i <number ; ++i)​
17 {​
18 f = ch[i * 2];​
19 s = ch[i * 2 + 1];​
20 /* 第一個for循環 */​
21 for (j = 0; j < 8; ++j)​
22 {​
23 if (0x80 & f)​
24 oled_draw_point(x,y,mode);​
25 else​
26 oled_draw_point(x,y,!mode);​
27 f <<= 1;​
28 y++;​
29 if(y-y0 == py)​
30 {​
31 y=y0;​
32 x++;​
33 break;​
34 }​
35 }​
36 /* 第二個for循環 */​
37 for (j = 0; j < 8; ++j)​
38 {​
39 if (0x80 & s)​
40 oled_draw_point(x,y,mode);​
41 else​
42 oled_draw_point(x,y,!mode);​
43 s <<= 1;​
44 y++;​
45 if(y-y0 == py)​
46 {​
47 y=y0;​
48 x++;​
49 break;​
50 }​
51 }​
52​
53 }​
54 }      

我們參考前面第26.2.3講解的顯示漢字的方法,顯示一個英文字元使用的是一個for循環就可以了,因為漢字的字寬是英文字元的兩倍,是以将一個漢字分為兩個部分進行顯示(相當于兩個英文字元),是以用到兩個for循環。​

程式中,x和y分别代表要在螢幕上顯示的坐标點。px和py是要顯示的漢字的字寬和字高,字寬和字高可以根據取模軟體上設定的來,本實驗我們取模軟體上設定的字寬是33,字高是26,是以如果在main.c檔案中調用此函數的話,這兩個數字要寫正确,否則可能會導緻顯示異常。​

第14行number是字型所占用的位元組數除以2,即将字型所占用的位元組數分為兩組,是以後面會有兩個for循環。本實驗生成的字模共有128個十六進制資料,是以可以将number直接設定為64(即128/2)。​

3. 修改main.c​

main.c檔案代碼如下,OLED_Show_Chinese(0,0, 33,26, Chinese, 1)這一行,x和y坐标是0,字寬和字高分别為33和26。​

#include "./SYSTEM/sys/sys.h"​
#include "./SYSTEM/delay/delay.h"​
#include "./SYSTEM/usart/usart.h"​
#include "./BSP/LED/led.h"​
#include "./BSP/BEEP/beep.h"​
#include "./BSP/KEY/key.h"​
#include "./BSP/OLED/oled.h"​
#include "./BSP/OLED/logo.h"​
/**​
 * @brief主函數​
 * @param無​
 * @retval無​
 */​
int main(void)​
{ ​
 HAL_Init();  /* 初始化HAL庫 */​
 /* 初始化M4核心時鐘,209M */​
 if(IS_ENGINEERING_BOOT_MODE())​
 {​
 sys_stm32_clock_init(34, 2, 2, 17, 6826);​
 }​
 delay_init(209); /* 延時初始化 */​
 led_init();  /* 初始化LED */​
 oled_init();  /* 初始化OLED */​
 while (1)​
 {​
 OLED_Show_Chinese(0,0, 33,26, Chinese, 1);/* 顯示一個漢字 */​
 oled_refresh_gram();  /* 更新顯存到OLED */ ​

 delay_ms(500);  /* 延時500ms */​
 LED0_TOGGLE();  /* LED0閃爍 */​
 }​
}      

26.7.3 編譯運作​

編譯運作,顯示結果如下:​

《STM32MP1 M4裸機HAL庫開發指南》第二十六章 OLED實驗