現(xiàn)在OLED顯示屏在嵌入式系統(tǒng)中應(yīng)用的越來越多。對于一些顯示信息不太復(fù)雜,以顯示信息為主的需求,我們一般會選擇OLED顯示屏。在這一篇中,我們將討論OLED顯示屏驅(qū)動的設(shè)計(jì)與實(shí)現(xiàn)。
1、功能概述
??從使用的情況來說,較為常用的是0.96英寸的OLED128x64的顯示屏。這種OLED屏多采用象SSD1306這類驅(qū)動芯片,所以我們對OLED屏的操作實(shí)際就是對控制芯片的操作。
??對于0.96英寸的OLED128x64的顯示屏,其像素點(diǎn)為128x64個(gè),對應(yīng)在顯示RAM中的128x64個(gè)位。在顯存中,這些區(qū)域被劃分為8個(gè)Page,這些頁的劃分具體如下圖所示:
??在每一頁中包括128x8個(gè)位對應(yīng)相應(yīng)的像素點(diǎn),對顯示像素的操作就是對鄉(xiāng)村中對應(yīng)的位的操作,每頁中像素點(diǎn)的排布如下:
??對于操作0.96英寸的OLED128x64顯示屏的接口有多種,如6800并行接口、8080并行接口、SPI串行接口以及I2C串行接口等。對于并行接口應(yīng)用較少,現(xiàn)在應(yīng)用較多的是SPI和I2C這兩種串行總線接口。在SPI接口方式下,有3個(gè)控制引腳是需要操作的,分別是復(fù)位、數(shù)據(jù)命令選擇和片選信號。而在I2C接口方式下,僅有復(fù)位引腳是可控的,但在發(fā)送命令或數(shù)據(jù)時(shí)會多一個(gè)字節(jié)的控制字。
2、驅(qū)動設(shè)計(jì)與實(shí)現(xiàn)
??我們已經(jīng)了解了0.96英寸的OLED128x64顯示屏的基本情況,在這里我們來考慮如何實(shí)現(xiàn)0.96英寸的OLED128x64顯示屏的驅(qū)動設(shè)計(jì)。
2.1、對象定義
??在使用一個(gè)對象之前我們需要獲得一個(gè)對象。同樣的我們想要OLED顯示屏就需要先定義OLED顯示屏的對象。
2.1.1、對象的抽象
??我們要得到OLED顯示屏對象,需要先分析其基本特性。一般來說,一個(gè)對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個(gè)方面思考一下OLED顯示屏的對象。
??先來考慮屬性,作為屬性肯定是用于標(biāo)識或記錄對象特征的東西。我們來考慮0.96英寸的OLED128x64顯示屏對象屬性。我們考慮SPI和I2C兩種接口的情形,所以我們要分辨當(dāng)前使用的接口形式以確定采取適當(dāng)?shù)牟僮鞣绞剑晕覀儗?a target="_blank">端口類型設(shè)置為其屬性以保存當(dāng)前的操作接口類型。在I2C接口時(shí),每一臺I2C從設(shè)備都需要有一個(gè)設(shè)備地址,我們要記錄當(dāng)前從設(shè)備的地址,所以將其設(shè)置為屬性。
??接著我們還需要考慮OLED顯示屏對象的操作問題。在SPI接口模式下,我們需要控制復(fù)位、數(shù)據(jù)命令選擇以及片選控制引腳,而在I2C接口模式下,我們需要控制復(fù)位引腳。這些控制引腳的操作都依賴于具體的硬件平臺,所以我們將其作為對象的操作。我們要想OLED發(fā)送命令和數(shù)據(jù),但不論是何種接口類型這一操作都依賴于具體的軟硬件平臺,所以我們將其作為對象的操作。為了控制操作時(shí)序,我們需要延時(shí)操作函數(shù),而延時(shí)操作也依賴于具體的軟硬件平臺,所以我們將其作為對象的操作。
??根據(jù)上述我們對OLED顯示屏的分析,我們可以定義OLED顯示屏的對象類型如下:
/*定義OLED對象類型*/
typedef struct OledObject {
uint8_t devAddress;
OledPortType port;
void (*Write)(struct OledObject *oled,uint8_t *wData,uint16_t wSize);
void (*ChipSelcet)(OledCSType en);
void (*DCSelcet)(OledDCType dc);
void (*ChipReset)(OledRSTType rst);
void (*Delayms)(volatile uint32_t nTime);
}OledObjectType;
2.1.2、對象初始化
??我們知道,一個(gè)對象僅作聲明是不能使用的,我們需要先對其進(jìn)行初始化,所以這里我們來考慮OLED顯示屏對象的初始化函數(shù)。一般來說,初始化函數(shù)需要處理幾個(gè)方面的問題。一是檢查輸入?yún)?shù)是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。
??而且0.96英寸的OLED128x64顯示屏在實(shí)現(xiàn)復(fù)位引腳的操作后將實(shí)現(xiàn)其初始化配置。據(jù)此我們設(shè)計(jì)OLED顯示屏對象的初始化函數(shù)如下:
/*OLED顯示屏對象初始化*/
void OledInitialization(OledObjectType *oled, //OLED對象
OledPortType port, //通訊端口
uint8_t address, //I2C設(shè)備地址
OledWrite write, //寫數(shù)據(jù)函數(shù)
OledChipReset rst, //復(fù)位信號操作函數(shù)指針
OledDCSelcet dc, //DC信號控制函數(shù)指針
OledChipSelcet cs, //SPI片選信號函數(shù)指針
OledDelayms delayms //毫秒延時(shí)函數(shù)指針
)
{
if((oled==NULL)||(write==NULL)||(rst==NULL) ||(delayms==NULL))
{
return;
}
oled->Write=write;
oled->ChipReset=rst;
oled->Delayms=delayms;
oled->port=port;
if(port==OLED_I2C)
{
if((address==0x3C)||(address==0x3D))
{
oled->devAddress=(address<<1);
}
else if((address==0x78)||(address==0x7A))
{
oled->devAddress=address;
}
else
{
oled->devAddress=0x00;
}
if(dc==NULL)
{
return;
}
oled->DCSelcet=dc;
oled->ChipSelcet=cs;
}
else
{
oled->devAddress=0xFF;
if(cs==NULL)
{
oled->ChipSelcet=OledChipSelect;
}
else
{
oled->ChipSelcet=cs;
}
oled->DCSelcet=dc;
}
oled->ChipReset(OLED_WORK);
oled->Delayms(100);
oled->ChipReset(OLED_RESET);
oled->Delayms(100);
oled->ChipReset(OLED_WORK);
SendToOled(oled,0xAE,OLEDDC_Command); //關(guān)閉顯示
SendToOled(oled,0x20,OLEDDC_Command); //Set Memory Addressing Mode
SendToOled(oled,0x10,OLEDDC_Command); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
SendToOled(oled,0xB0,OLEDDC_Command); //Set Page Start Address for Page Addressing Mode,0-7
SendToOled(oled,0xA1,OLEDDC_Command); //0xa0,X軸正常顯示;0xa1,X軸鏡像顯示
SendToOled(oled,0xC8,OLEDDC_Command); //0xc0,Y軸正常顯示;0xc8,Y軸鏡像顯示
SendToOled(oled,0x00,OLEDDC_Command); //設(shè)置列地址低4位
SendToOled(oled,0x10,OLEDDC_Command); //設(shè)置列地址高4位
SendToOled(oled,0x40,OLEDDC_Command); //設(shè)置起始線地址
SendToOled(oled,0x81,OLEDDC_Command); //設(shè)置對比度值
SendToOled(oled,0x7F,OLEDDC_Command); //------
SendToOled(oled,0xA6,OLEDDC_Command); //0xa6,正常顯示模式;0xa7,
SendToOled(oled,0xA8,OLEDDC_Command); //--set multiplex ratio(1 to 64)
SendToOled(oled,0x3F,OLEDDC_Command); //------
SendToOled(oled,0xA4,OLEDDC_Command); //0xa4,顯示跟隨RAM的改變而改變;0xa5,顯示內(nèi)容忽略RAM的內(nèi)容
SendToOled(oled,0xD3,OLEDDC_Command); //設(shè)置顯示偏移
SendToOled(oled,0x00,OLEDDC_Command); //------
SendToOled(oled,0xD5,OLEDDC_Command); //設(shè)置內(nèi)部顯示時(shí)鐘頻率
SendToOled(oled,0xF0,OLEDDC_Command); //------
SendToOled(oled,0xD9,OLEDDC_Command); //--set pre-charge period
SendToOled(oled,0x22,OLEDDC_Command); //------
SendToOled(oled,0xDA,OLEDDC_Command); //--set com pins hardware configuration
SendToOled(oled,0x12,OLEDDC_Command); //------
SendToOled(oled,0xDB,OLEDDC_Command); //--set vcomh
SendToOled(oled,0x20,OLEDDC_Command); //------
SendToOled(oled,0x8D,OLEDDC_Command); //--set DC-DC enable
SendToOled(oled,0x14,OLEDDC_Command); //------
SendToOled(oled,0xAF,OLEDDC_Command); //打開顯示
OledClearScreen(oled);
}
2.2、對象操作
??我們已經(jīng)完成了OLED顯示屏對象類型的定義和對象初始化函數(shù)的設(shè)計(jì)。但我們的主要目標(biāo)是獲取對象的信息,接下來我們還要實(shí)現(xiàn)面向OLED顯示屏的各類操作。
??對于0.96英寸的OLED128x64顯示屏來說,不論是采用何種接口方式,也不論是需要顯示什么內(nèi)容。對于我們來說,雖然在不同的接口模式下操作會有些許差別,但本質(zhì)上都是向OLED寫數(shù)據(jù)。
??在SPI接口模式下,我們在向OLED發(fā)送數(shù)據(jù)和命令時(shí),需要同時(shí)操作片選信號和數(shù)據(jù)命令選擇信號,以表明需要操作的對象和發(fā)送的是數(shù)據(jù)還是命令。具體的操作時(shí)序如下:
??在I2C接口模式下,我們在向OLED發(fā)送數(shù)據(jù)和命令時(shí),沒有片選和數(shù)據(jù)命令選擇信號,所以我們需要發(fā)送從站地址以區(qū)分要操作的對象,需要發(fā)送控制字節(jié)以區(qū)分是數(shù)據(jù)還是命令。具體的操作時(shí)序如下:
??根據(jù)前述對0.96英寸的OLED128x64顯示屏的描述以及上述時(shí)序圖,我們可以編寫向OLED發(fā)送數(shù)據(jù)的函數(shù)如下:
/*向OLED發(fā)送數(shù)據(jù)*/
static void SendToOled(OledObjectType *oled,uint8_t sData,OledDCType type)
{
uint8_t wData[2];
if(oled->port==OLED_SPI)
{
oled->ChipSelcet(OLEDCS_Enable);
if(type==OLEDDC_Command)
{
oled->DCSelcet(OLEDDC_Command);
}
else
{
oled->DCSelcet(OLEDDC_Data);
}
oled->Write(oled,&sData,1);
oled->ChipSelcet(OLEDCS_Disable);
}
else
{
if(type==OLEDDC_Command)
{
wData[0]=0x00;
}
else
{
wData[0]=0x40;
}
wData[1]=sData;
oled->Write(oled,wData,2);
}
}
3、驅(qū)動的使用
??我們已經(jīng)實(shí)現(xiàn)了0.96英寸的OLED128x64顯示屏驅(qū)動設(shè)計(jì)及實(shí)現(xiàn),現(xiàn)在我們需要對這一驅(qū)動進(jìn)行驗(yàn)證,基于此我們需要設(shè)計(jì)一個(gè)簡單的驗(yàn)證應(yīng)用。
3.1、聲明并初始化對象
??使用基于對象的操作我們需要先得到這個(gè)對象,所以我們先要使用前面定義的OLED顯示屏對象類型聲明一個(gè)OLED顯示屏對象變量,具體操作格式如下:
??OledObjectType oled;
??聲明了這個(gè)對象變量并不能立即使用,我們還需要使用驅(qū)動中定義的初始化函數(shù)對這個(gè)變量進(jìn)行初始化。這個(gè)初始化函數(shù)所需要的輸入?yún)?shù)如下:
??OledObjectType *oled, //OLED對象
??OledPortType port, //通訊端口
?? uint8_t address, //I2C設(shè)備地址
??OledWrite write, //寫數(shù)據(jù)函數(shù)
??OledChipReset rst, //復(fù)位信號操作函數(shù)指針
??OledDCSelcet dc, //DC信號控制函數(shù)指針
??OledChipSelcet cs, //SPI片選信號函數(shù)指針
??OledDelayms delayms //毫秒延時(shí)函數(shù)指針
??對于這些參數(shù),對象變量我們已經(jīng)定義了。所使用的通訊接口方式為枚舉,根據(jù)實(shí)際情況選擇就好了。而從站地址對于OLED來說,有幾種選擇,根據(jù)實(shí)際情況輸入就可。主要的是我們需要定義幾個(gè)函數(shù),并將函數(shù)指針作為參數(shù)。這幾個(gè)函數(shù)的類型如下:
/*向OLED下發(fā)指令,指令格式均為1個(gè)字節(jié)*/
typedef void (*OledWrite)(OledObjectType *oled,uint8_t *wData,uint16_t wSize);
/*復(fù)位信號操作函數(shù)指針*/
typedef void (*OledChipReset)(OledRSTType rst);
/*數(shù)據(jù)命令,用于SPI接口*/
typedef void (*OledDCSelcet)(OledDCType dc);
/*片選信號,用于SPI接口*/
typedef void (*OledChipSelcet)(OledCSType en);
/*毫秒秒延時(shí)函數(shù)*/
typedef void (*OledDelayms)(volatile uint32_t nTime);
??對于這幾個(gè)函數(shù)我們根據(jù)樣式定義就可以了,具體的操作可能與使用的硬件平臺有關(guān)系。片選操作函數(shù)用于多設(shè)備需要軟件操作時(shí),如采用硬件片選可以傳入NULL即可。具體函數(shù)定義如下:
void WriteDataToLED(struct OledObject *oled,uint8_t *wData,uint16_t wSize)
{
HAL_I2C_Master_Transmit(&oledhi2c,oled->devAddress,wData,wSize,1000);
}
void OLedChipResetf(OledRSTType rst)
{
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_8,(GPIO_PinState)rst);
}
??對于延時(shí)函數(shù)我們可以采用各種方法實(shí)現(xiàn)。我們采用的STM32平臺和HAL庫則可以直接使用HAL_Delay()函數(shù)。于是我們可以調(diào)用初始化函數(shù)如下:
/*OLED顯示屏對象初始化*/
OledInitialization(&oled, //OLED對象
OLED_I2C, //通訊端口
0x78, //I2C設(shè)備地址
WriteDataToLED, //寫數(shù)據(jù)函數(shù)
OLedChipResetf, //復(fù)位信號操作函數(shù)指針
NULL, //DC信號控制函數(shù)指針
NULL, //SPI片選信號函數(shù)指針
HAL_Delay //毫秒延時(shí)函數(shù)指針
);
??因在I2C接口模式下,片選信號和數(shù)據(jù)命令選擇信號并不需要控制所以以NULL輸入即可。
3.2、基于對象進(jìn)行操作
??我們定義了對象變量并使用初始化函數(shù)給其作了初始化。接著我們就來考慮操作這一對象獲取我們想要的數(shù)據(jù)。我們在驅(qū)動中已經(jīng)針對不同的字體大小設(shè)置了不同的操作函數(shù),接下來我們使用這一驅(qū)動開發(fā)我們的應(yīng)用實(shí)例。
/*OLED顯示信息*/
void OledDisplayMessage(void)
{
/* 世(0) 界(1) 你(2) 好(3)*/
uint8_t chinChar[4][32]={
{0x20,0x20,0x20,0xFE,0x20,0x20,0xFF,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x00,
0x00,0x00,0x00,0x7F,0x40,0x40,0x47,0x44,0x44,0x44,0x47,0x40,0x40,0x40,0x00,0x00},//"世",0
{0x00,0x00,0x00,0xFE,0x92,0x92,0x92,0xFE,0x92,0x92,0x92,0xFE,0x00,0x00,0x00,0x00,
0x08,0x08,0x04,0x84,0x62,0x1E,0x01,0x00,0x01,0xFE,0x02,0x04,0x04,0x08,0x08,0x00},//"界",1
{0x00,0x80,0x60,0xF8,0x07,0x40,0x20,0x18,0x0F,0x08,0xC8,0x08,0x08,0x28,0x18,0x00,
0x01,0x00,0x00,0xFF,0x00,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00},//"你",2
{0x10,0x10,0xF0,0x1F,0x10,0xF0,0x00,0x80,0x82,0x82,0xE2,0x92,0x8A,0x86,0x80,0x00,
0x40,0x22,0x15,0x08,0x16,0x61,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00}//"好",3
};
char pStr[]="Hello, World!";
float x=1.1;
float y=2.2;
float z=3.3;
//顯示16x16的漢字
OledShow16x16Char(&oled,0,32,chinChar[0]);
OledShow16x16Char(&oled,0,48,chinChar[1]);
OledShow16x16Char(&oled,0,64,chinChar[2]);
OledShow16x16Char(&oled,0,80,chinChar[3]);
//顯示8x16的ASCII字符
OledShowString(&oled,OLED_FONT_8x16,2,32,pStr);
//顯示8x16的ASCII字符
OledShowString(&oled,OLED_FONT_8x16,4,20,"X%0.1f,Y%0.1f,Z%0.1f",x,y,z);
}
4、應(yīng)用總結(jié)
??在本篇中,我們設(shè)計(jì)并實(shí)現(xiàn)了0.96英寸的OLED128x64顯示屏的驅(qū)動,并設(shè)計(jì)了一個(gè)簡單的驗(yàn)證應(yīng)用來驗(yàn)證這一驅(qū)動程序。在我們的驗(yàn)證應(yīng)用中使用OLED顯示了16下6點(diǎn)陣的中文字符,以及8x16點(diǎn)陣的ASCII字符,其顯示效果與我們預(yù)期一致。
??在使用驅(qū)動時(shí)需注意,0.96英寸的OLED128x64顯示屏支持SPI和I2C兩種接口,而且SPI也支持3線和4線模式,但我們在測試應(yīng)用中只使用了I2C接口,在I2C接口時(shí),不需要控制片選信號和數(shù)據(jù)命令選擇信號,所以在初始化時(shí)傳遞NULL值就可以了。
??在使用驅(qū)動時(shí)需注意,采用SPI接口的器件需要考慮片選操作的問題。如果片選信號是通過硬件電路來實(shí)現(xiàn)的,我們在初始化時(shí)給其傳遞NULL值。如果是軟件操作片選則傳遞我們編寫的片選操作函數(shù)。在使用SPI接口時(shí),支持SPI模式0(CPOL=CPHA=0)和模式3(CPOL=CPHA=1)。
-
OLED
+關(guān)注
關(guān)注
119文章
6272瀏覽量
227183 -
顯示屏
+關(guān)注
關(guān)注
28文章
4576瀏覽量
75940 -
驅(qū)動設(shè)計(jì)
+關(guān)注
關(guān)注
1文章
111瀏覽量
15493
發(fā)布評論請先 登錄

轉(zhuǎn): GD32驅(qū)動12832OLED顯示屏
怎么實(shí)現(xiàn)OLED矩陣顯示屏控制電路的設(shè)計(jì)?
用51單片機(jī)驅(qū)動oled顯示屏
ESP8266驅(qū)動OLED顯示屏的方法
有機(jī)電致發(fā)光顯示(OLED)及基于AT89C51的OLED顯示屏驅(qū)動電路的設(shè)計(jì)

蘋果iPhone明年將會完全放棄LCD顯示屏轉(zhuǎn)而采用OLED顯示屏
Linux驅(qū)動開發(fā)-編寫OLED顯示屏驅(qū)動

評論