UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Diseño con Microcontroladores Implementación de un reloj de tiempo real en el MSP430F149 Los relojes de tiempo real (Real Time Clocks) son utilizados en diversas aplicaciones: eventos generadores de marcas de tiempo, generación de eventos periódicos, etc. El implementar un RTC 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 RC 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 RC no afectan la precisión de la implementación. Implementación del RTC 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 RTC. 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 TACCR0, 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) ; RETI Pablo Naveas Farías Clear SR LPM3 Bits, on top of stack 16-01-2004 5 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Diseño con Microcontroladores 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 RSEG CODE(1) PUBLIC modus RSEG CODE modus: PUSH MOV.W ADD.W MOV.W BIC.W BIS.W MOV.W POP RET R6 SP,R6 R14,R6 @R6,R14 #0xF0,R14 R12,R14 R14,0(R6) R6 ; save R6 ; load SP to R6 ; add offset ; load SR en R14 ; clear modus bits. ; set modus bits ; save SR to old location ; restore R6 END El llamado a modus se realiza con 2 parámetros: el valor actual (mod) para ser escrito en el SR (Status Register) y el offset para el valor del SR almacenado en el stack. El argumento mod se pasa en R12 y el offset en R14. La función modus emplea el registro R6 para efectuar cálculos temporales, (es decir, se escribe en R6, dentro de modus) por esta razón es salvado el valor de R6 en el frame de modus y restaurado antes de salir.(El compilador IAR usa el convenio: “La rutina llamada tiene la responsabilidad de salvar lo que use”. De tal modo que el valor de R6 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 SR. Pablo Naveas Farías 16-01-2004 5 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Diseño con Microcontroladores Las rutinas de servicio de interrupciones crean su frame: almacenando PC y luego el registro de estado SR. Luego salvan los registros que usen, en el caso de ClockHandler se empujan 4 registros en el frame. La función modus salva R6. Luego de ejecutada la primera instrucción de modus, el stack puede visualizarse según: sp offset R6 PC R R R R SR 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 SR desde el stack, resetea los bits del modo LPM3 de acuerdo al parámetro pasado y guarda nuevamente el SR 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 RTC #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; Pablo Naveas Farías 16-01-2004 5 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Diseño con Microcontroladores unsigned int HR; unsigned char LCDtime[32]; void main (void) { ii = 0; SEC = 0; MIN = 7; HR = 12; InitOsc_TA(); InitPorts(); InitTimer_A(); InitLCD(); while (1) { LPM3; Clock(); } } // inicialización osciladores // inicialización puertos // inicialización Timer A // inicialización LCD // enter low power mode 3 // update clock void InitTimer_A (void) { P1SEL = 0x80; P1DIR |= 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 TACCR0 = 0xF424; // 62500 TACCR2 = 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; HR++; if (HR == 24) Pablo Naveas Farías 16-01-2004 5 UNIVERSIDAD TECNICA FEDERICO SANTA MARIA DEPARTAMENTO DE ELECTRONICA Diseño con Microcontroladores { HR = 0; } } } sprintf (LCDtime, "Hora Chile Cont: %d:%d:%d for (jj=0; jj != sizeof(LCDtime) - 1; jj++) { SEND_CHAR(LCDtime[jj]); if (jj==15) SEND_CMD (DD_RAM_ADDR2); } SEND_CMD (CUR_HOME); } ", HR, MIN, SEC); void InitOsc_TA (void) { WDTCTL = WDTPW | WDTHOLD; BCSCTL1 |= XTS; _BIC_SR(OSCOFF); do IFG1 &= ~OFIFG; while (IFG1 & OFIFG); // XT1 as high-frequency // turn on XT1 oscillator // wait in loop until crystal is stable BCSCTL1 |= DIVA1 | DIVA0; IE1 &= ~WDTIE; IFG1 &= ~WDTIFG; // stop watchdog timer // 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 |= RSEL2 | RSEL1 | RSEL0; // MCLK = DCO, 8 MHz } #pragma vector=TIMERA1_VECTOR __interrupt void ClockHandler (void) { if (TAIV == 10) // check for timer overflow modus(0x00,12); // exit low power mode 3 } Pablo Naveas Farías 16-01-2004 5