news 2026/6/4 15:14:12

FPGA实现数字PID控制器:从VHDL建模到乒乓球悬浮系统实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA实现数字PID控制器:从VHDL建模到乒乓球悬浮系统实践

1. 项目概述与核心思路

在嵌入式控制领域,将经典的控制算法“硬化”到可编程逻辑器件中,一直是一个兼具挑战与魅力的方向。PID控制器,作为工业界的常青树,其原理看似简单,但要在FPGA上用VHDL语言实现一个稳定、高效且实用的数字版本,却需要跨越从连续域到离散域的思维转换,并妥善处理定点数运算、时序同步、接口驱动等一系列硬件设计特有的问题。这个项目正是源于一次毕业设计,目标很明确:用一块Basys 3 FPGA开发板,驱动一个自制的乒乓球悬浮装置,让小球稳定地悬浮在透明管道的某个设定高度。这不仅仅是一个算法仿真,更是一次从理论、代码到物理实物的完整闭环验证。

整个系统的核心逻辑链条非常清晰:红外传感器测量乒乓球的实时高度,经过ADC转换为数字量,这个值作为反馈信号。在FPGA内部,PID控制器模块将设定高度与反馈高度进行比较,计算出误差,并据此通过比例、积分、微分运算生成一个控制量。这个控制量最终被转换为PWM信号的占空比,输出给管道底部的风扇。风扇转速的变化会改变向上的气流,从而调整乒乓球的悬浮高度,形成一个闭环控制系统。这个项目适合两类朋友:一类是正在学习数字电路设计或嵌入式系统,希望将控制理论与硬件实践结合起来的在校学生;另一类是从事电机驱动、电源管理或需要快速原型控制系统的工程师,希望了解如何在FPGA上构建确定性的实时控制内核。接下来,我将拆解整个实现过程,分享其中关键的设计决策、踩过的坑以及一些优化思路。

2. PID控制器的数字离散化与VHDL建模要点

将连续的PID控制器方程转化为适合在FPGA时钟驱动下运行的离散形式,是设计的第一步,也是最容易出错的一步。连续时间的PID输出公式为:u(t) = Kp * e(t) + Ki * ∫e(t)dt + Kd * de(t)/dt。在数字系统中,我们需要在固定的采样周期Ts下,对其进行近似。

2.1 离散化公式的选择与推导

我采用了位置式PID算法,这是一种直观且常见的离散化方法。其核心思想是用求和代替积分,用差分代替微分。

  • 比例项(P):最简单,直接使用当前采样时刻的误差e(k)P_out = Kp * e(k)
  • 积分项(I):积分是误差的累积。离散化后,用矩形法近似,即I_out = Ki * Ts * Σ e(i),其中i从0到k。在代码中,我们需要一个寄存器来累加历史误差(即误差和error_sum)。
  • 微分项(D):微分是误差的变化率。离散化后,用后向差分法近似,即D_out = Kd * (e(k) - e(k-1)) / Ts。这需要存储上一个采样时刻的误差e(k-1)

因此,完整的离散位置式PID公式为:u(k) = Kp * e(k) + Ki * Ts * Σ e(i) + Kd * (e(k) - e(k-1)) / Ts

这里有一个至关重要的细节:采样时间Ts必须显式地融入到积分和微分项的计算中。很多初学者实现的代码功能不正常,根源就在于忽略了Ts,导致积分和微分的作用与预期严重不符。在我的VHDL实现中,KiKd参数在输入时,实际对应的是Ki * TsKd / Ts,或者通过一个专门的“时间分频器”模块在计算时引入Ts因子。

2.2 VHDL实现中的关键设计决策

