本文使用的OLED是用通过IIC通信,分辨率为128*64,使用的是stm32f103c8t6主控芯片,使用Cube Mx进行配置。
VCC:+3.3v~+5v
GND:接地
SCL:IIC时钟线
SDA:IIC数据线
不需要配置GPIO口,直接配置IIC调成快速模式就可以了,这里使用的是IIC2,也可以使用IIC1,程序只需要修改一下宏定义即可。
与大多数屏幕一样,OLED的初始化也是进行对屏幕的参数进行配置。可以直接粘贴下面的代码进行配置,为了方便起见,可以直接用一个数组把要配置的参数存起来,然后用一个for循环发送命令把数组发生送出去,然后再延时一下把屏幕清除就初始化完成了。若不想深究底层原理的可以直接复制粘贴即可,若想深究一下的话可以去细读数据手册,这里就不多做赘述了。
const unsigned char OLED_init_cmd[25]=
{
0xAE,//关闭显示
0xD5,//设置时钟分频因子,震荡频率
0x80,//[3:0],分频因子;[7:4],震荡频率
0xA8,//设置驱动路数
0X3F,//默认0X3F(1/64)
0xD3,//设置显示偏移
0X00,//默认为0
0x40,//设置显示开始行 [5:0],行数.
0x8D,//电荷泵设置
0x14,//bit2,开启/关闭
0x20,//设置内存地址模式
0x02,//[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
0xA1,//段重定义设置,bit0:0,0->0;1,0->127;
0xC8,//设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
0xDA,//设置COM硬件引脚配置
0x12,//[5:4]配置
0x81,//对比度设置
0xEF,//1~255;默认0X7F (亮度设置,越大越亮)
0xD9,//设置预充电周期
0xf1,//[3:0],PHASE 1;[7:4],PHASE 2;
0xDB,//设置VCOMH 电压倍率
0x30,//[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
0xA4,//全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
0xA6,//设置显示方式;bit0:1,反相显示;0,正常显示
0xAF //开启显示
};
/**************************************************
函数名称:OLED_init
函数用途:屏幕初始化
接口变量:无
变量类型:void
***************************************************/
void OLED_init(void)
{
unsigned char i;
for(i=0;i<25;i++)
{
OLED_send_cmd(OLED_init_cmd[i]);
}
HAL_Delay(1000);
OLED_clear();
}
进行完初始化后就可以进行显示配置了,在进行显示控制的讲解之前我们务必要了解一下显示原理。
根据数据手册,像素为128 * 64的OLED的X轴为128个像素点,即X坐标范围是0 ~ 127。但是Y轴坐标如图所示被分为了8个page,一个page包含8列,这样也就构成了8*8=64个像素点,但是Y坐标范围是0 ~ 7。我们在配置Y坐标的时候要注意范围!
无论是字符显示还是图片显示,不变的逻辑就是先设置所要显示的图的起始地址,包含X和Y地址,确定好地址后再逐个发送每个像素点的值。
设置坐标的函数如下所示,先设置y的坐标后设置x坐标。
/**************************************************
函数名称:SetPos
函数用途:在屏幕中设置起点坐标
接口变量1:起点x坐标,范围:0~127
变量类型1:u8
接口变量2:起点的y坐标,范围:0~7
变量类型2:u8
***************************************************/
void SetPos(unsigned char x,unsigned char y) //设置起点坐标
{
OLED_send_cmd(0xb0+y);
OLED_send_cmd((x&0xf0)>>4|0x10); //取高位
OLED_send_cmd((x&0x0f)|0x01); //取低位
}
以下是对函数的解读,如果不想深究可以跳过:
根据数据手册可知y的坐标范围是0xb0 ~ 0xb7,所以在y坐标前要加一个起始页的地址,也就是0xb0。
而x的坐标分为高四位和低四位分两次发送,高四位的范围值在0x10 ~ 0x17,低四位的范围值则在0x00 ~ 0x0f,根据相应值的范围再看代码就好理解了很多。
由于OLED不带字库,所以我们需要用取模软件来设置我们自己的字库。这里用的取模软件是PCtoLCD2002,如图所示。
首先设置取模软件的格式,点击选项选择格式,具体格式如下图所示。
设置好取模软件的格式后就可以进行文字取模,可以对单个字符取模,也可以直接保存一整个ASCLL码表。
这里以16x16的字模为例,选择字模的像素宽度和高度后,在输入框中输入想要取模的字符,然后点生成字模就生成了相应的16进制码,直接保存就可以了。
如果想生成中文文字的话也是一个道理
如果想要直接保存一整个ASCLL码表的话,可按照下面步骤操作
直接把这个表复制在数组里然后调用就可以了。
注:如果在编译的时候显示在这里有错误的话可以把报错的那几行后面的注释 /* */ 给删掉,具体原因我也没纠结,删掉之后就好了。
图片取模软件用的是image2LCD,操作方式如图所示
注意这里需要导入的图片格式是jpg格式的图片,可以用系统自带的画图软件另存为jpeg格式,然后再image2LCD内部打开,软件会自动按照你设定的像素将图片缩小。
打开图片后直接点击保存后软件会自动生成一个.c文件,也就是一个数组,把数组拷贝进程序里调用它即可。
显示单个字符则是对应的ascll码表的值,所以可以在调用的时候直接用OLED_DisShowChar(0,0,‘a’);
来调用。代码第四行c = chr + 0;//得到偏移后的值的意思是若复制的是整个ascll码表则直接加0就可以。但也可以把前面没有用的一些字符的码组给删除掉,这样可以省出一些空间,若果这样的话则需要加上第一个字符的ascll码值,例如:把字符’a’前面的值都给删掉的话,则该代码修改成c = chr + ‘a’;//得到偏移后的值。这里由于是保存的全部ascll码表,所以不需要修改任何。
由于显示的字符大小都是16x16的,所以要将y的坐标分两次进行显示,因为y轴上的坐标分为8页,每页中有8列,所以要分成两次显示。
/**************************************************
函数名称:OLED_DisShowChar
函数用途:显示字符
接口变量1:显示字符的起始x坐标,范围0~127
变量类型1:u8
接口变量2:显示字符的起始y坐标,范围0~7
变量类型2:u8
接口变量3:需要显示的字符
变量类型3:u8
备注:显示的字符可以直接用“”,对用ASCLL码表,
字符分表率为16x16
***************************************************/
void OLED_DisShowChar(uint8_t x,uint8_t y,uint8_t chr)
{
unsigned char c=0,i=0;
c = chr + 0;//得到偏移后的值
if(x>127){x=0;y=y+2;}//#define Max_Column 128
SetPos(x,y); //先设置X,Y的坐标位置
for(i=0;i<8;i++)
{
OLED_send_data(F16X16[c*16+i]);//发送第一页的上半部分
}
SetPos(x,y+1);
for(i=0;i<8;i++)
{
OLED_send_data(F16X16[c*16+i+8]);//发送第一页的下半部分
}
}
显示字符串的函数也就是在不断调用显示字符的函数,每次加上一个字符的X坐标的偏移值就可以了。
调用的时候直接可以写入字符串即可,例如:OLED_Shownstring(0,0,“Hello Word!”);
/**************************************************
函数名称:OLED_Shownstring
函数用途:显示任意长度字符串
接口变量1:显示数字的起始x坐标,范围0~127
变量类型1:u8
接口变量2:显示数字的起始y坐标,范围0~7
变量类型2:u8
接口变量3:需要显示的字符串
变量类型:u8
***************************************************/
void OLED_Shownstring(uint8_t x,uint8_t y,uint8_t *str)
{
for(uint8_t i=0;i<50;i++)
{
if(*str == 0)
{
x = x + i*8;
break;
}
OLED_DisShowChar(x + i*8,y,*str);
str++;
}
}
显示数字的函数中使用了sprinf函数,如果有不了解的可以另外搜索一下,sprintf函数是将数字逐位转换到数组里的函数,包括小数点和负号,之后直接显示数组里的数字即可。
注:调用sprintf函数需在头文件中加入stdio库
/**************************************************
函数名称:OLED_Shownum
函数用途:显示任意数字
接口变量1:显示数字的起始x坐标,范围0~127
变量类型1:u8
接口变量2:显示数字的起始y坐标,范围0~7
变量类型2:u8
接口变量3:需要显示的数字
变量类型:float
***************************************************/
void OLED_Shownum(uint8_t x,uint8_t y,float num)
{
char str1[50];//存放数字
sprintf(str1,"%.5f",num);//把浮点数转换到数组中,并保留小数点后5位
for(uint8_t i=0;i<50;i++)
{
if(str1[i] == 0)
{
x = x + i*8;
break;
}
OLED_DisShowChar(x + i*8,y,str1[i]);
}
}
显示文字的逻辑和显示字符的逻辑是相同的,把对应的文字的像素点的数组进行显示即可。
/**************************************************
函数名称:OLED_ShowCN
函数用途:显示汉字
接口变量1:显示汉字的起始x坐标
变量类型1:u8
接口变量2:显示汉字的起始y坐标
变量类型2:u8
接口变量3:需要显示的汉字字数
变量类型3:u8
备注:汉字分辨率为16x16
***************************************************/
void OLED_ShowCN(unsigned char x,unsigned char y,unsigned char N)
{
unsigned char wm=0;
unsigned int addr = 32*N;
SetPos(x,y);
for(wm=0;wm<16;wm++)
{
OLED_send_data( F16X16[addr]);
addr +=1;
}
SetPos(x,y+1);
for(wm=0;wm<16;wm++)
{
OLED_send_data( F16X16[addr]);
addr +=1;
}
}
显示图片的函数这里为了方便我写了三个,原理和显示字符的原理也是一样的,可以根据不同需求进行复制。
/**************************************************
函数名称:Picture_display
函数用途:显示全屏图片
接口变量:需要显示的图片数组
变量类型:数组
***************************************************/
void Picture_display(const unsigned char *ptr_pic)
{
unsigned char page,column;
for(page=0;page<(64/8);page++) //page loop
{
SetPos(0,page);
for(column=0;column<128;column++) //column loop
{
OLED_send_data(*ptr_pic++);
}
}
}
/**************************************************
函数名称:Picture_ReverseDisplay
函数用途:显示全屏图片的反转
接口变量:需要显示的图片数组
变量类型:数组
***************************************************/
void Picture_ReverseDisplay(const unsigned char *ptr_pic)
{
unsigned char page,column,data;
for(page=0;page<(64/8);page++) //page loop
{
SetPos(0,page);
for(column=0;column<128;column++) //column loop
{
data=*ptr_pic++;
data=~data;
OLED_send_data(data);
}
}
}
/**************************************************
函数名称:OLED_DrawBMP
函数用途:显示任意分辨率图片
接口变量1:显示图片的起始x坐标,范围0~127
变量类型1:u8
接口变量2:显示图片的起始y坐标,范围0~7
变量类型2:u8
接口变量3:显示图片的终点x坐标,范围0~127
变量类型3:u8
接口变量4:显示图片的终点page数,范围0~7
变量类型4:u8
接口变量4:显示图片的数组
变量类型4:u8
备注:page数等于图片的y的终点坐标8,因为
OLED屏幕把y轴64的高度分为了8个page
***************************************************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0) y=y1/8;
else y=y1/8+1;
for(y=y0;y<y1;y++)
{
SetPos(x0,y);
for(x=x0;x<x1;x++)
{
OLED_send_data(BMP[j++]);
}
}
}
OLED的一些函数大概就是这么多了,另外再附上.c文件中的其他一些函数和.h文件,方便移植。
/**************************************************
函数名称:OLED_send_cmd
函数用途:I2C总线向控制寄存器发送指令
接口变量:需要发送的指令
变量类型:u8
***************************************************/
void OLED_send_cmd(uint8_t Command)
{
uint8_t *pData;
pData = &Command;
HAL_I2C_Mem_Write(&Hardware_IIC_No,0x78,0x00,I2C_MEMADD_SIZE_8BIT,pData,1,100);
}
/**************************************************
函数名称:OLED_send_data
函数用途:I2C总线向数据寄存器发送数据
接口变量:需要发送的数据
变量类型:u8
***************************************************/
void OLED_send_data(uint8_t Data)
{
uint8_t *pData;
pData = &Data;
HAL_I2C_Mem_Write(&Hardware_IIC_No,0x78,0x40,I2C_MEMADD_SIZE_8BIT,pData,1,100);
}
/**************************************************
函数名称:OLED_clear
函数用途:屏幕全灭
接口变量:无
变量类型:void
***************************************************/
void OLED_clear(void)
{
unsigned char page,column;
for(page=0;page<8;page++) //page loop
{
SetPos(0,page);
for(column=0;column<128;column++) //column loop
{
OLED_send_data(0x00);
}
}
}
/**************************************************
函数名称:OLED_full
函数用途:屏幕全亮
接口变量:无
变量类型:void
***************************************************/
void OLED_full(void)
{
unsigned char page,column;
for(page=0;page<8;page++) //page loop
{
SetPos(0,page);
for(column=0;column<128;column++) //column loop
{
OLED_send_data(0xff);
}
}
}
.h文件
#ifndef __OLED_H
#define __OLED_H
#include "main.h"
#include "gpio.h"
#include "i2c.h"
#include "stdio.h"
#define Hardware_IIC_No hi2c2 /*选择自己使用的IIC号码*/
void SetPos(unsigned char x,unsigned char y); //设置起点坐标
void OLED_ShowCN(unsigned char x,unsigned char y,unsigned char N);
void OLED_DisShowChar(uint8_t x,uint8_t y,uint8_t chr);
void OLED_send_cmd(uint8_t Command);
void OLED_send_data(uint8_t Data);
void OLED_clear(void);
void OLED_full(void);
void OLED_init(void);
void Picture_display(const unsigned char *ptr_pic);
void Picture_ReverseDisplay(const unsigned char *ptr_pic);
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);
void OLED_Shownum(uint8_t x,uint8_t y,float num);
void OLED_Shownstring(uint8_t x,uint8_t y,uint8_t *str);
#endif
另外,还有OELD也有一些显示gif动画的玩法,可以去找一些gif尺寸修改工具和gif分离器,把gif的尺寸改成小于128*64的尺寸后将gif逐帧分离,然后逐帧转换成数组间隔一定时间轮换显示就可以了,就是比较麻烦,也比较占内存,一般的gif至少都是十几帧二十几帧,逐帧取模的话比较麻烦确实是个很大的工作量也需要单片机有很大的内存,如果实在内存不够也可以将分解成的每一帧图片隔一帧删除一帧,这样也看不出来有什么变化。