Módulos

Control de Servomotores mediante VHDL y Dos Botones

Figura 2: Esquema del controlador del servomotor con dos botones añadidos.

Anteriormente se creó un módulo para el control de servomotores por medio de un vector de entrada de siete bits. Pero, ¿quién manipula un servomotor por medio de siete interruptores? Peor aún si está conectado por medio de un diminuto dip switch.

Comúnmente, la señal de control para el servomotor viene de otro dispositivo, un ratón de computadora, el teclado, ordenes por puerto serial, un valor determinado en el sensor de temperatura, ecuaciones que definen un sistema de control PID, entre otra infinidad de posibilidades. El objetivo de esta entrada es crear un contador, controlado por medio de dos botones, que sirva como señal de entrada para el módulo del servomotor (reemplazando siete entradas por únicamente dos).

Descargar codigo

Lo que ya tenemos

En Señal de Control para Servomotores con VHDL, como el título indica, se crearon dos componentes necesarios, tres si se cuenta la unión, para la implementación del control de un servomotor. Estos tres bloques se muestran en la figura 1, los cuales son:

  • Divisor de frecuencia de 50MHz a 64kHz, clk64kHz, para obtener la frecuencia de actualización y generar los estados necearios en el control.
  • Componente del controlador para servomotor, servo_pwm, el cual genera la señal de salida necesaria para controlar el servomotor en base a un vector de entrada de siete bits.
  • PORT MAP de los dos componentes anteriores, servo_pwm_clk64kHz, el cual junta ambos módulos para crear la caja negra correspondiente al módulo del control de servomotores.
Figura 1: Bloque actual del módulo de control de un servomotor.

Figura 1: Bloque actual del módulo de control de un servomotor.

En el mundo real, ajustar un servomotor por medio de siete interruptores, pensando en un valor en binario correspondiente, no es práctico ni deseado. Por lo tanto, es necesario crear una interfaz, por más simple que ésta sea, para facilitar el control de nuestro actuador.

Lo que ahora queremos

Debido a que nuestro control de servomotores ya funciona tal y como está, no es necesario realizar modificaciones. Por lo tanto, para cambiar el comportamiento del sistema es necesario añadir un bloque más, que reciba dos entradas correspondientes a los botones y las traduzca al vector de entrada de siete bits que espera el bloque servo_pwm.

Así pues, el diagrama a bloques se transformaría a algo similar a lo mostrado en la figura 2, donde el divisor de frecuencia y el controlador del servomotor permanecen sin ser modificados pero se añade un nuevo componente con dos entradas que produce la salida de siete bits. En consecuencia, el módulo “pegamento” se modifica también.

Figura 2: Esquema del controlador del servomotor con dos botones añadidos.

Figura 2: Esquema del controlador del servomotor con dos botones añadidos.

Es tiempo de ver un poco de código.

Contador de dos botones

El contador se encarga de dos tareas bastantes simples:

  1. incrementar el contador en uno cuando se presione cnt_up, y
  2. decrementar el contador en uno cuando se presione cnt_dn.

Sin embargo, debemos tomar en cuenta el desbordamiento. No queremos que después del valor máximo (correspondiente a 127) ocurra un desbordamiento que desplace al servomotor hasta el otro extremo (¡ouch!). De igual forma, no queremos que el decremento tras llegar a cero produzca un movimiento tan repentino. Estas condiciones se estipulan en las líneas 23 y 25 del listado 1.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity contador_servo_pwm is
    PORT (
        clk   : IN  STD_LOGIC; -- Reloj de 64kHz.
        reset : IN  STD_LOGIC; -- Botón de reset.
		cnt_up: IN  STD_LOGIC; -- Botón de incremento.
		cnt_dn: IN  STD_LOGIC; -- Botón de decremento.
        pos   : OUT STD_LOGIC_VECTOR(6 downto 0) -- Salida a servomotor.
    );
end contador_servo_pwm;

architecture Behavioral of contador_servo_pwm is
	-- Señal utilizada para modificar la salida "pos".
	signal contador: UNSIGNED(6 downto 0) := (OTHERS => '0');
begin
	proceso_contador: process (clk, reset, cnt_up, cnt_dn) begin
		if (reset = '1') then
			contador <= (others => '0');
		elsif rising_edge(clk) then
			if (cnt_up = '1' AND contador < 127) then
				contador <= contador + 1;
			elsif (cnt_dn = '1' AND contador > 0) then
				contador <= contador - 1;
			end if;
		end if;
	end process;
	
	-- Asignación del valor del contador a la salida del módulo.
	pos <= STD_LOGIC_VECTOR(contador);
end Behavioral;

En la línea 32 se asigna el valor del contador a la salida del módulo, misma que será utilizada para controlar la posición del servo en el siguiente módulo.

Descargar codigo

Modificando el módulo principal

Debido a que se desea utilizar la frecuencia de 64kHz como entrada para el módulo del contador, lo mejor es modificar el módulo principal anterior e incluir este nuevo archivo mediante otra instrucción PORT MAP. El listado 2 muestra el resultado tras las modificaciones.

Los cambios más significativos son:

  • Cambio de nombre de servo_pwm_clk64kHz a servo_pwm_contador_clk64kHz (líneas 4, 12 y 14).
  • Cambio de la señal de entrada pos en la declaración de la entidad en favor de los dos botones para el contador (líneas 8 y 9).
  • Adición del componente contador_servo_pwm (líneas 2 a 40).
  • Creación de la señal pos_out para interconectar el contador con el componente servo_pwm (línea 43).
  • Inclusión de una intrucción PORT MAP para el contador (línea 49 a 51).
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
 
entity servo_pwm_contador_clk64kHz is
    PORT(
        clk   : IN  STD_LOGIC;
        reset : IN  STD_LOGIC;
		cnt_up: IN  STD_LOGIC;
		cnt_dn: IN  STD_LOGIC;
        servo : OUT STD_LOGIC
    );
end servo_pwm_contador_clk64kHz;
 
architecture Behavioral of servo_pwm_contador_clk64kHz is
    COMPONENT clk64kHz
        PORT(
            entrada: in  STD_LOGIC;
            reset  : in  STD_LOGIC;
            salida : out STD_LOGIC
        );
    END COMPONENT;
     
    COMPONENT servo_pwm
        PORT (
            clk   : IN  STD_LOGIC;
            reset : IN  STD_LOGIC;
            pos   : IN  STD_LOGIC_VECTOR(6 downto 0);
            servo : OUT STD_LOGIC
        );
    END COMPONENT;

    COMPONENT contador_servo_pwm
        PORT (
			clk   : IN  STD_LOGIC;
			reset : IN  STD_LOGIC;
			cnt_up: IN  STD_LOGIC;
			cnt_dn: IN  STD_LOGIC;
			pos   : OUT STD_LOGIC_VECTOR(6 downto 0)
        );
    END COMPONENT;
     
    signal clk_out : STD_LOGIC := '0';
	signal pos_out : STD_LOGIC_VECTOR(6 downto 0);
begin
    clk64kHz_map: clk64kHz PORT MAP(
        clk, reset, clk_out
    );

	contador_servo_pwm_map: contador_servo_pwm PORT MAP(
		clk_out, reset, cnt_up, cnt_dn, pos_out
	);
     
    servo_pwm_map: servo_pwm PORT MAP(
        clk_out, reset, pos_out, servo
    );
end Behavioral;

Simulación

Es tiempo de demostrar que el nuevo componente hace lo que creemos que hace. Para ello, creamos un nuevo banco de pruebas simple. El listado 3 contiene el código fuente del banco de pruebas.

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

ENTITY servo_pwm_contador_clk64kHz_tb IS
END servo_pwm_contador_clk64kHz_tb;

ARCHITECTURE behavior OF servo_pwm_contador_clk64kHz_tb IS 
	-- Unidad bajo prueba.
	COMPONENT servo_pwm_contador_clk64kHz
		PORT(
			clk   : IN  std_logic;
			reset : IN  std_logic;
			cnt_up: IN  std_logic;
			cnt_dn: IN  std_logic;
			servo : OUT std_logic
		);
	END COMPONENT;

	-- Entradas.
	signal clk : std_logic := '0';
	signal reset : std_logic := '0';
	signal cnt_up : std_logic := '0';
	signal cnt_dn : std_logic := '0';
	-- Salidas.
	signal servo : std_logic;
	-- Definición del reloj.
	constant clk_period : time := 10 ns;
BEGIN
	-- Instancia de la unidad bajo prueba.
	uut: servo_pwm_contador_clk64kHz PORT MAP (
		clk => clk,
		reset => reset,
		cnt_up => cnt_up,
		cnt_dn => cnt_dn,
		servo => servo
	);

	-- Definición del proceso de reloj.
	clk_process :process
	begin
	clk <= '0';
	wait for clk_period/2;
	clk <= '1';
	wait for clk_period/2;
	end process;


	-- Procesamiento de estímulos.
	proceso_estimulos: process begin
		-- Crear condición de reset.
		reset <= '1';
        wait for 50 ns;
        reset <= '0';
		-- Esperar poco menos de dos ciclos.
		wait for 39 ms;
		-- Crear el estímulo.
		cnt_up <= '1';
		wait for 10 us; -- Tiempo a editar.
		cnt_up <= '0';
		wait;
	end process;
END;

El código mostrado es básicamente el esqueleto creado por las herramientas de Xilinx, siendo las líneas 56 a 59 aquellas que deben ser editadas para observar cambios en el banco de pruebas. Para este banco simplemente se manda la señal de reset y se dejan correr dos ciclos de 20ms antes de modificar los estímulos. Para los estímulos es importante tomar en cuenta la frecuencia de trabajo del contador.

Ya que el contador opera con un reloj interno de 64 kHz gracias al divisor, el estímulo debe durar aproximadamente 15 o 16 microsegundos para ser detectado. Por ello, no existe ninguna diferencia entre botón no presionado y el botón presionado durante sólo 10us (no se detecto el flanco debido a la frecuencia de operación).

Al cambiar el valor de 10us a 15us, el tiempo en alto de la señal de salida pasa de 0.49984ms a 0.51546ms (que corresponde a 64kHz o un solo pulso detectado). Por otra parte, si se elimina la línea 59, el servomotor llegará a su salida máxima tras 127 detecciones del botón (el desbordamiento es evitado). La figura 3 ilustra esta última condición.

Figura 3: Simulación del controlador del servomotor mediante dos botones para el contador.

Figura 3: Simulación del controlador del servomotor mediante dos botones para el contador.

Esto concluye el control de servomotor con la ligera modificación mediante uso de dos botones. El código fuente está diponible para su descarga en el botón colocado debajo:

Descargar codigo

Conclusiones

Hasta aquí, se ha concluido el desarrollo de una ligera mejora en el control del servomotor. Sin embargo, se dejaron algunas cosas de lado, como la simulación del botón de decremento, o las posibles combinaciones al presionar ambos botones, ¿cuáles son los valores esperados en esos casos? Un problema más que no se considero, y que puede no serlo según las especificaciones, es el hecho de que el contador sigue incrementando o decrementando mientras el botón siga presionado pero, ¿y si deseamos que sea una única vez sin importar cuánto tiempo dure presionado? ¿qué debería cambiar en ese caso?

Si conoces algún compañero que esté teniendo un dolor de cabeza tratando de implementar sistemas como el desarrollado en esta entrada, hazle un favor y compárte esta entrada. Hasta la próxima.

You Might Also Like

14 Comentarios

  • Responder
    Fernando Robles
    junio 12, 2013 at 12:21 pm

    Pues he navegado en esta pagina y he encontrado mucho material excelente, de muy buena calidad, y facil de usar, la explicacion no podria ser mejor, sabes, me gustaria saber cuando podras realizar algun tutorial para poder controlar una pantalla LCD 16×2 en este caso con una fpga, seria de mucha ayuda para trabajar y hacer mas proyectos. De antemano de agradezco y felicito por maravillosa pagina

    • Responder
      Carlos Ramos
      junio 16, 2013 at 12:23 am

      Buenas noches, Fernando. Gracias por tu comentario, hasta dan ganas de escribir mucho más. Respecto al tutorial para controlar un LCD 16×2, tengo planes para escribir algo relacionado en unas dos semanas. Si el componente es urgente para ti, puedes utilizar uno de los disponibles en Open Cores. Saludos.

  • Responder
    Aero91most
    enero 19, 2014 at 11:04 am

    Buenas, realmente encontrar esta web ha sido uno de mis mejores noticias desde hace semanas jajajaj, estoy estudiando ingenieria electronica y tengo una asignatura precisamente de vhdl, tengo que hacer un trabajo de grupo y varios de estos posts me estan ayudando mucho, asi que muchas gracias de verdad.

    En mi trabajo tengo ese problema que mencionas de las entradas, para que al pulsar el pulsador sólo se de un pulso de reloj, hemos metido un circuito antirrebote que se encuentra en las propias plantillas del programa de xilinx.

    Está en VHDL/Synthesys constructs/Coding examples/Misc/Debounce circuit.

    Basicamente si tienes pulsado el boton durante un minimo de dos ciclos de reloj, da una salida en alto de sólo un pulso de reloj. El reset en nuestro trabajo no se necesitaba y lo dejamos comentado.
    Lo copio aqui debajo por si a alguien le sirve:

    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_ARITH.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;

    entity Estabilizador is
    Port ( Pulsador_in : in STD_LOGIC;
    CLK : in STD_LOGIC;
    Pulsador_out : out STD_LOGIC–;
    — reset : in STD_LOGIC
    );
    end Estabilizador;

    architecture Behavioral of Estabilizador is

    — Provides a one-shot pulse from a non-clock input, with reset

    signal Q1, Q2, Q3 : std_logic;

    begin

    process(CLK)
    begin
    if (CLK’event and CLK = ‘1’) then
    — if (reset = ‘1’) then
    — Q1 <= '0';
    — Q2 <= '0';
    — Q3 <= '0';
    — else
    Q1 <= Pulsador_in;
    Q2 <= Q1;
    Q3 <= Q2;
    — end if;
    end if;
    end process;

    Pulsador_out <= Q1 and Q2 and (not Q3);
    end Behavioral;

    Simplemente instanciar en la entidad "top" un estabilizador para cada entrada de pulsador, justo antes del componente al que iba la señal de entrada.

    • Responder
      Carlos Ramos
      enero 21, 2014 at 1:30 pm

      Muchas gracias por tu contribución, Aero91most. El código de ejemplo provisto por Xilinx (o la compañía con la que se trabaje) siempre es una buena base para aprender más. En las próximas entradas trataré código de muestra con una explicación un tanto más elaborada y sencilla, nada mejor que utilizar lo que ya está hecho. Saludos.

  • Responder
    CARLOS MARTÍNEZ
    mayo 26, 2014 at 11:47 am

    Estoy trabajando en un proyecto que utiliza este sistema, pero al implementar el sistema con la fpga y al pulsar los botones, el servo se va a los extremos ya que los pulsos de reloj son muy pequeños, como puedo hacer para que al pulsar cada botón, el servo aumente o disminuya de uno en uno.
    Gracias

    • Responder
      Carlos Ramos
      mayo 27, 2014 at 10:40 am

      Buenos días, Carlos Martínez,
      En la tercera parte del metrónomo utilicé un contador con dos botones para modificar el valor. Ahí tuve el problema que mencionas, solamente iba de un extremo a otro. La solución implementada fue crear un divisor de frecuencia de 5Hz y usarlo como entrada para el contador. Puedes ver más detalles sobre ello en la entrada. Espero y te sea de utilidad.
      Saludos.

  • Responder
    Anayeli
    abril 17, 2015 at 3:38 pm

    Hola que tal! la verdad estoy muy confundida, por decir , si el usuario me dijera el numero de posiciones que quiere que avance el servo por decir 50 posiciones entonces esta constante seria mi entrada pero como manejo esta después o de manera contraria si me da un -20 ir hacia la izquierda,
    en fin siento que es muy parecido a los botones pero en ves que lo hagan de manera continua hasta oprimir reset o los demás al llegar al numero de posiciones parar el servo , de verdad no entiendo como manejar esta constante !!
    Espero me pueda ayudar ! Gracias!.

    • Responder
      Carlos Ramos
      abril 17, 2015 at 4:47 pm

      Anayeli, creo que el mayor reto sería cómo obtener esa constante de manera física. Por ejemplo, he realizado tal tarea utilizando un ratón como entrada (de manera muy imprecisa, y el servomotor se mueve de un lado para otro). A lo que llego es: ¿sería una entrada paralela (unos ocho interruptores para introducir la constante) o una entrada serial (algún dato serial, por I2C u otro protocolo).
      Imagina, por ejemplo, que es un conjunto de interruptores (entrada paralela). En lo que realizas el cambio, el servo ya se movió bastantes posiciones más. ¿O planeas manejar además una entrada para cargar el dato cuando ya lo tengas listo? Entraríamos, además, en un pequeño filtro para cambiarlo únicamente la primera vez (cuando haya una diferencia en la entrada).
      En este punto ya se volvió un lío todo. Por eso considero oportuno, primero que nada, definir cómo recibirás la entrada.
      Saludos.
      PS. Quizá derivé mucho de tu pregunta, hazme saber si fue así.

      • Responder
        Anayeli
        abril 23, 2015 at 8:43 pm

        Hola según mi idea es ingresarlo mediante un teclado matricial y sobre esto pues obtener el numero de posiciones que quiere el usuario avanze el servo pero de verdad no se como manejarlo

  • Responder
    Emma
    agosto 20, 2015 at 4:47 pm

    Hola buenas tardes, este código ha sido de gran ayuda, pero me gustaría saber si es posible que al pulsar un botón el servo no se pare llegando a 180° y viceversa sino que sea continuo, como puedo hacer o que parte del código tengo que modificar y que el motor se pare a un determinado numero de giros?

    • Responder
      Carlos Ramos
      agosto 20, 2015 at 6:36 pm

      Hola,
      Sinceramente no tengo idea de cómo hacer lo que indicas pues solamente he trabajado con servomotores con determinados límites (y creo que para hacer que de la vuelta completa habría que modificar el servomotor).
      Lo que puedes hacer es editar las líneas 23 y 25 para quitar los límites establecidos,

      if (cnt_up = '1') then --Eliminar &quot;AND contador &lt; 127&quot; de la línea 23.
      

      aunque no sé qué consecuencias haya en el lado del hardware.

      Me agradaría saber sobre tu implementación en el caso que mencionas.

      Saludos.

  • Responder
    daniel de los santos
    enero 9, 2017 at 4:27 pm

    he buscado información de VHDL y he encontrado esta excelente pagina y me ha ayudado muchísimo soy estudiante de 5to de la ingeniería electrónica , en esta ocasión te pido tu ayuda para controlar un servomotor así como lo hiciste con dos push botton pero nos pidieron que avance cada 20 grados hasta 180 que serian 9 pulsos y que no pase de 180 grados y de regreso a 0 también con los nueve pulsos cada 20 grados y la verdad no he hallado como hacerlo agradecería mucho tu ayuda .

  • Responder
    Harold Ramírez
    junio 4, 2019 at 8:47 pm

    Primeramente, quiero agradecer infinitamente a esta página y al motor de búsqueda de Google por trarme hasta aquí, realmente es una página con un material increíble.

    Ahora bien, tengo un problema, ya que apenas estoy iniciando en este mundo del VHDL, y es que si directamente creo un proyecto, doy clic derecho sobre él y agrego las fuentes que dejas para descargar, al momento de ver la simulación (habiendo sintetizado antes) me salen los siguientes errores:

    ERROR:HDLCompiler:102 – “Unknown” Line 0: cannot open vhdl file C:/Users/HaroldMartinez/Downloads/ef_servo_pwm_contador_clk64kHz/servo_pwm.vhd

    ERROR:Simulator:824 – Sorting of project file “D:/Universidad Distrital/Digitales1/ProyectosXilinx/Servomotor123/servo_pwm_contador_clk64kHz_stx_beh.prj” failed. Please sort the files manually, add a nosort keyword on the last line of “D:/Universidad Distrital/Digitales 1/Proyectos Xilinx/Servomotor123/servo_pwm_contador_clk64kHz_stx_beh.prj” and rerun the compiler.

    Se supone que debo organizar manualmente los componentes -según el error- pero… ¿cuál debería ser el orden? también dice que agregue una palabra clave al final del archivo .prj pero ¿con qué fin? o más bien ¿qué palabra?

    Perdón la ignorancia, si es que estoy haciendo un proceso equivocado, también opté por crear el módulo VHDL y copiar directamente ahí el código, pero el error es el mismo.

    Espero puedas ayudarme. Mil y mil gracias.

  • Deja tu comentario