代码下载
https://download.youkuaiyun.com/download/qq_37443333/11131657
这里有我上传的stm32f411re代码
简介
模式选择
oled支持多种接口方式,该模块提供了总共 4 种接口包括:6800、8080 两种并行接口方式、4线 SPI 接口方式以及 IIC 接口方式(修改接口方式需要修改硬件电路,一般一个商家的OLED出厂电路是固定也就是说接口方式是固定的,如果要修改方式则需要结合OLED原理电路图)
Pin说明
这里我们使用的6针OLED屏幕原理电路图,由PCB板背面的丝印可以知道这一块板子使用的是4线SPI,这也就是为什么第一天使用IIC程序显示屏幕失败的原因。
不仅要知道OLED电路图的针线,还要如何使用这些管脚,以下是数据手册中关于引脚的说明
D0~D7用来传输数据,从上面的介绍我们可以知道SPI4线接口模式,When serial mode is selected, D1 will be the serial data input SDIN and D0 will be the serial clock input SCLK(D1数据引脚、D0时钟引脚)。
由于我们使用的4线SPI不支持读(D2本应是读数据线,现模块将其接地)
这些引脚对于屏幕来说是input,所以驱动开发板引脚时就要out,使用推挽输出(速率上好像大家都没有统一)
特别注意
①本文章最后面贴出的源码是基于模拟SPI来传输数据,因为使用的是软件模拟SPI,故不需要必须使用带有外设功能SPI的引脚来传输,可以使用任意一个自由的管脚(如果没有现有资料告诉我们,这就需要看电路原理图加上测试得出哪一个管脚是自由的)
②在使用一个功能函数时一定更要问一遍自己是否使能、初始化了,有编译器不会检测到错误
4线SPI原理介绍
连线
信号线 | 功能 |
---|---|
CS | OLED 片选信号 |
RST(RES) | 硬复位 OLED |
DC | 命令/数据标志(0,读写命令;1,读写数据) |
SCLK | 串行时钟线。在 4 线串行模式下,D0 信号线作为串行时钟线 SCLK |
SDIN | 串行数据线。在 4 线串行模式下,D1 信号线作为串行数据线 SDIN |
SPI模式时序
stm32 SPI通过CS端片选选择SPI设备(主机上使用NSS连接从机CS,如果不够则使用其他IO模拟NSS),而IIC协议通过传输地址数据来选IIC设备,故SPI速度快,SPI具有全双工特性。
MISO:Master Input Slave Output
MOSI理解参考MISO
在 4 线 SPI 模式下,每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到
SSD1306,并且是高位在前的(这个信息是从SSD1306数据手册得知的)
SPI通讯知识
此处讲解的是硬件SPI通讯
SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串
行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果
CPOL=0,串行同步时钟的空闲状态为低电平;如果 CPOL=1,串行同步时钟的空闲状态为高电
平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果 CPHA=0,
在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果 CPHA=1,在串行同步时钟
的第二个跳变沿(上升或下降)数据被采样。SPI 主模块和与之通信的外设备时钟相位和极性
应该一致。
简单的说,CPOL控制时钟线空闲时电平高低,CPHA控制是第一个边沿还是第二个边沿触发
库函数说明
typedef struct
{
uint16_t SPI_Direction;
uint16_t SPI_Mode;
uint16_t SPI_DataSize;
uint16_t SPI_CPOL;
uint16_t SPI_CPHA;
uint16_t SPI_NSS;
uint16_t SPI_BaudRatePrescaler;
uint16_t SPI_FirstBit;
uint16_t SPI_CRCPolynomial;
}SPI_InitTypeDef;
第一个参数 SPI_Direction 是用来设置 SPI 的通信方式,可以选择为半双工,全双工,以及串行
发和串行收方式,这里我们选择全双工模式 SPI_Direction_2Lines_FullDuplex。
第二个参数 SPI_Mode 用来设置 SPI 的主从模式,这里我们设置为主机模式 SPI_Mode_Master,
当然有需要你也可以选择为从机模式 SPI_Mode_Slave。
第三个参数 SPI_DataSiz 为 8 位还是 16 位帧格式选择项,这里我们是 8 位传输,选择
SPI_DataSize_8b。
第四个参数 SPI_CPOL 用来设置时钟极性,我们设置串行同步时钟的空闲状态为高电平所以我
们选择 SPI_CPOL_High。
第五个参数 SPI_CPHA 用来设置时钟相位,也就是选择在串行同步时钟的第几个跳变沿(上升
或下降)数据被采样,可以为第一个或者第二个条边沿采集,这里我们选择第二个跳变沿,所
以选择 SPI_CPHA_2Edge
第六个参数 SPI_NSS 设置 NSS 信号由硬件(NSS 管脚)还是软件控制,这里我们通过软件控
制 NSS 关键,而不是硬件自动控制,所以选择 SPI_NSS_Soft。
第七个参数 SPI_BaudRatePrescaler 很关键,就是设置 SPI 波特率预分频值也就是决定 SPI 的时
钟的参数,从 2 分频到 256 分频 8 个可选值,初始化的时候我们选择 256 分频值
SPI_BaudRatePrescaler_256, 传输速度为 84M/256=328.125KHz。
第八个参数 SPI_FirstBit 设置数据传输顺序是 MSB 位在前还是 LSB 位在前,,这里我们选择SPI_FirstBit_MSB 高位在前。
第九个参数 SPI_CRCPolynomial 是用来设置 CRC 校验多项式,提高通信可靠性,大于 1 即可。
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线双向全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主 SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // SPI 发送接收 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二个跳变沿数据被采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由软件控制
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //预分频 256
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式
SPI_Init(SPI2, &SPI_InitStructure); //根据指定的参数初始化外设 SPIx 寄存器
SSD1306显示原理
SSD1306 的显存总共为 12864bit 大小,SSD1306 将这些显存分为了 8 页,每一页都是1288,所以整个屏幕分辨率是128*64
设置OLED显示内存的寻址方式
0x00: 表示水平寻址方式
0x01: 表示垂直寻址方式
0x10: 表示页寻址方式==(默认方式)==
使用举例: SPI_Write(0x02, OLED_Order);// 设置为页寻址方式
设置显示坐标
例如:设置页地址为0xB2; 列地址的低4位为0x03,列地址的高4位为0x00;则显示的位置如下图:
代码解读(底层基本代码)
更新显存
在此坑过,使用显示数字函数后一定要使用更新显存,不然不会显示
因为我们采用的方式通过定义一块GRAM数组,一个画点函数只是通过操作单片机内存数组来改变了单片机内存中的数组,而通过OLED_Refresh_Gram一次性更新到OLED中
/*
* OLED_Refresh_Gram 函数先设置页地址,然后写入列地址(也就是纵坐标),然后从 0 开
* 始写入 128 个字节,写满该页,最后循环把 8 页的内容都写入,就实现了整个从 STM32F4 显
* 存到 OLED 显存的拷贝
*/
void OLED_Refresh_Gram(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
}
}
想要强调的是:
设置列地址时是设置的列的起始地址,上面代码设置的是第一列,故从第一列开始写入。
将GRAM中对应的每一个数字发给显示!!!!!!!!!!!!!!!!!(加上怎么存放数据)
写字节函数
SPI写时序
是先发送7还是0,是上沿还是下沿采样,是16位还是8位。在固件库中可提供选择
模拟SPI如何选择?
//向OLED写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{
u8 i;
if(cmd)
OLED_RS_Set(); //如果想输入命令,则将DC置1
else
OLED_RS_Clr(); //如果想输入命令,则将DC置0
for(i=0;i<8;i++)
{
OLED_SCLK_Clr();
if(dat&0x80)
OLED_SDIN_Set();
else
OLED_SDIN_Clr();
OLED_SCLK_Set();
dat<<=1;
}
OLED_RS_Set();
}
oled显示、关闭显示、初始化函数都是根据OLED数据手册的命令来实现的。都可以通用
OLED显示函数
//开启OLED显示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
}
OLED关闭显示函数
//关闭OLED显示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
显示字符串
字符串码表导入了两种,一种是12x6像素的,一种是16x8的。
以16x8就是16行8列的像素点,以下程序的意思将x0,y0设置这个16x8的左上角第一个点,然后每次输入一位就y加1,如果该位是1就则oled该像素亮,否则不亮,往下移动,输入8位(1个字节后)size加一,再输入8位时,这一列就16位了,这时候(y-y0)=size了,就将y变为起始y0,也就是进入第一行第二列…当输入16个字节后,size==16,也就完成了16x8的字
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{
u8 temp,t,t1;
u8 y0=y;
chr=chr-' ';//得到偏移后的值
for(t=0;t<size;t++)
{
if(size==12)
temp=oled_asc2_1206[chr][t]; //调用12x06字体
else
temp=oled_asc2_1608[chr][t]; //调用16x08字体
for(t1=0;t1<8;t1++)
{
if(temp&0x80)
OLED_DrawPoint(x,y,mode);
else
OLED_DrawPoint(x,y,!mode);
temp<<=1;
y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
}
m^n的函数
//m^n函数
u32 oled_pow(u8 m,u8 n)
{
u32 result=1;
while(n--)result*=m;
return result;
}
显示数字
void OLED_ShowNumber(u8 x,u8 y,u32 num,u8 len,u8 size)
{
u8 t,temp;
u8 enshow=0;
for(t=0;t<len;t++)
{
temp=(num/oled_pow(10,len-t-1))%10; //取出每一位
if(enshow==0&&t<(len-1))
{
if(temp==0) ////如果显示的数字是07,0的位置显示空白
{
//无论是16*8还是12*6都是都是以X,Y为起始点,以16(y)x8(x)为例,但是第二个字符显示的位置就x+8,一列是两个字符
OLED_ShowChar(x+(size/2)*t,y,' ',size,1);
continue;
}else enshow=1;
}
OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1);
}
}
显示字符串
x行:0~127
y列:0~63
如果列大于了MAX_CHAR_POSX 则需要换行,因为此函数是针对16(列)x8(行)的,所以y列就+16
如果行大于了MAX_CHAR_POSY 则清除屏幕,起始点复原
//x,y:起点坐标
//*p:字符串起始地址
//用16x8字体
void OLED_ShowString(u8 x,u8 y,const u8 *p)
{
#define MAX_CHAR_POSX 122
#define MAX_CHAR_POSY 58
while(*p!='\0')
{
if(x>MAX_CHAR_POSX){x=0;y+=16;} //用的是16字体
if(y>MAX_CHAR_POSY){y=x=0;OLED_Clear();}
OLED_ShowChar(x,y,*p,12,1);
x+=8;
p++;
}
}
看手册得出的一些结论
①初始化加延时
To protect OEL panel and extend the panel life time, the driver IC power up/down routine should include
a delay period between high voltage and low voltage power sources during turn on/off. It gives the
OEL panel enough time to complete the action of charge and discharge before/after the operation.