在VHDL中实现上述算法,需要将其映射到寄存器传输级(RTL)描述。

  1. 定点数运算:Basys 3 FPGA没有硬核浮点运算单元,使用浮点数会消耗大量逻辑资源且速度慢。因此,必须采用定点数。我选择了Q格式表示法,例如Q11.5(16位中,11位整数,5位小数)。这需要在精度和动态范围之间权衡。比例、积分、微分系数Kp, Ki, Kd也都用定点数表示。
  2. 有符号数与误差极性:这是一个我早期遇到的坑。误差e(k) = setpoint - feedback。当反馈值超过设定值时,误差应为负数,控制器应减小输出。必须使用VHDL的signed数据类型来表示所有涉及减法和可能为负值的信号,否则当误差为负时,无符号数运算会导致溢出或逻辑错误,控制器反而会继续增加输出,造成系统发散。
  3. 积分抗饱和与微分冲击
    • 积分抗饱和:当系统输出长时间处于极限值(如PWM占空比已达100%或0%)而误差仍未消除时,积分项会不断累积(“wind up”),导致系统恢复时产生大幅超调。一个简单的处理方法是,在计算积分项前,判断输出是否已饱和,若饱和则停止积分累加。
    • 微分冲击:设定值的突变会导致误差微分项瞬间巨大,产生控制冲击。可以采用“微分先行”或对设定值变化进行滤波,但在本项目中,设定值变化不频繁,主要处理反馈噪声。
  4. 时序与控制流:PID计算不应在每个时钟周期都进行,而应在每个采样周期触发一次。我设计了一个trigger进程,根据系统时钟和设定的采样频率生成一个脉冲信号pid_enable。只有当pid_enable为高时,才采样新的反馈值,计算误差,并更新积分、微分项及最终输出。这确保了控制器以正确的、离散的时间节奏运行。

注意:在仿真时,如果简单地用“反馈值是否变化”来触发计算,只能用于功能验证,不能反映真实的时间关系。实际系统中必须依赖精确的采样时钟。

3. 系统架构与FPGA外围接口设计

一个能工作的控制系统,PID算法核心只占一部分,更多的工作在于如何让FPGA与外部世界(传感器、执行器)可靠地对话。基于Basys 3开发板,我的系统架构如下图所示(在VHDL中体现为顶层实体和组件实例化):

+-----------------------+ | FPGA (Basys 3) | | | 设定值输入 ------->| 七段数码管显示模块 |<---- 当前反馈值 (通过拨码开关) | | | | | +-----------------+ | 红外传感器 ------->|->| ADC读取与预处理 |->| +-----------------+ (模拟电压0-1V) | | (XADC IP核) | | | | | +-----------------+ | | PID控制器 | | | | | (pid.vhd) | | v | | | | +-----------------+ | +--------+--------+ | | 滑动平均滤波 | | | | | (average.vhd) | | v | +-----------------+ | +-----------------+ | | | PWM生成模块 | | | | (控制风扇转速) | | | +--------+--------+ +-----------------------+ | v PWM信号 ------> 5V风扇

3.1 模拟信号采集:XADC IP核配置与电压缩放

Basys 3板载了Xilinx的XADC模块,这是一个双通道12位ADC。红外传感器的输出通常是模拟电压,其范围可能超过XADC允许的0-1V输入范围。因此,必须设计一个电压分压电路。我使用了两个电阻(3kΩ和1kΩ串联),将传感器输出电压衰减到原来的1/4。例如,传感器输出0-4V,经分压后变为0-1V,完美匹配XADC。

在Vivado中,通过IP Catalog实例化XADC Wizard IP核,配置如下:

  • 选择单通道模式(例如,使用VP/VN引脚对)。
  • 设置���样率为1 MSPS(实际根据系统需求可降低)。
  • 输出数据格式为12位二进制(0-4095对应0V-1V)。
  • 在VHDL顶层,需要实例化该IP核,并将其daddr_in端口连接到通道地址,den_indwe_in端口用于控制读写,do_out端口读取转换结果。需要编写一个状态机来周期性地启动转换并读取数据。

3.2 数字信号输出:PWM模块的精细控制

PID控制器的输出是一个数字量,需要转换为PWM波来控制风扇转速。PWM模块的核心是一个计数器和比较器。

  1. 设定一个PWM周期计数器,例如基于100 MHz系统时钟,计数到10000,则PWM频率为10 kHz。
  2. PID输出值(经过限幅处理,如0-10000)作为“比较值”。
  3. 在每个PWM周期内,当周期计数器小于比较值时,PWM输出高电平;否则输出低电平。高电平时间占总周期的比例即为占空比,直接控制风扇的平均电压。

