Este hilo es la continuación Manejando un servo: Conocimientos previos .
Propondré una posible implementación en ensamblador del 8052 para controlar un servo por PWM.
En primer lugar haremos un breve recordatorio del funcionamiento de los timers en general.
Un timer es un contador interno de ciclos. Funcionan incrementando en una unidad el valor de un registro por cada ciclo de instrucción. Cuando llegan al máximo provocan un overflow que suele generar una petición de interrupción.
En 8052 nos proporciona 3 timers: T0, T1 y T2, aunque para este ejemplo sólamente vamos a usar T0 y T1.
Cada timer se compone de los siguientes registros:
- TH0 y TL0: Ya que los timers se pueden usar en modo 8 bit y modo 16 bits posee dos registros de 8 bits que almacenaran el valor del contador en cada instante.
- TCON: Registro global para todos los timers. Su nombre es Timer Control. En el nos encontraremos 4 bits significativos:
 
- TF0, TF1, TR0, TR1. Los bits TF0 y TR0 se refieren al T0 y TF1 y TR1 al T1 consecutivamente. TRx sirve para poner a contar el timer y TFx para ver si ha provocado overflow.
- TMOD: Tambien es globar para todos los timers. Su nombre es Timer Mode. Se usa para definir el comportamiento del timer.
Cada timer tiene tres modos de funcionamiento posibles:
- Modo 13 bit: El contador funciona en modo 13 bit, por lo que podremos contar de 0 a 2^13 ciclos de instruccion. Este modo no se suele usar.
 
- Modo 16 bit: Lo mismo pero en 16 bitm por lo que tendremos 2^16 que son 65536 ciclos de instruccion.
- Modo 8 bit con autoreload: Es un timer con autorecarga. En el THx colocamos el valor con el que queremos que se recargue. Entonce será el TLx quien se ira incrementando en cada ciclo de instrucción, y cuando provoque overflow, se recargará automáticamente con el valor contenido en THx.
- Modo Split: Tampoco lo usaremos pero sirve para crear dos timers separados de 8 bit con uno solo.
En este ejemplo vamos a usar dos timers de 16 bits.
Recordando el hilo anterior, para controlar el motor servo queríamos generar un pulso de 50hz, con el que variando el ancho de pulso controlaríamos la dirección del motor.
Como el pulso es de 50hz, tenemos 1/50 = 20ms. Necesitamos generar un evento periódico de 20 ms por lo que usaremos el Timer0.
Primero tenemos que calcular el número de ciclos de instrucción que tendrán que transcurrir para que hayan pasado 20 ms.
El 8052 ejecuta 1 ciclo de instrucción por cada 12 ciclos de reloj. De esta forma si tenemos un reloj a 12 Mhz, tendremos un ciclo de instruccion de(12 * 10^6) /12 = 1 * 10 ^ 6. La frecuencia es la inversa del periodo por lo que ejecutará: 1 ciclo de instrucción cada 1 ps.Con esto ya sabemos que si queremos que transcurran 20 ms, tendrán que transcurrir
- 20.000 ciclos de instrucción.
 
Recordaré muy brevemente el funcionamiento mediante interrupciones:
Cuando sucede un determinado evento que requiere un tratamiento especialcomo una division por 0, desbordamiento en el registro del timer o interrupcion externa, la cpu identifica este evento y salta a ejecutar una rutina especifica. Esta rutina tiene el nombre de RTI o "rutina de tratamiento de interrupcion".
Esto nos interesa ya que la forma de contar los 20ms será inicializando el contador con un valor determinado para que produzca desbordamiento justo cuando hayan transcurrido los 20ms.
Como vamos a usar un timer de 16 bits tendremos hasta 0xFFFF o 65.536. El timer se autoincrementa en una unidad en cada ciclo de instrucción por lo que si queremos que ocurra un overflow tras 20.000 ciclos de instrucción, tendremos que inicializar el contador con el valor:
65.536 - 20.000 = 45.536.
Si queremos que el evento ocurra de forma periódica tendremos que reiniciarlo con ese mismo valor justo en el instante en que entremos en la RTI.
Ahora pondremos a nivel alto la señal del servo.
Recordando el tema de los pulsos teníamos que según la dirección, el tiempo a nivel alto sería:
- 0.5 ms para izquierda
- 1.5 ms para centro
- 2.5 ms para derecha
 
