概述
cebemax hal库 stm32 OLED移植
序言:
这是一篇对于作者对于将OLED的驱动代码移植成hal库的文章,会有完整的过程,后面自然也会有完整的代码,希望能够帮到大家,同时也是做一个记录,方便自己查看。
注:萌新写文,大佬轻喷。
1.写命令与数据函数的完成
原标准库代码
void I2C_Configuration(void)
{
I2C_InitTypeDef I2C_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_DeInit(I2C1);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x30;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 400000;
I2C_Cmd(I2C1, ENABLE);
I2C_Init(I2C1, &I2C_InitStructure);
}
void I2C_WriteByte(uint8_t addr,uint8_t data)
{
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
I2C_SendData(I2C1, addr);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_SendData(I2C1, data);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(I2C1, ENABLE);
}
void WriteCmd(unsigned char I2C_Command)
{
I2C_WriteByte(0x00, I2C_Command);
}
void WriteDat(unsigned char I2C_Data)
{
I2C_WriteByte(0x40, I2C_Data);
}
函数说明
I2C_Configuration 其实就是 I2C 的初始化函数,hal库里有现成的,所以删除。
I2C_WriteByte 在下面的两个函数有用到,但hal库里有现成的,所以删除。
WriteCmd 和 WriteDat 改写成 HAL 库的方式DelayMs 改为 HAL 库中的函数: HAL_I2C_Mem_Write……
所以就变成了
函数移植
void WriteCmd(unsigned char I2C_Command)
{
HAL_I2C_Mem_Write(&hi2c1,0X78,0x00,I2C_MEMADD_SIZE_8BIT,&I2C_Command,1,100);
}
void WriteDat(unsigned char I2C_Data)
{
HAL_I2C_Mem_Write(&hi2c1,0X78,0x40,I2C_MEMADD_SIZE_8BIT,&I2C_Data,1,100);
}
大家一定很疑惑,为啥一下子就变成这样了勒?
别急,接下来就来好好讲讲。
首先,我们先来看看HAL_I2C_Mem_Write函数的原型。
机翻,不一定会很准确,但能看懂。
移植代码的说明
说过了,是机翻,作者英语不好。
/**
* @brief以阻塞模式向特定内存地址写入大量数据
* @param hi2c指针指向一个I2C_HandleTypeDef结构,包含
*指定I2C的配置信息。
目标设备地址:设备7位地址值
*在数据表中必须在调用接口前向左移动
* @param MemAddress内存地址
* @param MemAddSize内存地址的大小
@param pData指向数据缓冲区的指针
* @param大小要发送的数据量
* @param超时超时持续时间
@retval HAL状态
*/
我们再对照着完整的函数看。
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
两相对照,我们就能够看明白每一个参数大概是干啥的。
参数一:I2C_HandleTypeDef *hi2c
指向包含指定I2C的配置信息的I2C_HandleTypeDef结构的指针,大概就是填写你所使用的i2c通道,如&hi2c1,&hi2c2。
参数二:uint16_t DevAddress
这里填目标设备地址,7位地址值。
参数三:uint16_t MemAddress
内存地址,也是目标设备的。
参数四:uint16_t MemAddSize
内存地址的大小,目标设备。
参数五:uint8_t *pData
指向数据缓冲区的指针,要写的东西。
参数六:uint16_t Size
要发送的数据量。
参数七:uint32_t Timeout
超时持续时间,大概就是,传输了多久还没传输完,就算他超时。
这样一来,我们再来看一下我之前写的那两个函数。
void WriteCmd(unsigned char I2C_Command)
{
HAL_I2C_Mem_Write(&hi2c1,0X78,0x00,I2C_MEMADD_SIZE_8BIT,&I2C_Command,1,100);
}
void WriteDat(unsigned char I2C_Data)
{
HAL_I2C_Mem_Write(&hi2c1,0X78,0x40,I2C_MEMADD_SIZE_8BIT,&I2C_Data,1,100);
}
大概能看明白了吧。
其中,0X78就是OLED的地址,这是默认地址,还有一种是0X7A的,具体怎么搞,我也没仔细研究,暂时用不上。
I2C_MEMADD_SIZE_8BIT是一个宏定义
#define I2C_MEMADD_SIZE_8BIT 0x00000001U
代码的优化
作者想了想之后,这个函数还可以再优化一下。
两个函数合成一个,方便使用。
void OLED_Write(unsigned char dat,unsigned char cmd)
{
if(cmd)
HAL_I2C_Mem_Write(&hi2c1,0X78,0x00,I2C_MEMADD_SIZE_8BIT,&dat,1,100);
else
HAL_I2C_Mem_Write(&hi2c1,0X78,0x40,I2C_MEMADD_SIZE_8BIT,&dat,1,100);
}
显而易见,dat填要写的数据,cmd选择是写数据还是写命令。
那么,到此,最为基本的写命令与数据的函数就完成了。
接下来,开始写初始化。
2.OLED初始化函数
原标准库函数
void OLED_Init(void)
{
IIC_Init();
DelayMs(500); //初始化之前的延时很重要!
OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
OLED_WR_Byte(0x00,OLED_CMD);//设置低列地址
OLED_WR_Byte(0x10,OLED_CMD);//设置高列地址
OLED_WR_Byte(0x40,OLED_CMD);//设置起始行地址,集映射RAM显示起始行(0x00~0x3F)
OLED_WR_Byte(0x81,OLED_CMD);//设置对比度控制寄存器
OLED_WR_Byte(0xCF,OLED_CMD);//设置SEG输出电流亮度
OLED_WR_Byte(0xA1,OLED_CMD);//段重定义设置,bit0:0,0->0;1,0->127; 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8,OLED_CMD);//设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6,OLED_CMD);//设置正常显示(设置显示方式;bit0:1,反相显示;0,正常显示 )
OLED_WR_Byte(0xA8,OLED_CMD);//设置驱动路数 设置多路复用比(1比64)
OLED_WR_Byte(0x3F,OLED_CMD);//1/64 duty(默认0X3F(1/64))
OLED_WR_Byte(0xD3,OLED_CMD);//设置显示偏移位移映射RAM计数器(0x00~0x3F)
OLED_WR_Byte(0x00,OLED_CMD);//-not offset
OLED_WR_Byte(0xD5,OLED_CMD);//设置显示时钟分频比/振荡器频率
OLED_WR_Byte(0x80,OLED_CMD);//设置分频比,设置时钟为100帧/秒
OLED_WR_Byte(0xD9,OLED_CMD);//设置预充电周期
OLED_WR_Byte(0xF1,OLED_CMD);//设置预充15个时钟,放电1个时钟([3:0],PHASE 1;[7:4],PHASE 2;)
OLED_WR_Byte(0xDA,OLED_CMD);//设置COM硬件引脚配置
OLED_WR_Byte(0x12,OLED_CMD);//[5:4]配置
OLED_WR_Byte(0xDB,OLED_CMD);//设置VCOMH 电压倍率
OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM 释放电压([6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;)
OLED_WR_Byte(0x20,OLED_CMD);//设置页面寻址模式(0x00/0x01/0x02)
OLED_WR_Byte(0x02,OLED_CMD);//[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
OLED_WR_Byte(0x8D,OLED_CMD);//设置充电泵启用/禁用
OLED_WR_Byte(0x14,OLED_CMD);//设置(0x10禁用,0x14启用)
OLED_WR_Byte(0xA4,OLED_CMD);// 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) (0xa4/0xa5)
OLED_WR_Byte(0xA6,OLED_CMD);// 设置显示方式;bit0:1,反相显示;0,正常显示 (0xa6/a7)
OLED_WR_Byte(0xAF,OLED_CMD);//开启显示
OLED_Clear();
OLED_SetCursorAddrese(0,0);
}
这个不用解释吧,无非是定好的一连串命令。
一个个写太麻烦,搞一个数组。
移植后函数
const uint8_t OLED_Init_cmd[28]=
{
0xAE, //关闭显示
0x00,//设置低列地址
0x10,//设置高列地址
0x40,//设置起始行地址,集映射RAM显示起始行(0x00~0x3F)
0x81,//设置对比度控制寄存器
0xCF,//设置SEG输出电流亮度
0xA1,//段重定义设置,bit0:0,0->0;1,0->127; 0xa0左右反置 0xa1正常
0xC8,//设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 0xc0上下反置 0xc8正常
0xA6,//设置正常显示(设置显示方式;bit0:1,反相显示;0,正常显示 )
0xA8,//设置驱动路数 设置多路复用比(1比64)
0x3F,//1/64 duty(默认0X3F(1/64))
0xD3,//设置显示偏移位移映射RAM计数器(0x00~0x3F)
0x00,//-not offset
0xD5,//设置显示时钟分频比/振荡器频率
0x80,//设置分频比,设置时钟为100帧/秒
0xD9,//设置预充电周期
0xF1,//设置预充15个时钟,放电1个时钟([3:0],PHASE 1;[7:4],PHASE 2;)
0xDA,//设置COM硬件引脚配置
0x12,//[5:4]配置
0xDB,//设置VCOMH 电压倍率
0x40,//Set VCOM 释放电压([6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;)
0x20,//设置页面寻址模式(0x00/0x01/0x02)
0x02,//[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
0x8D,//设置充电泵启用/禁用
0x14,//设置(0x10禁用,0x14启用)
0xA4,// 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) (0xa4/0xa5)
0xA6,// 设置显示方式;bit0:1,反相显示;0,正常显示 (0xa6/a7)
0xAF //开启显示
};
然后再加一个初始化的for循环
void OLED_Init(void)
{
uint8_t i;
//IIC_Init(); //cubemax自动生成了,我们不需要。
HAL_Delay(500); //初始化之前的延时很重要!
for(i = 0;i < 29;i ++)
{
OLED_Write(OLED_Init_cmd[i],1);
}
OLED_Clear();
OLED_SetCursorAddrese(0,0);
}
原初始化函数里还有三个函数的调用,其中一个我们不需要,另外两个我们先放着,一个是用来清屏的,还有一个是用来设置光标位置的,后面马上就可以看到。
那么,至此,初始化便完成了,我们继续改其他的函数。
上原函数。
3.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 (OLED休眠)
}
/*
函数功能:清屏函数,清完屏,整个屏幕是黑色的!
说明: 清屏就是向OLED里写0
对于OLED屏,0就不显示,1才会在屏幕上显示出来一个点
*/
void OLED_Clear(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(0x00,OLED_DATA);
}
}
}
开显示的修改
/*函数功能:开启OLED显示 */
void OLED_Display_On(void)
{
OLED_Write(0X8D,1);//SET DCDC命令(设置电荷泵)
OLED_Write(0X14,1);//DCDC ON (开启电荷泵)
OLED_Write(0XAF,1);//DISPLAY ON(OLED唤醒)
}
关显示的修改
/*函数功能:关闭OLED显示*/
void OLED_Display_Off(void)
{
OLED_Write(0X8D,1);//SET DCDC命令 (设置电荷泵)
OLED_Write(0X10,1); //DCDC OFF (关闭电荷泵)
OLED_Write(0XAE,1);//DISPLAY OFF (OLED休眠)
}
清屏函数的修改
/*
函数功能:清屏函数,清完屏,整个屏幕是黑色的!
说明: 清屏就是向OLED里写0
对于OLED屏,0就不显示,1才会在屏幕上显示出来一个点
*/
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
OLED_Write(0xB0+i,1);//设置页地址(0~7)
OLED_Write(0x00,1);//设置显示位置―列低地址
OLED_Write(0x10,1);//设置显示位置―列高地址
for(n=0;n<128;n++)
{
OLED_Write(0x00,0);
}
}
}
直接复制的小伙伴,是不是有些出现了问题,哈哈。
是一些有关于宏定义的问题。
在c文件里加上这一段就行:
typedef signed char int8_t; // 标准表达方式 signed char 被等同于 int8_t;
typedef signed short int int16_t;
typedef signed int int32_t; //在32位环境里,int代表4个字节32位!!
/* exact-width unsigned integer types */
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef int32_t s32;
typedef int16_t s16;
typedef int8_t s8;
typedef uint32_t u32; //32位
typedef uint16_t u16; //16位
typedef uint8_t u8; //8位
接下来便是
写点,设光标以及垂直滚动的原函数
/*
函数功能:在显存数组上画一个点
函数参数:x,y为点的横纵坐标 c为这个点的亮灭(1亮0灭)
参数范围:x 0~128 y 0~8
每一个数据是 低位在前,高位在后
*/
void OLED_Draw_Point(u8 x,u8 y,u8 c)
{
u8 page,addr;
page = y/8; //显存的页地址
addr = y%8; //显存的一个字节数据中c所在的位置
if(c) OLED_GRAM[page][x] |= 1<<addr;
else OLED_GRAM[page][x] &= ~(1<<addr);
}
/*
函数功能: 设置光标位置
函数参数: x列的起始位置(0~127)
y页的起始位置(0~7)
比如: 0x8 高4位0000 低4位1000
*/
void OLED_SetCursorAddrese(u8 x,u8 y)
{
OLED_WR_Byte(0xB0+y,OLED_CMD); //设置页地址
//第6列显示数据 6被分成2个4位(高位和低位)。
OLED_WR_Byte((x&0xF0)>>4|0x10,OLED_CMD);//设置列高起始地址(半字节)
OLED_WR_Byte((x&0x0F)|0x00,OLED_CMD); //设置列低起始地址(半字节)
}
/*****************************************************
** 函数名称:OLED_VerticalDisplay
** 函数功能:实现OLED垂直滚动范围配置
** 参 数:1.toprow:设置滚动起始行
** 2.scrollrow:设置滚动行数
** 注意:toprow+scrollrow<64
** 函数返回:无
******************************************************/
void OLED_VerticalDisplay(u8 toprow,u8 scrollrow)
{
OLED_WR_Byte(SET_VERTICAL_SCROLL_AREA,OLED_CMD);
OLED_WR_Byte(toprow,OLED_CMD);
OLED_WR_Byte(scrollrow,OLED_CMD);
}
写点函数的修改
这些函数的名字啥的我都懒得改了,无所谓的。
/*
函数功能:在显存数组上画一个点
函数参数:x,y为点的横纵坐标 c为这个点的亮灭(1亮0灭)
参数范围:x 0~128 y 0~8
每一个数据是 低位在前,高位在后
*/
void OLED_Draw_Point(u8 x,u8 y,u8 c)
{
u8 page,addr;
page = y/8; //显存的页地址
addr = y%8; //显存的一个字节数据中c所在的位置
if(c) OLED_GRAM[page][x] |= 1<<addr;
else OLED_GRAM[page][x] &= ~(1<<addr);
}
这个好像不用改,不过涉及到一个数组,之前没有定义,定义一下。
u8 OLED_GRAM[8][128];/*定义OLED显存数组*/
函数内容很简单,就是要让哪位显示出来,就给哪位赋值一个1,不显示就给那位非一下,赋值0.
设光标函数的修改
/*
函数功能: 设置光标位置
函数参数: x列的起始位置(0~127)
y页的起始位置(0~7)
比如: 0x8 高4位0000 低4位1000
*/
void OLED_SetCursorAddrese(u8 x,u8 y)
{
OLED_Write(0xB0+y,1); //设置页地址
//第6列显示数据 6被分成2个4位(高位和低位)。
OLED_Write((x&0xF0)>>4|0x10,1);//设置列高起始地址(半字节)
OLED_Write((x&0x0F)|0x00,1); //设置列低起始地址(半字节)
}
滚动函数修改
/*****************************************************
** 函数名称:OLED_VerticalDisplay
** 函数功能:实现OLED垂直滚动范围配置
** 参 数:1.toprow:设置滚动起始行
** 2.scrollrow:设置滚动行数
** 注意:toprow+scrollrow<64
** 函数返回:无
******************************************************/
void OLED_VerticalDisplay(u8 toprow,u8 scrollrow)
{
OLED_Write(0xA3,1);//设置垂直滚动范围命令
OLED_Write(toprow,1);//设置滚动起始行
OLED_Write(scrollrow,1);//设置滚动行数
}
关闭滚动, 按页清屏,更新显示原函数
/*****************************************************
** 函数名称:OledScrollStop
** 函数功能:关闭滚屏功能
******************************************************/
void OledScrollStop(void)
{
OLED_WR_Byte(DEACTIVATE_SCROLL,OLED_CMD);
}
/*
函数功能: 按页清屏 (0~7)
*/
void OLED_PageClear(u8 page)
{
u8 j;
OLED_WR_Byte(0xB0+page,OLED_CMD); //设置页地址
OLED_WR_Byte(0x10,OLED_CMD); //设置列高起始地址(半字节)
OLED_WR_Byte(0x00,OLED_CMD); //设置列低起始地址(半字节)
for(j=0;j<128;j++)
{
OLED_WR_Byte(0,OLED_DATA); //写数据
}
}
/*
函数功能:更新显存到OLED屏幕上
*/
void OLED_Refresh_Screen(void)
{
u8 page,list; //定义页地址和列地址
for(page=0;page<8;page++)
{
OLED_WR_Byte(0xB0+page,OLED_CMD); //设置页地址(0~7)
OLED_WR_Byte(0x00,OLED_CMD); //设置显示位置―列低地址
OLED_WR_Byte(0x10,OLED_CMD); //设置显示位置―列高地址
for(list=0;list<128;list++)
{
OLED_WR_Byte(OLED_GRAM[page][list],OLED_DATA);
}
}
memset(OLED_GRAM,0,sizeof(OLED_GRAM)); /*清空显存数组*/
}
关闭滚动函数修改
/*****************************************************
** 函数名称:OledScrollStop
** 函数功能:关闭滚屏功能
******************************************************/
void OledScrollStop(void)
{
OLED_Write(0x2E,1);//关闭滚动显示功能
}
按页清屏函数修改
/*
函数功能: 按页清屏 (0~7)
*/
void OLED_PageClear(u8 page)
{
u8 j;
OLED_Write(0xB0+page,1); //设置页地址
OLED_Write(0x10,1); //设置列高起始地址(半字节)
OLED_Write(0x00,1); //设置列低起始地址(半字节)
for(j=0;j<128;j++)
{
OLED_Write(0,0); //写数据
}
}
更新显示函数修改
/*
函数功能:更新显存到OLED屏幕上
*/
void OLED_Refresh_Screen(void)
{
u8 page,list; //定义页地址和列地址
for(page=0;page<8;page++)
{
OLED_Write(0xB0+page,1); //设置页地址(0~7)
OLED_Write(0x00,1); //设置显示位置―列低地址
OLED_Write(0x10,1); //设置显示位置―列高地址
for(list=0;list<128;list++)
{
OLED_Write(OLED_GRAM[page][list],0);
}
}
memset(OLED_GRAM,0,sizeof(OLED_GRAM)); /*清空显存数组*/
}
memset(OLED_GRAM,0,sizeof(OLED_GRAM)); /清空显存数组/这个函数是#include "string.h"这里面的一个函数,整句话的意思是,向OLED_GRAM数组里面写满0.
4.字符显示函数
基础的函数都移植完了,那么接下来就是显示的函数了,原函数这里就不放了。
原程序里他把中英文搞一块儿我觉得编程序的时候不太友好,给阉割了, 我给单独领出来了,这里只能显示些英文符号。
也不是说他那样干不好,不过一般我们用起来其实最多几个汉字,自己取一下模就行。
或者有兴趣的也可以按照他那样搞一个中文字库,哈哈,有搞好的千万@我一下。
我还是把他的原版程序给你们看看吧。
原程序
void OLED_DisplayString(u8 x,u8 y,u8 width,u8 height,u8 *str)
{
u8 addr=0,i;
u16 font=0;
while(*str!='