Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Industrial programming >> VHDL

Cómo crear un efecto LED de respiración utilizando una onda sinusoidal almacenada en RAM de bloque

Me di cuenta de que muchos de los dispositivos que compré en los últimos años han pasado del parpadeo del LED a la respiración del LED. La mayoría de los aparatos electrónicos contienen un LED de estado cuyo comportamiento brinda indicaciones de lo que sucede dentro del dispositivo.

Mi cepillo de dientes eléctrico enciende un LED cuando es el momento de recargarlo, y mi teléfono móvil usa el LED para llamar mi atención por una amplia variedad de razones. Pero los LED no parpadean como en los viejos tiempos. Es más como un efecto pulsante analógico con intensidad variable continua.

La animación Gif a continuación muestra mi mouse Logitech usando este efecto para indicar que está cargando la batería. Llamo a este efecto LED de respiración porque el patrón de intensidad de la luz es similar en velocidad y aceleración al ciclo respiratorio humano. Se ve tan natural porque el ciclo de iluminación sigue un patrón de onda sinusoidal.

Este artículo es una continuación de la publicación de blog de la semana pasada sobre modulación de ancho de pulso (PWM). Hoy vamos a usar el módulo PWM, el contador de dientes de sierra y el módulo de reinicio que creamos en el tutorial anterior. ¡Haga clic en el enlace a continuación para leer el artículo sobre PWM!

Cómo crear un controlador PWM en VHDL

Almacenamiento de valores de onda sinusoidal en RAM de bloque

Si bien es posible generar una onda sinusoidal utilizando primitivas de procesamiento de señal digital (DSP) en la FPGA, una forma más sencilla es almacenar los puntos de muestra en RAM de bloque. En lugar de calcular los valores durante el tiempo de ejecución, calculamos una serie de valores sinusoidales durante la síntesis y creamos una memoria de solo lectura (ROM) para almacenarlos.

Considere el ejemplo mínimo a continuación, que muestra cómo almacenar una onda sinusoidal completa en una ROM de 3 × 3 bits. Tanto la entrada de dirección como la salida de datos tienen tres bits de ancho, lo que significa que pueden representar un valor entero en el rango de 0 a 7. Podemos almacenar ocho valores de seno en la ROM, y la resolución de los datos también es de 0 a 7 .

La función de seno trigonométrica produce un número en el rango [-1, 1] para cualquier entrada de ángulo, x . Además, el ciclo se repite cuando x ≥ 2π. Por lo tanto, es suficiente almacenar solo los valores de seno desde cero y hasta, pero sin incluir 2π. El valor del seno para 2π es el mismo que el seno para cero. La ilustración anterior muestra este concepto. Estamos almacenando valores de seno desde cero hasta \frac{7\pi}{4}, que es el último paso de espacios uniformes antes del círculo completo de 2π.

La lógica digital no puede representar valores reales, como el ángulo o el seno, con una precisión infinita. Ese es el caso en cualquier sistema informático. Incluso cuando se usan números de punto flotante de precisión doble, es solo una aproximación. Así es como funcionan los números binarios, y nuestra ROM sinusoidal no es diferente.

Para aprovechar al máximo los bits de datos disponibles, agregamos un desplazamiento y una escala a los valores del seno cuando calculamos el contenido de la ROM. El valor de seno más bajo posible de -1 se asigna al valor de datos 0, mientras que el valor de seno más alto posible de 1 se traduce en 2^{\mathit{datos\_bits}-1}, como se muestra en la siguiente fórmula.

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

Para traducir una dirección ROM en un ángulo, x, podemos usar la siguiente fórmula:

x =\frac{\mathit{dirección} * 2\pi}{2^\mathit{dirección\_bits}}

Por supuesto, este método no le brinda un convertidor universal de valor de ángulo a seno. Si eso es lo que desea, tendrá que usar lógica adicional o primitivos DSP para escalar la entrada de dirección y la salida de datos. Pero para muchas aplicaciones, una onda sinusoidal representada como un número entero sin signo es suficiente. Y como veremos en la siguiente sección, es exactamente lo que necesitamos para nuestro proyecto de ejemplo de LED pulsante.

Módulo ROM sinusoidal

El módulo de ROM sinusoidal presentado en este artículo inferirá RAM de bloque en la mayoría de las arquitecturas de FPGA. Considere hacer coincidir los genéricos de ancho y profundidad con las primitivas de memoria de su FPGA de destino. Eso le dará la mejor utilización de recursos. Siempre puede consultar el proyecto Lattice de ejemplo si no está seguro de cómo usar la ROM sinusoidal para un proyecto FPGA real.

