Files
CHJ/user/Device/oled.c
2026-03-20 21:16:58 +08:00

686 lines
15 KiB
C
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "../main/SystemInclude.h"
#include "../Device/oledfont.h"
// --- 1. 显存定义 ---
// 前台缓冲区:用于写入新的图形数据
u8 OLED_GRAM[144][8];
// 后台缓冲区:用于存储当前屏幕上实际显示的内容,用于对比差异
u8 OLED_GRAM_Back[144][8];
//反显函数
void OLED_ColorTurn(u8 i)
{
if(i==0)
{
OLED_WR_Byte(0xA6,OLED_CMD);//正常显示
}
if(i==1)
{
OLED_WR_Byte(0xA7,OLED_CMD);//反色显示
}
}
//屏幕旋转180度
void OLED_DisplayTurn(u8 i)
{
if(i==0)
{
OLED_WR_Byte(0xC8,OLED_CMD);//正常显示
OLED_WR_Byte(0xA1,OLED_CMD);
}
if(i==1)
{
OLED_WR_Byte(0xC0,OLED_CMD);//反转显示
OLED_WR_Byte(0xA0,OLED_CMD);
}
}
//延时
void IIC_delay(void)
{
u8 t=3;
while(t--);
}
//起始信号
void I2C_Start(void)
{
OLED_SDA_Set();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Clr();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
//结束信号
void I2C_Stop(void)
{
OLED_SDA_Clr();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Set();
}
//等待信号响应
void I2C_WaitAck(void) //测数据信号的电平
{
OLED_SDA_Set();
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
//写入一个字节
void Send_Byte(u8 dat)
{
u8 i;
for(i=0;i<8;i++)
{
if(dat&0x80)//将dat的8位从最高位依次写入
{
OLED_SDA_Set();
}
else
{
OLED_SDA_Clr();
}
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();//将时钟信号设置为低电平
dat<<=1;
}
}
//发送一个字节
//mode:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 mode)
{
I2C_Start();
Send_Byte(0x78);//写入设备地址
I2C_WaitAck();
if(mode){Send_Byte(0x40);}
else{Send_Byte(0x00);}
I2C_WaitAck();
Send_Byte(dat);
I2C_WaitAck();
I2C_Stop();
}
//开启OLED显示
void OLED_DisPlay_On(void)
{
OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
OLED_WR_Byte(0x14,OLED_CMD);//开启电荷泵
OLED_WR_Byte(0xAF,OLED_CMD);//点亮屏幕
}
//关闭OLED显示
void OLED_DisPlay_Off(void)
{
OLED_WR_Byte(0x8D,OLED_CMD);//电荷泵使能
OLED_WR_Byte(0x10,OLED_CMD);//关闭电荷泵
OLED_WR_Byte(0xAE,OLED_CMD);//关闭屏幕
}
/**
* @brief 差异刷新全屏(高效)
* @note 对比 OLED_GRAM 和 OLED_GRAM_Back仅刷新变化的字节
*/
void OLED_Refresh_Diff(void)
{
u8 i, n;
for(i=0; i<8; i++)
{
// 设置页地址
OLED_WR_Byte(0xb0+i, OLED_CMD);
OLED_WR_Byte(0x02, OLED_CMD); // Low Col Start Addr
OLED_WR_Byte(0x10, OLED_CMD); // High Col Start Addr
// 开启数据流传输
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
Send_Byte(0x40);
I2C_WaitAck();
for(n=0; n<128; n++)
{
// 对比前后台缓冲区
if(OLED_GRAM[n][i] != OLED_GRAM_Back[n][i])
{
// 如果数据变化,发送新数据到硬件
Send_Byte(OLED_GRAM[n][i]);
// 更新后台缓冲区,保持同步
OLED_GRAM_Back[n][i] = OLED_GRAM[n][i];
}
else
{
}
}
I2C_Stop();
}
}
// 【重新实现的差异刷新】:针对行的优化
void OLED_Refresh_Line_Diff(u8 line) // line 0-7
{
u8 n;
u8 x_start = 0;
u8 is_diff = 0;
// 1. 先扫描这一行有没有变化
for(n=0; n<128; n++)
{
if(OLED_GRAM[n][line] != OLED_GRAM_Back[n][line])
{
is_diff = 1;
break;
}
}
// 如果整行都没变直接返回省去所有I2C操作
if(!is_diff) return;
// 2. 设置页地址
OLED_WR_Byte(0xb0 + line, OLED_CMD);
OLED_WR_Byte(0x02, OLED_CMD);
OLED_WR_Byte(0x10, OLED_CMD);
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
Send_Byte(0x40);
I2C_WaitAck();
// 3. 逐字节对比并发送
for(n=0; n<128; n++)
{
if(OLED_GRAM[n][line] != OLED_GRAM_Back[n][line])
{
Send_Byte(OLED_GRAM[n][line]);
// 同步后台
OLED_GRAM_Back[n][line] = OLED_GRAM[n][line];
}
else
{
// 【注意】在连续I2C传输模式下如果不发送字节从机地址计数器不会前进
// 导致后续数据写入错位。
// 必须发送当前屏幕上的值(即 Back 缓存的值)来"占位"。
Send_Byte(OLED_GRAM_Back[n][line]);
}
I2C_WaitAck();
}
I2C_Stop();
}
// 兼容旧接口的全屏刷新
void OLED_Refresh(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
OLED_WR_Byte(0xb0+i,OLED_CMD);
OLED_WR_Byte(0x02,OLED_CMD);
OLED_WR_Byte(0x10,OLED_CMD);
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
Send_Byte(0x40);
I2C_WaitAck();
for(n=0;n<128;n++)
{
Send_Byte(OLED_GRAM[n][i]);
I2C_WaitAck();
}
I2C_Stop();
}
// 全屏刷新后,同步后台
memcpy(OLED_GRAM_Back, OLED_GRAM, sizeof(OLED_GRAM));
}
// 旧版刷新行接口(保留兼容)
void OLED_Refresh_Line(u8 line)
{
u8 n;
OLED_WR_Byte(0xb0 + line, OLED_CMD);
OLED_WR_Byte(0x02, OLED_CMD);
OLED_WR_Byte(0x10, OLED_CMD);
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
Send_Byte(0x40);
I2C_WaitAck();
for(n=0; n<128; n++)
{
Send_Byte(OLED_GRAM[n][line]);
I2C_WaitAck();
}
I2C_Stop();
// 同步该行后台
memcpy(OLED_GRAM_Back[0] + line*128, OLED_GRAM[0] + line*128, 128); // 简单处理,实际需按维度拷贝
// 上面的memcpy写法针对二维数组可能不直观改用循环:
for(n=0;n<128;n++) OLED_GRAM_Back[n][line] = OLED_GRAM[n][line];
}
//清屏函数
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
for(n=0;n<128;n++)
OLED_GRAM[n][i]=0;
}
// 这里不调用 OLED_Refresh由外部决定如何刷新
}
//画点
//x:0~127
//y:0~63
//t:1 填充 0,清空
//画点
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
u8 i,m,n;
i=y/8;
m=y%8;
n=1<<m;
if(t){OLED_GRAM[x][i]|=n;}
else
{
OLED_GRAM[x][i]=~OLED_GRAM[x][i];
OLED_GRAM[x][i]|=n;
OLED_GRAM[x][i]=~OLED_GRAM[x][i];
}
}
//画线
//x1,y1:起点坐标
//x2,y2:结束坐标
void OLED_DrawLine(u8 x1,u8 y1,u8 x2,u8 y2,u8 mode)
{
u16 t;
int xerr=0,yerr=0,delta_x,delta_y,distance;
int incx,incy,uRow,uCol;
delta_x=x2-x1; //计算坐标增量
delta_y=y2-y1;
uRow=x1;//画线起点坐标
uCol=y1;
if(delta_x>0)incx=1; //设置单步方向
else if (delta_x==0)incx=0;//垂直线
else {incx=-1;delta_x=-delta_x;}
if(delta_y>0)incy=1;
else if (delta_y==0)incy=0;//水平线
else {incy=-1;delta_y=-delta_x;}
if(delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴
else distance=delta_y;
for(t=0;t<distance+1;t++)
{
OLED_DrawPoint(uRow,uCol,mode);//画点
xerr+=delta_x;
yerr+=delta_y;
if(xerr>distance)
{
xerr-=distance;
uRow+=incx;
}
if(yerr>distance)
{
yerr-=distance;
uCol+=incy;
}
}
}
//x,y:圆心坐标
//r:圆的半径
void OLED_DrawCircle(u8 x,u8 y,u8 r)
{
int a, b,num;
a = 0;
b = r;
while(2 * b * b >= r * r)
{
OLED_DrawPoint(x + a, y - b,1);
OLED_DrawPoint(x - a, y - b,1);
OLED_DrawPoint(x - a, y + b,1);
OLED_DrawPoint(x + a, y + b,1);
OLED_DrawPoint(x + b, y + a,1);
OLED_DrawPoint(x + b, y - a,1);
OLED_DrawPoint(x - b, y - a,1);
OLED_DrawPoint(x - b, y + a,1);
a++;
num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
if(num > 0)
{
b--;
a--;
}
}
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//size1:选择字体 6x8/6x12/8x16/12x24
//mode:0,反色显示;1,正常显示
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size1,u8 mode)
{
u8 i,m,temp,size2,chr1;
u8 x0=x,y0=y;
if(size1==8)size2=6;
else size2=(size1/8+((size1%8)?1:0))*(size1/2); //得到字体一个字符对应点阵集所占的字节数
chr1=chr-' '; //计算偏移后的值
for(i=0;i<size2;i++)
{
if(size1==8)
{temp=asc2_0806[chr1][i];} //调用0806字体
else if(size1==12)
{temp=asc2_1206[chr1][i];} //调用1206字体
else if(size1==16)
{temp=asc2_1608[chr1][i];} //调用1608字体
else if(size1==24)
{temp=asc2_2412[chr1][i];} //调用2412字体
else return;
for(m=0;m<8;m++)
{
if(temp&0x01)OLED_DrawPoint(x,y,mode);
else OLED_DrawPoint(x,y,!mode);
temp>>=1;
y++;
}
x++;
if((size1!=8)&&((x-x0)==size1/2))
{x=x0;y0=y0+8;}
y=y0;
}
}
//显示字符串
//x,y:起点坐标
//size1:字体大小
//*chr:字符串起始地址
//mode:0,反色显示;1,正常显示
//显示字符串
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 size1,u8 mode)
{
while((*chr>=' ')&&(*chr<='~'))
{
OLED_ShowChar(x,y,*chr,size1,mode);
if(size1==8)x+=6;
else x+=size1/2;
chr++;
}
}
//m^n
u32 OLED_Pow(u8 m,u8 n)
{
u32 result=1;
while(n--) result*=m;
return result;
}
//显示数字
//x,y :起点坐标
//num :要显示的数字
//len :数字的位数
//size:字体大小
//mode:0,反色显示;1,正常显示
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size1,u8 mode)
{
u8 t,temp,m=0;
if(size1==8)m=2;
for(t=0;t<len;t++)
{
temp=(num/OLED_Pow(10,len-t-1))%10;
if(temp==0) OLED_ShowChar(x+(size1/2+m)*t,y,'0',size1,mode);
else OLED_ShowChar(x+(size1/2+m)*t,y,temp+'0',size1,mode);
}
}
//显示汉字
//x,y:起点坐标
//num:汉字对应的序号
//mode:0,反色显示;1,正常显示
void OLED_ShowChinese(u8 x,u8 y,u8 num,u8 size1,u8 mode)
{
u8 m,temp;
u8 x0=x,y0=y;
u16 i,size3=(size1/8+((size1%8)?1:0))*size1; //得到字体一个字符对应点阵集所占的字节数
for(i=0;i<size3;i++)
{
if(size1==16)
{temp=Hzk1[num][i];}//调用16*16字体
else if(size1==24)
{temp=Hzk2[num][i];}//调用24*24字体
else if(size1==32)
{temp=Hzk3[num][i];}//调用32*32字体
else if(size1==64)
{temp=Hzk4[num][i];}//调用64*64字体
else return;
for(m=0;m<8;m++)
{
if(temp&0x01)OLED_DrawPoint(x,y,mode);
else OLED_DrawPoint(x,y,!mode);
temp>>=1;
y++;
}
x++;
if((x-x0)==size1)
{x=x0;y0=y0+8;}
y=y0;
}
}
//num 显示汉字的个数
//space 每一遍显示的间隔
//mode:0,反色显示;1,正常显示
void OLED_ScrollDisplay(u8 num,u8 space,u8 mode)
{
u8 i,n,t=0,m=0,r;
while(1)
{
if(m==0)
{
OLED_ShowChinese(128,24,t,16,mode); //写入一个汉字保存在OLED_GRAM[][]数组中
t++;
}
if(t==num)
{
for(r=0;r<16*space;r++) //显示间隔
{
for(i=1;i<144;i++)
{
for(n=0;n<8;n++)
{
OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
}
}
OLED_Refresh();
}
t=0;
}
m++;
if(m==16){m=0;}
for(i=1;i<144;i++) //实现左移
{
for(n=0;n<8;n++)
{
OLED_GRAM[i-1][n]=OLED_GRAM[i][n];
}
}
OLED_Refresh();
}
}
//x,y起点坐标
//sizex,sizey,图片长宽
//BMP[]:要写入的图片数组
//mode:0,反色显示;1,正常显示
void OLED_ShowPicture(u8 x,u8 y,u8 sizex,u8 sizey,u8 BMP[],u8 mode)
{
u16 j=0;
u8 i,n,temp,m;
u8 x0=x,y0=y;
sizey=sizey/8+((sizey%8)?1:0);
for(n=0;n<sizey;n++)
{
for(i=0;i<sizex;i++)
{
temp=BMP[j];
j++;
for(m=0;m<8;m++)
{
if(temp&0x01)OLED_DrawPoint(x,y,mode);
else OLED_DrawPoint(x,y,!mode);
temp>>=1;
y++;
}
x++;
if((x-x0)==sizex)
{
x=x0;
y0=y0+8;
}
y=y0;
}
}
}
//OLED的初始化
void OLED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* 1. 配置GPIO1引脚4的参数 */ //P1.4SDA
GPIO_InitStruct.Pin = GPIO_PIN_4; // 选择引脚4需根据实际硬件修改
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;// 输出模式(非复用功能)
GPIO_InitStruct.Current = GPIO_CURRENT_4mA;// 驱动能力4mA根据负载调整
GPIO_InitStruct.Pull = GPIO_NOPULL;// 无上拉/下拉电阻
GPIO_InitStruct.SchmittTrigger = ENABLE;
GPIO_InitStruct.Alternate = GPIOx_AF_GPIO; // // AF为普通IO
/* 2. 应用GPIO配置 */
LHL_GPIO_Init(pGPIO1, &GPIO_InitStruct); // 初始化GPIO1
/* 配置GPIO2引脚0的参数 */ //P2.0SCL
GPIO_InitStruct.Pin = GPIO_PIN_0; // 选择引脚4需根据实际硬件修改
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;// 输出模式(非复用功能)
GPIO_InitStruct.Current = GPIO_CURRENT_4mA;// 驱动能力4mA根据负载调整
GPIO_InitStruct.Pull = GPIO_NOPULL;// 无上拉/下拉电阻
GPIO_InitStruct.SchmittTrigger = ENABLE;
GPIO_InitStruct.Alternate = GPIOx_AF_GPIO; // // AF为普通IO
LHL_GPIO_Init(pGPIO2, &GPIO_InitStruct); // 初始化GPIO2
/* 5. 配置GPIO1引脚6的参数 */ //P1.6RES
GPIO_InitStruct.Pin = GPIO_PIN_6; // 选择引脚4需根据实际硬件修改
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;// 输出模式(非复用功能)
GPIO_InitStruct.Current = GPIO_CURRENT_4mA;// 驱动能力4mA根据负载调整
GPIO_InitStruct.Pull = GPIO_NOPULL;// 无上拉/下拉电阻
GPIO_InitStruct.SchmittTrigger = ENABLE;
GPIO_InitStruct.Alternate = GPIOx_AF_GPIO; // // AF为普通IO
LHL_GPIO_Init(pGPIO1, &GPIO_InitStruct); // 初始化GPIO2
OLED_RES_Clr();
delay_ms(200);
OLED_RES_Set();
OLED_WR_Byte(0xAE,OLED_CMD); /*display off*/
OLED_WR_Byte(0x02,OLED_CMD); /*set lower column address*/
OLED_WR_Byte(0x10,OLED_CMD); /*set higher column address*/
OLED_WR_Byte(0x40,OLED_CMD); /*set display start line*/
OLED_WR_Byte(0xB0,OLED_CMD); /*set page address*/
OLED_WR_Byte(0x81,OLED_CMD); /*contract control*/
OLED_WR_Byte(0xcf,OLED_CMD); /*128*/
OLED_WR_Byte(0xA1,OLED_CMD); /*set segment remap*/
OLED_WR_Byte(0xA6,OLED_CMD); /*normal / reverse*/
OLED_WR_Byte(0xA8,OLED_CMD); /*multiplex ratio*/
OLED_WR_Byte(0x3F,OLED_CMD); /*duty = 1/64*/
OLED_WR_Byte(0xad,OLED_CMD); /*set charge pump enable*/
OLED_WR_Byte(0x8b,OLED_CMD); /* 0x8B 内供 VCC */
OLED_WR_Byte(0x33,OLED_CMD); /*0X30---0X33 set VPP 9V */
OLED_WR_Byte(0xC8,OLED_CMD); /*Com scan direction*/
OLED_WR_Byte(0xD3,OLED_CMD); /*set display offset*/
OLED_WR_Byte(0x00,OLED_CMD); /* 0x20 */
OLED_WR_Byte(0xD5,OLED_CMD); /*set osc division*/
OLED_WR_Byte(0x80,OLED_CMD);
OLED_WR_Byte(0xD9,OLED_CMD); /*set pre-charge period*/
OLED_WR_Byte(0x1f,OLED_CMD); /*0x22*/
OLED_WR_Byte(0xDA,OLED_CMD); /*set COM pins*/
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xdb,OLED_CMD); /*set vcomh*/
OLED_WR_Byte(0x40,OLED_CMD);
// 初始化时清空缓冲区
OLED_Clear();
memset(OLED_GRAM_Back, 0, sizeof(OLED_GRAM_Back));
// 初次全刷
OLED_Refresh();
OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/
}
/**
* @brief 在内存缓冲区中填充一个矩形区域(用于局部擦除)
* @param x1: 起始x坐标
* @param y1: 起始y坐标
* @param x2: 结束x坐标
* @param y2: 结束y坐标
* @note 此函数只修改内存中的OLED_GRAM不直接操作硬件
*/
void OLED_FillArea(u8 x1, u8 y1, u8 x2, u8 y2)
{
u8 x, y;
for(y=y1; y<y2; y++)
{
for(x=x1; x<x2; x++)
{
OLED_DrawPoint(x, y, 0); // 修改前台缓冲区
}
}
}
// 新增:填充矩形区域为黑色(用于绘制电量格)
// 参数x1,y1 起始坐标; x2,y2 结束坐标 (不包含x2,y2)
void OLED_FillRect(u8 x1, u8 y1, u8 x2, u8 y2)
{
u8 x, y;
for(y=y1; y<y2; y++)
{
for(x=x1; x<x2; x++)
{
OLED_DrawPoint(x, y, 0); // mode=1绘制黑色像素
}
}
}
// 新增:填充矩形区域为白色 (mode=0 代表画白色/清空)
void OLED_FillRect_White(u8 x1, u8 y1, u8 x2, u8 y2)
{
u8 x, y;
for(y = y1; y < y2; y++)
{
for(x = x1; x < x2; x++)
{
OLED_DrawPoint(x, y, 1); // mode=0绘制白色像素
}
}
}