STM32OLED使用
STM32OLED使用
市面上大部分OLED使用SSD1306作为主控芯片,在这里使用STM32F103作为主控芯片,使用IIC总线点亮OLED。
1.IIC设置以及初始化
共需要引用4个头文件“stm32f10x_rcc.h”,“stm32f10x_gpio.h”,“stm32f10x_i2c.h”,“string.h”
void IIC_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIO时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);//开启IIC时钟
GPIO_InitTypeDef IIC_Gpio;//定义GPIO结构体
IIC_Gpio.GPIO_Pin |= GPIO_Pin_6 | GPIO_Pin_7;//设置GPIO引脚
IIC_Gpio.GPIO_Mode = GPIO_Mode_AF_OD;//设置GPIO为复用推挽输出
IIC_Gpio.GPIO_Speed = GPIO_Speed_50MHz;//设置GPIO速度
GPIO_Init(GPIOB,&IIC_Gpio);//载入GPIO配置
I2C_DeInit(I2C1);//初始化IIC
I2C_InitTypeDef IIC_InitStruct;//定义IIC结构体
IIC_InitStruct.I2C_ClockSpeed = 400000;//设置IIC时钟速率
IIC_InitStruct.I2C_Mode = I2C_Mode_I2C;//设置IIC模式
IIC_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;//设置IIC占空比
IIC_InitStruct.I2C_OwnAddress1 = 0x21;//设置IIC本机地址,可随意设置,但不能与已有设备重复
IIC_InitStruct.I2C_Ack = I2C_Ack_Enable;//设置IIC应答
IIC_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;//设置IIC应答地址长度
I2C_Init(I2C1,&IIC_InitStruct);//载入IIC配置
I2C_Cmd(I2C1,ENABLE);//使能IIC
}
2.IIC发送函数
由于STM32标准库的事件查询组不具备阻塞性质,需要进行封装成阻塞式来等待事件完成
void IIC_WaitEvent(I2C_TypeDef* I2Cx,uint32_t Check_Event)//等待事件更新
{
while(I2C_CheckEvent(I2C1,Check_Event) != SUCCESS);
}
对于OLED屏幕,不但需要发送一次读写位,还需要在发送一个命令位来确定是写入数据还是命令,数据的话需要先发送0x40,命令的化需要发送0x80。
#define OLED_Address 0x78
void IIC_Send(uint8_t data,uint8_t type)//发送数据函数,type为1时发送命令,为0时发送数据
{
if(type)//发送命令
{
I2C_GenerateSTART(I2C1,ENABLE);//生成IIC起始信号
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT);//等待IIC模式被选中事件
I2C_Send7bitAddress(I2C1,OLED_Address,I2C_Direction_Transmitter);//发送IIC地址和方向
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//等待IIC发送器模式被选中事件
I2C_SendData(I2C1,0x80);//发送命令位
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
I2C_SendData(I2C1,data);//发送数据
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
I2C_GenerateSTOP(I2C1,ENABLE);//生成IIC停止信号
}
else//发送数据
{
I2C_GenerateSTART(I2C1,ENABLE);//生成IIC起始信号
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT);//等待IIC模式被选中事件
I2C_Send7bitAddress(I2C1,OLED_Address,I2C_Direction_Transmitter);//发送IIC地址和方向
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//等待IIC发送器模式被选中事件
I2C_SendData(I2C1,0x40);//发送数据位
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
I2C_SendData(I2C1,data);//发送数据
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
I2C_GenerateSTOP(I2C1,ENABLE);//生成IIC停止信号
}
}
void IIC_SendBuff(uint8_t* str,uint16_t len,uint8_t type)//发送多字节
{
I2C_GenerateSTART(I2C1,ENABLE);//生成IIC起始信号
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT);//等待IIC模式被选中事件
I2C_Send7bitAddress(I2C1,OLED_Address,I2C_Direction_Transmitter);//发送IIC地址和方向
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//等待IIC发送器模式被选中事件
if(type)
{
I2C_SendData(I2C1,0x80);//发送命令位
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
while(len--)//循环发送数据
{
I2C_SendData(I2C1,str);//发送数据
str++;
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
}
}
else
{
I2C_SendData(I2C1,0x40);//发送数据位
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
while(len--)//循环发送数据
{
I2C_SendData(I2C1,str);//发送数据
str++;
IIC_WaitEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待发送完成事件
}
}
I2C_GenerateSTOP(I2C1,ENABLE);//生成IIC停止信号
}
3.OLED初始化
OLED初始化指令解析:
Hex | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | 命令内容 |
---|---|---|---|---|---|---|---|---|---|
0x81 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 设置对比度,下一条命令代表对比度参数 |
A[7:0] | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 | 设置对比度值范围为1~255 |
0xA4 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 开启正常显示 |
0xA5 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 点亮屏幕所所有像素点 |
0xA6 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 正常显示 |
0xA7 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 像素反向显示 |
0xAE | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 关闭OLED显示,进入睡眠模式 |
0xAF | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | 开启OLED显示,从睡眠模式中唤醒 |
0x20 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 设置页面地址模式,下一条命令为具体模式设置 |
A[1:0] | * | * | * | * | * | * | A1 | A0 | 设置页面地址模式,A[1:0]=00:水平寻址模式,A[1:0]=01:垂直寻址模式,A[1:0]=10:页寻址模式,A[1:0]=11:无效 |
0x21 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 设置列显示位置,下两条命令为具体模式设置 |
A[7:0] | * | A6 | A5 | A4 | A3 | A2 | A1 | A0 | 列起始地址,范围0-127 |
B[7:0] | * | B6 | B5 | B4 | B3 | B2 | B1 | B0 | 列结束地址,范围0-127 |
0x22 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 设置页显示位置,下两条命令为具体模式设置 |
A[7:0] | * | * | * | * | * | A2 | A1 | A0 | 页起始地址,范围0-7 |
B[7:0] | * | * | * | * | * | B2 | B1 | B0 | 页结束地址,范围0-7 |
0xA0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 设置列扫描方向,从左向右扫描 |
0xA1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 设置列扫描方向,从右向左扫描 |
0xA8 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 设置多路复用器,下一条命令为具体参数 |
A[7:0] | * | * | A5 | A4 | A3 | A2 | A1 | A0 | 设置多路复用器为初始模式,即16bit模式[1] |
0xC0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 设置行扫描方向,从上到下扫描 |
0xC8 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 设置行扫描方向,从下到上扫描 |
0xD3 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 设置显示偏移量,下一条命令为具体参数 |
A[5:0] | * | * | A5 | A4 | A3 | A2 | A1 | A0 | 设置显示偏移量,范围0~63 |
0xD5 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 设置显示时钟分频比,下一条命令为具体参数 |
A[7:0] | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 | 设置显示时钟,A[3:0]+1为振荡器分频比,A[7:4]为振荡器频率,范围为0~1111,复位值为1000 |
0xD9 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 设置预充电周期,下一条命令为具体参数 |
A[7:0] | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 | 设置预充电周期,在阶段1,A[3:0]个周期为无效周期,范围0~15;在阶段2,A[7:4]个周期为有效周期,范围0~15 |
0xDB | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 设置VCOMH级别,下一条命令为具体参数 |
A[7:0] | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 | A[6:4]=000b,VCOMH=0.65VCC;A[6:4]=010b,VCOMH=0.77VCC;A[6:4]=011b,VCOMH=0.83*VCC |
0xE3 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 无操作 |
0xD6 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 设置放大模式,下一条命令为具体参数 |
A[7:0] | * | * | * | * | * | * | * | A0 | A[0] = 0,关闭放大模式,A[0] = 1,开启放大模式 |
0x8D | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 开启电荷泵,后续两条指令必须为0x14和0xAF |
上述指令并不包括所有SSD1306的指令,在这里只给出配置相关的指令,具体指令请参考SSD1306的数据手册。
详细初始化代码:
uint8_t OLED_InitCmd[] =
{ 0xAE,//关闭OLED显示
0x00,//设置低列地址
0x10,//设置高列地址
0x40,//设置开始行地址
0x81,//设置对比度控制寄存器
0xCF,//设置SEG输出电路亮度
0xA1,//设置SEG列扫描方向,0xA0左右翻转,
0xC8,//设置COM输出扫描方向,0xC0上下翻转,0xC8正常显示
0xA6,//设置正常显示
0xA8,//设置多路复用器,
0x3F,//设置多路复用为16bit
0xD3,//设置移位映射寄存器
0x00,//无操作
0xD5,//设置显示时钟分频比/振荡器频率
0x80,//设置分频比,设置时钟为100帧/s
0xD9,//设置预充电周期
0xF1,//设置预充电周期具体参数
0xDA,//设置COM硬件配置
0x12,//设置为页面寻址模式
0xDB,//设置VCOMH
0x40,//设置VCOM取消电平
0x20,//设置页面寻址模式
0x02,//设置为页面寻址模式
0x8D,//设置电荷泵
0x14,//设置电荷泵启用
0xA4,//开启正常显示
0xA6,//禁用反向显示
0xAF};//开启OLED显示
void OLED_Init()
{
IIC_init();//初始化IIC
IIC_SendBuff(OLED_InitCmd,sizeof(OLED_InitCmd)/sizeof(uint8_t),OLED_CMD);将OLED初始化命令发送到OLED
}
OLED画点函数实现
由于OLED使用的显示为页面寻址,此寻址方式有一个很大的问题,假设此时要显示的东西位于页1的后4行和页2的前四行,如果不进行数据的读取而进行刷新,是必然会丢掉页1的前4行和页2的后4行数据。因此必须要使用页面缓冲的刷新方式,而不能单纯使用点刷新或者行刷新,即最小刷新单位为一个页面。 我的思路是将每个页面数据都存放在长度为128的数组中,每次修改显示数据时,先将要更改的数据存入要显示的位置,在将数组中的数据全部发送到OLED,这样就可以保证不会丢失数据。
uint8_t OLED_Page_Rom[8][128] = {};//定义页面缓存,即显存
void OLED_SetPos(uint16_t ram_address,uint8_t page_address)//设置显示起始位置
{
IIC_Send(0xb0 | page_address,OLED_CMD);//设置行起始位置
IIC_Send(0x00 | (ram_address&0x00ff),OLED_CMD);//设置列
}
void OLED_Point_Preload(uint16_t x,uint16_t y,uint8_t draw)//显存预加载函数,将要更改的像素数据传入对应的数组位置
{
uint8_t page_address = y/8;//得到y坐标对应的页地址
uint8_t line_address = y%8;//得到y坐标对应的页面中的行地址
if((OLED_Page_Rom[page_address][x] & 0x01<<line_address) != (draw-30)) //代表原显存与要修改的值不同
{
OLED_Page_Rom[page_address][x] |= (draw<<line_address&0xff); //将修改的值存入对应位置
}
}
void OLED_PageRam_Load(uint8_t page_address,uint8_t* str)//显存发送函数
{
uint8_t blank[128];//定义局部变量
memcpy(blank,str,128);//将传入的变量传入局部变量blank
OLED_SetPos(0,page_address);设置显示起始位置
IIC_SendBuff(blank,sizeof(blank)/sizeof(uint8_t),OLED_DATA);//向OLED发送数据
}
void OLED_Draw_Point(uint16_t x,uint16_t y,uint8_t draw)
{
uint8_t page_address = y/8;//得到对应的页地址
OLED_Point_Preload(x,y,draw);//预加载显存
OLED_PageRam_Load(page_address,OLED_Page_Rom[page_address]);//发送显存
}