简介:这份资源面向电子信息、自动化等专业学生及FPGA初学者,提供基于FPGA的8×8点阵汉字滚动显示完整设计方案,可用于EDA课程实验、课程设计或自学练手。压缩包共1个doc文件,约273KB,以实验报告形式组织,涵盖FPGA结构与特点概述、VHDL控制逻辑编写、QuartusII软件仿真与下载流程,以及LED点阵列扫描显示原理、汉字字模存储与滚动控制方案,并附有设计任务要求与结论讨论。已有1672人学习下载,读者可据此理解点阵扫描时序、视觉暂留与字模取模思路,对照报告完成从程序编写、仿真到实验箱下载验证的完整流程,适合作为FPGA入门项目的参考范例。
1. 从一块 8×8 点阵说起:这套 FPGA 滚动汉字方案到底解决什么问题
一块 8×8 的 LED 点阵只有 64 个发光点,却要滚动显示“北华电子”四个汉字,还要在 FPGA 上跑通从 VHDL 编码、QuartusII 仿真到实验箱下载的完整链路——这就是本方案要交付的东西。它面向两类人:正在做 EDA 课程设计、需要一份能照着搭起来并写进报告的学生,以及要快速复现一套点阵扫描显示原型的从业者。核心难点不在“点亮 LED”,而在扫描时序、字模存储和滚动控制三者的配合,任何一环时序算错,屏幕上就是一片乱码或者干脆不亮。下面按需求、架构、实现、避坑、验收的顺序把整套方案拆开讲。
2. 需求拆解与技术选型:为什么是 FPGA + VHDL + 列扫描
2.1 三条硬性指标决定了方案边界
原始任务书里写得很清楚:在 8×8 LED 点阵上由右至左滚动显示多个汉字,用 FPGA 芯片实现硬件设计并提供后置配置方案,同时建立描述滚动显示原理的数学模型。把这三条翻译成工程语言,就是三个约束。
第一,显示对象是汉字而不是单字符。8×8 点阵一个屏只能容纳一个 8×8 的汉字字形,要显示“北华电子”四个字并滚动,必须做字模库加窗口平移,不能靠静态译码。第二,滚动方向固定为从右至左,意味着显示窗口的起始列索引要随时间递减,这直接决定了地址计数器的计数方向。第三,要有后置配置方案,也就是程序在 QuartusII 里综合、分配引脚、生成配置文件后能下载进 FPGA 并脱离仿真器独立运行,这一条把“仿真通过”和“板子上跑通”分成了两个验收节点,很多人卡在后者。
从视觉暂留的角度反推时序指标:行扫描或列扫描方式下,刷新频率必须大于 16×8=128Hz,周期小于 7.8ms,否则人眼会看到闪烁。这个数字是整个时序设计的锚点,后面分频系数怎么定、扫描时钟取多少,都围绕它来。
2.2 FPGA 与 VHDL 的选型理由
为什么不用单片机加移位寄存器?因为点阵扫描对时序的确定性要求高,每一列的切换必须在精确的时钟沿完成,软件轮询会引入抖动。FPGA 的寄存器资源多、采用查找表结构,天然适合这种并行加时序的逻辑,VHDL 又能把“列扫描计数器”“字模查找”“分频器”写成独立的进程,综合后映射成硬件电路,时序可预测。
选 VHDL 而不是 Verilog,在这个场景下没有本质优劣,主要是课程体系和实验箱例程以 VHDL 为主,复用现成模板能少踩语法坑。QuartusII 作为开发环境,支持 VHDL 输入、内嵌仿真器、能完成从设计输入到硬件配置的全流程,还支持 LPM 宏功能模块,对课程设计来说工具链是闭合的。
需要提前认清 FPGA 的局限:器件规模有限、单元延迟较大、互连采用开关矩阵导致延时估计不精确。这意味着字模不能无节制地堆在片内寄存器里,四个 8×8 汉字共 256 位,用寄存器实现尚可,再多就得考虑外扩 ROM。方案里采用寄存器存字模,正是权衡了规模和实现复杂度后的选择。
2.3 扫描方式对比:为什么最终落在列扫描
点阵扫描分点扫描、行扫描、列扫描三种。点扫描逐点驱动,扫描频率要大于 16×64=1024Hz,对时钟和驱动电路要求最高;行扫描和列扫描一次驱动一行或一列共 8 颗 LED,频率只需大于 128Hz,实现难度低一个量级。
本方案的点阵接口是列选信号 SEL[2..0] 经 4 线 8 线译码器输出 8 列,行选信号 H0~H7 由 8 个独立位控制、高电平有效。由于列选是一个 3 位向量,每一时刻只能选中一列,因此只能逐列扫描:选中某一列后,把该列 8 个点的字模数据送到行线上,点亮对应的 LED,然后快速切到下一列。这就是列扫描的由来,不是主观偏好,是接口结构决定的。
| 扫描方式 | 最低刷新频率 | 单次驱动 LED 数 | 实现复杂度 | 本方案是否采用 |
|---|---|---|---|---|
| 点扫描 | 1024Hz | 1 | 高 | 否 |
| 行扫描 | 128Hz | 8 | 中 | 否 |
| 列扫描 | 128Hz | 8 | 中 | 是 |
提示:一次驱动 8 颗 LED 时电流会叠加,实验箱接口通常已带驱动,若自己焊板必须在行或列加限流电阻,否则亮度不足甚至拉低端口电平。
3. 总体架构设计:字模库、扫描控制、分频与滚动窗口四个模块
3.1 模块划分与数据流
整套设计可以拆成四个职责清晰的模块,数据流是单向的:分频模块产生扫描时钟和滚动节拍,滚动窗口模块维护当前显示的字索引和列偏移,字模库模块根据索引输出 8 位列数据,扫描控制模块把列数据送上行线并同步切换列选信号。
用文字描述这张架构图:最左侧是系统时钟输入,进入分频模块后分出两路——一路是 kHz 级的扫描时钟,驱动扫描控制模块的列计数器;另一路是 Hz 级的滚动节拍,驱动滚动窗口模块更新字索引。滚动窗口模块输出当前字地址给字模库,字模库是一组 case 语句构成的查找表,按地址返回 8 个 8 位列数据。扫描控制模块内部有一个 3 位列计数器 cnt8,循环 000 到 111,每个计数值选通一列,同时从字模库取对应列数据送到 com 端口,red 端口输出列选码。
这个划分的好处是每个模块都能单独仿真:分频模块看波形周期对不对,字模库看每个地址输出的 8 位数据是否和手工画的点阵图一致,扫描控制看列选码是否按 000→111 循环。原始方案一试图模块化但仿真没通过,方案二改成单文件大 case 才跑通,本质原因是模块间信号握手没理清。下面给的实现按模块化思路重新组织,但把接口信号收敛到最少,避免重蹈覆辙。
3.2 关键信号定义
| 信号名 | 位宽 | 方向 | 含义 |
|---|---|---|---|
| clk_1khz | 1 | 输入 | 扫描基准时钟 |
| clk_1hz | 1 | 输入 | 滚动节拍时钟 |
| red | 3 | 输出 | 列选码,经译码器选通 8 列之一 |
| com | 8 | 输出 | 行数据,高电平点亮对应行 |
| cnt8 | 3 | 内部 | 列扫描计数器 |
| cont | 5 | 内部 | 字索引与列偏移计数器 |
| d0~d7 | 8 | 内部 | 当前字的 8 列字模 |
把 red 定义成 3 位而不是 8 位,是因为实验箱上列选经过 4 线 8 线译码器,FPGA 只需给出 3 位二进制码,译码和驱动在板上完成。这一点在分配引脚时容易搞错,如果误把 red 当成 8 位直接驱动列线,会出现两列同时亮或全灭。
3.3 数学模型:滚动本质是窗口平移
滚动显示的数学描述可以写成一个滑动窗口函数。设字模库为矩阵 M,每个汉字是 8×8 的 0/1 矩阵,四个汉字横向拼接成 8×32 的长条矩阵。显示窗口宽度为 8 列,窗口左边界位置记为 p(t),随时间从 0 递增到 24(32 减 8),窗口内取出的 8 列就是当前屏上显示的内容。p(t) 每过一个滚动节拍加 1,到 24 后回绕到 0,形成循环滚动。
这个模型解释了为什么滚动控制只需要一个计数器:计数器值就是窗口左边界,字模库按“窗口左边界 + 当前列号”去长条矩阵里取数。原始方案用 cont 从 00000 数到 11110(30)再回绕,正是这个窗口平移的离散实现。理解了这个模型,字模怎么排、计数器上限设多少、滚动速度怎么调,全都变成改参数的事,不用再靠试。
4. 核心模块实现:分频、字模查找与列扫描的 VHDL 落地
4.1 分频模块:从系统时钟得到扫描节拍
-- 分频进程:将输入时钟分频,Q 为 3 位计数器 FenPin: process(clk_1khz) begin if clk_1khz'EVENT AND clk_1khz = '1' then if Q = "111" then Q <= (OTHERS => '0'); -- 计满 8 个周期回绕 else Q <= Q + '1'; end if; end if; end process FenPin; clk <= Q(0); -- 取最低位作为扫描时钟,频率为输入的 1/2这段代码把输入时钟做 8 分频计数,再取 Q(0) 作为扫描时钟。Q(0) 每两个输入周期翻转一次,所以扫描时钟频率是输入的一半。如果输入是 1kHz,扫描时钟就是 500Hz,远高于 128Hz 的下限,满足视觉暂留要求。参数怎么改:要调扫描速度就改 Q 的位宽或取哪一位,取 Q(1) 是 1/4 频,取 Q(2) 是 1/8 频。注意分频后的时钟必须直接驱动扫描计数器,中间不要再插组合逻辑,否则综合后可能产生毛刺导致列选错位。
4.2 字模库:case 语句构建查找表
-- 字模查找进程:cont 为字索引,输出 d0~d7 共 8 列数据 process(cont) begin if cont = "00000" then d0 <= "00010100"; -- “北”第 0 列 d1 <= "00010100"; -- “北”第 1 列 d2 <= "01111110"; -- “北”第 2 列 d3 <= "00000000"; -- “北”第 3 列 d4 <= "01111110"; -- “北”第 4 列 d5 <= "01010000"; -- “北”第 5 列 d6 <= "01010000"; -- “北”第 6 列 d7 <= "01100000"; -- “北”第 7 列 elsif cont = "11011" then d0 <= "00001001"; -- “子”第 0 列 d1 <= "00001001"; -- “子”第 1 列 d2 <= "01001001"; -- “子”第 2 列 d3 <= "11111001"; -- “子”第 3 列 d4 <= "00001101"; -- “子”第 4 列 d5 <= "00001011"; -- “子”第 5 列 d6 <= "00001001"; -- “子”第 6 列 d7 <= "00000000"; -- “子”第 7 列 end if; end process;字模的生成方法是把每个汉字画在 8×8 方格纸上,有笔画处填 1、无笔画处填 0,按列从上到下读成 8 位二进制,再转成十六进制写进 case。这里每个 cont 值对应一屏完整的 8 列数据,cont 递增就是窗口右移一列。参数说明:cont 的位宽决定了能存多少屏,5 位可存 32 屏,四个汉字横向拼接共 32 列,正好够用。如果要显示更多汉字,把 cont 加宽、case 分支补齐即可,但要注意片内寄存器资源,超过八个汉字建议改用 ROM 宏模块。
注意:字模的 0/1 极性必须和硬件一致。本方案行线高电平有效、列线低电平选通,所以字模里 1 表示点亮。如果换一块共阳点阵,字模要整体取反,否则显示出来是“负片”。
4.3 列扫描控制:cnt8 循环与数据选通
-- 列扫描进程:cnt8 循环 000~111,逐列选通 P1: process(clk) begin if (clk'event and clk = '1') then case cnt8 is when "000" => data <= d0; when "001" => data <= d1; when "010" => data <= d2; when "011" => data <= d3; when "100" => data <= d4; when "101" => data <= d5; when "110" => data <= d6; when "111" => data <= d7; when others => null; end case; end if; end process P1; -- 列计数器自增 P2: process(clk) begin if clk'event and clk = '1' then cnt8 <= cnt8 + 1; end if; end process P2; com <= data; -- 行数据输出 red <= cnt8; -- 列选码输出P1 根据 cnt8 从 8 列字模里选一列送到 data,P2 让 cnt8 每个扫描时钟加一,com 和 red 分别输出行数据和列选码。两个进程用同一个时钟,综合后是同步逻辑,不会出现列选和行数据错拍。这里有个容易忽略的点:cnt8 是 3 位,加到 111 后自然回绕到 000,不需要额外写回绕判断,VHDL 的无符号加法在 std_logic_vector 上按位截断,正好实现模 8 计数。如果误用了 std_logic_arith 的有符号加法,回绕行为会不同,这是方案一仿真失败的常见原因之一。
4.4 滚动节拍:cont 的更新与停顿控制
-- 滚动节拍进程:每个 clk_1hz 上升沿 cont 加一 third: process(osd) begin if (osd = '1' and osd'event) then if cont = "11110" then cont <= "00000"; -- 到 30 回绕,形成循环滚动 else cont <= cont + '1'; end if; end if; end process third;cont 的上限设成 30(11110),对应四个汉字拼接后窗口能平移的最大位置。每个滚动节拍 cont 加一,屏幕内容整体左移一列,到末尾回绕,形成无缝循环。滚动速度由 clk_1hz 决定,想快就提高这个时钟频率,想慢就降低。原始方案里还提到用延时计数器控制每个字之间的停顿,实现方式是在 cont 更新前插入一个计数到 10000 的等待进程,停顿时间等于计数值乘以时钟周期,改这个数就能调停顿长短。
5. 避坑与排查:五个最容易翻车的点
5.1 仿真波形正常但下载后不亮
现象:QuartusII 里功能仿真波形完全正确,下载进 FPGA 后点阵全灭或只亮一列。原因通常是引脚分配和实验箱实际接线不一致,尤其是列选码的位序。red 输出 3 位,如果分配引脚时把 red(0) 接到了译码器的高位,列选顺序就反了,看起来像不亮。解决方法是拿万用表逐位测译码器输入,对照引脚分配表核对位序,或者先在板上做一个只点亮第一列的最小测试,确认列选通路无误再上完整程序。
5.2 显示闪烁或出现拖影
现象:汉字能显示但明显闪烁,或者滚动时相邻列有残影。原因有两个方向:扫描频率不够,或者列切换和行数据更新不同步。先算扫描频率,用分频后的时钟频率除以 8(列数),结果必须大于 128Hz。如果频率够还闪,检查 P1 和 P2 是否用了同一个时钟沿,两个进程时钟不一致会导致某一列的数据晚一拍到达,形成拖影。解决办法是把列计数和数据选通合并到同一个进程,或者确保两个进程都用 clk 的上升沿。
5.3 字模显示成乱码或镜像
现象:点阵上出现的图案和设计的汉字对不上,像是翻转或错位。原因是字模读取顺序和扫描顺序不匹配。列扫描是从第 0 列到第 7 列,如果字模按行存储却按列读取,图案就会转置。另一种情况是 0/1 极性搞反,显示成负片。排查方法是单独仿真字模库模块,把 cont 固定为 0,看 d0~d7 输出是否和手画的第 0 列一致,再逐列核对。极性问题和转置问题在波形上一眼能看出来,比在板子上猜快得多。
5.4 滚动到边界时跳变或丢字
现象:汉字滚动到最后一列时突然跳回开头,中间少显示一列,或者两个汉字之间出现空白断层。原因是 cont 的回绕上限设错。四个汉字拼接共 32 列,窗口宽 8 列,窗口左边界最大只能到 24,对应 cont 上限 24 而不是 30。原始方案设成 30 是因为字模排列里留了间隔列,如果字模是紧凑排列的,上限必须按实际总列数减 8 来算。解决办法是先确定字模总列数 N,cont 上限设为 N-8,回绕时从 0 重新开始,这样窗口平移才连续。
5.5 综合报错或资源超限
现象:QuartusII 综合阶段报错,提示寄存器不足或组合逻辑环路。原因是字模库用大 case 语句实现,每个分支都综合成独立的查找逻辑,汉字一多资源就爆。另外如果进程里同时用了上升沿和电平敏感信号,会综合出锁存器甚至环路。解决办法是把字模改存到片内 ROM 宏模块,用地址线读取,寄存器占用立刻降下来;进程写法上坚持“一个进程一个时钟沿”,组合逻辑单独用并行赋值语句写,不要混在时序进程里。
6. 验收与答辩:怎么证明这套方案真的跑通了
验收分三个层次,逐层过,不要跳。第一层是功能仿真,在 QuartusII 里对分频、字模、扫描三个模块分别做波形仿真,截图保存每个模块的输入输出波形,重点看扫描时钟周期是否小于 7.8ms、列选码是否按 000 到 111 循环、字模输出是否和手工点阵图逐位一致。这一层的证据是波形图,答辩时直接展示,比口头描述有说服力。
第二层是板级验证,下载配置文件后观察实际显示效果。验收标准写清楚:四个汉字“北华电子”能完整显示,滚动方向从右至左,滚动过程无闪烁无拖影,循环一周后无缝衔接。用手机慢动作录像拍一段滚动过程,逐帧检查有没有丢列,这是最直观的证据。如果条件允许,用示波器抓一路列选信号和一路行数据,确认两者的时序关系符合设计预期。
第三层是文档与答辩。报告里要有的东西:需求指标表(刷新频率、滚动方向、显示字数)、模块划分图、关键进程的代码片段及说明、仿真波形截图、板级测试照片或录像、遇到的问题及解决过程。答辩时老师最常问的三个问题提前准备好答案:为什么选列扫描而不是行扫描(接口结构决定,列选是 3 位向量只能逐列选通)、扫描频率怎么算的(输入时钟除以分频系数再除以 8,结果要大于 128Hz)、字模怎么生成的(8×8 方格手绘转二进制再转十六进制)。把这三个问题答利索,基本就稳了。
| 验收项 | 合格标准 | 证据形式 |
|---|---|---|
| 扫描频率 | 大于 128Hz | 仿真波形周期测量 |
| 列选循环 | 000 到 111 连续循环 | 波形截图 |
| 字模正确性 | 与手绘点阵逐位一致 | 模块仿真对比 |
| 滚动效果 | 从右至左,无闪烁无断层 | 慢动作录像 |
| 循环衔接 | 首尾无缝 | 连续录像观察 |
我自己的习惯是,每次下载前先把引脚分配表打印出来,拿笔逐条对着实验箱接线划勾,划完再点 Program。这个动作看起来笨,但省下的返工时间远比它花的多。还有一点,仿真通过不等于板子上通过,中间隔着引脚分配、电平极性、驱动能力三道坎,任何一道没对齐都会让你在实验室多坐两小时。希望这套拆解能帮你把课设一次跑通,把时间花在调滚动速度和字模美观上,而不是耗在找那不亮的某一列。
本文还有配套的精品资源,点击获取