
简介基于FPGAVerilog的寻迹避障小车完整工程资料以Basys2开发板为核心面向电子类课程设计、FPGA初学者和智能车竞赛备赛者。整套方案把蓝牙通信、红外寻迹和避障判断整合在统一的控制逻辑中实现无线遥控、自主循线与躲避障碍的联动功能。压缩包共518个文件约1.99MB包含Verilog源程序、比特流配置、引脚约束文件和集成开发环境工程等核心内容同时附有综合实现过程生成的日志、时序报告和超文本说明便于核对工程流程、排查错误或了解工具原理。工程内部模块化程度较高顶层模块负责例化连接分频模块产生工作时钟蓝牙接收模块解析遥控指令避障和寻迹模块分别处理障碍检测与路线跟踪。已有4071人学习适合需要完整参考工程、快速复现硬件逻辑或在此基础上扩展功能的读者。 做寻迹避障小车很多人第一反应就是单片机配个红外对管便宜又简单。但我偏要在这个题目上换个玩法——用FPGA用Verilog把整个控制逻辑做成硬件电路。这个项目做完之后你会发现FPGA做小车不是炫技而是真正能把“并行处理”“时序确定性”这些概念落到实处的东西尤其是在多路传感器同时接入、多任务需要同时处理的时候优势非常明显。先说说这个项目适合谁对FPGA有一定基础、想找一个小而完整的工程练手的人或者正在准备毕业设计、电子竞赛需要一个能展示“硬件思维”的项目来做技术储备的人。它需要你接触传感器信号采集、PWM电机驱动、状态机设计、异步信号同步化处理几乎涵盖了一个中等规模数字系统设计的全部要素。这篇文章会把我从方案选型到最终调试踩过的坑、想明白的道理全部写出来。1. 项目整体思路与方案选型1.1 为什么用FPGA做小车并行与确定性的胜利单片机做小车逻辑是“轮询”的CPU一条一条地执行指令先读传感器再算控制最后输出PWM。这个过程本身没有错但当车速提高、传感器数量增多、控制策略变复杂时CPU的负载会迅速上升而且任何一个中断响应不及时都可能导致小车冲出赛道。FPGA的本质是一大堆可以同时工作的逻辑单元每个传感器通道可以独立占用一组逻辑资源电机控制可以由独立的硬件定时器完成寻迹算法和避障算法在硬件上天然并行。打个比方单片机就是一个只能一个人干活的小店忙的时候什么都得排队FPGA是一间流水线工厂采购、加工、质检、包装各条线同时运转。对于寻迹避障这种“多输入、多输出、要求实时响应”的场景FPGA的思维方式天然贴合。1.2 整体框架传感器、决策、执行三大部分这小车的整体结构分成三层感知层、决策层、执行层。感知层负责采集赛道信息和障碍物信息决策层在FPGA内部完成逻辑判断执行层把决策变成电机的实际运动。三层之间通过标准接口互联每个部分都可以独立调试这也是硬件工程师做项目的基本素养不要一开始就整个系统联调那是给自己挖坑。感知层我用了两路模块红外反射式寻迹传感器阵列和超声波测距模块。红外传感器负责检测黑线超声波负责探测前方障碍物。决策层是整个FPGA逻辑的核心包括信号同步、滤波、状态判断、PWM生成等模块。执行层是双路直流减速电机配上驱动芯片靠PWM控制转速靠方向引脚控制正反转。系统框图文字版 红外寻迹传感器阵列 → 信号同步与滤波 → 寻迹状态判断 → 综合决策状态机 → PWM/方向输出 → 电机驱动 → 左右轮 超声波模块 → 回波计时 → 距离计算 → 避障逻辑 ↗这个结构看起来平平无奇但里面每个环节都有值得展开的细节。后面我会逐个拆开讲。2. 核心硬件设计与模块规划2.1 寻迹传感器阵列为什么不用单路对管入门教程里常用的方案是三个红外对管并排安装中间那个对准黑线两边偏离。三个管子输出三路高低电平逻辑简单代码好写。但这种方案的上限很低一旦车速提起来或者弯道半径小三路传感器能够提供的有效状态非常有限中间状态判断很粗糙小车容易在两个值之间来回抖动。我选的是五路数字量输出的红外阵列模块市面上常见的那种带比较器输出的模块每路独立输出高/低电平。五路的好处是能把“小车与黑线的相对位置”细分成更精确的状态从而让转向决策更平滑。五路从左边到右边编号为L2、L1、C、R1、R2状态映射做起来也直观。单独看每一路它是一个红外发射管加接收管对地构成的分压电路输出经过电压比较器后变成0/1数字信号。当发射管对准白色地面时反光强接收管导通比较器输出低电平或者高电平取决于模块设计对准黑色线条时反光弱输出相反电平。用之前务必确认模块输出极性——我见过不少人代码写反了导致小车“找黑走”变成了“见黑躲”。2.2 超声波避障模块给小车加一双眼避障方案有红外避障、激光测距、超声波测距几种。红外避障模块成本最低但方向性差、环境光干扰大在室内白天阳光直射时经常误报。激光测距精度最高但价格对一个小车项目来说偏贵。超声波是折中成本可控、原理清晰、能测真实距离特别适合“前方有障碍物就减速/转向”这种宏观避障需求。我用的HC-SR04超声波模块工作方式是电平触发给Trig引脚一个10微秒以上的高电平模块内部发出8个40kHz的超声波脉冲同时把Echo引脚拉高直到收到回波再拉低。Echo引脚高电平的持续时间乘以声速340 m/s再除以2就是障碍物距离。距离(cm) Echo高电平时间(us) × 0.034 / 2 T_us × 0.017这里有个容易被忽视的点Echo高电平时间最大值对应距离约4米左右对小车场景完全够用。真正要注意的是测量周期不能太短——声波传播本身有时间延迟连续快速触发会导致上一次的回波还没回来下一次已经发出去了测量结果直接错乱。我实测的稳定触发周期是50ms以上换算成小车速度是足够实时避障的。2.3 电机驱动与PWM控制TB6612比L298N好用驱动芯片我在L298N和TB6612之间做过对比。L298N是老牌芯片驱动能力强但压降大、发热明显而且逻辑控制引脚电平兼容性一般用FPGA的3.3V IO去控制它的逻辑输入不太保险。TB6612FNG是东芝的驱动芯片体积小、压降低、逻辑输入可以直接接3.3V双路H桥驱动两个直流电机刚刚好。TB6612的控制方式很标准每路电机有AIN1/AIN2两个方向引脚一个PWMA速度引脚。方向引脚决定正反转PWM引脚决定占空比。两张表格可以总结清楚模式AIN1AIN2PWMA电机状态正转10占空比前进/右轮正转反转01占空比后退/右轮反转刹车11任意快速制动自由00任意惯性滑行控制逻辑在Verilog里实现本质上就是一个组合逻辑加一个PWM生成器。PWM频率我选10kHz这个频率对直流电机来说是甜点区太低会听到明显的啸叫和顿挫感太高驱动芯片的开关损耗会变大。10kHz听不到声音电机响应也线性实测下来运转非常安静。3. Verilog核心逻辑实现与状态机设计3.1 顶层架构模块化设计的基本功FPGA项目最忌讳的就是把所有逻辑堆在一个顶层文件里。我的程序按功能拆成四个模块uart_debug调试用串口发送这个后面说、line_tracker寻迹采集与状态映射、ultrasonic_driver超声波触发与距离计算、motor_controlPWM生成与方向控制。顶层只负责例化这四个模块、连接信号、分配管脚。每个模块的接口尽量做成标准的输入输出方便单独仿真和复用。例如ultrasonic_driver对外就四个信号clk系统时钟、trig触发输出、echo回波输入、distance_cm距离值输出里面有多少计数器、多少状态外界完全不需要关心。这种“黑盒化”设计思维是FPGA工程化和单片机编程最大的区别——单片机到处是全局变量FPGA则强调模块间信号接口清晰。3.2 寻迹模块与滤波消抖硬件层面解决毛刺问题寻迹传感器的输出虽然是数字信号但绝对不是什么干净的0/1。传感器在黑白边缘附近时比较器输出会抖动产生大量毛刺另外电机启动瞬间电流波动也可能导致电平波动。如果直接把原始信号拿来做状态判断小车会在直道上左右颤动像喝醉了一样。处理手法人人都知道滤波。但FPGA里的滤波和单片机的软件延时消抖不太一样。我写了一个简单的“连续采样确认”逻辑对每一路输入信号用系统时钟连续采样多次只有当连续N个周期都读到同一个电平时才认为这个电平是有效的。N的大小决定实时性和稳定性的平衡。代码示意单路输入消抖动三拍确认 reg [2:0] sync_ff; reg filtered; always (posedge clk) begin if (rst) sync_ff 3d0; else begin sync_ff[0] raw_in; sync_ff[1] sync_ff[0]; sync_ff[2] sync_ff[1]; if (sync_ff) filtered 1b1; else if (~|sync_ff) filtered 1b0; // 中间态保持不变继续积攒足够多的同电平样本 end end理解这个逻辑的关键在于它不是简单地“读一次就用”而是引入了时间窗口的概念。只有当一个电平持续足够长的时间才采信这天然地滤掉了窄脉冲毛刺。实现上用移位寄存器加一个与门、一个或非门即可资源占用极低。3.3 寻迹状态判断五路传感器映射到运动模式五路传感器滤波之后就进入组合逻辑映射。这里我定义了一组寻迹状态全白或全黑视为丢线进入搜索模式只有C亮即只有中间传感器检测到黑线直线前进L1、C亮轻微偏左小左转L2、L1、C亮严重偏左大左转右侧同理为什么这样映射关键在于人字纹赛道、十字交叉口这类特殊路况。比如十字路口时五路可能全部压线或者全部悬空如果状态机不处理小车会冲出去或者原地打转。我的处理是检测到全亮认为是十字路口直接执行直行因为这种情况下通常赛道是贯通的检测到全灭认为是飞线或大弯出界进入摇头搜索模式控制电机左转右转交替寻线。这个逻辑在Verilog里就是一个case语句加几个判断分支实现起来并不难难的是要根据自己赛道的实际情况去调整优先级。巡线赛道的实际路况千差万别任何状态映射都需要实测调参。3.4 超声波避障逻辑决策优先级的设计避障逻辑和寻迹逻辑本质上是一个优先级问题正常情况下听寻迹的当前方距离小于阈值时避障逻辑接管。用状态机的语言来描述就是有一个主状态寻迹模式/避障模式避障模式下再细分具体动作停车、后退、转向。避障决策伪代码 if (distance 20) → 停车 elsif (distance 35) → 后退并右转或左转取决障碍物分布 else → 正常寻迹行驶距离阈值的选取和车速有关不能拍脑袋定。我的车在占空比60%左右时速度大约0.5m/s20cm的停车距离从发出停车指令到完全刹停经过状态机切换、PWM输出、电机响应、机械制动这几个环节实际刹车距离比理论上更长所以阈值留了余量。这个“停不住”的问题我后面专门讲。3.5 PWM生成与差速转向控制小车的真正细节PWM生成用计数器实现。10kHz的PWM对应时钟周期100微秒如果系统时钟是50MHz计数器需要从0数到4999再根据比较值决定输出高低电平。占空比和方向分离控制方向由AIN1/AIN2决定速度由PWM占空比决定。差速转向是寻迹小车的精髓所在左转不是简单地把方向引脚翻转而是左轮减速、右轮保持或加速利用速度差实现平滑转向。如果直接用“停左轮、转右轮”的方式小车在高速时容易因为惯性翻车或者甩尾。我的转向策略是轻微偏转时只降低一侧车轮的PWM占空比到60%左右严重偏转时把内侧轮占空比降到20%并短暂反转实现原地大幅度转向。这个策略调起来非常费时间但效果也是肉眼可见的。你在逻辑里改一个参数下载到板子上跑一圈感受是完全不一样的。我的建议是把几个关键参数基础速度、转向差速比例、严重转向比例定义成参数常量放在文件头部方便反复调。4. 实操中的典型问题与排查记录4.1 传感器信号全是乱码异步信号同步化第一次上电调试我通过串口把五路传感器的原始值发到电脑上结果数据跳动得完全没法看。一开始怀疑是传感器模块坏了换了一个还是一样。后来才意识到问题出在异步信号处理上——传感器输出的电平变化与FPGA系统时钟毫无关系直接用这个信号去采样和判断必然出现亚稳态。解决办法是标准的“两级寄存器同步”跨时钟域信号进入FPGA后先打两拍再使用。第一拍可能采到不稳定的电平第二拍理论上就能得到稳定值。虽然两拍不能完全消除亚稳态但能把亚稳态发生的概率降低到工程上可以忽略的程度。加了两级同步之后串口输出的数据干净得像仿真一样。代码示意异步输入同步化 reg [1:0] sensor_sync; always (posedge clk) begin sensor_sync[0] sensor_in; // 第一拍可能亚稳态 sensor_sync[1] sensor_sync[0]; // 第二拍基本稳定 end // 后续逻辑只使用 sensor_sync[1]从这之后我养成一个习惯所有外部输入的信号不管传感器、按键、还是通信接口一律先进同步器。这个习惯救了我后面好多次。4.2 仿真正常上板不动综合工具与仿真的差异我至今记得最抓狂的一次调试寻迹逻辑在Vivado仿真里跑得完美无缺波形漂亮每个状态跳转都符合预期。下载到板子上之后小车完全不动传感器状态读回来又全是正常的。后来我逐段排查发现问题出在一个忘记复位的计数器和一条“仿真能过、综合不能过”的语句上。具体来说我在寻迹消抖模块里用了一个计数器复位时只清了一部分信号初值情况下计数器的比较条件永远不满足所以逻辑永远不跳转。仿真时因为初始态默认是X不定态仿真器按自己的规则处理了看起来一切正常上板后实际电路的初值是按配置比特流确定的计数器进入了错误状态逻辑就卡死了。这种“仿真与综合不一致”的坑在FPGA开发里太常见了原因归根结底是RTL代码写得不严谨依赖了仿真器的宽容处理。解决办法没有捷径只能把所有寄存器都显式复位所有组合逻辑都写全default分支做到“仿真通过不是标准综合后行为一致才算数”。4.3 避障总是刹不住距离换算和响应延迟有一次我设置了30cm避障阈值心想怎么都够安全了。结果小车在靠近障碍物时还是直接顶上去才停。排查后发现问题有两层一是超声波模块的测量周期是50ms也就是说每50ms才能更新一次距离数据而小车在这个间隔内已经跑了2.5厘米二是从检测到距离过近到PWM实际输出变化中间还隔着状态机跳转和多个时钟周期。深挖之后我意识到避障系统的“延迟”不是某个单一模块造成的而是整个链路的累计延迟。测量周期、同步打拍、状态机判断、PWM更新每一环都贡献几个毫秒累计起来就相当可观了。解决办法是提高超声波触发频率到20ms同时把避障判定阈值从30cm提高到40cm——既然物理上做不到零延迟就在逻辑上预留时间余量。4.4 电机噪声导致传感器误判电源隔离还有一个特别典型的工程问题电机一转传感器数据就乱。用示波器测传感器电源引脚发现上面叠加了大量毛刺幅度能达到几百毫伏。电机是感性负载启动和换向瞬间会产生很大的反向电动势如果传感器和电机共用同一路电源这些噪声会直接串到传感器的供电和参考电压上导致比较器误翻转。解决思路不是去“过滤”噪声而是从根源上切断耦合路径电机电源和逻辑电源分开供电各自用独立的稳压芯片电机驱动芯片的电源引脚旁边加大容量电解电容吸收瞬时电流传感器模块和FPGA之间的信号线不要和电机线绑在一起走。这一套做完之后传感器误判的问题几乎消失了。4.5 编译报错module not found文件添加遗漏Vivado里新建工程的时候如果你把Verilog文件直接拖进工程但忘了加到编译的source列表综合时会报“module xxx not found”。这个错误我在第一次建工程时就遇到过。原因是Vivado有“自动添加文件”和“手动添加文件”两种模式从工程目录拖动文件到source视图时文件可能只是被加入工程目录而没有真正添加到编译目标。解决办法是右键source视图空白处选择“Add Sources”用对话框添加文件或者直接在Sources窗口确认文件是否出现在Design Sources列表中。这个错误虽然简单但对新手来说极其劝退——代码明明没有语法错误却报找不到模块。现在每次新建工程我加完文件之后第一件事就是检查Design Sources里有没有全部列出。5. 调试工具与效率心得5.1 串口调试给FPGA加一个“眼睛”FPGA里跑的逻辑对局外人来说是个黑盒下载到板子上之后你看不到内部状态也不知道传感器读回来的值到底是什么。所以我在系统里加了一个UART发送模块把五路传感器原始值、超声波距离值、当前状态机编号打包成帧从串口发到电脑上。这个调试手段帮了大忙——所有看起来“不可见”的问题一旦能输出到电脑上就变成了可以分析的日志。UART发送模块本身也很简单波特率生成器加移位寄存器。我用的波特率是115200时钟分频后按位发送起始位1位、数据8位、停止位1位。整个模块不到50行Verilog但价值巨大。调试时在电脑上开一个串口助手就能实时观察小车内部决策状态。5.2 FPGA内部的逻辑分析仪ILA的使用如果你用的Xilinx器件Vivado自带集成逻辑分析仪ILA可以把你关注的内部信号在板子上实时抓取出来看波形。用ILA调试这东西第一次用就爱不释手你不需要在电脑上装任何硬件探针直接在工程里例化一个ILA核把想看的信号接上去综合下载之后就能在硬件上抓波形。但ILA有个前提信号必须是被综合保留的。如果你在综合时开了大优化某些中间信号可能会被优化掉ILA抓不到。稳妥的做法是给需要观察的信号加上(* keep true *)约束告诉综合工具不要优化这个信号。我用ILA抓通过一次PWM输出的波形发现占空比和设置值完全对应心里立刻踏实了——软硬件的问题定位就靠这些手段一件件缩小范围。5.3 上板前的仿真清单提前拦截低级错误分享一下我现在养成的习惯上板之前必做一轮“仿真清单”检查。第一所有外部输入信号有没有做两级同步第二所有寄存器的复位值是不是都明确赋值了第三组合逻辑有没有写全case分支第四PWM比较值和计数器的位宽是否配套会不会溢出第五跨模块的握手信号有没有漏加。这五条是我几次被坑之后总结出来的每一条都对应一次真实的翻车经历。曾经有一次因为计数器位宽不够导致PWM输出频率完全错乱小车电机发出刺耳的尖叫查了一个多小时才定位。从那以后位宽检查成了我的基本盘。你现在让我带新人做FPGA项目头三天不看别的就练这个检查清单的养成。6. 调参与实测记录6.1 刚上电的小车是会“抽风”的复位与初值第一次把完整代码下载到板子上按下电源开关小车突然猛地原地转了一圈然后才稳定下来。原因是FPGA上电瞬间各个模块处于未定义状态电机驱动引脚的电平组合进入了“反转”模式直到全局复位信号完成所有寄存器清零才恢复正常。这个现象在上电时非常常见。解决办法是在顶层模块里加一个上电复位电路用一个计数器从0开始计数计到某个值比如几毫秒之后才释放复位信号。这样FPGA配置完成后所有模块会先被强制复位一小段时间等一切稳定之后再开始正常工作。这个简单的时序设计可以避免绝大多数上电乱动的问题。6.2 速度参数调优追求“不抖动的最快速度”寻迹小车调参的核心指标是“在稳定循迹前提下的最大直线速度”。速度太低走起来没意义速度太高在弯道就会冲出赛道。我做了个简单的对照实验固定其他参数只调整PWM基础占空比记录不同速度下的跑圈表现。基础PWM占空比直线表现弯道表现结论40%稳定轻松过弯保守55%稳定中等弯道OK急弯微抖推荐65%稳定急弯容易冲出边缘75%有抖动大弯都难过不可用最终我落在55%附近同时为急弯准备了一个“急停转弯”逻辑——检测到L2R2同时有效说明压到了急弯边缘立即降低PWM到30%并加大差速。这个逻辑实测应对180°回头弯很有效。6.3 连续运行稳定性长时间跑下来会不会漂小车连续跑十几分钟之后会出现转向越来越不灵敏的情况。起初我怀疑是FPGA逻辑状态出了问题但用ILA抓了半天发现逻辑一切正常。后来排查才发现是电机长时间运行发热扭矩下降再加上电池电压逐渐下降同样的PWM占空比对应的实际转速变小了视觉效果就是“车变钝了”。这个问题的本质是开环控制在对抗环境变化。要彻底解决得加编码器做闭环PID但那样复杂度立刻上去一个量级。作为小项目我选择了“电压补偿”的简易方案用ADC读电池电压低于某个阈值就自动提升PWM占空比抵消电压下降的影响。这个方案虽然粗糙但很实用算是性价比极高的改进。写在最后这个寻迹避障小车项目做到最后其实已经不只是一个“小车”了。它像是一个微缩的数字系统设计全流程训练从需求分析到方案选型从模块设计到系统集成从仿真验证到上板调试每一个环节都在逼你思考“硬件是怎么工作的”而不是“代码是怎么写出来的”。我做完这个项目最大的感受是FPGA的开发思维和单片机真的不一样它逼着你养成模块化、同步化、严谨化的设计习惯任何一个不够严谨的地方都会在硬件上以最直接的方式“报复”你——不是编译报错而是小车跑飞。最后再分享一个小经验调试这类项目时我强烈建议你用串口把内部状态实时发出来同时用ILA抓波形两边对照着看。很多玄学问题一旦你同时看到“逻辑上的状态”和“物理上的输出”立刻就不玄学了。还有一个细节项目文件命名和版本管理要养成好习惯调参的时候你会频繁修改代码没有版本管理的话改到某个参数发现不如之前好的时候想回退都麻烦。这个项目虽然不算大但足以帮你建立起一套完整的FPGA工程化工作流以后再去做图像处理、高速接口这些复杂项目时这些基本功会让你少走非常多弯路。本文还有配套的精品资源点击获取