借助Verilog,在FPGA中实现了带编码器的两台电机的电机控制系统的RTL级设计。

介绍
借助硬件描述语言 (HDL) Verilog 和 AMD Vivado 设计套件,在 AMD Spartan-7 FPGA 中实现带编码器的两个电机的控制器系统的 RTL 设计。
在这个项目中,使用了搭载 Spartan-7 FPGA开发板,将使用它作为电机控制器系统。
在项目开始前,肯定会有人问:“既然用MCU编程要简单快捷得多,为什么还要用 FPGA 来做这么复杂的工作呢?” 这个问题问得好。所以,我们将简要探讨一下人们选择 FPGA 的各种原因。
在 FPGA 与MCU的对比竞争之后,将介绍电机驱动器、H 桥、PWM、编码器、PID 反馈控制系统、编码和测试、收集日期的分析等多方面的基本概念。然后,将详细介绍如何构建自己的 FPGAbot。
FPGA 与MCU
为特定产品或可交付系统选择“最佳”处理技术,在许多方面对其成功至关重要。影响因素包括支持成本、供应链、盈利能力、寿命等等。从工程角度来看,决策中最具影响力的典型因素是时间、成本、面积和功耗。
FPGA 优势
FPGA 以其低延迟、固有的并行处理能力和灵活性而闻名。此外,FPGA还能为许多独立的计算模块、外部传感器和执行器等提供高确定性和精确的定时/延时。因此,会发现 FPGA 被用于许多关键任务应用,例如:
1)军事和航空航天系统
雷达系统 - 例如实时(RT)信号处理和数据采集。 无人驾驶飞行器(UAV)——例如飞行控制、传感器处理和安全通信。
航空电子设备、卫星、导弹制导系统等
2)工业控制系统(ICS)
对电网、炼油厂等关键基础设施进行实时控制和监控。
工业自动化 - 机器控制,实现故障检测、冗余和容错控制。
功能安全——例如汽车和工业控制的容错系统。
3)医疗器械
诊断成像系统 - 快速准确的数据处理。
病人监护——FPGA 为病人监护设备提供高性能、RT 监护和信号分析。
救生设备...
ETC。
4) 电信等
许多关键任务应用程序需要实时处理、确定性行为、低延迟、可编程性、并行处理能力、安全性和功率效率。
MCU优势
对于成本敏感的产品或普通爱好者来说,MCU 是节省成本和加快系统开发周转的更好选择。
微控制器使用标准编程语言进行软件开发,例如 Python/microPython 和 C/C++。FPGA 设计的学习曲线(架构、硬件描述语言、时序问题等)则要陡峭得多,需要投入大量时间和经验才能高效完成。
MCU 也更适合外设接口和执行重复性任务。通用架构、串行执行流程以及易于外设集成的特性,使得 MCU 在非关键任务系统中更具吸引力。
了解了这些之后,现在让我们深入了解移动机器人电机控制系统的细节!
高级电机控制器系统设计
本节介绍了移动机器人电机控制器设计所需的主要外部元件和FPGA功能模块。此外,还提供了系统的高层原理图。
电机驱动器
FPGA 需要分别驱动两个 12V 直流有刷电机。我以前用过 12V 直流电机,发现这个电压水平能够满足我的 DIY 机器人所需的扭矩。我通常会选择低转速电机(例如 190 RPM),因为它们比高转速电机提供更大的扭矩。
L298N H 桥电机驱动器是驱动 12V 电机的绝佳选择,因为它价格低廉,业余爱好者已经写了很多关于其使用的文章,并且易于连接。
L298N 本质上是一个放大器,其数字接口由较小的输入控制电压(例如 3.3V 或 5V)控制,并在较大的直流电压范围内产生比例输出。L298N 模块可在 5 至 35V 直流电压 (Vs) 范围内工作。由于我们使用的是 12V 直流电机,因此源电压为 12V,如图 1 所示。
注意:如果要启用 L298N 板载的 5V 电源调节器,则最大输入直流电源不应超过 12V。否则,5V 直流调节器会过热并关闭。
编码器
我总是购买配备霍尔效应传感器的直流电机。这些传感器可以测量每个电机的速度和旋转方向。在本设计中,编码器反馈用于PID算法,以保持FPGA机器人直线稳定地移动。里程也可以根据这些信息计算出来。将在后面的技术回顾部分和FPGA电机控制器设计细节部分详细讨论编码器的工作原理和使用方法。
因此,该系统至少需要以下功能元素:
顶层架构
可以选择 (1) 在使能端使用脉冲宽度调制 (PWM),并通过 IN1、2、3 和 4 上的高低逻辑电平控制方向(图 2a);或 (2) 在所有四个输入端口上使用 PWM,并将使能端设置为逻辑高电平(图 2b)。虽然已经创建了这两个版本,但在本项目描述中,将选择选项 (1)(图 2b)。选择选项 (1) 的原因将在 L298N 部分解释。
输入设定点(PWM 的占空比设置)和旋转方向源信号选择器。
编码器计数器
PID 反馈控制系统/算法将电机速度同步到设定值。
数字控制器,在本例中是 Spartan-7 FPGA。