- Izquierda: 65536 - 500
- Derecha: 65536 - 2500
- Centro: 65536 - 1500
 
Al entrar a timer 1 pondremos a nivel bajo la señal referente al servo y se desactivará a si mismo (timer 1).
De esta forma hemos conseguido generar el pulso periódico en función de la dirección.
El programa a continuación propone una implementación muy elemental:
----------------------------
SERVOSIMPLE.ASM
               sEnC
GPL
-----------------------------
MOTOR_01        EQU    P2.0        ;Pin asignado al servo
MOTOR_01_STATUS        BIT    01H     ; Bit de estado (1 = On, 0 = Off)
MOTOR_01_DIRECCION    BIT    00AH    ; Bit de direccion (1 = Izq, 2 = Der)
;Constantes de tiempo
TIME_MOTOR_20            EQU    45536    ; 65536 - 20000
TIME_MOTOR_LEFT            EQU    65036
TIME_MOTOR_RIGHT        EQU    63036
TIME_MOTOR_STOP            EQU    64036
ORG 0000H
JMP    START
ORG 000BH
LJMP    RTI_TIMER0
ORG 001Bh
LJMP    RTI_TIMER1
RTI_TIMER0:
PUSH    ACC
PUSH    PSW
MOV    TH0,#HIGH TIME_MOTOR_20    ;
MOV    TL0,#LOW TIME_MOTOR_20      ;
SETB    MOTOR_01        ; Ponemos a nivel alto la señal del motor
;Salto en funcion de los bits de estado
JB    MOTOR_01_STATUS, RTI_TIMER0_MOVE
JMP    RTI_MOTOR_STOP
RTI_TIMER0_MOVE:
JB    MOTOR_01_DIRECCION,RTI_MOTOR_LEFT
JMP    RTI_MOTOR_RIGHT
RTI_MOTOR_LEFT:        MOV    TH1,#HIGH TIME_MOTOR_LEFT
       MOV    TL1,#LOW TIME_MOTOR_LEFT
       SETB    TR1
       LJMP    RTI_MOTOR_EXIT
RTI_MOTOR_STOP:        MOV    TH1,#HIGH TIME_MOTOR_STOP
       MOV    TL1,#LOW TIME_MOTOR_STOP
       SETB    TR1
       LJMP    RTI_MOTOR_EXIT
RTI_MOTOR_RIGHT:    MOV    TH1,#HIGH TIME_MOTOR_RIGHT
       MOV    TL1,#LOW TIME_MOTOR_RIGHT
       SETB    TR1
RTI_MOTOR_EXIT:        SETB    ET1
       POP    PSW
       POP    ACC
       RETI
RTI_TIMER1:    PUSH    ACC
   PUSH    PSW
   CLR    MOTOR_01    ; Ponemos a nivel bajo la señal del motor
   CLR    ET1
   CLR    TR1
   POP    PSW
   POP    ACC
   RETI
START:
;Estado del motor
       SETB    MOTOR_01_STATUS ;Encendido
       CLR    MOTOR_01_DIRECCION ;Izquierda
;Inicializacion del contador e interrupciones
       SETB    ET0
       MOV    TL0,#LOW TIME_MOTOR_20
       MOV    TH0,#HIGH TIME_MOTOR_20
       MOV    TMOD,#011H
       SETB EA                ; Perm
       SETB    TR0
       SETB    ET0
;Bucle infinito
ETI:        NOP
   JMP    ETI
   END
Observaciones:
No estamos contando el tiempo que transcurre entre que entramos en la RTI hasta que iniciamos los contadores.
En el próximo hilo propondré una solución a este problema, y además podrá manejar más de un servo en cualquier estado simultaneamente.