☰
FPGA实现12键矩阵键盘扫描与8×8点阵动态显示
2026/10/28 14:25:23 网站建设 项目流程

简介:《按键阵列扫描及点阵显示器控制电路设计》面向学习数字电子技术与FPGA应用的学生及工程人员,围绕3行4列按键阵列的识别与8行8列点阵显示器的字符显示控制展开,重点解决按键消抖、逐列扫描、键码锁存以及动态扫描显示等典型问题。资源包内为1个doc文档,约253KB,内容按设计指标、系统概述、单元电路设计与分析等章节组织,结合LP-2900开发装置说明74138译码、计数器、状态机、ROM键符译码和行列驱动电路的具体实现。已有551人浏览学习,可作为课程设计、实验报告或同类人机交互电路设计的参考,帮助读者梳理从按键编码、显示码存储到点阵循环显示的完整设计思路,并理解多键同时按下时只响应首个按键的处理逻辑。

1. 12个键、64个点:从一次按键到一屏字符的完整链路

按下“5”,8×8 点阵屏上出现一个“5”;再按“*”,它换成“*”;两个键同时按住,只认先到的那个。键盘处理、屏幕显示、多键先响应,这三条需求拆开看都不复杂,但要落在 FPGA 上,就得把分频、扫描计数、列译码、行线选择、锁存、字模 ROM、动态扫描、反相和红绿配色串成一条环环相扣的链路。这套按键阵列扫描与点阵显示控制电路设计,正是把这条链路完整走一遍的工程样例:3 行 4 列共 12 个按键,8 行 8 列共 64 个点的显示器,12 个符号一一对应。它适合两类人:数字电子技术课程设计要交板子的,以及想弄明白“矩阵键盘 + 点阵屏”这对经典组合在门级是怎么跑起来的人。它没有现成库可以调用,只能照着原理图连,写 AHDL 或 Verilog,再上板点亮。

2. 系统架构与器件选型:扫描链和显示链怎么共用一套键码

整套电路可以拆成两条并行链路,它们只在“键码”这个 4 位信号上汇合。按键扫描链路负责在 12 个键里找出被按下的那个,把它的编号锁进寄存器;显示链路负责把这个编号当作字模表的高位地址,配合行扫描计数循环取出 8 个字节,逐行刷到点阵上。两条链路共享一个时钟源,但节拍完全不同:扫描要慢到人手动一下就扫过一轮,显示要快到人眼看不出逐行切换。

2.1 两条链路的数据流

按键侧的顺序是:4 位计数器产生扫描码,低两位送 3-8 译码器拉低某一列,高两位送 4 选 1 数据选择器选中某一行,行线电平经数据选择器输出后取反,作为锁存器的时钟,把当前计数值存下来。显示侧的顺序是:3 位计数器同时驱动行译码器和 ROM 的低 3 位地址,ROM 输出 8 位列码,经同或门做可选的极性反转,再进颜色选择网络,最后从 C1~C8 出去驱动点阵列线。

信号来源去向位宽说明
扫描码 Q3~Q0状态机 A(74161)列译码器 A、数据选择器、寄存器4同时编码行和列
键码寄存器(74377)ROM 地址 A6~A34锁存后保持不变
行扫描码状态机 B(74161)行译码器 B、ROM 地址 A2~A03循环 0~7
列码 D7~D0ROM(128×8)反相选择器、颜色选择器8一位控制一列
ROW1~ROW8译码器 B(74138)点阵行线8高电平选通
C1~C8颜色选择器(8×74138)点阵列线(经反相器)8逻辑高有效

这张表值得对着原理图看一遍,因为它解释了后面所有“接反了会怎样”的问题:键码和行扫描码在 ROM 地址里是拼在一起的,高 4 位是键,低 3 位是行,地址位序一旦调换,取出来的字模就会整体错位成另一个字符。

2.2 单元电路分工

分频器用 7 片 7490 级联,把板上 10MHz 晶振分成 10MHz 到 1Hz 共 8 档,给两个状态机提供不同节拍。状态机 A、B 各用一片 74161,前者做按键扫描计数,后者做行扫描计数。寄存器用 74377,8 位只用其中 4 位。显示码存储器是自建的 128×8 ROM,7 位地址、8 位数据。数据选择器用 74153,双片封装只取一片,选 3 路行线。译码器用 74138,一路管列、一路管行。反相选择器是纯组合逻辑搭的 9 进 8 出同或门,外接一个电平开关控制正相或反相。颜色选择器用了 8 片 74138,每位数据配一片,靠 A 脚的电平在红、绿两路输出之间二选一。