对于上面列出的前两点,即从设定值生成PWM信号所需的逻辑,了解L298N的工作原理非常有帮助。因此,下一节(可选阅读)将介绍这部分内容以及其他背景知识。
技术回顾
本节将对组件进行技术回顾,并介绍与这些组件接口所需的数字和模拟概念。本节涵盖的信息并非执行本项目的必要条件,但有助于理解创建各种模块和逻辑的原因。
L298N电机驱动器
如图 3 所示,L298N 可归类为一种直流放大器,它在其输入端转换脉冲宽度调制 (PWM) 信号,在本例中为 0 和 3.3VDC,并将其按比例转换为稳定的 0V 直流电压至 Vs,如前所述。

FPGA 的较低输出电压 0 - 3.3V 能够驱动 TTL 逻辑输入 (IN1 - IN4) 并使能 A 和 B 的原因在于 [1] 中列出的规格以及图 4b 所示的规格。其中说明如下:
控制信号输入电压范围:
逻辑低电平:-0.3V≤Vin≤1.5V
逻辑高电平:2.3V ≤ Vin ≤ Vss
其中,Vss 由 L298N 模块板载的 5V 直流稳压器供电。由于 FPGA 不会从 L298N 接收数字数据,因此无需逻辑电平转换器。
L298N 模块是基于 L298 集成电路 (IC) 构建的。L298 IC 是一款高电压、高电流双全桥驱动器。L298 IC 的额定电压 Vs = 5V 至 46V(图 2a 和 2b),但 L298N 模块在 35V 以上的电压下存在散热和元件限制。


L298 双 H 桥
让我们快速看一下 H 桥电路。如果还没有完全理解下面的信息,不用担心,但为了完整性,我想补充一下。另外,如果愿意,可以跳过本节。
图 5 是 [1] 中 L298 框图的复制版。图中展示了数字电路(与门和非门)和模拟电路(BJT 晶体管和电阻)的混合。逻辑门和 BJT 晶体管构成了电流控制电路。为了更好地理解电流控制电路的整体功能,下面给出了该图的简化版本及其说明。

图 6 用开关概念代替了电流控制电路。这展示了 H 桥电路的工作原理,我们也将用它来解释在 IN 引脚和 EN 引脚上使用 PWM 的区别。

要使电机正转,需要闭合 S1 和 S4,完成从 Vs 经电机到地的电路(即电流路径)。S3 和 S2 根据需要保持断开。要使电机反转,则需要相反的情况,即闭合 S3 和 S2,断开 S1 和 S4。图 7a 和 b 说明了这一概念。

将此电路分析与图6联系起来,让我们看看用与逻辑门和BJT实现的开关。下面的逻辑方程等同于与门的输出,我们在这里将其标记为_d(代表数字)。
SW1_d = IN1 AND ENA
SW2_d = NOT IN1 AND ENA
SW3_d = IN2 AND ENA
SW4_d = NOT IN2 AND ENA
当上述任意 SWx_d 等于逻辑高电平时,相应的 BJT 导通。这是通过基极-发射极正向偏置(Vbe 超过基极输入和发射极输出引脚之间 +0.7V 的压降阈值)来实现的。当集电极电压分别大于基极和发射极电压(Vc >> Vb > Ve)时,导通的晶体管就像一个闭合的开关。参见图 8。

希望开关类比能够简化数字晶体管的控制逻辑。如果感兴趣并且具备相关背景知识,建议找一本好书或参考资料,进一步了解晶体管的工作原理以及不同类型的晶体管。
接下来,如果需要复习一下 PWM,我们来快速回顾一下。如果不需要,可以跳过此部分。
PWM
为了使连接到 L298N 电机驱动器的电机实现变速,我们使用了 PWM(脉冲宽度调制)。PWM 是一种获取可变模拟电压的数字方法。因此,方波数字信号的占空比(逻辑高电平时间与总脉冲周期的关系)可以通过增加或减少来改变模拟电路中的平均电压(图 9)。例如,如果 NPN 晶体管的基极-发射极结在给定的时间内导通时间更长,则电流会更大,从而导致集电极输出连接和发射极(在本例中为地)之间的电压降 (Vce) 更大。图 10 演示了这一概念 [7]。


