04_基于wiringPi的OLED驱动编写

目的:

       通过OLED的屏幕显示案例,熟悉树莓派的IIC接口的使用方法。

目录

1、基础知识

1.1、OLED介绍

1.2、OLED初始化

1.3、OLED显示

1.3.1、页寻址模式

1.3.2、水平寻址模式

1.3.3、垂直寻址模式

1.4、树莓派上IIC的接口使用方法

1.4.1、头文件

 1.4.2、初始化设备ID的函数

1.4.3、读取设备节点值的函数

1.4.4、给设备节点写值的函数

1.4.5、给设备节点的某一个寄存器写值

1.4.6、读取设备节点某一个寄存器的值

2、功能实现

2.1、原理图

2.2、代码实现

2.2.1、头文件实现

2.2.2、宏定义

2.2.3、更新显存函数

2.2.4、点亮屏幕函数

2.2.5、关闭屏幕函数

2.2.6、OLED清屏函数

2.2.7、OLED画点函数

2.2.8、OLED屏幕填充函数

2.2.9、OLED屏幕初始化函数

2.2.10、OLED设置光标点函数

2.2.11、OLED显示字符函数

2.2.12、OLED显示数字函数   

2.2.13、OLED显示字符串函数

2.2.14、主函数

2.3、生成执行程序

 2.4、功能实现


1、基础知识

1.1、OLED介绍

      OLED (Organic Light-Emitting Diode)即有机发光二极管,在手机OLED上属于新型产品,被称誉为“梦幻显示器”。OLED显示技术与传统的LCD显示方式不同,无需背光灯,采用非常薄的有机材料涂层和玻璃基板(或柔性有机基板),当有电流通过时,这些有机材料就会发光。而且OLED显示屏幕可以做得更轻更薄,可视角度更大,并且能够显著的节省耗电量。

       OLED显示原理与LCD有着本质上的区别,主要是通过电场驱动,有机半导体材料和发光材料通过过载流子注入和复合后实现发光。从本质上来说,就是通过ITO玻璃透明电极作为器件阳极,金属电极作为阴极,通过电源驱动,将电子从阴极传输到电子传输层,空穴从阳极注入到空穴传输层,之后分迁移到发光层,二者相遇后产生激子,让发光分子激发,经过辐射后产生光源。简单来说,一块OLED屏幕,就是由百千万个“小灯泡”组成。

     此次案例使用的是一款基于SSD1306驱动的0.96寸OLED显示屏。SSD1306是一款带控制器的用于OLED点阵图形显示系统的单片CMOS OLED/PLED驱动器。它由128个SEG(列输出)和64个COM(行输出)组成。该芯片专为共阴极OLED面板设计。

    SSD1306内置对比度控制器、显示RAM(GDDRAM)和振荡器,以此减少了外部元件的数量和功耗。该芯片有256级亮度控制。数据或命令由通用微控制器通过硬件选择的6800/8000系通用并行接口、I2C接口或串行外围接口发送。该芯片适用于许多小型便携式应用,如手机副显示屏、MP3播放器和计算器等。

1.2、OLED初始化

参考OLED的数据手册,OLED初始化步骤如下所示。

  • (1)Set MUX Ratio A8h, 3Fh  
  • (2)Set Display OffsetD3h, 00h
  • (3)Set Display StartLine 40h
  • (4)Set Segment re-mapA0h/A1h
  • (5)Set COM Output ScanDirection C0h/C8h
  • (6)Set COM Pinshardware configuration DAh,02h
  • (7)Set Contrast Control 81h,7Fh
  • (8)Disable EntireDisplay On A4h
  • (9)Set Normal DisplayA6h
  • (10)Set Osc FrequencyD5h, 80h
  • (11)Enable charge pumpregulator 8Dh, 14h
  • (12)Display On AFh

