FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

FPGA实习项目实战:2026年用Zynq实现智能小车控制

二牛学FPGA二牛学FPGA
技术分享
5小时前
0
0
2

Quick Start

  • 准备硬件:Zynq-7020 开发板(如 Zedboard、PYNQ-Z2)、电机驱动模块(L298N 或 TB6612)、直流电机 ×2、编码器 ×2、超声波测距模块(HC-SR04)、红外循迹模块 ×2、12V 锂电池组。
  • 安装 EDA 工具:Vivado 2024.2(含 Vitis)、Xilinx SDK 2024.2。确保安装时勾选“Zynq-7000”器件库。
  • 创建 Vivado 工程:器件选 xc7z020clg484-1,新建 Block Design,添加 Zynq Processing System IP 并配置 PS 侧(DDR、UART、GPIO MIO)。
  • 添加 PL 外设 IP:在 Block Design 中添加 AXI GPIO(用于电机 PWM 与编码器捕获)、AXI Timer(用于超声测距回波计时)、AXI Quad SPI(用于红外循迹 ADC 读取,如使用模拟输出模块)。
  • 生成比特流并导出硬件:综合→实现→生成比特流。File → Export → Export Hardware(含比特流)。
  • 启动 Vitis 创建应用工程:基于导出的硬件平台,新建 C/C++ 应用工程,选择“Empty Application”。
  • 编写控制程序:初始化 GPIO、Timer、中断;实现 PID 速度闭环控制(编码器反馈)、超声避障逻辑、循迹阈值判断。代码见“实施步骤”章节。
  • 编译并下载:编译生成 .elf,通过 JTAG 下载到开发板。上电后小车应自动执行预设模式(如循迹前进,遇障碍后退转向)。
  • 验收:串口终端(115200 baud)打印速度、距离、模式状态;小车在直线赛道上以 0.3 m/s 速度循迹,误差 < 5 cm;遇 30 cm 内障碍物自动停车并转向。

前置条件与环境

项目/推荐值说明替代方案
器件/板卡Xilinx Zynq-7020 (xc7z020clg484-1)Zynq-7010 (资源少,慎用);PYNQ-Z2 兼容
EDA 版本Vivado 2024.2 + Vitis 2024.2Vivado 2023.x(需注意 IP 版本兼容性)
仿真器Vivado Simulator 或 ModelSim SE-64 2024QuestaSim(需自行编译库)
时钟/复位PS 侧 50 MHz 晶振;PL 侧使用 PS 输出的 FCLK_CLK0 (100 MHz)外部 PL 时钟(需约束)
接口依赖UART (MIO 14/15) 用于调试;AXI GPIO 连接电机 PWM 与编码器自定义 IOB 约束(非 AXI)
约束文件XDC 需包含:时钟周期 10 ns(100 MHz)、所有 PL 引脚位置/电平(3.3V LVCMOS)自动推导(不推荐)
电源12V 2A 锂电池组供电机;板卡 5V/2A 适配器单电源方案需注意纹波

