現(xiàn)在OLED顯示屏在嵌入式系統(tǒng)中應(yīng)用的越來越多。對(duì)于一些顯示信息不太復(fù)雜,以顯示信息為主的需求,我們一般會(huì)選擇OLED顯示屏。在這一篇中,我們將討論OLED顯示屏驅(qū)動(dòng)的設(shè)計(jì)與實(shí)現(xiàn)。
1、功能概述
??從使用的情況來說,較為常用的是0.96英寸的OLED128x64的顯示屏。這種OLED屏多采用象SSD1306這類驅(qū)動(dòng)芯片,所以我們對(duì)OLED屏的操作實(shí)際就是對(duì)控制芯片的操作。
??對(duì)于0.96英寸的OLED128x64的顯示屏,其像素點(diǎn)為128x64個(gè),對(duì)應(yīng)在顯示RAM中的128x64個(gè)位。在顯存中,這些區(qū)域被劃分為8個(gè)Page,這些頁(yè)的劃分具體如下圖所示:
??在每一頁(yè)中包括128x8個(gè)位對(duì)應(yīng)相應(yīng)的像素點(diǎn),對(duì)顯示像素的操作就是對(duì)鄉(xiāng)村中對(duì)應(yīng)的位的操作,每頁(yè)中像素點(diǎn)的排布如下:
??對(duì)于操作0.96英寸的OLED128x64顯示屏的接口有多種,如6800并行接口、8080并行接口、SPI串行接口以及I2C串行接口等。對(duì)于并行接口應(yīng)用較少,現(xiàn)在應(yīng)用較多的是SPI和I2C這兩種串行總線接口。在SPI接口方式下,有3個(gè)控制引腳是需要操作的,分別是復(fù)位、數(shù)據(jù)命令選擇和片選信號(hào)。而在I2C接口方式下,僅有復(fù)位引腳是可控的,但在發(fā)送命令或數(shù)據(jù)時(shí)會(huì)多一個(gè)字節(jié)的控制字。
2、驅(qū)動(dòng)設(shè)計(jì)與實(shí)現(xiàn)
??我們已經(jīng)了解了0.96英寸的OLED128x64顯示屏的基本情況,在這里我們來考慮如何實(shí)現(xiàn)0.96英寸的OLED128x64顯示屏的驅(qū)動(dòng)設(shè)計(jì)。
2.1、對(duì)象定義
??在使用一個(gè)對(duì)象之前我們需要獲得一個(gè)對(duì)象。同樣的我們想要OLED顯示屏就需要先定義OLED顯示屏的對(duì)象。
2.1.1、對(duì)象的抽象
??我們要得到OLED顯示屏對(duì)象,需要先分析其基本特性。一般來說,一個(gè)對(duì)象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個(gè)方面思考一下OLED顯示屏的對(duì)象。
??先來考慮屬性,作為屬性肯定是用于標(biāo)識(shí)或記錄對(duì)象特征的東西。我們來考慮0.96英寸的OLED128x64顯示屏對(duì)象屬性。我們考慮SPI和I2C兩種接口的情形,所以我們要分辨當(dāng)前使用的接口形式以確定采取適當(dāng)?shù)牟僮鞣绞剑晕覀儗?a target="_blank">端口類型設(shè)置為其屬性以保存當(dāng)前的操作接口類型。在I2C接口時(shí),每一臺(tái)I2C從設(shè)備都需要有一個(gè)設(shè)備地址,我們要記錄當(dāng)前從設(shè)備的地址,所以將其設(shè)置為屬性。
??接著我們還需要考慮OLED顯示屏對(duì)象的操作問題。在SPI接口模式下,我們需要控制復(fù)位、數(shù)據(jù)命令選擇以及片選控制引腳,而在I2C接口模式下,我們需要控制復(fù)位引腳。這些控制引腳的操作都依賴于具體的硬件平臺(tái),所以我們將其作為對(duì)象的操作。我們要想OLED發(fā)送命令和數(shù)據(jù),但不論是何種接口類型這一操作都依賴于具體的軟硬件平臺(tái),所以我們將其作為對(duì)象的操作。為了控制操作時(shí)序,我們需要延時(shí)操作函數(shù),而延時(shí)操作也依賴于具體的軟硬件平臺(tái),所以我們將其作為對(duì)象的操作。
??根據(jù)上述我們對(duì)OLED顯示屏的分析,我們可以定義OLED顯示屏的對(duì)象類型如下:
/*定義OLED對(duì)象類型*/
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、對(duì)象初始化
??我們知道,一個(gè)對(duì)象僅作聲明是不能使用的,我們需要先對(duì)其進(jìn)行初始化,所以這里我們來考慮OLED顯示屏對(duì)象的初始化函數(shù)。一般來說,初始化函數(shù)需要處理幾個(gè)方面的問題。一是檢查輸入?yún)?shù)是否合理;二是為對(duì)象的屬性賦初值;三是對(duì)對(duì)象作必要的初始化配置。
??而且0.96英寸的OLED128x64顯示屏在實(shí)現(xiàn)復(fù)位引腳的操作后將實(shí)現(xiàn)其初始化配置。據(jù)此我們?cè)O(shè)計(jì)OLED顯示屏對(duì)象的初始化函數(shù)如下:
/*OLED顯示屏對(duì)象初始化*/
void OledInitialization(OledObjectType *oled, //OLED對(duì)象
OledPortType port, //通訊端口
uint8_t address, //I2C設(shè)備地址
OledWrite write, //寫數(shù)據(jù)函數(shù)
OledChipReset rst, //復(fù)位信號(hào)操作函數(shù)指針
OledDCSelcet dc, //DC信號(hào)控制函數(shù)指針
OledChipSelcet cs, //SPI片選信號(hào)函數(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è)置對(duì)比度值
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、對(duì)象操作
??我們已經(jīng)完成了OLED顯示屏對(duì)象類型的定義和對(duì)象初始化函數(shù)的設(shè)計(jì)。但我們的主要目標(biāo)是獲取對(duì)象的信息,接下來我們還要實(shí)現(xiàn)面向OLED顯示屏的各類操作。
??對(duì)于0.96英寸的OLED128x64顯示屏來說,不論是采用何種接口方式,也不論是需要顯示什么內(nèi)容。對(duì)于我們來說,雖然在不同的接口模式下操作會(huì)有些許差別,但本質(zhì)上都是向OLED寫數(shù)據(jù)。
??在SPI接口模式下,我們?cè)谙騉LED發(fā)送數(shù)據(jù)和命令時(shí),需要同時(shí)操作片選信號(hào)和數(shù)據(jù)命令選擇信號(hào),以表明需要操作的對(duì)象和發(fā)送的是數(shù)據(jù)還是命令。具體的操作時(shí)序如下:
??在I2C接口模式下,我們?cè)谙騉LED發(fā)送數(shù)據(jù)和命令時(shí),沒有片選和數(shù)據(jù)命令選擇信號(hào),所以我們需要發(fā)送從站地址以區(qū)分要操作的對(duì)象,需要發(fā)送控制字節(jié)以區(qū)分是數(shù)據(jù)還是命令。具體的操作時(shí)序如下:
??根據(jù)前述對(duì)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ū)動(dòng)的使用
??我們已經(jīng)實(shí)現(xiàn)了0.96英寸的OLED128x64顯示屏驅(qū)動(dòng)設(shè)計(jì)及實(shí)現(xiàn),現(xiàn)在我們需要對(duì)這一驅(qū)動(dòng)進(jìn)行驗(yàn)證,基于此我們需要設(shè)計(jì)一個(gè)簡(jiǎn)單的驗(yàn)證應(yīng)用。
3.1、聲明并初始化對(duì)象
??使用基于對(duì)象的操作我們需要先得到這個(gè)對(duì)象,所以我們先要使用前面定義的OLED顯示屏對(duì)象類型聲明一個(gè)OLED顯示屏對(duì)象變量,具體操作格式如下:
??OledObjectType oled;
??聲明了這個(gè)對(duì)象變量并不能立即使用,我們還需要使用驅(qū)動(dòng)中定義的初始化函數(shù)對(duì)這個(gè)變量進(jìn)行初始化。這個(gè)初始化函數(shù)所需要的輸入?yún)?shù)如下:
??OledObjectType *oled, //OLED對(duì)象
??OledPortType port, //通訊端口
?? uint8_t address, //I2C設(shè)備地址
??OledWrite write, //寫數(shù)據(jù)函數(shù)
??OledChipReset rst, //復(fù)位信號(hào)操作函數(shù)指針
??OledDCSelcet dc, //DC信號(hào)控制函數(shù)指針
??OledChipSelcet cs, //SPI片選信號(hào)函數(shù)指針
??OledDelayms delayms //毫秒延時(shí)函數(shù)指針
??對(duì)于這些參數(shù),對(duì)象變量我們已經(jīng)定義了。所使用的通訊接口方式為枚舉,根據(jù)實(shí)際情況選擇就好了。而從站地址對(duì)于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ù)位信號(hào)操作函數(shù)指針*/
typedef void (*OledChipReset)(OledRSTType rst);
/*數(shù)據(jù)命令,用于SPI接口*/
typedef void (*OledDCSelcet)(OledDCType dc);
/*片選信號(hào),用于SPI接口*/
typedef void (*OledChipSelcet)(OledCSType en);
/*毫秒秒延時(shí)函數(shù)*/
typedef void (*OledDelayms)(volatile uint32_t nTime);
??對(duì)于這幾個(gè)函數(shù)我們根據(jù)樣式定義就可以了,具體的操作可能與使用的硬件平臺(tái)有關(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);
}
??對(duì)于延時(shí)函數(shù)我們可以采用各種方法實(shí)現(xiàn)。我們采用的STM32平臺(tái)和HAL庫(kù)則可以直接使用HAL_Delay()函數(shù)。于是我們可以調(diào)用初始化函數(shù)如下:
/*OLED顯示屏對(duì)象初始化*/
OledInitialization(&oled, //OLED對(duì)象
OLED_I2C, //通訊端口
0x78, //I2C設(shè)備地址
WriteDataToLED, //寫數(shù)據(jù)函數(shù)
OLedChipResetf, //復(fù)位信號(hào)操作函數(shù)指針
NULL, //DC信號(hào)控制函數(shù)指針
NULL, //SPI片選信號(hào)函數(shù)指針
HAL_Delay //毫秒延時(shí)函數(shù)指針
);
??因在I2C接口模式下,片選信號(hào)和數(shù)據(jù)命令選擇信號(hào)并不需要控制所以以NULL輸入即可。
3.2、基于對(duì)象進(jìn)行操作
??我們定義了對(duì)象變量并使用初始化函數(shù)給其作了初始化。接著我們就來考慮操作這一對(duì)象獲取我們想要的數(shù)據(jù)。我們?cè)隍?qū)動(dòng)中已經(jīng)針對(duì)不同的字體大小設(shè)置了不同的操作函數(shù),接下來我們使用這一驅(qū)動(dòng)開發(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é)
??在本篇中,我們?cè)O(shè)計(jì)并實(shí)現(xiàn)了0.96英寸的OLED128x64顯示屏的驅(qū)動(dòng),并設(shè)計(jì)了一個(gè)簡(jiǎn)單的驗(yàn)證應(yīng)用來驗(yàn)證這一驅(qū)動(dòng)程序。在我們的驗(yàn)證應(yīng)用中使用OLED顯示了16下6點(diǎn)陣的中文字符,以及8x16點(diǎn)陣的ASCII字符,其顯示效果與我們預(yù)期一致。
??在使用驅(qū)動(dòng)時(shí)需注意,0.96英寸的OLED128x64顯示屏支持SPI和I2C兩種接口,而且SPI也支持3線和4線模式,但我們?cè)跍y(cè)試應(yīng)用中只使用了I2C接口,在I2C接口時(shí),不需要控制片選信號(hào)和數(shù)據(jù)命令選擇信號(hào),所以在初始化時(shí)傳遞NULL值就可以了。
??在使用驅(qū)動(dòng)時(shí)需注意,采用SPI接口的器件需要考慮片選操作的問題。如果片選信號(hào)是通過硬件電路來實(shí)現(xiàn)的,我們?cè)诔跏蓟瘯r(shí)給其傳遞NULL值。如果是軟件操作片選則傳遞我們編寫的片選操作函數(shù)。在使用SPI接口時(shí),支持SPI模式0(CPOL=CPHA=0)和模式3(CPOL=CPHA=1)。
-
OLED
+關(guān)注
關(guān)注
119文章
6276瀏覽量
227597 -
顯示屏
+關(guān)注
關(guān)注
28文章
4593瀏覽量
76119 -
驅(qū)動(dòng)設(shè)計(jì)
+關(guān)注
關(guān)注
1文章
111瀏覽量
15520
發(fā)布評(píng)論請(qǐng)先 登錄

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

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

評(píng)論