news 2026/9/20 0:33:13

CPLD循迹小车实战:VHDL编写PWM调速与红外传感器控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CPLD循迹小车实战:VHDL编写PWM调速与红外传感器控制

简介:基于CPLD的智能小车循迹控制系统课程设计报告书,面向电子技术、EDA课程设计及可编程逻辑器件初学者,完整展示从任务要求到系统实现的全流程设计思路。内容涵盖红外循迹模块、电源模块、CPLD控制模块、驱动模块与直流电机等核心电路的工作原理,重点介绍红外传感器经LM339电压比较器向CPLD传递偏离信号、通过PWM与主控模块控制L298N驱动电机实现自动循迹的设计方法。资源包内为1个doc文档,大小约3.79MB,包含报告正文、电路结构图、CPLD顶层电路图、仿真波形、实验照片与调试记录等完整图文素材,可直接作为同类课题的报告撰写与硬件调试参考。已有77人浏览学习,文档章节结构清晰,从任务与要求、方案论证到实验结果分析、调试问题解决与改进建议均有涉及,适合用于快速理解智能循迹小车的CPLD实现方案并借鉴其文档组织方式。

1. 用 CPLD 而不是单片机做循迹小车,到底在学什么?

拿到“智能小车循迹”课程设计题目,很多人第一反应是上 STM32F103ZET6 或 51 单片机,毕竟库函数、例程一抓一大把。但题目里“用可编程逻辑器件 FPGA/CPLD 设计实现”这一条,直接把要求拉回到了数字电路基本功:你要自己定义传感器电平怎么变成速度字,PWM 占空比由谁产生,组合逻辑和时序逻辑的边界在哪里。这份课设以 EPM7128SLC84-6 为核心,用四路红外对管加 LM339 电压比较器识别黑线,再用 L298N 驱动左右两组直流电机,最终实现直行、左小弯、左大弯、右小弯、右大弯和停车。想抄的人能拿到完整 VHDL 结构;想搞懂的人也能从这份设计中看到一套最小可复用的 CPLD 控制骨架,后续换摄像头循迹、五路循迹传感器,也只是换输入前端和状态表而已。

2. 四路红外 + LM339 + L298N:循迹小车硬件链路与电平约定

2.1 红外循迹模块:为什么黑线对应低电平

红外循迹模块的原理并不复杂:发射管一直发出红外光,白线反射强,接收管光电流大,输出端电压高;黑色胶带吸收红外光,接收管截止,输出端电压低。CPLD 不能直接读模拟电压,所以输出端要接一个 LM339 电压比较器,把传感器电压和 2.5V 参考电压做比较。检测到白线时输出高电平,检测到黑线时输出低电平,这样p1~p4就成了 CPLD 能直接处理的标准逻辑电平。

这个模块最容易被忽略的是参考电压的稳定性。很多实验板上用两个 10kΩ 电阻分压产生 2.5V,看起来很干净,但白纸反光率和黑胶带材质不同,固定阈值在上午和下午都不一样。常见做法是把分压电阻换成可调电位器,调到“白纸输出高、黑胶带输出低”的中间位置。我一般还会把探头离地控制在 1cm 以内,距离太高时接收管收到的环境光会盖过反射光,导致误判。

四个传感器在小车底盘上的排列顺序要和 VHDL 里的位序完全对应:p1是最左外侧,p2是左内侧,p3是右内侧,p4是最右外侧。原文里写P1~P4从左到右、1表示压线,如果你的接线把左右反过来,仿真时从波形编辑器里看不出问题,一上板小车就会对着黑线反向跑。

2.2 电机驱动:L298N 的 H 桥和使能端

L298N 是一个双 H 桥驱动器,内部包含两路独立输出。OUT1/OUT2接一组电机,OUT3/OUT4接另一组。这份设计买了四个直流电机,左右各两个,采用两两并联的方式:左侧两个电机并联到一个 H 桥,右侧两个并联到另一个 H 桥。这样每一侧的两个轮子转速一致,控制起来相当于只有左右两个执行器。