¡Deje su correo electrónico en el siguiente formulario para descargar los archivos VHDL y los proyectos ModelSim / iCEcube2!

La entidad

El siguiente código muestra la entidad del módulo ROM sinusoidal. Contiene dos entradas genéricas que le permiten especificar el ancho y la profundidad de la RAM del bloque inferido. Estoy usando especificadores de rango en las constantes para evitar el desbordamiento involuntario de enteros. Más sobre eso más adelante en este artículo.

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; 

La declaración del puerto tiene una entrada de reloj, pero no se reinicia porque las primitivas de RAM no se pueden reiniciar. La dirección entrada es donde asignamos el ángulo escalado (x ) y los datos la salida es donde aparecerá el valor del seno escalado.

Declaraciones de tipos

En la parte superior de la región declarativa del archivo VHDL, declaramos un tipo y un subtipo para nuestro objeto de almacenamiento ROM. El rango_dirección subtipo es un rango de enteros igual al número de ranuras en nuestra RAM, y el rom_type describe una matriz 2D que almacenará todos los valores de seno.

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);

Sin embargo, no vamos a declarar la señal de almacenamiento todavía. Primero, necesitamos definir la función que producirá los valores sinusoidales, que podemos usar para convertir la RAM en una ROM. Tenemos que declararlo arriba de la declaración de la señal para que podamos usar la función para asignar un valor inicial a la señal de almacenamiento de la ROM.

Tenga en cuenta que estamos usando los addr_bits genérico como base para definir addr_range . Esa es la razón por la que tuve que especificar un valor máximo de 30 para addr_bits . Porque para valores más grandes, el 2**addr_bits - 1 el cálculo se desbordará. Los enteros VHDL tienen una longitud de 32 bits, aunque eso está a punto de cambiar con VHDL-2019, que usaba enteros de 64 bits. Pero por ahora, tenemos que vivir con esta limitación al usar números enteros en VHDL hasta que las herramientas comiencen a admitir VHDL-2019.

Función para generar valores de seno

El siguiente código muestra el init_rom función que genera los valores del seno. Devuelve un rom_type objeto, por lo que tenemos que declarar primero el tipo, luego la función y, finalmente, la constante ROM. Dependen unos de otros en ese orden exacto.

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;

La función usa algunas variables de conveniencia, incluyendo rom_v , una copia local de la matriz que llenamos con valores sinusoidales. Dentro del subprograma, usamos un bucle for para iterar sobre el rango de direcciones, y para cada ranura de ROM, calculamos el valor del seno usando las fórmulas que describí anteriormente. Y al final, devolvemos la rom_v variable que ahora contiene todas las muestras de seno.

La conversión de enteros en la última línea del ciclo for es la razón por la que tuve que restringir los bits_datos genérico a 31 bits. Se desbordará para longitudes de bit más grandes.

constant rom : rom_type := init_rom;

Debajo de init_rom definición de la función, pasamos a declarar la rom objeto como una constante. Una ROM es simplemente una RAM en la que nunca escribes, así que está perfectamente bien. Y ahora es el momento de usar nuestra función. Llamamos a init_rom para generar los valores iniciales, como se muestra en el código anterior.

El proceso de la ROM

La única lógica en el archivo ROM sinusoidal es el proceso bastante simple que se detalla a continuación. Describe un bloque de RAM con un único puerto de lectura.

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

Módulo superior

Este diseño es una continuación del proyecto PWM que presenté en mi publicación de blog anterior. Tiene un módulo de reinicio, un módulo generador de PWM y un módulo de contador de ciclos de reloj de funcionamiento libre (contador de dientes de sierra). Consulte el artículo de la semana pasada para ver cómo funcionan estos módulos.

El siguiente diagrama muestra las conexiones entre los submódulos en el módulo superior.

El siguiente código muestra la región declarativa del archivo VHDL superior. En el diseño de PWM de la semana pasada, el duty_cycle objeto era un alias para los MSB del cnt encimera. Pero eso no funcionaría ahora porque la salida de la ROM sinusoidal controlará el ciclo de trabajo, así que la reemplacé con una señal real. Además, he creado un nuevo alias con el nombre addr esos son los MSB del contador. Lo usaremos como entrada de dirección para la 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);

Puede ver cómo crear una instancia de nuestra nueva ROM sinusoidal en el módulo superior en la lista a continuación. Configuramos el ancho y la profundidad de la RAM para seguir la longitud del contador interno del módulo PWM. Los datos la salida de la ROM controla el duty_cycle entrada al módulo PWM. El valor en el duty_cycle la señal representará un patrón de onda sinusoidal cuando leamos las ranuras de RAM una tras otra.

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
  );

