#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<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;tdistance) { 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>=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>=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>=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.4(SDA) 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.0(SCL) 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.6(RES) 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(); // 拉低 RES 引脚 delay_ms(200); // 延时 200ms OLED_RES_Set(); // 拉高 RES 引脚,完成硬件复位 // --- OLED 初始化命令序列 --- // 1. 基础显示设置 OLED_WR_Byte(0xAE,OLED_CMD); // **[0xAE] 关闭显示** // 初始化过程中通常先关闭显示,配置完成后再开启。 // [可修改]:无特殊情况不建议修改顺序。 OLED_WR_Byte(0x02,OLED_CMD); // **[0x00~0x0F] 设置低列地址** // 设置显存列地址的低4位。这里设为 0x02。 // [可修改]:通常设为 0x00。设为 0x02 可能是为了微调画面水平位置。 OLED_WR_Byte(0x10,OLED_CMD); // **[0x10~0x1F] 设置高列地址** // 设置显存列地址的高4位。这里设为 0x10 (即地址0)。 // [可修改]:通常配合低列地址使用,一般不建议修改。 OLED_WR_Byte(0x40,OLED_CMD); // **[0x40~0x7F] 设置显示起始行** // 设置屏幕显示的起始行地址(垂直滚动用)。 // 0x40 代表从第 0 行开始显示。 // [可修改]:若需要垂直滚动画面,可修改此值 (0x40~0x7F)。 OLED_WR_Byte(0xB0,OLED_CMD); // **[0xB0~0xB7] 设置页地址** // 设置页地址模式下的页地址(0-7)。初始化时通常设为第0页。 // [可修改]:初始化设置,一般不改。 // 2. 亮度与对比度 (影响显示效果) OLED_WR_Byte(0x81,OLED_CMD); // **双字节命令:设置对比度** OLED_WR_Byte(0xcf,OLED_CMD); // 对比度值:0xCF (范围 0x00~0xFF) // **[可更改]**:数值越大越亮。 // 建议:根据环境光调整。若觉得太亮刺眼或想省电,可调小 (如 0x7F)。 // 文档默认复位值为 0x7F。 // 3. 扫描方向 (屏幕方向翻转) OLED_WR_Byte(0xA1,OLED_CMD); // **[0xA0/0xA1] 段重映射** // 0xA0: 列地址0映射到SEG0 (正常) // 0xA1: 列地址0映射到SEG127 (水平镜像翻转) // **[可更改]**:如果屏幕显示左右镜像了,改用 0xA0。 OLED_WR_Byte(0xA6,OLED_CMD); // **[0xA6/0xA7] 设置正常/反显** // 0xA6: 正常显示 (0灭1亮) // 0xA7: 反显 (0亮1灭) // **[可更改]**:可视需求改为 0xA7 实现反色效果。 // 4. 硬件驱动配置 (匹配屏幕分辨率 128x64) OLED_WR_Byte(0xA8,OLED_CMD); // **双字节命令:设置多路复用率** OLED_WR_Byte(0x3F,OLED_CMD); // 值:0x3F (十进制63) // 计算公式:MUX Ratio = 值 + 1。63+1=64。 // 因为屏幕是 64 行,**不可修改**。 // 5. 电源与电荷泵配置 (关键!决定屏幕能否点亮) OLED_WR_Byte(0xad,OLED_CMD); // **双字节命令:电荷泵设置** OLED_WR_Byte(0x8b,OLED_CMD); // 0x8B: 开启内部 DC-DC 升压 (内供 VCC) // 0x8A: 关闭内部升压 (需外部供高压 VCC) // **[不建议修改]**:必须开启 (0x8B) 屏幕才会亮。 OLED_WR_Byte(0x33,OLED_CMD); // **设置 VPP 电压 (针对特定驱动IC)** // 代码注释写的是 set VPP 9V。 // 0x30: ~7.4V, 0x31: ~8.0V, 0x32: ~8.4V, 0x33: ~9.0V // **[可更改]**:如果屏幕显示对比度不够或字体太淡,可尝试微调 (0x30~0x33)。 // 但过高电压可能影响寿命,建议参考文档推荐值。 OLED_WR_Byte(0xC8,OLED_CMD); // **[0xC0/0xC8] COM 输出扫描方向** // 0xC0: 正常模式 (从COM0扫到COM[N]) // 0xC8: 重映射模式 (从COM[N]扫到COM0,垂直翻转) // **[可更改]**:如果屏幕显示上下颠倒了,改用 0xC0。 OLED_WR_Byte(0xD3,OLED_CMD); // **双字节命令:设置显示偏移** OLED_WR_Byte(0x00,OLED_CMD); // 偏移值:0x00 // **[可更改]**:用于垂直平移显示区域。范围 0x00~0x3F。 OLED_WR_Byte(0xD5,OLED_CMD); // **双字节命令:设置时钟分频** OLED_WR_Byte(0x80,OLED_CMD); // 低4位:分频因子;高4位:振荡器频率。 // 值 0x80 表示分频比为 1。 // **[可更改]**:增大频率可提高刷新率,但可能导致显示不稳或功耗增加。 OLED_WR_Byte(0xD9,OLED_CMD); // **双字节命令:设置预充电周期** OLED_WR_Byte(0x1f,OLED_CMD); // 高4位:Phase 2; 低4位:Phase 1。 // **[可更改]**:影响像素点亮的充电时间。如果显示有拖影或亮度不均,可微调。 // 文档中其他示例代码常用 0x22 或 0xF1。 OLED_WR_Byte(0xDA,OLED_CMD); // **双字节命令:设置 COM 引脚配置** OLED_WR_Byte(0x12,OLED_CMD); // 0x12: Sequential COM pin config (适用于 128x64) // 0x02: Alternative COM pin config (适用于 128x32) // **[不可修改]**:这是由屏幕硬件结构决定的,128x64 必须用 0x12。 OLED_WR_Byte(0xdb,OLED_CMD); // **双字节命令:设置 VCOMH 电压倍率** OLED_WR_Byte(0x40,OLED_CMD); // 0x40: ~0.89 x VCC // **[可更改]**:影响“黑电平”电压。数值过低可能导致背景不够黑(发灰),过高可能导致对比度下降。 // 常用值:0x20, 0x30, 0x40。 // --- 结束初始化 --- OLED_Clear(); memset(OLED_GRAM_Back, 0, sizeof(OLED_GRAM_Back)); OLED_Refresh(); OLED_WR_Byte(0xAF,OLED_CMD); // **[0xAF] 开启显示** // 配置完成后点亮屏幕。 } /** * @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