Implementación de un reloj de tiempo real en el MSP430F149 Los relojes de tiempo real (eal Time Clocks) son utilizados en diversas aplicaciones: eventos generadores de marcas de tiempo, generación de eventos periódicos, etc. El implementar un TC con el microcontrolador MSP430 simplifica el diseño de sistemas y reduce costos, ya que no es necesario contar con un dispositivo dedicado que realice esta función. En esta implementación, se utiliza el Timer A del MSP430 para implementar un reloj de tiempo real. Este timer cuenta con 3 registros de comparación/captura, y utiliza como señal de reloj al ACLK del microcontrolador. Generación de la señal de reloj Todos los microcontroladores de la familia MSP430 contienen un oscilador tipo C controlado digitalmente (DCO) y un oscilador de cristal. En el caso de la tarjeta de desarrollo Easyweb se cuenta con un oscilador de cristal de 8 MHz. El DCO es utilizado usualmente como reloj de la CPU, y el oscilador de cristal es usado frecuentemente como reloj para los periféricos; en este caso como la fuente de reloj para el Timer A, que entrega a su vez la base de tiempo requerida para la implementación. De este modo, los problemas de inestabilidad del oscilador tipo C no afectan la precisión de la implementación. Implementación del TC La implementación consiste en la configuración del Timer A como fuente de interrupciones periódicas (cada 1 segundo) y una pequeña rutina de CPU que cuenta las interrupciones actualizando y desplegando en la LCD la cuenta del TC. La CPU puede dormir o realizar otras funciones entre las interrupciones (ver código anexo). La inicialización de los osciladores permite configurar el DCO como reloj de la CPU (8 MHz) y el oscilador de cristal como señal de reloj del ACLK, a una frecuencia de 1 MHz (8 MHz dividido por 8, según configuración del módulo básico de reloj). La inicialización del Timer A realiza la selección del ACLK como fuente de reloj (dividida nuevamente por 8 queda en 125 khz), para lograr finalmente una frecuencia de generación de interrupciones de 1 Hz mediante el uso del modo up-down del Timer A: en el modo comparación, el timer cuenta hasta el valor del registro TACC0, previamente seteado en 62.500. La rutina de servicio de interrupciones manipula los bits del status register que fueron colocados en el stack justo antes de ingresar a ella. En assembler, esto puede ser realizado mediante el código: BIC #LPM3,0(SP) ; ETI Clear S LPM3 Bits, on top of stack
En C no es posible acceder al stack de una forma directa, por lo que se incorpora un llamado a una rutina externa, escrita en assembler y que permite cambiar entre los distintos modos de operación que tiene el MSP430 (en este caso, cambiar entre el estado activo y el estado LPM3, de bajo consumo). Para realizar esto, en el código C, se incluye el prototipo de la función escrita en assembler. extern void modus(int mod, int offset); La rutina en assembler (modus.s43) debe ser incorporada al proyecto y es la siguiente: NAME MODUS SEG CODE(1) PUBLIC modus SEG CODE modus: PUSH 6 ; save 6 MOV.W SP,6 ; load SP to 6 ADD.W 14,6 ; add offset MOV.W @6,14 ; load S en 14 BIC.W #0xF0,14 ; clear modus bits. BIS.W 12,14 ; set modus bits MOV.W 14,0(6) ; save S to old location POP 6 ; restore 6 ET END El llamado a modus se realiza con 2 parámetros: el valor actual (mod) para ser escrito en el S (Status egister) y el offset para el valor del S almacenado en el stack. El argumento mod se pasa en 12 y el offset en 14. La función modus emplea el registro 6 para efectuar cálculos temporales, (es decir, se escribe en 6, dentro de modus) por esta razón es salvado el valor de 6 en el frame de modus y restaurado antes de salir.(el compilador IA usa el convenio: La rutina llamada tiene la responsabilidad de salvar lo que use. De tal modo que el valor de 6 es el mismo antes y después de llamar a modus. Para calcular este offset puede generarse un listado mixto de C y assembler de la rutina de servicio de interrupciones y luego contar los registros que se colocan en el stack al comienzo del cuerpo de la función. Luego se suma 1 al número de instrucciones que salvan registros y se multiplica por dos, obteniendo el offset en bytes del S.
Las rutinas de servicio de interrupciones crean su frame: almacenando PC y luego el registro de estado S. Luego salvan los registros que usen, en el caso de ClockHandler se empujan 4 registros en el frame. La función modus salva 6. Luego de ejecutada la primera instrucción de modus, el stack puede visualizarse según: sp offset 6 PC S PC Fondo del frame de modus Tope del frame de ClockHandler Fondo del frame de ClockHandler En el diagrama las direcciones de memoria aumentan hacia abajo. Se ilustra el frame de modus apilado con el frame de ClockHandler. Notar que el número de registros colocados en el stack puede depender de la versión del compilador y de las opciones de éste (optimization level). Por lo tanto, lo que realiza la función modus es el cambio desde el modo LPM3 (bajo consumo) al estado activo: toma los bits del S desde el stack, resetea los bits del modo LPM3 de acuerdo al parámetro pasado y guarda nuevamente el S en el stack, de tal manera que al volver de la rutina de servicio de la interrupción. ANEXO: Código para implementación del TC #include <msp430x14x.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include "MSP430lib.c" extern void modus(int mod, int offset); void InitTimer_A (void); void InitOsc_TA (void); void Clock (void); int ii, jj; unsigned int SEC; unsigned int MIN;
unsigned int H; unsigned char LCDtime[32]; void main (void) ii = 0; SEC = 0; MIN = 7; H = 12; InitOsc_TA(); InitPorts(); InitTimer_A(); InitLCD(); // inicialización osciladores // inicialización puertos // inicialización Timer A // inicialización LCD while (1) LPM3; // enter low power mode 3 Clock(); // update clock void InitTimer_A (void) P1SEL = 0x80; P1DI = BIT7; // enable Dallas output (P1.7) /* Config. Timer_A*/ TACTL = ID1 ID0 TASSEL0 TAIE; // use ACLK = 1 M / 8 = 125 khz TACCTL2 = OUTMOD1 OUTMOD0 CCIS0; // output mode 3, por P1.7 (Dallas) TACCTL2 &= ~CAP; // compare mode TACC0 = 0xF424; // 62500 TACC2 = 0x7A12; // 31250 TACTL = MC1 MC0; // start timer in up/down-mode _EINT(); void Clock (void) SEC++; if (SEC == 60) SEC = 0; MIN++; if (MIN == 60) MIN = 0; H++; if (H == 24)
H = 0; sprintf (LCDtime, "Hora Chile Cont: %d:%d:%d ", H, MIN, SEC); for (jj=0; jj!= sizeof(lcdtime) - 1; jj++) SEND_CHA(LCDtime[jj]); if (jj==15) SEND_CMD (DD_AM_ADD2); SEND_CMD (CU_HOME); void InitOsc_TA (void) WDTCTL = WDTPW WDTHOLD; BCSCTL1 = XTS; _BIC_S(OSCOFF); // stop watchdog timer // XT1 as high-frequency // turn on XT1 oscillator do IFG1 &= ~OFIFG; while (IFG1 & OFIFG); // wait in loop until crystal is stable BCSCTL1 = DIVA1 DIVA0; IE1 &= ~WDTIE; IFG1 &= ~WDTIFG; // ACLK = XT1 / 8 = 1 MHz // disable WDT int. // clear WDT int. flag WDTCTL = WDTPW WDTTMSEL WDTCNTCL WDTSSEL WDTIS1; // use WDT as timer, flag each. 512 pulses from ACLK while (!(IFG1 & WDTIFG)); // count 1024 pulses from XT1 (until XT1's // amplitude is OK) IFG1 &= ~OFIFG; // clear osc. fault int. flag DCOCTL = DCO2 DCO1 DCO0; BCSCTL1 = SEL2 SEL1 SEL0; // MCLK = DCO, 8 MHz #pragma vector=timea1_vecto interrupt void ClockHandler (void) if (TAIV == 10) // check for timer overflow modus(0x00,12); // exit low power mode 3