Simulación de la ROM de onda sinusoidal

La siguiente imagen muestra la forma de onda de la simulación del módulo superior en ModelSim. Cambié la presentación del duty_cycle sin firmar señal a un formato analógico para que podamos observar la onda sinusoidal.

Es el led_5 salida de nivel superior que transporta la señal PWM, que controla el LED externo. Podemos ver que la salida cambia rápidamente cuando el ciclo de trabajo aumenta o disminuye. Pero cuando el ciclo de trabajo está en la parte superior de la onda sinusoidal, led_5 es un '1' constante. Cuando la ola está en la parte inferior de la pendiente, la salida permanece brevemente en '0'.

¿Quieres probarlo en la computadora de tu casa? ¡Ingrese su dirección de correo electrónico en el siguiente formulario para recibir los archivos VHDL y los proyectos ModelSim e iCEcube2!

Implementación de respiración LED en la FPGA

Usé el software Lattice iCEcube2 para implementar el diseño en la placa iCEstick FPGA. ¡Utilice el formulario de arriba para descargar el proyecto y pruébelo en su placa si tiene un iCEstick!

La siguiente lista muestra el uso de recursos, según lo informado por el software Synplify Pro que viene con iCEcube2. Muestra que el diseño utiliza una primitiva de RAM de bloque. Esa es nuestra ROM sinusoidal.

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%)

Después de enrutar el diseño en iCEcube2, encontrará el .bin archivo en el led_breathing_Implmnt\sbt\outputs\bitmap carpeta dentro de Lattice_iCEcube2_proj directorio del proyecto.

Puede usar el software Lattice Diamond Programmer Standalone para programar el FPGA, como se muestra en el manual de usuario de iCEstick. Eso es lo que hice, y la animación Gif a continuación muestra el resultado. La intensidad de la luz del LED oscila con un patrón de onda sinusoidal. Se ve muy natural y el LED parece "respirar" si le pones un poco de imaginación.

Reflexiones finales

El uso de RAM de bloque para almacenar valores de seno precalculados es bastante sencillo. Pero existen algunas limitaciones que hacen que este método sea adecuado solo para ondas sinusoidales con resoluciones X e Y limitadas.

La primera razón que me viene a la mente es el límite de 32 bits para valores enteros que mencioné anteriormente. Estoy seguro de que puedes superar este problema calculando el valor del seno de forma más inteligente, pero eso no es todo.

Por cada bit con el que extiende la dirección de la ROM, está duplicando el uso de RAM. Si necesita alta precisión en el eje X, es posible que no haya suficiente RAM, incluso en un FPGA más grande.

Si la cantidad de bits utilizados para los valores de seno del eje Y supera la profundidad de RAM nativa, la herramienta de síntesis utilizará RAM o LUT adicionales para implementar la ROM. Se consumirá más de su presupuesto de recursos a medida que aumente la precisión de Y.

En teoría, solo necesitamos almacenar un cuadrante de la onda sinusoidal. Por lo tanto, podría salirse con la suya con una cuarta parte del uso de RAM si usara una máquina de estado finito (FSM) para controlar la lectura de ROM. Tendría que invertir el cuadrante del seno para las cuatro permutaciones de los ejes X e Y. Luego, podría generar una onda sinusoidal completa a partir del único cuadrante almacenado en el bloque de RAM.

Desafortunadamente, es difícil lograr que los cuatro segmentos se unan sin problemas. Dos muestras iguales en las uniones en la parte superior e inferior de la onda sinusoidal distorsionan los datos al crear puntos planos en la onda sinusoidal. La introducción de ruido anula el propósito de almacenar solo el cuadrante para mejorar la precisión de la onda sinusoidal.


VHDL

  1. Cómo crear una plantilla de CloudFormation con AWS
  2. Cómo crear UX sin fricciones
  3. Cómo crear una lista de cadenas en VHDL
  4. Cómo crear un banco de pruebas controlado por Tcl para un módulo de bloqueo de código VHDL
  5. Cómo crear un controlador PWM en VHDL
  6. Cómo inicializar RAM desde un archivo usando TEXTIO
  7. Cómo crear un banco de pruebas de autocomprobación
  8. Cómo crear una lista enlazada en VHDL
  9. Cómo crear una matriz de objetos en Java
  10. Cómo los profesionales médicos utilizan la fabricación digital para crear modelos anatómicos de próxima generación
  11. Cómo llamar a un bloque de funciones desde un cliente OPC UA utilizando un modelo de información