Verilog Práctico
Capítulo 10. Pulse Width Modulation
2ª Parte. Servos
Continuamos con los tutoriales sobre PWM. Esta segunda parte está enfocada a su aplicación en Servos.
Introducción
Un servo es un motor de corriente continua que tiene la capacidad de ubicarse en cualquier posición dentro de su rango de operación (típicamente 0 - 180º) y mantenerse estable en dicha posición.
Lleva incorporado un sistema de regulación controlado mediante pulsos PWM a una frecuencia de 50 Hz y con un duty cycle de entre 1 y 2 milisegundos. Es decir, la posición del eje de un servo se controla con pulsos PWM donde 1 ms son 0º y 2 ms son 180º.
Variando el ancho del pulso controlamos la posición del eje, algo que aprendimos a hacer en el tutorial anterior.
Si bien es cierto que los datasheet indican que para variar la posición de un servo hay que aplicar pulsos PWM de entre 1 y 2 milisegundos, tras hacer varias pruebas he observado que el ancho del pulso varía en realidad entre 0.54 y 2.54 milisegundos, al menos con los servos Tower Pro SG90. Estas cifras pueden variar dependiendo del fabricante.
La imagen siguiente muestra un pulso de ancho 0.54 milisegundos que equivale a la posición 0º.
Aquí una captura de un ancho de pulso de 2.54 milisegundos, equivalente a 180º.
En las imágenes anteriores sólo se ve un pulso porque he reducido la base de tiempos para medir con precisión, pero en realidad estamos aplicando pulsos a una frecuencia de 50 Hz. Si aumentamos la base de tiempos, esto es lo que vemos:
Ejemplo 1. Control de posición manual con dos pulsadores
En el capítulo anterior vimos un ejemplo (el nº4) en el que se explica cómo generar pulsos PWM de ancho variable con dos pulsadores, uno para incrementar el ancho del pulso y otro para decrementarlo.
Pues bien, partiendo de ese código Verilog, vamos a introducir algunas modificaciones para adaptarlo al control de la posición de un servo. Veamos cómo.
Declaramos las entradas y salidas.
module pwm4servo (input clk, sw1, sw2, output servo);
Declaramos un registro counter para generar un reloj de 50 Hz y un registro position que es lo mismo que duty cycle en el ejemplo original.
reg [17:0] counter = 0;
reg [17:0] position = 18480;
inc_pos y dec_pos son dos variables intermedias para almacenar un tic simple, cada vez que accionamos uno de los pulsadores de la Alhambra. Mira el Capítulo 8 de esta serie para más info.
wire inc_pos, dec_pos;
tic switch1 (clk, sw1, inc_pos);
tic switch2 (clk, sw2, dec_pos);
Este bloque comprueba si accionamos los pulsadores de la Alhambra sw1 y sw2 y decrementa o incrementa la posición del servo dentro de unos límites máximo de 180º y mínimo de 0º.
always @(posedge clk)
begin
if (inc_pos == 1 && position <= 29280)
position <= position + 1200;
else if (dec_pos == 1 && position >= 7680)
position <= position - 1200;
end
Hagamos algunos cálculos. Si la Alhambra tiene un reloj de 12 Mhz significa que para contar 1 milisegundo necesita 12.000 ciclos de reloj, luego para contar 0.54 ms necesita 6480 ciclos y para 2.54 ms son necesarios 30.480 ciclos de reloj. Ver las capturas más arriba.
El valor de 1200 es el incremento de posición; cuanto queremos que se desplace el eje con cada pulsación. Si ponemos un valor muy bajo tendremos más precisión de movimientos, pero el eje necesitará más pulsaciones para completar un desplazamiento completo de 0 a 180º.
Al límite superior le he restado 1200 (30480 - 1200 = 29280) y al límite inferior le he sumado 1200 (6480 + 1200 = 7680) para dejar unos márgenes de seguridad. Estas cifras no están grabadas a fuego en el código Verilog, os invito a que hagáis pruebas con distintos valores para ver cómo afectan al funcionamiento del servo y luego pongáis las que se adapten a vuestras necesidades.
El siguiente bloque genera el reloj de 50 Hz, que es la frecuencia a la que funcionan los servos.
always @(posedge clk)
begin
if (counter < 240000) counter <= counter + 1;
else counter <= 0;
end
Esta instrucción genera los pulsos PWM, mira el capítulo anterior para ver cómo funciona.
assign servo = (counter < position) ? 1:0;
Tan sólo falta añadir el código para generar los tic de los pulsadores, ya vimos cómo funciona en el capítulo 8.
Lo ponemos todo junto y el código queda así:
module pwm4servo (input clk, sw1, sw2, output servo);
reg [17:0] counter = 0;
reg [17:0] position = 18480;
wire inc_pos, dec_pos;
tic switch1 (clk, sw1, inc_pos);
tic switch2 (clk, sw2, dec_pos);
always @(posedge clk)
begin
if (inc_pos == 1 && position <= 29280)
position <= position + 1200;
else if (dec_pos == 1 && position >= 7680)
position <= position - 1200;
end
always @(posedge clk)
begin
if (counter < 240000) counter <= counter + 1;
else counter <= 0;
end
assign servo = (counter < position) ? 1:0;
endmodule
// Modulo generador tic para los pulsadores sw1 y sw2
module tic (input clk, btn_in, output out);
reg d2;
reg r_in;
always @(posedge clk)
d2 <= btn_in;
always @(posedge clk)
r_in <= d2;
reg btn_prev = 0;
reg btn_out = 0;
reg [16:0] counter = 0;
always @(posedge clk) begin
if (btn_prev ^ r_in == 1'b1) begin
counter <= 0;
btn_prev <= r_in;
end
else if (counter[16] == 1'b0)
counter <= counter + 1;
else
btn_out <= btn_prev;
end
//-- Generar tic en flanco de subida del boton
reg old;
always @(posedge clk)
old <= btn_out;
assign out = !old & btn_out;
endmodule
Archivo .pcf
set_io sw1 10
set_io sw2 11
set_io clk 21
set_io servo 112
Lo subimos a la Alhambra y lo que vemos es una señal PWM que aumenta cuando pulsamos sw1 y disminuye cuando pulsamos sw2. Esta señal PWM variable aplicada en el pin de control del servo, se traduce en un aumento / disminución de la posición del eje.
El siguiente vídeo ilustra lo que acabamos de explicar. El servo de la derecha, un MG90 gira en sentido contrario, esto depende de cada fabricante.
Ejemplo 2. Servo a dos posiciones
En este ejemplo veremos cómo mover el eje del servo a dos posiciones predefinidas cualesquiera.
Este bloque detecta cuando pulsamos el sw1 y asigna a la variable position un valor u otro. En este caso he puesto los valores 6480 que equivalen a 0º y 30480 a 180º.
always @(posedge clk)
begin
if (sw1 == 1)
position <= 6480;
else
position <= 30480;
end
El código Verilog queda así:
module servo_2_pos (input clk, sw1, output servo);
reg [17:0] counter = 0;
reg [17:0] position = 18480;
always @(posedge clk)
begin
if (sw1 == 1)
position <= 6480;
else
position <= 30480;
end
// 50 Hz clk
always @(posedge clk)
begin
if (counter < 240000) counter <= counter + 1;
else counter <= 0;
end
assign servo = (counter < position) ? 1:0;
endmodule
Archivo .pcf
set_io sw1 10
set_io clk 21
set_io servo 112
Lo subimos a la Alhambra y esto es lo que vemos: en reposo el pulsador no está accionado, por lo que el ancho del pulso es 30480 = 2.54 ms = 180º. Cuando pulsamos sw1 asignamos a la variable position el valor 6480 = 0.54 ms = 0º.
Ejemplo 3. Servo a “n” posiciones, modo manual.
Igual que en el ejemplo anterior, podemos hacer que el eje del servo se mueva a múltiples posiciones predefinidas, dependiendo de determinadas condiciones.
Por ejemplo con los dos pulsadores de la Alhambra podemos simular 4 condiciones diferentes y asignar a cada condición una posición del servo.
Definimos una entrada input [1:0] switches que es un array de dos bits donde se almacena el estado de los pulsadores sw1 y sw2 de la Alhambra.
module servo_2_pos (input clk, input [1:0] switches, output servo);
Con una estructura case (switches) evaluamos el estado de los pulsadores y asignamos a la variable position un valor predefinido.
always @(posedge clk)
begin
case (switches)
2'b00: position <= 6480; // 0 grados
2'b01: position <= 12480; // 45 grados
2'b10: position <= 18480; // 90 grados
2'b11: position <= 30480; // 180 grados
endcase
end
Código Verilog
module servo_n_pos (input clk, input [1:0] switches, output servo);
reg [17:0] counter = 0;
reg [17:0] position = 18480;
always @(posedge clk)
begin
case (switches)
2'b00: position <= 6480; // 0 grados
2'b01: position <= 12480; // 45 grados
2'b10: position <= 18480; // 90 grados
2'b11: position <= 30480; // 180 grados
endcase
end
// 50 Hz clk
always @(posedge clk)
begin
if (counter < 240000) counter <= counter + 1;
else counter <= 0;
end
assign servo = (counter < position) ? 1:0;
endmodule
Archivo .pcf
set_io switches[0] 10
set_io switches[1] 11
set_io clk 21
set_io servo 112
Lo subimos a la Alhambra y esto es lo que vemos: sin pulsar ningún botón, el servo apunta a 0º, si pulsamos sw1 apunta a 45º, si pulsamos sw2 apunta a 90º y si pulsamos ambos, la posición es 180º.
Ejemplo 4. Múltiples Servos a “n” posiciones, modo automático
Podemos automatizar el ejemplo anterior, de manera que el servo se vaya moviendo de una posición a la siguiente, sin intervención humana.
Ya vimos algo parecido en el Capítulo 4 cuando utilizamos un prescaler para automatizar el proceso de un contador y mostrarlo en un display de 7 segmentos.
Y para añadir un poco más de dificultad al ejercicio, vamos a conectar un segundo servo y que cada uno se mueva a distintas posiciones.
Lo ponemos todo junto y el código Verilog queda así:
module servo_2_pos (input clk, output servo1, servo2);
//-- Registro para implementar contador de N bits
reg [23:0] prescaler = 0;
//-- El bit más significativo se saca por la salida
assign clk_out = prescaler[22];
reg [17:0] counter = 0;
reg [17:0] position1 = 18480;
reg [17:0] position2 = 18480;
always @(posedge clk)
begin
prescaler <= prescaler + 1;
end
// en este registro se almacena el valor del contador
reg [1:0] contador;
always @(posedge clk_out)
begin
contador <= contador + 1;
// comprobamos el contador
// cuando llega a 3 lo reseteamos
if (contador >= 2'd3)
contador <= 2'd0;
case (contador)
2'b00: position2 <= 30480; // 180º
2'b01: position2 <= 6480; // 0º
2'b10: position2 <= 24480; // 135º
2'b11: position2 <= 12480; // 45º
endcase
case (contador)
2'b00: position1 <= 6480; // 0º
2'b01: position1 <= 12480; // 45º
2'b10: position1 <= 24480; // 135º
2'b11: position1 <= 30480; // 180º
endcase
end
// 50 Hz clk
always @(posedge clk)
begin
if (counter < 240000) counter <= counter + 1;
else counter <= 0;
end
assign servo1 = (counter < position1) ? 1:0;
assign servo2 = (counter < position2) ? 1:0;
endmodule
Archivo .pcf
set_io clk 21
set_io servo1 112
set_io servo2 119
Lo subimos a la Alhambra y vemos como los dos servos se mueven a sus posiciones predefinidas de manera automática.
Ejemplo 5. Múltiples Servos a “n” posiciones con distinto reloj
Sucede que no es necesario que los servos se muevan de manera sincronizada ¡cada uno puede llevar su propio reloj!
Podríamos declarar dos prescaler (o asignar dos bit diferentes del mismo prescaler) y obtener dos señales de reloj distintas y hacer que los movimientos de cada servo se sincronicen con procesos diferentes ¡que pasada!
Para independizar ambos servos tenemos que duplicar algunas señales, como los relojes, los contadores y los registros donde se almacena la posición.
El código Verilog queda así:
module servo_n_pos (input clk, output servo1, servo2);
reg [23:0] prescaler = 0;
assign clk1 = prescaler[22];
assign clk2 = prescaler[23];
reg [17:0] counter = 0;
reg [17:0] position1 = 18480;
reg [17:0] position2 = 18480;
always @(posedge clk)
begin
prescaler <= prescaler + 1;
end
reg [1:0] contador1;
always @ (posedge clk1) begin
contador1 <= contador1 + 1;
// comprobamos el contador
// cuando llega a 3 lo reseteamos
if (contador1 >= 2'd3)
contador1 <= 2'd0;
case (contador1)
2'b00: position1 <= 30480; // 180º
2'b01: position1 <= 6480; // 0º
2'b10: position1 <= 24480; // 135º
2'b11: position1 <= 12480; // 45º
endcase
end
reg [1:0] contador2;
always @ (posedge clk2) begin
contador2 <= contador2 + 1;
// comprobamos el contador
// cuando llega a 3 lo reseteamos
if (contador2 >= 2'd3)
contador2 <= 2'd0;
case (contador2)
2'b00: position2 <= 6480; // 0º
2'b01: position2 <= 12480; // 45º
2'b10: position2 <= 24480; // 135º
2'b11: position2 <= 30480; // 180º
endcase
end
// 50 Hz clk
always @(posedge clk)
begin
if (counter < 240000) counter <= counter + 1;
else counter <= 0;
end
assign servo1 = (counter < position1) ? 1:0;
assign servo2 = (counter < position2) ? 1:0;
endmodule
Archivo .pcf
set_io clk 21
set_io servo1 112
set_io servo2 119
Lo subimos a la Alhambra y vemos como los dos servos se mueven a sus posiciones predefinidas cada uno sincronizado con una señal de reloj diferente.
Y todavía podemos ir más allá. Podríamos conectar un servo en cada pin de la Alhambra, todos funcionando en paralelo, cada uno con su propia señal de reloj. Esta es una de las grandes ventajas de las FPGAs.
Ejemplo 6. Servo swap
En este ejemplo veremos cómo hacer un movimiento continuo de vaivén.
Declaramos las entradas y salidas. El led nos indica si se mueve en sentido horario u antihorario, únicamente tiene propósito informativo, se puede eliminar si se desea.
module servo_swap (input clk, output led, servo);
La señal toggle indica si el servo se mueve en un sentido u otro.
reg toggle = 1;
Cuando llegamos a uno de los límites, cambiamos el valor de la señal toggle.
if (position >= 30480) toggle <= 0;
if (position <= 6480) toggle <= 1;
Cada vez que el contador pasa por 0, evaluamos la señal toggle, si vale 0 decrementamos la posición, si vale 1 la incrementamos.
if (counter == 0)
begin
if (toggle == 0) position <= position - 120; // velocidad
if (toggle == 1) position <= position + 120; // velocidad
end
end
Lo ponemos todo junto y el código Verilog queda así:
// Verilog code for servo swap
module servo_swap (input clk, output led, servo);
reg [17:0] counter;
reg [15:0] position = 12480; // posición inicial
reg toggle = 1;
always @ (posedge clk)
begin
if (position >= 30480) toggle <= 0;
if (position <= 6480) toggle <= 1;
if (counter == 0)
begin
if (toggle == 0) position <= position - 50; // velocidad
if (toggle == 1) position <= position + 50; // velocidad
end
end
// reloj 50 Hz
always @(posedge clk)
begin
if (counter < 240000) counter <= counter + 1;
else counter <= 0;
end
assign servo = (counter < position) ? 1:0;
assign led = toggle;
endmodule
Archivo .pcf
set_io clk 21
set_io servo 112
set_io led 95
Lo subimos a la Alhambra y vemos como el servo se mueve de un lado a otro de manera continua.
Eso es todo, espero que os haya gustado
¡Hasta pronto!
Links
How to code Verilog for a servo
Descargas
Descarga el código Verilog de los ejemplos vistos en este tutorial aquí abajo.