1.3、OLED显示

       要知道知道在哪里显示,就需要先知道哪些地方可能显示。oled模块的分辨率是128×64,也就是说一共能显示128×64这么多个“点”,下面我们用一个128列,64行的表格来描述更清晰一些,如下所示:

Col0 Col1 Col2 Col3 Col4 ...... Col125 Col126 Col127
PAG0 bit0 Row0 0
bit1 Row1 1
bit2 Row2 0
bit3 Row3 0
bit4 Row4 0
bit5 Row5 1
bit6 Row6 0
bit7 Row7 1
PAG1 bit0 Row8
bit1 Row9
bit2 Row10
bit3 Row11
bit4 Row12
bit5 Row13
bit6 Row14
bit7 Row15
PAG2
PAG3
PAG4
PAG5
PAG6
PAG7

      显示模块上的每一个点对应着这个表格的一个空格,假设你在某一个空格中放1表示这个‘点’亮,那么放0就表示这个‘点’暗。由于我们在写入数据时通常以字节为单位,那么现在把表格中的Col0这一列对应的Row0-Row7作为一个单位,一共8个空格,刚好对应一个字节。

      那么这个字节的高低位如何分配呢?最低位放到Row0-Col0对应的空格,最高位放到Row7-Col0对应的空格。这样当你写入一个字节的数据0x08时,对应的Col0-Row3这个‘点’就亮了,其他7个点为暗。那么这个数据0x08写到哪里去了?这个显示模块一定有个存储空间来存放这些写入的数据,暂且把它叫做PAGE0,PAGE1,...PAGE7. 这样每一个PAGE就对应着8行,比如PAGE0就对应着Row0-Row7。

      经过以上分析,问题就变得简单了,就是如何访问PAGE0-PAGE7,然后往里面填数据就行了。这个就涉及到寻址模式了,一共三种,分别是页寻址,水平寻址和垂直寻址模式。以下三幅图描述了这三种寻址模式。

1.3.1、页寻址模式

04_基于wiringPi的OLED驱动编写

 

1.3.2、水平寻址模式

04_基于wiringPi的OLED驱动编写

 

1.3.3、垂直寻址模式

04_基于wiringPi的OLED驱动编写

       下面就去查看OLED模块说明书的指令表确定其中任意一种寻址模式,然后根据需要显示的内容填数据就可以了。

1.4、树莓派上IIC的接口使用方法

参考官方的链接:http://wiringpi.com/reference/i2c-library/

1.4.1、头文件

使用IIC接口的之前需要添加头文件

#include <wiringPiI2C.h>

 1.4.2、初始化设备ID的函数

 int wiringPiI2CSetup (int devId) ;

返回设备节点

1.4.3、读取设备节点值的函数

int wiringPiI2CRead (int fd) ;

1.4.4、给设备节点写值的函数

int wiringPiI2CWrite (int fd, int data) ;

1.4.5、给设备节点的某一个寄存器写值

写1个字节
int wiringPiI2CWriteReg8 (int fd, int reg, int data) ;

写2个字节
int wiringPiI2CWriteReg16 (int fd, int reg, int data) ;

1.4.6、读取设备节点某一个寄存器的值

读取一个字节数据
int wiringPiI2CReadReg8 (int fd, int reg) ;

读取两个字节数据
int wiringPiI2CReadReg16 (int fd, int reg) ;

2、功能实现

        通过对OLED屏幕的程序编写实现“hello”字符的显示

2.1、原理图

04_基于wiringPi的OLED驱动编写

图2-1-1 OLED原理图

     通过接线 OLED接在了树莓派的I2C-1端口上。

2.2、代码实现

     通过IIC工具的到设备的ID地址为0x3C。

i2cdetect -y 1

04_基于wiringPi的OLED驱动编写

 图2-2-1 OLED的IIc地址

和数据手册的描述地址0x78有区别,是由于在Linux系统中显示的是7位地址。

数据手册          linux系统中

01111000 ---->0111100

