基于STM32单片机模块练习——OLED模块
相关知识点
向OLED写一个字节
/**
* @brief I2C_WriteByte,向OLED寄存器地址写一个byte的数据
* @param addr:寄存器地址
* data:要写入的数据
* @retval 无
*/
void I2C_WriteByte(uint8_t addr,uint8_t data)
{
i2c_Start();//开启I2C总线
/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
i2c_SendByte(OLED_ADDRESS|OLED_I2C_WR);
/*等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* OLED器件无应答 */
}
i2c_SendByte(addr);//0x00表示发送命令,0x40表示发送数据
/*等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* OLED器件无应答 */
}
i2c_SendByte(data);//发送数据
/*等待ACK */
if (i2c_WaitAck() != 0)
{
goto cmd_fail; /* OLED器件无应答 */
}
/* 发送I2C总线停止信号 */
i2c_Stop();
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
/* 发送I2C总线停止信号 */
i2c_Stop();
}
OLED初始化函数
void OLED_Init(void)
{
Delay_s(1); // 1s,这里的延时很重要,上电后延时,没有错误的冗余设计
//等待SSD1306的上电初始化
WriteCmd(0xAE); //关闭有机发光二极管面板显示器
WriteCmd(0x20); //设置内存寻址模式
WriteCmd(0x10); //00,水平寻址模式;01,垂直寻址模式;10,页寻址模式;11,Invalid
WriteCmd(0xb0); //通过命令B0h至B7h设置目标显示位置的页面起始地址
WriteCmd(0xc8); //Set COM Output Scan Direction/一旦发出该命令,显示屏将显示
WriteCmd(0x00); //该命令为页面寻址模式下的显示数据内存指定8位列起始地址的低位半字节。列地址将随着每次数据访问而递增。
WriteCmd(0x10); //该命令为页面寻址模式下的显示数据内存指定8位列起始地址的高位半字节。列地址将随着每次数据访问而递增。
WriteCmd(0x40); //该命令设置显示起始行寄存器,通过选择0到63之间的一个值来确定显示内存的起始地址。
WriteCmd(0x81); //-该命令设置显示器的对比度(亮度)设置
WriteCmd(0xff); //亮度调节 0x00~0xff
WriteCmd(0xa1); //列地址127被映射到SEG0
WriteCmd(0xa6); //内存中正常显示(复位)0:显示面板中关闭1:显示面板中打开
WriteCmd(0xa8); //--set multiplex ratio(1 to 64)
WriteCmd(0x3F); //????????
WriteCmd(0xa4); //A4h命令从整个显示“开”阶段恢复显示。A5h命令强制整个显示器为“开”,不管显示数据内存的内容如何。
WriteCmd(0xd3); //这是一个双字节命令。第二个命令指定显示起始行到COM0~COM63之一的映射(假设COM0是显示起始行,则显示起始行寄存器等于0)。
WriteCmd(0x00); //-not offset
WriteCmd(0xd5); //设置显示时钟分频比/振荡器频率
WriteCmd(0xf0); //--set divide ratio????????
WriteCmd(0xd9); //该命令用于设置预充电周期的持续时间。间隔以DCLK数计算,其中复位等于2千位。
WriteCmd(0x22); //??????????
WriteCmd(0xda); //--set com pins hardware configuration?????
WriteCmd(0x12);//??????????
WriteCmd(0xdb); //设置VCOMH取消选择级别????
WriteCmd(0x20); //0x20,0.77xVcc?????????
WriteCmd(0x8d); //--set DC-DC enable电荷泵设置
WriteCmd(0x14); //启用电荷泵
WriteCmd(0xaf); //正常模式下显示开
OLED_Fill(0x00);//全屏灭
}
/**
* @brief WriteCmd,向OLED写入命令
* @param I2C_Command:命令代码
* @retval 无
*/
void WriteCmd(unsigned char I2C_Command)//写命令
{
I2C_WriteByte(0x00, I2C_Command); //0x00,表示发送的数据是命令
} //0x40,表示发送的是数据
使用前先进行设备的检测
/*
*********************************************************************************************************
* 函 数 名: OLED检测测试
* 功能说明: 检测I2C总线设备,实际是对OLED_CheckDevice()的封装
* 形 参:
* 返 回 值: 返回值 0 表示没有检测到OLED,返回1表示检测到OLED
*********************************************************************************************************
*/
uint8_t OLED_Test(void)
{
if (OLED_CheckDevice(OLED_ADDRESS) == 1)//返回值为1,表示没有从机设备应答
{
return 0;
}
else
{
return 1;
}
}
/*
*********************************************************************************************************
* 函 数 名: i2c_CheckDevice
* 功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
* 形 参:_Address:设备的I2C总线地址
* 返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t OLED_CheckDevice(uint8_t _Address)//_Address为从机设备地址
{
uint8_t ucAck;
i2c_Start(); /* 发送启动信号 */
i2c_SendByte(_Address|OLED_I2C_WR);/* 发送设备地址 */
ucAck = i2c_WaitAck(); /* 检测设备的ACK应答 */
i2c_Stop(); /* 发送停止信号 */
return ucAck;
}
全屏填充
/**
* @brief OLED_Fill,填充整个屏幕
* @param fill_Data:要填充的数据
* @retval 无
*/
void OLED_Fill(unsigned char fill_Data)//全屏填充
{
unsigned char m,n;
for(m=0;m<8;m++)
{
WriteCmd(0xb0+m); 通过命令B0h至B7h设置目标显示位置的页面起始地址
WriteCmd(0x00); //0 //该命令为页面寻址模式下的显示数据内存指定8位列起始地址的低位半字节。列地址将随着每次数据访问而递增。
WriteCmd(0x10); //128 //该命令为页面寻址模式下的显示数据内存指定8位列起始地址的高位半字节。列地址将随着每次数据访问而递增。
for(n=0;n<128;n++)
{
WriteDat(fill_Data);//0x00,清空屏幕;0xff,全屏点亮;
}
}
}
/**
* @brief WriteDat,向OLED写入数据
* @param I2C_Data:数据
* @retval 无
*/
void WriteDat(unsigned char I2C_Data)//写数据
{
I2C_WriteByte(0x40, I2C_Data); //0x40,表示发送的是数据
} //0x00,表示发送的数据是命令
显示字符串函数
/**
* @brief OLED_SetPos,设置光标
* @param x,光标x位置
* y,光标y位置
* @retval 无
*/
void OLED_SetPos(unsigned char x, unsigned char y) //设置起始点坐标
{
WriteCmd(0xb0+y);通过命令B0h至B7h设置目标显示位置的页面起始地址
WriteCmd(((x&0xf0)>>4)|0x10);//指定格式为:0x1X;该命令为页面寻址模式下的显示数据内存指定8位列起始地址的高4位。列地址将随着每次数据访问而递增。
WriteCmd((x&0x0f));//指定格式为:0x0X;该命令为页面寻址模式下的显示数据内存指定8位列起始地址的高4位。列地址将随着每次数据访问而递增。
}
**
* @brief OLED_ShowStr,显示codetab.h中的ASCII字符,有6*8和8*16可选择
* @param x,y : 起始点坐标(x:0~127, y:0~7);
* ch[] :- 要显示的字符串;
* TextSize : 字符大小(1:6*8 ; 2:8*16)列*行
* @retval 无
*/
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
unsigned char c = 0,i = 0,j = 0;
switch(TextSize)
{
case 1:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;//当前字符相对于' '的偏移量
if(x > 126)//因为列最大就是127,所以当x达到>126时,证明已经把这一页全部写完,该换页了
{
x = 0;
y++;//页面换到下一页
}
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(F6x8[c][i]);//[c]是当前字符在字库中所在的行数,因为大小是6*8的,所以有六个字节需要发送
x += 6;//因为一个字符占用6列,所以起始列地址应该在原来的基础上加6,准备发送下一个字符
j++;//字符串中的下一个字符
}
}break;
case 2:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 120)//留够写下最后一个字符的列数
{
x = 0;
y++;
}
OLED_SetPos(x,y);//先写这个字符的上半部分
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);//再写这个字符的下半部分
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);
x += 8;
j++;
}
}break;
}
}
在正常显示数据随机存取存储器读或写和页面寻址模式下,需要以下步骤来定义起始随机存取存储器访问指针位置:
■通过命令B0h到B7h设置目标显示位置的页面起始地址。
■通过命令00h-0Fh设置指针的起始列地址的低4位。
■通过命令10h~1Fh设置指针的起始列地址的高4位。
以上三个步骤,顺序可以随便调换
让OLED进入休眠
/**
* @brief OLED_OFF,让OLED休眠 -- 休眠模式下,OLED功耗不到10uA
* @param 无
* @retval 无
*/
void OLED_OFF(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X10); //关闭电荷泵
WriteCmd(0XAE); //OLED休眠
}
唤醒OLED
/**
* @brief OLED_ON,将OLED从休眠中唤醒
* @param 无
* @retval 无
*/
void OLED_ON(void)
{
WriteCmd(0X8D); //设置电荷泵
WriteCmd(0X14); //开启电荷泵
WriteCmd(0XAF); //OLED唤醒
}
显示汉字函数
/**
* @brief OLED_ShowCN,显示codetab.h中的汉字,16*16点阵
* @param x,y: 起始点坐标(x:0~127, y:0~7);
* N:汉字在codetab.h中的索引,也就是第几个汉字
* @retval 无
*/
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
{
unsigned char wm=0;
unsigned int adder=32*N;//写一个汉字需要32个字节
OLED_SetPos(x , y);//先写一个汉字的上半部分
for(wm = 0;wm < 16;wm++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
OLED_SetPos(x,y + 1);//再写一个汉字的下半部分
for(wm = 0;wm < 16;wm++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
}
//注意:此函数没有为显示下一个汉字做相应的列地址更换准备,需要在调用的时候,自行更换
for(i=0;i<4;i++)//测试显示中文
{
OLED_ShowCN(22+i*16,0,i);//22为起始列地址,i*16为更换列地址,为发送下一个汉字做准备
}
取模软件选项设置
显示一个字符函数
/**
* @brief OLED_ShowChar,显示codetab.h中的ASCII字符,有6*8和8*16可选择
* @param x,y : 起始点坐标(x:0~127, y:0~7);
* ch:- 要显示的字符;
* TextSize : 字符大小(1:6*8 ; 2:8*16)列*行
* @retval 无
*/
void OLED_ShowChar(unsigned char x, unsigned char y, unsigned char ch, unsigned char TextSize)
{
unsigned char c = 0,i = 0;
switch(TextSize)
{
case 1:
{
if(ch != '\0')
{
c = ch - 32;//当前字符相对于' '的偏移量
if(x > 126)//因为列最大就是127,所以当x达到>126时,证明已经把这一页全部写完,该换页了
{
x = 0;
y++;//页面换到下一页
}
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(F6x8[c][i]);//[c]是当前字符在字库中所在的行数,因为大小是6*8的,所以有六个字节需要发送
}
}break;
case 2:
{
if(ch != '\0')
{
c = ch - 32;
if(x > 126)//留够写下最后一个字符的列数
{
x = 0;
y++;
}
OLED_SetPos(x,y);//先写这个字符的上半部分
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);//再写这个字符的下半部分
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);
}
}break;
}
}
显示一个数字函数
//m^n函数
uint32_t oled_pow(uint8_t m,uint8_t n)
{
uint32_t result=1;
while(n--)result*=m;
return result;
}
//x,y :起点坐标
//len :数字的位数
//size:字体大小
//num:数值(0~4294967295);
void OLED_ShowNum(uint8_t x,uint8_t y,uint32_t num,uint8_t len,uint8_t size)
{
uint8_t t,temp;
uint8_t enshow=0;
for(t=0;t<len;t++)//一位一位地分步发送这个数字
{
temp=(num/oled_pow(10,len-t-1))%10;//将这个数字拆分开.比如:23拆成2 和 3 ;如果将10换成2,那么就可以把数字以二进制的形式显示;如果len的长度大于实际的长度,比如输入66,len却是3,那么函数把它拆分成0 6 6,依次发送
if(enshow==0&&t<(len-1))//
{
if(temp==0)//由于len的输入值大于所要显示的数字的实际位数,那么数字前面会多出0,这条语句就会把0给显示为“ ”
{
switch(size)//按显示的大小尺寸进行选择
{
case 1 : OLED_ShowChar((x+6*t),y,' ',size);break;//6*8
case 2 : OLED_ShowChar((x+8*t),y,' ',size);break;//8*16
}
continue;
}else enshow=1; //如果第一个需要显示的字符不是0的话,那么证明接下来的所有数字都是有效的,都不需要这个if语句来显示‘ ’
}
switch(size)
{
case 1 : OLED_ShowChar((x+6*t),y,(temp + '0'),size);;break;//6*8
case 2 : OLED_ShowChar((x+8*t),y,(temp + '0'),size);;break;//8*16
}
}
}
显示图片函数(有待研究)
/**
* @brief OLED_DrawBMP,显示BMP位图
* @param x0,y0 :起始点坐标(x0:0~127, y0:0~7);
* x1,y1 : 起点对角线(结束点)的坐标(x1:1~128,y1:1~8)
* @retval 无
*/
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0)
y = y1/8;
else
y = y1/8 + 1;
for(y=y0;y<y1;y++)
{
OLED_SetPos(x0,y);
for(x=x0;x<x1;x++)
{
WriteDat(BMP[j++]);
}
}
}
拓展
一个万能的显示十进制数字的函数(可显示负数、整数、小数)
话不多说,上代码。
void OLED_ShowNum(uint8_t x,uint8_t y,float num,uint8_t int_len,uint8_t f_len,uint8_t size)
{
uint8_t t,temp;
uint8_t enshow=0;
int int_temp,f_temp;
//*******只有输入负数的情况下才显示负号,否则显示空格********
if(num < 0)
{
num = 0 - num;//将负数取反,变为正数,接下来显示它的绝对值
switch(size)
{
case 1 : OLED_ShowChar(x,y,'-',size);break;//6*8
case 2 : OLED_ShowChar(x,y,'-',size);break;//8*16
}
}
//**********************************************************
int_temp = (int)num;//取这个数字的整数部分
//**********************这一块部分与普通显示数字的部分相同**************************
for(t=0;t<int_len;t++)//一位一位地分步发送这个数字
{
temp=(int_temp/oled_pow(10,int_len-t-1))%10;//将这个数字拆分开.比如:23拆成2 和 3
if(enshow==0&&t<(int_len-1))
{
if(temp==0)
{
switch(size)
{
case 1 : OLED_ShowChar((x+6*(t+1)),y,' ',size);break;//6*8
case 2 : OLED_ShowChar((x+8*(t+1)),y,' ',size);break;//8*16
}
continue;
}else enshow=1;
}
switch(size)
{
case 1 : OLED_ShowChar((x+6*(t+1)),y,(temp + '0'),size);;break;//6*8
case 2 : OLED_ShowChar((x+8*(t+1)),y,(temp + '0'),size);;break;//8*16
}
}
//*****************************************************************************
if(f_len != 0)//如果希望显示小数部分,那么就先显示一个‘.’
{
switch(size)
{
case 1 : OLED_ShowChar((x+6*(int_len+1)),y,'.',size);;break;//6*8
case 2 : OLED_ShowChar((x+8*(int_len+1)),y,'.',size);;break;//8*16
}
}
f_temp = (int)((num - int_temp)*(oled_pow(10,f_len)));//将小数部分取出,并扩大为整数,方便显示
for(t = 0; t < f_len; t++)//将小数部分一位一位的显示
{
temp=(f_temp/oled_pow(10,f_len-t-1))%10;//拆分为单个数字
switch(size)
{
case 1 : OLED_ShowChar((x+6*(t+int_len+2)),y,(temp + '0'),size);;break;//6*8
case 2 : OLED_ShowChar((x+8*(t+int_len+2)),y,(temp + '0'),size);;break;//8*16
}
}
}