亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Industrial programming >> VHDL

如何使用存储在 Block RAM 中的正弦波创建呼吸 LED 效果

我注意到过去几年我购买的许多小工具已经从 LED 闪烁转向 LED 呼吸。大多数电子小玩意都包含一个状态 LED,其行为会指示设备内部正在发生的事情。

我的电动牙刷在充电时会闪烁 LED,而我的手机出于各种原因使用 LED 来引起我的注意。但 LED 不再像过去那样闪烁。它更像是一种强度不断变化的模拟脉冲效果。

下面的 Gif 动画显示了我的罗技鼠标使用此效果来指示它正在为电池充电。我将这种效果称为呼吸 LED 因为光强度模式在速度和加速度上与人类呼吸周期相似。它看起来很自然,因为照明周期遵循正弦波模式。

本文是上周关于脉宽调制 (PWM) 的博客文章的延续。今天,我们将使用我们在上一个教程中创建的 PWM 模块、锯齿波计数器和复位模块。点击下方链接阅读PWM相关文章!

如何在 VHDL 中创建 PWM 控制器

在块 RAM 中存储正弦波值

虽然可以使用 FPGA 中的数字信号处理 (DSP) 原语生成正弦波,但更直接的方法是将采样点存储在块 RAM 中。我们不是在运行时计算值,而是在综合期间计算一系列正弦值,并创建一个只读存储器 (ROM) 来存储它们。

考虑下面的最小示例,它显示了如何将完整的正弦波存储在 3×3 位 ROM 中。地址输入和数据输出都是 3 位宽,也就是说它们可以表示 0 到 7 范围内的整数值。我们可以在 ROM 中存储 8 个正弦值,数据的分辨率也是 0 到 7 .

三角正弦函数为任何角度输入产生一个范围为 [-1, 1] 的数字,x .此外,当 x ≥ 2π 时,循环重复。因此,只存储从零到最大的正弦值就足够了,但不包括 2π。 2π 的正弦值与零的正弦值相同。上图显示了这个概念。我们将正弦值从零存储到 \frac{7\pi}{4},这是整个 2π 圆之前的最后一个均匀空间步骤。

数字逻辑不能以无限的精度表示真实值,例如角度或正弦值。在任何计算机系统中都是如此。即使使用双精度浮点数,它也只是一个近似值。这就是二进制数的工作原理,我们的正弦 ROM 也不例外。

为了充分利用可用数据位,我们在计算 ROM 内容时为正弦值添加了偏移量和比例。可能的最低正弦值 -1 映射到数据值 0,而可能的最高正弦值 1 转换为 2^{\mathit{data\_bits}-1},如下面的公式所示。

\mathit{data} =\mathit{Round}\left(\frac{(1 + \sin \mathit{addr}) * (2^\mathit{data\_bits} - 1)}{2}\right)

要将ROM地址转换为角度x,我们可以使用以下公式:

x =\frac{\mathit{addr} * 2\pi}{2^\mathit{addr\_bits}}

当然,这种方法并没有给你一个通用的角度到正弦值转换器。如果这是您想要的,您将不得不使用额外的逻辑或 DSP 原语来缩放地址输入和数据输出。但是对于许多应用程序,表示为无符号整数的正弦波就足够了。正如我们将在下一节中看到的,这正是我们的示例 LED 脉冲项目所需要的。

正弦ROM模块

本文介绍的正弦 ROM 模块将推断大多数 FPGA 架构上的块 RAM。考虑将宽度和深度泛型与目标 FPGA 的内存原语相匹配。这将为您提供最佳的资源利用率。如果您不确定如何将正弦 ROM 用于真正的 FPGA 项目,您可以随时参考示例 Lattice 项目。

在下面的表格中留下您的电子邮件以下载 VHDL 文件和 ModelSim / iCEcube2 项目!

实体

下面的代码显示了正弦 ROM 模块的实体。它包含两个通用输入,可让您指定推断的 Block RAM 的宽度和深度。我在常量上使用范围说明符来防止无意的整数溢出。本文稍后会详细介绍。

entity sine_rom is
  generic (
    addr_bits : integer range 1 to 30;
    data_bits : integer range 1 to 31
  );
  port (
    clk : in std_logic;
    addr : in unsigned(addr_bits - 1 downto 0);
    data : out unsigned(data_bits - 1 downto 0)
  );
end sine_rom; 

端口声明有一个时钟输入,但没有复位,因为 RAM 原语不能复位。 地址 输入是我们分配缩放角度(x ) 值和 数据 输出是缩放正弦值将出现的位置。