2.2.1、头文件实现

#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>
#include <wiringPiI2C.h>
#include "oledfont.h"

2.2.2、宏定义

#define u8 unsigned char
#define u32 unsigned int

#define SlaveAddress 0x3C  //OLED地址
#define OLED_CMD  0x00  //OLED写命令
#define OLED_DATA 0x40  //OLED写数据

int fd;
u8 OLED_GRAM[128][8];

2.2.3、更新显存函数

//更新显存到LCD		 
void OLED_Refresh_Gram(void)
{
	u8 i,n;		    
	for(i=0;i<8;i++)  
	{  
		wiringPiI2CWriteReg8 (fd,OLED_CMD,0xb0+i);    //设置页地址(0~7)
		wiringPiI2CWriteReg8 (fd,OLED_CMD,0x00);      //设置显示位置—列低地址
		wiringPiI2CWriteReg8 (fd,OLED_CMD,0x10);      //设置显示位置—列高地址   
		for(n=0;n<128;n++)wiringPiI2CWriteReg8(fd,OLED_DATA,OLED_GRAM[n][i]); 
	}   
}

2.2.4、点亮屏幕函数

/开启OLED显示    
void OLED_Display_On(void)
{
	wiringPiI2CWriteReg8(fd,OLED_CMD,0X8D);  //SET DCDC命令
	wiringPiI2CWriteReg8(fd,OLED_CMD,0X14);  //DCDC ON
	wiringPiI2CWriteReg8(fd,OLED_CMD,0XAF);  //DISPLAY ON
}

2.2.5、关闭屏幕函数

//关闭OLED显示     
void OLED_Display_Off(void)
{
	wiringPiI2CWriteReg8(fd,OLED_CMD,0X8D);  //SET DCDC命令
	wiringPiI2CWriteReg8(fd,OLED_CMD,0X10);  //DCDC OFF
	wiringPiI2CWriteReg8(fd,OLED_CMD,0XAE);  //DISPLAY OFF
}

2.2.6、OLED清屏函数

//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!	  
void OLED_Clear(void)  
{  
	u8 i,n;  
	for(i=0;i<8;i++)for(n=0;n<128;n++) OLED_GRAM[n][i]=0X00;  
	OLED_Refresh_Gram();//更新显示
}

2.2.7、OLED画点函数

//画点 
//x:0~127
//y:0~63
//t:1 填充 0,清空				   
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
	u8 pos,bx,temp=0;
	if(x>127||y>63)return;//超出范围了.
	pos=7-y/8;
	bx=y%8;
	temp=1<<(7-bx);
	if(t)OLED_GRAM[x][pos]|= temp;
	else OLED_GRAM[x][pos]&=~temp;	    
}

2.2.8、OLED屏幕填充函数

//x1,y1,x2,y2 填充区域的对角坐标
//确保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63	 	 
//dot:0,清空;1,填充	  
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot)  
{  
	u8 x,y;  
	for(x=x1;x<=x2;x++)
	{
		for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);
	}													    
	OLED_Refresh_Gram();//更新显示
}

2.2.9、OLED屏幕初始化函数

//屏幕初始化函数
int oled_init(void)
{
    wiringPiSetup();
    fd = wiringPiI2CSetup (SlaveAddress);
    if(fd < 0)
    {
        printf("IIC初始化失败\r\n");
        return fd;
    }

    wiringPiI2CWriteReg8(fd,OLED_CMD,0xAE);  //关闭显示
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xD5);  //设置时钟分频因子,震荡频率
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x50);  //[3:0],分频因子;[7:4],震荡频率
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xA8);  //设置驱动路数
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x3F);  //默认0X3F(1/64)
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x00);  //默认为0
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x40);  //设置显示开始行 [5:0],行数.

    wiringPiI2CWriteReg8(fd,OLED_CMD,0x8D);  //电荷泵设置
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x14);  //bit2,开启/关闭
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x20);  //设置内存地址模式 
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x02);  //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xA1);  //段重定义设置,bit0:0,0->0;1,0->127;
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xC0);  //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xDA);  //设置COM硬件引脚配置
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x12);  //[5:4]配置
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x81);  //对比度设置
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xEF);  //1~255;默认0X7F (亮度设置,越大越亮)
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xD9);  //设置预充电周期
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xF1);  //[3:0],PHASE 1;[7:4],PHASE 2;
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xDB);  //设置VCOMH 电压倍率
    wiringPiI2CWriteReg8(fd,OLED_CMD,0x30);  //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
    
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xA4);  //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xA6);  //设置显示方式;bit0:1,反相显示;0,正常显示
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xAF);  //开启显示	
    
    OLED_Clear();
    return 0;
}