后两个器件看起来有点“浪费”:为了一个取反和一个换色,动用了 9 片芯片。但在实验箱这种以验证逻辑为主的场景里,用现成译码器搭选择网络,比写一段组合逻辑更容易在原理图上定位问题,波形也能直接探到每个引脚。

2.3 时钟预算:10MHz 怎么分到八档

7490 是二-五-十进制计数器,内部一个 2 分频和一个 5 分频。做 10 分频有两种接法:时钟进 CLKA、QA 接 CLKB,从 QD 取输出,占空比只有 20%;时钟进 CLKB、QD 接 CLKA,从 QA 取输出,占空比 50%。这里选第二种,原因是扫描信号的高低电平时间要均衡:列线拉低的时间和释放的时间各占一半,行线选通与列码建立的时间余量才一致,不会出现某一档频率下“列码还没稳定行线就关了”的情况。

7 片级联后总的分频比是 10⁷,从 10MHz 一路抽头得到 8 档:

档位频率典型用途
010MHz系统主频,一般只做参考
11MHz高速计数、示波器观察用
2100kHz行扫描上限试验
310kHz行扫描快速档
41kHz行扫描常用档(8 行一轮 125Hz 刷新)
5100Hz慢速扫描,肉眼可见逐行点亮
610Hz单步观察
71Hz指示灯闪烁

按键扫描的节拍要另算。12 个键,每个键占一个扫描周期,如果单键扫描时间取 1ms,整轮就是 12ms。人按一次键的闭合时间通常在零点几秒到一秒多,12ms 远小于这个量级,不会漏键。反过来,如果单键扫描时间取 10ms,整轮 120ms,虽然还能用,但已经接近一些快速点按的边界了,这是后面排查“偶尔按了没反应”时要先怀疑的参数。

3. 按键扫描链路拆解:74161、74153、74377如何锁住第一个按下的键

这条链路的精髓不在于“扫”,而在于“停”。扫描谁都会做,难的是在扫到第一个闭合键的瞬间把计数器按住,让它不再往前走,这样后续再按别的键也不会改写显示。整个“只响应最先作用的按键”的语义,就是靠使能端实现的。

3.1 4位计数器产生键码:谁管行、谁管列

状态机 A 是一片 74161,复位端和置数端都接高电平使其失效,ENP 和 ENT 并联后由数据选择器的输出 Y 控制,时钟接分频器输出。它的 4 位输出 Q3~Q0 要拆成两组用:一组去 74138 选列,一组去 74153 选行。哪两位管哪一组,直接决定了键码的编号方式。

按教材上的常规接法,Q3、Q2 进数据选择器的 B1、B0 选行,Q1、Q0 进译码器的 A1、A0 选列。这样计数值 0001 时,Y0 为低(第 0 列),同时选中 X1(第 1 行),扫到的是第 1 行第 0 列的键。12 个有效键码是 0~11,无效的是 12~15,因为只有 3 行。

如果反过来接,Q3、Q2 去选列、Q1、Q0 去选行,有效键码就变成了另一套编号,无效码也跟着变成 3、7、11、15。两种接法都能跑,但字模表的排布必须跟着改:ROM 的高 4 位地址就是键码,键码编号变了,12 个字模块的位置全得挪。这一点在后面第 5 章会展开,它是这个项目里最容易翻车的地方。

用等效的硬件描述语言写出来,扫描核心大概是这样:

