現在OLED顯示屏在嵌入式系統中應用的越來越多。對于一些顯示信息不太復雜,以顯示信息為主的需求,我們一般會選擇OLED顯示屏。在這一篇中,我們將討論OLED顯示屏驅動的設計與實現。
1、功能概述
??從使用的情況來說,較為常用的是0.96英寸的OLED128x64的顯示屏。這種OLED屏多采用象SSD1306這類驅動芯片,所以我們對OLED屏的操作實際就是對控制芯片的操作。
??對于0.96英寸的OLED128x64的顯示屏,其像素點為128x64個,對應在顯示RAM中的128x64個位。在顯存中,這些區域被劃分為8個Page,這些頁的劃分具體如下圖所示:
??在每一頁中包括128x8個位對應相應的像素點,對顯示像素的操作就是對鄉村中對應的位的操作,每頁中像素點的排布如下:
??對于操作0.96英寸的OLED128x64顯示屏的接口有多種,如6800并行接口、8080并行接口、SPI串行接口以及I2C串行接口等。對于并行接口應用較少,現在應用較多的是SPI和I2C這兩種串行總線接口。在SPI接口方式下,有3個控制引腳是需要操作的,分別是復位、數據命令選擇和片選信號。而在I2C接口方式下,僅有復位引腳是可控的,但在發送命令或數據時會多一個字節的控制字。
2、驅動設計與實現
??我們已經了解了0.96英寸的OLED128x64顯示屏的基本情況,在這里我們來考慮如何實現0.96英寸的OLED128x64顯示屏的驅動設計。
2.1、對象定義
??在使用一個對象之前我們需要獲得一個對象。同樣的我們想要OLED顯示屏就需要先定義OLED顯示屏的對象。
2.1.1、對象的抽象
??我們要得到OLED顯示屏對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下OLED顯示屏的對象。
??先來考慮屬性,作為屬性肯定是用于標識或記錄對象特征的東西。我們來考慮0.96英寸的OLED128x64顯示屏對象屬性。我們考慮SPI和I2C兩種接口的情形,所以我們要分辨當前使用的接口形式以確定采取適當的操作方式,所以我們將端口類型設置為其屬性以保存當前的操作接口類型。在I2C接口時,每一臺I2C從設備都需要有一個設備地址,我們要記錄當前從設備的地址,所以將其設置為屬性。
??接著我們還需要考慮OLED顯示屏對象的操作問題。在SPI接口模式下,我們需要控制復位、數據命令選擇以及片選控制引腳,而在I2C接口模式下,我們需要控制復位引腳。這些控制引腳的操作都依賴于具體的硬件平臺,所以我們將其作為對象的操作。我們要想OLED發送命令和數據,但不論是何種接口類型這一操作都依賴于具體的軟硬件平臺,所以我們將其作為對象的操作。為了控制操作時序,我們需要延時操作函數,而延時操作也依賴于具體的軟硬件平臺,所以我們將其作為對象的操作。
??根據上述我們對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、對象初始化
??我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,所以這里我們來考慮OLED顯示屏對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。
??而且0.96英寸的OLED128x64顯示屏在實現復位引腳的操作后將實現其初始化配置。據此我們設計OLED顯示屏對象的初始化函數如下:
/*OLED顯示屏對象初始化*/
void OledInitialization(OledObjectType *oled, //OLED對象
OledPortType port, //通訊端口
uint8_t address, //I2C設備地址
OledWrite write, //寫數據函數
OledChipReset rst, //復位信號操作函數指針
OledDCSelcet dc, //DC信號控制函數指針
OledChipSelcet cs, //SPI片選信號函數指針
OledDelayms delayms //毫秒延時函數指針
)
{
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); //關閉顯示
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); //設置列地址低4位
SendToOled(oled,0x10,OLEDDC_Command); //設置列地址高4位
SendToOled(oled,0x40,OLEDDC_Command); //設置起始線地址
SendToOled(oled,0x81,OLEDDC_Command); //設置對比度值
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,顯示內容忽略RAM的內容
SendToOled(oled,0xD3,OLEDDC_Command); //設置顯示偏移
SendToOled(oled,0x00,OLEDDC_Command); //------
SendToOled(oled,0xD5,OLEDDC_Command); //設置內部顯示時鐘頻率
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、對象操作
??我們已經完成了OLED顯示屏對象類型的定義和對象初始化函數的設計。但我們的主要目標是獲取對象的信息,接下來我們還要實現面向OLED顯示屏的各類操作。
??對于0.96英寸的OLED128x64顯示屏來說,不論是采用何種接口方式,也不論是需要顯示什么內容。對于我們來說,雖然在不同的接口模式下操作會有些許差別,但本質上都是向OLED寫數據。
??在SPI接口模式下,我們在向OLED發送數據和命令時,需要同時操作片選信號和數據命令選擇信號,以表明需要操作的對象和發送的是數據還是命令。具體的操作時序如下:
??在I2C接口模式下,我們在向OLED發送數據和命令時,沒有片選和數據命令選擇信號,所以我們需要發送從站地址以區分要操作的對象,需要發送控制字節以區分是數據還是命令。具體的操作時序如下:
??根據前述對0.96英寸的OLED128x64顯示屏的描述以及上述時序圖,我們可以編寫向OLED發送數據的函數如下:
/*向OLED發送數據*/
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、驅動的使用
??我們已經實現了0.96英寸的OLED128x64顯示屏驅動設計及實現,現在我們需要對這一驅動進行驗證,基于此我們需要設計一個簡單的驗證應用。
3.1、聲明并初始化對象
??使用基于對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的OLED顯示屏對象類型聲明一個OLED顯示屏對象變量,具體操作格式如下:
??OledObjectType oled;
??聲明了這個對象變量并不能立即使用,我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:
??OledObjectType *oled, //OLED對象
??OledPortType port, //通訊端口
?? uint8_t address, //I2C設備地址
??OledWrite write, //寫數據函數
??OledChipReset rst, //復位信號操作函數指針
??OledDCSelcet dc, //DC信號控制函數指針
??OledChipSelcet cs, //SPI片選信號函數指針
??OledDelayms delayms //毫秒延時函數指針
??對于這些參數,對象變量我們已經定義了。所使用的通訊接口方式為枚舉,根據實際情況選擇就好了。而從站地址對于OLED來說,有幾種選擇,根據實際情況輸入就可。主要的是我們需要定義幾個函數,并將函數指針作為參數。這幾個函數的類型如下:
/*向OLED下發指令,指令格式均為1個字節*/
typedef void (*OledWrite)(OledObjectType *oled,uint8_t *wData,uint16_t wSize);
/*復位信號操作函數指針*/
typedef void (*OledChipReset)(OledRSTType rst);
/*數據命令,用于SPI接口*/
typedef void (*OledDCSelcet)(OledDCType dc);
/*片選信號,用于SPI接口*/
typedef void (*OledChipSelcet)(OledCSType en);
/*毫秒秒延時函數*/
typedef void (*OledDelayms)(volatile uint32_t nTime);
??對于這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平臺有關系。片選操作函數用于多設備需要軟件操作時,如采用硬件片選可以傳入NULL即可。具體函數定義如下:
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);
}
??對于延時函數我們可以采用各種方法實現。我們采用的STM32平臺和HAL庫則可以直接使用HAL_Delay()函數。于是我們可以調用初始化函數如下:
/*OLED顯示屏對象初始化*/
OledInitialization(&oled, //OLED對象
OLED_I2C, //通訊端口
0x78, //I2C設備地址
WriteDataToLED, //寫數據函數
OLedChipResetf, //復位信號操作函數指針
NULL, //DC信號控制函數指針
NULL, //SPI片選信號函數指針
HAL_Delay //毫秒延時函數指針
);
??因在I2C接口模式下,片選信號和數據命令選擇信號并不需要控制所以以NULL輸入即可。
3.2、基于對象進行操作
??我們定義了對象變量并使用初始化函數給其作了初始化。接著我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經針對不同的字體大小設置了不同的操作函數,接下來我們使用這一驅動開發我們的應用實例。
/*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、應用總結
??在本篇中,我們設計并實現了0.96英寸的OLED128x64顯示屏的驅動,并設計了一個簡單的驗證應用來驗證這一驅動程序。在我們的驗證應用中使用OLED顯示了16下6點陣的中文字符,以及8x16點陣的ASCII字符,其顯示效果與我們預期一致。
??在使用驅動時需注意,0.96英寸的OLED128x64顯示屏支持SPI和I2C兩種接口,而且SPI也支持3線和4線模式,但我們在測試應用中只使用了I2C接口,在I2C接口時,不需要控制片選信號和數據命令選擇信號,所以在初始化時傳遞NULL值就可以了。
??在使用驅動時需注意,采用SPI接口的器件需要考慮片選操作的問題。如果片選信號是通過硬件電路來實現的,我們在初始化時給其傳遞NULL值。如果是軟件操作片選則傳遞我們編寫的片選操作函數。在使用SPI接口時,支持SPI模式0(CPOL=CPHA=0)和模式3(CPOL=CPHA=1)。
-
OLED
+關注
關注
119文章
6213瀏覽量
224475 -
顯示屏
+關注
關注
28文章
4502瀏覽量
74490 -
驅動設計
+關注
關注
1文章
111瀏覽量
15294
發布評論請先 登錄
相關推薦
評論