目录
一、OLED屏幕的特点及原理
1、OLED的特点
2、OLED的显示原理
3、OLED的显示过程
二、OLED屏幕的使用过程
1、SH1106驱动芯片介绍
2、通信接口
3、程序设计
SPI通信相关函数
SPI所用IO口的初始化函数
SPI发送一字节函数
OLED屏幕相关函数
OLED所用到的IO初始化函数
对OLED复位函数
主控芯片发送数据/命令到OLED函数
清屏函数
OLED屏幕初始化配置函数
4、OLED屏幕显示
①命令介绍
②功能函数
确定显示位置函数
显示可设定大小的字符函数
显示可设定大小的汉字函数
显示可选大小的汉字和字符混合函数
显示图片
显示动态图
多张照片来回切换形成动态图
动态图取模
一、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_SCKMOSI----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);//CR1SPI1->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); //42MHZSPI1->CR1 |= (1<<2); //主模式SPI1->CR1 &= ~(3<<0); //0.0模式 时钟极性0 时钟相位0//CR2SPI1->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 PB13GPIOB->MODER &= ~(3<<26);GPIOB->MODER |= (1<<26);GPIOB->OTYPER &= ~(1<<13);GPIOB->OSPEEDR &= ~(3<<26);GPIOB->OSPEEDR |= (2<<26);//OLED_DC PA15GPIOA->MODER &= ~(3<<30);GPIOA->MODER |= (1<<30);GPIOA->OTYPER &= ~(1<<15);GPIOA->OSPEEDR &= ~(3<<30);GPIOA->OSPEEDR |= (2<<30);//OLED_CS PB7GPIOB->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;//延时100mstim11_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号列 0x00OLED_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 panelOLED_writeByte(0x02,OLED_CMD);//---SET low column addressOLED_writeByte(0x10,OLED_CMD);//---SET high column addressOLED_writeByte(0x40,OLED_CMD);//--SET start line address SET Mapping RAM Display Start Line (0x00~0x3F)OLED_writeByte(0x81,OLED_CMD);//--SET contrast control registerOLED_writeByte(0xFF,OLED_CMD); // SET SEG Output Current BrightnessOLED_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 displayOLED_writeByte(0xA8,OLED_CMD);//--SET multiplex ratio(1 to 64)OLED_writeByte(0x3f,OLED_CMD);//--1/64 dutyOLED_writeByte(0xD3,OLED_CMD);//-SET display offSET Shift Mapping RAM Counter (0x00~0x3F)OLED_writeByte(0x00,OLED_CMD);//-not offSETOLED_writeByte(0xd5,OLED_CMD);//--SET display clock divide ratio/oscillator frequencyOLED_writeByte(0x80,OLED_CMD);//--SET divide ratio, SET Clock as 100 Frames/SecOLED_writeByte(0xD9,OLED_CMD);//--SET pre-charge periodOLED_writeByte(0xF1,OLED_CMD);//SET Pre-Charge as 15 Clocks & Discharge as 1 ClockOLED_writeByte(0xDA,OLED_CMD);//--SET com pins hardware configurationOLED_writeByte(0x12,OLED_CMD);OLED_writeByte(0xDB,OLED_CMD);//--SET vcomhOLED_writeByte(0x40,OLED_CMD);//SET VCOM Deselect LevelOLED_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/disableOLED_writeByte(0x14,OLED_CMD);//--SET(0x10) disableOLED_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 panelOLED_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~7list: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~7list: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~7list:2~12816*16 size:1632*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~7list:2~12816*16 size:1624*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~7list:2~12816*16 16*16 size:1632*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~7list: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;}