// 12键(3行×4列)扫描核心,等效于 74161 + 74153 + 74377 // 行线 X0~X2 上拉,无键按下为 1;列线 Y0~Y3 低电平有效 module key_scan_core ( input wire clk_scan, // 扫描节拍,取 1kHz input wire rst_n, input wire [2:0] row_in, // X0~X2 output reg [3:0] key_code, // 锁存后的键码,送 ROM 的 A6~A3 output reg [3:0] cnt, // 4位扫描计数器,等效 74161 output wire locked // 1 = 已锁存,暂停扫描 ); wire [1:0] col = cnt[1:0]; // Q1Q0 -> 74138 的 A1A0,选列 wire [1:0] row = cnt[3:2]; // Q3Q2 -> 74153 的 B1B0,选行 // 74138 只用了低 2 位地址,输出低有效 reg [3:0] col_line; always @(*) begin case (col) 2'd0: col_line = 4'b1110; 2'd1: col_line = 4'b1101; 2'd2: col_line = 4'b1011; default: col_line = 4'b0111; endcase end // 74153 按行号选一路行线;行号 3 不存在,等效于接高电平 wire row_sel = (row < 2'd3) ? row_in[row] : 1'b1; assign locked = ~row_sel; // 有键按下时 Y=0,封锁计数器 reg locked_d; always @(posedge clk_scan or negedge rst_n) begin if (!rst_n) locked_d <= 1'b0; else locked_d <= locked; end // 74377 在 Y 的下降沿(即 locked 的上升沿)锁存一次 always @(posedge clk_scan or negedge rst_n) begin if (!rst_n) key_code <= 4'd0; else if (locked & ~locked_d) key_code <= cnt; end // ENP=ENT=locked 取反:没键按下就继续数,按下就停 always @(posedge clk_scan or negedge rst_n) begin if (!rst_n) cnt <= 4'd0; else if (!locked) cnt <= cnt + 4'd1; end endmodule

代码里几个关键点对应到硬件:cnt是 74161 的输出,locked是数据选择器输出经反相后的结果,key_code是 74377 的锁存值。锁存条件写成locked & ~locked_d,是为了复现“下降沿有效”:只有一个扫描节拍内新出现的按下动作才写入,按住不放不会反复刷新。row < 3这个判断对应 74153 的第 4 路输入接高电平,因为只有 3 行,第 4 路永远不该被选中。

参数上,clk_scan取 1kHz 意味着单键 1ms、整轮 12ms;如果板上分频器只给到 100Hz,单键就变成 10ms、整轮 120ms,这时候快速点按就可能漏,需要把分频档位往上调一档。

3.2 行线选择与锁存时序

74153 在这里干的事很朴素:4 选 1,把当前扫描行对应的那根行线接到输出 Y 上。三路行线接 1C0、1C1、1C2,第四路 1C3 因为无键按下时应当为高,直接接 VCC。这样扫描到不存在的第 4 行时,Y 恒为高,不会误判成有键按下。

锁存环节有个细节容易忽略:74377 的锁存端是下降沿有效,而数据选择器在“有键按下”时输出低电平。也就是说,按下动作本身产生的是一个下降沿,如果直接把 Y 接到锁存端,锁存会发生在按下瞬间电平跳变的那一刻,此时计数值可能还没稳定。所以中间要加一级反相器,把 Y 取反后再送时钟端,让锁存动作落在反相后的上升沿上,正好对应计数器已经停稳之后的那个节拍边缘。

这个反相器不是可有可无的“逻辑取反”,它是时序上的一个缓冲。少了它,上板后常见的现象是显示的字偶尔差一格,像是按了“5”出来“6”,因为锁存抓到了计数器跳变的中间态。

3.3 消抖与“只认第一个”的封锁逻辑

机械按键的触点在闭合瞬间会弹跳几毫秒到十几毫秒,波形上是一串快速脉冲。如果扫描节拍正好落在这串脉冲里,Y 就会产生多个下降沿,锁存器被反复写入,虽然写进去的可能是同一个键码,但如果抖动期间计数器已经走到下一格,就会锁成相邻的键。

基础做法是用基本 RS 触发器对行线信号整形,把抖动的模拟波形变成干净的单次跳变。这个做法在单键电路里很好用,但在阵列扫描里有个前提:RS 触发器要放在数据选择器之后、锁存器之前,而且两只交叉耦合的与非门要共用同一组电源和地,否则整形出来的边沿仍然带毛刺。

更稳妥的常见做法是加两级同步采样:用扫描节拍对row_sel连续采两次,两次一致才认。这相当于一个数字低通,把短于一个扫描周期的毛刺全部滤掉。代价是响应延迟一个节拍,对 1ms 的扫描周期来说完全可以接受。

“只认第一个”的实现则完全落在使能端上:Y 为低时,74161 的 ENP 和 ENT 同时为 0,计数器保持当前值不再递增。此时无论再按下哪个键,数据选择器的输出怎么变,计数值都不动,ROM 的高 4 位地址也就不动,显示自然停在第一个键上。等到手松开,Y 回到高,计数器恢复计数,下一次按下才会写入新的键码。这里没有复杂的优先级仲裁电路,一个使能信号就完成了全部语义。

