本文记录了一个英语六级压线过的工业程序员,如何用三件套(翻译+示波器+玄学改参)征服全英文datasheet,最终在老板期待的目光中让OLED显示出第一个汉字—“'艹”的完整过程。
文章标题
- 1 “菜鸟程序员”的“摆烂”哲学
- 2 从时序图到代码:SPI党对8080接口的屈服
- 3 CV大法好?手册初始化代码的“甜蜜陷阱”
- 4 从雪花到空白:被遗忘的写RAM命令引发的血案
- 5 从ASCII到汉字:一个OLED码农的造字运动
- 6 开发总结:学会了很多,但下次别学了
1 “菜鸟程序员”的“摆烂”哲学
就在前几天,老板神秘兮兮地扔给我一块裸屏:“这可是高端货,工业级OLED!”我接过来一看,心凉了半截——没有配套PCB,没有转接板,连个螺丝孔都没有,就一块光秃秃的玻璃片,价格标签上的数字倒是格外醒目。
作为资深“摆烂”程序员,我的标准开发流程向来简单粗暴:买模块→要例程→复制粘贴→完工收钱。但这次,当我满怀期待地向供应商索要参考代码时,对方热情地发来了两个文件——SSD1322-Revision 0.10 to Univision.pdf和GME240128-01.pdf,全是英文的,连个"Hello World"的示例都没有!
[我]:您好,请问有参考代码吗
[商家]:有的(啪地甩过来两个PDF)
[我]:好的,谢谢(直接两眼一黑)
不死心的我又联系了五六个供应商,结果收到的回复出奇地一致:“资料已发,请查收附件”,点开一看,还是那两份熟悉的手册。那一刻我终于明白,这次是真的要自己动手写驱动了——对于一个连datasheet目录都懒得看的人来说,这简直是职场生涯的最大挑战!
2 从时序图到代码:SPI党对8080接口的屈服
OK,事到如今只能靠自己了,我先打开了来自硬件同事的原理图,仔细对照英文手册看了一下,硬件设计上使用的是8080接口,我的内心是崩溃的——这都2025年了,为什么不用SPI!(摔)。但抱怨归抱怨,活还是要干的。仔细研究了下关键引脚:
引脚名称 | 功能描述(翻译成人话) |
---|---|
E(RD) | 在8080模式下,该引脚接收读信号,当该引脚被拉低且芯片被选择时,将启动读操作。 |
R/W(WR) | 在8080模式下,该引脚作为写输入,当该引脚被拉低且芯片被选择时,启动写操作。 |
DC | 该引脚是数据/指令控制引脚,与微控制器相连。当该引脚被拉高时,D0到D7中的内容将被解读为数据。当该引脚被拉低时,D0到D7中的内容将被解读为指令。 |
CS | 该引脚是连接到MCU的芯片选择输入。只有当该引脚被拉低时,芯片才在MCU通信中启用。 |
RES | 该引脚为复位信号输入。当该引脚被拉低时,芯片将执行初始化操作。在正常工作期间,请保持该引脚悬空或高电平。 |
OK,了解了这些功能之后我们看一下时序图,这是我们程序设计的关键!
这是一个写的时序图,作为显示器件,很少会用到读功能,所以我们只分析写时序,大概是这么个流程顺序
- 先给DC引脚"打暗号"(高低电平决定发数据还是指令)
- 把CS引脚按在地上(拉低表示"我要开始表演了")
- 在D0~D7上摆好"八仙过海"(并行数据必须同时到位)
- 给WR引脚来个"摸头杀"(拉低再拉高完成写入)
正当我感叹英文手册看得眼冒金星时,突然发现附录里居然有初始化代码!这感觉就像在泡面桶底发现了“再来一桶”——直到后来我才明白,这个“惊喜”其实是厂商埋的雷…
3 CV大法好?手册初始化代码的“甜蜜陷阱”
看到原理图的瞬间我直接破防了——PB2这不是BOOT1引脚吗?!(设计同事你认真的吗.jpg)赶紧翻开芯片手册查证,结果发现:只要BOOT0老实接地,BOOT1确实可以“改行”当普通IO用,连特殊配置都不需要。行吧,硬件大佬永远是对的…
接下来就是见证玄学的时刻——手搓8080时序!经过反复研读那张堪比藏宝图的时序图,终于写出了灵魂写命令函数:
/*
*==============================================================================
*函数名称:Med_Oled_WriteCmd
*函数功能:OLED写命令函数
*输入参数:cmd - 要写入的命令(1字节)
*返回值:无
*备 注:严格遵循8080接口时序,延时满足SSD1322规格书要求
*==============================================================================
*/
void Med_Oled_WriteCmd (u8 cmd)
{
OLED_DC = 0; // 命令模式
OLED_CS = 0; // 片选使能
// 设置好数据
OLED_RW = 0; // WR 拉低(开始写周期)
delay_us(1); // 保持低电平时间(>50ns)
OLED_RW = 1; // WR 上升沿(锁存数据)
OLED_CS = 1; // 片选禁用
}
OK,设计完基本的发送数据/命令函数之后,我们就开始设计初始化函数,刚好手册给出来了!直接开启CV大法,虽然看不懂每个寄存器在搞什么行为艺术,但手册总不会骗人吧?
通电瞬间——亮了!虽然满屏雪花像极了90年代电视机,但至少证明芯片还活着!此刻的我仿佛已经看到胜利曙光!只要再加个清屏函数,就能…等等,这个雪花怎么清不掉?!
4 从雪花到空白:被遗忘的写RAM命令引发的血案
当我终于搞明白SSD1322的像素结构时,差点把咖啡喷在手册上——这货居然用4bit表示一个像素,而且最大只能控制120×128区域!而我手上的屏幕是240×128
这意味着我至少需要分上下两个区域控制,也就是分成两个60*128,上下两部分,OK,我们来设计一下清屏函数
void Med_Oled_Clear(u8 color)
{
u8 x, y;
/* 设置显示范围:240列(60地址×4像素) × 128行 */
Med_Oled_SetColumnAddress(0, 59); // 设置列地址范围0-59(对应240像素)
Med_Oled_SetRowAddress(0, 127); // 设置行地址范围0-127(128行)
/* 全屏填充 */
for(y = 0; y < 128; y++) // 行循环
{
for(x = 0; x < 60; x++) // 列循环(60地址×4像素=240列)
{
/*
* 写入格式说明:
* - 每个字节包含2个相同4bit像素
* - 例如color=0xF → 写入0xFF
* - 60次写入=120字节=240像素
*/
}
}
}
信心满满地烧录测试…结果屏幕依然倔强地展示着它的雪花艺术!啊?还是满屏雪花点!此时我开始觉得有点不对劲,不得不开启“侦探模式”,然后开始对照手册疯狂翻,硬着头皮去对照寄存器表挨个查看初始化程序中的配置!
终于!功夫不负有心人!我发现初始化程序里却掉了关键一步,开启写RAM命令!
Med_Oled_WriteCmd(0x5C); // 开启RAM写入
终于!雪花屏变成了期待已久的"黑洞屏"!这感觉就像终于让傲娇的OLED开口说了第一句人话…
5 从ASCII到汉字:一个OLED码农的造字运动
当清屏函数终于驯服了那块倔强的OLED,我以为胜利在望——直到意识到还要手搓字库、字符显示、字符串显示,甚至汉字显示!这感觉就像好不容易造好了笔,却发现还得自己发明文字系统…(╯°□°)╯
转机出现在我掘地三尺找到的某商家SPI参考代码!虽然通信协议不同,但字库设计和显示逻辑简直就是救命稻草。
由于初始化配置了列行式+从下到上扫描+COM分割,连显示8×16字符都要分成上下两场“演出”,先显示上半部分8×8,再蹦跶到下半部分8×8
// 伪代码:分裂的字符人生
void ShowChar(u8 x, u8 y, char c)
{
ShowUpperPart(x, y, c); // 上半场
ShowLowerPart(x, y+8, c); // 下半场
}
好不容易搞定基础字符,主管却表示:“这字小得要用显微镜看!”,于是开启了32×64大字模式:重新取模32*64点阵,分8次处理数据块。
最后是移植汉字显示函数,商家给出的参考代码在调用汉字显示函数时传入的是指针
// 原版:每次调用都要算偏移量
OLED_ShowCHinese(10, 0, HZ+32*5); // 显示第5个汉字
我的优化版让函数自己做数学题
// 进化版:传入索引自动计算
void ShowChinese(u8 x, u8 y, u8 index)
{
u8 *font_ptr = HZ + index * 32; // 自动定位字库
// ...显示逻辑...
}
// 调用时清爽如风:
ShowChinese(10, 0, 5); // 直接传序号!
6 开发总结:学会了很多,但下次别学了
看着终于正常工作的OLED屏幕,我的心情复杂得像这个4bit灰度显示——既有完成挑战的淡淡成就感,更多的是“再也不想碰”的强烈解脱感。
这场底层驱动开发确实学到了8080时序、显存管理、字库设计…但下次如果还有选择,我一定选带现成驱动的TFT屏或者商家提供完整SDK的OLED!
作为一个资深“摆烂”的开发者,我认为能用现成轮子就不要自己造!能专注业务层就不要碰底层!除非…老板给的实在太多了(苦笑)
最后声明:本文来自一个被硬件折磨到摆烂的软件工程师,所有“浪费生命”的吐槽都是——
while(1)
{
printf("个人观点,大佬轻喷\n");
delay(1000);
}