Peripheral driver library development notes 34: OLED display driver

Keywords: OLED

  nowadays, OLED displays are more and more used in embedded systems. For some requirements that display information is not too complex and mainly display information, we generally choose OLED display. In this article, we will discuss the design and implementation of OLED display driver.

1. Function overview

  in terms of use, the 0.96 inch OLED128x64 display is commonly used. This OLED screen mostly uses driver chips such as SSD1306, so our operation of OLED screen is actually the operation of control chip.

   for the 0.96 inch OLED128x64 display screen, its pixel points are 128x64, corresponding to 128x64 bits in the display RAM. In the video memory, these areas are divided into 8 pages. The division of these pages is shown in the following figure:

   each page includes 128x8 bits corresponding to corresponding pixel points. The operation of display pixels is the operation of corresponding bits in the countryside. The arrangement of pixel points in each page is as follows:

  there are many interfaces for operating 0.96 inch OLED128x64 display screen, such as 6800 parallel interface, 8080 parallel interface, SPI serial interface and I2C serial interface. There are few applications for parallel interfaces. SPI and I2C serial bus interfaces are widely used now. In SPI interface mode, three control pins need to be operated, namely reset, data command selection and chip selection signal. In I2C interface mode, only the reset pin is controllable, but there will be one more byte of control word when sending command or data.

2. Driver design and Implementation

   we have learned the basic situation of 0.96 inch OLED128x64 display. Here, let's consider how to realize the driver design of 0.96 inch OLED128x64 display.

2.1 object definition

  before using an object, we need to get an object. Similarly, if we want an OLED display, we need to define the object of the OLED display first.

2.1.1 object abstraction

  to get OLED display object, we need to analyze its basic characteristics first. Generally speaking, an object contains at least two characteristics: properties and operations. Next, let's think about the object of OLED display from these two aspects.

    let's consider attributes first. As attributes, they must be used to identify or record the characteristics of objects. Let's consider the 0.96 inch OLED128x64 display object properties. We consider SPI and I2C interfaces, so we need to distinguish the currently used interface form to determine the appropriate operation mode, so we set the port type as its attribute to save the current operation interface type. During I2C interface, each I2C slave device needs to have a device address. We need to record the address of the current slave device, so set it as the attribute.

  then we need to consider the operation of OLED display objects. In SPI interface mode, we need to control reset, data command selection and chip selection control pins, while in I2C interface mode, we need to control reset pins. The operation of these control pins depends on the specific hardware platform, so we take them as the object operation. We want to send commands and data to OLED, but no matter what interface type, this operation depends on the specific software and hardware platform, so we regard it as an object operation. In order to control the operation timing, we need the delay operation function, and the delay operation also depends on the specific software and hardware platform, so we take it as the object operation.

  according to the above analysis of OLED display, we can define the object types of OLED display as follows:

/*Define OLED object types*/
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 object initialization

   we know that an object cannot be used only for declaration. We need to initialize it first, so here we consider the initialization function of OLED display object. Generally speaking, initialization functions need to deal with several aspects. First, check whether the input parameters are reasonable; Second, assign initial values to the attributes of the object; Third, make necessary initialization configuration for objects.

   moreover, the 0.96 inch OLED128x64 display will realize its initialization configuration after the operation of the reset pin. Therefore, we design the initialization function of OLED display object as follows:

/*OLED Display object initialization*/
void OledInitialization(OledObjectType *oled,      //OLED object
        OledPortType port,       //Communication port
        uint8_t address,        //I2C device address
        OledWrite write,        //Write data function
        OledChipReset rst,       //Reset signal operation function pointer
        OledDCSelcet dc,        //DC signal control function pointer
        OledChipSelcet cs,       //SPI chip selection signal function pointer
        OledDelayms delayms       //Millisecond delay function pointer
        )
{
 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); //Turn off display
 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-axis displays normally; 0xa1, X-axis mirror display
 SendToOled(oled,0xC8,OLEDDC_Command); //0xc0, Y-axis displays normally; 0xc8, Y-axis mirror display
 SendToOled(oled,0x00,OLEDDC_Command); //Set the lower 4 bits of the column address
 SendToOled(oled,0x10,OLEDDC_Command); //Set the upper 4 bits of the column address
 SendToOled(oled,0x40,OLEDDC_Command); //Set start line address
 
 SendToOled(oled,0x81,OLEDDC_Command); //Set contrast value
 SendToOled(oled,0x7F,OLEDDC_Command); //------
 
 SendToOled(oled,0xA6,OLEDDC_Command); //0xa6, normal display mode; 0xa7,
 
 SendToOled(oled,0xA8,OLEDDC_Command); //--set multiplex ratio(1 to 64)
 SendToOled(oled,0x3F,OLEDDC_Command); //------
 
 SendToOled(oled,0xA4,OLEDDC_Command); //0xa4, the display changes with the change of ram; 0xa5, display contents ignore RAM contents
 
 SendToOled(oled,0xD3,OLEDDC_Command); //Set display offset
 SendToOled(oled,0x00,OLEDDC_Command); //------
 
 SendToOled(oled,0xD5,OLEDDC_Command); //Set internal display clock frequency
 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); //Open display
 
 OledClearScreen(oled);
}

