這算是我這個系列的第一篇博客吧。首先要解決的就是屏幕顯示問題。我選擇了目前新興起的OLED顯示模塊。
OLED(OrganicLightEmittingDiode),中文譯作有機發光二極管,目前被廣泛的應用於移動設備甚至電視上。它既擁有超快的響應速度和輕薄的優勢,又存在壽命與對大尺寸支持不足的瓶頸。
OLED的優點
1、厚度可以小於1毫米,僅為LCD屏幕的1/3,並且重量也更輕;
2、固態機構,沒有液體物質,因此抗震性能更好,不怕摔;
3、幾乎沒有可視角度的問題,即使在很大的視角下觀看,畫面仍然不失真;
4、響應時間是LCD的千分之一,顯示運動畫面絕對不會有拖影的現象;
5、低溫特性好,在零下40度時仍能正常顯示,而LCD則無法做到;
6、制造工藝簡單,成本更低;
7、發光效率更高,能耗比LCD要低;
8、能夠在不同材質的基板上制造,可以做成能彎曲的柔軟顯示器。
OLED的缺點
1、壽命通常只有5000小時,要低於LCD至少1萬小時的壽命;
2、不能實現大尺寸屏幕的量產,因此目前只適用於便攜類的數碼類產品;
3、存在色彩純度不夠的問題,不容易顯示出鮮艷、濃郁的色彩。 ******************【摘自百度】

首先,該模塊采用SPI 或 IIC 通信方式,最多占用5個IO口。我使用的是7針模塊,采用4線SPI 通信方式。
該模塊有以下特點:
1. 模塊有單色和雙色可選,單色為純藍色,雙色為黃藍雙色(本人選用單色);
2. 顯示尺寸為0.96寸
3. 分辨率為128*64
4. 多種接口方式,該模塊提供了總共 5 種接口包括: 6800、 8080 兩種並行接口方式、 3線或4線的SPI接口,IIC接口方式
5. 不需要高壓,直接接3.3V就可以工作;(可以與stm32的引腳直接相接)
該模塊內部采用SSD1306驅動,顯存為128*64bit大小, SSD1306將全部顯存分為8頁,每頁128字節

OLED相當於64行128列點陣,每個像素點,0點亮,1熄滅
OLED將縱向64行分為8頁,每頁8行
該實驗的難點就在於理解取模的ASCII碼表與寫入程序的關系。下面我們來詳細分析一下。
首先根據這個官方給出的設置格式,我們采用列行式,就是先取列,再取行。比如我們取個大寫的 “A” 的字模。
{0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20} ,/*"A",0*/
/* (8 X 16 , 宋體 )*/
分析:
第一個0X00------表示第一列前8個像素,從高位向低位,也就是從下往上寫,全滅,所以是0X00,所以在SPI_Write()函數中,是從高位往低位寫的。
第二個0X00------表示第二列前8個像素,同上。
第三個0XC0--> 1100 0000,從高位往低位,正好下面兩個像素亮了。
后面都是這樣分析,大家可以自己對一下。
也就是說按照他的設置,這個取模軟件取的是按照從高位往低位取,前8個字節是第一頁的所有像素狀態。一共可以取128個字節。因為每一頁有128列,8行。但是這個大寫字母和漢字不一樣,他的寬度是漢字的一半,所以生成的ASCII碼表只有16個,一列,因為前8個字節是第一頁的,后8個字節是第二頁的,一個16*16的漢字需要占用兩頁(16行),16列。
下面是節選的顯示漢字的程序分析:

