PWM en la cuadrícula ICEStick

La última vez les mostré un bloque PWM simple y un núcleo UART de código abierto. Esta vez juntaré estas partes para crear una salida periférica de una computadora PWM.

Integración

Con código de trabajo (funcionando, al menos, en simulación) puede agregar el bloque PWM al controlador UART para obtener la primera versión del generador PWM de la computadora. Para simplificar las cosas, actualmente solo envía un número hexadecimal de 8 bits para configurar el ciclo de salida PWM. Por ejemplo, si envía una A mayúscula (65 decimal o 41 hexadecimal), la salida será 65/256 o aproximadamente el 25%. Es más fácil, como mencioné la última vez, usar un programa final que pueda enviar códigos hexadecimales como Cutecom.

Intentaré simplificar la integración inicial, ejecutarla y luego agregar funciones. Primero, necesita crear el núcleo PWM:

reg [7:0] pwmduty;
wire pwmout;
assign LED0 = pwmout;
ppwmblock pwm(iCE_CLK, reset, pwmduty, 8'b0, pwmout);

Luego, simplemente conecte el carácter recibido al ciclo de desarrollo del registro PWM:

if (received) begin
    tx_byte <= rx_byte;
    pwmduty <= rxbyte;
end

Debido al archivo PCF, el LED0 La salida está conectada, sorprendentemente, al LED de la tabla 0. Si desea experimentar con el código, intente cambiar para controlar otro LED o tal vez incluso varios LED a la vez. También puede intentar usar el PWM de área igual en lugar del proporcional, especialmente si puede ver la salida por amplitud. Si desea conectar un tamaño, es posible que desee cambiar la salida (o duplicarla) a uno de los pines del borde o al zócalo PMOD.

Hay dos cosas que debe tener en cuenta si está intentando agregar o copiar resultados. Primero, el archivo PCF incluido tiene pines no utilizados comentados con un signo #. La última versión de las herramientas tiene una forma de marcar una tarea de lápiz como opcional, pero sin ella, el proceso de compilación fallará si no usa un pin nombrado en el archivo PCF. Lo segundo es que debe agregar cualquier puerto que esté utilizando al módulo avanzado (vea cómo funcionan los LED). Los nombres no importan si el nombre en el módulo de nivel superior coincide con el nombre en el archivo PCF.

Hice otro cambio en la demostración de UART. Modifiqué el Makefile para comprender que la salida dependía de algo más que el uart_demo.v expediente. Puede encontrar el proyecto completo en GitHub en el directorio v1.

La pista de amplitud de la derecha muestra la salida del FPGA con un ciclo obligatorio de 0xA0. Puede notar que hay un tamaño extraño en el medio de la serie de pulsos. Esto se debe a que 0xA0 (160 en decimal) no divide por igual a 256. Por lo tanto, los valores numéricos (y los estados de salida) se verán como en la figura.

Tenga en cuenta que después de comenzar, la secuencia repite los 8 tics del reloj. Hay un pulso alto corto y dos pulsos altos dobles. Dado que solicité un ciclo de impuestos de 160/256 o 62,5%, el algoritmo genera 5 pulsos altos por cada 8 tics de reloj y 5/8 es, de hecho, 0,625. El pulso corto significa que debe crear el tamaño si desea una pantalla limpia. Soy de la vieja escuela, así que configuré el gatillo, pero podrías usar el gatillo de ancho de pulso para sincronizar el pulso más corto.

Ampliando el diseño

Quiero expandir el diseño para que sea más útil y mostrar algunas características adicionales de Verilog. En particular, quiero hacer tres cosas para expandir el sistema:
1) Agregue más bloques PWM con diferentes resoluciones
2) Entradas de salida de los bloques PWM a diferentes ubicaciones.
3) Implementar un protocolo robusto para controlar los bloques PWM

Agregar nuevos bloques no es nada difícil. El módulo Verilog pwmblock (y sus sobres asociados) son como un artículo. El archivo principal crea un ejemplo del módulo utilizando uno de los sobres de la siguiente manera:

