首页 / 资讯中心 / 文章详情

FPGA循迹小车实战:Verilog实现与Quartus II避坑指南

FPGA循迹小车实战:Verilog实现与Quartus II避坑指南 ★ FEATURED ARTICLE
简介这份资源面向电子设计入门者与FPGA学习者提供一套基于Verilog与Quartus II的循迹小车完整工程用于解决红外传感器数据解析、行驶方向决策与电机控制等硬件逻辑设计问题。压缩包共137个文件约4.57MB以Quartus II工程文件为主包括qpf、qsf工程配置v源文件与vt、vmake仿真相关文件sof、pof比特流以及大量cdb、hdb、rdb等综合与布局布线中间数据库和rpt报告另含readme说明与testbench验证代码覆盖设计输入、综合、仿真到下载的完整流程。已有3233人学习下载适合课程设计、竞赛小车与自学FPGA的读者参考。工程可直接在Quartus II中打开编译借助testbench观察传感器输入与电机输出波形理解循迹状态机与PWM调速逻辑并对照报告排查时序与引脚分配问题是少见的软硬件结合实战案例。1. 从“传感器一抖就冲出赛道”说起FPGA 循迹小车到底要解决什么做过循迹小车的人大多有过这样的经历红外传感器在赛道边缘抖了一下单片机轮询没跟上车头一偏就压线冲出赛道。用 MCU 做循迹本质上是“采样—判断—输出”串行执行一旦中断被占用或者程序跑飞实时性就打折扣。而 FPGA 用纯硬件并行处理多路传感器可以同一时钟沿采样、同一时钟沿输出 PWM响应延迟稳定在几个时钟周期内这正是循迹场景最看重的确定性。这套方案面向两类人一是要做 FPGA 课程设计、数字系统综合实验的学生二是想用 Verilog 把“感知—决策—执行”整条链路走通、交一份完整方案的从业者。核心关键词就是 verilog、quartus ii、循迹小车、fpga——四个词对应四件事用什么写、在哪综合、做什么、跑在什么平台上。下面按需求、架构、模块实现、避坑、验收的顺序把这份方案拆到能照着搭的程度。2. 需求与选型为什么用 FPGA 而不是单片机器件和工具链怎么定2.1 先把功能需求钉死别一上来就写代码循迹小车的需求看起来简单但真正落到 FPGA 上必须先把指标量化否则后面模块划分全是拍脑袋。我一般会先列一张需求表把“能跑”翻译成“可测量”需求项量化指标对应设计点循迹精度能识别 35 路红外传感器的黑白状态传感器采样与消抖模块响应实时性从传感器变化到 PWM 占空比更新 ≤ 10 ms并行采样 组合决策电机控制双路直流电机PWM 频率 120 kHz 可调PWM 生成模块速度调节至少 3 档基础速度分频参数化调试可视板载 LED 指示传感器状态与运行模式状态指示模块复位与安全上电复位、急停按键复位同步模块这张表的价值在于它直接决定了后面要写几个模块、每个模块的输入输出是什么。很多同学翻车的起点就是需求没定就开写写到一半发现“要不要加消抖”“PWM 频率定多少”全得回头改。2.2 FPGA 与 MCU 的取舍不是谁更强而是谁更合适循迹小车用 MCU 完全能做而且成本更低。选 FPGA 的理由通常有三条第一多路传感器并行采样天然适合硬件描述语言第二PWM、编码器计数、消抖这些逻辑可以同时跑互不抢占第三课程设计本身要求体现数字系统设计能力FPGA 能把“组合逻辑 时序逻辑 状态机”完整展示出来。反过来说如果只是做一个能跑就行的小车MCU 更省事。所以这套方案的定位很明确它是一份数字系统设计训练循迹只是载体。想清楚这一点后面就不会纠结“为什么不用现成库”。2.3 器件与工具链Quartus II 下的最小可行配置工具链锁定 Quartus II语言用 Verilog-2001器件选型按实验室常见的中低端 FPGA 开发板来定通常需要满足至少 30 个可用 I/O5 路传感器 2 路 PWM 2 路方向 LED 按键 时钟有 50 MHz 板载晶振支持 3.3 V I/O 电平。工程建立时几个关键设置别漏# Quartus II 工程关键设置在 Assignment Editor 或 .qsf 中体现 # 1. 顶层实体与器件族 set_global_assignment -name TOP_LEVEL_ENTITY top_tracking_car set_global_assignment -name FAMILY Cyclone IV E # 2. 时钟约束板载 50 MHz 晶振 create_clock -name clk -period 20.000 [get_ports clk] # 3. 未使用引脚设为三态输入避免误驱动 set_global_assignment -name RESERVE_ALL_UNUSED_PINS AS INPUT TRI-STATED # 4. 配置方式按开发板实际选择JTAG 调试 / AS 固化这段约束的含义create_clock周期 20 ns 对应 50 MHz是后续所有分频和计数的基准未用引脚设三态输入是血泪经验早期没设烧录后个别引脚电平冲突现象是“程序没错但行为诡异”查了半天才发现是引脚分配问题。器件族和配置方式按实际开发板改不要照抄。提示Quartus II 版本与器件支持是绑定的选器件前先确认手头版本是否支持该器件族否则综合阶段直接报错白白浪费时间。3. 总体架构设计四层数据流与六个模块的职责划分3.1 从传感器到电机的数据通路整套系统按数据流分成四层感知层红外传感器输入、决策层循迹判断与速度决策、执行层PWM 与电机方向、辅助层复位、时钟、指示。六个模块的职责划分如下sensor_sync传感器输入同步与消抖把异步的 GPIO 电平变成干净的内部信号track_decision根据多路传感器组合判断当前偏差方向输出转向指令speed_ctrl把转向指令映射成左右轮的目标占空比支持档位调速pwm_gen参数化 PWM 发生器双路独立占空比motor_drive把 PWM 与方向信号组合成电机驱动接口top_tracking_car顶层例化与连线不含逻辑。这种划分的好处是每个模块都能单独仿真、单独验收。答辩时老师问“你这个消抖怎么验证的”你可以直接拿出sensor_sync的测试波形而不是含糊说“整体跑通了”。3.2 时钟域与复位策略系统只有一个时钟域50 MHz 板载晶振经clk_div分出 1 kHz 的采样节拍和 PWM 计数节拍。单时钟域是刻意为之——多时钟域会引入跨时钟同步问题对循迹这种毫秒级响应的场景毫无必要反而增加翻车概率。复位采用异步复位、同步释放的经典结构// 异步复位同步释放避免复位释放时的亚稳态 module reset_sync ( input wire clk, input wire rst_async_n, output wire rst_sync_n ); reg rst_meta, rst_sync; always (posedge clk or negedge rst_async_n) begin if (!rst_async_n) begin rst_meta 1b0; rst_sync 1b0; end else begin rst_meta 1b1; // 第一级打拍释放沿同步到 clk rst_sync rst_meta; end end assign rst_sync_n rst_sync; endmodule两级触发器的作用是把复位释放沿对齐到时钟避免各模块复位释放时刻不一致导致的状态机跑飞。参数上没什么可调的rst_async_n接按键或上电复位电路即可。这个模块虽小但它是后面所有时序逻辑稳定工作的前提别省。3.3 参数化设计让一套代码适配不同赛道赛道宽度、传感器数量、电机特性在不同实验条件下会变硬编码是自找麻烦。我一般把关键参数提到顶层用parameter定义module top_tracking_car #( parameter CLK_FREQ 50_000_000, // 板载时钟频率 parameter PWM_FREQ 10_000, // PWM 频率 10 kHz parameter SAMPLE_DIV 50_000, // 1 kHz 采样节拍分频 parameter DEBOUNCE_N 8 // 消抖连续采样次数 )( input wire clk, input wire rst_n, input wire [4:0] sensor_in, // 5 路红外1 为黑线 input wire [1:0] speed_sel, // 速度档位 output wire pwm_l, pwm_r, output wire dir_l, dir_r );PWM_FREQ决定电机驱动是否啸叫10 kHz 以上人耳基本听不到SAMPLE_DIV决定循迹响应节拍1 kHz 对小车足够DEBOUNCE_N是消抖强度赛道反光强时适当加大。把这些参数集中管理换赛道只改顶层不动子模块。4. 核心模块实现消抖、决策、PWM 三段关键逻辑4.1 传感器同步与消抖把抖动挡在决策之前红外传感器输出是异步信号直接进决策逻辑会引入亚稳态和毛刺。标准做法是“两级同步 计数消抖”module sensor_sync #( parameter DEBOUNCE_N 8 )( input wire clk, input wire rst_n, input wire [4:0] sensor_raw, output reg [4:0] sensor_clean ); reg [4:0] sync0, sync1; reg [3:0] cnt [0:4]; // 每路独立计数器 // 两级同步消除亚稳态 always (posedge clk or negedge rst_n) begin if (!rst_n) begin sync0 5b0; sync1 5b0; end else begin sync0 sensor_raw; sync1 sync0; end end // 连续 DEBOUNCE_N 拍一致才更新输出 integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sensor_clean 5b0; for (i 0; i 5; i i 1) cnt[i] 4d0; end else begin for (i 0; i 5; i i 1) begin if (sync1[i] ! sensor_clean[i]) begin if (cnt[i] DEBOUNCE_N - 1) begin sensor_clean[i] sync1[i]; cnt[i] 4d0; end else begin cnt[i] cnt[i] 1b1; end end else begin cnt[i] 4d0; end end end end endmodule逻辑说明sync0/sync1两级触发器把异步输入对齐到clk每路独立计数器在“输入与当前输出不一致”时累加连续DEBOUNCE_N拍都一致才翻转输出中途恢复一致则清零。参数DEBOUNCE_N越大越稳但响应越慢8 拍在 1 kHz 节拍下约 8 ms是精度和响应的折中。注意计数器位宽要能覆盖DEBOUNCE_N这里 4 位最大 15够用。4.2 循迹决策用组合逻辑表达“偏差方向”5 路传感器从左到右排列黑线压在哪几路上就代表车相对赛道偏了多少。决策逻辑本质是一张真值表用case表达最直观module track_decision ( input wire [4:0] sensor, // 1 表示检测到黑线 output reg [1:0] turn // 00 直行 01 左转 10 右转 11 丢线 ); always (*) begin case (sensor) 5b00100: turn 2b00; // 正中直行 5b01100, 5b01000, 5b11000: turn 2b01; // 偏左左转 5b00110, 5b00010, 5b00011: turn 2b10; // 偏右右转 5b00000: turn 2b11; // 全白丢线 default: turn 2b00; // 多路同时触发按直行兜底 endcase end endmodule这里的设计取舍default分支不能省综合工具对未覆盖状态的处理可能产生锁存器这是组合逻辑的经典坑。多路同时触发比如十字路口按直行兜底是循迹场景下最稳妥的策略。如果赛道有急弯可以把turn扩展成三档微左、急左但真值表会变长建议先用两档跑通再迭代。4.3 PWM 生成与电机驱动占空比怎么算、方向怎么给PWM 用计数器比较法周期由PWM_FREQ决定占空比由duty输入决定module pwm_gen #( parameter CLK_FREQ 50_000_000, parameter PWM_FREQ 10_000 )( input wire clk, input wire rst_n, input wire [7:0] duty, // 0~100百分比 output reg pwm_out ); localparam PERIOD CLK_FREQ / PWM_FREQ; // 5000 reg [15:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 16d0; pwm_out 1b0; end else begin if (cnt PERIOD - 1) cnt 16d0; else cnt cnt 1b1; // 占空比比较duty 为百分比 pwm_out (cnt (PERIOD * duty) / 100); end end endmodule参数说明PERIOD是计数上限50 MHz / 10 kHz 5000duty用 0100 的百分比方便上层直接给“速度 60%”这种语义。注意PERIOD * duty在综合时是常数乘位宽要够16 位计数器配 5000 上限没问题。电机驱动侧方向信号和 PWM 组合// 左轮turn01 时左轮减速、右轮加速实现左转 assign dir_l (turn 2b01) ? 1b0 : 1b1; assign dir_r (turn 2b10) ? 1b0 : 1b1; // 占空比由 speed_ctrl 根据档位和转向查表给出转向的本质是差速左转时左轮占空比降低、右轮保持或提高。speed_ctrl里用一张小查找表把{turn, speed_sel}映射成{duty_l, duty_r}比写一堆 if-else 清晰得多。5. 避坑与排查仿真过了上板翻车的五个高频问题5.1 现象仿真波形完美上板电机不转原因通常是引脚分配或电平不匹配。FPGA 的 I/O 电平标准3.3 V LVTTL/LVCMOS与电机驱动模块的输入阈值不一致或者 PWM 引脚分配到了专用配置引脚上。解决先在 Assignment Editor 里核对每个引脚的 Location 和 I/O Standard用 LED 指示 PWM 输出是否在翻转逐级定位是“没输出”还是“输出了但驱动不认”。5.2 现象小车原地抖动或反复左右摆这是决策逻辑振荡常见于消抖不足或转向档位过激。传感器在黑白边界反复跳变决策输出跟着跳电机就抖。解决加大DEBOUNCE_N并在speed_ctrl里加一个简单的“转向保持”逻辑——检测到转向后维持若干毫秒再允许切换相当于软件上的迟滞。血泪经验是别指望调 PID循迹这种开关量场景迟滞比 PID 更直接有效。5.3 现象综合报错“multiple drivers”或行为诡异多半是同一个reg在多个always块里赋值或者组合逻辑里漏了default导致推断出锁存器。解决一个信号只在一个always块里赋值所有case写全default综合后看 Quartus 的 Warning 列表锁存器推断和多位驱动都会明确报出来别只看 Error。5.4 现象换一块板子就不工作参数硬编码或时钟频率假设错误。比如代码里默认 50 MHz换到 25 MHz 板子上PWM 频率和采样节拍全变。解决把CLK_FREQ作为顶层参数所有分频基于它计算上板前用 SignalTap 抓一下实际时钟和计数器确认节拍符合预期。这也是前面强调参数化的原因。5.5 现象丢线后小车乱冲track_decision对全白状态只给了turn11但speed_ctrl没处理这个状态电机保持上一拍占空比。解决丢线时强制降速或停车并在顶层加一个超时保护——连续 N 个采样周期都是丢线直接输出零占空比。这个保护逻辑很短但能避免小车冲出赛道撞墙答辩演示时尤其重要。6. 验收与答辩怎么证明这套设计真的跑通了验收不是“小车能跑一圈”就完事得有可复现的证据链。我一般按三步走仿真验收、上板验收、指标验收。仿真验收看三样东西sensor_sync的消抖波形输入抖动、输出干净、track_decision的真值表覆盖每个case分支都激励到、pwm_gen的占空比精度给 50% 看高电平占比。用 Quartus 自带的波形仿真或 ModelSim 都行关键是波形截图要能对应到需求表里的指标。上板验收按信号链逐级点灯先让 LED 跟随sensor_clean确认感知层正常再让 LED 指示turn确认决策层正常最后接电机看差速。逐级验证的好处是出问题时能立刻定位到哪一层而不是“小车不跑”这种笼统现象。指标验收用一张表对照需求验收项方法合格标准消抖效果手动抖动传感器看 LED输出无毛刺响应延迟SignalTap 抓采样到 PWM 更新≤ 10 msPWM 频率示波器测电机引脚与设定值误差 5%循迹通过标准赛道跑 3 圈不脱线、不撞墙丢线保护人为移出赛道1 s 内停车答辩时老师最爱问的两个问题“你这个消抖为什么是 8 拍不是 4 拍”“丢线了怎么办”——前者答“8 ms 响应与稳定性的折中实测 4 拍仍有抖动”后者直接指超时保护模块。能答到参数层面说明你是真做过不是拼凑的。从那以后我每次交 FPGA 项目都强制自己先跑一遍“仿真—点灯—接执行器”三级验收再谈整体演示。这套流程救过我不止一次也让我在答辩时心里有底。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站