2.2.10、OLED设置光标点函数

//设置光标
void OLED_SetPos(u8 x, u8 y)
{
    wiringPiI2CWriteReg8(fd,OLED_CMD,0xB0+y);
    wiringPiI2CWriteReg8(fd,OLED_CMD,((x&0xf0)>>4)|0x10);
    wiringPiI2CWriteReg8(fd,OLED_CMD,(x&0x0f)|0x01);
}

2.2.11、OLED显示字符函数

//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示				 
//size:选择字体 16/12 
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{      			    
	u8 temp,t,t1;
	u8 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数
	chr=chr-' ';//得到偏移后的值		 
    for(t=0;t<csize;t++)
    {   
		if(size==12)temp=asc2_1206[chr][t]; 	 	//调用1206字体
		else if(size==16)temp=asc2_1608[chr][t];	//调用1608字体
		else if(size==24)temp=asc2_2412[chr][t];	//调用2412字体
		else return;								//没有的字库
        for(t1=0;t1<8;t1++)
		{
			if(temp&0x80)OLED_DrawPoint(x,y,mode);
			else OLED_DrawPoint(x,y,!mode);
			temp<<=1;
			y++;
			if((y-y0)==size)
			{
				y=y0;
				x++;
				break;
			}
		}  	 
    }          
}
//m^n函数
u32 mypow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--)result*=m;    
	return result;
}

 

2.2.12、OLED显示数字函数   

//显示2个数字
//x,y :起点坐标	 
//len :数字的位数
//size:字体大小
//mode:模式	0,填充模式;1,叠加模式
//num:数值(0~4294967295);	 		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
{         	
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/mypow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				OLED_ShowChar(x+(size/2)*t,y,' ',size,1);
				continue;
			}else enshow=1; 
		 	 
		}
	 	OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); 
	}
} 

 

2.2.13、OLED显示字符串函数

//显示字符串
//x,y:起点坐标  
//size:字体大小 
//*p:字符串起始地址 
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size)
{	
    while((*p<='~')&&(*p>=' '))//判断是不是非法字符!
    {       
        if(x>(128-(size/2))){x=0;y+=size;}
        if(y>(64-size)){y=x=0;OLED_Clear();}
        OLED_ShowChar(x,y,*p,size,1);	 
        x+=size/2;
        p++;
    }  
	
}

2.2.14、主函数

int main(void)
{
    int ret;

    ret = oled_init();
    if(ret != 0)
    {
        printf("OLED init error \r\n");
        return -1;
    }


    OLED_ShowString(0,0,(const u8 *)("hello"),24);
    OLED_Refresh_Gram();//更新显示

    return 0;
}

2.3、生成执行程序

      编译的时候需要添加wiringPi库,编译步骤如下所示:

gcc -Wall -o oled oled.c -lwiringPi

04_基于wiringPi的OLED驱动编写

 2.4、功能实现

执行程序

./oled

OLED屏幕上显示出hello字样。

04_基于wiringPi的OLED驱动编写

 图2-4-1 显示结果

上一篇:04. IP子网划分


下一篇:04 MySQL多表&事务