目录
一、OLED屏幕的特点及原理
1、OLED的特点
①本项目采用挂有 1.3 寸的 OLED 屏,分辨率为 132*64; OLED 驱动芯片为 SH1106。
②132列: 0~ 131列 64行:0 ~ 63
③ 将64行分为8份,每份8行, 共有8页 列:0~131
页地址和列地址确定一个具体的位置(3号页的65列)
2、OLED的显示原理
直接控制发光二极管的通电就可以控制每各子像素的颜色配比,每个像素点都是独立发光的。
左:OLED 右:LCD
与LCD不同的是OLCD无液晶层和背光层,不会漏光
显示原理:
①每个像素点由一个LED灯控制亮灭
②数据每一位(二进制位)控制一个像素点
③纵向刷屏(先确定某一页,然后确定此页中的某一列为起始 依次刷屏(页内自动列加))
页内列地址自动自增
页地址不会自动递增
3、OLED的显示过程
①MCU将数据传输给OLED控制器(驱动芯片)
OLED驱动芯片----->SH1106
一般MCU不集成LCD/OLED控制器,都是外接控制器芯片
②通过OLED控制器的作用将内容显示在屏幕上
③传输数据需要通信方式(SPI / IIC / 8080 / 6080 / FSMC /DMX)
二、OLED屏幕的使用过程
1、SH1106驱动芯片介绍
本款OLED的驱动芯片是SH1106.
OLED驱动芯片SH1106是一款专为OLED显示屏设计的控制器,它负责控制屏幕上的像素点,实现图像的显示。
显存,即显示内存,是驱动芯片中用于存储图像数据的存储区域。
显存:显示数据的RAM为132 *64 bits.
分辨率:SH1106支持的最大分辨率为132x64像素,但常用于驱动128x64像素的OLED显示屏。
接口支持:SH1106支持SPI(Serial Peripheral Interface)和I2C(Inter-Integrated Circuit)两种通信协议,以及8位6800系列、8080系列并行接口,这使得它可以灵活地应用于各种微控制器平台。
功能集成:SH1106内置了对比度控制、显示RAM、振荡器以及高效的DC-DC转换器,这些功能减少了外部组件的数量,并降低了功耗。
2、通信接口
多种接口方式,该模块提供了总共4种接口。包括:6800、8080两种并行接口方式、 4线的串行SPI接口方式、IIC接口方式。
以上4种模式通过模块的BS0~2设置,BS0~2的设置与模块接口模式的关系如表所示:
通信方式选择:
根据硬件原理图我们可知:000----------------------标准SPI
引脚说明:
OLED_CS: OLED 片选信号。----------------------------------PB7 //通用推挽输出
OLED_RES:硬复位 OLED。 -----------------------------------PB13 //通用推挽输出
OLED_D/C:命令/数据标志(0,读写命令; 1,写数据)-------PA15 //通用推挽输出
OLED_SI: SPI1 的 MOSI 接口---------------------------------PB5 //复用推挽输出
OLED_SCL: SPI1 的 SCLK 接口---------------------------------PB3 //复用推挽输出
3、程序设计
所用到的宏定义
//命令数标识宏定义
#define OLED_CMD 0
#define OLED_DAT 1
//命令数据选择线宏定义
#define OLED_CD_H (GPIOA->ODR |= 1 << 15)
#define OLED_CD_L (GPIOA->ODR &= ~(1 << 15))
//片选宏定义
#define OLED_CS_H (GPIOB->ODR |= 1 <<7)
#define OLED_CS_L (GPIOB->ODR &= ~(1 <<7))
//复位管脚宏定义
#define OLED_RST_L (GPIOB->ODR &= ~(1 << 13))
#define OLED_RST_H (GPIOB->ODR |= 1 <<13)
SPI通信相关函数
SPI所用IO口的初始化函数
/***********************************************
*函数名 :spi1_init
*函数功能 :spi1控制器初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :SCK------PB3 SPI1_SCK
MOSI----PB5 SPI1_MOSI
************************************************/
void spi1_init(void)
{
/*IO控制器配置*/
//端口时钟使能
RCC->AHB1ENR |= (1<<1);
//端口模式配置
GPIOB->MODER &= ~((3<<6)|(3<<10));
GPIOB->MODER |= ((2<<6)|(2<<10));
//端口输出类型配置
GPIOB->OTYPER &= ~((1<<3)|(1<<5));
//端口输出速度配置
GPIOB->OSPEEDR &= ~((3<<6)|(3<<10));
GPIOB->OSPEEDR |= ((2<<6)|(2<<10)); //50M
//端口上下拉配置
GPIOB->PUPDR &= ~((3<<6)|(3<<10));
//端口复用功能配置
GPIOB->AFR[0] &= ~((0xf<<12)|(0xf<<20));
GPIOB->AFR[0] |= ((5<<12)|(5<<20));
/*SPI1控制器配置*/
//spi控制器时钟使能
RCC->APB2ENR |= (1<<12);
//CR1
SPI1->CR1 &= ~(1<<15); //双线单向模式 全双工
SPI1->CR1 &= ~(1<<11); //8位数据帧格式
SPI1->CR1 &= ~(1<<10); //全双工
SPI1->CR1 |= (1<<9); //选择软件从器件管理
SPI1->CR1 |= (1<<8); //内部NSS接口出现高电平,允许控制器通信
SPI1->CR1 &= ~(1<<7); //先发高位
SPI1->CR1 &= ~(7<<3); //42MHZ
SPI1->CR1 |= (1<<2); //主模式
SPI1->CR1 &= ~(3<<0); //0.0模式 时钟极性0 时钟相位0
//CR2
SPI1->CR2 &= ~(1<<4); //MOT
//spi控制器使能
SPI1->CR1 |= (1<<6);
}
SPI发送一字节函数
/***********************************************
*函数名 :spi1_byte
*函数功能 :spi1传输一字节函数
*函数参数 :u8 data
*函数返回值:u8
*函数描述 :
************************************************/
u8 spi1_byte(u8 data)
{
u8 val;
//等待之前数据发送完成
while(!(SPI1->SR & (1<<1)));
//将要发送的数据给到数据寄存器
SPI1->DR = data;
//等待接收数据完成
while(!(SPI1->SR & (1<<0)));
//将数据寄存器赋值给变量
val = SPI1->DR;
//返回变量
return val;
}
OLED屏幕相关函数
OLED所用到的IO初始化函数
/*****************************************************************************
*函数名 :OLED_IO_init
*函数功能 :OLED屏幕所用到的IO初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :
OLED_CS: OLED 片选信号。----------PB7 //通用输出
OLED_RES:硬复位 OLED。 -----------PB13 //通用输出
OLED_D/C:命令/数据标志(0,读写命令; 1,写数据)。-------PA15 //通用输出
******************************************************************************/
void OLED_IO_init(void)
{
RCC->AHB1ENR |= (3<<0);//PA PB
//OLED_RES PB13
GPIOB->MODER &= ~(3<<26);
GPIOB->MODER |= (1<<26);
GPIOB->OTYPER &= ~(1<<13);
GPIOB->OSPEEDR &= ~(3<<26);
GPIOB->OSPEEDR |= (2<<26);
//OLED_DC PA15
GPIOA->MODER &= ~(3<<30);
GPIOA->MODER |= (1<<30);
GPIOA->OTYPER &= ~(1<<15);
GPIOA->OSPEEDR &= ~(3<<30);
GPIOA->OSPEEDR |= (2<<30);
//OLED_CS PB7
GPIOB->MODER &= ~(3<<14);
GPIOB->MODER |= (1<<14);
GPIOB->OTYPER &= ~(1<<7);
GPIOB->OSPEEDR &= ~(3<<14);
GPIOB->OSPEEDR |= (2<<14);
GPIOB->ODR |= (1 << 7); //片选拉高不选择OLED
}
对OLED复位函数
/****************************************************
*函数名 :OLED_RST
*函数功能 :OLED屏幕硬件复位函数
*函数参数 :无
*函数返回值:无
*函数描述 :
****************************************************/
void OLED_RST(void)
{
//拉低复位线
OLED_RST_L;
//延时100ms
tim11_delay_ms(100);
//拉高复位线
OLED_RST_H;
}
主控芯片发送数据/命令到OLED函数
/******************************************************
*函数名 :OLED_writeByte
*函数功能 :主控芯片发送数据/命令到OLED
*函数参数 :u8 data,u8 cmd_data
*函数返回值:无
*函数描述 :要发送命令,cmd_data 传 0 OLED_CMD DC线拉低->调用SPI发送一字节函数
要发送数据,cmd_data 传 1 OLED_DAT DC线拉高->调用SPI发送一字节函数
******************************************************/
void OLED_writeByte(u8 data,u8 cmd_data)
{
//片选线拉低
OLED_CS_L;
(cmd_data)?(OLED_CD_H):(OLED_CD_L);
spi1_byte(data);
//片选线拉高
OLED_CS_H;
}
根据底层标准设置驱动代码来确定函数的格式
清屏函数
说明:
每页的列编号会自动递增.
每页中的每列是8个像素点,需要8位数据(1byte)填充
每个像素点给1 就亮 给0就灭
分析:
一共有8页,每页都要做同样的事,所以,页编号可以设计一个循环结构
每页内有132列,要传入132次数据,所以列编号可以设计一个循环结构
/************************************************
*函数名 :OLED_clear
*函数功能 :OLED清屏函数
*函数参数 :无
*函数返回值:无
*函数描述 :list: 2 ~ 130
**************************************************/
void OLED_clear(void)
{
u8 page,list_cont;
/*页循环*/
for(page=0;page<8;page++)
{
//确定页地址
OLED_writeByte(0xB0+page,OLED_CMD);
//每页设定列的起始地址 0号列 0x00
OLED_writeByte(0x10,OLED_CMD);
OLED_writeByte(0x00,OLED_CMD);
/*每页中 根据 列数 传 数据字节数 的循环*/ //根据列数算出的传入数据的次数循环
for(list_cont=0;list_cont<132;list_cont++)
{
OLED_writeByte(0x00,OLED_DAT);
}
}
}
OLED屏幕初始化配置函数
/*******************************************************
*函数名 :OLED_init
*函数功能 :OLED屏幕初始化配置
*函数参数 :无
*函数返回值:无
*函数描述 :
*********************************************************/
void OLED_init(void)
{
//spi初始化
spi1_init();
//OLED屏幕所用到的IO初始化
OLED_IO_init();
//硬件复位
OLED_RST();
//底层标准设置驱动移植
OLED_writeByte(0xAE,OLED_CMD);//--turn off oled panel
OLED_writeByte(0x02,OLED_CMD);//---SET low column address
OLED_writeByte(0x10,OLED_CMD);//---SET high column address
OLED_writeByte(0x40,OLED_CMD);//--SET start line address SET Mapping RAM Display Start Line (0x00~0x3F)
OLED_writeByte(0x81,OLED_CMD);//--SET contrast control register
OLED_writeByte(0xFF,OLED_CMD); // SET SEG Output Current Brightness
OLED_writeByte(0xA1,OLED_CMD);//--SET SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_writeByte(0xC8,OLED_CMD);//SET COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_writeByte(0xA6,OLED_CMD);//--SET normal display
OLED_writeByte(0xA8,OLED_CMD);//--SET multiplex ratio(1 to 64)
OLED_writeByte(0x3f,OLED_CMD);//--1/64 duty
OLED_writeByte(0xD3,OLED_CMD);//-SET display offSET Shift Mapping RAM Counter (0x00~0x3F)
OLED_writeByte(0x00,OLED_CMD);//-not offSET
OLED_writeByte(0xd5,OLED_CMD);//--SET display clock divide ratio/oscillator frequency
OLED_writeByte(0x80,OLED_CMD);//--SET divide ratio, SET Clock as 100 Frames/Sec
OLED_writeByte(0xD9,OLED_CMD);//--SET pre-charge period
OLED_writeByte(0xF1,OLED_CMD);//SET Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_writeByte(0xDA,OLED_CMD);//--SET com pins hardware configuration
OLED_writeByte(0x12,OLED_CMD);
OLED_writeByte(0xDB,OLED_CMD);//--SET vcomh
OLED_writeByte(0x40,OLED_CMD);//SET VCOM Deselect Level
OLED_writeByte(0x20,OLED_CMD);//-SET Page Addressing Mode (0x00/0x01/0x02)
OLED_writeByte(0x02,OLED_CMD);//
OLED_writeByte(0x8D,OLED_CMD);//--SET Charge Pump enable/disable
OLED_writeByte(0x14,OLED_CMD);//--SET(0x10) disable
OLED_writeByte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
OLED_writeByte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
OLED_writeByte(0xAF,OLED_CMD);//--turn on oled panel
OLED_writeByte(0xAF,OLED_CMD); /*display ON*/
//清屏函数
OLED_clear();
}
4、OLED屏幕显示
①命令介绍
命令 0X81: 设置对比度。包含两个字节,第一个 0X81 为命令,随后发送的
一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。
命令 0XAE/0XAF: 0XAE 为关闭显示命令; 0XAF 为开启显示命令。
命令 0XB0~B7: 用于设置页地址,其低三位的值对应着 GRAM 的页地址。
此命令使用来确定页编号的
0xB0---------0号页
0xB7---------7号页
OLED_writeByte(0xB3,CMD) //确定页编号是3号页
命令 0X00~0X0F: 用于设置显示时的起始列地址低四位。
命令 0X10~0X1F: 用于设置显示时的起始列地址高四位。
列:每一页有0~131列,
例:0页30列 ①将30转为16进制--------0x1E
②将高位给高位即1给0X1x------0X11
③将低位给低位即E给0X0x------0X0E
②功能函数
确定显示位置函数
函数功能:
将起始页地址和列地址发送发送给OLED
方便了列地址的传入,可以直接传入十进制数据形式,函数会将列地址分为高位和低位发送出去
/*********************************************
*函数名 :OLED_setstart
*函数功能 :确定显示起始位置函数 哪页的哪列
*函数参数 :u8 page 页号 u8 list 列号
*函数返回值:无
*函数描述 :
通过此函数能确定要显示的起始位置
*********************************************/
void OLED_setstart(u8 page,u8 list)
{
OLED_writeByte(0xB0+page,OLED_CMD); //页地址
OLED_writeByte((list>>4)|0x10,OLED_CMD); //列高位
OLED_writeByte(list&0x0f,OLED_CMD); //列低位
}
显示可设定大小的字符函数
为什么要取模
因为OLED屏幕显示是二进制显示方式,
取模软件的作用就是将字符、汉子、图形转化成二进制形式
取模软件的使用
设置:点阵格式:阴码
取模方式:列行式
取模走向:逆向(低位在前)
自定义格式: C51
把行前缀和行后缀的 {} 去掉
由下往上刷
显示16*16的字符
思路:
把所有可显示字符都取模放在一个数组中,我们可以通过ASCII码计算出要显示的字符与空格 字符的偏移量,因为16*16的字符每个字符的模数据是16个,通过偏移量*16能找到要显示的字 符的模数据在数组中元素的位置.
/************************************************
*函数名 :oled_dis_char16
*函数功能 :显示一个16*16的字符
*函数参数 :u8 page,u8 list,u8 ch
*函数返回值:无
*函数描述 :16*16 实际是 8*16 字高占用2页
page:0~7
list:2~129
*************************************************/
void oled_dis_char16(u8 page,u8 list,u8 ch)
{
u8 n;
u8 i,j;
/*计算要显示的字符与空格的偏移量*/
n = ch - ' ';
/*显示*/
//所用页数循环
for(i=0;i<2;i++)
{
//确定每页的起始显示位置
OLED_setstart(page+i,list);
//每页发送的数据字节数 取决字的宽度
for(j=0;j<8;j++)
{
OLED_writeByte(F16X16[n*16+i*8+j],OLED_DAT);//n*16 偏移到所要显示的字符 i*8 每页要显示的取模数据 j 每个取模数据的显示
}
}
}
显示32*24的字符
说明:
实际是16*24的字符
字符占用3页,每页中的每列需要8位数据(1byte)填充
每页一共有16列,所以每页要传16个字节
每个字符字模数据一共有48个
思路:
把所有可显示字符都取模放在一个数组中,我们可以通过ASCII码计算出要显示的字符与空格字符的偏移量,因为16*24的字符每个字符的模数据是4448个,通过偏移量*48能找到要显示的字 符的模数据在数组中元素的位置.
/************************************************
*函数名 :oled_dis_char24
*函数功能 :显示一个32*24的字符
*函数参数 :u8 page,u8 list,u8 ch
*函数返回值:无
*函数描述 :32*24 实际是 16*24 字高占用3页
page:0~7
list:2~129
*************************************************/
void oled_dis_char24(u8 page,u8 list,u8 ch)
{
u8 n;
u8 i,j;
/*计算要显示的字符与空格的偏移量*/
n = ch - ' ';
/*显示*/
//所用页数循环
for(i=0;i<3;i++)
{
//确定每页的起始显示位置
OLED_setstart(page+i,list);
//每页发送的数据字节数 取决字的宽度
for(j=0;j<16;j++)
{
OLED_writeByte(F32X24[n*48+i*16+j],OLED_DAT);//n*16 偏移到所要显示的字符 i*16 每页要显示的取模数据 j 每个取模数据的显示
}
}
}
优化程序:
显示一个可选择大小的字符函数
/************************************************
*函数名 :oled_dis_char
*函数功能 :显示一个字符
*函数参数 :u8 page,u8 list,u8 ch,u8 size
*函数返回值:无
*函数描述 :page:0~7
list:2~128
16*16 size:16
32*24 size:24
*************************************************/
void oled_dis_char(u8 page,u8 list,u8 ch,u8 size)
{
u8 n;
u8 i,j;
/*计算要显示的字符与空格的偏移量*/
n = ch - ' ';
/*显示*/
//所用页数循环
for(i=0;i<size/8;i++)
{
//确定每页的起始显示位置
OLED_setstart(page+i,list);
if(size==16)//16号字符
{
//每页发送的数据字节数 取决字的宽度
for(j=0;j<8;j++)
{
OLED_writeByte(F16X16[n*16+i*8+j],OLED_DAT);
}
}
else if(size==24)//24号字符
{
//每页发送的数据字节数 取决字的宽度
for(j=0;j<16;j++)
{
OLED_writeByte(F32X24[n*48+i*16+j],OLED_DAT);
}
}
}
}
显示可设定大小的汉字函数
说明:
一个汉字在数组中占两个元素的位置。
思路:
把所要显示的汉字存储到一维字库数组中
把所要显示的汉字按顺序分别取模16*16和32*32的字库数据存储到一维字模数组 中
通过传入想要打印的汉字在字库中查找,计算出所要显示的汉字与字库中的第一个 汉字的偏移量
需要避免所要找的汉字不是字库里的,并且如果不是字库里的,则需要退出程序
如果是字库里的,再选择大小
注意:
后期要添加新的字,一定要在字库的后面添加,然后取模也要放在模数据数组的后面
/************************************************
*函数名 :oled_dis_hz
*函数功能 :显示一个的汉字
*函数参数 :u8 page,u8 list,u8 *hz,u8 size
*函数返回值:无
*函数描述 :
page:0~7
list:2~128
16*16 size:16
24*24 size:24
*************************************************/
void oled_dis_hz(u8 page,u8 list,u8 *hz,u8 size)
{
u8 n = 0;
u8 i,j;
/*计算要显示的汉字与字库数组中的首个汉字的偏移个数*/
while(table[2*n] != '\0')
{
if(*hz==table[2*n] && *(hz+1)==table[2*n+1])
{
break;
}
n++;
}
//判断n的值是否有效,无效就结束函数
if(table[2*n] == '\0')
{
return;
}
//n值就是要显示的汉字与第一个汉字的偏移个数
/*显示*/
for(i=0;i<size/8;i++) //页数循环
{
//确定每页的起始位置
OLED_setstart(page+i,list);
for(j=0;j<size;j++) //每页要传入的数据字节数循环 有字的宽度决定
{
if(size == 16)
{
OLED_writeByte(hz16[n*32+16*i+j],OLED_DAT);
}
else if(size==24)
{
OLED_writeByte(hz24[n*72+24*i+j],OLED_DAT);
}
}
}
}
显示可选大小的汉字和字符混合函数
思路:
接收字符串,通过指针偏移循环查找所显示的字符或汉字
判断字符-->调用显示字符函数,偏移到下一个元素(字符占一个字节),显示下一个字符(列+字符宽度)
否则是汉字-->调用显示汉字函数,偏移到下一个元素(汉字占两个字节),显示下一个汉字(列+汉字宽度)
问题:
如何判断遇到的是字符还是汉字?
不是字符就一定是汉字
如何判断是不是字符?
32~127
/************************************************
*函数名 :oled_dis_str
*函数功能 :显示可设置大小的字符和汉字混合形式
*函数参数 :u8 page,u8 list,u8 *str,u8 size
*函数返回值:无
*函数描述 :
page:0~7
list:2~128
16*16 16*16 size:16
32*24 24*24 size:24
*************************************************/
void oled_dis_str(u8 page,u8 list,u8 *str,u8 size)
{
while(*str != '\0')
{
//判断是否是字符
if(*str >= 32 && *str <= 127)
{
oled_dis_char(page,list%131,*str,size);
str++;
if(size == 16)
{
list += 8;
}
else if(size == 24)
{
list += 16;
}
}
else
{
oled_dis_hz(page,list%131,str,size);
str += 2;
if(size == 16)
{
list += 16;
}
else if(size == 24)
{
list += 24;
}
}
}
}
显示图片
说明:
在iconfont上下载PNG格式图标
将PNG格式转换位bmp格式并修改像素大小(电脑的画图软件)
用取模软件修改丢失像素(鼠标左右键修补像素点)
生成字模(注意将每行的括号去掉--在设置里面)
显示原理:
通过图形的高度知道要占的页数
通过图形的宽度知道每页刷屏的列数
每页要刷的列数与要传入的数据字节数--->取决于图片的宽度
/************************************************
*函数名 :oled_dis_pic
*函数功能 :显示可设置大小的字符和汉字混合形式
*函数参数 :u8 page,u8 list,u8 w,u8 h,u8 *pic
*函数返回值:无
*函数描述 :
w:指定要显示的图片的宽度
h:指定要显示的图片的高度
page:0~7
list:2~128
*************************************************/
void oled_dis_pic(u8 page,u8 list,u8 w,u8 h,const u8 *pic)
{
u8 i,j;
u8 p;
//计算图形占的实际页数
(h%8==0)?(p=h/8):(p=h/8+1);
for(i=0;i<p;i++)
{
//确定每页的起始位置
OLED_setstart(page+i,list);
for(j=0;j<w;j++)
{
OLED_writeByte(pic[w*i+j],OLED_DAT);
}
}
}
显示动态图
多张照片来回切换形成动态图
利用指针数组存放每张图片的地址,再利用定时中断控制变量来寻找图片的地址空间,以显示在LCD屏幕上实现切换图片,形成动态图
static u8 pic_n = 0;
//动态图片切换
const u8 *pics[] = {pic_0,pic_1,pic_2,pic_3,pic_4,pic_5,pic_6};
if(timer_buff[4] >= 50)
{
oled_dis_pic(0,1,64,64,pics[pic_n]);
pic_n++;
if(pic_n>6)
{
pic_n=0;
}
timer_buff[4] = 0;
}
动态图取模
用软件生成模数据的.c文件,添加到工程中
利用定时中断控制变量来寻找图片的地址空间,以显示在LCD屏幕上
if(timer_buff[4] >= 50)
{
oled_dis_pic(0,1,64,64,tkr64x64[pic_n]);
pic_n++;
if(pic_n>=102)
{
pic_n=0;
}
timer_buff[4] = 0;
}
标签:函数,16,CMD,OLED,writeByte,屏幕,u8
From: https://blog.csdn.net/weixin_56459724/article/details/143990939