Verilog Práctico

Capítulo 10. Pulse Width Modulation

2ª Parte. Servos

24 de abril de 2021

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

 

ServoBit

 

How to code Verilog for a servo

 

Descargas

 

Descarga el código Verilog de los ejemplos vistos en este tutorial aquí abajo.