L298N 的逻辑功能可以放进一张表里:

ENA(ENB)IN1(IN3)IN2(IN4)电机状态
HHL正转
HLH反转
H同 IN2同 IN1快速停止
LXX停止

原设计把IN1/IN2固定为高电平和低电平,电机永远只朝前进方向转,CPLD 只改变ENAENB上的 PWM 占空比来调速。这个接线方式最简单,但代价就是不能后退,也没法实现“一侧轮前进、一侧轮后退”的大半径回旋。如果你希望小车能在极端路径下救回来,就得把IN1/IN2也交到 CPLD 手里。

2.3 电源与共地:别让比较器参考电压漂掉

电源模块在报告里写得很简短:L298N 输出一路电压供逻辑和电机,再供给循迹模块。实际搭电路时要分开看:电机电源VS接电池组,L298N 的逻辑电源VSS取 +5V,CPLD 开发板和 LM339 也从 +5V 取电。电机启动瞬间电流很大,如果功率地和控制地没有在单点汇合,地线上的压降会把 LM339 的 2.5V 参考电压拉偏。

所以我一般会在电机端子两端并联一个 100nF 瓷片电容和一个 100μF 电解电容,再把电机外壳接地。传感器电源入口也加一颗 10μF 退耦电容。调试时如果发现小车一转起来红外 LED 就乱闪,不要先怀疑程序,先量一下电机启动瞬间的电源波形,大概率是地弹。

CPLD 的 I/O 电平也要确认。EPM7128SLC84-6 的 I/O 电压由VCCIO决定,如果板子工作在 3.3V,输出高电平约 3.3V,接 L298N 的逻辑输入通常没问题,因为 L298N 的高电平阈值只有约 2.3V。但反过来,如果传感器模块输出是 5V,而 CPLD 的引脚不兼容 5V,就需要加电平转换或限流电阻。

2.4 信号流与 PWM 频率选择

整条控制链路是:四路比较器输出 → CPLD 的p1~p4ctl组合逻辑输出两路 4 位速度字 → 两路pwm计数器 → 两路pwmout→ L298N 的ENA/ENB→ 左右轮电机。CPLD 是并行执行,传感器状态变化到速度字更新的延迟只是组合逻辑门延迟加 PWM 计数器当前周期,不像单片机需要轮询,这是 CPLD 方案最大的优势。

但 PWM 计数器本身会吃掉一部分响应时间。原文用的是 4 位计数器,从 0 数到 9 回绕,也就是说 10 个时钟周期才产生一个完整 PWM 周期。如果直接用 4MHz 晶振,PWM 频率是 400kHz,对直流电机来说太高,但 L298N 开关损耗会变大。另一个问题是速度字精度只有 10%,直行和转弯之间的调节颗粒不够细。把这些总结成管脚约束示例,放到 Quartus 的.qsf文件里就是下面这样,实际引脚号必须按你自己的原理图改:

set_location_assignment PIN_134 -to clk set_location_assignment PIN_27 -to p[1] set_location_assignment PIN_28 -to p[2] set_location_assignment PIN_29 -to p[3] set_location_assignment PIN_30 -to p[4] set_location_assignment PIN_98 -to pwm_left set_location_assignment PIN_99 -to pwm_right

说明:Tcl 里的PIN_134PIN_27只是示例,EPM7128 的物理引脚要用 Quartus 的 Pin Planner 对照硬件原理图逐脚确认。p[1]这种写法是总线形式,如果你的 VHDL 端口名是p1p2这种分散信号,就把它们拆开单独约束。下载器用 HW-USBN-2A 或 USB-Blaster 都可以,接 JTAG 时注意TCKTDOTMSTDIGND方向不能接反。

3. CPLD 内部逻辑:PWM 发生器与循迹状态表的 VHDL 实现

3.1 PWM 模块:4 位计数器产生 0~9 档占空比

