1. 项目概述:从零开始搭建你的第一个智能交通灯系统
如果你刚开始接触FPGA和VHDL,可能会觉得用硬件描述语言去控制红绿灯有点“大材小用”,或者觉得无从下手。别担心,我刚开始学的时候也是这么想的,但后来发现,这个项目简直是入门数字逻辑设计的“黄金标准”。它麻雀虽小,五脏俱全,能让你一次性把状态机、时序逻辑、分频、数码管驱动这些核心概念全给练了。
简单来说,我们要做的就是一个十字路口的交通灯控制器。东西方向是主干道,绿灯亮40秒;南北方向是支路,绿灯亮20秒。每次绿灯变红灯之前,黄灯会先亮5秒,并且每秒闪烁一次,给司机一个缓冲时间。听起来是不是和现实中的红绿灯一模一样?没错,我们的目标就是用一块FPGA开发板,通过VHDL代码,在Quartus软件里把这个逻辑“烧”进去,让它像真的一样跑起来。
这个项目特别适合两类朋友:一是正在学习数字电路或FPGA的在校学生,它能帮你把课本上的理论变成看得见、摸得着的仿真波形;二是对嵌入式控制感兴趣的开发者,想了解如何用硬件逻辑来实现一个稳定可靠的时序控制系统。整个过程会涉及到创建工程、编写代码、功能仿真、查看RTL电路图,直到最后可能的下板验证。我会把我自己踩过的坑、调试的小技巧都分享出来,保证你跟着做一遍,就能对VHDL和Quartus的联动开发有个扎实的理解。
2. 开发环境搭建与工程创建
工欲善其事,必先利其器。第一步,我们得把“厨房”收拾好。这里的主角是Intel Quartus Prime(我用的是18.1标准版,其他版本如Lite版也完全没问题)和ModelSim仿真工具。Quartus是Intel(以前是Altera)FPGA的官方集成开发环境,而ModelSim是业界常用的仿真软件,两者通常捆绑在一起安装。
安装过程我就不赘述了,记得把VHDL支持选上。安装好后,打开Quartus,我们开始创建新工程。点击“File” -> “New Project Wizard”。第一步会问你工程存放的目录和工程名,这里有个小建议:路径和工程名都不要用中文,也不要包含空格,最好用简单的英文或拼音,比如“traffic_light”。这是为了避免后续编译和仿真时出现一些莫名其妙的错误,我早年可没少在这上面栽跟头。
一路“Next”,到了选择器件型号的页面。如果你手头有DE2-115、Cyclone IV EP4CE115F29C7这类开发板,就直接选对应的型号。如果只是做仿真,还没有具体硬件,可以选一个资源足够的器件,比如Cyclone IV E系列的EP4CE115F29C7,它逻辑单元多,管脚也够用,仿真起来没问题。后面的设置都用默认值,直接“Finish”完成工程创建。
工程创建好后,我们首先要添加VHDL源文件。右键点击工程导航栏里的工程名,选择“New” -> “VHDL File”。我给主控制文件起名叫traffic_light_ctrl.vhd。记住,VHDL文件的后缀是.vhd或.vhdl。创建好文件后,一个空白的编辑窗口就打开了,接下来我们就可以在里面“施展魔法”了。
3. 系统架构设计与模块划分
在动手写代码之前,我们先在脑子里或者纸上把整个系统的“骨架”搭好。一个好的架构能让编码和调试事半功倍。对于这个交通灯系统,我习惯把它分成三个核心模块,就像搭积木一样,每个模块各司其职。
第一个模块是“心脏”——分频模块(Clock Divider)。我们的FPGA开发板上的晶振通常是50MHz,也就是时钟信号每秒钟震荡5千万次。这个速度对我们控制秒级的红绿灯来说太快了。所以,我们需要一个“减速器”,把50MHz的时钟分频成1Hz(每秒一次)的时钟。这个1Hz的时钟将作为整个系统状态切换和计时的基础节拍。你可以把它想象成乐队的指挥,整个系统都跟着它的拍子走。
第二个模块是“大脑”——交通灯控制模块(Traffic Light Controller)。这是整个项目的核心,我们将会用一个**有限状态机(Finite State Machine, FSM)**来实现。状态机是数字逻辑设计的精髓,特别适合描述这种有明确步骤和状态转移的系统。我们的状态机有四个状态:主干道绿灯、主干道黄灯、支路绿灯、支路黄灯。状态机在1Hz时钟的驱动下,根据每个状态的计时是否完成,来决定跳转到下一个状态。同时,它还要负责输出六路信号,分别控制两个方向的红、黄、绿灯的亮灭。
第三个模块是“显示器”——数码管动态扫描模块(7-Segment Display Driver)。为了让系统更直观,我们通常会用数码管来显示当前状态的剩余时间。比如,主干道绿灯时,显示倒计时40、39、38... 这里涉及到二进制到十进制BCD码的转换,以及如何用有限的几个管脚驱动多个数码管(动态扫描)。动态扫描的原理是利用人眼的视觉暂留,快速轮流点亮每一个数码管,只要速度够快,看起来就是同时亮的。
这三个模块的关系是:分频模块为控制模块提供1Hz基准时钟;控制模块根据状态和计时,一方面输出灯控信号,另一方面将当前需要显示的时间数值传递给显示模块;显示模块则负责把这些数字在数码管上正确地展示出来。在顶层文件(Top-Level Entity)里,我们会把这三个“积木”实例化并连接起来。
4. 核心模块一:分频器设计详解
分频器是数字系统里最基础也最常用的模块之一。它的任务很明确:把输入的高频时钟clk_50M(50MHz)变成一个低频时钟clk_1Hz(1Hz)。50MHz的周期是20纳秒,而1Hz的周期是1秒,这意味着我们需要进行五千万分频。
在仿真的时候,如果我们真的去数五千万个时钟上升沿,那仿真时间会非常漫长。所以,在写测试代码时,我们通常会用一个小得多的分频系数(比如5分频或10分频)来代替,这样能极大加快仿真速度。等仿真功能正确后,准备烧录到实际硬件时,再把分频系数改回50_000_000。这是一个非常实用的技巧。
下面是一个经典的可参数化分频器的VHDL代码。我加了详细的注释,方便你理解每一行在做什么。
-- 分频模块:将50MHz时钟分频为1Hz
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_unsigned.all; -- 使用无符号数运算
ENTITY clk_divider IS
GENERIC (
DIV_FACTOR : INTEGER := 50_000_000 -- 分频系数,实际硬件用这个值
-- DIV_FACTOR : INTEGER := 5 -- 仿真时用这个小值,加快仿真速度
);
PORT (
clk_in : IN STD_LOGIC; -- 输入时钟,50MHz
clk_out : OUT STD_LOGIC -- 输出时钟,1Hz
);
END clk_divider;
ARCHITECTURE rtl OF clk_divider IS
SIGNAL counter : INTEGER RANGE 0 TO DIV_FACTOR-1 := 0; -- 分频计数器
SIGNAL clk_temp : STD_LOGIC := '0'; -- 内部时钟信号
BEGIN
PROCESS(clk_in)
BEGIN
IF rising_edge(clk_in) THEN -- 在输入时钟的每个上升沿
IF counter = DIV_FACTOR/2 - 1 THEN -- 计数到一半时翻转时钟
clk_temp <= NOT clk_temp;
counter <= 0; --


384

被折叠的 条评论
为什么被折叠?