L298N PWM输入响应特性
如本文开头所述,使用 PWM 实现变速有两种不同的方法。方法 1 是用 PWM 驱动输入引脚(IN1、2、3 和 4),并将使能引脚设置为高电平,如图 11a 所示。方法 2 是将输入引脚对设置为高电平和低电平,以获得正确的旋转方向,并用 PWM 驱动使能引脚,以实现变速,如图 11b 所示。


使用方法 1 可获得如图 7a 和 7b 所示的电流。唯一的区别在于每个脉冲周期内从 Vs 到 Gnd 的电流流动时间。
使用方法 2 会得到略有不同的响应。如果驱动电机正向旋转,IN1 上的 PWM 信号将处于活动状态,而 IN2 上的 PWM 信号将保持在低电平。由于 ENA 已通过跳线设置为逻辑高电平,因此开关切换完全由 IN1 上的有效 PWM 信号控制。将 IN2 和 ENA 都设置为受控的稳定值(0 和 1)后,SW3 始终设置为“关闭”或“打开”,SW4 始终设置为“打开”或“闭合”。但是,用于 SW4(~In1 和 EnA)的非门将在 PWM 占空比的低电平期间打开 SW2。这发生在 SW1 处于“关闭”或“打开”状态时。因此,电机的正极和负极连接到地。这会导致在 SW2 处于“打开”状态时释放反向的感应电动势 (EMF)。这种配置在较低转速下有用,但在较高转速下,由于反向 EMF 电压的反复损耗,会导致更高的电流消耗。

12V直流编码器齿轮电机
我们为移动机器人选择的直流电机是专门配备磁编码器的。这对于此类直流电机来说很常见。对电机控制精度要求更高的直流电机则使用光学编码器。
旋转磁编码器有两个霍尔效应传感器。它们相对于电机轴线呈 90 度角放置。图 13 展示了这种布置如何产生相位差 90 度的编码器信号。

此图简化了,因为只使用了两个磁极。大多数带有磁编码器的电机都有数十到数百个这样的磁极,从而提高了电机旋转位置的分辨率。此外,有些系统会配备 4 个霍尔传感器,以进一步提高精度。
上面显示的霍尔效应传感器产生的两个信号被指定为通道 A 和 B。这些通道具有以下特点:
它们的相位差为 90 度(见图 14)。
如果 A 领先于 B,那么电机就只能朝一个可能的方向转动。
如果 B 领先于 A,那么电动机的转动方向与前一个子弹的转动方向相反。
方波的频率与齿轮的旋转速度成正比。

通过计算电机单次旋转产生的脉冲数,可以推导出由所用物理极数获得的分辨率。该信息可用于估算机器人的里程,方法是使用累积计数(我喜欢称之为“tics”)来计数/旋转,并使用车轮的直径(或半径)来计算其周长。
FPGA电机控制器设计细节
本节将详细介绍移动机器人电机驱动系统的关键 Verilog 代码。为了方便起见,该代码将与图 2a 所示的系统框图关联起来,并在下面的图 15 中复制。此外,我推导的 PID 算法可用作超前-跟随电机速度比较系统,并将进行详细解释。
Verilog 源层次结构
从图 15 所示的初始顶层设计中,派生出 Verilog 模块层次结构,然后在 AMD Vivado 中创建(图 16)。


外部引脚声明
接下来是用于 FPGA 与外部接口的输入和输出信号声明的 Verilog 代码(图 17)。在本例中,它用于 L298N、两个 12V 直流编码器电机(图 1)以及 RC 接收器单元(未显示)。

