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

Creación de un efecto LED respiratorio usando una onda senoidal desde RAM de bloques

He notado que muchos de los gadgets que he comprado en los últimos años han pasado del parpadeo LED a la "respiración" LED. La mayoría de dispositivos electrónicos contienen un LED de estado cuyo comportamiento indica lo que ocurre dentro del dispositivo.

Mi cepillo eléctrico parpadea un LED cuando hay que recargarlo, y mi móvil usa el LED para llamar mi atención por diversas razones. Pero los LEDs no parpadean como en los viejos tiempos. Es más bien un efecto de pulsación analógico con intensidad variable continua.

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

Creación de un efecto LED respiratorio usando una onda senoidal desde RAM de bloques

Este artículo es continuación de la entrada del blog de la semana pasada sobre modulación por ancho de pulso (PWM). Hoy vamos a usar el módulo PWM, el contador sierra y el módulo de reset que creamos en el tutorial anterior. Haga clic en el enlace siguiente para leer el artículo sobre PWM.

Cómo crear un controlador PWM en VHDL

Almacenar valores de onda senoidal en RAM de bloques

Aunque es posible generar una onda senoidal usando primitivas de procesamiento digital de señales (DSP) en el FPGA, una forma más sencilla es almacenar los puntos de muestreo en RAM de bloques. En lugar de calcular los valores en tiempo de ejecución, calculamos una serie de valores senoidales durante la síntesis y creamos una memoria de solo lectura (ROM) para almacenarlos.

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

Creación de un efecto LED respiratorio usando una onda senoidal desde RAM de bloques

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

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

Para aprovechar al máximo los bits de datos disponibles, añadimos un desplazamiento y una escala a los valores senoidales al calcular el contenido de la ROM. El valor senoidal más bajo posible de -1 mapea al valor de dato 0, mientras que el valor senoidal más alto posible de 1 se traduce en 2^{\mathit{data\_bits}-1}, como muestra la fórmula siguiente.

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

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

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

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

Módulo ROM senoidal

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

Deje su correo en el formulario a continuación para descargar los archivos VHDL y proyectos ModelSim / iCEcube2.

La entidad

El código siguiente muestra la entidad del módulo ROM senoidal. Contiene dos entradas genéricas que le permiten especificar la anchura y profundidad de la RAM de bloques inferida. Uso especificadores de rango en las constantes para prevenir desbordamiento entero no intencional. 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 de puerto tiene una entrada de reloj, pero no reset porque las primitivas RAM no pueden tener reset. La entrada addr es donde asignamos el valor del ángulo escalado (x), y la salida data es donde aparecerá el valor senoidal escalado.

Declaraciones de tipos

Al principio de la región declarativa del archivo VHDL, declaramos un tipo y un subtipo para nuestro objeto de almacenamiento ROM. El subtipo addr_range es un rango entero igual al número de slots en nuestra RAM, y el rom_type describe un array 2D que almacenará todos los valores senoidales.

 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 senoidales, que usaremos para convertir la RAM en ROM. Tenemos que declararla encima de la declaración de señal para poder usar la función para asignar un valor inicial a la señal de almacenamiento ROM.

Observe que usamos el genérico addr_bits 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 mayores, el cálculo 2**addr_bits - 1 desbordará. Los enteros VHDL son de 32 bits, aunque eso está a punto de cambiar con VHDL-2019, que usa enteros de 64 bits. Pero por ahora, tenemos que vivir con esta limitación al usar enteros en VHDL hasta que las herramientas empiecen a soportar VHDL-2019.

Función para generar valores senoidales

El código siguiente muestra la función init_rom que genera los valores senoidales. Devuelve un objeto rom_type, por eso tenemos que declarar el tipo primero, luego la función, y finalmente la constante ROM. Dependían entre sí 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 del array que rellenamos con valores senoidales. Dentro del subprograma, usamos un bucle for para iterar sobre el rango de direcciones, y para cada slot de ROM, calculamos el valor senoidal usando las fórmulas que describí antes. Y al final, devolvemos la variable rom_v que ahora contiene todas las muestras senoidales.

La conversión a entero en la última línea del bucle for es la razón por la que tuve que restringir el genérico data_bits a 31 bits. Desbordará para cualquier longitud de bits mayor.

 constant rom : rom_type := init_rom; 

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

El proceso ROM

La única lógica en el archivo ROM senoidal es el proceso bastante simple que se lista a continuación. Describe una RAM de bloques con un solo 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 entrada anterior. Tiene un módulo de reset, un módulo generador PWM, y un módulo contador libre de ciclos de reloj (contador sierra). Consulte el artículo de la semana pasada para ver cómo funcionan estos módulos.

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

Creación de un efecto LED respiratorio usando una onda senoidal desde RAM de bloques

