目录
一:OLED使用原理
硬件部分
SSD1306框图及引脚定义
选择通信接口
原理图
软件部分
4线SPI的传输时序
3线SPI的传输时序
I2C的通信时序
执行逻辑框图
二:基本命令表
滚动命令表
寻址设置命令表
硬件配置命令表
时间及驱动命令
初始化过程(内部提供VCC)
三:使用部分
OLED显示汉字
显示图像
照片显示在屏幕上面
制作菜单选择界面
倒置屏幕
把0.96寸的移植到1.3寸的文件下里
四:问题解决
汉字编译出现错误
编码格式的转化
五:代码部分
OLED写命令
OLED写数据
函数初始化
显示光标位置
一:OLED使用原理
0.96寸----显示原理
OLED的显存分布情况。我们可以理解为:水平方向分布了128个像素点,垂直方向分布了64个像素点。而驱动芯片在点亮像素点的时候,是以8个像素点为单位的。官方的例程推荐的是垂直扫描的方式,也就是先画垂直方向的8个像素点(如下图所示),所以我们在画点的时候Y的取值为0-7,X的取值为0-127。页是芯片设计者为了方便将同一列的8个点阵编成一组,用一个8bit数表示,这样的8行128个数被称为1页。(一共8页,每页128个字节)
硬件部分
SSD1306框图及引脚定义
选择通信接口
设置相应的B0,B1,B2,可以选择我们不同的通信方式。
其他引脚在不同通信协议下的意义:
原理图
I2C:
可以看到B0,B1,B2.满足选择通信接口的要求
SPL接口原理图:
可以看到B0,B1,B2.满足选择通信接口的要求
软件部分
https://mp.csdn.net/mp_blog/creation/editor/138286468https://mp.csdn.net/mp_blog/creation/editor/138286468
4线SPI的传输时序
串行传输只允许写,不允许读。因此,只有两个功能:写命令、写数据
E和R/W#时钟没用到,接低电平,CS#片选接低电平,表示始终选中此芯片
D/C#:高电平:数据,低电平:命令
D0:是串行时钟,在每个上升沿时,从机采样数据
D1:数据输入线,时钟线上升沿,数据维持稳定,从机采样数据。一个时钟,只发送一位
3线SPI的传输时序
D/C: D/C是借用SDIN引脚指定的,一个时钟,发送一位,但是在每个字节之前,先发送一个位,表示D/C之后,再跟8个位,传输一个字节。 每次传输的一个字节,都有9个位:第一个是D心,指定此字节是命令还是数据,剩下8个,才是真正的字节。
I2C的通信时序
串行传输只允许写,不允许读
CSDN
Co(连续模式位):Co = 1,每发送一个字节数据前都加一个Control byte(命令和数据可以灵活切换)
Co = 0,在字节数据前只发送一个Control byte,之后全部都是字节数据
D/C#表示了我们选择写数据还是写命令。
D/C#=1-------写数据。
D/C#=0------写命令
执行逻辑框图
左上角:128*64bit像素的点阵显示屏,以左上角为原点,向右为x正轴(0~127),向下为y正轴(0~63)
左下角:128*8Byte的GDDRAM,x轴与点阵显示屏一样,y轴有所不同,8位一组分为一页,范围为PAGE0 ~ PAGE7,共8页。每传输一个字节数据,将其展开,纵向排列(LSB在上,MSB在下),一位控制一个像素点
每写完一个字节数据后,内部的地址指针自动向右移动一个单位。当写到页的最后一字节时,地址指针默认回到页的起始位置,也可以通过配置寻址模式实现自动换页,换到下一页的开头
如果想要实现Y坐标的任意指定,需要读取GGDRAM,但串行传输只允许写数据,那需要在程序中定义缓存数组来实现:先读写缓存数组,最后一起更新到屏幕的GDDRAM中
二:基本命令表
滚动命令表
寻址设置命令表
硬件配置命令表
时间及驱动命令
初始化过程(内部提供VCC)
三:使用部分
#include "stm32f1xx_hal.h"
#include "rcc.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "exit.h"
#include "UART.h"
#include "OLED.h"
#include "iwdg.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
LED_Init(); /* LED初始化 */
LED_Exit_Init();
OLED_Init();
Uart_Init(115200);
// /*显示一个字符:必须使用‘’符号*/
// OLED_ShowChar(0,0,'A',OLED_8X16);
// OLED_ShowChar(0,20,'A',OLED_6X8);
// /*显示一个字符串*/
// OLED_ShowString(20,0,"Hello Word!",OLED_8X16);
// OLED_ShowString(20,20,"Hello Word!",OLED_6X8);
/*--------------------------------------------------------------------------*/
/*
指定长度(Length)>数据长度(Number)------在数据前面补0
指定长度(Length)<数据长度(Number)------数据的高位丢失
不要在数据前面加 0,不然会变为8进制
*/
// /*显示数字的函数*/
// OLED_ShowNum(0,0,12345,5,OLED_8X16); //十进制,正整数
// OLED_ShowSignedNum(48,0,-66,2,OLED_8X16); //符号数字(十进制,整数)
// OLED_ShowHexNum(0,20,0xA5A5,4,OLED_8X16); //十六进制数字(十六进制,正整数)
// OLED_ShowBinNum(0,40,0XA5A5,16,OLED_8X16); //二进制数字(二进制,正整数)
// OLED_ShowFloatNum(40,20,81.232,2,3,OLED_8X16); //浮点数字(十进制,小数)
/*-----------------------------------------------------------*/
/*显示汉字*/
// OLED_WriteCommand(0x2E); //关闭滚动
// OLED_ShowChinese(0,0,"你好,世界。");
// //显示流动汉字
// OLED_WriteCommand(0x2E); //关闭滚动
// OLED_WriteCommand(0x27); //水平向左或者右滚动 26/27
// OLED_WriteCommand(0x00); //虚拟字节
// OLED_WriteCommand(0x00); //起始页 0
// OLED_WriteCommand(0x07); //滚动时间间隔
// OLED_WriteCommand(0x07); //终止页 7
// OLED_WriteCommand(0x00); //虚拟字节
// OLED_WriteCommand(0xFF); //虚拟字节
// OLED_ShowChinese(0,0,"你好,世界。");
// OLED_ShowChinese(0,16,"你好,西安。");
// OLED_WriteCommand(0x2F); //开启滚动
/*------------------------------------------------*/
// /*显示图像*/
// OLED_ShowImage(0,0,102,64,SA);
// //显示照片
// OLED_GetPoint(0,0);
//
OLED_Update();
/*-------------------------------------------*/
//OLED printf函数使用方法和c语言中的使用方法一样
//这个没有办法显示汉字
//OLED_Printf(0,0,OLED_8X16,"Num1:%05d",123);
/*------------------------绘图函数-------------*/
// OLED_DrawPoint(0,0); //点亮一个像素点
// OLED_DrawPoint(0,1); //点亮一个像素点
// OLED_DrawPoint(0,2); //点亮一个像素点
// OLED_DrawPoint(0,3); //点亮一个像素点
// OLED_DrawPoint(0,4); //点亮一个像素点
// OLED_DrawPoint(0,5); //点亮一个像素点
// OLED_DrawPoint(0,6); //点亮一个像素点
// OLED_DrawPoint(0,7); //点亮一个像素点
// OLED_Update();
//
while(1)
{
}
}
OLED显示汉字
取模
将这写文字的16进制的代码复制下来,到OLED_Data.c文件的
const ChineseCell_t OLED_CF16x16[] = {}里面去,按照下面的内容。
/*汉字字模数据*********************/
/*相同的汉字只需要定义一次,汉字不分先后顺序*/
/*必须全部为汉字或者全角字符,不要加入任何半角字符*/
/*宽16像素,高16像素*/
const ChineseCell_t OLED_CF16x16[] = {
",",
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x58,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
"。",
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x18,0x24,0x24,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
"你",
0x00,0x80,0x60,0xF8,0x07,0x40,0x20,0x18,0x0F,0x08,0xC8,0x08,0x08,0x28,0x18,0x00,
0x01,0x00,0x00,0xFF,0x00,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00,
"好",
0x10,0x10,0xF0,0x1F,0x10,0xF0,0x00,0x80,0x82,0x82,0xE2,0x92,0x8A,0x86,0x80,0x00,
0x40,0x22,0x15,0x08,0x16,0x61,0x00,0x00,0x40,0x80,0x7F,0x00,0x00,0x00,0x00,0x00,
"世",
0x20,0x20,0x20,0xFE,0x20,0x20,0xFF,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x00,
0x00,0x00,0x00,0x7F,0x40,0x40,0x47,0x44,0x44,0x44,0x47,0x40,0x40,0x40,0x00,0x00,
"界",
0x00,0x00,0x00,0xFE,0x92,0x92,0x92,0xFE,0x92,0x92,0x92,0xFE,0x00,0x00,0x00,0x00,
0x08,0x08,0x04,0x84,0x62,0x1E,0x01,0x00,0x01,0xFE,0x02,0x04,0x04,0x08,0x08,0x00,
/*按照上面的格式,在这个位置加入新的汉字数据*/
//...
"西",
0x02,0x02,0xE2,0x22,0x22,0xFE,0x22,0x22,0x22,0xFE,0x22,0x22,0xE2,0x02,0x02,0x00,
0x00,0x00,0xFF,0x48,0x44,0x43,0x40,0x40,0x40,0x43,0x44,0x44,0xFF,0x00,0x00,0x00,/*",0*/
"安",
0x80,0x90,0x8C,0x84,0x84,0x84,0xF5,0x86,0x84,0x84,0x84,0x84,0x94,0x8C,0x80,0x00,
0x00,0x80,0x80,0x84,0x46,0x49,0x28,0x10,0x10,0x2C,0x23,0x40,0x80,0x00,0x00,0x00,/*1*/
/*未找到指定汉字时显示的默认图形(一个方框,内部一个问号),请确保其位于数组最末尾*/
"",
0xFF,0x01,0x01,0x01,0x31,0x09,0x09,0x09,0x09,0x89,0x71,0x01,0x01,0x01,0x01,0xFF,
0xFF,0x80,0x80,0x80,0x80,0x80,0x80,0x96,0x81,0x80,0x80,0x80,0x80,0x80,0x80,0xFF,
};
代码:
OLED_ShowChinese(0,0,"你好。");
汉字流动代码:
OLED显示流动汉字
OLED_WriteCommand(0x2E); //关闭滚动
OLED_WriteCommand(0x27); //水平向左或者右滚动 26/27
OLED_WriteCommand(0x00); //虚拟字节
OLED_WriteCommand(0x00); //起始页 0
OLED_WriteCommand(0x07); //滚动时间间隔
OLED_WriteCommand(0x07); //终止页 7
OLED_WriteCommand(0x00); //虚拟字节
OLED_WriteCommand(0xFF); //虚拟字节
OLED_ShowChinese(0,0,"你好,世界。");
OLED_ShowChinese(0,16,"你好,西安。");
OLED_WriteCommand(0x2F); //开启滚动
OLED_Update();
显示图像
和上面的文字取模是一个道理
输入宽x (0~127) 高y (0~63) 范围
然后再面板中画出想要的图像,把16进制文件复制到 OLED_Data.c
/*显示图像*/
OLED_ShowImage(0,0,128,64,SA);
OLED_Update();
照片显示在屏幕上面
取模软件只能打开BMP格式的照片,所以需要使用PS把照片进行处理一下。一般是进行2个操作:调整图片的大小,去调颜色(进行二值化处理)
ps处理图片
1:为了凸显轮廓细节
2:进行二值化
3:改变图像尺寸:
注意宽度和高度的取值范围,单位必须为像素。
4:再次进行二值化(效果更加好)
5:保存图片
保存格式选择BMP格式,取模软件只能识别BMP格式的图片。
这个一般直接默认就欧克。
这个里面我们的PS处理完成,取模和上面的一样。
注意:代码中的宽度和高度必须和我们改变图片的尺寸为一样的。
// /*显示图像*/
OLED_ShowImage(0,0,102,64,SA);
//显示照片
OLED_Update();
制作菜单选择界面
函数:
/*显存控制函数*/
void OLED_Clear(void); 清除全部
void OLED_ClearArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height); 清除部分指定区域
void OLED_Reverse(void); 全部取反
void OLED_ReverseArea(int16_t X, int16_t Y, uint8_t Width, uint8_t Height); 指定区域取反
效果:
while(1)
{
OLED_ShowString(0,0,"Hello Word!",OLED_8X16);
OLED_ShowString(0,20,"Hello STM32!",OLED_8X16);
OLED_ReverseArea(0,0,90,16); //取反
delay_ms(1500);
OLED_Update();
OLED_ClearArea(0,0,128,16); //清除指定部分区域
OLED_ShowString(0,0,"Hello Word!",OLED_8X16);
OLED_ReverseArea(0,20,93,16); //取反
delay_ms(1500);
OLED_Update();
}
OLED菜单选择界面
倒置屏幕
如果在有一些的情况下,需要把我们的OLED屏幕反着叉的话,进行下面的操作。
在OLED.C文件下找到OLED_Init()函数:
原本的:
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常,0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常,0xC0上下反置
改为:
OLED_WriteCommand(0xA0); //设置左右方向,0xA1正常,0xA0左右反置
OLED_WriteCommand(0xC0); //设置上下方向,0xC8正常,0xC0上下反置
显示的东西出现倒置,在把屏幕叉反,显示的东西为正,满足我们的要求。
把0.96寸的移植到1.3寸的文件下里
原来的:
void OLED_SetCursor(uint8_t Page, uint8_t X)
{
/*如果使用此程序驱动1.3寸的OLED显示屏,则需要解除此注释*/
/*因为1.3寸的OLED驱动芯片(SH1106)有132列*/
/*屏幕的起始列接在了第2列,而不是第0列*/
/*所以需要将X加2,才能正常显示*/
// X += 2;
/*通过指令设置页地址和列地址*/
OLED_WriteCommand(0xB0 | Page); //设置页位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}
取消X+=2的注释,1.3寸的OLED就可以正常使用了
四:问题解决
汉字编译出现错误
使用UTF-8编码可能会出现:
解决方法:
--no-multibyte-chars
开启软件对UTF-8写入汉字写入代码的支持了
编码格式的转化
我们使用不同的编码格式,需要对于的OLED的文件需要转化为对应的格式。
把文件到导入里面,转化为需要的编码格式。
在OLED Data.h文件里面该:
#define OLED_CHN_CHAR_WIDTH 2 UTF-8编码格式给3,GB2312编码格式给2
五:代码部分
我们使用的为l2C 0.96寸的OLED,编码格式为GB2312.
l2C可以参考我们的(I2C的代码不做解释):
https://mp.csdn.net/mp_blog/creation/editor/138002186https://mp.csdn.net/mp_blog/creation/editor/138002186
OLED写命令
* 函 数:OLED写命令
* 参 数:Command 要写入的命令值,范围:0x00~0xFF
返 回 值:无
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start(); I2C起始
OLED_I2C_SendByte(0x78); 发送OLED的I2C从机地址
OLED_I2C_SendByte(0x00); 控制字节,给0x00,表示即将写命令
OLED_I2C_SendByte(Command); 写入指定的命令
OLED_I2C_Stop(); I2C终止
}
D/C=1--->写数据; D/C=0---->写命令。
从机地址:SA0默认为低电平,我们对于OLED是写入(0--->主机给从机发送数据);
0 1 1 1 1 0 0 0=0x78; 如果有多个从机地址重复可以拉高SA改变从机地址。
按照I2C的通信时序,首先开始i2C的通信,选择OLED的从机地址,接收应答。选择模式有2个(c0为决定,1--->连续模式,0--非连续模式):
(模式1:发送一个命令,应答;在发送一个数据应答,循环往复-----连续模式
模式2:发送一个命令,应答,后面的全部是发送数据,应答。在次模式下,只需要发送一个命令,既可------非连续模式。)
我们选择模式2:0 0 0 0 0 0 0 0=0x00,然后再接收一个从机的应答。这个时候已经进入我们的写命令了,下一个直接发送我们需要写入的命令,再接收一个从机的应答。然后就停止了。
关于代码中没有接收写接收从机应答?
void OLED_I2C_SendByte
{
.......
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
再处理完之后,我们加了一个信号,不处理应答。也就不用接收从机的应答了,下面不在解释关于接收从机的问题。
OLED写数据
* 函 数:OLED写数据
* 参 数:Data 要写入数据的起始地址
* 参 数:Count 要写入数据的数量
* 返 回 值:无
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
uint8_t i;
OLED_I2C_Start(); I2C起始
OLED_I2C_SendByte(0x78); 发送OLED的I2C从机地址
OLED_I2C_SendByte(0x40); 控制字节,给0x40,表示即将写数据
循环Count次,进行连续的数据写入
for (i = 0; i < Count; i ++)
OLED_I2C_SendByte(Data[i]); 依次发送Data的每一个数据
}
OLED_I2C_Stop(); I2C终止
}
前面的和上面一样。0100 0000=0x40表示我们进入的写数据模式。因为进入的是写非连续模式,下面可以一直写数据。
函数初始化
完全是根据初始化过程(内部提供VCC)下面的表来使用的。
显示光标位置
* 函 数:OLED设置显示光标位置
* 参 数:Page 指定光标所在的页,范围:0~7
* 参 数:X 指定光标所在的X轴坐标,范围:0~127
* 返 回 值:无
* 说 明:OLED默认的Y轴,只能8个Bit为一组写入,即1页等于8个Y轴坐标
void OLED_SetCursor(uint8_t Page, uint8_t X)
{
/*如果使用此程序驱动1.3寸的OLED显示屏,则需要解除此注释*/
/*因为1.3寸的OLED驱动芯片(SH1106)有132列*/
/*屏幕的起始列接在了第2列,而不是第0列*/
/*所以需要将X加2,才能正常显示*/
// X += 2;
/*通过指令设置页地址和列地址*/
OLED_WriteCommand(0xB0 | Page); 置页位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); 设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); 设置X位置低4位
}
y:页的前5个位命令,后3个位为页的地址。(一共8页,一页8个位)
X: 命令前4位=0000 后面4位表示x的低4位数据。
X:命令前4位=0001 后面4位为x的高8位。因为x的高8位在命令中表示的是,低4为所以,需要左移4个字节。