PWM 模块的核心是十进制加减计数器。每次clk上升沿,计数器从 0 加到 9,然后回 0。当计数值小于速度字a时输出高电平,否则输出低电平,因此输出占空比正好是a/10。速度字是 4 位二进制,例如"1000"等于十进制 8,占空比 80%;"0011"等于十进制 3,占空比 30%。

library ieee; use ieee.std_logic_1164.all; use ieee.std_logic_unsigned.all; entity pwm is port( clk : in std_logic; a : in std_logic_vector(3 downto 0); pwmout : out std_logic ); end pwm; architecture cond of pwm is signal count : std_logic_vector(3 downto 0); begin process(clk) begin if rising_edge(clk) then if count = "1001" then count <= "0000"; else count <= count + 1; end if; if count < a then pwmout <= '1'; else pwmout <= '0'; end if; end if; end process; end cond;

这段代码里,count <= count + 1pwmout <= ...都是非阻塞赋值。在同一个进程里,后面的if count < a用的是时钟沿之前的旧count值,所以不会出现当拍计数清零导致输出异常。std_logic_unsignedcount + 1可以直接做无符号加法,这在 MAX+PLUS II 的 VHDL 环境里很常见。

要注意的是这个模块没有复位信号。仿真时如果count初始值是Ucount + 1可能一直停在U;实际 CPLD 上电后寄存器状态虽然可能为 0,但依赖上电默认值不是好习惯。更稳妥的写法是增加一个异步复位rst_n,在复位时把count清成"0000"。作为课设演示,不加复位也能跑,但后面想加到 8 位 PWM 时,最好先把复位补上。

3.2 控制模块:状态表与速度字的组合逻辑映射

主控制模块ctl把四个传感器输入p1~p4拼接成一个 4 位向量TEMP,然后按黑线位置输出左右轮速度字。它没有时钟,是纯组合逻辑,传感器一变输出就变。原文里定义的转向规则和 VHDL 判断条件可以整理成一张状态表。

LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_UNSIGNED.ALL; USE IEEE.STD_LOGIC_ARITH.ALL; ENTITY ctl IS PORT( p1, p2, p3, p4 : IN STD_LOGIC; temp1, temp2 : OUT STD_LOGIC_VECTOR(3 DOWNTO 0) ); END ctl; ARCHITECTURE behave OF ctl IS BEGIN PROCESS(p1, p2, p3, p4) VARIABLE TEMP : STD_LOGIC_VECTOR(3 DOWNTO 0) := "0000"; BEGIN TEMP := p1 & p2 & p3 & p4; IF TEMP = "0000" THEN temp1 <= "1000"; temp2 <= "1000"; -- 四路全白,直行 ELSIF TEMP = "0010" THEN temp1 <= "1000"; temp2 <= "0011"; -- 右内侧压线,左快右慢,右小弯 ELSIF TEMP = "0001" OR TEMP = "0011" THEN temp1 <= "1001"; temp2 <= "0001"; -- 右侧压线,左快右慢,右大弯 ELSIF TEMP = "0100" THEN temp1 <= "0011"; temp2 <= "1000"; -- 左内侧压线,左慢右快,左小弯 ELSIF TEMP = "1000" OR TEMP = "1100" THEN temp1 <= "0001"; temp2 <= "1001"; -- 左侧压线,左慢右快,左大弯 ELSE temp1 <= "0000"; temp2 <= "0000"; -- 全黑或其他异常,停车 END IF; END PROCESS; END behave;

这段代码的逻辑说明:p1 & p2 & p3 & p4&拼接,TEMP的最高位是p1,最低位是p4。所以"0100"表示p2=1,也就是左内侧压线,小车已经偏到黑线右边,需要向左修正;"0010"表示p3=1,右内侧压线,需要向右修正。TEMP是变量,:=是立即赋值,不会产生寄存器。

