Resumen
Se propone resolver la cuestión de la realización de retardos en microcontroladores PIC por medio de un programa que genera el código necesario.
Introducción
En el mundo de la programación de microcontroladores, a veces ocurre que debemos crear un bucle de espera o retardo en el código, quizás porque necesitamos sincronizar el sistema con un sistema externo o con el mundo físico, o simplemente porque sabemos que no necesitamos que el sistema atienda ninguna actuación externa durante un tiempo, o porque el sistema externo necesita de una actuación precisa por parte del sistema microcontrolador.
En la mayor parte de las ocasiones, ese tipo de esperas se puede realizar con:
- los temporizadores internos
- esperando en un bucle, leyendo las entradas hasta que se produzca un evento (un temporizador externo o la acción de un actuador externo)
- poner el controlador en modo SLEEP. El microcontrolador se despertará cuando ocurra un evento o interrupción
Justificación
Pero no siempre es así. Hay ocasiones en las que necesitamos esperar un tiempo muy determinado o un número exacto de ciclos.
Los retardos se suelen crear como bucles sencillos:
Using asm Syntax Highlighting
- movlw 0x42 ; 1 ciclo
- movwf d1 ; 1 ciclo
- Delay_0
- decfsz d1, f ; 1 ciclo
- goto Delay_0 ; 2 ciclos
Coloreado en 0.006 segundos, usando GeSHi 1.0.8.4
Explicación
Pero no es tan sencillo, ya que:
- el periodo de un ciclo depende de la frecuencia de reloj del microcontrolador
- cada vuelta decrementado-goto consume un número de ciclos fijo, con lo que a veces no se obtiene un resultado exacto
Echando cuentas: 2 + 0x41 * 3 + 2 = 199 ciclos.
Si quisiéramos tener exactamente 200 ciclos, estamos obligados a añadir un 'nop' al final (1 ciclo de instrucción más).
La cosa se complica si queremos tiempos de espera mayores. En esos casos, se hacen bucles anidados. Así, los ciclos consumidos por los bucles más profundos son multiplicados por los exteriores. Además, queremos que esos bucles internos consuman aún más ciclos que un ciclo normal, ya que queremos realizar una espera mucho más larga.
Una posibilidad es la de encadenar los goto de cada bucle anidado, haciendo que se sume 2 ciclos en cada salto. Ejemplo para tres bucles anidados:
Using asm Syntax Highlighting
- movlw 0x91 ; 1 ciclo
- movwf d1 ; 1 ciclo
- movlw 0xFC ; 1 ciclo
- movwf d2 ; 1 ciclo
- movlw 0xDA ; 1 ciclo
- movwf d3 ; 1 ciclo
- Delay_0
- decfsz d1, f ; 1 ciclo
- goto $+2 ; 2 ciclos
- decfsz d2, f ; 1 ciclo
- goto $+2 ; 2 ciclos
- decfsz d3, f ; 1 ciclo
- goto Delay_0 ; 2 ciclos
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4
Además, cuando 'd1' agota su valor inicial, en la siguiente vuelta (impuesta por la siguiente vuelta de 'd2'), comenzará a decrementar desde '0', por lo que realizará 255 vueltas consumiendo 7 ciclos (más una vuelta final, de 2 ciclos).
El cálculo de los ciclos generados es el siguiente:
<ciclos generados por N niveles de anidamiento>
= <ciclos para la inicialización de N niveles>
+ <ciclos generados por las últimas vueltas de los bucles>
+ <ciclos generados por el bucle más interno>
* ∑(i=1,i=N){ 256^(i-1) * (di-1) }
= 2 * N + 2 * N + (2 * N + 1) * ∑(i=1,i=N){ 256^(i-1) * (di-1) }
= 2 * 2 * N + (2 * N + 1) * ∑(i=1,i=N){ 256^(i-1) * (di-1) }
Este cálculo no siempre es exacto con respecto a la espera que se desea: se obtienen números de ciclo que son múltiplos de los <ciclos generados por el bucle más interno>. Eso implica que a veces faltan (<ciclos generados por el bucle más interno> - 1) para llegar a la cifra correcta.
Esto se resuelve con el añadido de instrucciones adicionales, después de los bucles anidados. Poniendo una instrucción de goto, tenemos 2 ciclos más. Y poniendo una instrucción nop, 1 ciclo más.
El cálculo de los parámetros 'di' es el siguiente:
Primero se va probando para N=1 (un solo bucle), y si la cantidad de ciclos generada no es suficiente, se repite el cálculo para N=2, y así sucesivamente.ciclos := <número_de_ciclos_que_deseamos_generar> - (2 * 2 * N)
loop := int(ciclos / (1 + 2 * N))
Para i=1 a N:
di := 1 + (int(loop/(256^(i-1))) % 256)
:Fin Para
ciclos_restantes_al_final := ciclos - loop * (1 + 2 * N)
Programa
Para facilitar la tarea del cálculo, se muestra el siguiente programa escrito en Perl, que genera un salida en texto con el código fuente en ensamblador que produce la espera deseada.
Como argumentos, se indicará la frecuencia de trabajo de la señal de reloj del microcontrolador, y la espera deseada, expresada tanto en forma de tiempo como en forma de ciclos de instrucción. Opcionalmente, se puede indicar argumento '-s' seguido de un nombre, y se generará el mismo código, pero ajustado para que sea incorporado al programa principal en forma de subrutina.
Ejemplos de llamada:
./delay_pic.pl -freq 4mhz -delay 1s
./delay_pic.pl -frec 32768hz -espera 200ms
./delay_pic.pl -f 20000000 -d 1000000
./delay_pic.pl 20mhz 500us
./delay_pic.pl -sub Wait_4µs 64Mhz 4us
El código se encuentra en Github: https://github.com/joaquinferrero/delay_pic
Ejemplo de salida:
Using bash Syntax Highlighting
- $ ./delay_pic.pl -freq 4mhz -delay 779 -v -s
- Generador de bucles de espera en ensamblador PIC
- Joaquín Ferrero (alias explorer). 2014.10.09
- ; ----------------------------------------------------------------------------------------------------
- ; Espera = 779 ciclos de instrucción
- ; Frecuencia de reloj = 4mhz
- ;
- ; Espera real = 0.000779 segundos = 779 ciclos
- ; Error = 0.00 %
- cblock 0x70
- Espera_d1
- Espera_d2
- endc
- Espera:
- ;773 ciclos
- movlw 0x9A
- movwf Espera_d1
- movlw 0x01
- movwf Espera_d2
- Espera_loop:
- decfsz Espera_d1, f
- goto $+2
- decfsz Espera_d2, f
- goto Espera_loop
- ;2 ciclos
- goto $+1
- ;4 ciclos (incluyendo la llamada)
- return
- ; Generado por delay_pic.pl (Joaquín Ferrero. 2014.10.09) jue 09 oct 2014 00:02:15 CEST
- ; ./delay_pic.pl -freq 4mhz -delay 779 -v -s
- ; generador-de-codigos-de-retardo-para-microcontroladores-pic-t8602.html
- ; ----------------------------------------------------------------------------------------------------
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4
La salida es -prácticamente- igual al generador de Golovchenko. Hay algunas diferencias: aparte del idioma, este programa genera siempre una solución basada en bucles, a diferencia del de Golovchenko, en que en ocasiones, genera ciclos por medio de más sentencias goto $+1 extras (ejecutarlo para 779 ciclos de espera, a 4 Mhz, por ejemplo).