#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(); 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