类型声明

在 VHDL 文件的声明区域的顶部,我们为我们的 ROM 存储对象声明了一个类型和一个子类型。 addr_range subtype 是一个整数范围,等于我们 RAM 中的插槽数,而 rom_type 描述了一个二维数组,它将存储所有的正弦值。

subtype addr_range is integer range 0 to 2**addr_bits - 1;
type rom_type is array (addr_range) of unsigned(data_bits - 1 downto 0);

但是,我们现在还不打算声明存储信号。首先,我们需要定义产生正弦值的函数,我们可以用它把 RAM 变成 ROM。我们必须在信号声明之上声明它,以便我们可以使用该函数为ROM存储信号分配一个初始值。

请注意,我们使用的是 addr_bits 泛型作为定义 addr_range 的基础 .这就是我必须为 addr_bits 指定最大值 30 的原因 .因为对于较大的值,2**addr_bits - 1 计算会溢出。 VHDL 整数是 32 位长,不过随着使用 64 位整数的 VHDL-2019 即将改变。但就目前而言,在 VHDL 中使用整数时,我们必须忍受这个限制,直到工具开始支持 VHDL-2019。

生成正弦值的函数

下面的代码显示了 init_rom 生成正弦值的函数。它返回一个 rom_type 对象,这就是为什么我们必须先声明类型,然后是函数,最后是 ROM 常量。它们以确切的顺序相互依赖。

function init_rom return rom_type is
  variable rom_v : rom_type;
  variable angle : real;
  variable sin_scaled : real;
begin

  for i in addr_range loop

    angle := real(i) * ((2.0 * MATH_PI) / 2.0**addr_bits);
    sin_scaled := (1.0 + sin(angle)) * (2.0**data_bits - 1.0) / 2.0;
    rom_v(i) := to_unsigned(integer(round(sin_scaled)), data_bits);
    
  end loop;
  
  return rom_v;
end init_rom;

该函数使用了一些便利变量,包括 rom_v ,我们用正弦值填充的数组的本地副本。在子程序内部,我们使用 for 循环遍历地址范围,并且对于每个 ROM 插槽,我们使用我之前描述的公式计算正弦值。最后,我们返回 rom_v 现在包含所有正弦样本的变量。

for 循环中最后一行的整数转换是我必须限制 data_bits 的原因 通用到 31 位。任何更大的位长度都会溢出。

constant rom : rom_type := init_rom;

init_rom 下面 函数定义,我们继续声明 rom 对象作为常量。 ROM 只是一个你永远不会写入的 RAM,所以这很好。现在是时候使用我们的函数了。我们称 init_rom 生成初始值,如上代码所示。

ROM 进程

正弦 ROM 文件中的唯一逻辑是下面列出的相当简单的过程。它描述了具有单个读取端口的块 RAM。

ROM_PROC : process(clk)
begin
  if rising_edge(clk) then
    data <= rom(to_integer(addr));
  end if;
end process;

顶部模块

此设计是我在之前的博客文章中介绍的 PWM 项目的延续。它有一个复位模块、一个 PWM 发生器模块和一个自由运行的时钟周期计数器(锯齿计数器)模块。请参阅上周的文章,了解这些模块的工作原理。

下图显示了顶层模块中子模块之间的连接。

下面的代码显示了顶层 VHDL 文件的声明区域。在上周的 PWM 设计中,duty_cycle object 是 cnt 的 MSB 的别名 柜台。但这现在行不通,因为正弦 ROM 的输出将控制占空比,所以我用实际信号替换了它。此外,我创建了一个名为 addr 的新别名 那是计数器的 MSB。我们将它作为ROM的地址输入。

signal rst : std_logic;
signal cnt : unsigned(cnt_bits - 1 downto 0);
signal pwm_out : std_logic;
signal duty_cycle : unsigned(pwm_bits - 1 downto 0);

-- Use MSBs of counter for sine ROM address input
alias addr is cnt(cnt'high downto cnt'length - pwm_bits);

您可以在下面列表的顶部模块中看到如何实例化我们的新正弦 ROM。我们将 RAM 的宽度和深度设置为跟随 PWM 模块内部计数器的长度。 数据 ROM 的输出控制 duty_cycle 输入到 PWM 模块。 duty_cycle 上的值 当我们一个接一个地读取 RAM 插槽时,信号将描绘一个正弦波模式。

SINE_ROM : entity work.sine_rom(rtl)
  generic map (
    data_bits => pwm_bits,
    addr_bits => pwm_bits
  )
  port map (
    clk => clk,
    addr => addr,
    data => duty_cycle
  );

