Quick Start
- 步骤一:准备硬件——Zynq-7010/7020开发板(如PYNQ-Z2、ZedBoard)、电机驱动模块(L298N)、直流电机×2、超声波测距模块(HC-SR04)、红外循迹模块×2、电池组(7.4V~12V)。
- 步骤二:安装Vivado 2024.2(或更高版本)及Vitis统一平台,确保支持Zynq-7000系列。
- 步骤三:在Vivado中创建新工程,选择xc7z020clg484-1(PYNQ-Z2)或对应器件。
- 步骤四:添加Zynq Processing System IP核,配置DDR(512MB)、UART1(115200 baud)、GPIO MIO(复位按键)和AXI GPIO(用于电机PWM与传感器输入)。
- 步骤五:编写顶层Verilog模块,例化Zynq PS与自定义RTL模块(PWM发生器、超声波测距控制器、循迹逻辑)。
- 步骤六:综合、实现并生成比特流,导出硬件描述(XSA文件)至Vitis。
- 步骤七:在Vitis中创建裸机应用工程,编写C代码控制小车:初始化GPIO、PWM占空比调整、传感器读取、避障与循迹状态机。
- 步骤八:下载比特流与软件ELF到开发板,连接电机与传感器,上电后小车应能自动避障或循迹(根据模式选择)。
- 验收点:小车在平坦地面直线行驶不跑偏,遇障碍物20cm内停止或转向,循迹时能沿黑线行驶。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Zynq-7020(xc7z020clg484-1) | Zynq-7010(xc7z010)或Artix-7+外部MCU(不推荐) |
| EDA版本 | Vivado 2024.2 + Vitis 2024.2 | Vivado 2023.x(需调整IP核版本) |
| 仿真器 | Vivado Simulator(XSim) | ModelSim/Questa(需编译库) |
| 时钟/复位 | PS侧50MHz晶振,PL侧由PS提供100MHz时钟;复位使用PS复位输出 | 外部晶振+复位芯片(增加BOM) |
| 接口依赖 | PMOD接口连接传感器,GPIO连接电机驱动模块 | Arduino Shield兼容板(如Pmod Shield) |
| 约束文件 | XDC约束:时钟周期10ns,I/O标准LVCMOS33,输入延迟2ns | 自动推导(不推荐,可能导致时序违规) |
| 电源 | 5V/2A直流电源(板卡供电)+ 7.4V电池组(电机供电) | 12V电池组+降压模块 |
目标与验收标准
- 功能点:
- 电机控制:两路PWM(频率1kHz,占空比0%~100%),实现前进、后退、左转、右转、停止。
- 超声波测距:测量距离2cm~200cm,精度±1cm,更新频率≥20Hz。
- 红外循迹:检测黑线/白地,输出数字信号(0/1),响应时间<10ms。
- 避障模式:前方20cm恢复直行。
- 循迹模式:根据左右传感器差值调整转向,保持沿黑线行驶。
- 性能指标:
- Fmax:PL逻辑时钟≥100MHz(典型值150MHz)。
- 资源占用:LUT<2000,FF<1500,BRAM<4个,DSP<2个。
- 延迟:从传感器输入到电机输出≤1ms(不含机械惯性)。
- 验收方式:
- 上板测试:小车在1m×2m测试场地完成避障与循迹各3次,成功率100%。
- 仿真验证:PWM波形占空比误差<1%,超声波测距时序符合数据手册。
- 日志输出:UART打印传感器值与状态机状态,无异常。
实施步骤
工程结构
car_controller/
├── vivado/
│ ├── car_controller.xpr # Vivado工程文件
│ ├── src/
│ │ ├── top.v # 顶层模块(例化PS与RTL)
│ │ ├── pwm_gen.v # PWM发生器
│ │ ├── ultrasonic_ctrl.v # 超声波测距控制器
│ │ ├── line_tracker.v # 循迹逻辑
│ │ └── car_fsm.v # 避障/循迹状态机
│ ├── constr/
│ │ └── car_controller.xdc # 时序与I/O约束
│ └── ip/ # IP核目录(Zynq PS, AXI GPIO)
├── vitis/
│ ├── car_app/
│ │ ├── src/
│ │ │ ├── main.c # 主控逻辑
│ │ │ ├── gpio_drv.c # GPIO驱动
│ │ │ ├── pwm_drv.c # PWM驱动
│ │ │ └── sensor_drv.c # 传感器驱动
│ │ └── lscript.ld # 链接脚本
│ └── platform/ # 硬件平台描述
└── README.md
逐行说明
- 第1行:工程根目录,vivado/存放硬件设计,vitis/存放软件。
- 第2行:Vivado工程文件,双击打开。
- 第3~7行:RTL源文件,每个模块独立文件,便于复用与调试。
- 第8行:约束文件,定义时钟、I/O标准与输入延迟。
- 第9行:IP核目录,由Vivado自动管理。
- 第10~15行:Vitis应用工程,main.c为主循环,驱动文件封装底层操作。
- 第16行:链接脚本,定义内存布局(通常无需修改)。
- 第17行:硬件平台描述,由Vivado导出。
- 第18行:项目说明文档。
关键模块:PWM发生器
// pwm_gen.v
module pwm_gen #(
parameter CLK_FREQ = 100_000_000, // 输入时钟频率(Hz)
parameter PWM_FREQ = 1_000, // PWM频率(Hz)
parameter RESOLUTION = 8 // 占空比分辨率(位)
) (
input wire clk,
input wire rst_n,
input wire [RESOLUTION-1:0] duty, // 占空比(0~2^RESOLUTION-1)
output reg pwm_out
);
localparam COUNTER_MAX = CLK_FREQ / PWM_FREQ; // 计数器最大值
localparam SCALE = COUNTER_MAX / (2**RESOLUTION); // 每单位占空比对应计数值
reg [31:0] counter;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
pwm_out = COUNTER_MAX - 1) begin
counter <= 0;
end else begin
counter <= counter + 1;
end
pwm_out <= (counter < duty * SCALE) ? 1 : 0;
end
end
endmodule
逐行说明
- 第1~2行:模块定义,参数化时钟频率、PWM频率和分辨率,便于移植。
- 第3~8行:端口声明,clk和rst_n为全局信号,duty为输入占空比,pwm_out为输出。
- 第9行:计算计数器最大值,例如100MHz/1kHz=100000。
- 第10行:计算每单位占空比对应的计数值,例如100000/256≈390。
- 第11行:32位计数器,足够覆盖最大值。
- 第12~14行:异步复位,计数器与输出清零。
- 第15~18行:计数器从0到COUNTER_MAX-1循环。
- 第19行:当计数器小于duty*SCALE时输出高电平,否则低电平,实现占空比控制。
- 综合意图:生成一个周期为1ms的PWM波形,占空比由duty控制,用于驱动电机。
- 仿真影响:需验证duty=0时pwm_out始终为0,duty=255时接近100%占空比。
关键模块:超声波测距控制器
// ultrasonic_ctrl.v
module ultrasonic_ctrl #(
parameter CLK_FREQ = 100_000_000
) (
input wire clk,
input wire rst_n,
output reg trig, // 触发信号(10us高脉冲)
input wire echo, // 回波信号(高电平宽度对应距离)
output reg [15:0] distance_cm // 距离(单位:cm)
);
localparam TRIG_PULSE = CLK_FREQ / 100_000; // 10us
localparam SOUND_SPEED = 343; // 声速m/s
localparam CM_PER_COUNT = (SOUND_SPEED * 100) / (CLK_FREQ / 2); // 每个时钟周期对应距离(cm)
reg [31:0] counter;
reg [3:0] state;
reg [31:0] echo_width;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
trig <= 0;
distance_cm <= 0;
state <= 0;
counter <= 0;
echo_width <= 0;
end else begin
case (state)
0: begin // 等待触发
trig <= 1;
if (counter < TRIG_PULSE - 1) begin
counter <= counter + 1;
end else begin
trig <= 0;
counter <= 0;
state <= 1;
end
end
1: begin // 等待echo上升沿
if (echo) begin
counter <= 0;
state <= 2;
end
end
2: begin // 测量echo高电平宽度
if (echo) begin
counter <= counter + 1;
end else begin
echo_width <= counter;
distance_cm <= counter * CM_PER_COUNT;
counter <= 0;
state <= 3;
end
end
3: begin // 等待50ms再触发下一次
if (counter < CLK_FREQ / 20) begin
counter <= counter + 1;
end else begin
counter <= 0;
state <= 0;
end
end
default: state <= 0;
endcase
end
end
endmodule
逐行说明
- 第1~2行:模块定义,参数化时钟频率。
- 第3~8行:端口,trig输出10us脉冲,echo输入回波,distance_cm输出距离。
- 第9行:计算10us所需的时钟周期数,100MHz下为1000。
- 第10行:声速343m/s。
- 第11行:每个时钟周期对应距离,公式为(343*100)/(100MHz/2)≈0.000686cm,实际使用整数运算。
- 第12~14行:内部寄存器,counter为通用计数器,state为状态机,echo_width暂存回波宽度。
- 第15~21行:异步复位,所有寄存器清零。
- 第22~33行:状态0,输出10us触发脉冲,然后进入状态1。
- 第34~38行:状态1,等待echo上升沿,检测到后重置计数器并进入状态2。
- 第39~47行:状态2,测量echo高电平宽度,下降沿到来时计算距离并进入状态3。
- 第48~54行:状态3,等待50ms(20Hz更新率)后再触发下一次测量。
- 综合意图:实现HC-SR04的时序控制,直接输出厘米级距离。
- 仿真影响:需验证trig脉冲宽度为10us,echo宽度与distance_cm成正比。
时序与约束
# car_controller.xdc
create_clock -period 10.000 -name sys_clk [get_ports {clk}]
set_input_delay -clock sys_clk -max 2.000 [get_ports {echo}]
set_input_delay -clock sys_clk -min 0.500 [get_ports {echo}]
set_output_delay -clock sys_clk -max 4.000 [get_ports {pwm_out}]
set_output_delay -clock sys_clk -min 1.000 [get_ports {pwm_out}]
set_property IOSTANDARD LVCMOS33 [get_ports {trig}]
set_property IOSTANDARD LVCMOS33 [get_ports {echo}]
set_property IOSTANDARD LVCMOS33 [get_ports {pwm_out}]
set_property PACKAGE_PIN L15 [get_ports {clk}] ;# 示例引脚,需根据原理图修改
逐行说明
- 第1行:创建100MHz时钟,周期10ns。
- 第2~3行:输入延迟约束,echo信号最大延迟2ns,最小0.5ns,确保建立/保持时间。
- 第4~5行:输出延迟约束,pwm_out最大延迟4ns,最小1ns。
- 第6~8行:设置I/O标准为LVCMOS33(3.3V)。
- 第9行:指定时钟引脚位置,需根据开发板原理图修改。
- 常见坑:未约束输入延迟可能导致时序分析不准确,上板出现偶发错误。
验证
// tb_pwm_gen.v 测试PWM发生器
module tb_pwm_gen;
reg clk, rst_n;
reg [7:0] duty;
wire pwm_out;
pwm_gen #(.CLK_FREQ(100_000_000), .PWM_FREQ(1_000), .RESOLUTION(8)) uut (
.clk(clk), .rst_n(rst_n), .duty(duty), .pwm_out(pwm_out)
);
initial begin
clk = 0;
forever #5 clk = ~clk; // 100MHz
end
initial begin
rst_n = 0; #100 rst_n = 1;
duty = 128; // 50%占空比
#1000;
duty = 64; // 25%占空比
#1000;
$finish;
end
endmodule
逐行说明
- 第1~2行:测试模块,无端口。
- 第3~5行:声明信号并例化UUT。
- 第6~8行:生成100MHz时钟,周期10ns。
- 第9~13行:复位后设置duty=128(50%),运行1ms后改为64(25%),再运行1ms结束。
- 预期结果:pwm_out占空比在第一个1ms内为50%,第二个1ms内为25%。
- 失败先查什么:检查duty*SCALE是否溢出,counter是否从0开始。
上板
- 步骤:连接开发板JTAG → 下载比特流 → 运行Vitis应用 → 观察小车行为。
- 常见坑:电机驱动模块需要独立电源,不能从开发板取电(电流不足)。
- 排查:如果小车不动,检查PWM输出引脚电平是否正常(万用表测电压)。
原理与设计说明
为什么用Zynq而不是纯MCU?Zynq的PL部分可以并行处理传感器与电机控制,延迟低至微秒级,而PS部分运行状态机与通信,分工明确。关键trade-off:
- 资源 vs Fmax:PWM发生器使用计数器而非DDS,资源少但频率固定;若需变频PWM,可改用DDS但消耗更多DSP。
- 吞吐 vs 延迟:超声波测距采用状态机而非中断,吞吐固定20Hz,但延迟可控(无中断抖动)。
- 易用性 vs 可移植性:AXI GPIO接口简单但占用PL资源;若用EMIO可减少资源但时序复杂。
验证与结果
| 指标 | 实测值(示例) | 测量条件 |
|---|---|---|
| Fmax | 150 MHz | Vivado时序报告,最差路径在PWM计数器 |
| LUT占用 | 1,234 | 综合后报告,含PS与PL逻辑 |
| FF占用 | 987 | 综合后报告 |
| BRAM占用 | 2 | 用于AXI GPIO缓冲 |
| PWM占空比误差 | <0.5% | 示波器测量,duty=128时占空比49.8% |
| 超声波测距误差 | ±1.2 cm | 标准障碍物(10cm/50cm/100cm)各测10次 |
| 循迹响应时间 | 8 ms | 从传感器变化到电机转向 |
以上数值为典型配置下的示例结果,实际以工程与数据手册为准。
故障排查(Troubleshooting)
- 现象1:小车不启动,电源指示灯不亮。原因:电源反接或电压不足。检查点:万用表测电源输出。修复:更换电池或重新接线。
- 现象2:电机嗡嗡响但不转。原因:PWM频率过低或占空比太小。检查点:示波器测PWM引脚。修复:提高PWM频率至1kHz以上,占空比>10%。
- 现象3:超声波测距始终为0。原因:trig脉冲宽度不足或echo未连接。检查点:示波器测trig与echo。修复:确认trig脉冲为10us,echo引脚正确。
- 现象4:循迹传感器无反应。原因:传感器阈值设置错误或环境光干扰。检查点:调节传感器电位器。修复:重新校准阈值。
- 现象5:小车直线行驶跑偏。原因:两轮电机转速不一致。检查点:测量两路PWM占空比。修复:软件中微调左右电机占空比。
- 现象6:Vivado综合报错“Unconstrained path”。原因:未添加时序约束。检查点:查看综合日志。修复:添加XDC约束。
- 现象7:Vitis下载失败“No compatible device”。原因:JTAG驱动未安装或板卡未上电。检查点:设备管理器查看。修复:安装驱动或重新上电。
- 现象8:小车避障时频繁转向。原因:超声波测距更新率太低或噪声。检查点:串口打印距离值。修复:增加滤波(中值平均)。
- 现象9:循迹时冲出赛道。原因:转向增益过大。检查点:观察转向角度。修复:减小PID的P项。
- 现象10:电池续航不足10分钟。原因:电机驱动效率低或电池容量小。检查点:测量电机电流。修复:更换大容量电池或优化PWM频率。
扩展与下一步
- 扩展1:加入蓝牙/WiFi模块(如ESP8266),实现手机APP遥控。
- 扩展2:使用PID算法优化循迹与避障,提高稳定性。
- 扩展3:增加摄像头(OV7670)与图像处理,实现视觉循迹。
- 扩展4:参数化PWM频率与分辨率,通过PS寄存器动态配置。

评论 0
暂无评论,快来抢沙发吧