我使用STM32F103C8T6對該模塊進行驅動,程序修改自中景園科技官方驅動程序。親測可用。
OLED引腳介紹:
CS:OLED片選信號
RST:OLED復位端口
DC: 命令/數據選擇端口(0:讀寫命令, 1: 讀寫數據)
SCLK(D0):串口時鍾線
SDIN(D1): 串口數據線
關於SPI的相關知識,可以參見這篇博客:http://www.cnblogs.com/qsyll0916/p/8053905.html
首先是SPI.C,包含了對該模塊的各種操作,就是對SPI 寫進行符合OLED的包裝。寫字符,寫數字,寫字符串,可調顯示字體大小,但是需要包含兩個ASCII字庫。
#include "spi.h" #include "word.h" //字庫頭文件 #define OLED_Order 0 //定義寫命令 #define OLED_Data 1 //定義寫數據 //盡在內部調用函數 static u32 oled_pow(u8 m,u8 n); static void OLED_GPIO_INIT(void); static void SPI_Write(u8 data, u8 Mode); static void OLED_Coord(u8 x, u8 y); //使用管腳初始化 static void OLED_GPIO_INIT(void) { GPIO_InitTypeDef GPIO_InitStruct; //開啟GPIOD的時鍾 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //設置GPIO的基本參數 GPIO_InitStruct.GPIO_Pin = OLED_CS_PIN | OLED_RST_PIN | OLED_DC_PIN | OLED_D0_PIN | OLED_D1_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //輸出速度50MHz GPIO_Init(OLED_PORT, &GPIO_InitStruct); GPIO_SetBits(OLED_PORT, OLED_CS_PIN | OLED_RST_PIN | OLED_DC_PIN | OLED_D0_PIN | OLED_D1_PIN); } /* SPI寫數據/命令 * Mode :OLED_Order:寫命令 OLED_Data:寫數據 * data :數據/命令 */ static void SPI_Write(u8 data, u8 Mode) { u8 i = 0; if(Mode) { OLED_DC(1); //DC引腳輸入高,表示寫數據 } else { OLED_DC(0); //DC引腳輸入低,表示寫命令 } OLED_CS(0); //CS引腳輸入低,片選使能 for(i = 0; i < 8; i++) { OLED_D0(0); //D0引腳輸入低 if(data&0x80) //判斷傳輸的數據最高位為1還是0 { OLED_D1(1); //D1引腳輸入高 } else { OLED_D1(0); //D1引腳輸入低 } OLED_D0(1); //D1引腳輸入高 data<<=1; //將數據左移一位 } OLED_DC(1); //DC引腳輸入低 OLED_CS(1); //CS引腳輸入高,片選失能 } /* 設置OLED屏的顯示坐標 * X : 表示OLED的水平坐標(0—127) * Y : 表示OLED的頁(0—7) */ static void OLED_Coord(u8 x, u8 y) { SPI_Write((0xb0 + y) ,OLED_Order); SPI_Write((((x & 0xf0)>>4) | 0x10), OLED_Order);//高4位 SPI_Write((x & 0x0f)|0x01, OLED_Order);//低4位 } //清屏,一開始這里寫錯了,把寫命令寫成了寫數據,導致清屏不正確,發現屏幕上有很多噪點,說明沒有清屏成功。 void OLED_Clear(void) { u8 i = 0, j = 0; for(i = 0; i < 8; i++) { SPI_Write(0xb0 + i,OLED_Order); SPI_Write(0x00,OLED_Order); SPI_Write(0x10,OLED_Order); for(j = 0; j < 128; j++) { SPI_Write(0x00, OLED_Data); } } } //關oled顯示 void OLED_Display_Off(void) { SPI_Write(0x8D,OLED_Order); SPI_Write(0x10,OLED_Order); SPI_Write(0xAE,OLED_Order); } //開oled顯示 void OLED_Display_On(void) { //電荷泵設置(初始化時必須打開,否則看不到顯示) SPI_Write(0x8D, OLED_Order); SPI_Write(0x14, OLED_Order);//bit2 0:關閉 1:打開 SPI_Write(0xAF, OLED_Order);//0xAF:開顯示 } //oled參數初始化 void OLED_Init(void) { OLED_GPIO_INIT(); //端口初始化 OLED_RST(1); delay_ms(100); OLED_RST(0); delay_ms(100); OLED_RST(1); SPI_Write(0xAE, OLED_Order);//0xAE:關顯示 SPI_Write(0x00, OLED_Order);//設置低列地址 SPI_Write(0x10, OLED_Order);//設置高列地址 //設置行顯示的開始地址(0-63) //40-47: (01xxxxx) SPI_Write(0x40, OLED_Order); //設置對比度 SPI_Write(0x81, OLED_Order); SPI_Write(0xff, OLED_Order);//這個值越大,屏幕越亮(和上條指令一起使用)(0x00-0xff) SPI_Write(0xA1, OLED_Order);//0xA1: 左右反置, 0xA0: 正常顯示(默認0xA0) SPI_Write(0xC8, OLED_Order);//0xC8: 上下反置, 0xC0: 正常顯示(默認0xC0) //0xA6: 表示正常顯示(在面板上1表示點亮,0表示不亮) //0xA7: 表示逆顯示(在面板上0表示點亮,1表示不亮) SPI_Write(0xA6, OLED_Order); SPI_Write(0xA8, OLED_Order);//設置多路復用率(1-64) SPI_Write(0x3F, OLED_Order);//(0x01-0x3f)(默認為3f) //設置顯示抵消移位映射內存計數器 SPI_Write(0xD3, OLED_Order); SPI_Write(0x00, OLED_Order);//(0x00-0x3f)(默認為0x00) //設置顯示時鍾分頻因子/振盪器頻率 SPI_Write(0xD5, OLED_Order); //低4位定義顯示時鍾(屏幕的刷新時間)(默認:0000)分頻因子= [3:0]+1 //高4位定義振盪器頻率(默認:1000) SPI_Write(0x80, OLED_Order);// //時鍾預充電周期 SPI_Write(0xD9, OLED_Order); SPI_Write(0xF1, OLED_Order);//[3:0],PHASE 1; [7:4] PHASE 2 //設置COM硬件應腳配置 SPI_Write(0xDA, OLED_Order); SPI_Write(0x12, OLED_Order);//[5:4] 默認:01 SPI_Write(0xDB, OLED_Order);// SPI_Write(0x40, OLED_Order);// //設置內存尋址方式 SPI_Write(0x20, OLED_Order); //00: 表示水平尋址方式 //01: 表示垂直尋址方式 //10: 表示頁尋址方式(默認方式) SPI_Write(0x02, OLED_Order);// //電荷泵設置(初始化時必須打開,否則看不到顯示) SPI_Write(0x8D, OLED_Order); SPI_Write(0x14, OLED_Order);//bit2 0:關閉 1:打開 //設置是否全部顯示 0xA4: 禁止全部顯示 SPI_Write(0xA4, OLED_Order); //0xA6: 表示正常顯示(在面板上1表示點亮,0表示不亮) //0xA7: 表示逆顯示(在面板上0表示點亮,1表示不亮) SPI_Write(0xA6, OLED_Order);// SPI_Write(0xAF, OLED_Order);//0xAF:開顯示 SPI_Write(0xAF, OLED_Order); //不知道為什么要寫兩次 OLED_Clear(); OLED_Coord(0,0); } //顯示漢字,設置坐標, void OLED_ShowChinese(u8 x, u8 y, u8 chinese) { u8 t,adder=0; OLED_Coord(x,y); for(t=0;t<16;t++) //每行16個元素,一個字需要兩行字符串 { SPI_Write(Hzk[2*chinese][t],OLED_Data); adder+=1; } OLED_Coord(x,y+1); for(t=0;t<16;t++) { SPI_Write(Hzk[2*chinese+1][t],OLED_Data); adder+=1; } } //在指定位置顯示一個字符,包括部分字符 //x:0~127 //y:0~63 //mode:0,反白顯示;1,正常顯示 //size:選擇字體 16/12 void OLED_ShowChar(u8 x, u8 y, u8 chr) { unsigned char c=0, i=0; c = chr - ' '; //得到偏移后的值 if(x > Max_Column - 1) {x=0;y=y+2;} if(SIZE ==16) //8*16字符 { OLED_Coord(x,y); for(i=0;i<8;i++) SPI_Write(F8X16[c*16+i],OLED_Data); OLED_Coord(x,y+1); for(i=0;i<8;i++) SPI_Write(F8X16[c*16+i+8],OLED_Data); } else //6*8字符 { OLED_Coord(x,y+1); for(i=0;i<6;i++) SPI_Write(F6x8[c][i],OLED_Data); } } //顯示字符串 void OLED_Show_String(u8 x, u8 y, u8 *chr) { u8 j=0; while (chr[j]!='\0') { OLED_ShowChar(x,y,chr[j]); x+= 8 ; if(x>120){x=0;y+=2;} //自動換行寫 j++; } } //m^n函數 static u32 oled_pow(u8 m,u8 n) { u32 result = 1; while(n--)result*=m; return result; } //顯示數字 //x,y :起點坐標 //len :數字的位數 //size:字體大小 //mode:模式 0,填充模式;1,疊加模式 //num:數值(0~4294967295); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size) { u8 t = 0, temp = 0; u8 enshow=0; for(t=0;t<len;t++) { temp=(num/oled_pow(10,len-t-1))%10; if(enshow==0&&t<(len-1)) { if(temp==0) { OLED_ShowChar(x+(size/2)*t,y,' '); continue; }else enshow=1; } OLED_ShowChar(x+(size/2)*t,y,temp+'0'); } }
然后就是頭文件:SPI.h
#ifndef __SPI_H #define __SPI_H #include <stm32f10x.h> #include "systick.h" #define OLED_PORT GPIOA #define OLED_CS_PIN GPIO_Pin_3 #define OLED_RST_PIN GPIO_Pin_4 #define OLED_DC_PIN GPIO_Pin_5 #define OLED_D0_PIN GPIO_Pin_6 #define OLED_D1_PIN GPIO_Pin_7 //X為1時對應GPIO端口輸出高電平,X為0時對應GPIO端口輸出低電平 #define OLED_CS(X) X?GPIO_SetBits(OLED_PORT, OLED_CS_PIN):GPIO_ResetBits(OLED_PORT, OLED_CS_PIN) #define OLED_RST(X) X?GPIO_SetBits(OLED_PORT, OLED_RST_PIN):GPIO_ResetBits(OLED_PORT, OLED_RST_PIN) #define OLED_DC(X) X?GPIO_SetBits(OLED_PORT, OLED_DC_PIN):GPIO_ResetBits(OLED_PORT, OLED_DC_PIN) #define OLED_D0(X) X?GPIO_SetBits(OLED_PORT, OLED_D0_PIN):GPIO_ResetBits(OLED_PORT, OLED_D0_PIN) #define OLED_D1(X) X?GPIO_SetBits(OLED_PORT, OLED_D1_PIN):GPIO_ResetBits(OLED_PORT, OLED_D1_PIN) //OLED模式設置 #define SIZE 16 //#define SIZE 8 //SIZE選擇英文字體的大小 #define XLevelL 0x00 #define XLevelH 0x10 #define Max_Column 128 #define Max_Row 64 #define Brightness 0xFF #define X_WIDTH 128 #define Y_WIDTH 64 void OLED_Init(void); void OLED_Clear(void); void OLED_Display_Off(void); void OLED_Display_On(void); void OLED_ShowChinese(u8 x, u8 y, u8 chinese); void OLED_ShowChar(u8 x, u8 y, u8 chr); void OLED_Show_String(u8 x, u8 y, u8 *chr); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size); #endif
寫完這兩個文件,就可以在主函數調用 OLED_ShowChinese() ,進行參數配置后,就可以顯示了。
具體調用方式為:OLED_ShowChinese(32,0,0);//錢
這個里面前兩個參數是進行顯示坐標選擇,第三個參數是選擇你的字庫里面第幾個漢字的行數。比如我選擇了 0,那么在我的字庫頭文件中前兩個ASCII碼表就是我要顯示漢字的ASCII碼表,這個碼表是采用字模軟件生成的。
char Hzk[][32]={ {0x20,0x10,0x2C,0xE7,0x24,0x24,0x00,0x90,0x90,0xFF,0x90,0x49,0x4A,0x48,0x40,0x00}, {0x01,0x01,0x01,0x7F,0x21,0x11,0x40,0x40,0x20,0x13,0x0C,0x14,0x22,0x41,0xF8,0x00},/*"錢",7*/ /* (16 X 16 , 宋體 )*/
使用PCtoLCD2002完美版進行取模。具體字模生成方式可參見中景園官方教程:https://wenku.baidu.com/view/42efcb877cd184254a353584.html

漢字生成為兩行16進制碼表。
顯示效果:

