OLED驱动函数详解
前提
SSD1306是一款带控制器的单片CMOS OLED/PLED驱动,用于OLED点阵图形显示系统。SSD1306 MCU接口由8个数据引脚和5个控制引脚组成。下表总结了不同接口模式下的引脚分配。可以通过BS[2:0]引脚上的硬件选择来设置不同的MCU模式

通讯方式
可以通过BS[2:0]引脚上的硬件选择来设置不同的MCU模式,本文选择的是IIC通讯。对于模拟iic,大家可以参考一下这篇文章,很详细的:IIC的基本原理

地址排列
OLED本身是没有显存的,它的显存是依赖于SSD1306提供的。SSD1306的显存总共为128 * 64bit大小,SSD1306将这些显存分为了8页。每页向下偏移8bit,并包含128个字节,总共8页,这样刚好是8*8*64=64*128的点阵大小。所以行地址范围为0-7,列地址范围为0-127,对应下文驱动函数的Y轴和X轴。地址如下排序

寻址方式
想要在OLED中正常显示(0/1),必须将对应的显存地址给定相应的数据。这就涉及到了寻址方式。本文采用的寻址方式是水平寻址,在水平寻址模式中,在读取/写入显示RAM之后,列地址指针自动增加1。如果列地址指针到达列结束地址,则将列地址指针重置为列开始地址,并将页地址指针增加1。水平寻址模式下页面和列地址点的移动顺序如图所示。当列和页地址指针都到达结束地址时,指针将重置为列起始地址和页起始地址。字库点阵的显示方式需要和采用的寻址方式一致,不然会显示错误。

地址我们可以找到了,该如何写入数据呢?当我们定位到一个地址后(x,y)在物理空间上高位在下,低位在上写入8bit数据,刚好一页就是8bit,所以当前页的当前列的数据就完全写入了。如下图所示便是在(3,2)处写入0xF0的结果。

以上内容均可从SSD1306数据手册中获得:SSD1306
正文
详细说明一下各种格式的驱动函数
初始化
//#include "i2c.h"
#include "IIC.h"
#include "oledfont.h"
#include "stm32f1xx_hal.h"
#include "mpu6050.h"
void WriteCmd(unsigned char I2C_Command)
{
//IIC写命令函数
MPU_Write_Len(OLED0561_ADD>>1,COM,1,&I2C_Command);
}
void WriteDat(unsigned char I2C_Data)
{
//IIC写数据函数
MPU_Write_Len(OLED0561_ADD>>1,DAT,1,&I2C_Data);
}
//初始化函数,直接copy就行,想进一步理解得啃数据手册
void OLED_Init(void)
{
HAL_Delay(100);
WriteCmd(0xAE); //display off
WriteCmd(0x20); //Set Memory Addressing Mode
WriteCmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
WriteCmd(0xb0); //Set Page Start Address for Page Addressing Mode,0-7
WriteCmd(0xc8); //Set COM Output Scan Direction
WriteCmd(0x00); //---set low column address
WriteCmd(0x10); //---set high column address
WriteCmd(0x40); //--set start line address
WriteCmd(0x81); //--set contrast control register
WriteCmd(0xff); //???? 0x00~0xff
WriteCmd(0xa1); //--set segment re-map 0 to 127
WriteCmd(0xa6); //--set normal display
WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
WriteCmd(0x3F); //
WriteCmd(0xa4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
WriteCmd(0xd3); //-set display offset
WriteCmd(0x00); //-not offset
WriteCmd(0xd5); //--set display clock divide ratio/oscillator frequency
WriteCmd(0xf0); //--set divide ratio
WriteCmd(0xd9); //--set pre-charge period
WriteCmd(0x22); //
WriteCmd(0xda); //--set com pins hardware configuration
WriteCmd(0x12);
WriteCmd(0xdb); //--set vcomh
WriteCmd(0x20); //0x20,0.77xVcc
WriteCmd(0x8d); //--set DC-DC enable
WriteCmd(0x14); //
WriteCmd(0xaf); //--turn on oled panel
}
一些使用命令的函数
void OLED_SetPos(unsigned char x, unsigned char y)
{
WriteCmd(0xb0+y);
WriteCmd(((x&0xf0)>>4)|0x10);
WriteCmd((x&0x0f)|0x01);
}
void OLED_Fill(unsigned char fill_Data)
{
unsigned char m,n;
for(m=0;m<8;m++)
{
WriteCmd(0xb0+m); //设置页地址(0~7)
WriteCmd(0x00); //设置显示位置—列低地址
WriteCmd(0x10); //设置显示位置—列高地址
for(n=0;n<128;n++)
{
WriteDat(fill_Data);
}
}
}
//清屏
void OLED_CLS(void)
{
OLED_Fill(0x00);
}
//开启OLED显示
void OLED_ON(void)
{
WriteCmd(0X8D); //SET DCDC命令
WriteCmd(0X14); //DCDC ON
WriteCmd(0XAF); //DISPLAY ON
}
//关闭OLED显示
void OLED_OFF(void)
{
WriteCmd(0X8D); //SET DCDC命令
WriteCmd(0X10); //DCDC OFF
WriteCmd(0XAE); //DISPLAY OFF
}
显示一个字符
//x:0~127
//y:0~7
//size:选择字体 16:8*16 / 12:6*8
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{
unsigned char c=0,i=0;
c=chr-' ';//获取偏移,定位该字符数组起始下标 c*16
if(x>128-1){ x=0; y=y+2; }//处理越界
if(Char_Size ==16)
{ //绘制8*16
OLED_SetPos(x,y);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i]);//8*16的上半部分即8*8
OLED_SetPos(x,y+1); //前往下一页,+1即 Y 偏移8位
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);//8*16的下半部分即8*8
}
else {
//绘制6*8
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(F6x8[c][i]);
}
}
在指定位置显示一个字符串
//x:0~127
//y:0~7
//size:选择字体 16:8*16 / 12:6*8
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
unsigned char c = 0,i = 0,j = 0;
switch(TextSize)
{
case 12:
case 1: //打印6*8点阵
{
while(ch[j] != '\0') //字符串结束符
{
//当前字符减去32(ASCII为'0'),得到偏移量即字库中该字符的数组起始下标
c = ch[j] - 32;
if(x > 126) //处理越界
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(F6x8[c][i]); //从x,y坐标依次打印6*8点阵
x += 6;
j++;
}
}break;
case 16:
case 2:
{ //打印16*16点阵,同上
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 120)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1); //前往下一页,+1即 Y 偏移8位
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);
x += 8;
j++;
}
}break;
}
}
字符串居左显示
void OLED_ShowStr_Left( unsigned char y, unsigned char ch[], unsigned char TextSize){
//这个好理解,x设置为0即可
OLED_ShowStr(0, y, ch, TextSize);
}
字符串居右显示
void OLED_ShowStr_Right( unsigned char y, unsigned char ch[], unsigned char len, unsigned char TextSize){
//使用方法 OLED_ShowStr_Right(5, name,sizeof (name), 16);
//sizeof包含了\0,比实际显示的大1,因此减去1
//TextSize= 16/12 想要居右 X = 128-字符总长度 = 128 - TextSize/2*(len-1)
OLED_ShowStr ((128 - TextSize/2*(len-1)), y, ch, TextSize);
}
字符串居中显示
void OLED_ShowStr_Center( unsigned char y, unsigned char ch[], unsigned char len, unsigned char TextSize){
//使用方法 OLED_ShowStr_Center(5, name,sizeof (name), 16);
//TextSize= 16/12 想要居中 X 为居右时的一半 即 64 - TextSize/4*(len-1)
OLED_ShowStr ((64 - TextSize/4*(len-1)), y, ch, TextSize);
}
在指定位置显示一个中文字符
//在指定位置显示一个中文字符
//x:0~127
//y:0~7
//size2:选择字体 16:8*16 / 12:6*8
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
{
unsigned char wm=0;
unsigned int adder=32*N; //每个字符占32byte,所以偏移量是32,其实还是数组下标
OLED_SetPos(x , y); //定位显示位置
for(wm = 0;wm < 16;wm++) //当前页显示上半部分
{
WriteDat(F16x16[adder]);
adder += 1;
}
OLED_SetPos(x,y + 1); //定位下一页
for(wm = 0;wm < 16;wm++) //显示下半部分
{
WriteDat(F16x16[adder]);
adder += 1;
}
}
在指定区域显示图片
//在指定区域显示图片
//x0,y0是左上角坐标,x1,y1是右下角坐标,显示该范围的图像
//x:0~127
//y:0~7
//BMP[]:位图数组名
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned char x,y;
for(y=y0;y<y1;y++)
{
OLED_SetPos(x0,y);
for(x=x0;x<x1;x++)
{
//Y轴一行是128BIT,加上x的偏移,就是XY处的数据(数组下标)
WriteDat(BMP[128 * y + x]);
//WriteDat(BMP[j++]);
}
}
}
在指定位置显示一定长度的整数
//返回 M 的 N 次幂
u32 oled_pow(u8 m,u8 n)
{
u32 result=1;
while(n--)result*=m;
return result;
}
//显示数字
//x,y :起点坐标
//len :数字的位数
//size2:选择字体 16:8*16 / 12:6*8
//num:数值(0~4294967295);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{ //总体思想是把数字的每一位提取出来再转化成字符格式用OLED_ShowChar输出。
u8 t,temp; //临时存储每位的值
u8 enshow=0; //使能输出标志
//len为整形长度
for(t=0;t<len;t++)
{
//在某进制数中,用某数除以某权位再对基数求余,即可得到该数位的值。
//例如:用一个十进制整形数除以100,再对10求余可以得到百位的数值
// 用一个十进制整形数除以 10,再对10求余可以得到十位的数值
// 用一个十进制整形数除以 1,再对10求余可以得到个位的数值
//下一行便是以此执行上述步骤,分别取到每数位的值。从高到低
temp = (num / oled_pow(10,len-t-1)) % 10;
//无效高位(高位位0)用空格代替
if(enshow==0 && t<(len-1))
{
//某高位为0
if(temp==0)
{
//输出空格
if(size2 == 16)
OLED_ShowChar(x+(8)*t,y,' ',size2);
else
OLED_ShowChar(x+(6)*t,y,' ',size2);
continue;
}else enshow=1;
}
//int转字符ASCII并输出。temp+'0':'0'加上整形的偏移就是阿拉伯数字相应字符型ASCII
//x+(6)*t 根据不同的字体宽度计算同一行下一个字符偏移
if(size2 == 16)
OLED_ShowChar(x+(8)*t,y,temp+'0',size2);
else
OLED_ShowChar(x+(6)*t,y,temp+'0',size2);
}
}
在指定位置显示一定长度的小数
//显示小数
//x,y :起点坐标
//len :整数数字的位数
//size2:选择字体 16:8*16 / 12:6*8
void OLED_ShowFloat(uint8_t x,uint8_t y,float num, uint8_t len, uint8_t size2)
{
uint32_t temp;
float num1=num;
float temp2;
temp=(int)num1; //强制转化为整形,保留了小数点之前的数
OLED_ShowNum(x,y,temp,len,size2); //显示小数点之前的数字
temp2=num-temp; //取出小数部分
temp2=temp2*100; //乘以100转化为整数
temp=(int)(temp2);
//x+(size2/2)*len根据不同的字体宽度计算同一行下一个字符偏移
OLED_ShowChar(x+(size2/2)*len,y,'.',size2);//打印小数点
OLED_ShowNum(x+(size2/2)*(len+1),y,temp,2,size2);//打印小数部分(2位)
}
结束
神功大成。给个链接:oled参考
7013

被折叠的 条评论
为什么被折叠?



