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

Diseño de un controlador RC Servo basado en FPGA con PWM preciso

Los servos de radiocontrol (RC) son el corazón de aviones, coches y barcos de aficionados. Convierten una orden de radio remota en una posición angular precisa. Como los sistemas RC llevan décadas existiendo, la interfaz estándar de la industria es modulación por ancho de pulso (PWM) en lugar de un protocolo puramente digital. Con el tiempo determinista que ofrece un field‑programmable gate array (FPGA), implementar PWM de alta precisión es sencillo. Este artículo recorre un controlador de servo genérico en VHDL que funciona con cualquier servo RC usando PWM, incluyendo un testbench de simulación y una implementación real en una placa FPGA Lattice iCEstick.

Cómo funciona el control PWM para un servo RC

Aunque los fundamentos de PWM se tratan en otra parte, los servos RC requieren un enfoque ligeramente diferente. El servo ignora el ciclo de trabajo completo; solo muestrea la duración del período alto de cada pulso. El intervalo estándar entre pulsos es 20 ms, lo que corresponde a una frecuencia de 50 Hz. Dentro de esa ventana de 20 ms, el servo lee un pulso cuyo ancho típicamente varía de 1 ms (posición mínima) a 2 ms (posición máxima), aunque los valores exactos varían según el fabricante.

Diseño de un controlador RC Servo basado en FPGA con PWM preciso

La figura ilustra la relación temporal: un período de 20 ms se repite, y el tiempo alto de cada pulso mapea directamente a la posición angular del servo.

El controlador de servo en VHDL

Construiremos un módulo VHDL genérico que acepte parámetros genéricos para frecuencia de reloj, frecuencia de pulso PWM, anchos de pulso mínimo y máximo (en microsegundos), y el número de posiciones discretas entre los extremos. Como el reloj del FPGA funciona en el rango de megahercios, la aritmética de contadores enteros nos da resolución suficiente. Para evitar ambigüedades de redondeo, usaremos la función round del paquete ieee.math_real, que siempre redondea alejándose de cero.

library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.round;

A continuación está la declaración de entidad para el módulo servo:

entity servo is generic ( clk_hz : real; pulse_hz : real; -- Frecuencia de pulso PWM min_pulse_us : real; -- microsegundos en posición mínima max_pulse_us : real; -- microsegundos en posición máxima step_count : positive -- Número de posiciones discretas ); port ( clk : in std_logic; rst : in std_logic; position : in integer range 0 to step_count - 1; pwm : out std_logic ); end servo;

La entrada position impulsa el servo desde su mínimo (0) hasta su máximo (step_count‑1). La salida pwm se conecta al cable de señal del servo (generalmente amarillo o blanco). La mayoría de FPGAs entregan lógica de 3.3 V, por lo que se requiere un conversor de nivel para igualar la entrada de 5 V del servo.

La región declarativa

Comenzamos definiendo una función auxiliar que convierte un tiempo en microsegundos al número entero más cercano de ciclos de reloj:

function cycles_per_us (us_count : real) return integer is begin return integer(round(clk_hz / 1.0e6 * us_count)); end function;

Usando esa función, calculamos las constantes de temporización principales:

constant min_count : integer := cycles_per_us(min_pulse_us); constant max_count : integer := cycles_per_us(max_pulse_us); constant min_max_range_us : real := max_pulse_us - min_pulse_us; constant step_us : real := min_max_range_us / real(step_count - 1); constant cycles_per_step : positive := cycles_per_us(step_us);

Luego, configuramos el contador PWM que funciona a la frecuencia de pulso deseada:

constant counter_max : integer := integer(round(clk_hz / pulse_hz)) - 1; signal counter : integer range 0 to counter_max; signal duty_cycle : integer range 0 to max_count;

Contar ciclos de reloj

El siguiente proceso implementa el contador libre:

COUNTER_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then counter <= 0; else if counter < counter_max then counter <= counter + 1; else counter <= 0; end if; end if; end if; end process;

Proceso de salida PWM

La salida PWM se mantiene alta durante tantos ciclos de contador como dicte el valor actual de duty_cycle:

PWM_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pwm <= '0'; else pwm <= '0'; if counter < duty_cycle then pwm <= '1'; end if; end if; end if; end process;

Cálculo del ciclo de trabajo

En reset, duty_cycle toma por defecto el ancho de pulso mínimo. En caso contrario, se calcula a partir de la entrada position:

DUTY_CYCLE_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then duty_cycle <= min_count; else duty_cycle <= position * cycles_per_step + min_count; end if; end if; end process;

Aunque cycles_per_step se redondea al entero más cercano, el error resultante es insignificante porque el reloj del FPGA es órdenes de magnitud más rápido que la tasa de pulso de 50 Hz.

El testbench del servo

Para verificar el diseño, construimos un testbench simple que recorre manualmente todos los valores de posición y captura la forma de onda. Un reloj de prueba de 1 MHz acelera la simulación manteniendo un tiempo realista.

Constantes de simulación

constant clk_hz : real := 1.0e6; constant clk_period : time := 1 sec / clk_hz; constant pulse_hz : real := 50.0; constant pulse_period : time := 1 sec / pulse_hz; constant min_pulse_us : real := 1000.0; constant max_pulse_us : real := 2000.0; constant step_count : positive := 5;

Señales del DUT

signal clk : std_logic := '1'; signal rst : std_logic := '1'; signal position : integer range 0 to step_count - 1; signal pwm : std_logic;

Generación de reloj:

clk <= not clk after clk_period / 2;

Instanciación del DUT

DUT : entity work.servo(rtl) generic map ( clk_hz => clk_hz, pulse_hz => pulse_hz, min_pulse_us => min_pulse_us, max_pulse_us => max_pulse_us, step_count => step_count ) port map ( clk => clk, rst => rst, position => position, pwm => pwm );

