现在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,InvalidSendToOled(oled,0xB0,OLEDDC_Command); //Set Page Start Address for Page Addressing Mode,0-7SendToOled(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 periodSendToOled(oled,0x22,OLEDDC_Command); //------SendToOled(oled,0xDA,OLEDDC_Command); //--set com pins hardware configurationSendToOled(oled,0x12,OLEDDC_Command); //------SendToOled(oled,0xDB,OLEDDC_Command); //--set vcomhSendToOled(oled,0x20,OLEDDC_Command); //------SendToOled(oled,0x8D,OLEDDC_Command); //--set DC-DC enableSendToOled(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)。

欢迎关注:

外设驱动库开发笔记34:OLED显示屏驱动相关推荐

  1. 外设驱动库开发笔记33:LCD1602液晶显示屏驱动

      LCD1602是一种工业字符型液晶,能够同时显示16x02即32个字符.LCD1602液晶显示的原理是利用液晶的物理特性,通过电压对其显示区域进行控制,即可以显示出图形.在这一章我们就来讨论LCD ...

  2. 外设驱动库开发笔记0:EPD总体设计

    在产品开发过程中,不可避免需要使用很多外部的元件及传感器,这些元器件也许是板载的,也许是板外的,但不管怎样,为其开发驱动程序都是必须的.每次都需要为这些元器件编写驱动程序.但每次重复编写调试很麻烦,于 ...

  3. 外设驱动库开发笔记22:ADXL345三轴数字加速度计驱动

    移动设备的广泛应用增加对移动过程中各种参数的检测需求.ADXL345三轴数字加速度计可以用来检测加速度.进而测量倾斜角度等.在这一篇中,我们将讨论ADXL345三轴数字加速度计驱动程序的设计与实现. ...

  4. 外设驱动库开发笔记13:MLX90614红外温度传感器驱动

    红外温度传感器一般用于非接触式的温度检测.在我们的系统中经常会有这样的需求.所以我们将其设计为通用的驱动库以备复用.这一篇我们将讲述MLX90614红外温度传感器驱动的设计与实现. 1.功能概述 ML ...

  5. 外设驱动库开发笔记9:SHT1x系列温湿度传感器驱动

    在我们的产品中,经常需要检测温湿度数据.有很多检测温湿度的方法和模块,其中SHT1x系列温湿度传感器就是一种成本较低使用方便的温湿度检测模块.下面我们就来说一说如何实现SHT1x系列温湿度传感器的驱动 ...

  6. python实现sht驱动_【技术】外设驱动库开发笔记9:SHT1x系列温湿度传感器驱动

    在我们的产品中,经常需要检测温湿度数据.有很多检测温湿度的方法和模块,其中SHT1x系列温湿度传感器就是一种成本较低使用方便的温湿度检测模块.下面我们就来说一说如何实现SHT1x系列温湿度传感器的驱动 ...

  7. 外设驱动库开发笔记54:外设库驱动设计改进的思考

      不知不觉中我们已经发布了五十多篇外设驱动的文章.前段时间有一位网友提出了一些非常中肯的建议,这也让我们开始考虑怎么优化驱动程序设计的问题.在这一篇中我们将来讨论这一问题. 1.问题分析   首先我 ...

  8. 外设驱动库开发笔记35:迪文触摸屏驱动

      有些时候嵌入式系统也需要显示更为复杂的图形,需要更丰富的数据展示.为此,我们需要更大,色彩更丰富,带触屏的显示屏,当然性价比更高就最好了.在我们的项目中遇到此类需求,我们有时会选择DWIN触摸屏. ...

  9. 外设驱动库开发笔记29:DS17887实时时钟驱动

    一些时候,在我们的嵌入式产品中需要记录时间,所以我们就需要获取时钟,当然实现的方式多种多样,有的MCU本身就有这一功能,不过精度较低.当我们的应用要求较高时就需要使用专门的实时时钟芯片,如DS1788 ...

最新文章

  1. 无法安装驱动程序此计算机上不存在,11.2.4 “安装程序没有找到安装在此计算机上的硬盘驱动器”问题 (1)...
  2. 关于SAP Cloud for Customer Cloud Application Studio登录失败的问题
  3. XAMPP on Mac 组态 Virual Host
  4. Redis(二):Redis入门与性能测试
  5. asp.net网页中导出EXCEL,WORD的环境设置验证实例
  6. linux中cv调用摄像头,ubuntu中opencv调用摄像头
  7. 模型计算量(FLOPs)和参数量(Params)的理解
  8. C# CRC16 在textbox中显示实时时间
  9. Java解析SQL生成语法树_Atitit.sql ast 表达式 语法树 语法 解析原理与实现 java php c#.net js python...
  10. 《手把手教你学DSP》总结1
  11. 计算机的启动盘,做win7启动盘制作方法
  12. android自定义图标下载,Android使用IconFont矢量图标库
  13. angular中uibpagination里面参数配置
  14. Navigation导航系统
  15. 【手把手带你Godot游戏开发】FlappyBird:1.Hello World
  16. 最笨的管理,就是什么事都自己做
  17. MongoDB最新最佳连接工具:Robo 3T
  18. 汽车防抱死制动系统(ABS)技术
  19. 使用chrome调试手机,平板,pc上各种浏览器上的页面
  20. css 水平垂直居中的几种常见方式

热门文章

  1. ASP.NET MVC教程四:ASP.NET MVC中页面传值的几种方式
  2. 关于表情符号与UTF-8的探讨
  3. 关于TCP/IP协议及网络通信相关问题
  4. C#AE创建FeatureDataset/创建数据、集要素集
  5. Delphi中动态调用DLL的方法
  6. 【ACM】hdu_1862_EXCEL排序_201308091948
  7. 用SQL实现记录上下移动的思路
  8. 4.1-大秦立国-ip演变
  9. 王道计算机组成原理 物理层整理 超详细版
  10. java 搭建 web服务器 socket实现