TEMP传感器状态temp1(左轮)temp2(右轮)动作
"0000"四路全白1000(8)1000(8)直行
"0100"左内压线0011(3)1000(8)左小弯
"1000"/"1100"左外/左侧双路压线0001(1)1001(9)左大弯
"0010"右内压线1000(8)0011(3)右小弯
"0001"/"0011"右外/右侧双路压线1001(9)0001(1)右大弯
其他全黑/交叉/悬空0000(0)0000(0)停车

从表里能看到大弯和小弯的区别:小弯左右速度字是 8 和 3,差值 5;大弯是 9 和 1,差值 8。差值越大,两侧轮速差异越明显,转弯半径越小。temp1决定左轮 PWM 占空比,temp2决定右轮 PWM 占空比。比如左小弯时左轮 30%、右轮 80%,车身自然会向左转。

3.3 顶层电路图:把 ctl 和 PWM 连起来

在 MAX+PLUS II 里新建一个 gdf 文件,把ctl和两个pwm符号拖进去。p1~p4是顶层输入,ctltemp1接到左 PWM 的atemp2接到右 PWM 的a,两个 PWM 的clk接同一个时钟。最后的输出pwm_lpwm_r分别接 L298N 的ENAENB

这一步看起来只是连线,但决定了下载后能不能跑。如果生成的网表里clk没有被锁定到实际晶振引脚,综合器会随便分配一个引脚,而实验箱上那个引脚没有时钟输入,小车自然不转。答案就是报告里提到的“CLK 输入管脚没有锁定”。所以顶层 gdf 里不要漏掉clk输入,管脚分配时先锁时钟,再锁传感器和 PWM 输出。

仿真时可以用波形编辑器给p1~p4加激励,观察temp1temp2pwmout。我一般会先仿真ctl的组合逻辑,确认状态表正确;再仿真完整顶层,确认 PWM 占空比。比如给TEMP=0100,左 PWM 输出应该是 30% 占空比,右 PWM 是 80%,在波形上数高电平周期即可验证。

4. 下载与调试:CLK 管脚、传感器阈值和车速标定

4.1 从 MAX+PLUS II 到 Quartus 的下载流程

EPM7128 老芯片用 MAX+PLUS II 很顺手,但新电脑上更多用 Quartus II 兼容旧工程。下载流程大致是:把所有.vhd加入工程,把 gdf 设为顶层,编译通过后再分配管脚。管脚分配这一步不要跳过,也不要依赖自动分配。

常见做法是先在顶层 gdf 里给每个输入输出命名,然后在 Quartus 的 Assignment Editor 里逐一填写 Location。如果工程是 Tcl 脚本管理,也可以把管脚约束写进.qsf

set_global_assignment -name FAMILY "MAX7000S" set_global_assignment -name DEVICE EPM7128SLC84-6 set_location_assignment PIN_83 -to clk set_location_assignment PIN_54 -to p_i[0] set_location_assignment PIN_55 -to p_i[1] set_location_assignment PIN_56 -to p_i[2] set_location_assignment PIN_57 -to p_i[3] set_location_assignment PIN_70 -to pwm_l set_location_assignment PIN_71 -to pwm_r

说明:这里的p_i[0]p_i[3]是向量写法,对应 VHDL 里的p1~p4时可以在顶层端口定义成std_logic_vector(3 downto 0),或者直接写四个独立信号。引脚号是示意,实际要以你的原理图为准。分配完管脚后重新编译,打开 Pin-Out File 检查每个信号是否都在预期引脚上。

下载时使用 JTAG 方式。老的并口 ByteBlaster 已经很少见,现在很多下载器如 HW-USBN-2A 也能识别 MAX7000S 器件。连接顺序为TCKTDOTMSTDIGND,部分板子还有VCC参考脚,不接也可以工作。

提示:下载后如果小车没有任何反应,先不要怀疑程序,先量 CPLD 的clk引脚有没有时钟信号,再看pwm_lpwm_r有没有波形。CLK 没锁脚是这类课设里最常见的翻车点。

4.2 速度字、占空比和实际车速的换算

