DSP28335驱动OLED12864:从软件模拟IIC到界面显示实战
1. DSP28335与OLED12864的硬件连接基础第一次用DSP28335驱动OLED12864屏幕时最让我头疼的就是硬件连接问题。市面上常见的0.96寸OLED模块有两种引脚排列版本VCC和GND的位置居然是相反的我当年就因为这个烧坏过一块屏幕现在想起来还肉疼。这里分享几个硬件连接的关键细节GPIO32和GPIO33是DSP28335上最常用的软件IIC引脚组合实测稳定性很好。接线时要注意OLED模块的VCC接3.3V千万别接5VGND接地SCL接GPIO32SDA接GPIO33。有些模块会标注RES和DC引脚但在IIC模式下这两个引脚可以悬空不接。DSP28335的系统时钟高达150MHz直接驱动IIC设备会出问题。我在调试时发现如果不加延时屏幕根本不会有任何反应。后来用逻辑分析仪抓波形才发现DSP的IO翻转速度太快OLED根本来不及响应。这就是为什么后面所有GPIO操作都要加3us左右的延时。2. 软件模拟IIC的完整实现2.1 GPIO初始化配置先来看GPIO的初始化代码这里有几个容易踩坑的地方#define OLED_SCLK_Clr() GpioDataRegs.GPBCLEAR.bit.GPIO321 #define OLED_SCLK_Set() GpioDataRegs.GPBSET.bit.GPIO321 #define OLED_SDIN_Clr() GpioDataRegs.GPBCLEAR.bit.GPIO331 #define OLED_SDIN_Set() GpioDataRegs.GPBSET.bit.GPIO331 void Gpio_Init() { EALLOW; // 必须的寄存器保护 SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK 1; // 开启GPIO时钟 // GPIO32(SCL)配置 GpioCtrlRegs.GPBPUD.bit.GPIO32 0; // 启用上拉 GpioCtrlRegs.GPBDIR.bit.GPIO32 1; // 输出模式 GpioCtrlRegs.GPBMUX1.bit.GPIO32 0; // GPIO功能 GpioCtrlRegs.GPBQSEL1.bit.GPIO32 3; // 异步模式 // GPIO33(SDA)配置同上 GpioCtrlRegs.GPBPUD.bit.GPIO33 0; GpioCtrlRegs.GPBDIR.bit.GPIO33 1; GpioCtrlRegs.GPBMUX1.bit.GPIO33 0; GpioCtrlRegs.GPBQSEL1.bit.GPIO33 3; EDIS; // 关闭寄存器保护 }特别注意GpioCtrlRegs.GPBQSEL1的配置必须设为3异步模式否则在150MHz主频下会出现信号同步问题。我曾经因为这个配置错误调试了一整天。2.2 IIC时序的软件模拟完整的IIC通信需要实现起始信号、停止信号、数据发送和应答检测。这里给出经过实际验证的代码void IIC_Start() { OLED_SDIN_Set(); OLED_SCLK_Set(); DELAY_US(5); // 必须的延时 OLED_SDIN_Clr(); DELAY_US(6); OLED_SCLK_Clr(); } void Write_IIC_Byte(unsigned char IIC_Byte) { unsigned char i; for(i0;i8;i) { OLED_SCLK_Clr(); if(IIC_Byte 0x80) OLED_SDIN_Set(); else OLED_SDIN_Clr(); IIC_Byte 1; DELAY_US(2); OLED_SCLK_Set(); DELAY_US(2); OLED_SCLK_Clr(); DELAY_US(2); } } void IIC_Stop() { OLED_SCLK_Clr(); OLED_SDIN_Clr(); DELAY_US(2); OLED_SCLK_Set(); DELAY_US(6); OLED_SDIN_Set(); DELAY_US(6); }实测发现DELAY_US(2)是最稳定的延时参数小于2us会导致通信失败。每个时钟周期(SCL高低电平变化)总共需要约6us的延时。3. OLED屏幕初始化与显示控制3.1 屏幕初始化序列OLED初始化需要发送一系列配置命令这些命令在数据手册中都能找到。下面是我优化过的初始化函数void OLED_Init() { OLED_WR_Byte(0xAE,OLED_CMD); // 关闭显示 OLED_WR_Byte(0xD5,OLED_CMD); // 设置时钟分频 OLED_WR_Byte(0x80,OLED_CMD); // 建议值 OLED_WR_Byte(0xA8,OLED_CMD); // 设置复用率 OLED_WR_Byte(0x3F,OLED_CMD); // 1/64 duty OLED_WR_Byte(0xD3,OLED_CMD); // 设置显示偏移 OLED_WR_Byte(0x00,OLED_CMD); // 无偏移 OLED_WR_Byte(0x40,OLED_CMD); // 设置起始行 OLED_WR_Byte(0x8D,OLED_CMD); // 电荷泵设置 OLED_WR_Byte(0x14,OLED_CMD); // 启用电荷泵 OLED_WR_Byte(0x20,OLED_CMD); // 内存地址模式 OLED_WR_Byte(0x00,OLED_CMD); // 水平地址模式 OLED_WR_Byte(0xA1,OLED_CMD); // 段重映射 OLED_WR_Byte(0xC8,OLED_CMD); // 输出扫描方向 OLED_WR_Byte(0xDA,OLED_CMD); // COM引脚配置 OLED_WR_Byte(0x12,OLED_CMD); // 备用配置 OLED_WR_Byte(0x81,OLED_CMD); // 对比度控制 OLED_WR_Byte(0xCF,OLED_CMD); // 对比度值 OLED_WR_Byte(0xD9,OLED_CMD); // 预充电周期 OLED_WR_Byte(0xF1,OLED_CMD); // 推荐值 OLED_WR_Byte(0xDB,OLED_CMD); // VCOMH设置 OLED_WR_Byte(0x40,OLED_CMD); // 推荐值 OLED_WR_Byte(0xA4,OLED_CMD); // 显示全部打开 OLED_WR_Byte(0xA6,OLED_CMD); // 正常显示 OLED_WR_Byte(0xAF,OLED_CMD); // 开启显示 }特别注意0xA1和0xC8这两个命令它们控制显示方向。如果发现显示内容上下或左右反了调整这两个参数即可。3.2 字符显示的实现显示字符需要先设置光标位置然后发送字符点阵数据。这里给出16x16字符显示的实现void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size) { unsigned char cchr- ; // 计算字库偏移量 if(x127) return; // 边界检查 if(y7) return; OLED_Set_Pos(x,y); for(u8 i0;i8;i) OLED_WR_Byte(F8X16[c*16i],OLED_DATA); OLED_Set_Pos(x,y1); for(u8 i0;i8;i) OLED_WR_Byte(F8X16[c*16i8],OLED_DATA); }F8X16是预先定义好的16x8字体点阵数组。实际使用时发现OLED的Y坐标是以页(8个像素)为单位的所以y值范围是0-7每个y值对应屏幕上的8行像素。4. 实际应用中的优化技巧4.1 显示刷新优化直接刷新整个屏幕会导致明显的闪烁。我采用的优化方案是建立显示缓冲区数组所有显示操作先在缓冲区完成定时全屏刷新时只更新有变化的部分u8 oled_buffer[128][8]; // 显示缓冲区 void OLED_Refresh() { for(u8 y0;y8;y){ OLED_Set_Pos(0,y); for(u8 x0;x128;x){ OLED_WR_Byte(oled_buffer[x][y],OLED_DATA); } } }4.2 多级菜单实现在项目中经常需要实现多级菜单系统。我的做法是定义菜单结构体数组使用当前菜单索引变量根据按键输入切换菜单typedef struct{ u8 current; u8 parent; char text[16]; void (*action)(void); }MenuItem; MenuItem menu[10] { {0,0,Main Menu,NULL}, {1,0,Settings,NULL}, {2,1,Brightness,SetBrightness}, // 更多菜单项... }; void ShowMenu(u8 index) { OLED_Clear(); OLED_ShowString(0,0,menu[index].text,16); // 显示其他菜单项... }4.3 低功耗处理在电池供电应用中可以通过以下方式降低功耗动态调整屏幕亮度空闲时关闭显示降低刷新频率void OLED_PowerSave(u8 enable) { if(enable){ OLED_WR_Byte(0xAE,OLED_CMD); // 关闭显示 OLED_WR_Byte(0x8D,OLED_CMD); // 关闭电荷泵 OLED_WR_Byte(0x10,OLED_CMD); // 深睡眠模式 }else{ OLED_Init(); // 重新初始化 } }在调试过程中建议先用LED指示灯指示程序运行状态再逐步添加OLED显示功能。当遇到显示异常时先用逻辑分析仪检查IIC信号波形确认时序正确后再排查其他问题。