编码器计数器模块
编码器计数器模块所用的代码源自fpga4fun.com [9](http://fpga4fun.com/)。在那里,可以找到详细的解释以及描述所用逻辑的波形图。本质上,只需使用系统时钟进行过采样、几个逻辑门和 D 触发器(为跨时钟域添加了几个额外的触发器)即可确定自旋的计数和方向。图 18 展示了代码的实现。

我还添加了仅创建正计数的逻辑。这对于我们稍后介绍的PID是必需的。
RC信号选择模块
此模块的名称有点用词不当。最初,该模块仅用于接收来自 RC 模块的方向信号,并将每个信号转换为两位值(图 19)。一位指示左轮应该前进还是后退,另一位指示右轮。每位都位于每个设定值(左轮和右轮)的最高有效位 (MSB)。目前,来自此模块并进入 PID 模块的左右电机设定值是相同的值(位 [6:0])。可以在顶部模块中更改用户设定值,方法是将 curr_setpt1 和 2 初始化为新值,然后运行 Vivado 进行综合、实现并生成比特流。此过程最多需要两分钟左右。


PWM模块
如上所述,用户设定值(顶层名为 curr_setpt1 & 2)存储在 8 位寄存器的低 7 位中。因此,设定值取值范围为 0 至 127。该值表示馈入 L298N 的 PWM 的占空比。PWM 如何将设定值转换为占空比时间宽度?这是通过 PWM 模块中的 7 位计数器实现的。PWM 的输出最初为逻辑高电平(计数器 = 0)。当以 256 kHz 时钟频率计数的计数器计数到设定值时,PWM 输出逻辑低电平,直到计数器达到 127。结果是一个 2kHz 方波 PWM,占空比为设定值/127。
使用 Vivado 时钟向导从 100 MHz 系统时钟生成了一个 8.192 MHz 时钟。这样,256 kHz 模块中计数器的一位只需要与逻辑“1”进行比较。最终的 PWM 频率为 2 kHz,接近 1.5 kHz 的示例 [2],并且运行良好。
PID
PID 控制器模块的设计并不像我想象的那么简单。问题在于设定值(0 - 127)与每设定采样周期 T(例如 100 毫秒)读回的增量计数之间的转换。图 21 展示了讨论中的 PID 反馈回路框图。

对我来说,这里的问题是我没有看到(也没有产生)从 delta tics(计数)/T 回到设定值的低错误转换。
因此,决定使用设定点输入 r[k] 作为基准值,并添加一个基于两个电机先前计数和当前计数差异的修改值(误差值),从而包括每个电机每个周期 T(速度)总计数差异的离散时间分析/比较。
根据使用这些小型直流编码器电机的经验,注意到,在相同的PWM占空比设置下,一个电机的扭矩往往大于另一个电机。因此,在PID控制算法中,可以快速确定响应速度更快的电机,并将其用作主电机,以匹配速度和行驶距离。这样,速度修改只会针对速度较慢的电机的设定值输入r[k]进行。否则,电机的转速会非常快地上升。
让我们看一下所提出的领先-跟随方程,然后看一下这个离散领先-跟随电机反馈控制回路的伪代码。
e[k] = Feedback_1[k] - Feedback_2[k]; (1)
其中,e[k] 是在采样周期 k 处计算出的误差,Feedback_n[k] 是在采样周期 k 处电机 n(即 n = 1 或 2)的编码器计数器值。
Δx1[k] = Feedback_1[k] - PrevFeedback_1[k-1] (2)
Δx2[k] = Feedback_2[k] - PrevFeedback_2[k-1] (3)
Δx1x2[k] = Δx1[k] - Δx2[k] (4)
其中,PrevFeedback_n[k-1] 是电机 n 的编码器计数 Feedback[k-1] 在采样周期 k-1 保存,Δx1x2[k] 是电机 1 和 2 的转速差。
与图21所示的PID控制系统一样,有一些权重值(常数Ki、Ke和Kv)用于将公式(1)和公式(4)相乘,然后再相加。因此,得到的公式如下(图22所示的部分代码):

我们将在下面讨论局部放电后误差方程的总体方程为:
u[k] = setpoint[k] + motorNSpdMod (5)
其中,N 再次表示电机数量 1 或 2。
可能注意到,公式 (5) 缺少积分元素。我将解释如何在 u[k](公式 5)中添加积分,这是平滑 FPGA 代码部分中误差振荡(反复从正到负)的关键,但最初并未考虑到这一点。
在 Vivado 中使用 Verilog 编写设计代码后,我创建了一个直流编码器电机的仿真模型,并在测试台上进行了两次实例化。该电机模型从被测单元 (UUT) 采样 PWM 波输入,并将计算出的转速转换为相应频率的编码器 A/B 通道波形。编码器波形通道被输入到 UUT 中。此外,我添加了一个“阻力”值,使电机的转速相对于其他电机有所降低。这使得速度较慢的电机的编码器数量较少,从而与实际电机和系统响应具有一定的相似性。当然,这是一个非常简单的模型,因为对一个电机增加的阻力是恒定的。如果在电机模型中至少创建一个简单的物理惯性响应,将会非常有利。该模型还假设移动机器人使用恒定的平坦水平表面。
不幸的是,我看到机器人落地后的响应与仿真结果不同,而我之前预想的没有物理模型会是这样的。
FPGA 中使用的公式经过测试,并根据需要进行了修改(例如,缩短 T 值、调整 Ke 和 Kv 值等),最初使用 Arduino Mega MCU 和 FPGAbot(不含 FPGA)在 C/C++ 中进行了验证。这缩短了验证和修改的周期。以下代码片段来自 Mega 测试(函数 PD_Alg(args)),代表了领先跟随算法(图 22)。

代码的下半部分,motorNSpdMod 会根据最大设定值 (255 - C_fwdSpd - 20) 进行检查,以防止值过高。255 是最大占空比值(0 到 255),C_fwdSpd 实际上是两个电机的设定值(相同值),而 20 只是一个任意值,在 Arduino IDE 控制台上查看 Serial.print() 滚动显示的数据时,这个值似乎是合理的,如图 23 所示。

从打印出来的数据来看,显然可以通过稍微调整 Ke 和 Kv,甚至在混合中添加一个积分元素来实现更好的响应。
FPGA 变体的领先-跟随 PID 方程实现
图 23a 和 b 分别给出了在领先-跟随方程 (5) 中添加积分之前和之后的仿真结果。


下图(图 25a 和 b)分别表示 e[k](误差)、dx1dx2(电机速度变化量)和 u[k](电机调制值)。图 25b 还添加了 Ki*integral[k](调制值之和乘以分数)的数据,这些数据列于公式 (6) 中。
u[k] = setpoint[k] + motorNSpdMod + Ki ΣmotorNSpdMod (6)


从这两幅图中不难看出,本例中积分非常迅速地抑制了误差和速度增量的振荡。这使得 u[k] 的调整非常平缓,与原始设定值吻合。
领先-跟随FB控制算法Verilog代码
图 22 所示的 C/C++ 代码在 MCU 上按顺序运行。这就是它们的工作原理。为了在 Verilog 中复制顺序计算,使用了一个状态机,每 10 ns(100 MHz 系统时钟)从一个计算状态切换到另一个计算状态。该算法的计算分布在 7 个状态中,计算时间为 70 ns。并不担心将计算时间缩短几十纳秒,并牺牲代码的可读性。与采样周期 T 相比,节省的时间在持续时间上要小三个数量级。为了使其更具可读性,将计算拆分成多个简洁的阶段会创建数据依赖关系,从而需要额外的时钟周期。
为了避免使用浮点数表示 Ki、Ke 和 Kv,从而节省大量 FPGA 逻辑资源,通过将误差、速度增量和积分右移(除以 2^n)来近似这些小数值。然而,在 Verilog 中使用有符号和无符号变量(寄存器)时需要谨慎。我发现,如果使用逻辑移位“>>”,负数右移最终会得到一个很大的正数。为了避免这种情况,需要使用算术右移表示“>>>”。
图 26a、b 和 c 中的代码演示了目前为止在 Verilog 中实现 PID 领先-跟随反馈 (FB) 控制算法所涵盖的概念,并在 Vivado 中进行了仿真(图 24a 和 b)。



其他
一些其他的细节,可以看一组图片吧~







项目地址
完整的项目可以在Github上找到。
https://github.com/ZenoRobotics/FPGAbot-HacksterIO
参考
[1] 意法半导体 L298 IC 数据表。
https://www.st.com/en/motor-drivers/l298.html
[2] BYU(杨百翰大学)发布的 L298N 电机驱动器及 PWM 详情 PDF。
https://brightspotcdn.byu.edu/cd/87/bbf866d84c06a0c52fa995396f30/l298n-motor-driver-quick-start-v6.pdf
[3] 意法半导体 L297 步进电机控制器数据表。
https://www.st.com/content/ccc/resource/technical/document/datasheet/f9/35/6e/3f/48/18/48/51/CD00000063.pdf/files/CD00000063.pdf/_jcr_content/translations/en.CD00000063.pdf
[4] “什么是H桥”。
https://www.build-electronic-circuits.com/h-bridge/
[5]“NPN晶体管”。
https ://www.electronics-tutorials.ws/transistor/tran_2.html
[6]“PWM基础知识”。
https ://docs.arduino.cc/learn/microcontrollers/analog-output/
[7] Rechtenwald,G.“基本直流电机电路”。
https ://cdn.sparkfun.com/assets/resources/4/4/DC_motor_circuits_slides.pdf
[8] “理解光学和磁性编码器的分辨率”。
https ://www.electronicdesign.com/technologies/components/article/21798142/understanding-resolution-in-optical-and-magnetic-encoders
[9]“FPGA项目:正交解码器”。
https ://www.fpga4fun.com/QuadratureDecoder.html