速度字是 4 位二进制,PWM 模块把它当成 0~9 的十进制数。标定之前先把每个速度字对应的占空比列成表,这是后面调参的基础。

速度字二进制十进制PWM 占空比典型用途
0000000%停车
10001110%最低速/大弯内轮
30011330%小弯内轮
81000880%直行/小弯外轮
91001990%大弯外轮

标定的方法是把小车架空,示波器探头接在 PWM 输出和 GND 之间,测高电平宽度。直流电机端电压的平均值等于占空比乘以电源电压,所以 80% 占空比在 5V 电源下大约是 4V。如果电机最低启动电压是 1V,10% 的占空比可能让轮子不转,这时候大弯内轮的速度字要从 1 改成 2 或 3,否则内轮抱死,转弯效果反而差。

实际调整时不要死守原文给的 8 和 3。直线速度太快容易冲出跑道,就先降直行档位,比如从"1000"改成"0111""0110";转弯不够就加大内外轮差值,比如把大弯从 9/1 改成 9/2,小弯从 8/3 改成 8/4。每次只改一个档位,改完就放在跑道上试一圈。

4.3 冲出跑道、抖动和反转问题排查

调试中出现的问题基本集中在传感器阈值、速度档位和地线噪声三块。下面这张表可以直接当排错清单用:

现象可能原因检查手段
小车完全不转CPLD 下载失败/CLK 没锁脚/L298N 使能悬空示波器测 PWM 输出,测 ENA/ENB 电平
直行冲出去,转弯不响应内外轮速度差太小,转弯时间不够加大差值,如 8/3 改为 9/2
黑线附近来回摆头LM339 阈值太灵敏,传感器抖动调电位器降灵敏度,或加迟滞比较器
转弯后进入白线仍继续转弯组合逻辑只认当前电平,没有记忆上次方向在 ctl 中增加丢线记忆状态
电机一启动 CPLD 就复位地线不干净,电机启停拉低电源加滤波电容,功率地与控制地单点汇合
传感器全部接反,方向反着跑p1~p4 位序和底盘安装相反对照状态表,交换 p1/p4 或 p2/p3

报告里提到“小车在转弯地方总会冲出跑道”,我遇到过类似情况。原因通常是直线占空比太高,小车进入弯道时速度还没降下来,红外探头已经越过黑线。解决办法不是把转弯速度调得更猛,而是把直行速度降到赛道能承受的范围,再配合丢线记忆,让小车在短暂全白时还能保持上一个转向状态。

4.4 环境光与传感器一致性调整

原文建议“在白地板上贴黑线”,这是最稳妥的。黑色电工胶带最好是哑光的,不要用带反光的胶带。每路传感器都要单独标定:先把小车放在白色区域,调该路电位器让输出刚好为高;再用黑胶带完全遮住探头,调同一电位器让输出翻转为低。四路都做一遍之后再上电跑车。

如果现场没有示波器,可以用板上的 LED 观察比较器输出。LM339 输出低电平时 LED 灭,高电平时 LED 亮。当小车慢慢推过黑线时,四个 LED 应该按 p1→p2→p3→p4 的顺序依次变化。如果中间少了一个,那一路探头高度可能不对,或者电位器偏了。这一步在装底盘前做最省时间。

5. 进阶技巧:迟滞比较器、丢线记忆与示波器验证

5.1 用迟滞比较器消除传感器抖动

LM339 是开集输出,直接拿 2.5V 固定阈值比较时没有回差。小车压线时接收管电压是缓变的,输出会在阈值附近反复翻转,CPLD 就可能在一个弯道里连续判断多次方向。一个低成本改法是利用 LM339 的开集输出:在输出端到同相输入端接一个 100kΩ~1MΩ 的正反馈电阻,让阈值产生约 200mV 迟滞。翻转一旦发生,输出会保持稳定,直到信号明显越过另一个阈值。如果不想动硬件,就把 ctl 改成时钟驱动,连续采样两拍,只有连续读到相同电平时才更新状态。

5.2 丢线记忆:把纯组合逻辑改成状态机

