HT1621段码屏逐段扫描测试:RAM映射与GPIO时序驱动实战
简介:面向嵌入式开发者的HT1621液晶驱动测试文件,聚焦LCD段码与字符显示验证。HT1621是常用于电子表、小型仪表的点阵LCD控制芯片,开发者可利用该头文件中的函数声明,快速完成初始化、清屏、写入点、设置显示区域等操作,并针对每个独立段进行点亮/熄灭测试,再通过发送字符代码验证预定义字符与自定义字符的显示效果,从而确认LCD硬件与驱动逻辑均无异常。压缩包体积仅1KB,内含一个头文件(.h),声明了HT1621控制所需的基本函数接口,适合直接放入单片机工程引用,免去重复编写底层驱动。目前已有78人学习,适用于正在调试HT1621驱动LCD或希望深入理解点阵液晶控制流程的中初级嵌入式开发者。借助该头文件中的接口,可直观掌握段式LCD的逐段控制方法、字符编码规则及常见显示故障排查思路,为相关产品开发打下基础。
1. 拿到“LCD.rar_HT1621测试每段LCD”时,你要面对的是什么
拿到一个名为“LCD.rar_HT1621测试每段LCD”的压缩包,意味着你手里十有八九是一块段码液晶屏、一颗 HT1621 驱动芯片,以及一段还没跑通的逐段点亮测试程序。这类包在面板厂、家电控制器产线、维修站里流传最广,里面通常是一块 32 SEG × 4 COM 的段码屏,配上几句逐段扫描代码和一张模糊的段位图。你要做的事,不是把整屏点亮就收工,而是把每一段、每一根 COM/SEG 交叉点单独点亮、单独熄灭,确认玻璃、绑定、驱动芯片、固件四条链路没有一处断点。
这里有个反直觉的结论:逐段测试段码屏,比点亮一块点阵屏更折磨人。点阵屏的坐标和屏幕位置是线性对应的,写第 100 个像素,眼睛能猜到它在哪;段码屏不是,RAM 里的一个 bit 和屏幕上那个“8”字的一横,中间隔着 COM 扫描顺序、SEG 走线、玻璃内部 ITO 布线,甚至隔着屏厂画图时的命名习惯。你写一个地址,屏幕亮起来的可能是跟你的预期差了十万八千里的那一段。这篇文章要讲的,就是把这条链路从黑匣子变成一张可以对着看的表。
下面的内容适合三类人:刚接手段码屏项目的嵌入式工程师,想把半成品测试程序改成能上产线的工装固件的开发者,以及手上有一堆来路不明段码屏、需要快速判断好坏的人。我会从 HT1621 的 RAM 映射讲起,然后给一份能直接抄的逐段扫描代码,最后把调试里最容易翻车的几个现象和解决路径摆出来。
2. HT1621 的 RAM 映射:每一段在芯片里到底住哪
段码屏和点阵屏的本质区别,在于它的电极不是像素矩阵,而是预先在玻璃上走好的几十条 ITO 线段。驱动芯片做的事情,不是告诉屏幕“哪个点亮”,而是给每一段对应的 COM 和 SEG 电极之间加上一个交变电压。HT1621 就是干这个的:芯片内部有一块小 RAM,RAM 里每一位对应一个 COM/SEG 交叉点,程序往某个 bit 写 1,对应的段就进入点亮状态;写 0,段就熄灭。所谓“逐段测试”,本质上就是把这个 RAM 空间穷举一遍——每一位都单独写 1、再写 0,肉眼确认屏幕上的反应。
2.1 三条线驱动的串行接口:CS、WR、DATA 的分工
HT1621 的接口比 I2C 和 SPI 都原始,一共就三条线:CS(片选)、WR(写时钟)、DATA(数据)。没有地址线,也没有单独的数据总线,所有命令、地址、数据都是一个 bit 一个 bit 从 DATA 线上送进去的。CS 拉低表示一次通信开始,WR 每次上升沿锁存 DATA 线上的一个 bit,等所有位送完,CS 拉高,命令生效。时序要求不苛刻,但有一点要注意:数据必须在 WR 上升沿之前稳定,很多初次调试的人在这里翻车,把建立时间留得太短,导致整包数据错位。
这套接口有两种写法。一种是完全模拟时序,用 GPIO 逐 bit 翻转 WR;另一种是把 MCU 的硬件 SPI 配成 Mode0,用 SCK 当 WR,MOSI 当 DATA。我一般推荐前者,因为 HT1621 的速率上限不高,GPIO 模拟完全够用,而且调试时你能用示波器一根线一根线地查;硬件 SPI 省不了多少 CPU,反而会引入极性配置的额外变量。芯片手册里给出的典型工作时序,写时钟高电平持续时间在微秒级就能稳定跑,这意味着普通 8 位单片机的 GPIO 翻转速度都绰绰有余。
命令格式分两种:写命令和写数据。写命令是开头 3 个 bit 作为标志,后面跟命令码和参数;写数据是开头 101 标志,后跟 6 位 RAM 地址,再跟 4 位或 8 位数据。具体命令码每个屏厂给的参考代码不一定一样,说明书也有好几个版本,但大类逃不出系统配置、系统使能、LCD 配置、写数据这四类。
| 命令类别 | 作用 | 常见参数点 |
|---|---|---|
| 系统配置 | 关/开系统、选择振荡源 | 使用内部 RC 振荡还是外部时钟 |
| 系统使能 | 偏压选择、COM 个数设置 | 1/2 或 1/3 bias,COM 数按屏的尺寸选 |
| LCD 配置 | 开/关 LCD 输出 | 必须在系统配置之后执行 |
| 写数据 | 往 RAM 写入段位状态 | 6 位地址 + 4/8 位数据 |
2.2 RAM 的点位与地址:先建一张你自己的段位表
HT1621 的 RAM 规模取决于芯片型号和配置,常见的是 32 SEG × 4 COM,也就是 128 个 bit。每一个 bit 的内存地址和屏幕位置不是数字上直接对应的,中间隔着芯片内部扫描顺序。比如地址 0 的 bit0 可能对应屏幕最左上角那个数字的 A 段,也可能对应最右下角的某个图标段。没有任何办法能从型号推导出来,唯一可靠的做法是:一块已知良好的屏,接好后一次点亮一个 bit,肉眼记录下“地址 + bit → 屏幕位置”的对应关系,做成一张段位表。
这张表是后续所有工作的地基。我见过不少人跳过这一步,直接拿参考程序的段位表套自己的屏,结果 7 段数码管数字乱跳、小数点出现在奇怪的位置,最后花一下午才发现是表对不上。建表的方法很简单:写一个循环,从地址 0 开始,每次只写一个 bit,点亮后延时 200ms 让眼睛看清,再熄灭、换下一位。把屏幕每个位置标上号,对应关系写进注释里。
// 建表程序:逐个点亮 RAM 中的每一位,肉眼记录屏上位置 void build_seg_map(void) { uint8_t addr, bit; for (addr = 0; addr < 32; addr++) { // 按 32 SEG 地址遍历 for (bit = 0; bit < 4; bit++) { // 每个地址下 4 个 COM bit ht1621_clear_all(); // 先全灭,避免上一位干扰 ht1621_set_bit(addr, bit, 1); // 只点亮当前位 delay_ms(200); // 给肉眼足够观察时间 } } ht1621_clear_all(); }这里addr对应 SEG 编号,bit对应 COM 编号。实际屏幕上亮起的那一段,就是你要记录的对象。有人会问,为什么要先全灭再点亮?因为段码屏有保持效应,前一个点亮的段如果没熄灭,会跟当前段一起亮,你以为是一次点亮了两段,其实是上一段没灭干净。200ms 延时是观察舒适度折中值,太短看不清,太长磨洋工。
记录时我建议直接用屏的实物照片,打印出来,一张张标号。比对着 CAD 图纸猜坐标可靠得多。段位表建好后,写成一堆宏定义或数组,后续所有测试程序都以它为基准,不要再用裸地址。
2.3 初始化顺序与偏压配置:先让液晶有电场再说点亮
HT1621 的初始化顺序看着简单,但顺序错了,后续所有段都不会正常亮。常见翻车点是上电后直接开 LCD 输出,导致整屏对比度极低、像隔着一层毛玻璃。正确的顺序是:上电后先等电源稳定,然后关系统、关 LCD 输出、配偏压和 COM 数,延时十几毫秒后再开系统、开 LCD 输出,最后清屏。这条顺序不能乱,因为液晶需要一点时间建立电场,偏压没配好就开输出,等于在错误的电压下驱动液晶,长时间这样还会加速屏的老化。
偏压配置必须和屏的规格一致。常见段码屏有两种偏压:1/2 bias 和 1/3 bias。区别在于驱动电压的档位数,1/3 bias 的对比度通常更好,但对 VLCD 电压的精度要求更高。屏体标签上一般会印,没印就找屏厂要规格书。误配了偏压,表现是整屏普遍偏亮或偏暗,而且调 VLCD 电压也没用,因为档位结构不对。
COM 数的设置同样要和屏匹配。4 COM 的屏配成 3 COM,会出现有一段始终无法点亮;反过来,3 COM 的屏配成 4 COM,多出的那个 COM 会浮空,表现为部分段出现鬼影、微微亮但不清晰。这些参数写入 LCD 配置命令时一并设置,具体命令位要看手册,但排查思路是固定的:先确认屏的规格,再确认代码配置,再谈其他。
3. 手写逐段扫描固件:GPIO 模拟时序的最小实现
理解了 RAM 映射后,就可以写一个能直接跑的逐段扫描固件。这里给的是最小实现,不依赖厂商库,就三个文件的事:一个 GPIO 模拟时序层,一个写 RAM 函数,一个扫描主循环。芯片端以 32 SEG × 4 COM 为例,如果你的屏是 6 COM 或 8 COM,把循环边界改一下即可,原理完全一样。
3.1 时序层封装:三个函数覆盖全部操作
时序层是全部代码的地基。这里把 CS、WR、DATA 三个引脚做抽象,后续所有命令和数据的发送都基于一个发送单 bit 的函数。实现里故意没做任何优化,目的是让时序可读、可量、可查。注意ht1621_write_bit里的顺序:先把数据放到 DATA 线上,再拉高 WR 锁存,最后拉低,绝不能在 WR 高电平期间翻 DATA。
// HT1621 三线接口的 GPIO 模拟层 // 假设引脚已初始化为推挽输出 #define HT1621_CS_PIN GPIO_PIN_0 #define HT1621_WR_PIN GPIO_PIN_1 #define HT1621_DATA_PIN GPIO_PIN_2 static void ht1621_delay_us(uint32_t us) { // 简易延时,用 NOP 循环或系统滴答实现 for (volatile uint32_t i = 0; i < us * 10; i++) { __NOP(); } } static void ht1621_write_bit(uint8_t bit) { // 数据线先置位,再给写时钟上升沿 HAL_GPIO_WritePin(HT1621_DATA_PORT, HT1621_DATA_PIN, bit ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(HT1621_WR_PORT, HT1621_WR_PIN, GPIO_PIN_SET); ht1621_delay_us(2); HAL_GPIO_WritePin(HT1621_WR_PORT, HT1621_WR_PIN, GPIO_PIN_RESET); ht1621_delay_us(2); } static void ht1621_send_command(uint8_t cmd) { // cmd 为完整命令字节,格式见具体型号手册 HAL_GPIO_WritePin(HT1621_CS_PORT, HT1621_CS_PIN, GPIO_PIN_RESET); for (int i = 7; i >= 0; i--) { ht1621_write_bit((cmd >> i) & 0x01); } HAL_GPIO_WritePin(HT1621_CS_PORT, HT1621_CS_PIN, GPIO_PIN_SET); } static void ht1621_write_data(uint8_t addr, uint8_t data) { // 写数据:101 标志 + 6 位地址 + 8 位数据 HAL_GPIO_WritePin(HT1621_CS_PORT, HT1621_CS_PIN, GPIO_PIN_RESET); ht1621_write_bit(1); ht1621_write_bit(0); ht1621_write_bit(1); for (int i = 5; i >= 0; i--) { ht1621_write_bit((addr >> i) & 0x01); } for (int i = 7; i >= 0; i--) { ht1621_write_bit((data >> i) & 0x01); } HAL_GPIO_WritePin(HT1621_CS_PORT, HT1621_CS_PIN, GPIO_PIN_SET); }ht1621_send_command的参数是完整的命令字节,比如系统配置、LCD 开关这些。具体值我在注释里故意不写死,因为 HT1621 不同批次手册的表达有差异,直接抄网上代码容易踩坑。ht1621_write_data是核心函数,地址 6 位、数据 8 位,这是最常见的写模式。如果你的代码风格是 4 位数据模式,最后那个循环改成 3 位即可。
这里的参数有两个值得注意。延时 2us 是下限,不是推荐值,实际调的时候可以拉长到 5us、10us,不影响性能,但能显著降低长走线场景下的信号质量问题。另一个是 CS 拉低到第一个 bit 之间的时间,有些屏要求 CS 建立时间,至少保证 1us 以上,不然第一个 bit 容易丢。
3.2 点亮任意一段:地址与 COM bit 的换算
有了ht1621_write_data,点亮任意一段就是一个公式的事。前面建好的段位表告诉我们,屏幕上第几段对应哪个地址的哪个 bit。封装一个ht1621_set_pixel函数,参数是段位坐标,内部把坐标换算成地址和数据。
// 按段位坐标点亮或熄灭 // seg: 0~31 的 SEG 编号 // com: 0~3 的 COM 编号 void ht1621_set_pixel(uint8_t seg, uint8_t com, uint8_t on) { uint8_t addr = seg; // 8 位模式下地址直接对应 SEG 号 uint8_t data = on ? (1 << com) : 0; // 对应 COM 位置 1,其余位置 0 ht1621_write_data(addr, data); }这个函数的巧妙之处在于它的简单。地址对应 SEG 号,数据里的位对应 COM 号,两者都是直接左移操作,不需要查表。如果实际屏的 RAM 布局不是这种线性关系——确实有些型号的地址高位是 COM、低位是 SEG——那就在这个函数里做映射,把它当作唯一的换算入口。整个应用层只和ht1621_set_pixel打交道,底层怎么换都不影响。
注意这里on ? (1 << com) : 0,如果传on=1, com=2,数据就是0x04,也就是第 3 位(从 0 数起)为 1。这个数据一次只写一个 bit,但ht1621_write_data一次会写入 8 位,所以同一地址下其他 COM 的段会被同时写 0,造成同地址的其他段熄灭。这是正常的,不是 bug。逐段测试时反而要利用这个特性:每次写之前先清屏,然后单点写入,保证一次只亮一段。
3.3 主扫描循环:让每一段在眼睛前走一遍
最小可用的扫描程序,就是一个双层循环:外层遍历所有 SEG,内层遍历所有 COM,每步亮一个段、停一会儿、再清屏。
// 逐段扫描:从 seg0/com0 到 seg31/com3 void seg_scan_test(void) { uint8_t seg, com; for (seg = 0; seg < 32; seg++) { for (com = 0; com < 4; com++) { ht1621_clear_all(); // 先把所有段熄灭 ht1621_set_pixel(seg, com, 1); // 点亮目标段 delay_ms(80); // 停留 80ms,等眼睛看清 } } ht1621_clear_all(); // 扫描结束,全灭 }ht1621_clear_all就是把每个地址写 0,代码很简单,但作用很关键。如果不做这一步,相邻的两次写入会在同一个地址上叠加,比如先点了 seg5/com1,下次点亮 seg5/com2 时,com1 那一段还亮着,你会看到同一个地址下两个 COM 的段同时亮,误以为屏有短路。80ms 的停留时间是我个人习惯值,产线上用工装的话可以缩到 40ms,靠摄像头拍照判断;人眼目检建议不低于 60ms,否则暗段和有轻微显示缺陷的段不容易看清楚。
延时可以用阻塞方式,也可以塞进系统 tick。这里用阻塞是为了让程序在任何 MCU 上都能跑,不依赖 RTOS。如果 MCU 主频很高,记得把优化开大一点,否则delay_ms(80)可能被编译器优化成空循环,实际停留时间缩水到几毫秒。这种情况我遇到过,现象是屏幕一闪而过,根本没法看,最后是反汇编才发现问题。
4. 逐段检查的进阶组织:段位表、亮度调整与整屏验证
逐段扫描跑通以后,测试程序还只是“会亮”。离“能上产线”还有一段路:要把亮段变成可判断的标准,要让亮度可控,还要把单段测试扩展到整屏验证。这一章写的是怎么把一个裸扫描程序,变成一套真正能用的测试方案。
4.1 把段位表写进程序:从肉眼记忆到结构化数组
建表程序跑出来的结果,最终要变成一个可供程序查询的数据结构。常见做法是定义两个数组:一个按屏幕位置组织,叫“逻辑段号”,另一个按 RAM 地址组织,叫“物理映射”。逻辑段号 0 代表屏幕最左上角那段,1 代表它右边那段,以此类推,这样写测试用例时不用去想芯片内部结构。
// 段位表:逻辑段号 -> 物理 SEG/COM 坐标 // 数组下标是逻辑段号,值是物理坐标 typedef struct { uint8_t seg; uint8_t com; } seg_coord_t; const seg_coord_t seg_map[128] = { {0, 0}, // 逻辑段 0:屏左上角第一个点 {0, 1}, // 逻辑段 1:屏左上角第一点的下段 {1, 0}, // 逻辑段 2:第一点右侧段 // …… 这里按建表时的记录逐项填 }; // 按逻辑段号点亮 void seg_test_by_number(uint8_t logic_seg) { if (logic_seg >= 128) return; ht1621_clear_all(); ht1621_set_pixel(seg_map[logic_seg].seg, seg_map[logic_seg].com, 1); }这个数组建好后,测试程序就从“循环所有地址”进化成“循环所有逻辑段号”。产线工人不需要理解 SEG 和 COM 是什么,只看屏幕;逻辑段号让程序里的循环和实物位置直接对应。这里的坑在于数组长度一定要和数据手册对得上,多定义或漏定义都会导致段号错位。填表时建议一次只填一行,编译烧录,点亮验证一个,再填下一行,不要一次性填完再验证。
4.2 亮度不够时先调偏压而不是改命令
逐段测试里有种常见诉求:屏幕亮度不够,想通过软件“调亮”。HT1621 没有像点阵屏那样的全局亮度寄存器,它的亮度主要由三个因素决定:VLCD 电压、偏压配置、刷新率。程序里改命令能影响的是后两者,但影响幅度有限;真正的亮度大头在 VLCD。
LCD 亮度这件事,我吃过的亏是拿软件命令反复试,最后发现屏的亮度玄学地变,时亮时暗。后来看了屏厂的参考设计才知道,VLCD 走的是一个可调电阻分压,我那个板上电阻焊错了,软件怎么改都是白搭。正确的调法是:先用示波器量 VLCD 引脚的直流电平,对照屏规格书里给出的驱动电压范围,如果是硬件分压就调电位器,如果是外部稳压源就调输出电压。偏压档位确认无误后,再考虑软件层面通过改 RC 振荡器的频率来微调刷新率。
| 亮度因素 | 调节手段 | 注意事项 |
|---|---|---|
| VLCD 电压 | 电位器或外部稳压 | 电压过高会加速液晶老化 |
| 偏压档位 | 命令配置 | 必须与屏规格一致 |
| 刷新率 | 外部 RC 电容 | 频率过低会看到闪烁 |
| 温度 | 无法软件调节 | 低温时屏整体变暗属正常 |
4.3 整屏验证:全亮、滚扫、呼吸模式
单段逐测能发现断线,但发现不了“整屏亮度不均匀”或者“某段长期点亮后有残影”这类问题。所以要加整屏验证环节。整屏全亮是最简单的一招:所有地址写 1,保持 1 秒,看屏上有没有暗点、灭段、颜色不均。然后全灭 1 秒,再全亮,反复三次。这种检测方式对绑定工艺残留的轻微短路特别敏感——有短路的地方,全亮时会看到一个额外的段微微发光,或者亮度比周围暗一截。
呼吸模式则用来测对比度和响应速度:全亮后,用软件把 VLCD 以 PWM 方式逐步调高再调低,让屏像呼吸一样均匀亮暗。任何响应迟钝、亮度跳跃的段都会在这个模式下暴露。这比单纯全亮有用得多,因为它把段与段之间的驱动差异放大了。实现上,PWM 控制 VLCD 需要额外硬件,但也可以用命令控制 LCD 输出开关,以 50Hz 左右的频率交替开断,视觉上等效于半亮。这个土办法没有额外硬件成本,应急够用。
LCD 屏显示中文的场合,段码屏和点阵屏是两种路线。段码屏上的中文通常是玻璃定制出来的固定笔画段,比如“故障”“设置”“加热”这种词,每个词做成一段,点亮就是显示。这种段在逐段测试时容易被忽略,因为它们不像数字一样在 RAM 里连续排列。我习惯在建表时单独把中文段列成一组,别跟数字混在一起,否则扫描到某个地址时,屏幕蹦出一个汉字,而建表记录里没这个位置,容易懵。
5. 常见翻车排查:HT1621 逐段测试最容易误判的四类现象
逐段测试的难点不在写代码,而在屏亮起来之后怎么判断哪里不对。以下是四类我踩过、也看同行踩过的高频问题,按“现象 → 原因 → 解决”列出来。每一条都来自实际调试,不是理论推演。
5.1 一整段或一整列始终不亮
现象:扫描到某个地址时,屏幕对应位置无论怎么写都黑着,其他段都正常。或者反过来,某一列的所有段都不亮,其他列完全正常。
原因:这种“整齐的不亮”绝不是程序问题,因为程序是循环遍历的,逻辑上不可能只跳过某一个地址。问题出在硬件链路:单个段不亮通常是该段对应的 SEG 或 COM 走线在玻璃绑定处断了;一整列不亮,说明这一列共用的 COM 或 SEG 线断了。常见于屏的边缘位置,因为绑定压力不均或 ITO 走线在玻璃边缘有微裂。
解决:先用万用表二极管档测该 SEG/COM 到驱动芯片引脚之间的通断,排除绑定问题。如果通断正常,再量玻璃上的 ITO 线路。最土的办法是在全亮模式下用手指轻轻按压屏的绑定区域,看那一列会不会出现忽亮忽暗——按压时亮起来,说明是虚接。但注意,按压测试只适合排查,不能作为维修手段。
5.2 上电全亮一下然后慢慢消失
现象:每次上电,屏幕会整屏亮一下,大约持续一两秒,然后逐渐变暗、消失。程序里明明有初始化代码,却好像没生效。
原因:这是初始化顺序错误。最常见的就是上电后、偏压没配好之前就打开了 LCD 输出。液晶在电源刚建立的瞬间,电极间已经有杂散电荷,如果输出驱动没有按正确的偏压时序开启,会先产生一个短暂的全亮冲击,然后电荷泄放,屏幕慢慢暗掉。第二个常见原因是清屏函数没执行,RAM 里残留了芯片出厂测试的随机数据。
解决:把初始化顺序固定成严格序列:延时等待电源稳定 → 关系统 → 关 LCD 输出 → 配置偏压/COM → 延时 10ms 以上 → 开系统 → 开 LCD 输出 → 立即清屏。这一步完成后,在调试时把延时值留大一点,用示波器同时抓 VLCD 和 LCD 输出命令的电平,确认 LCD 输出确实在 VLCD 稳定之后才打开。
5.3 点一段,旁边的段跟着出现虚影
现象:点亮某一段时,它旁边隔着一段距离的另一段微微发光。亮度不高,但肉眼可见,尤其在低亮度背景下非常明显。全灭状态下,这些虚影也偶尔闪现。
原因:两个来源。一是偏压档位过高,导致驱动电压超过了液晶的阈值电压,原本不该亮的半选择点也被触发。判断方法是降低 VLCD 电压,如果虚影减弱,就是这个原因。二是数据建立时间不足,WR 上升沿采样到不确定数据,RAM 里某个 bit 被误写成 1,产生一个固定位置的“鬼段”。第二种情况通常只有固定一段出问题,切换地址后消失。
解决:先修时序,把ht1621_write_bit里的延时从 2us 放大到 10us,观察虚影是否消失。如果消失,说明原来的建立时间在长走线场景下不够。时序没问题,就查偏压配置:把 VLCD 往下调一档,看是否改善。注意,偏压下调会影响正常段的亮度,要找到一个两者均衡的电压点,而不是一味追求无虚影。
5.4 点亮目标段时,同地址下相邻 COM 的段也亮
现象:在 4 COM 屏上,点亮 seg5/com1 时,seg5/com2、com3 也亮,有时是全亮,有时是亮度减半。清屏之后它们又会灭掉,看起来像逻辑错误。
原因:如果清屏后能灭,说明写入的数据值不对。ht1621_set_pixel里on ? (1 << com) : 0是逐位操作,但如果屏实际用的是 4 位写数据模式,而代码用了 8 位模式,多出来的高 4 位会被解释成下一个地址的数据,导致地址自增错误,后面的段被一起点着。另一个原因是地址位从 MSB 还是 LSB 发送的次序反了,数据移了一位。
解决:先用逻辑分析仪抓ht1621_write_data的波形,对照手册确认地址和数据的 bit 顺序。很多翻车最后都发现在这里,发送顺序从高位到低位还是反过来,手册一般会写清楚,但网上流传的代码经常和手册不一致。顺序确认后,检查写模式是 4 位还是 8 位,修改最后一个发送循环的长度即可,别的都不用动。
6. 一个能留在产线工装里的自检技巧:把单段扫描改成跟随验证
当逐段测试不再需要你坐在旁边盯着看时,它才算真正上了产线。我最后想留一个技巧:把前几章的扫描程序扩展成一个带按键的“跟随验证”模式。这个模式的意思是,产线工人不需要数屏幕上有多少段,只需要看当前亮的是哪一段、听到蜂鸣器提示,就知道该段是否合格。
实现要点是加两个按键:一个“通过”键,一个“失败”键。程序当前亮起一段,工人判断这段有没有问题,按对应按键,程序自动进入下一段;每到一段先清晰点亮 150ms,然后自动熄灭,进入下一段。工人只需按节奏操作,全程不用看代码、不用看地址。段号可以用屏上的正常显示区实时显示,当前段号对应屏幕上哪一段,就在那个位置同时亮起一个提示段。
// 跟随验证:按键确认当前段是否合格 void follow_verify_mode(void) { uint8_t seg_num = 0; while (1) { ht1621_clear_all(); ht1621_set_pixel(seg_map[seg_num].seg, seg_map[seg_num].com, 1); // 等待按键,带超时和防抖 uint8_t key = wait_key_with_timeout(2000); if (key == KEY_PASS) { seg_num++; // 合格,进入下一段 if (seg_num >= 128) break; } else if (key == KEY_FAIL) { // 记录失败段号到 EEPROM,蜂鸣器长鸣提示 save_fail_seg(seg_num); seg_num++; } else { // 超时未按键,默认通过,但记录待确认段号 save_pending_seg(seg_num); seg_num++; } } }这套设计的价值在于把主观判断变成了可记录的流程。超时未按键默认通过,是产线效率的妥协,但会留下一个待确认记录,后续复检时优先看这些段。保存失败段号到 EEPROM 是这一套里最有用的一步,因为有了记录,不良品可以回溯到具体的绑定工序,而不是只说“这块屏坏了”。
我在实际项目里把这套东西用过几轮,第一版只是简单地逐段照亮,后来发现产线工人看久了会疲劳,误判率上升,才加了按键和记录。第二版加了蜂鸣器提示音,通过一个短音、失败一个长音,效率又提了一截。现在如果让我做一个新的段码屏项目,我仍然会先把逐段扫描写出来、建段位表、跑一遍全亮验证,再谈业务逻辑。这套东西不花哨,但能帮你把一块黑匣子屏变成一张清清楚楚的地图。希望帮到你。
本文还有配套的精品资源,点击获取