4. 点阵显示链路拆解:128×8 ROM、动态扫描与红绿配色

显示侧要解决的是“怎么用 8 根行线加 8 根列线,让 64 个点在任何时刻看起来都是同时亮的”。答案是把时间切片:任一时刻只点亮一行,8 行轮一遍只要够快,人眼就会把残影拼成完整的字符。所有设计都围绕这个切片展开。

4.1 ROM地址映射:高4位是键,低3位是行

12 个符号,每个符号 8 行,每行 8 位,一共需要 12×8=96 个存储单元。地址线至少要 7 位(2⁷=128),所以 ROM 做成 128×8。地址的高 4 位 A6~A3 接寄存器输出的键码,低 3 位 A2~A0 接行扫描计数器的输出。这样每个键码在地址空间里占连续的 8 个单元,从低地址到高地址正好对应点阵从上到下的 8 行。

这个映射关系有个直接推论:ROM 里会有 32 个单元永远取不到,因为 4 位键码有 16 种取值,而实际只有 12 个键。取不到的块是键码 12~15 对应的地址段,如果计数器位序接反,取不到的就变成 3、7、11、15 对应的段。这 32 个单元必须显式填 0,不能留空让综合工具随便填,否则上电瞬间点阵上会冒出几行乱码。

存储数据的位序同样要对齐。ROM 的 D7~D0 依序控制 C1~C8,那么一个字节从高位到低位就对应点阵从左到右的 8 列。以字符“0”为例,第一行只想亮中间四列,数据就是0x3C(00111100);边框行想亮最左最右两点,数据就是0x81(10000001)。写数据文件时如果把这个顺序搞反,显示出来的字会左右镜像。

-- 128×8 ROM 初始化片段,键码 0 占 0x00~0x07 DEPTH = 128; WIDTH = 8; ADDRESS_RADIX = HEX; DATA_RADIX = HEX; CONTENT BEGIN 00 : 3C; -- 第 1 行,中间四点亮 01 : 66; -- 第 2 行 02 : 6E; 03 : 76; 04 : 66; 05 : 66; 06 : 3C; 07 : 00; -- 第 8 行留空,字模不顶到边 18 : 00; -- 键码 3 是无效码,整块清零 19 : 00; -- 其余无效块同理,每块 8 个单元 END;

这段 mif 片段里,00~07是键码 0 的 8 行字模,18~1F是键码 3 的 8 个单元。填 0 的意义不只是“好看”,它让无效键码对应的显示是黑的,而不是随机亮点,调试时一眼就能看出是取到了无效块还是字模本身有错。

4.2 行扫描与列码同步

状态机 B 也是单片 74161,使能端接高电平常开,复位和置数端接高失效,时钟从分频器取一档。输出只取 QC、QB、QA 三位,组成 0~7 八个状态,送 74138 译码成 8 根行选通线 ROW1~ROW8。同一组三位信号还要接到 ROM 的 A2~A0,保证“选通第 i 行”和“取出第 i 行字模”是同一个时刻。

这里的同步靠的是物理上的同一根线,不是靠两个计数器碰巧同频。如果行译码和 ROM 地址分别来自两个不同的计数器,哪怕频率一样,上电初相也可能差一拍,结果是每一行显示的都是上一行的内容,整个字看起来像被向上平移了一格。这类问题在仿真里不一定暴露,上板才看得见。

行扫描频率的选择要兼顾刷新率和亮度。8 行扫一轮,若单行停留 1ms,整屏刷新率是 125Hz,远高于人眼 50Hz 的临界闪烁频率,看起来是稳定的。如果单行停留 10ms,刷新率掉到 12.5Hz,屏幕会明显抖动。另一方面,单行停留时间太短,LED 的导通时间不足,亮度会掉下来,在环境光强的地方看不清。常见折中是单行 0.5~2ms 之间,对应分频档位的 1kHz 到 4kHz。

列信号的建立要在行选通之前完成。74138 译码有传输延迟,ROM 读出也有访问时间,如果行线已经拉高、列码还在变化,那一行会闪出一段错误的亮点。稳妥做法是在行选通和列输出之间留半个节拍的余量,或者干脆接受这点轻微拖影,因为 8×8 的点阵面积小,肉眼很难分辨。