ppwmblock pwm(iCE_CLK, reset, pwmduty, 8'b0, pwmout);

Es bastante fácil agregar más. Necesitarás encontrar espacio para poner el ciclo PWM obligatorio, las calculadoras y los enlaces, por supuesto. Podría simplemente crear nuevos registros y cables, pero sería más inteligente y más extensible usar matrices.

La sintaxis para declarar matrices es simple:

reg [7:0] pwmduty[3:0];

Esto define cuatro elementos (0-3) de una cantidad de 8 bits. La única parte difícil es que usar una matriz parece usar piezas de un registro:

dc <= pwmduty[2];

El compilador es lo suficientemente inteligente como para saber que desde entonces pwmduty es una matriz, los paréntesis indican una firma de matriz, no un segmento de bits. Una vez que tenga una matriz, es fácil organizar cada artículo para que tenga sus propios contadores. Por ejemplo:

ppwmblock pwm3(iCE_CLK, reset, pwmduty[3], 8'b0, pwmout[3]);

Por supuesto, no tiene sentido dirigir a todos a un LED. El archivo PCF define la correspondencia entre los números de pin en el chip y los nombres en el archivo Verilog. Por ejemplo, aquí hay parte del archivo PCF:

# Red LEDs
set_io LED0 99
set_io LED1 98
set_io LED2 97
set_io LED3 96
# Green LED
set_io LED4 95
# IrDA port
#set_io RXD 106
#set_io TXD 105
#set_io SD 107
# Pmod connector
#set_io PIO1_02 78 # Pin 1
#set_io PIO1_03 79 # Pin 2
#set_io PIO1_04 80 # Pin 3
#set_io PIO1_05 81 # Pin 4
#set_io PIO1_06 87 # Pin 7
#set_io PIO1_07 88 # Pin 8
#set_io PIO1_08 90 # Pin 9
#set_io PIO1_09 91 # Pin 10

Puede usar cualquiera de los nombres de señal en el archivo PCF para dirigir las salidas a otros LED o al conector de borde si desea conectarse para controlar la salida con amplitud, por ejemplo. Tenga en cuenta que solo los pines de E / S usados ​​no están comentados en el archivo PCF.

Nuevos parámetros de protocolo y Verilog

El siguiente problema es cómo cargar los nuevos registros de devcycle. Cada vez que tienes una serie de datos en este tipo de dispositivo, tienes que pensar en lo que sucede si escuchas en medio de un flujo de varios bytes. Por ejemplo, un protocolo incorrecto tomaría el primer byte como número de canal (0-3) y el segundo byte como ciclo operativo (0-255). Podría tener el caso cuando el FPGA se reinicia entre los dos bytes de comando y luego no estará sincronizado con la computadora.

Hay varias formas de solucionar este problema. Por ejemplo, suponga que siempre se establece un bit de 7 bytes para especificar que es un canal. Si el bit 7 está limpio, es un ciclo de desarrollo. Por supuesto, esto significa que el ciclo de trabajo solo puede ser de 0-127.

Para controlar los LED, no se puede distinguir fácilmente entre, por ejemplo, 50 y 51 ciclos de trabajo sin amplitud. Entonces, otra posibilidad es asegurarse de que todos los valores de devcycle sean iguales y multiplicar el devcycle que la computadora le envía por 2 (el desplazamiento a la izquierda lo facilitará). Puede ver un ejemplo de este esquema de protocolo en la siguiente figura.

Alternativamente, puede configurar bloques PWM de 7 bits que serían un poco más ingeniosos. Cambiar la longitud de bits es fácil porque utilicé parámetros en la definición de bloques PWM. Es posible que haya notado esto en los archivos. Aquí está parte de la definición modular para pwmblock:

module pwmblock #(parameter CNT_WIDTH=8, DIV_WIDTH=8) (input clk, input reset, 
   input [CNT_WIDTH-1:0] increment, ...

Esto le dice al compilador Verilog que, por defecto, CNT_WIDTH es 8, pero los casos separados del módulo pueden usar otros valores. En el resto de la definición modular (como la creación del argumento incremental), se puede aludir al código CNT_WIDTH en lugar de un número constante. En el caso a priori, se utiliza un aumento [7:0] para un total de 8 bits.

Si observa el banco de pruebas (en EDAPlayground), verá un módulo PWM de prueba que no usa los valores predeterminados:

epwmblock #(.CNT_WIDTH(10)) dut1(clk, reset, 10'h3ff, 10'h10, 8'h0, ep);

En este caso, el contador tiene 10 bits de ancho y se usaría un incremento [9:0]. Todos los lugares del código que esperan un contador de 8 bits ahora usarán un contador de 10 bits.

Hay otras formas de realizar la misma tarea. Por ejemplo, puede usar el bit 6 de los comandos del canal como un bit de respaldo para incluir al principio o al final del siguiente byte del ciclo de trabajo (es decir, 0x80, 0x23 establecería el ciclo de trabajo en 0x23 pero 0xC0, 0x23 lo establecería en 0xA3 ). Sin embargo, esto es un poco más difícil y no muy útil, pero valdría la pena intentarlo si quieres practicar.

Independientemente de cómo esté tratando con el protocolo de comando, deberá cambiar el código que procesa los caracteres UART recibidos. Así es como lo hice:

if ( !rx_byte[7] )  pwmduty[pwmchan] <= { rx_byte[6:0], 1'b0 };
else pwmchan <= rx_byte[6:0];

Como puede ver, el pwmchan Un registro contiene el número de canal PWM y retiene el último valor del comando de canal (después de eliminar los bits adicionales). Luego, cualquier byte de ciclo de desarrollo de PWM (un byte donde el bit 7 es 0) va al registro de ciclo de desarrollo apropiado a través del canal actual para seleccionar el elemento correcto de la matriz. También hago un cambio para multiplicar por 2 agregando un bit cero:

{ rx_byte[6:0], 1'b0 };

El protocolo no necesita mucho que tratar en la FPGA. Es robusto y eficiente (necesita enviar un comando de canal solo cuando desea cambiar de canal).

Puede encontrar el proyecto final en GitHub en el directorio v2. Aunque Makefile se ocupa del proceso de compilación, si desea saber más sobre cómo funcionan las herramientas de código abierto, puede ver el video de la última vez que hablé sobre ellas, que se encuentra a continuación.

Resultado

Envié el primer canal PWM a J3 en el borde del iCEStick para poder conectar una encuesta extensa para ver los resultados reales. A continuación verá el resultado al 50% (40 hexadecimal) y al 12,5% (10 hexadecimal). Recuerde, el porcentaje se multiplica por dos, por lo que las dos pistas generales representan 128/256 y 32/256.

El texto es pequeño, pero el tamaño muestra las medidas positivas y negativas de un ciclo de trabajo cerca de la parte inferior de la pantalla. La salida es PWM proporcional, así que tenga en cuenta los cambios de frecuencia (alrededor de 6 MHz al 50% y 1,5 MHz al 12,5%). Estos números tienen sentido, porque el reloj base de la FPGA es de 12 MHz. Estos porcentajes funcionan de tal manera que no hay error (es decir, el ciclo de trabajo se calcula dividido equitativamente por 256). De lo contrario, las partes fraccionarias harán que la onda cuadrada rebote (verá fantasmas en una escala con persistencia o simplemente tendrá problemas para disparar). Esto es normal porque la parte fraccionaria se distribuye en muchos ciclos.

Obviamente, la ventaja real de esto sería enviar datos a través del puerto serie de un software para controlar luces navideñas, motores, ventiladores o alguna otra función PWM útil. Si tuviera que cambiar el código UART para que funcione en algunos pines externos, podría controlar el PWM de un microcontrolador como Arduino o Raspberry Pi.

¿Sin CPU?

¿Puedes hacer PWM con CPU? Por supuesto que puede. Especialmente porque muchas CPU tienen lógica dedicada para producir una cierta cantidad de canales PWM. Por supuesto, si necesita más PWM de lo que proporciona su CPU, deberá hacerlo en el software. Eso es posible, pero no es tan efectivo, especialmente para muchos canales.

Con la FPGA, puede agregar muchos canales PWM completamente sin un cambio en el rendimiento. Incluso podría darle a cada canal su propio UART si eso tuviera sentido para su aplicación (y puede imprimir todo en el chip que elija).

Una vez que tenga UART en la FPGA, tiene muchas opciones. La entrada y salida digital sería simple. PWM se puede utilizar para impulsar luces o motores. Incluso podría usarlo para impulsar servicios. El límite es su imaginación, su habilidad con Verilog y su acceso a núcleos preparados.

  • kratz dice:

    Gracias por registrarte! Hice algo similar para intentar dominar una matriz de led, pero no tenía la parte UART, así que no fui mucho más allá de mostrar algo estático. Tendré que intentarlo algún día.

    FPGA + LED Matrix, Part 3

  • James Cordell dice:

    Hola

    Buen artículo. Sería genial leer señales PWM. Esto entonces abriría la posibilidad de crear un dispositivo que vaya entre los servicios y el receptor. Como una computadora voladora, etc.

    Encontré algunos códigos PWM leídos a continuación, pero en VHDL no es mi lenguaje más fuerte durante mucho tiempo. ¿Sería difícil convertir esto a Verilog?

    http://opencores.org/websvn,listing?repname=serial_div_uu&path=%2Fserial_div_uu%2Ftrunk%2F#path_serial_div_uu_trunk_

Eva Jiménez
Eva Jiménez

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *