En una entrada anterior habíamos desarrollado un componente para el control de un motor DC mediante PWM. En esta ocasión, desarrollaremos un componente para generar la señal de control para un servomotor.
[wpdm_file id=11]
Control de un servomotor
Primero que nada, un servomotor no es más que un motor de corriente directa con una tarjeta electrónica adjunta para facilitar su control. Para dicho control es necesario generar un pulso como se muestra en la figura 1.
La duración o frecuencia del pulso determina la posición en la que se debe situar el motor. Cada servomotor tiene su propio rango de frecuencias, determinadas por el fabricante en la hoja de datos. Para la figura 1, los valores de la señal están entre 1 y 2 ms.
Diseño del control
La señal de control para el servomotor se compone de dos frecuencias:
- Frecuencia de actualización de 20ms.
- Ancho de pulso que controla la posición del servomotor, provista por el fabricante. Para este ejemplo, asumiremos que la frecuencia va de 0.5 a 2.5ms.
¿Cómo empezamos a desarrollar los dos divisores de frecuencia necesarios para esta señal? Primero que nada, es necesario encontrar el rango de operación:
\(rango = tiempo_{max} – tiempo_{min} = 2.5ms – 0.5ms = 2 ms\)Ahora es necesario saber la resolución del servomotor, o cantidad de posiciones que puede tomar. Por lo tanto, la frecuencia mínima necesaria es:
\(f_{necesaria} = \left(\dfrac{rango}{resolucion}\right)^{-1}\)Si nuestro servomotor puede tomar 128 posiciones, tenemos que:
\(f_{necesaria} = \left(\dfrac{2ms}{128}\right)^{-1} = \left(0.015625ms\right)^{-1} = 64 kHz\)Por tanto, es necesario diseñar un divisor de frecuencia de 64 kHz (el cual se muestra en el listado 1). Siempre puedes consultar cómo hacer un divisor de frecuencia.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity clk64kHz is Port ( entrada: in STD_LOGIC; reset : in STD_LOGIC; salida : out STD_LOGIC ); end clk64kHz; architecture Behavioral of clk64kHz is signal temporal: STD_LOGIC; signal contador: integer range 0 to 780 := 0; begin divisor_frecuencia: process (reset, entrada) begin if (reset = '1') then temporal <= '0'; contador <= 0; elsif rising_edge(entrada) then if (contador = 780) then temporal <= NOT(temporal); contador <= 0; else contador <= contador + 1; end if; end if; end process; salida <= temporal; end Behavioral;
Finalmente, sabemos que con un reloj de 64 kHz tenemos 1ms cada 64 iteraciones. Para tener la frecuencia de 20 ms basta multiplicar 64 * 20, implementado con un contador de 0 a 1279.
Implementación en VHDL
Para la implementación en VHDL tenemos tres entradas: reloj de 64kHz, reset, y un vector que puede tomar valores de 0 a 127. La única salida es la señal de control para el servomotor. El listado 2 muestra el código completo del componente.
[wpdm_file id=10]
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity servo_pwm is PORT ( clk : IN STD_LOGIC; reset : IN STD_LOGIC; pos : IN STD_LOGIC_VECTOR(6 downto 0); servo : OUT STD_LOGIC ); end servo_pwm; architecture Behavioral of servo_pwm is -- Contador de 0 a 1279. signal cnt : unsigned(10 downto 0); -- Señal temporal para generar el PWM. signal pwmi: unsigned(7 downto 0); begin -- Valor mínimo debe ser de 0.5ms. pwmi <= unsigned('0' & pos) + 32; -- Proceso del contador, de 0 a 1279. contador: process (reset, clk) begin if (reset = '1') then cnt <= (others => '0'); elsif rising_edge(clk) then if (cnt = 1279) then cnt <= (others => '0'); else cnt <= cnt + 1; end if; end if; end process; -- Señal de salida para el servomotor. servo <= '1' when (cnt < pwmi) else '0'; end Behavioral;
La señal cnt sirve para implementar el contador de 0 a 1279, mismo que se describe en el proceso de las líneas 22 a 33.
La señal de entrada pos es un vector con valores de 0 a 127, que dan el rango de 0ms a 2ms. Así que es necesario sumar un total de 32 a la señal para generar un pulso de 0.5ms a 2.5ms (línea 21). La señal de salida estará activa solamente cuando el valor de la señal pwmi sea menor que el valor del contador.
El listado 3 incluye el PORT MAP
necesario para unir el divisor con el controlador del servomotor.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity servo_pwm_clk64kHz is PORT( clk : IN STD_LOGIC; reset: IN STD_LOGIC; pos : IN STD_LOGIC_VECTOR(6 downto 0); servo: OUT STD_LOGIC ); end servo_pwm_clk64kHz; architecture Behavioral of servo_pwm_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; signal clk_out : STD_LOGIC := '0'; begin clk64kHz_map: clk64kHz PORT MAP( clk, reset, clk_out ); servo_pwm_map: servo_pwm PORT MAP( clk_out, reset, pos, servo ); end Behavioral;
Simulación y banco de pruebas
Para el banco de pruebas se utilizaron diversos valores de entrada para pos, desde 0 a 127 (líneas 43 a 59).
LIBRARY ieee; USE ieee.std_logic_1164.ALL; ENTITY servo_pwm_clk64kHz_tb IS END servo_pwm_clk64kHz_tb; ARCHITECTURE behavior OF servo_pwm_clk64kHz_tb IS -- Unidad bajo prueba. COMPONENT servo_pwm_clk64kHz PORT( clk : IN std_logic; reset : IN std_logic; pos : IN std_logic_vector(6 downto 0); servo : OUT std_logic ); END COMPONENT; -- Entradas. signal clk : std_logic := '0'; signal reset: std_logic := '0'; signal pos : std_logic_vector(6 downto 0) := (others => '0'); -- Salidas. signal servo : std_logic; -- Definición del reloj. constant clk_period : time := 20 ns; BEGIN -- Instancia de la unidad bajo prueba. uut: servo_pwm_clk64kHz PORT MAP ( clk => clk, reset => reset, pos => pos, 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 reset <= '1'; wait for 50 ns; reset <= '0'; wait for 50 ns; pos <= "0000000"; wait for 20 ms; pos <= "0101000"; wait for 20 ms; pos <= "1010000"; wait for 20 ms; pos <= "1111000"; wait for 20 ms; pos <= "1111111"; wait; end process; END;
El resultado de la simulación se muestra en la figura 2. Para la frecuencia de 20 ms se obtuvo una frecuencia de 19.9936 ms, para la frecuencia mínima se obtuvo 0.4920 ms y para la frecuencia máxima se obtuvo una frecuencia de 2.4835 ms.
El contenido de los cuatro listados se incluye en el siguiente enlace de descarga:
[wpdm_file id=11]
Aplicaciones
Ahora ya tenemos un componente para controlar un motor DC y un controlador para servomotores. Son componentes relativamente simples, pero podemos empezar a pensar en aplicaciones y un poco de róbotica, para el movimiento de brazos como el Lynx 6.
39 Comentarios
cristian
abril 20, 2013 at 5:46 pmhola que tal tengo una duda en esta parte :
pwmi <= unsigned('0' & pos) + 32;
es la que no entiendo porq & pos y porque sumas 32?
te agradeceria la respuesta de antemano gracias
cristian
abril 20, 2013 at 6:04 pmpor cierto unsigned no me sale como si fuera palabra reservada o mejor dicho no se me pone de color azul
Carlos Ramos
abril 20, 2013 at 7:26 pmUna buena observación Cristian, gracias por señalarlo. La palabra
unsigned
no es una palabra reservada de VHDL dado que corresponde a un tipo de dato derivado (que proviene deSTD_LOGIC_VECTOR
). Al parecer, el desarrollador del plugin de coloreado de sintaxis para VHDL se tomó la libertad de añadirlo a los tipos de datos que se colorean (no me vean a mí, a pesar de que…) aunque el entorno de Xilinx no lo hace.Carlos Ramos
abril 20, 2013 at 7:03 pmHola, Cristian. Gracias por el comentario. Como se estableció, el rango del servomotor va de 0.5 a 2.5ms. Ese valor de 32 se utiliza para asegurarnos de que siempre contamos con el rango mínimo de 0.5ms (dado que 64 iteraciones es corresponden a 1ms).
El operador & se utiliza para concatenar o agregar otro bit al valor de
pos
con el fin de prevenir un desbordamiento debido la adición de ese 32 (es decir, para prevenir errores en los cálculos).cristian
abril 22, 2013 at 4:13 pmmuchas gracias por responder, me ha servido mucho.
Miguel
mayo 28, 2013 at 11:45 pmBuenos días, como le hago para controlarlo con 2 push button uno para la izquierda y otro para la derecha, seria de gran ayuda si me resolvieran esa duda, se los agradecería.
Carlos Ramos
junio 1, 2013 at 11:56 pmBuenas tardes, Miguel, disculpa la demora en mi respuesta. Acabo de publicar una entrada relacionada a tu problema, la cual puedes consultar aquí. En caso de que ya hayas solucionado tu problema, me gustaría que dejaras un comentario en esa entrada acerca de tu solución. Gracias por tu comentario.
Loraine
noviembre 10, 2014 at 2:58 pmHola una pregunta: La resolución ya viene definida? Es decir, según el tipo de servo motor tenemos que poner un valor especifico en la resolución?
Carlos Ramos
noviembre 11, 2014 at 9:38 pmHola, Loraine. La resolución viene definida por el fabricante del servomotor, que indica cuántas posiciones puede tomar el actuador. En base a la cantidad de posiciones que dice el fabricante creas el controlador. Si el servo tiene más posiciones de las del controlador, éstas no serán utilizadas (el controlador no puede generar las posiciones). Caso contrario, el controlador tiene más posiciones que el servo, el servo tomará la misma posición para diversos valores en el controlador (el servo no sabe qué hacer con unas señales, y las aproxima a un valor que sí conoce). Espero haber respondido tu pregunta.
diego parrado
diciembre 2, 2014 at 5:35 pmBuenas noches desearía saber como controlar un motor mediante xilinx con maquinas de estado, que controlen el sentido y también reconozca en el display de la tarjeta basys las palabras izquierda o derecha según sea el caso. y que me permita parar el motor y se muestre en la tarjeta stop cuando este pare.
Gracias por su colaboración.
Carlos Ramos
diciembre 8, 2014 at 9:01 pmBuenas noches, Diego. Gracias por comentar. Primero que nada, ¿qué clase de motor deseas controlar? ¿seguimos hablando de motor DC? En segunda, ¿cuántos visualizadores planeas utilizar para mostrar la dirección? Algo como “Izq” y “Der” (según las letras usadas en siete segmentos y VHDL). El primer paso para la solución sería controlar el motor en ambos sentidos. Posteriormente, ocuparse de los visualizadores. Saludos.
Tetsuo
junio 3, 2015 at 1:24 pmBuen dia, me aparece “wait statement without until clause not supported for synthesis” cuando le doy clic en Implement Top Module
Carlos Ramos
noviembre 14, 2016 at 11:58 amTetsuo, probablemente estás intentando sintetizar el banco de prueba como un módulo para el FPGA. La instrucción
wait;
sí se puede simular, pero no es físicamente posible.Juan Carlos Solar
junio 22, 2015 at 7:27 pmBuenas noches, disculpa, tengo una duda: ¿Por qué asumes que el rango de operación va de 0.5 ms a 2.5 ms si en la imagen muestras que va de 1ms a 2ms? ¿Existe un rango estándar?
Carlos Ramos
agosto 21, 2015 at 10:23 amHola, Juan.
El rango depende del fabricante, aunque asumo habrá cierto conjunto que se considere “estándar” o sea más utilizado que otros valores.
La imagen y los cálculos no tienen relación, aunque tienes razón, es un punto de posible confusión.
Gracias por tu retroalimentación :D.
Saludos cordiales.
Rene Garcia
enero 14, 2016 at 12:11 pmSi necesitas una frecuencia de 64kHz no deberias contar hasta (50MHz/64kHz)/2=390?
Carlos Ramos
junio 29, 2016 at 7:53 amRené, es algo de lo que apenas me estoy dando cuenta, años después de diseñar el componente. Permíteme verificar el componente.
Maximo
abril 6, 2016 at 3:00 pmEstoy trabajando con un Micro Servo 9g SG90 TowerPro, su rango es de 1 a 2 ms, sin embargo no encuentro en su datasheet el numero de posiciones que puede tomar, ¿hay algun estandar respecto al numero de posiciones?¿De casualidad sabras cuantas posiciones tiene el servomotor SG90? Agradezco tu atencion. Excelente pagina, me ha servido de ayuda, gracias.
Carlos Ramos
abril 6, 2016 at 3:39 pmGeneralmente necesitamos muchas menos de las que tiene. Simplemente calcula para la cantidad de posiciones que necesitas, ignorando la cantidad máxima de posiciones que el servo puede tomar. Saludos.
Mauricio Rossainz
junio 29, 2016 at 5:08 am¿Qué tal? Yo tengo una pregunta.
En los archivos que nos ofreces para bajar, en clk64kHz tienes un contador de integer que va de 0 a 780 y no entiendo por qué 780.
Entiendo por tu articulo acerca de divisor de frecuencia, que referencias en este articulo, que el contador debe ir de 0 a (escala/2)-1.
Si te entiendo bien nuestro valor de escala es la frecuencia de entrada (que no sé cuál es) sobre la frecuencia que deseamos (64kHz en nuestro caso).
Si hago ingenieria inversa, el valor máximo del contador + 1 es 781 * 2 es 1562 que sería nuestra escala.
Luego, sé que la frecuencia que desamos es 64000 Hz y que escala es fentrada/fdeseada entonces fentrada es fdeseada * escala y 64000 * 1562 me da 99968000 Hz ¿Dé dónde sale eso?
Carlos Ramos
junio 29, 2016 at 7:50 amMauricio, tienes una muy buena observación. He de revisar la simulación del componente para ver si opera de la manera adecuada (y si lo hace, ¿por qué es así?). Mi frecuencia de entrada es la provista por la tarjeta Basys2, que es de 50MHz.
Debido a que al parecer no utilice la parte de $$\frac{escala}{2} – 1$$, tus cálculos dan la frecuencia de entrada correcta, cerca de 100MHz (que es el doble de la frecuencia de entrada en la Basys2).
Muchas gracias por tu aporte, permíteme revisar el componente.
Mauricio
febrero 21, 2017 at 7:31 pm¿Qué tal? Poco menos de un año ha pasado y nunca te agradecí apropiadamente por ayudarme con el proyecto que estaba realizando en ese entonces.
Seguí todas tus instrucciones para crear mi pwm y me funciono de maravilla. Gracias.
Dicho lo anterior, me quede mucho con la duda de qué es lo que pasaba con este ejemplo. Ya no lo necesito pero si me pudieses dar respuesta te lo agradecería enormemente.
Carlos Ramos
febrero 21, 2017 at 11:32 pmMauricio, ¡muchas gracias por tu comentario!
Am… ¿qué pasaba de qué? Si puedes recordarme la pregunta, sería genial XD.
Eliseo Rojas
octubre 10, 2016 at 8:20 pmHola carlos tengo un pequeño proyecto espero puedas ayudarme, tengo un radio control FS T4B y su receptor FS R6B la meta es que este receptor pueda manejar un motor brushed de 12V y 55Amp con inversion de giro, bueno esa parte no tiene mucho problema ya que solucinamos con puente “H” de mosfets, mi problema es; de que modo puedo tomar la señal de salida del receptor FS R6B la misma que esta diseñada para controlar un servomotor, entonces si la señal es de 1ms para 0grados, 1.5 para 90 grados y 2ms para 180 grados, mi intensión seria de 1 a 1.3 ms sentido antihorario y 1.4 a 1.6 ms parada y de 1.7 a 2ms sentido horario, desde ya agradesco mucho tu ayuda Gracias
Carlos Ramos
octubre 12, 2016 at 12:10 pmEs decir, ¿quieres tomar la señal que te da un componente para un servomotor y acoplarla a un motor de DC? ¿en qué lenguaje estás implementando el software de tu proyecto?
Eliseo Rojas
octubre 13, 2016 at 5:22 amHumm ahi esta el detalle, esta en idea y la verdad es que de ahi no tengo idea por donde empezar
Eliseo Rojas
octubre 13, 2016 at 5:25 amY peor aun no se casi nada de programación pero puedo aprender lo necesario debido a que esto ya es un reto
Daniel V
noviembre 6, 2016 at 9:25 amHola,
enhorabuena por este gran aporte. He implementado tu código (con algunas variaciones) para la Basys3 de Xilinx y funciona perfectamente. Tan solo tengo una pequeña duda respecto al servo, ¿en base a qué eliges que tu servo tiene 128 posiciones?
gracias de antemano
Carlos Ramos
noviembre 7, 2016 at 9:07 amDaniel, ¡muchas gracias por tu comentario!
Sobre tu pregunta, hay dos respuestas: 1) tú eres el diseñador, tú decides o 2) te pasan las especificaciones y trabajas con ellas.
En mi caso, tenía que trabajar con un servomotor en particular, así que en base a eso cree el componente para su frecuencia de operación y cantidad de posiciones.
Si no tuvieras un servomotor, tú como diseñador puedes ver si te enfocas en el rango más utilizado que le quede a más servomotores o si tienes un modelo en particular que quieras controlar.
Espero te sirva mi respuesta.
Daniel V
noviembre 13, 2016 at 9:17 amMuchas Gracias,
Trabaje sobre este servo.
http://www.servodatabase.com/servo/towerpro/sg90
Ajusté los parámetros correspondientes para que la PWM siguiera lo especificado por el fabricante.
Carlos Ramos
noviembre 14, 2016 at 9:07 am¡Uy! No es como si esa página te diera mucha información. En casos como ese tenemos el gran método por excelencia: prueba y error. Se siguen los lineamientos básicos:
– Si suena raro, ya te pasaste.
– Diseñas para lo mínimo necesario para tu aplicación (si necesitas ocho posiciones, diseñas para ocho).
Saber cuántas posiciones puede tomar es difícil, pero la experimentación te puede ir diciendo (sobre todo si tú alcanzas a percibir movimiento).
Espero que te diviertas, o te hayas divertido, jugando con el servo.
Carolina Ortiz
noviembre 10, 2016 at 5:10 pmHola, cuales son las especificaciones del servomotor que implementaron ( datos del servomotor) y como montaron la etapa de potencia.
Gracias
Carlos Ramos
noviembre 11, 2016 at 3:34 pmCarolina, la verdad es que ese módulo ya tiene como tres años que se realizó. Y, como no lo tengo documentado, no lo sé :D. Lo siento :(.
eduar
diciembre 11, 2016 at 11:35 pma que se deve este error?
Carlos Ramos
diciembre 12, 2016 at 9:01 amEduar, al parecer estás tratando de compilar el archivo de simulación como si lo quisieras usar para programar la tarjeta. Asegúrate que el archivo con código para simulación sea un banco de pruebas (o testbench).
Neil Yáñez
agosto 14, 2017 at 8:51 pmHola. Saludos, muy clara la explicación. ¿Deseo consultar sobre como se podría implementar por medio de programación el control del ángulo de un servomotor en VHDL, por medio del movimiento del movimiento de un potenciómetro? Siendo más exacto con el uso del accesorio Pmod Rotary Encoder.
Saludos desde Chile.
Luis
agosto 19, 2018 at 9:24 pm— Señal de salida para el servomotor.
servo <= '1' when (cnt < pwmi) else '0';
"La señal de salida estará activa solamente cuando el valor de la señal pwmi sea menor que el valor del contador."
no estaría activa cuando el valor de pwmi sea mayor que cnt, en vez de menor?
no sé si es un error.
nicolas ortiz moreno
noviembre 8, 2018 at 6:49 pmHola carlos muy interesante tu aporte, quisiera usar este código para generar dar 4 posiciones 0, 30, 60, 90 grados con una combinacion binaria de 00,10,01,11 respectivamente pero soy un poco novato en este tema de VHDL tenes algun ejemplo donde pueda basarme para desarrollar este ejemplo
Armando
mayo 14, 2019 at 10:42 amHola, tengo un proyecto el cual consta realizar una banda transportadora con un servomotor, no tengo mucha experiencia y aun tengo dudas de como empezar a programarlo espero me puedas ayudar. Muchas gracias.