实操心得:PWM频率的选择很重要。频率太低(如几十Hz),风扇可能会发出可闻噪音,且转速调节不平滑。频率太高(如上百kHz),可能超出风扇驱动电路的响应能力。对于普通直流风扇,1kHz到20kHz是一个常见的范围。我选择10kHz,效果较好。

3.3 人机交互与调试接口

调试是硬件项目的重中之重。Basys 3上的LED和七段数码管是宝贵的调试资源。

  • 16个LED:我将PID计算出的原始输出值(或PWM占空比)的高位连接到LED上。这样,通过观察LED的亮灭模式,可以直观判断输出是否饱和(全亮或全暗),或者是否在合理范围内动态变化。这在初期排查控制器是否“活着”时非常有用。
  • 4位七段数码管:我将其分为两组显示。前两位显示当前ADC读取的电压值(反馈值),后两位显示设定的目标电压值(设定值)。两者都显示到小数点后两位(例如,0.75V显示为“75”)。这让我能实时监控系统的“眼睛”(传感器)看到的和“大脑”(控制器)想要的之间差距,对于手动调节PID参数至关重要。

4. VHDL代码核心模块详解与实现

这里深入剖析几个关键VHDL模块的设计与编码细节。

4.1 PID控制器核心(pid.vhd)实体与架构

entity pid_controller is Port ( clk : in STD_LOGIC; reset : in STD_LOGIC; enable : in STD_LOGIC; -- 采样使能信号,来自trigger模块 setpoint : in STD_LOGIC_VECTOR (11 downto 0); -- 设定值,12位,对应0-4095 feedback : in STD_LOGIC_VECTOR (11 downto 0); -- 反馈值,12位 kp_num : in STD_LOGIC_VECTOR (15 downto 0); -- Kp分子,Q格式 kp_den : in STD_LOGIC_VECTOR (7 downto 0); -- Kp分母,用于缩放 ki_num : in STD_LOGIC_VECTOR (15 downto 0); -- Ki*Ts分子 ki_den : in STD_LOGIC_VECTOR (7 downto 0); -- Ki*Ts分母 kd_num : in STD_LOGIC_VECTOR (15 downto 0); -- Kd/Ts分子 kd_den : in STD_LOGIC_VECTOR (7 downto 0); -- Kd/Ts分母 output : out STD_LOGIC_VECTOR (15 downto 0) -- 控制器输出,16位 ); end pid_controller;

架构内部的主要进程:

process(clk, reset) begin if reset = '1' then error_sum <= (others => '0'); last_error <= (others => '0'); output_reg <= (others => '0'); elsif rising_edge(clk) then if enable = '1' then -- 仅在采样时刻计算 -- 1. 计算当前误差(有符号数运算) current_error_signed <= signed('0' & setpoint) - signed('0' & feedback); current_error <= std_logic_vector(current_error_signed(11 downto 0)); -- 2. 计算比例项 P = Kp * e(k) p_term <= resize(signed(current_error) * signed(kp_num), p_term'length) / signed(kp_den); -- 3. 计算积分项 I = Ki * Ts * Σe(i) (简易抗饱和处理) if output_reg < OUTPUT_MAX and output_reg > OUTPUT_MIN then error_sum <= error_sum + current_error_signed; end if; i_term <= resize(error_sum * signed(ki_num), i_term'length) / signed(ki_den); -- 4. 计算微分项 D = Kd * (e(k) - e(k-1)) / Ts d_term <= resize((current_error_signed - last_error) * signed(kd_num), d_term'length) / signed(kd_den); last_error <= current_error_signed; -- 5. 求和并限幅 pid_sum <= p_term + i_term + d_term; if pid_sum > OUTPUT_MAX then output_reg <= OUTPUT_MAX; elsif pid_sum < OUTPUT_MIN then output_reg <= OUTPUT_MIN; else output_reg <= pid_sum; end if; end if; end if; end process; output <= std_logic_vector(output_reg);

4.2 滑动平均滤波器(average.vhd)抑制传感器噪声

红外传感器输出易受环境光干扰,存在高频噪声。直接在数字域使用滑动平均滤波器是一种简单有效的平滑方法。

entity average is Generic ( DATA_WIDTH : integer := 12; AVG_WINDOW : integer := 8 -- 平均窗口大小,取2的幂次便于除法 ); Port ( clk : in STD_LOGIC; en : in STD_LOGIC; din : in STD_LOGIC_VECTOR (DATA_WIDTH-1 downto 0); dout : out STD_LOGIC_VECTOR (DATA_WIDTH-1 downto 0) ); end average;

其原理是维护一个长度为AVG_WINDOW的移位寄存器组。每次新数据到来时,将其移入,最老的数据移出,并计算寄存器组内所有数据的和,然后右移log2(AVG_WINDOW)位(即除以AVG_WINDOW)得到平均值。这种方法能有效滤除随机噪声,但会引入一定的相位滞后。窗口越大,滤波效果越好,但滞后越严重,需要根据系统响应速度折中。我选择窗口大小为8,在平滑性和实时性之间取得了不错平衡。

4.3 顶层模块集成与时钟域管理

顶层文件(top.vhd)负责将所有模块像搭积木一样连接起来,并处理时钟和复位信号。

  • 时钟分频:系统主时钟为100MHz。需要生成多个不同频率的时钟使能信号:
    • ADC_sample_en: 用于触发ADC采样,频率约50kHz(周期20us)。
    • PID_enable: PID计算使能,与采样率同步,也是50kHz。
    • PWM_clk_en: 用于更新PWM比较值,频率为PWM频率(10kHz)。
    • display_refresh_en: 用于刷新七段数码管显示,频率约100Hz。 这些使能信号通常由计数器生成,是同步设计,比使用分频后的时钟信号更安全。
  • 数据通路:明确数据流:ADC数据 -> 平均滤波 -> PID反馈输入端。PID输出 -> 限幅 -> PWM模块。设定值可以通过拨码开关或按钮设置,并传递给PID和显示模块。
  • 复位同步:确保所有模块在系统上电或按下复位键时,能同步地初始化为已知状态。

5. 系统调试、参数整定与问题排查实录

将代码综合、实现并下载到板子后,真正的挑战才开始。乒乓球可能一动不动,也可能疯狂振荡。

5.1 PID参数整定实战:从零到稳定

对于这个二阶欠阻尼系统(乒乓球在气流中),我采用了经典的试凑法,并结合了一些观察经验。

  1. 纯比例控制(P):先将KiKd设为0,逐渐增大Kp。观察现象:Kp太小,小球无法到达设定高度;Kp增大,小球开始上升,但会在设定高度下方某个位置稳定(静差)。继续增大Kp,静差减小,但会出现振荡。记录下开始出现持续振荡的Kp值,记为Ku(临界增益)。
  2. 加入积分控制(PI):引入一个较小的Ki值。积分作用能消除静差。但Ki太大会导致系统响应变慢,超调增大,甚至引发低频振荡。需要耐心微调KpKi,目标是让小球能较平稳地到达设定点,即使有轻微过冲也能快速稳定。
  3. 加入微分控制(PID):微分能预测误差变化趋势,抑制过冲。但微分对噪声非常敏感。由于我们已经有了平均滤波,可以尝试加入较小的Kd。观察效果:Kd有助于减小超调,让稳定过程更“干脆”。但Kd过大会放大高频噪声,可能导致控制输出高频抖动,反而使系统不稳定。

踩坑记录:最初调试时,我直接使用了未经滤波的ADC数据,且Kd设置稍大,结果风扇转速疯狂跳动,小球根本无法稳定。后来意识到是微分项放大了传感器噪声。教训是:在引入微分项之前,必须确保反馈信号足够干净。

最终,我通过观察七段数码管上反馈值的跳动情况,以及LED显示的输出变化趋势,结合小球的实际运动,找到了一组相对稳定的参数。这个过程没有捷径,需要反复试验、观察和调整。

5.2 常见问题与排查速查表

现象可能原因排查步骤与解决方案
小球完全不动,风扇不转1. PWM输出引脚配置错误。
2. PID输出始终为0或最小值。
3. 电源或风扇连接问题。
1. 检查约束文件(.xdc),确认PWM输出引脚已正确分配到板载PMOD口或GPIO,并用示波器或逻辑分析仪检测该引脚是否有信号。
2. 使用ILA(集成逻辑分析仪)IP核,抓取setpoint,feedback,error,output等内部信号,看计算逻辑是否正确。检查复位信号是否一直有效。
3. 用万用表测量风扇供电电压。
小球剧烈上下振荡1. PID参数不合理,尤其是KpKd过大。
2. 传感器噪声大,且微分项Kd不为零。
3. 采样频率过高或过低,与系统动态不匹配。
1. 回归纯比例控制,调小Kp直至振荡停止,再缓慢增加。
2. 增大平均滤波的窗口,或尝试更复杂的滤波器(如一阶低通)。暂时将Kd设为0。
3. 尝试调整trigger模块的采样分频系数,改变采样周期。通常采样频率应为系统带宽的5-20倍。
小球能稳定,但存在静态误差积分作用不足或未生效。1. 检查积分项Ki是否大于0。
2. 检查积分抗饱和逻辑是否过于激进,导致在稳定点附近积分停止累加。
3. 适当增大Ki,但需配合调整Kp
响应速度慢,小球移动迟缓1.Kp太小。
2. 积分项Ki主导,系统处于“软”控制状态。
3. PWM频率过低,风扇响应跟不上。
1. 在保持稳定的前提下,逐步增大Kp
2. 适当减小Ki,让比例项起主要作用。
3. 提高PWM生成模块的计数频率,例如将PWM频率从1kHz提升到10kHz。
改变设定值后,系统发散1. 设定值变化幅度过大,超出线性范围。
2. 参数是针对某个工作点优化的,系统非线性强。
1. 实现设定值斜坡函数,让其缓慢变化,而不是阶跃跳变。
2. 考虑在不同高度区间使用不同的PID参数集(增益调度),或者采用非线性PID。

5.3 使用Vivado仿真与调试工具

在烧录到板子前,仿真能避免很多低级错误。

  1. 编写Testbench:为pid.vhd编写测试平台(tb_pid.vhd),模拟设定值阶跃变化和反馈值变化。观察output信号是否按预期响应。重点测试误差正负变化时,输出增减方向是否正确。
  2. 行为仿真:在Vivado中运行仿真,查看波形图。验证使能信号enable触发时,计算是否发生。检查定点数运算有无溢出。
  3. ILA(集成逻辑分析仪):这是FPGA调试的神器。在设计中插入ILA IP核,将想要观察的内部信号(如error_sum,p_term,i_term,d_term等)连接到其探针上。综合实现后,生成比特流文件并下载,在Vivado Hardware Manager中设置触发条件,即可像示波器一样实时捕获FPGA内部信号的变化,这对分析动态过程和无器件现象至关重要。

6. 项目优化与扩展思路

基础版本成功后,可以从多个维度进行优化和扩展,这体现了数字控制的灵活性。

6.1 进阶滤波:从平均滤波到数字滤波器

滑动平均滤波器是一种特殊的FIR(有限长单位冲激响应)滤波器。我们可以将其通用化,实现一个可配置系数的FIR滤波器IP核。通过MATLAB或Python的scipy.signal工具设计一个低通滤波器(如汉宁窗、凯泽窗),计算出滤波器系数,将其量化为定点数,写入VHDL代码的系数ROM中。这样可以实现更精确的频响控制,更有效地滤除特定频段的噪声,同时可能减少相位滞后。

6.2 串级PID控制器(Cascade PID)设计

对于这个乒乓球悬浮系统,一个更高级的控制策略是串级控制。其思想是设计两个嵌套的PID环:

  • 外环(主环):以乒乓球高度为反馈,输出作为内环的设定值。这个设定值不再是高度,而是期望的风扇转速
  • 内环(副环):以风扇的实际转速(可通过测速计、编码器或反电动势估算获得)为反馈,控制PWM占空比,快速跟踪外环给出的转速指令。

这样做的好处是,内环可以快速抑制风扇电机本身的扰动(如电压波动、负载变化),外环则专心处理高度控制。内环的动态响应通常比外环快得多,整个系统的抗干扰能力和性能会得到提升。在VHDL实现上,需要实例化两个PID控制器模块,并正确连接它们的设定值和反馈通路。

6.3 自适应与参数自整定

能否让FPGA自己找到合适的PID参数?这是一个更前沿的方向。可以尝试实现简单的自整定算法,如继电器反馈法。思路是:先将控制器置于开关模式(类似Bang-Bang控制),使系统产生稳定振荡,测量其振荡周期和幅度,然后根据齐格勒-尼科尔斯(Ziegler-Nichols)等经验公式计算出一组初始PID参数。这需要额外的逻辑来检测振荡周期和幅值,并在线更新PID模块的Kp,Ki,Kd参数寄存器。

6.4 系统非线性补偿

实验中发现,在管道的不同高度,系统的增益(即风扇转速变化对高度的影响程度)是不同的。这是一个非线性系统。简单的固定参数PID在全程范围内性能可能不最优。可以在FPGA内实现一个查表法(LUT)的增益调度器。根据当前高度(反馈值)所在区间,从预先计算好的参数表中读取对应的PID参数组,动态加载到PID控制器中,从而在全范围内获得一致的良好性能。

这个基于VHDL的PID控制器项目,从理论推导、代码编写、仿真测试到硬件实现和物理调试,完成了一个完整的数字控制系统开发流程。它深刻地揭示了软件算法与硬件实现之间的差异,比如对时序的严格考量、定点数精度的把握、噪声处理的重要性等。最终看到乒乓球在管道中稳稳地悬浮在预设高度时,那种将抽象算法转化为物理现实的成就感,是纯软件仿真无法比拟的。希望这份详细的梳理和记录,能为你的FPGA控制之旅提供一块坚实的垫脚石。如果在具体的代码实现或调试中遇到问题,欢迎随时交流探讨。

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

基于Arduino的交互式感官墙:从传感器到灯光音效的嵌入式开发实践

1. 项目概述与核心价值如果你对用技术创造一些能与人“对话”的物件感兴趣&#xff0c;那么这个基于Arduino的交互式感官墙项目&#xff0c;绝对是一个能让你从想法快速落地到实物的绝佳案例。它远不止是一个简单的灯光装饰&#xff0c;而是一个融合了嵌入式开发、传感器技术、…

作者头像 李华
网站建设 2026/6/4 15:11:58

宏观认知(4):AI与社会——吴恩达《AI for Everyone》Week4学习笔记

文章目录本章小结个人感悟前言1. AI对经济的宏观影响2. AI对就业的影响&#xff1a;一个被严重夸大的故事3. AI对个人工作与学习的影响4. AI的社会挑战&#xff1a;偏见、隐私与滥用5. AI与教育&#xff1a;培养面向未来的人才6. 应对AI时代&#xff1a;个人可以做什么7. 从201…

作者头像 李华
网站建设 2026/6/4 15:10:11

北斗导航 | 融合北斗导航的未来导航新方向

北斗导航的未来,早已不再是单纯的“我在哪”,而是走向时空智能服务——把定位、导航、授时与通信、遥感、人工智能深度融合,构建一个全时全域的感知网络。以下几个融合新方向,正在重新定义导航的边界: 1. 通导遥一体化:天基信息的实时闭环 将北斗的导航能力、低轨星座的…

作者头像 李华
网站建设 2026/6/4 15:09:08

Opus 4.6临界点:工作流重构与Agent自治协同实战指南

1. 这不是又一个“更强的AI”&#xff0c;而是工作流重构的临界点Claude Opus 4.6发布那天&#xff0c;我正坐在上海陆家嘴一家律所的会议室里&#xff0c;帮客户做一份跨境并购的尽调摘要。投影仪上还开着FactSet的实时数据终端&#xff0c;屏幕右下角突然弹出一条财经快讯推送…

作者头像 李华
网站建设 2026/6/4 15:04:56

抖音内容高效保存方案:douyin-downloader开源工具深度解析

抖音内容高效保存方案&#xff1a;douyin-downloader开源工具深度解析 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback s…

作者头像 李华