2.2 object operation

  we have completed the definition of OLED display object type and the design of object initialization function. However, our main goal is to obtain the information of the object. Next, we have to realize various operations for OLED display.

  for the 0.96 inch OLED128x64 display, no matter what interface mode is adopted or what content needs to be displayed. For us, although there are some differences in the operation under different interface modes, they are essentially writing data to OLEDs.

   in SPI interface mode, when sending data and commands to OLED, we need to operate the chip selection signal and data command selection signal at the same time to indicate the object to be operated and whether the data or command is sent. The specific operation sequence is as follows:

   in the I2C interface mode, when we send data and commands to OLED, there are no chip selection and data command selection signals, so we need to send the slave address to distinguish the object to be operated, and we need to send the control byte to distinguish whether it is data or command. The specific operation sequence is as follows:

   according to the above description of 0.96 inch OLED128x64 display screen and the above timing diagram, we can write the function of sending data to OLED as follows:

/*Send data to 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. Use of drivers

  we have implemented the design and implementation of 0.96 inch OLED128x64 display driver. Now we need to verify this driver. Based on this, we need to design a simple verification application.

3.1. Declare and initialize objects

   to use object-based operation, we need to get this object first, so we must first declare an OLED display object variable using the OLED display object type defined above. The specific operation format is as follows:

  OledObjectType oled;

   the declared object variable cannot be used immediately. We also need to initialize the variable with the initialization function defined in the driver. The input parameters required for this initialization function are as follows:

  OledObjectType *oled, //OLED object

  OledPortType port, / / communication port

   uint8_t address, //I2C device address

   OledWrite write, / / write data function

   OledChipReset rst, / / reset the signal operation function pointer

   OledDCSelcet dc, //DC signal control function pointer

   OledChipSelcet cs, //SPI chip selection signal function pointer

   OledDelayms delayms / / pointer to millisecond delay function

  for these parameters, the object variables have been defined. The communication interface used is enumeration, which can be selected according to the actual situation. The slave address has several options for OLED, which can be entered according to the actual situation. The main thing is that we need to define several functions and take function pointers as parameters. The types of these functions are as follows:

/*Issue instructions to OLED in the format of 1 byte*/
typedef void (*OledWrite)(OledObjectType *oled,uint8_t *wData,uint16_t wSize);

/*Reset signal operation function pointer*/
typedef void (*OledChipReset)(OledRSTType rst);

/*Data command for SPI interface*/
typedef void (*OledDCSelcet)(OledDCType dc);

/*Chip selection signal for SPI interface*/
typedef void (*OledChipSelcet)(OledCSType en);   

/*Millisecond delay function*/
typedef void (*OledDelayms)(volatile uint32_t nTime);  

  for these functions, we can define them according to the style. The specific operation may be related to the hardware platform used. The chip selection operation function is used when software operation is required for multiple devices. If hardware chip selection is adopted, NULL can be passed in. The specific functions are defined as follows:

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);
}

   we can use various methods to implement the delay function. The STM32 platform and HAL library we use can directly use HAL_Delay() function. So we can call the initialization function as follows:

/*OLED Display object initialization*/
OledInitialization(&oled,     //OLED object
         OLED_I2C,    //Communication port
         0x78,      //I2C device address
         WriteDataToLED, //Write data function
         OLedChipResetf, //Reset signal operation function pointer
         NULL,      //DC signal control function pointer
         NULL,      //SPI chip selection signal function pointer
         HAL_Delay    //Millisecond delay function pointer
         );

   in I2C interface mode, the chip selection signal and data command selection signal do not need to be controlled, so it can be input with NULL.

3.2. Operation based on object

  we defined the object variable and initialized it with the initialization function. Then we will consider operating this object to obtain the data we want. We have set different operation functions for different font sizes in the driver. Next, we use this driver to develop our application example.

/*OLED display information*/
void OledDisplayMessage(void)
{
 /* World (0) (1) you (2) good (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},//"World", 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},//"Boundary", 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},//"You", 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}//"OK", 3
 };
 
 char pStr[]="Hello, World!";
 
 float x=1.1;
 float y=2.2;
 float z=3.3;
 
 //Display 16x16 Chinese characters
 OledShow16x16Char(&oled,0,32,chinChar[0]);
 OledShow16x16Char(&oled,0,48,chinChar[1]);
 OledShow16x16Char(&oled,0,64,chinChar[2]);
 OledShow16x16Char(&oled,0,80,chinChar[3]);
 
 //Displays ASCII characters of 8x16
 OledShowString(&oled,OLED_FONT_8x16,2,32,pStr);
 //Displays ASCII characters of 8x16
 OledShowString(&oled,OLED_FONT_8x16,4,20,"X%0.1f,Y%0.1f,Z%0.1f",x,y,z);
 
}

4. Application Summary

  in this article, we design and implement the driver of 0.96 inch OLED128x64 display screen, and design a simple verification application to verify the driver. In our verification application, OLED is used to display Chinese characters with 16 lower 6-dot matrix and ASCII characters with 8x16 dot matrix. The display effect is consistent with our expectation.

   when using the driver, it should be noted that the 0.96 inch OLED128x64 display supports SPI and I2C interfaces, and SPI also supports 3-wire and 4-wire modes. However, we only use the I2C interface in the test application. When using the I2C interface, we do not need to control the chip selection signal and data command selection signal, so we can pass NULL value during initialization.

   when using the driver, it should be noted that the chip selection operation should be considered for devices with SPI interface. If the chip selection signal is realized through hardware circuit, we pass NULL value to it during initialization. If it is a software operation chip selection, the chip selection operation function written by us will be passed. When using SPI interface, SPI mode 0 (CPOL=CPHA=0) and mode 3 (CPOL=CPHA=1) are supported.

Welcome to:

Posted by Sven70 on Sun, 21 Nov 2021 14:15:58 -0800