4.3 反相选择与红绿切换

反相选择器解决的是“前景背景互换”的需求。它由 9 个输入、8 个输出组成:8 个输入来自 ROM 的数据位,第 9 个是外接电平开关,输出是每一位与开关做同或的结果。真值表很直白,开关为 0 时输出等于输入,开关为 1 时输出取反,整屏的黑白就翻过来了。

用同或门而不是异或门,是因为这里的开关信号默认是高电平无效。同或的逻辑是“相同为 1、不同为 0”,开关为 1 时输出取反,开关为 0 时输出跟随,正好对应“按下开关翻转、松开恢复”。这个器件的 9 进 8 出结构在原理图上表现为 8 组并行的门电路,每组一个与门、一个或门加若干非门,看起来繁琐但延迟一致,不会出现某几位翻转快、某几位翻转慢导致的颜色撕裂。

颜色选择器用了 8 片 74138,每片负责一位数据。每片 74138 的 A 脚接同一个电平开关,B、C 脚接固定电平,使能端由 ROM 对应位的数据控制。这样当该位为 1 时,译码器让 Y0 或 Y1 之一有效,分别对应红色和绿色两路输出;该位为 0 时两路都不亮。8 片并排,就实现了“同一位数据可以按开关选红或选绿”。

这里有个实际约束:74138 的输出是低有效,而点阵列线经反相器驱动后是逻辑高有效。所以从颜色选择器出来的信号还要再取一次反,或者干脆在点阵接口处统一反相。原文提到列信号经反相器驱动以满足电流驱动能力,这个反相器同时承担了逻辑极性和驱动能力两个职责,接线时不能只当作逻辑门看。

5. 避坑与排查:显示错乱、误触发、闪烁的定位顺序

这块电路在仿真里跑通不难,难的是上板。下面几条是这个项目里最容易出现的问题,每条都给出从现象到定位的路径。

5.1 现象:显示的字和按下的键对不上,或者整屏错位

原因几乎都是 ROM 地址的位序和扫描计数器的位序不一致。如果状态机 A 的 Q3、Q2 去选了列、Q1、Q0 去选了行,那么有效键码就变成另一套编号,原本按教材接法排好的 96 个单元,取出来的块会整体偏移,按“1”显示成“4”这类现象就会出现。

解决分两步。先把 ROM 从 96 个有效单元扩到 128 个,把无效键码对应的 4 个块(每块 8 个单元)全部填 0,让错位时至少是黑屏而不是乱码。再按实际的位序重新排字模块,让键码编号和物理按键一一对应。改完之后最好列一张键码对照表,把“计数值、行号、列号、按键标号、ROM 块起始地址”五列写清楚,贴在手边。

5.2 现象:按一次键,显示跳好几个字符

原因是抖动没有被滤掉,锁存器在几毫秒内被写了多次,而计数器在这期间已经走到了下一格。用示波器看数据选择器输出 Y,能看到按下瞬间有一串密集的负脉冲。

解决是在 Y 之后加两级由扫描节拍同步的采样,连续两次读到低才认。如果板上已经有 RS 触发器整形电路,检查两只交叉耦合门是否共地、上拉电阻是否够小,接触不良的按键也会产生类似波形,换个按键试试能快速区分是电路问题还是器件问题。

5.3 现象:按住一个键不放,再按别的键没反应

这不是故障,是设计语义。计数器被 Y 封锁后不再递增,键码保持不变,其他键的按下动作根本不会被扫描到。如果需求改成“后按的覆盖先按的”,就要把封锁逻辑改成边沿触发的一次性暂停,或者加一个释放检测,在按键全部松开后强制复位计数器。

排查时先确认是“设计如此”还是“卡死”:松开第一个键,看显示是否还能响应新按键。能响应就是语义正确,不能响应就是 Y 没有回到高电平,检查行线上拉电阻和按键接触。

5.4 现象:点阵整体闪烁,或者某一行明显偏暗

闪烁通常是行扫描频率太低。8 行一轮,单行 10ms 对应刷新率 12.5Hz,肉眼很容易察觉。把分频档位往上调一到两档,或者直接改行扫描计数器的时钟源。

