Por Marcelo Valdéz, enero 2003
Demoras para número de ciclos que sean potencias de 2
Demoras de un número de ciclos que no es potencia de 2
Demoras de más de 31 ciclos
1. El bucle de 3 ciclos
2. El bucle de 4 ciclos
Rutinas de demora utilizando contador de 2 bytes
Demoras para número de ciclos que sean potencias de 2:
Utilizando las instrucciones nop, goto, call y return, se pueden generar demoras exactas de hasta 31 ciclos, sin modificar el registro de trabajo w, ni el registro de estado STATUS. Tampoco es necesario utilizar variables RAM (GPR).
1. Delay de 1 ciclo (1us @ 4MHz)
Simplemente, utilizar la instrucción "nop" (consumo: 1 instrucción):
nop [1]
2. Delay de 2 ciclos (2us @ 4MHz)
La forma más económica es utilizar "goto $ + 1" (el goto siempre requiere 2 ciclos, consumo: 1 instrucción):
goto $ + 1 [2]
3. Delay de 4 ciclos
Hacer un llamado a una rutina que simplemente retorna (consumo: 2 instrucciones):
call DEMORA4 [2] . . . DEMORA4: return [2] {4}
4. Delay de 8 ciclos
Hacer un llamado a una rutina que a su vez llama a la rutina de 4 ciclos (consumo: 3 instrucciones):
call DEMORA8 [2] . . . DEMORA8: call DEMORA4 [4] {6} DEMORA4: return [2] {8}
5. Delay de 16 ciclos (16us @ 4MHz)
Hacer un llamado a una rutina que llama a su vez a la demora de 8 ciclos y luego continúa con la demora de 4 ciclos (consumo: 4 instrucciones):
DELAY: call DEMORA16 [2] STACK = DELAY . . . DEMORA16: call DEMORA8 [8] {10} STACK = DEMORA16 / DELAY DEMORA8: call DEMORA4 [4] {14} STACK = DEMORA8 / DEMORA16 / DELAY DEMORA4: return [2] {16} STACK = DEMORA16 / DELAY
Demoras de un número de ciclos que no es potencia de 2:
Se generan simplemente componiendo en forma binaria las demoras de 1, 2, 4, 8 y 16 ciclos, para obtener cualquier retardo entre 1 y 31 ciclos. No es conveniente continuar con este método más allá de los 31 ciclos, ya que se gastan demasiados niveles del Stack (que tiene solamente 8), y pueden generarse overflows.
Por ejemplo, para un Delay de 13 ciclos, hay que hacer una llamada y regreso a una demora de 9 ciclos (se pierden 4 ciclos por el call y el return). La demora de 9 ciclos se logra mediante una llamada a DEMORA8 y un nop (consumo: 6 instrucciones):
call DEMORA13 [2] . . . DEMORA13: call DEMORA8 [8] {10} nop [1] {11} return [2] {13} DEMORA8: call DEMORA4 DEMORA4: return
Demoras de más de 31 ciclos
Antes voy a definir los bucles simples de tres y cuatro ciclos:
Bucle de 3 ciclos:
decfsz Dlay, f [1*(Dlay-1) + 2*1] ([falso + verdadero]) goto $ - 1 [2*(Dlay-1) + 0]
La primera instrucción se repite (Dlay-1) veces con salida por falso (instrucción de 1 ciclo), y una vez como verdadero (instrucción de 2 ciclos), el tiempo total consumido por esta instrucción será de:
Dlay - 1 + 2 = Dlay + 1
La segunda instrucción se ejecuta unicamente (Dlay-1) veces, gastando 2 ciclos por pasada, por lo que el total consumido en ciclos es de:
2*(Dlay - 1) = 2*Dlay - 2
La demora total del bucle de 3 ciclos será:
Dlay + 1 + 2*Dlay - 2 = 3*Dlay - 1
Si se tiene en cuenta la instrucción utilizada para cargar algún valor en la variable Dlay, el número de ciclos de este bucle simple, será de 3*Dlay. Por eso lo denomino "Bucle de 3 ciclos"
Bucle de 4 ciclos:
addlw -1 [1*Dlay] Skpz [1*(Dlay-1) + 2*1] goto $ - 2 [2*(Dlay-1) + 0]
Como se ve, el bucle de 4 ciclos no utiliza variables RAM, ya que solamente utiliza el acumulador.
La primera instrucción se repite exactamente Dlay veces.
total = Dlay
La segunda instrucción se ejecuta (Dlay-1) veces con salida por FALSO (usando 1 ciclo), y una sola vez con salida por VERDADERO (usando 2 ciclos). El total de ciclos gastados al finalizar el bucle, será de:
(Dlay - 1) + 2 = Dlay + 1
La tercera instrucción solamente se ejecuta Dlay-1 veces, usando 2 ciclos por pasada. El total consumido en ciclos será:
2*Dlay - 2
La demora total del bucle será de:
Dlay + (Dlay + 1) + (2*Dlay - 2) = 4*Dlay - 1
Teniendo en cuenta el ciclo gastado en cargar el acumulador con un determinado valor, la demora generada es de 4*Dlay ciclos. Por eso es que lo he llamado "bucle de 4 ciclos"
Rutinas de demora utilizando el bucle de 3 ciclos
(12 a 13 instrucciones)
Para poder utilizar el bucle de 3 ciclos, es necesario cargar la variable Dlay con el valor adecuado, para ello deberá utilizarse el registro de trabajo W (dos instrucciones)
Para preservar el valor de W y el estado de STATUS (que será modificado automáticamente al ejecutar la instrucción decfsz), será necesario salvarlos al ingresar a la rutina, y recuperarlo al final (7 instrucciones):
DEMORA3C: [2] movwf _W [1] movf STATUS, W [1] movwf _STATUS [1] movlw n [1] movwf Dlay [1] decfsz Dlay, f [(Dlay-1)*1 + 1*2] goto $ - 1 [(Dlay-1)*2 + 0] movf _STATUS, W [1] movwf STATUS [1] swapf _W, f [1] swapf _W, W [1] return [2]
La cantidad total de ciclos en este caso es de:
T = 7+ (Dlay - 1 + 2) + (2*Dlay - 2) + 6
T = 3*n + 12
Fijado T, el valor inicial a cargar en Dlay (n = Delay inicial) resulta ser:
n= (T - 12) / 3
n = (35 - 12) / 3 = 23 / 3 = 7 + 2/3
Lo que indica que se debe cargar 7 en Dlay, y agregar un delay de 2 ciclos (goto) fuera del bucle:
DEMORA35: [2] movwf _W [1] movf STATUS, W [1] movwf _STATUS [1] movlw 7 [1] movwf Dlay [1] {7} decfsz Dlay, f [(7-1)*1 + 1*2] = [8] {15} goto $ - 1 [(7-1)*2 + 0] = [12] {27} goto $ + 1 [2] {29} movf _STATUS, W [1] movwf STATUS [1] swapf _W, f [1] swapf _W, W [1] return [2] {35}![]()
Rutinas de demora utilizando el bucle de 4 ciclos
(12 a 14 instrucciones)
Hay que agregar las instrucciones para inicializar el acumulador (1 instrucción), y para salvar y recuperar los registros W y STATUS (7 instrucciones):
DEMORA4C: [2] movwf _W [1] movf STATUS, W [1] movwf _STATUS [1] movlw n [1] addlw -1 [n] Skpz [(n-1)*1 + 1*2] = [n + 1] goto $ - 2 [(n-1)*2 + 0] = [2n - 2] movf _STATUS, W [1] movwf STATUS [1] swapf _W, f [1] swapf _W, W [1] return [2]
La cantidad total de ciclos en este caso es de:
T = 6 + n + (n + 1) + (2n - 2) + 6
T = 4*n + 11
Fijado T, el valor inicial a cargar en W (n = W inicial) resulta ser:
n= (T - 11) / 4
n = (38 - 11) / 4= 27 / 4= 6 + 3/4
Lo que indica que se debe cargar 6 en Dlay, y agregar un delay de 3 ciclos (goto + nop) fuera del bucle:
DEMORA38: [2] movwf _W [1] {3} movf STATUS, W [1] {4} movwf _STATUS [1] movlw 6 [1] addlw -1 [6] {12} Skpz [6 + 1] = [7] {19} goto $ - 2 [2*6 - 2] = [10] {29} goto $ + 1 [2] {31} nop [1] {32} movf _STATUS, W [1] movwf STATUS [1] swapf _W, f [1] swapf _W, W [1] return [2] {38}![]()
Rutinas de demora utilizando contador de 2 bytes
(12 a 14 instrucciones)
Es necesario reservar espacio para una variable RAM de 2 bytes, de la siguiente forma:
Para módulo no linkeable:
CBLOCK 000C
Dlay: 2
ENDC
Para módulo linkeable:
GLOBAL Dlay
UDATA
Dlay RES 2