1. 程式人生 > 其它 >外設驅動庫開發筆記34:OLED顯示屏驅動

外設驅動庫開發筆記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)。

歡迎關注:

如果閱讀這篇文章讓您略有所得,還請點選下方的【好文要頂】按鈕。

當然,如果您想及時瞭解我的部落格更新,不妨點選下方的【關注我】按鈕。

如果您希望更方便且及時的閱讀相關文章,也可以掃描上方二維碼關注我的微信公眾號【木南創智