某一行偏暗则是那一行的驱动能力不足。检查对应的 ROW 线译码输出是否接错、该行 LED 的限流电阻是否偏大,以及列信号反相器是否在这一行上漏接。8 行里如果只有一行异常,基本可以锁定在译码器输出或连线,不用怀疑 ROM 数据。

5.5 现象:一切换颜色,整屏全灭

颜色选择器里 74138 的使能极性搞反了。74138 的 G1 是高有效、G2A 和 G2B 是低有效,三个使能端要同时满足才工作。如果只接了 G1 而把另外两个悬空,译码器可能一直处于禁止状态,输出全高,经反相后全低,屏幕就黑了。

排查方法很直接:用万用表量 74138 的三个使能脚,确认电平符合数据手册的有效条件,再量 A、B、C 三个地址脚是否跟随开关变化。这一步花不了五分钟,但能省掉半天怀疑 ROM 的时间。

6. 二次开发:加键、换字模、改配色的三条改造路径

这套电路的扩展性比看上去好,因为它把“键码”“字模”“颜色”三件事拆在了三个独立的位置,改哪一件都不用动另外两件。下面按改动量从小到大排一下。

第一条路是换字模,改动量最小,收益最直接。把 12 个符号换成箭头、字母、简单图标都行,只要还是 8×8。手工填 mif 容易出错,我一般用脚本生成:

# 把 8×8 点阵字模写成 Quartus 的 .mif,键码即块号 GLYPHS = { 0x0: [0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00], # 数字 0 0x1: [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00], # 数字 1 # 其余 10 个键码照此补齐,无效键码块留空 } LINES = ["DEPTH = 128;", "WIDTH = 8;", "ADDRESS_RADIX = HEX;", "DATA_RADIX = HEX;", "CONTENT BEGIN"] for code in range(16): # 16 个键码块,无效块填 0 rows = GLYPHS.get(code, [0x00] * 8) for r, byte in enumerate(rows): # 低 3 位地址对应行号 LINES.append(f" {code * 8 + r:02X} : {byte:02X};") LINES.append("END;") with open("font_8x8.mif", "w", encoding="utf-8") as f: f.write("\n".join(LINES))

脚本的关键是code * 8 + r这个地址计算:高 4 位是键码、低 3 位是行号,所以第 code 个字符的第 r 行落在code*8+r。GLYPHS.get(code, [0x00]*8)保证没有定义的键码块自动填 0,不会因为漏写而让综合工具填入随机值。生成之后在 Quartus 里重新编译 ROM,注意 mif 文件路径要用相对路径,换机器打开工程时最容易在这里报错。

第二条路是加键,从 12 键扩到 16 键(4 行 4 列)。要动的地方有三处:74153 的第 4 路输入不能再接高电平,要接到第 4 根行线;ROM 从 128×8 扩到 256×8,地址加到 8 位;无效键码块从 4 个变成 0 个,字模表刚好填满。如果继续扩到 5 行以上,4 位计数器就不够用了,得换 5 位或者改用二进制以外的编码方式。加键之后记得重算扫描周期:16 键 × 1ms = 16ms,仍然远小于人手按键时间,不用调节拍。

第三条路是改显示效果,比如让字符滚动或者做多字符切换。滚动不需要改 ROM,只需要在行扫描计数器之外再加一个帧计数器,每过若干帧把 ROM 的高 4 位地址整体加一,取下一个字符的块。这里要注意帧计数器的进位不能影响行扫描的连续性,否则滚动的时候会撕裂。我的做法是让帧计数器只在行扫描归零的那一刻递增,保证每次切换都发生在帧边界上。

上板验证的顺序也有讲究,别一上来就按键盘。先断开按键部分,只给显示链路上电,用固定键码强制 ROM 输出某个字符,确认 8 行扫描正常、字形正确、没有镜像;再接上按键,用单步时钟一格一格走,看锁存是不是发生在正确的计数值上;最后才测多键同时按下和颜色切换。这个顺序能把问题限制在一条链路里,省掉“到底是扫描错了还是显示错了”的反复怀疑。

说个我自己踩过的坑:有一次改完字模,显示一直整体下移一行,查了两个小时 ROM 数据,最后发现是 mif 文件里第一行的地址写成了01而不是00,整个块往后错了一位。从那以后我每次生成 mif 都强制走一遍脚本,不再手工改任何一个字节,生成完先打印前 16 行地址和数据对一遍再编译。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询