STM32OLED使用

STM32OLED使用.md

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]);//发送显存
}

posted @ 2024-03-01 21:31  T7H  阅读(106)  评论(0编辑  收藏  举报