Secuenciador del testbench

El secuenciador resetea el DUT, luego itera por todos los valores de posición, esperando un período de pulso entre cambios. Al completar la secuencia, la simulación termina.

SEQUENCER : process begin wait for 10 * clk_period; rst <= '0'; wait for pulse_period; for i in 0 to step_count - 1 loop position <= i; wait for pulse_period; end loop; report "Simulation done. Check waveform."; finish; end process;

Forma de onda de simulación del servo

La siguiente captura muestra la forma de onda del testbench. La salida PWM está alta solo para los primeros ciclos de contador, coincidiendo con el ancho de pulso esperado para cada valor de posición.

Diseño de un controlador RC Servo basado en FPGA con PWM preciso

Descargue los archivos completos del proyecto para reproducir la simulación.

Implementación de ejemplo en FPGA

A continuación, controlamos un servo real TowerPro SG90 desde una placa Lattice iCEstick. El servo requiere nivel lógico de 5 V, así que usamos un conversor de nivel entre el pin del FPGA y el cable de señal del servo.

Entidad del módulo superior

entity top is port ( clk : in std_logic; rst_n : in std_logic; -- Pull‑up pwm : out std_logic ); end top;

Señales y constantes

Calibramos los anchos de pulso del servo experimentalmente: 0.5 ms a 2.5 ms producen un barrido completo de 180°. Las siguientes constantes coinciden con el reloj del iCEstick y los parámetros del servo.

constant clk_hz : real := 12.0e6; -- reloj iCEstick constant pulse_hz : real := 50.0; constant min_pulse_us : real := 500.0; -- 0.5 ms constant max_pulse_us : real := 2500.0; -- 2.5 ms constant step_bits : positive := 8; -- 0 a 255 constant step_count : positive := 2**step_bits;

El contador libre es de 25 bits, dando un tiempo de desbordamiento de aproximadamente 2.8 segundos en el reloj de 12 MHz.

constant cnt_bits : integer := 25; signal cnt : unsigned(cnt_bits - 1 downto 0);

Conexión de los módulos:

signal rst : std_logic; signal position : integer range 0 to step_count - 1; signal rom_addr : unsigned(step_bits - 1 downto 0); signal rom_data : unsigned(step_bits - 1 downto 0);

Instanciación del módulo servo

SERVO : entity work.servo(rtl) generic map ( clk_hz => clk_hz, pulse_hz => pulse_hz, min_pulse_us => min_pulse_us, max_pulse_us => max_pulse_us, step_count => step_count ) port map ( clk => clk, rst => rst, position => position, pwm => pwm );

Instanciación de contador auto-ajustable

COUNTER : entity work.counter(rtl) generic map ( counter_bits => cnt_bits ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );

Instanciación de ROM senoidal

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

Asignaciones concurrentes conectan el contador, ROM y servo:

position <= to_integer(rom_data); rom_addr <= cnt(cnt'left downto cnt'left - step_bits + 1);

Pruebas en el Lattice iCEstick

Todo el circuito está montado en protoboard: la salida de 3.3 V del FPGA se eleva a 5 V para el servo. La señal PWM se conecta al pin 119 del iCEstick. Al alimentar, el servo oscila suavemente entre 0° y 180°, haciendo una pausa breve en los extremos. Un video de la forma de onda PWM en un osciloscopio demuestra el tiempo correcto.

Diseño de un controlador RC Servo basado en FPGA con PWM preciso

Reflexiones finales

Hay muchas formas de implementar un controlador PWM de servo en VHDL, pero usar contadores enteros mantiene la lógica sintetizada simple: solo contadores, registros y multiplexores. El error más común es desbordamiento silencioso en aritmética de 32 bits, así que siempre verifique que los cálculos intermedios permanezcan dentro de límites para sus parámetros elegidos.

Tenga en cuenta que este estilo PWM está adaptado para servos RC. Para aplicaciones donde el ciclo de trabajo importa más que la frecuencia (p.ej., control de potencia analógico), una estrategia PWM diferente es preferible.

Lea sobre control de potencia analógico usando PWM aquí:
Cómo crear un controlador PWM en VHDL

¿Listo para probarlo usted mismo? Descargue el archivo ZIP que contiene el código VHDL completo, testbench ModelSim, proyecto iCEcube2 y configuración del programador introduciendo su dirección de correo a continuación.

¡Hágame saber qué piensa en los comentarios!

VHDL

  1. Por Qué los Pasadores No Son la Mejor Opción para Posicionar Pies de Máquina – Alternativas y Soluciones Prácticas
  2. ¿Sus controladores servo pueden repararse? Conocimientos expertos y soluciones rentables
  3. Resolución del Error 'C Axis Drive Not OK' en Servo Drive Osai-Prima Electro 97942050N
  4. Transmisión de Datos de Temperatura en Tiempo Real desde ppDAQC Pi‑Plate a InitialState con Raspberry Pi Headless
  5. Monitoriza la temperatura de tu casa con un panel Raspberry Pi
  6. Control de Ventilador PWM de 4 Pines a 25 kHz con Arduino Uno – Montaje, Código y Pruebas
  7. Controlador de Potencia PWM: Construye un Driver de Lámpara con Modulación por Ancho de Pulso
  8. Asegurando IA en Cabina con Entorno de Ejecución Confiado en SoC FPGA Seguro
  9. Guía paso a paso: Extracción de datos de PLC con IIoT para insights en tiempo real
  10. Implementación de un controlador PWM en VHDL: Diseño, simulación y demo en FPGA
  11. Inicialización de RAM de bloques FPGA desde archivos de texto usando la biblioteca TEXTIO de VHDL