目标与验收标准

  • 功能点
    • 性能指标
      • 验收方式

        实施步骤

        阶段一:工程结构与硬件设计

        • 创建 Vivado 工程:选择 RTL Project,勾选“Do not specify sources at this time”。器件选择 xc7z020clg484-1。
        • 搭建 Block Design
          • 连接与地址映射:运行自动连接,确保所有 AXI 外设挂载到 PS 的 M_AXI_GP0 口。地址范围默认(0x4000_0000–0x4001_FFFF 等)。
          • 生成顶层 HDL:右键 Block Design → Create HDL Wrapper(选择“Let Vivado manage wrapper”)。
          • 添加约束文件:新建 .xdc,写入以下关键约束:
          # 时钟约束(100 MHz)
          create_clock -period 10.000 -name clk_100 [get_ports {FCLK_CLK0}]
          
          # 电机 PWM 引脚(PL 侧,3.3V)
          set_property PACKAGE_PIN Y9 [get_ports {pwm_left}]
          set_property IOSTANDARD LVCMOS33 [get_ports {pwm_left}]
          set_property PACKAGE_PIN AA9 [get_ports {pwm_right}]
          set_property IOSTANDARD LVCMOS33 [get_ports {pwm_right}]
          
          # 编码器输入
          set_property PACKAGE_PIN AB10 [get_ports {enc_left_a}]
          set_property IOSTANDARD LVCMOS33 [get_ports {enc_left_a}]
          set_property PACKAGE_PIN AB11 [get_ports {enc_left_b}]
          set_property IOSTANDARD LVCMOS33 [get_ports {enc_left_b}]

          逐行说明

          • 第 1 行:`create_clock` 约束定义 100 MHz 时钟周期为 10 ns,名称 `clk_100`,用于时序分析。
          • 第 4 行:`set_property PACKAGE_PIN Y9` 将顶层端口 `pwm_left` 绑定到 FPGA 的 Y9 引脚(具体引脚号以板卡原理图为准)。
          • 第 5 行:`IOSTANDARD LVCMOS33` 设置该引脚为 3.3V CMOS 电平,与电机驱动模块逻辑电平匹配。
          • 第 6–7 行:同理定义右电机 PWM 引脚。
          • 第 10–13 行:编码器 A/B 相输入引脚约束,同样 3.3V 电平。

          阶段二:关键模块 RTL 设计(PL 侧)

          本节实现电机 PWM 生成与编码器计数模块,均在 PL 侧用 Verilog 编写,通过 AXI GPIO 与 PS 交互。

          // pwm_gen.v
          module pwm_gen (
              input  wire       clk,          // 100 MHz
              input  wire       rst_n,        // 异步复位,低有效
              input  wire [7:0] duty_cycle,   // 占空比 0-255
              output reg        pwm_out
          );
          
          reg [7:0] counter;
          
          always @(posedge clk or negedge rst_n) begin
              if (!rst_n) begin
                  counter &lt;= 8'd0;
                  pwm_out  &lt;= 1'b0;
              end else begin
                  counter &lt;= counter + 1'b1;
                  if (counter &lt; duty_cycle)
                      pwm_out &lt;= 1'b1;
                  else
                      pwm_out &lt;= 1'b0;
              end
          end
          
          endmodule

          逐行说明

          • 第 1–7 行:模块端口声明。`clk` 为 100 MHz 系统时钟,`rst_n` 低有效复位,`duty_cycle` 是 8 位无符号占空比输入(0–255 对应 0%–100%),`pwm_out` 为输出。
          • 第 9 行:`reg [7:0] counter` 定义 8 位计数器,从 0 递增到 255 循环,周期 = 256 / 100 MHz = 2.56 µs,PWM 频率约 390 kHz。若需更低频率(如 1 kHz),可增加分频。
          • 第 11–18 行:时序逻辑。复位时清零。每个时钟上升沿计数器加 1。当 `counter < duty_cycle` 时输出高电平,否则低电平。注意:`duty_cycle=0` 时始终低,`duty_cycle=255` 时 255/256 高,非 100%——若需 100% 可做边界处理。
          // encoder_counter.v
          module encoder_counter (
              input  wire       clk,
              input  wire       rst_n,
              input  wire       enc_a,
              input  wire       enc_b,
              output reg [15:0] position
          );
          
          reg [1:0] a_prev, b_prev;
          wire      a_rise, a_fall;
          
          // 边沿检测
          always @(posedge clk or negedge rst_n) begin
              if (!rst_n) begin
                  a_prev &lt;= 2'b0;
                  b_prev &lt;= 2'b0;
              end else begin
                  a_prev &lt;= {a_prev[0], enc_a};
                  b_prev &lt;= {b_prev[0], enc_b};
              end
          end
          
          assign a_rise = (a_prev == 2'b01);
          assign a_fall = (a_prev == 2'b10);
          
          // 四倍频计数
          always @(posedge clk or negedge rst_n) begin
              if (!rst_n)
                  position &lt;= 16'd0;
              else if (a_rise) begin
                  if (enc_b)
                      position &lt;= position - 1'b1;  // 反转
                  else
                      position &lt;= position + 1'b1;  // 正转
              end else if (a_fall) begin
                  if (enc_b)
                      position &lt;= position + 1'b1;
                  else
                      position &lt;= position - 1'b1;
              end
          end
          
          endmodule

          逐行说明

          • 第 1–7 行:模块端口。`enc_a`、`enc_b` 为编码器两相信号,`position` 输出 16 位有符号位置计数(实际用无符号,软件可解释为有符号)。
          • 第 9–10 行:`a_prev`、`b_prev` 为 2 位移位寄存器,用于边沿检测。
          • 第 12–19 行:每个时钟沿将当前 `enc_a` 移入 `a_prev[0]`,`a_prev[1]` 保留上一拍值。同理 `b_prev`。
          • 第 21–22 行:`a_rise` 在 `a_prev` 从 0→1 时(即 `a_prev == 2'b01`)为高;`a_fall` 在 1→0 时(`2'b10`)为高。
          • 第 24–36 行:在 A 相上升沿或下降沿时,根据 B 相电平判断方向。若 `enc_b` 为高,则反转(减计数);为低则正转(加计数)。实现四倍频(每圈脉冲数 ×4),提高分辨率。

          阶段三:PS 侧软件(C 语言控制逻辑)

          在 Vitis 中创建应用工程,编写主控程序。核心功能:PID 速度控制、超声测距、循迹判断。

          #include &lt;stdio.h&gt;
          #include &quot;xgpio.h&quot;          // AXI GPIO 驱动
          #include &quot;xtmrctr.h&quot;         // AXI Timer 驱动
          #include &quot;xparameters.h&quot;     // 外设基地址定义
          
          #define PWM_PERIOD_MS 10     // 10 ms 控制周期
          
          XGpio gpio_inst;             // 电机 PWM 与编码器 GPIO
          XTmrCtr timer_inst;          // 超声测距定时器
          
          int main() {
              // 初始化外设
              XGpio_Initialize(&amp;gpio_inst, XPAR_AXI_GPIO_0_DEVICE_ID);
              XTmrCtr_Initialize(&amp;timer_inst, XPAR_AXI_TIMER_0_DEVICE_ID);
          
              // 设置 GPIO 方向:通道1输出,通道2输入
              XGpio_SetDataDirection(&amp;gpio_inst, 1, 0x00);  // 输出
              XGpio_SetDataDirection(&amp;gpio_inst, 2, 0xFF);  // 输入
          
              u16 position = 0;
              u16 target_speed = 100;  // 目标速度(编码器计数/周期)
              s32 error, integral = 0, derivative;
              s32 prev_error = 0;
              float kp = 0.5, ki = 0.1, kd = 0.05;
          
              while (1) {
                  // 读取编码器位置
                  position = XGpio_DiscreteRead(&amp;gpio_inst, 2);
          
                  // 计算误差(假设 position 为速度测量值)
                  error = target_speed - position;
                  integral += error;
                  derivative = error - prev_error;
          
                  // PID 输出
                  s32 output = (s32)(kp * error + ki * integral + kd * derivative);
                  if (output &gt; 255) output = 255;
                  if (output &lt; 0)   output = 0;
          
                  // 写 PWM 占空比
                  XGpio_DiscreteWrite(&amp;gpio_inst, 1, (u32)output);
          
                  prev_error = error;
          
                  // 延时 10 ms(简单忙等,实际可用定时器中断)
                  for (volatile int i = 0; i &lt; 1000000; i++);
              }
              return 0;
          }

          逐行说明

          • 第 1–5 行:包含头文件。`xgpio.h` 提供 AXI GPIO API,`xtmrctr.h` 提供定时器 API,`xparameters.h` 包含硬件平台生成的常量(如设备 ID)。
          • 第 7 行:定义控制周期 10 ms,实际通过忙等近似实现,不精确。生产级应使用定时器中断。
          • 第 9–10 行:声明 GPIO 和定时器实例。
          • 第 14–15 行:`XGpio_Initialize` 通过设备 ID 初始化外设。ID 值在 `xparameters.h` 中定义(如 `XPAR_AXI_GPIO_0_DEVICE_ID`)。
          • 第 18–19 行:`SetDataDirection` 设置通道方向。参数 2 为通道号,0x00 表示全部输出,0xFF 全部输入。
          • 第 21–25 行:定义 PID 变量。`target_speed` 是期望的编码器计数(每 10 ms 的脉冲数),`kp`、`ki`、`kd` 为比例、积分、微分系数(浮点数,实际可定点化)。
          • 第 27–44 行:主循环。读取位置 → 计算误差 → PID 运算 → 限幅 → 写 PWM → 更新前次误差 → 延时。注意:`integral` 未做抗饱和处理,长时间积分可能溢出,可增加限幅。
          • 第 43 行:忙等延时,CPU 占用 100%。实际应使用 `sleep()` 或定时器中断。

          常见坑与排查(阶段一至三)

          • 坑 1:Block Design 中 AXI 外设地址冲突。现象:综合报错“Address overlap”。解决:在 Address Editor 中手动分配唯一地址范围,或让 Vivado 自动分配。
          • 坑 2:编码器计数方向反。现象:电机正转时位置递减。解决:交换编码器 A/B 相接线,或在软件中取反。
          • 坑 3:PWM 频率过高导致电机啸叫。现象:电机发出尖锐噪声。解决:增加分频器,使 PWM 频率降至 1–10 kHz。
          • 坑 4:Vitis 工程编译报错“undefined reference”。原因:未链接驱动库。解决:在工程属性中勾选“xgpio”、“xtmrctr”等库。

          原理与设计说明

          为什么用 Zynq 而非纯 FPGA? 智能小车控制需要实时性高的 PID 算法(PS 侧运行 C 代码灵活)和高速编码器捕获(PL 侧硬件计数低延迟)。Zynq 的 PS+PL 异构架构正好满足:PS 做决策与通信,PL 做高速 I/O 与定时。

          PID 控制周期选择 10 ms 的原因:电机机械时间常数通常在 10–100 ms 量级,10 ms 控制周期足以覆盖动态响应,同时避免过高的 CPU 负载。若周期过短(如 1 ms),PL 侧需额外硬件加速;过长(> 50 ms)会导致速度波动大。

          编码器四倍频 vs 单倍频:四倍频利用 A/B 相所有边沿,每圈脉冲数 ×4,分辨率提高 4 倍,但 PL 侧逻辑略增(约 20 LUT)。对于低速小车(0.1–0.5 m/s),单倍频已够用,但四倍频可减少低速时的量化噪声。

          超声测距为何用 AXI Timer 捕获模式? 超声波模块需要精确测量回波脉冲宽度(微秒级)。PS 侧软件轮询 GPIO 会因中断延迟产生误差,而 PL 侧定时器硬件捕获可达到 ±1 µs 精度,对应距离精度 ±0.17 mm。

          验证与结果

          验证项测量条件结果(示例值)说明
          PWM 频率示波器探头接 PWM 引脚390 kHz(无分频)/ 1 kHz(分频后)390 kHz 可驱动小电机,但可能啸叫
          标签:
          本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
          如需转载,请注明出处:https://z.shaonianxue.cn/41129.html
          二牛学FPGA

          二牛学FPGA

          初级工程师
          这家伙真懒,几个字都不愿写!
          95819.43W3.99W3.67W
          分享:
          成电国芯FPGA赛事课即将上线
          2026年FPGA实习生招聘指南:基于国产平台的实践经验与简历优化
          2026年FPGA实习生招聘指南:基于国产平台的实践经验与简历优化上一篇
          FPGA实习项目实战:2026年用Zynq实现智能小车控制下一篇
          FPGA实习项目实战:2026年用Zynq实现智能小车控制
          相关文章
          总数:991
          Verilog中parameter与localparam的区别及模块参数化设计实践指南

          Verilog中parameter与localparam的区别及模块参数化设计实践指南

          QuickStart创建新工程(Vivado/Quartus),…
          技术分享
          10天前
          0
          0
          23
          0
          FPGA时序与并行计算快速上手指南:理科背景的思维迁移与实践

          FPGA时序与并行计算快速上手指南:理科背景的思维迁移与实践

          对于拥有数学、物理背景的学习者而言,理解FPGA设计的核心——时序与并行…
          技术分享
          14天前
          0
          0
          34
          0
          FPGA学习资源盘点:2026年值得关注的开发板、开源项目与在线社区

          FPGA学习资源盘点:2026年值得关注的开发板、开源项目与在线社区

          本文旨在为FPGA学习者与从业者提供一份系统、实用的资源导航。我们将遵循…
          技术分享
          14天前
          0
          0
          42
          0
          评论表单游客 您好,欢迎参与讨论。
          加载中…
          评论列表
          总数:0
          FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
          没有相关内容