El código siguiente muestra la región declarativa del archivo VHDL superior. En el diseño PWM de la semana pasada, el objeto duty_cycle era un alias para los MSBs del contador cnt. Pero eso no funcionaría ahora porque la salida de la ROM senoidal controlará el ciclo de trabajo, así que lo he reemplazado por una señal real. Además, he creado un nuevo alias llamado addr que son los MSBs 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); -- Usar MSBs del contador para entrada de dirección de ROM senoidal alias addr is cnt(cnt'high downto cnt'length - pwm_bits); 

Puede ver cómo instanciar nuestra nueva ROM senoidal en el módulo superior en el listado siguiente. Establecemos la anchura y profundidad de la RAM para seguir la longitud del contador interno del módulo PWM. La salida data de la ROM controla la entrada duty_cycle del módulo PWM. El valor en la señal duty_cycle representará un patrón de onda senoidal cuando leamos los slots de RAM uno tras otro.

 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 senoidal

La imagen siguiente muestra la forma de onda de la simulación del módulo superior en ModelSim. He cambiado la presentación de la señal unsigned duty_cycle a formato analógico para poder observar la onda senoidal.

Creación de un efecto LED respiratorio usando una onda senoidal desde RAM de bloques

Es la salida de nivel superior led_5 la que lleva la señal PWM, que controla el LED externo. Podemos ver que la salida cambia rápidamente cuando el ciclo de trabajo sube o baja. Pero cuando el ciclo de trabajo está en la cima de la onda senoidal, led_5 es un '1' estable. Cuando la onda está en la parte inferior de la pendiente, la salida se queda brevemente en '0'.

¿Quiere probarlo en su ordenador? Introduzca su dirección de correo en el formulario a continuación para recibir los archivos VHDL y los proyectos ModelSim e iCEcube2.

Implementación de LED respiratorio en el FPGA

Usé el software Lattice iCEcube2 para implementar el diseño en la placa FPGA iCEstick. Use el formulario anterior para descargar el proyecto y probarlo en su placa si tiene un iCEstick.

El listado siguiente muestra el uso de recursos, según informa el software Synplify Pro que viene con iCEcube2. Muestra que el diseño usa una primitiva RAM de bloques. Esa es nuestra ROM senoidal.

 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 archivo .bin en la carpeta led_breathing_Implmnt\sbt\outputs\bitmap dentro del directorio del proyecto Lattice_iCEcube2_proj.

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

Creación de un efecto LED respiratorio usando una onda senoidal desde RAM de bloques

Reflexiones finales

Usar RAM de bloques para almacenar valores senoidales precalculados es bastante sencillo. Pero hay algunas limitaciones que hacen que este método sea adecuado solo para ondas senoidales con resoluciones X e Y limitadas.

La primera razón que viene a la mente es el límite de 32 bits para valores enteros que discutí antes. Estoy seguro de que puede superar este problema calculando el valor senoidal de forma más inteligente, pero no es todo.

Por cada bit que extienda la dirección de la ROM, duplica el uso de RAM. Si necesita alta precisión en el eje X, puede que no haya suficiente RAM, incluso en un FPGA más grande.

Si el número de bits usados para los valores senoidales del eje Y supera la profundidad nativa de la RAM, la herramienta de síntesis usará RAMs adicionales o LUTs para implementar la ROM. Consumirá más de su presupuesto de recursos al aumentar la precisión Y.

En teoría, solo necesitamos almacenar un cuadrante de la onda senoidal. Por tanto, podría salirse con la suya con un cuarto del uso de RAM si usara una máquina de estados finitos (FSM) para controlar la lectura de la ROM. Tendría que invertir el cuadrante senoidal en las cuatro permutaciones de los ejes X e Y. Entonces, podría construir una onda senoidal completa a partir del único cuadrante almacenado en RAM de bloques.

Desafortunadamente, es difícil que los cuatro segmentos se unan suavemente. Dos muestras iguales en las uniones en la cima y el fondo de la onda senoidal distorsionan los datos creando zonas planas en la onda. Introducir ruido anula el propósito de almacenar solo el cuadrante para mejorar la precisión de la onda senoidal.


VHDL

  1. Creación de experiencias de usuario sin fricción: una guía de diseño UX
  2. Invocar bloques de función PLC vía clientes OPC UA: Guía práctica
  3. La Fabricación Digital Revoluciona el Modelado Anatómico en la Medicina Moderna
  4. Construya su primera plantilla AWS CloudFormation – Guía práctica
  5. Creación de listas de cadenas en VHDL: Buenas prácticas y ejemplo
  6. Creación de un testbench dirigido por Tcl para un módulo VHDL de cerradura por código
  7. Implementación de un controlador PWM en VHDL: Diseño, simulación y demo en FPGA
  8. Inicialización de RAM de bloques FPGA desde archivos de texto usando la biblioteca TEXTIO de VHDL
  9. Cómo construir un testbench de auto-comprobación en VHDL: Guía práctica
  10. Implementación de lista enlazada dinámica en VHDL con tipos protegidos y punteros de acceso
  11. Crear un array de objetos en Java: Guía paso a paso