这篇文章来源于DevicePlus.com英语网站的翻译稿。
在本教程的第1部分中,我们介绍了FPGA,并在嵌入式 Micro的Mojo FPGA上完成了一个简单的入门项目。在第2部分中,我们将介绍一个更复杂的项目:在Mojo FPGA上实现硬件PWM。
脉冲宽度调制(PWM)被广泛应用于嵌入式系统中,用以控制LED亮度、电机转速,甚至可用于通信。如果您使用的是Arduino,那么在使用analogWrite()函数的时候一定遇到过PWM。在Mojo上实现PWM之前,我们应该首先了解一下PWM是如何工作的!
脉冲宽度调制(PWM)
微控制器和其他嵌入式系统处理器使用数字信号来进行计算和执行任务。这些信号仅以两种电平状态中的一种出现:“高”电平(通常为3.3V或5V)和“低”电平(通常为0V)。这两种电平分别编码为二进制1和0,因此可用于执行各种各样的工作任务。
但是,如果我们想输出比“开和关”更多分级的电压呢?在诸如上文中所提到的要求输出可变强度的应用激发了对该问题的探索。一种解决方案是将系统连接到称为“数模转换器(DAC)”的外部设备,该设备从主处理器获取二进制1和0形式的数字信号输入,并输出几乎连续的0V到系统“最大电压”范围内的电压值。但是,对于大多数嵌入式系统应用程序来说,存在一种更简单的方法:PWM!
从某种程度上讲,PWM利用了人类的感知系统和物理系统(如电机)能够对变化的输入进行快速平均这一事实。系统生成一个包含高频率脉冲的数字信号,因此很难将每个脉冲区分开来。在给定的波形周期内,信号在该时间内的某些时间为高电平,其余时间为低电平—高电平所占的时间比例称为信号的占空比或工作周期。平均输出效果(无论是LED的亮度还是电机转速)取决于PWM信号的占空比。当然,顾名思义,我们可以通过更改波形的占空比(脉冲宽度)来改变输出效果!下图显示了不同占空比及其对应波形的比较。
图1:不同占空比及其相关脉冲波形的比较。显然,当信号一直为高电平,占空比为100%时,出现了最“强烈”的输出效果
Arduino板上的微控制器IC(如ATMega芯片)具有专用于根据处理器指令生成PWM信号的内部硬件。这些威廉希尔官方网站 的输出连接到微控制器IC上的特定引脚。这也意味着只有这些引脚可以提供PWM信号。但是,在像Mojo这样的FPGA上,我们可以根据需要配置内部硬件,也就是说我们可以创建任意数量的硬件PWM威廉希尔官方网站 !
Mojo FPGA上的硬件PWM
在本教程中,我们将探索如何在Verilog中实现硬件PWM,并了解Verilog代码的模块化如何使我们能够根据需要在Mojo中配置尽可能多的硬件PWM威廉希尔官方网站 。
以下是您需要遵照实施的所有内容:
• Mojo V3开发板
我们将在嵌入式Micro提供的Mojo Base Project的基础上,为该项目构建我们自己的Verilog代码。以这些项目为基础来构建是很有帮助的,因为设备规格和ISE中的其他初始化工作都已经为我们设置好了!如果需要,您可以通过对项目目录中的.xise项目文件进行重命名来更改项目名称。我将其命名为“MojoPWM.xise”。
通常,我们首先会在UCF中表明名称和引脚编号,用于与Mojo上的I/O引脚建立的不同连接。但是,对于本项目,我们将使用板载LED,信号名称和引脚连接都已经指定好了。因此,此处不需要额外的声明。我们将从创建一个新的Verilog模块开始,该模块将指定我们硬件PWM的行为。并非将代码直接放入mojo_top模块中,我们将创建一个独立的模块以利用模块化设计。如果我们要创建不同的硬件PWM威廉希尔官方网站 来运行不同的LED,则只需要创建该种独立PWM模块的新实例即可,无需复制和粘贴大量代码。
右键单击左侧“Hierarchy”窗口中的任意位置,然后点击名为“New Source…”的选项,在“Source Type”的选项列表中,选择“Verilog Module”,并将文件命名为“PWM.v”,以此将创建出一个新的Verilog文件,该文件具有用于PWM模块的框架。
在真正开始编写代码之前,我们先来讨论一下我们的PWM实现方法。如前所述,我们用该硬件生成的信号本身是周期性的,也就是说信号值与时间有关。因此,我们必须根据不间断的滴答时钟信号来指定其行为。这个时钟信号已经作为系统输入包含在mojo_top模块中了,为方波信号,图形如下所示:
我们的硬件操作可总结如下:
• 每个时钟周期(从信号的上升沿开始),我们将增加内部计数器的值,该值的范围为0-255。
• 占空比将作为输入包含在模块中,范围为0-255(就像Arduino的硬件PWM中的那样)。如果我们的计数器值小于占空比,那么输出信号将为高电平,否则,输出信号将为低电平。
• 在复位线上收到高电平值时,硬件将复位计数器。
我们选择值255作为最大计数器值,因为这是8位数值中可以存储的最大值(11111111)。如果从该值增加1,则计数器将回复到00000000,因为会溢出。要了解有关二进制计算和整数的二进制表示的更多信息,请点击下面附录中的链接进行查看!
这是一个时序图,表示我们随内部时钟信号的硬件操作:
图2:占空比为3/255的8位硬件PWM模块的时序图。二进制中的最大占空比值与最大计数器值相同,均为11111111
我们将以输入和输出信号列表作为开始,进行对PWM模块的Verilog描述:
input clk,
input rst,
input[7:0] duty,
output sig_drv
您可能已经从它们的名称中看出来了,这四个信号分别为时钟、复位、占空比值和PWM输出信号。
接下来,我们需要限定输出信号sig_drv的数据类型。Verilog有两种数据类型,线网(wire)和寄存器(reg)。虽然这两种类型之间的差异对我们的应用程序来说是很微小的,但是有一个主要区别需要注意,就是当我们就像在本项目中这样使用always块时,只能写regs而不能写wires。我们随后会讨论always块及其相关操作。如果信号列表中的信号没有被限定为wire或reg,Verilog将默认其声明为wire类型。在这种情况下,我们需要通过模块信号列表之后的以下行将sig_drv描述为reg:
reg sig_drv;
我们还将使用如上所述的8位计数器,并且通过always块对其进行设置。因此,我们需要声明一个8位大小的计数器,如下所示:
reg[7:0] counter;
您可能已经注意到了,像许多其他编程语言一样,Verilog是0索引的,这意味着计数总是从0开始的。因此,一个8位计数器中的位索引值为数字0到7。
接下来,我们对8位计数器的向上计数和输出信号的驱动的逻辑进行描述。我们可以使用always来完成!always块是一种Verilog结构,用户可以指定仅在always块的触发条件被满足时才会进行的操作。一个 always块的基本结构如下:
always @ (…)
begin
…
end
在 “@” 符号后的括号内,用户需要指定触发条件,该条件将决定何时执行块内的逻辑。在我们的项目中,需要两个always块:
always @(*)
begin
end
always @(posedge clk or posedge rst)
begin
end
在第一个块中,触发条件为“*” ,这意味着只要项目中的任何信号发生变化,该块内的逻辑就会被执行。硬件工程师可能将此块称为组合逻辑,该逻辑始终将输出值定义为输入值的某些函数。在此块中,我们将放入在所有时刻都起作用的逻辑,而不是每个时钟周期只执行一次的逻辑,如输出信号的驱动。
如前所述,输出信号为高电平驱动还是低电平驱动取决于计数器相比于占空比的大小。可以通过以下代码行中的always @ (*) 块实现此功能:
if (duty > counter)
begin
sig_drv = 1’b1;
end
else
begin
sig_drv = 1’b0;
end
sig_drv信号的宽度为1位(只有0和1两种值…一个位),因此我们在给它分配的值前加上字符“1’b”。从上面的代码中我们可以看到,当占空比大于计数器值时,sig_drv线被驱动为1(高),否则被驱动为0(低)。
在第二个块中,触发条件为posedge clk 或 posedge rst。这意味着当时钟信号从低电平变为高电平或复位线从低电平变为高电平时,该块内的逻辑被执行,且在每个时钟周期内仅执行一次。我们将使用该always块来指定每执行一个时钟周期时的计数器增加。可以使用此块中的以下代码行完成此操作:
if (rst)
begin
counter <= 8’b0;
end
else
begin
counter <= counter + 1;
end
if语句的第一段指定了当复位线变为高电平时,必须将8位计数器复位为全零。第二段的else条件下指定了如果复位线不是高电平,则计数器的值会被增加。
我们可以看到分配给计数器的值取决于其先前的值。硬件工程师将这种类型的逻辑称为顺序模型,因为输出既是输入的函数,也是过去状态值的函数。
关于该代码最后需要说明的是 “<=” 运算符,即非阻塞赋值运算符,用于将值赋给计数器变量。当我们像往常一样使用 “=” 运算符(阻塞赋值运算符)来给信号赋值时,其实我们已经默许Verilog对编写的代码自上而下来执行。换句话说,如果我们连续编写了两个“=”赋值语句,那么第一个赋值操作将会在第二个赋值操作开始之前完成执行。这其实在某些逻辑上是必要的,因为我们可能会使用以此方式分配的值进行后续计算。
但是,在基于高速时钟信号的顺序逻辑中,我们实际上希望所有的值的分配都在某种程度上并行发生(如果它们彼此独立的话),这样我们就不会将程序延迟到与下一个时钟边沿发生冲突的程度。在本程序中,我们没有在每个时钟边沿上指定执行多种任务。但是,如果我们需要这样做的话,使用非阻塞赋值运算符可以完成这项操作。
完整的PWM模块应如下所示:
现在我们已经创建了PWM模块,可以在mojo_top模块中将其实例化了!在Verilog中,将一个模块在另一个模块中实例化使您可以在更高级别的模块中一次或多次调用子模块功能,而不必复制其代码。就我们的项目来说,我们可以根据需要创建足够多的PWM信号来驱动不同的LED,甚至连接到Mojode 输出引脚上!要配置PWM信号来点亮Mojo上的第8个LED,我们可以添加以下行:
PWM my_pwm(.clk(clk), .rst(rst), .duty(8’b01000000), .sig_drv(led[7]));
该行的第一个单词PWM是我们要实例化的模块的名称。这将在我们选择实例化多个副本时帮助识别同一PWM模块的不同实例。
在模块名称后的括号内,我们使用了.(signal_name) 格式将更高级别模块 (signal_name) 中的信号分配给子模块中的相应信号 (module_signal_name)。
如果要更改PWM信号的占空比,我们要做的就是更改传递到占空比参数中的值。如果被驱动的输出信号,则只需要将作为参数传递的信号更改为.sig_drv。
您所完成的mojo_top模块应如下所示:
要将代码上传到Mojo板上,请按照之前的步骤进行操作:在ISE中生成编程文件,加载Mojo Loader应用程序,然后将.bin文件上传到Mojo。
恭喜您!您已经在Mojo上实现硬件PWM了!如果想进行进一步的实验,请尝试创建多个硬件PWM信号并为其提供不同的占空比参数!您可以通过修改代码,来实现通过一些拨动开关将占空比值输入到Mojo中吗?
我们希望您对自己的第一个FPGA项目感到满意!请继续关注来获取更多有关FPGA和微控制器的教程!
审核编辑黄宇
-
微控制器
+关注
关注
48文章
7552浏览量
151423 -
PWM
+关注
关注
114文章
5186浏览量
213939 -
Arduino
+关注
关注
188文章
6469浏览量
187077
发布评论请先 登录
相关推荐
评论