外設驅動庫開發筆記34:OLED顯示屏驅動
現在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)。
歡迎關注:
如果閱讀這篇文章讓您略有所得,還請點選下方的【好文要頂】按鈕。
當然,如果您想及時瞭解我的部落格更新,不妨點選下方的【關注我】按鈕。
如果您希望更方便且及時的閱讀相關文章,也可以掃描上方二維碼關注我的微信公眾號【木南創智】