原版ctl是全组合逻辑,TEMP=0000时无条件直行。高速过弯时,小车很可能因为惯性冲出黑线,四路全白,结果它直行冲得更远。更稳的做法是加一个寄存器记录上一次有效转向,当检测到全白时延续上一次转向,直到再次看到黑线。大致框架如下:

if rising_edge(clk) then if TEMP = "0000" then temp1 <= last_left; -- 全白丢线,沿用上次速度字 temp2 <= last_right; else case TEMP is when "0100" => temp1 <= "0011"; temp2 <= "1000"; last_left <= "0011"; last_right <= "1000"; -- 其他状态同理 when others => null; end case; end if; end if;

说明:last_leftlast_right是新增的 4 位寄存器信号,在每次正常检测到黑线时更新。丢线时查表不复位,而是继续输出上一次的速度字。这个技巧在电磁循迹和摄像头循迹里也很常用,本质是给控制器加了一点“惯性”。

5.3 用示波器验证 PWM 时序

示波器是这套系统最有价值的调试工具。把探头接到pwm_l,地线夹接 CPLD 的 GND,触发电平设为 1.65V,然后看高电平和低电平宽度。对 4 位计数器,一个完整周期是 10 个clk,速度字 8 时高电平应占 8 个时钟周期。如果看到占空比不是预期值,先用万用表量ctl输出的temp1引脚电平,再反向排查传感器状态。

报告中提到仿真波形存在“判断延时”,这个延时主要来自 PWM 计数器要等当前计数周期结束。把clk频率降低到 1kHz~10kHz,判断反应会慢但占空比更稳定;把clk提高到几 MHz,PWM 频率又太高。折中做法是给ctl单独用较低时钟,PWM 用较高时钟,两者分开。

5.4 把 IN1/IN2 交给 CPLD,实现倒车大转弯

原文硬件把 L298N 的IN1/IN2固定成前进方向,所以不能实现“一侧轮前进、一侧轮后退”的大转弯。改进方法是增加两路 CPLD 输出dir_ldir_r,分别控制左右两侧 H 桥的方向。正转时IN1=1, IN2=0,反转时IN1=0, IN2=1。当需要更小的转弯半径时,让一侧轮反转,另一侧正转,差速效果比 9/1 速度字更明显。需要注意的是,反转瞬间电流方向突变,PWM 占空比不要直接从 80% 跳到 0,最好做一两个时钟周期的斜坡过渡,否则电机齿轮会受力冲击,电源也会被拉低。

把 4 位 PWM 计数器换成 8 位unsigned,再把速度字从常数表改成由循迹状态查表得到的 0~255 占空比数值,这套系统就不再只是课设,而是一台可以加 PID 调参的试验车。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 0:31:11

Hugging Face / OpenRouter:MiniMax M3 的 TaoToken 调用方式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 0:26:58

SAP MRP 运行逻辑与 MD04 例外信息排查指南

简介&#xff1a;这份资源是SAP PP模块的系列培训教材之一&#xff0c;聚焦MRP基本概念、MRP元素与运行参数&#xff0c;面向ERP顾问、生产计划人员及SAP初学者&#xff0c;帮助理解物料需求计划的运行逻辑与系统操作。压缩包内为1个PPT文件&#xff0c;约622KB&#xff0c;以图…

作者头像 李华
网站建设 2026/9/20 0:26:38

IDEA AI编程插件实测:从Copilot到通义灵码的六款选择与避坑指南

前几年我一直是那种“代码补全够用就好”的开发者&#xff0c;IDEA自带的智能提示用了快十年&#xff0c;觉得它已经足够懂我。直到有一次&#xff0c;我在一个Spring Boot老项目里接手一块没人愿意碰的模块&#xff0c;面对几百行没有注释的遗留代码&#xff0c;我忽然意识到自…

作者头像 李华
网站建设 2026/9/20 0:25:49

接口报 401?TaoToken + Cline 这样核对模型 ID

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华