十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FPGA超声波避障小车全攻略:从Verilog时序到PWM驱动

FPGA超声波避障小车全攻略:从Verilog时序到PWM驱动 简介基于FPGA的超声波避障小车ego1工程资源面向FPGA入门学习者与电子设计竞赛备赛者适用于智能车竞赛、FPGA课程设计或毕业设计等场景。学习者需具备基本的Verilog编程与数字逻辑基础项目可帮助快速上手外设联动控制。项目利用ego1开发板100Hz时钟资源通过自定义PWM占空比控制电机转速与舵机角度并自行编写计时器获取超声波往返时间从而实时测量距离并完成避障还可扩展OpenMV色块识别模拟红绿灯控制小车出发与停止。资源包为ZIP格式约3.43MB内容以FPGA工程文件为主结构便于按模块查阅截至目前已有1047人学习下载。拿到后可以快速搭建基础小车框架理解FPGA定时器、PWM信号生成、传感器数据采集与外设联动等核心逻辑工程中提供了较完整的模块划分便于修改占空比和测距阈值也适合在此基础上进一步扩展视觉识别与路径规划功能。通过该项目可串联起FPGA开发中的时钟分频、PWM输出、定时计数等关键环节。 最近把吃灰的 EGO1 开发板翻出来完整做了个超声波避障小车项目前后折腾了两个周末。EGO1 在高校实验室里很常见核心是 Xilinx Artix-7 系列的 XC7A35T属于入门级 FPGA 开发板但做这个项目完全够用。超声波避障小车用一句话概括就是FPGA 控制超声波模块测距根据距离信息通过 PWM 调速驱动电机让小车自动前进、后退和转弯。听起来很像是单片机课的活儿但换成 FPGA 来做完全是另一种体验。整个项目会强迫你把 Verilog 时序、状态机设计、PWM 生成、跨模块协同这些基本功全部过一遍而且它不像流水灯那样点完就完是一台真能在地板上跑起来的机器反馈感很强。这篇文章把我的完整实现过程、设计思路和踩坑记录写下来给手头有 EGO1、想做点综合性数字逻辑项目的朋友做个参考。1. 项目架构与方案选型1.1 为什么要用 FPGA 而不是单片机如果只是想让小车跑起来STM32 或者 Arduino 会更顺手。但换到 FPGA 上这个项目的性质就变了你不再是在写控制程序而是在设计一套硬件逻辑电路。超声波测距时的计数器、生成 PWM 的分频器、做避障决策的状态机这些在单片机上只是对外设寄存器的配置在 FPGA 里则需要你自己用 Verilog 一点一点搭出来。FPGA 还有一个天然优势是并行性。测距模块在持续测量PWM 模块在输出占空比状态机在根据最新距离做判断三个模块独立运行互不抢占。这跟单片机里“主循环里轮流执行”的思维有本质区别。做完这个项目你对“硬件并行”和“时序逻辑”的理解会比啃十遍书都深刻。这也是我推荐用 EGO1 来做这个题目的核心原因——它的学习价值远超小车本身。EGO1 板载资源很齐全LED、数码管、拨码开关、按键、排针扩展口都引出来了不需要额外设计底板很适合快速搭原型。芯片是 Artix-7 XC7A35T逻辑单元够用就算后续想往里面加摄像头图像处理、加 PID 闭环控制资源也还留有余量。1.2 系统整体数据流先把整个系统的数据流理清楚后面写代码才不会乱。超声波模块 HC-SR04 负责测距它的 Echo 引脚输出一个高电平脉冲宽度等于声波往返时间。FPGA 内部有一个测距控制模块用计数器记录这个高电平持续了多少微秒再换算成距离值。距离值同时送给两个模块一个是决策状态机根据距离判断当前应该前进、后退还是转向另一个是数码管显示模块实时把距离显示出来方便观察。决策状态机输出转向指令后PWM 生成模块会根据指令调整左右两个电机的占空比和方向引脚电平最终通过电机驱动板驱动直流电机转动。EWORD EGO1 的板载时钟一般是 50MHz 级别具体频率以你自己的原理图为准。我下面所有参数计算都基于 50MHz 展开如果你的板子晶振不同分频参数要相应调整。1.3 EGO1 的资源评估开始写代码之前我给 EGO1 的资源做了个粗略评估。这个小车项目大概需要一个 16 位左右的计数器用来测距一个 PWM 发生器代码量很少一个 4-6 状态的状态机再加上数码管扫描显示。逻辑单元消耗非常有限XC7A35T 跑这个项目属于“杀鸡用牛刀”但正因为资源富余我们可以把代码写得清晰、可读而不必抠门到用各种技巧去省资源。IO 方面要注意EGO1 的排针供电和 IO 电平一般是 3.3V 标准而 HC-SR04 超声波模块是 5V 供电Echo 输出也是 5V 高电平。这个电平差如果不管轻则逻辑误判重则损伤 FPGA 引脚后面我会专门讲处理方案。2. 硬件搭建与电气注意事项2.1 底盘与电机选择底盘我用的是一块常见的亚克力 2 驱小车底盘两个 TT 减速电机加一个万向轮。TT 电机自带减速齿轮箱扭矩够大在瓷砖地面和短毛地毯上都能跑价格也便宜坏了直接换不心疼。如果你手头是 4 驱底盘也没关系控制逻辑和 2 驱完全一样只要把左右两侧电机并联接到驱动板上就行。电源部分我是分开供电的电机用两节 18650 锂电池串联供电约 7.4VEGO1 开发板用 USB 口单独供电。两块电源的地线必须接到一起否则 FPGA 和驱动板之间的控制信号没有参考电位逻辑电平会乱跳这是我在实际中踩过的坑。2.2 电机驱动板选型电机驱动板我用的 TB6612FNG比 L298N 更适合这个场景。TB6612 内部是 MOSFET 构成的 H 桥导通压降低发热小工作电压支持 2.7V 到 5.5V 的控制逻辑和 3.3V 的 FPGA IO 直连没有问题。L298N 的压降太大用电池供电时会造成电机端电压明显偏低而且它体积大放在小车底盘上很占地方。驱动板接线也不复杂每个电机有两根控制方向线IN1/IN2和一根使能线PWM。FPGA 输出 PWM 信号接到驱动板的 PWMA/PWMB控制电机转速IN1/IN2 控制正反转。PWM 频率我习惯设在 10kHz 左右这个频率听不到高频噪声电机响应也足够快。如果频率太低电机会发出“嗡嗡”的噪声而且转动不平顺。下面是 TB6612 的真值表逻辑写状态机的时候会用到IN11、IN20 时电机正转IN10、IN21 时反转IN1IN21 时刹车IN1IN20 时滑行。FPGA 侧只要记住这四种组合就够了。2.3 超声波模块与电平匹配问题HC-SR04 的工作原理不复杂Trig 引脚收到一个大于 10us 的高电平脉冲后模块自动发出 8 个 40kHz 的超声波脉冲然后 Echo 引脚输出高电平高电平持续时间就是声波从发射到遇障碍物反射回来的总时间。距离 高电平时间us / 58单位是厘米。这个 58 是从声速 340m/s 推导出来的经验常数很好用。电平匹配是关键。HC-SR04 的 VCC 接 5VEcho 引脚在回波时会拉到接近 5V而 EGO1 的 IO 是 3.3V 电平直接把 5V 信号接进去有风险。最稳妥的做法是加一个电阻分压Echo 输出串联 2kΩ 电阻再接一个 1kΩ 电阻到地在分压点取信号给 FPGA。这样 5V 会把分压点拉到约 3.3V安全又有余量。Trig 引脚用 FPGA 的 3.3V 高电平驱动通常也能正常触发如果发现模块没反应可以用万用表量一下 Trig 电压再决定要不要加电平转换。供电也要注意HC-SR04 如果直接从 FPGA 排针取 3.3V测距结果会不稳定建议从 5V 电源轨给它供电地线照样要和 FPGA 共地。2.4 引脚规划我当时的引脚规划如下你可以对照自己的板子调。原则上避开 EGO1 上已经被板载外设占用的引脚具体以开发板原理图为准。信号方向说明clk输入板载时钟50MHzrst_n输入复位按键低有效trig输出接 HC-SR04 Trigecho输入接 HC-SR04 Echo分压后pwm_l输出左电机 PWMAin1_l / in2_l输出左电机方向控制pwm_r输出右电机 PWMBin1_r / in2_r输出右电机方向控制seg[7:0] / sel[3:0]输出数码管段选和位选接线之前一定先看原理图核对每个排针的实际定义不同批次的 EGO1 排针丝印可能有差异。我吃过一次亏想当然把信号接到一个标注为 IO 的引脚上结果那个引脚连着板载按键电平一直拉高折腾了大半个小时才发现。3. 核心逻辑设计与原理解析3.1 超声波测距模块的时序设计测距模块是整个项目的地基距离量得不准后面避障决策全是白搭。HC-SR04 的时序要求很明确Trig 拉高至少 10us然后拉低等待 Echo 上升沿Echo 上升沿到来后开始计数直到 Echo 下降沿停止。计数值除以分频系数就得到微秒数。在 50MHz 时钟下计数器每计 50 个周期就是 1us所以我的做法是先用一个计数器产生微秒脉冲再用另一个计数器记录 Echo 高电平持续了多少个微秒。关键 Verilog 逻辑如下我做了简化但思路是完整的reg [15:0] cnt_us; // 微秒计数器 reg [19:0] cnt_echo; // echo 高电平时间单位 us // 产生 trig 信号每 50ms 拉高 20us always (posedge clk or negedge rst_n) begin if (!rst_n) begin trig 0; end else begin trig (cnt_trig 20) ? 1 : 0; end end // echo 高电平计时 always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_echo 0; end else if (echo) begin if (us_pulse) cnt_echo cnt_echo 1; end else begin cnt_echo 0; end endus_pulse是微秒脉冲使能信号每 50 个时钟周期拉高一个周期。Echo 拉高期间cnt_echo持续累加Echo 一旦拉低计数清零同时把当前值锁存到距离寄存器里。这里的一个细节是不要把累加和清零放在同一个 always 块里做暂存否则上板后你会抓到一堆毛刺。正确做法是单独用一个always块在 Echo 下降沿把cnt_echo寄存下来。还要加一个溢出保护。当障碍物超过量程或者没有回波时Echo 会一直为低计数器停在 0看起来很正常。但如果超声波模块前面被异物挡住Echo 可能持续拉高很长时间计数器会一直累加溢出。我的处理是当计时值超过 30000即 30ms对应大约 5 米距离时直接强制把结果置为最大量程 400cm不再继续累计。这样状态机就不会拿到一个溢出的异常小值。3.2 PWM 调速与差速转向PWM 生成是 FPGA 里的常规操作。我用一个自由运行的计数器作为三角波载波占空比通过比较值控制计数器值小于占空比寄存器时输出高电平否则输出低电平。载波频率就是计数器的溢出频率10kHz 的设置是计数到 4999 后清零50MHz / 5000 10kHz。转向的方式我用的是差速转向。直行时左右轮占空比相同需要左转时左轮占空比减小、右轮保持小车就会向左偏转。如果需要原地掉头就让左右轮方向相反此时占空比控制转速可以让小车原地旋转。避障场景里原地旋转比较实用因为车体不需要额外的前进空间。reg [15:0] duty_l, duty_r; // 占空比寄存器0~4999 reg [15:0] cnt_pwm; assign pwm_l (cnt_pwm duty_l) ? 1 : 0; assign pwm_r (cnt_pwm duty_r) ? 1 : 0;占空比寄存器的值不要用阻塞赋值在状态机里直接改最好通过一个独立的使能信号在状态跳变时更新。我自己调试的时候遇到过一个现象状态机一改占空比PWM 输出立刻乱跳后来发现是状态机的组合逻辑毛刺污染了寄存器写入。加了一级打拍之后问题就消失了。3.3 避障决策状态机避障逻辑是这个项目的灵魂。我设计的 FSM 有四个状态空闲、前进、后退并左转、前进恢复。实际情况里还可以加右转状态但我的初始版本只做了固定左转逻辑更简单调试也更快。工作流程是上电进入空闲状态等待启动信号启动后进入前进状态持续读取距离值。如果距离大于 25cm认为前方安全两轮全速向前如果距离在 15cm 到 25cm 之间减速慢行防止惯性冲撞如果距离小于 15cm说明障碍物已经很近立刻停车并倒车同时左转持续 1 秒后重新进入前进状态。关键代码片段状态编码用独热码方便综合和排错localparam S_IDLE 4b0001; localparam S_FORWARD 4b0010; localparam S_BACK 4b0100; localparam S_TURN 4b1000; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; end else begin case (state) S_IDLE: if (start) state S_FORWARD; S_FORWARD: if (dist_cm 15 dist_valid) state S_BACK; S_BACK: if (back_cnt 100_0000) state S_TURN; // 50MHz 下 0.2s S_TURN: if (turn_cnt 500_0000) state S_FORWARD; // 1s default: state S_IDLE; endcase end end状态机里最容易忽略的是“每个状态的进入条件必须考虑它是否可能无法退出”。比如倒车状态如果小车一直被墙卡住倒车时间再长也退不出去这时候要靠定时器强制执行下一次状态跳转而不是等某个传感器条件。我用的是定时器超时强制跳转效果不错。3.4 数码管显示与调试辅助数码管显示距离值是非常有必要的调试手段。虽然也可以用电脑串口看但小车在地上跑的时候低头看一眼数码管比盯着串口方便得多。EGO1 板载 4 位数码管我用动态扫描的方式驱动每 1ms 切换一位视觉上四个数字是同时亮的。动态扫描的代码不复杂核心是一个 2bit 位选计数器和每个位对应的 7 段码查询表。这里有个小坑如果扫描频率太慢数码管会明显闪烁太快则可能出现拖影。1kHz 左右的扫描频率比较合适也就是每位点亮 1ms。如果你有串口模块强烈建议加一个 UART 发送模块把距离值和当前状态机状态实时发到电脑上。这样避障逻辑出问题时你能清楚看到“距离读到了什么值状态机为什么跳到了这个状态”。我在调试状态机卡死问题时就是靠串口日志定位的比单纯看波形高效太多。4. Vivado 工程创建与上板调试4.1 工程结构与代码组织Vivado 工程里我按模块划分了源文件每个模块一个 .v 文件top.v负责顶层例化、时钟分频、复位同步hc_sr04_ctrl.v管超声波时序和距离换算pwm_gen.v是双路 PWM 生成器avoid_fsm.v是避障状态机seg_display.v驱动数码管还有可选的uart_tx.v用于串口输出。这种模块化划分不是写代码方便是调试方便。每个模块都可以单独做仿真验证出了问题也能快速定位到具体模块。我建议你在写 top 之前先把测距模块单独仿真一遍用 testbench 模拟 Echo 信号确认计数值和距离换算正确再往下走。如果直接写完一个大的 top 再查波形里全是信号排错会非常痛苦。4.2 约束文件写法约束文件是整个上板环节最容易出错的地方我没有用 Vivado 的图形化引脚规划器而是直接手写 XDC 约束。基本格式是给每个 IO 指定引脚位置和电平标准set_property PACKAGE_PIN P17 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property PACKAGE_PIN R2 [get_ports trig] set_property IOSTANDARD LVCMOS33 [get_ports trig] set_property PACKAGE_PIN T2 [get_ports echo] set_property IOSTANDARD LVCMOS33 [get_ports echo] create_clock -period 20.000 -name sys_clk [get_ports clk]那个create_clock一定要写如果不写时钟约束Vivado 在时序分析时会认为时钟是默认频率可能产生误导性的时序报告。另外我给所有输出 IO 都设置了IOSTANDARD LVCMOS33这是 EGO1 的标准电平有些开发板部分 BANK 是 2.5V 的那就不能直接套用。引脚约束以你自己板子的原理图为准别照抄我这份。4.3 综合、实现与比特流生成工程创建后按顺序走 RTL Analysis、Synthesis、Implementation最后 Generate Bitstream。这个小项目没有复杂的时序收敛压力一般一次综合就能过。如果时序报告里有路径违规通常不是时钟频率问题而是跨时钟域处理没做好或者没加复位同步器。上板下载用 Hardware Manager连接 EGO1 的 JTAG 口Program Device 选择生成的 .bit 文件。EGO1 用的是 Artix-7 芯片Vivado 版本建议用支持 7 系列的最新稳定版我用的是 2021.1 和 2023.2 都顺利跑通过。4.4 实测数据记录上板后我做了几组测距数据对比用尺子量好距离再看数码管显示值。以下是实测记录实际距离(cm)数码管显示(cm)误差(cm)10111202113534-1505221001033误差基本在 2-3cm 以内对于避障场景完全够用。误差主要来自声速随温度的变化和 58 这个常数不够精细。如果你想让测距更准可以把 58 换成 58.2或者用温度传感器测环境温度修正声速。不过对避障小车来说这个精度已经绰绰有余。地面材质对超声波的影响也很大。我在瓷砖地面测试时数据很稳定换到软地毯上距离值偶尔会跳变。地毯吸收声波、多径反射严重。所以小车最终用了一段时间后我加了一层简单的软件滤波连续采 5 次距离去掉最大值最小值取中间值作为有效距离。这个改动让数据稳了很多但也让响应变慢了大概 20ms实测避障效果反而更连贯了。5. 常见问题与排查技巧5.1 Echo 信号一直为高怎么办这是超声波模块最常见的故障现象。先量模块引脚电压Echo 一直为高通常有两种可能一是 Trig 没有被 FPGA 正确拉低模块认为一直在触发二是模块供电电压不足内部逻辑处于不确定状态。第一步用示波器或万用表量 Trig 引脚确认 FPGA 输出的电平在空闲时是低电平第二步量模块 VCC确保是稳定 5V。如果两者都正常查一下共地线是否接实了。很多时候是面包板上的杜邦线松动造成的别一上来就怀疑代码。5.2 距离值跳变、偶尔爆表距离值不稳定基本不是逻辑问题是物理环境问题。超声波探头前方如果对着桌腿、门框这类有棱角的物体声波会多径反射Echo 信号的下降沿会不干净计数结果忽大忽小。另外探头朝向太偏下会把地面反射当障碍物所以安装时传感器要正对前方距离地面至少 5cm。软件上用我前面提到的“采集 5 次取中值”就能解决大部分问题。如果还想再平滑一点可以做一个滑动平均但取中值在抵抗异常值方面更有效我推荐中值滤波。5.3 电机一启动数码管闪乱或 FPGA 掉电这个故障的根因是电源。电机启动瞬间的电流冲击非常大如果 FPGA、超声波模块、电机驱动共用同一组电池供电电池内阻上会产生很大的压降FPGA 检测到供电跌落就会复位数码管自然乱跳。解决办法是“大电流设备和小 signal 电路分开供电”电机单独供 7.4VFPGA 和传感器用 USB 或单独电池供电但地线必须连接在一起。另外在电机两端并联一个 104 陶瓷电容和一个 100uF 电解电容可以有效吸收电刷火花产生的干扰实测效果非常明显。5.4 状态机卡死、小车转圈不停如果小车一直转圈说明状态机停在转向状态出不来了。原因通常是转向状态的条件只依赖于传感器读数而传感器在转向时正好看不到障碍物或者距离值一直处于避障阈值内导致退出条件永远不满足。解决方法是给每个状态都加一个最大持续时间超时无条件跳到下一状态。我前面代码里用到的back_cnt和turn_cnt就是干这个的。5.5 故障速查表现象可能原因排查方式Echo 一直为高Trig 未拉低/供电不足/共地不良量 Trig 电平量 VCC检查 GND距离值跳变多径反射/安装角度不对调整探头朝向加大于 5cm 离地高度软件滤波数码管乱跳电机电源干扰/共地不良分开供电加去耦电容检查地线小车转圈不停状态机退出条件不满足增加超时强制跳转逻辑个别电机不转驱动板使能引脚没接 PWM检查 PWMA/PWMB 接线和占空比寄存器值FPGA 反复复位电池供电跌落换容量更大的电池独立供电我在做这个小车过程中最强烈的感受是FPGA 项目的调试节奏和单片机很不一样。单片机出 bug 你可以在线打断点看变量FPGA 出 bug 你得靠波形、LED、串口这些最原始的反馈去推断内部状态。但也正因为如此做一次就能对时序、状态、跨模块数据流有特别扎实的体会。后面如果你想继续扩展我建议按这个顺序来先加一个舵机云台让超声波模块可以左右扫描避障逻辑从“只往前看”变成“看三个方向”然后加一路编码器做电机测速再写一个简单的增量式 PID 闭环让小车走直线不跑偏更远一点的玩法是把摄像头模组接进来做图像识别EGO1 的资源跑一些简单的图像处理算法也已经够入门了。最后分享一个实用技巧把距离阈值、转向持续时间、PWM 频率这些参数用parameter定义在顶层模块里调参时只改顶层不用翻各子模块。我测试不同地面材质时光调阈值就试了七八组参数这个设置帮我省了不少事。做硬件项目调试的便利性往往会决定你能把这个项目推进到多深。本文还有配套的精品资源点击获取
返回列表