模拟正弦波ROM

下图显示了 ModelSim 中顶层模块仿真的波形。我更改了未签名 duty_cycle 的显示方式 信号转换成模拟格式,这样我们就可以观察正弦波了。

它是 led_5 携带 PWM 信号的顶层输出,控制外部 LED。我们可以看到,当占空比上升或下降时,输出变化很快。但是当占空比处于正弦波的顶部时,led_5 是一个稳定的“1”。当波在斜率底部时,输出会短暂停留在“0”。

想在您的家用电脑上试用吗?在下面的表格中输入您的电子邮件地址以接收 VHDL 文件以及 ModelSim 和 iCEcube2 项目!

在 FPGA 上实现 LED 呼吸

我使用 Lattice iCEcube2 软件在 iCEstick FPGA 板上实现了设计。如果您拥有 iCEstick,请使用上面的表格下载项目并在您的板上试用!

下面的清单显示了资源使用情况,由 iCEcube2 附带的 Synplify Pro 软件报告。它表明该设计使用一个块 RAM 原语。这就是我们的正弦 ROM。

Resource Usage Report for led_breathing 

Mapping to part: ice40hx1ktq144
Cell usage:
GND             4 uses
SB_CARRY        31 uses
SB_DFF          5 uses
SB_DFFSR        39 uses
SB_GB           1 use
SB_RAM256x16    1 use
VCC             4 uses
SB_LUT4         65 uses

I/O ports: 7
I/O primitives: 7
SB_GB_IO       1 use
SB_IO          6 uses

I/O Register bits:                  0
Register bits not including I/Os:   44 (3%)

RAM/ROM usage summary
Block Rams : 1 of 16 (6%)

Total load per clock:
   led_breathing|clk: 1

@S |Mapping Summary:
Total  LUTs: 65 (5%)

在 iCEcube2 中路由设计后,你会发现 .bin led_breathing_Implmnt\sbt\outputs\bitmap 中的文件 Lattice_iCEcube2_proj 内的文件夹 项目目录。

您可以使用 Lattice Diamond Programmer Standalone 软件对 FPGA 进行编程,如 iCEstick 用户手册中所示。这就是我所做的,下面的 Gif 动画显示了结果。 LED 的光强度以正弦波模式振荡。它看起来很自然,如果你想象一下,LED 似乎会“呼吸”。

最后的想法

使用块 RAM 存储预先计算的正弦值非常简单。但是有一些限制使得这种方法只适用于 X 和 Y 分辨率有限的正弦波。

想到的第一个原因是我之前讨论过的整数值的 32 位限制。我相信你可以通过更巧妙地计算正弦值来解决这个问题,但这还不是全部。

对于您扩展 ROM 地址的每一位,您的 RAM 使用量都会增加一倍。如果需要 X 轴上的高精度,可能没有足够的 RAM,即使在更大的 FPGA 上也是如此。

如果用于 Y 轴正弦值的位数超过原生 RAM 深度,则综合工具将使用额外的 RAM 或 LUT 来实现 ROM。随着 Y 精度的提高,它会消耗更多的资源预算。

理论上,我们只需要存储一个象限的正弦波。因此,如果您使用有限状态机 (FSM) 来控制 ROM 读数,则可以避免四分之一的 RAM 使用量。对于 X 轴和 Y 轴的所有四个排列,它必须反转正弦象限。然后,您可以从存储在 Block RAM 中的单象限构建完整的正弦波。

不幸的是,很难让所有四个细分市场都顺利加入。正弦波顶部和底部接头处的两个相等样本通过在正弦波上创建平坦点来扭曲数据。引入噪声破坏了仅存储象限以提高正弦波精度的目的。


VHDL

  1. 如何使用 AWS 创建 CloudFormation 模板
  2. 如何创建无摩擦的用户体验
  3. 如何在 VHDL 中创建字符串列表
  4. 如何为 VHDL 代码锁定模块创建 Tcl 驱动的测试平台
  5. 如何在 VHDL 中创建 PWM 控制器
  6. 如何使用 TEXTIO 从文件初始化 RAM
  7. 如何使用就绪/有效握手在块 RAM 中创建 AXI FIFO
  8. 如何创建自检测试平台
  9. 如何在 VHDL 中创建链接列表
  10. 如何在 Java 中创建对象数组
  11. 医疗专业人员如何使用数字制造来创建下一代解剖模型
  12. 如何使用信息模型从 OPC UA 客户端调用功能块