Wydział Informatyki Politechniki Białostockiej Laboratorium Informatyki Technicznej, Przedmiot: Architektura Komputerów |
Data:
20.12.2012 |
Ćwiczenie nr 6 Temat: Zegar czasu rzeczywistego w trybie niskiego poboru mocy Grupa: 6 Zespół: Dariusz Mikołajczuk Sylwester Leszczyński |
Prowadzący: dr inż. Krzysztof Bielawski
|
Tematem kolejnych ćwiczeń dotyczących układu MSP430 było zaprojektowanie i wykonanie zegara czasu rzeczywistego. Zegar miał wykorzystywać układ Timera A, generującego przerwania co ściśle określony czas. Pomiędzy kolejnymi przerwaniami urządzenie miało pozostawać w trybie niskiego poboru mocy, co w praktyce oznaczało wyłączenie procesora.
Po pomyślnym zaprogramowaniu zegara i przetestowaniu go, dodaliśmy dodatkowo funkcję budzika. Godzinę alarmu można zmieniać podczas pracy zegara (bez jego zatrzymywania). Alarm nie wyłączony przez użytkownika, wyłącza się automatycznie po 4 minutach sygnału.
Aby przestawić czas zegara należy zrestartować urządzenie.
Konfiguracja oscylatora i zegarów
Pierwszym krokiem przy projektowaniu zegara było skonfigurowanie oscylatora i podstawowych zegarów układu. Użyty został oscylator LFXT1 w trybie wysokiej częstotliwości. Zasila on MCLK (8 MHz) oraz ACLK (4 MHz). Na koniec został wyłączony oscylator DCO i (dla pewności) zegar SMCLK. Wszystko to zostało wykonane w funkcji InitOsc2(), której kod zamieszczam dalej.
Konfiguracja Timera A
Następnie należało tak skonfigurować Timer A, aby wywoływał on przerwanie co pewien ułamek sekundy. W obecnej wersji zegara przerwanie to wywołuje się co 100 ms (wersja co 250 ms powodowała nadmierne grzanie się urządzenia).
W rejestrze TACTL ustawiane są następujące bity:
TASSEL0 – timer taktowany z ACLK
MC_1 – timer cyklicznie zlicza od zera do wartości zapisanej w CCR0, w momencie wyzerowania licznika wywoływane jest przerwanie Timer A.
ID_3 – podzielenie przez 8 częstotliwości zegara źródłowego
Ponieważ Timer A taktowany jest z ACLK (4 MHz), to częstotliwość z jaką taktowany jest Timer A wynosi ftimerA = 4 MHz / 8 = 500 kHz
CCTL0 = CCIE; – włączenie przerwań od CCR0
CCR0 = 50000; – wartość zapisana w CCR0 (rejestr 16-bitowy)
Wiedząc teraz z jaką częstotliwością taktowany jest licznik Timera A, oraz znając maksymalną wartość do jakiej wykonywane jest zliczanie, łatwo można obliczyć jak często licznik będzie się zerował ( a tym samym jak często będzie wywoływane przerwanie Timer A ):
CCR0 / ftimerA = 50 000 / 500 000 Hz = 0,1 s = 100 ms – przerwanie będzie wykonywane co 100ms
Po skonfigurowaniu zegarów i timera musieliśmy pamiętać jeszcze o odblokowaniu przerwań:
IE1 |= WDTIE;
_EINT();
Procedura obsługi przerwania Timer A
Procedura ta stanowi główną część programu. Wykonuje ona w skrócie następujące czynności, w kolejności:
Włącza procesor.
Zwiększa wartość zmiennej licznik o 1, zmienna ta przechowuje liczbę zliczonych dotychczas dziesiętnych części sekundy.
Jeśli wartość licznika jest podzielna przez 10 oznacza to, że została odmierzona pełna sekunda, wówczas następuje aktualizacja czasu na wyświetlaczu (o ile nie jesteśmy akurat w trybie konfiguracji budzika).
Odczytuje aktualny stan klawiszy, klawisze sterują tu ustawianiem budzika oraz jego włączaniem/wyłączaniem.
Jeśli nie jesteśmy akurat w trybie konfiguracji budzika, oraz nie dzwoni on aktualnie, następuje wyłączenie procesora do czasu następnego przerwania.
Budzik
Budzik zostaje włączony automatycznie, gdy zmienna budzon jest równa 1 (na wyświetlaczu pojawia się odpowiednia informacja) oraz gdy nadejdzie odpowiednia godzina. Zostaje wówczas uruchomiony watchdog w trybie timer. Obsługa buzera następuje w procedurze obsługi przerwania watchdoga.
Budzik można wyłączyć w dowolnym momencie klawiszami B2 i B3, w razie braku reakcji użytkownika na sygnał alarmu, budzik zostanie wyłączony po 4 minutach.
Aby wejść w tryb konfiguracji godziny budzika należy wcisnąć B1 podczas pracy zegara.
Zaawansowana konfiguracja budzika
Sygnał budzika został w zasadzie ustalony z góry, jest to typowy sygnał: trzy piknięcia po czym chwila przerwa i znów piknięcia. Jednakże ze względu na problemy ze skonfigurowaniem sygnału utworzyliśmy dodatkowy konfigurator budzika.
Aby go uruchomić, należy bezpośrednio po restarcie urządzenia, podczas menu zmiany godziny, wcisnąć klawisz B4.
Przechodzimy wówczas do kolejnego menu, w którym klawisz B3 wybiera zmienną, B1 i B2 zmienia jej wartość, natomiast B4 powraca do poprzedniego menu.
Można tu dokonać następujących zmian:
"Test mode":
wartość 1 pozwala na szybkie przetestowanie sygnału alarmu. Po dowolnej zmianie ustawień budzika należy ustawić godzinę zegara na 00:00. Po czym szybko wcisnąć B1, co pozwoli na zmianę czasu budzika – nie należy jej zmieniać, lecz należy szybko wcisnąć B4. Budzik zostanie włączony. Uruchomi się dokładnie o godzinie 00:00:03
wartość 0 pozostawia domyślny sposób uruchamiania się budzika (o pełnej minucie)
"WDT source" – źródło taktujące timer watchdoga odpowiedzialny za obsługę sygnału buzera. Dostępne źródła to MCLK (8MHz) i ACLK (4MHz)
[częstotliwość taktowania układu WDT to (oznaczenie) ]
"WDT divider" – mnożnik okresu czasu odmierzanego przez timer WDT. Dostępne wartości to 32768, 8192, 512, 64. Czas co jaki nastąpi przerwanie WDT można obliczyć ze wzoru:
"MAXL (short)" – czas (mierzony liczbą przerwań WDT) pomiędzy sygnałami w grupie trzech sygnałów
"MAXH (long)" – czas (liczba przerwań) pomiędzy grupami trzech sygnałów,
"MAXP (pulse)" – długość pojedynczego sygnału mierzona liczbą przerwań
"FREQ (okres)" – wartość mająca wpływ na częstotliwość pojedynczego sygnału. Sygnał generowany jest poprzez naprzemienne włączanie i wyłączanie buzera z odstępem związanym z wartością FREQ.
Generowanie pojedynczego sygnału można opisać następująco:
while (1) {
for(k=0; k!=FREQ; ++k) {_NOP(); _NOP(); _NOP(); _NOP();}
P4OUT ^= BIT2; // włącz/wyłącz buzer
< odczekaj czas TWDT >
}
Program napisany został z wykorzystaniem kompilatora MSPGCC. Wykorzystana została w nim standardowa biblioteka display.h ze strony Olimexu (należy tu dodać trzy funkcje, które załączam w dalszej części).
Folder projektu powinien zawierać następujące pliki: display.c, display.h, main.c, Makefile.bat.
Do kompilacji i flashowania potrzebny jest zainstalowany pakiet MSPGCC (w folderze C:\mspgcc) oraz MSP430_flasher (C:\MSP430_FLasher)
Aby skompilować program należy zaktualizować ścieżkę do folderu z projektem, czyli zmienną pd w pliku Makefile.bat, po czym uruchomić plik Makefile.bat.
Zawartość pliku Makefile.bat |
@echo off :: Folder z binarkami mspgcc (bez końcowego '\') set md=C:\mspgcc\bin
:: Folder projektu (bez końcowego '\') set pd="C:\Documents and Settings\student\Pulpit\MSP_zegar_mspgcc_10_prz_BUDZIK"
if not exist %pd%\output (mkdir %pd%\output) del %pd%\output\*.* /Q ::@echo on
:: Create object files %md%\msp430-gcc.exe -Os -g -c -mmcu=msp430x149 %pd%\display.c -o %pd%\output\display.o %md%\msp430-gcc.exe -Os -g -c -mmcu=msp430x149 %pd%\main.c -o %pd%\output\main.o
:: Link all files together cd %md% msp430-gcc.exe -Os -g -mmcu=msp430x149 %pd%\output\display.o %pd%\output\main.o -o %pd%\zegar.elf cd %pd%
:: Create hex file ready to download to the device %md%\msp430-objcopy.exe -O ihex %pd%\zegar.elf %pd%\zegar.hex
@echo on :: Load a hex file into device C:\MSP430_FLasher\MSP430Flasher.exe -n MSP430F149 -w %pd%\zegar.hex -i LPT1 -v -g -z [VCC] pause |
Dodatkowe funkcje umieszczone w display.c |
/* Wypisuje liczbę typu short int na wyświetlaczu */ void printDec(short int dec) { char num[7]; char p = 0; if (dec == 0x8000) { printString("-32768"); return; } if (dec < 0) { dec *= -1; num[0] = '-'; p++; } else if (dec == 0) {SEND_CHAR('0'); return;}
char c, zero = 1; short int md = 10000;
/* first digit (most significant) */ c = dec/md; if ((zero) && (c)) zero=0; if (!zero) {num[p] = c+'0'; p++; dec -= md*c;} md /= 10; /* 2nd digit */ c = dec/md; if ((zero) && (c)) zero=0; if (!zero) {num[p] = c+'0'; p++; dec -= md*c;} md /= 10; /* 3rd digit */ c = dec/md; if ((zero) && (c)) zero=0; if (!zero) {num[p] = c+'0'; p++; dec -= md*c;} md /= 10; /* 4th digit */ c = dec/md; if ((zero) && (c)) zero=0; if (!zero) {num[p] = c+'0'; p++; dec -= md*c;} md /= 10; /* 5th digit */ c = dec/md; if ((zero) && (c)) zero=0; if (!zero) {num[p] = c+'0'; p++;} /* print */ char i; for (i = 0; i != p; i++) SEND_CHAR(num[i]); }
/* Wypisuje znak pod określonym adresem w DDRAM. Adres przyjmuje wartość 0x00-0x0F dla pierwszej linii oraz 0x40-0x4F dla drugiej linii wyświetlacza */ void CHAR_DD(unsigned char znak, unsigned char adres) { adres |= DD_RAM_ADDR; int temp; Delayx100us(10); temp = adres & 0xf0; LCD_Data &= 0x0f; LCD_Data |= temp; bitclr(P2OUT,RS); _E(); temp = adres & 0x0f; temp = temp << 4; LCD_Data &= 0x0f; LCD_Data |= temp; bitclr(P2OUT,RS); _E();
Delayx100us(5); temp = znak & 0xf0; LCD_Data &= 0x0f; LCD_Data |= temp; bitset(P2OUT,RS); _E(); temp = znak & 0x0f; temp = temp << 4; LCD_Data &= 0x0f; LCD_Data |= temp; bitset(P2OUT,RS); _E(); }
/* Odczekanie (aktywne) 1ms */ void Delayx1ms(unsigned int d) { unsigned int j,k; for(j=0; j != d; ++j) { for(k=0; k != 4000; ++k) _NOP(); } } |
Dodatkowe nagłówki umieszczone w display.h |
void printDec(short int dec); void CHAR_DD(unsigned char znak, unsigned char adres); void Delayx1ms(unsigned int d); |
Zawartość pliku main.c |
#include "stdio.h" #include "stdlib.h" #include "display.h" #include <signal.h> #include <io.h> #include <msp430x14x.h>
#define WDTCL *((short*) 0x120) #ifndef WDTHOLD #define WDTHOLD 0x0080 #endif #ifndef WDTPW #define WDTPW 0x5A00 #endif #define P4IN *((unsigned char*) 0x1C) #define P4DIR *((unsigned char*) 0x1E) #define P2OUT *((unsigned char*) 0x29) #define P2DIR *((unsigned char*) 0x2A) #define P1OUT *((unsigned char*) 0x21) #define P1DIR *((unsigned char*) 0x22) #define B1 (P4IN & 0x10) #define B2 (P4IN & 0x20) #define B3 (P4IN & 0x40) #define B4 (P4IN & 0x80) #define STATUS_LED_ON (P2OUT &= ~0x02) #define STATUS_LED_OFF (P2OUT |= 0x02) #define DD_RAM_ADDR 0x80 #define DD_RAM_ADDR2 0xC0 #define CLR_DISP 0x01 #define CUR_HOME 0x02 #define BUZ_ON (P4OUT |= BIT2) //P4.2 #define BUZ_OFF (P4OUT &= ~BIT2) //P4.2
/* Pozycje w DDRAM */ #define HH 0x04 // godziny #define MIN 0x07 // minuty #define SS 0x0A // sekundy #define F1 0x06 // pierwszy ':' #define F2 0x09 // drugi ':'
unsigned short int licznik=0; unsigned char budzon=0; unsigned char godzB=0; unsigned char minB=0; unsigned char minuty=0; unsigned char godziny=0;
unsigned char disp=1; unsigned char talarm=0; unsigned char buz=0; unsigned char num=0; unsigned char pulse;
void defineChars() { // definicje polskich znaków używanych w komunikatach char a[8]={0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F, 0x01, 0x02}; // ą char c[8]={0x02, 0x04, 0x0E, 0x11, 0x10, 0x11, 0x0E, 0x00}; // ć char l[8]={0x0C, 0x04, 0x04, 0x06, 0x0C, 0x04, 0x0E, 0x00}; // ł char addr=0x40, i; for (i = 0; i != 8; i++) { // 0x00 ą SEND_CMD(addr++); SEND_CHAR(a[i]); } for (i = 0; i != 8; i++) { // 0x01 ć SEND_CMD(addr++); SEND_CHAR(c[i]); } for (i = 0; i != 8; i++) { // 0x02 ł SEND_CMD(addr++); SEND_CHAR(l[i]); } SEND_CMD(DD_RAM_ADDR); }
void InitPorts2() { // Inicjuje porty klawiszy, diod, styczników i buzera P4DIR &= ~0x10; // Ustawienie bitu P4.4 na 0 (tryb wejsciowy) B1 key P4DIR &= ~0x20; // Ustawienie bitu P4.5 na 0 (tryb wejsciowy) B2 key P4DIR &= ~0x40; // Ustawienie bitu P4.6 na 0 (tryb wejsciowy) B3 key P4DIR &= ~0x80; // Ustawienie bitu P4.7 na 0 (tryb wejsciowy) B4 key P4DIR |= BIT2; // Inicjalizacja buzera BUZ_OFF; // wyłącz buzer P2DIR |= 0x02; // Ustawienie bitu P2.1 na 1 (tryb wyjściowy) STATUS_LED P2OUT &= ~0x02; // Zgaś diodę STATUS_LED jeśli zapalona P1DIR |= 0x20; // Ustawienie bitu P1.5 na 1 (tryb wyjściowy) REL_1 P1OUT &= ~0x20; // Wyłącz stycznik REL_1 i powiązaną z nim diodę P1DIR |= 0x40; // Ustawienie bitu P1.6 na 1 (tryb wyjściowy) REL_2 P1OUT &= ~0x40; // Wyłącz stycznik REL_2 i powiązaną z nim diodę }
/* Inicjalizacja oscylatorów i zegarów * Ostatecznie pracuje tylko oscylator LFXT1, który zasila ACLK (4MHz) i MCLK (8MHz) */ void InitOsc2() { BCSCTL1 |= XTS; // oscylator LFXT1 w trybie wysokiej częstotliwości (8MHz) _BIC_SR(OSCOFF); // włączenie oscylatora LFXT1
char i; do { IFG1 &= ~OFIFG; // wyczyszczenie flagi OFIFG rejestru IFG1 for (i = 0xFF; i > 0; i--); // odczekanie } while (IFG1 & OFIFG); // czekaj dopóki flaga błędu oscylatora jest ustawiona
BCSCTL1 |= DIVA0; // częstotliwość ACLK = LFXT1/2 = 4MHz BCSCTL1 &= ~DIVA1; // BCSCTL2 |= SELM0 | SELM1; // częstotliwość MCLK = LFTX1 = 8MHz _BIS_SR(SCG0); // wyłączenie oscylatora DCO _BIS_SR(SCG1); // wyłączenie zegara SMCLK }
/* Kontrola budzika (domyślne wartości zmiennych) */ #define WDTCONFIG (WDTPW+WDTTMSEL+WDTCNTCL+WDTSSEL+WDTIS0) // domyślne ustawienia unsigned short maxl=30; unsigned short maxh=150; unsigned short maxp=30; unsigned short freq=50; unsigned short wdtconfig=0; unsigned char ws=0; unsigned char ds=1; unsigned char dbg=0;
void alarmConf() { // konfigurator budzika wdtconfig=WDTTMSEL+WDTCNTCL; while ((B4) == 0) {_NOP(); _NOP(); _NOP(); _NOP();} Delayx1ms(300); unsigned char md=6;
unsigned char pr=1;
SEND_CMD(CLR_DISP); SEND_CMD(CUR_HOME); SEND_CMD(DD_RAM_ADDR2); printString("Test mode"); while (1) { if (pr) { pr = 0; SEND_CMD(DD_RAM_ADDR); SEND_CMD(CUR_HOME); if (md == 0) { if (ws) printString("MCLK 8MHz"); else printString("ACLK 4MHz"); } else { if (md==1) { switch (ds) { case 0: printString("32768"); break; case 1: printString("8192 "); break; case 2: printString("512 "); break; case 3: printString("64 "); break; }; } else { if (md == 6) { if (dbg) CHAR_DD('1', 0x00); else CHAR_DD('0', 0x00); } else { printString(" "); SEND_CMD(DD_RAM_ADDR); switch (md) { case 2: printDec(maxl); break; case 3: printDec(maxh); break; case 4: printDec(maxp); break; case 5: printDec(freq); break; }; } } } } if ((B2) == 0) { switch(md) { case 0: ws=!ws; break; case 1: ds++; ds%=4; break; case 2: maxl++; break; case 3: maxh++; break; case 4: maxp++; break; case 5: freq++; break; case 6: dbg = !dbg; break; }; pr=1; Delayx1ms(150); }
if ((B1) == 0) { switch(md) { case 0: ws=!ws; pr=1; Delayx1ms(150); break; case 1: if (ds) ds--; else ds=3; pr=1; Delayx1ms(150); break; case 2: if (maxl) {maxl--; pr=1; Delayx1ms(150);} break; case 3: if (maxh) {maxh--; pr=1; Delayx1ms(150);} break; case 4: if (maxp) {maxp--; pr=1; Delayx1ms(150);} break; case 5: if (freq) {freq--; pr=1; Delayx1ms(150);} break; case 6: dbg=!dbg; pr=1; Delayx1ms(150); break; }; } if ((B3) == 0) { SEND_CMD(CLR_DISP); SEND_CMD(CUR_HOME); SEND_CMD(DD_RAM_ADDR2); md++; md %= 7; pr=1; switch (md){ case 0: printString("WDT source"); break; case 1: printString("WDT divider"); break; case 2: printString("MAXL (short)"); break; case 3: printString("MAXH (long)"); break; case 4: printString("MAXP (pulse)"); break; case 5: printString("FREQ (okres)"); break; case 6: printString("Test mode"); break; }; Delayx1ms(250); } if ((B4) == 0) { if (! ws) wdtconfig += WDTSSEL; switch (ds) { case 1: wdtconfig += WDTIS0; break; case 2: wdtconfig += WDTIS1; break; case 3: wdtconfig += (WDTIS0+WDTIS1); break; } Delayx1ms(200); return; } } }
/* Ustawienie zegara przy starcie urządzenia */ void setClock(unsigned char *godziny, unsigned char *minuty, char budzik) { SEND_CMD(CLR_DISP); SEND_CMD(DD_RAM_ADDR); if (budzik) { printString(" GodzBudz "); SEND_CHAR((*godziny)/10+'0'); // wypisanie pierwszej cyfry godzin pod adresem DDRAM 0x0A SEND_CHAR((*godziny)%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem } else printString(" Godzina: 00"); SEND_CMD(DD_RAM_ADDR2); printString(" B1- B2+ B3(OK) ");
while (1) { if ((B2) == 0) { (*godziny)++; *godziny %=24; CHAR_DD((*godziny)/10+'0', 0x0A); // wypisanie pierwszej cyfry godzin pod adresem DDRAM 0x0A SEND_CHAR((*godziny)%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem Delayx1ms(200); } if ((B1) == 0) { if (*godziny == 0) (*godziny) = 23; else (*godziny)--; CHAR_DD((*godziny)/10+'0', 0x0A); // wypisanie pierwszej cyfry godzin pod adresem DDRAM 0x0A SEND_CHAR((*godziny)%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem Delayx1ms(200); } if ((B3) == 0) { Delayx1ms(400); break; } if (((B4) == 0) && (budzik)) {SEND_CMD(CLR_DISP); return;} if (((B4) == 0) && (! budzik)) { alarmConf(); SEND_CMD(CLR_DISP); SEND_CMD(CUR_HOME); printString(" Godzina: "); SEND_CHAR((*godziny)/10+'0'); // wypisanie pierwszej cyfry godzin pod adresem DDRAM 0x0A SEND_CHAR((*godziny)%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem SEND_CMD(DD_RAM_ADDR2); printString(" B1- B2+ B3(OK) "); } } SEND_CMD(CLR_DISP); SEND_CMD(DD_RAM_ADDR); if (budzik) { printString(" MinBudz "); SEND_CHAR((*minuty)/10+'0'); // wypisanie pierwszej cyfry minut pod adresem 0x09 SEND_CHAR((*minuty)%10+'0'); // wypisanie drugiej cyfry minut pod kolejnym adresem } else printString(" Minuta: 00"); SEND_CMD(DD_RAM_ADDR2); printString(" B1- B2+ B3(OK) "); while (1) { if ((B2) == 0) { (*minuty)++; *minuty %=60; SEND_CMD(DD_RAM_ADDR+9); CHAR_DD((*minuty)/10+'0', 0x09); // wypisanie pierwszej cyfry minut pod adresem 0x09 SEND_CHAR((*minuty)%10+'0'); // wypisanie drugiej cyfry minut pod kolejnym adresem Delayx1ms(200); } if ((B1) == 0) { if (*minuty == 0) *minuty = 59; else (*minuty)--; CHAR_DD((*minuty)/10+'0', 0x09); // wypisanie pierwszej cyfry minut pod adresem 0x09 SEND_CHAR((*minuty)%10+'0'); // wypisanie drugiej cyfry minut pod kolejnym adresem Delayx1ms(200); } if ((B3) == 0) break; } } /* Funkcja główna */ int main(void) { WDTCTL = WDTPW + WDTHOLD; InitPorts(); InitOsc2(); InitLCD(); InitPorts2(); defineChars(); STATUS_LED_OFF; SEND_CMD(CLR_DISP); setClock(&godziny, &minuty, 0); // ustawienie domyślnych parametrów budzika jeśli nie były modyfikowane if (! wdtconfig) wdtconfig=WDTCONFIG;
SEND_CMD(CLR_DISP); SEND_CMD(CUR_HOME); CHAR_DD(godziny/10+'0', HH); // wypisanie pierwszej cyfry godzin pod adresem HH SEND_CHAR(godziny%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem SEND_CHAR(':'); SEND_CHAR(minuty/10+'0'); // wypisanie pierwszej cyfry minut pod adresem MM SEND_CHAR(minuty%10+'0'); // wypisanie drugiej cyfry minut pod kolejnym adresem SEND_CHAR(':'); SEND_CHAR('0'); SEND_CHAR('0');
/* Konfiguracja Timera A TASSEL0 - źródło zegarowe ACLK MC_1 - cyklicznie zlicza od zera do wartości zapisanej w TACCR0 podczas zerowania licznika następuje przerwanie Timer_A ID_3 - podzielenie przez 8 częstotliwości zegara źródłowego (4MHz/8 = 500kHz) (oba bity ustawione)
Przerwanie następuje co (CCR0) / (f_timerA) = 50000 / (4MHz/8) = 100 ms */
TACTL = TASSEL0 + MC_1 + ID_3;
// Włączenie przerwań od CCR0 CCTL0 = CCIE;
// Przerwanie Timer_A uruchamiane co 100 ms CCR0 = 50000;
//Włączenie przerwanń IE1 |= WDTIE; // aktywuje przerwania NMI _EINT();
licznik = 0;
_BIS_SR(CPUOFF); // wyłącz procesor
/* Pętla nieskończona */ while (1) { }
}
/* Procedura obsługi przerwania watchdoga odpowiedzialnego za sygnał budzika */ int k; char m; interrupt(WDT_VECTOR) watchdog_timer(void) { if (pulse) { // pojedyncze piknięcie for(k=0; k!=freq; ++k) {_NOP(); _NOP(); _NOP(); _NOP();} P4OUT ^= BIT2; if (++num == maxp) {num=pulse=0; BUZ_OFF;} } else { // odstęp pomiędzy sygnałami alarmu num++; if (m == 3) { // dłuższy odstęp między grupą trzech piknięć alarmu if (num == maxh) {m=num=0; pulse=1;} } else { // krótszy odstęp między poszczególnymi piknięciami w danej trójce if (num == maxl) {num=0; pulse=1; m++;} } } }
/* Procedura obsługi przerwania Timer A * Uruchamia się ono 10 razy w ciągu sekundy (w pozostałym czasie urządzenie pozostaje w trybie niskiego poboru mocy) procesor jest wyłączony, a przetwarzanie wstrzymane */ interrupt(TIMERA0_VECTOR) Timer_A(void) { _BIC_SR_IRQ(CPUOFF); // włącz procesor ++licznik;
if ((licznik % 10) == 0) { // odmierzono pełną sekundę if ((buz) && (++talarm == 240)) { // budzik dzwonił 4 minuty, wyłącz go talarm = buz = budzon = 0; BUZ_OFF; SEND_CMD(DD_RAM_ADDR2); printString(" "); WDTCTL = WDTPW|WDTHOLD; } if ((dbg) && (budzon) && (godziny==godzB) && (minuty==minB) && licznik==30 && (disp){ // włączenie budzika w trybie testowym o godzinie 00:00:03 m=talarm=num=0; budzon=pulse=buz=1; WDTCTL = WDTPW|wdtconfig; } /* */ if (licznik == 600) { // odmierzono pełną minutę 60*100ms licznik = 0; // zerowanie licznika dziesiętnych części sekundy minuty++; if (minuty == 60) { // odmierzono pełną godzinę minuty = 0; godziny++; if (godziny == 24) godziny = 0; // odmierzono pełną dobę if (disp) { CHAR_DD(godziny/10+'0', HH); // wypisanie pierwszej cyfry godzin pod adresem HH SEND_CHAR(godziny%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem } } if (disp) { CHAR_DD(minuty/10+'0', MIN); // wypisanie pierwszej cyfry minut pod adresem MM SEND_CHAR(minuty%10+'0'); // wypisanie drugiej cyfry minut pod kolejnym adresem CHAR_DD('0', SS); SEND_CHAR('0'); } if ((!dbg) && (budzon) && (godziny==godzB) && (minuty==minB) && (disp)) { // włączenie budzika w zwykły sposób (o pełnej minucie) m=talarm=num=0; budzon=pulse=buz=1; WDTCTL = WDTPW|wdtconfig; } /* */ } else { // wypisz sekundy if (disp) { CHAR_DD(licznik/100+'0', SS); // wypisanie pierwszej cyfry sekund pod adresem SS SEND_CHAR((licznik%100)/10 + '0'); // wypisanie drugiej cyfry sekund pod kolejnym adresem } } }
// opuszczenie trybu zegara i wejście w tryb konfiguracji budzika if (((B1) == 0) && (disp==1)) { if (buz) { // wyłącz alarm jeśli akurat dzwoni talarm = buz = budzon = 0; BUZ_OFF; WDTCTL = WDTPW|WDTHOLD; } disp=0; SEND_CMD(DD_RAM_ADDR); SEND_CHAR('B'); SEND_CHAR('u'); SEND_CHAR('d'); SEND_CHAR('z'); SEND_CHAR('i'); SEND_CHAR('k'); SEND_CHAR(':'); SEND_CHAR(' '); SEND_CHAR(godzB/10+'0'); // wypisanie pierwszej cyfry godzin pod adresem HH SEND_CHAR(godzB%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem SEND_CHAR(':'); SEND_CHAR(minB/10+'0'); // wypisanie pierwszej cyfry minut pod adresem MM SEND_CHAR(minB%10+'0'); // wypisanie drugiej cyfry minut pod kolejnym adresem
SEND_CMD(DD_RAM_ADDR2); printString("B2-HH B3-MM B4OK"); }
if (((B2) == 0) && (disp==0)) { // zmiana godziny budzika w trybie konfiguracji budzika godzB++; godzB %= 24; CHAR_DD(godzB/10+'0', 0x08); SEND_CHAR(godzB%10+'0'); }
if (((B3) == 0) && (disp==0)) { // zmiana minuty budzika w trybie konfiguracji budzika minB++; minB %= 60; CHAR_DD(minB/10+'0', 0x0B); SEND_CHAR(minB%10+'0'); }
// opuszczenie trybu konfiguracji budzika i powrót do trybu zegara if (((B4) == 0) && (disp==0)) { budzon=1; disp=1; SEND_CMD(CLR_DISP); SEND_CMD(CUR_HOME); CHAR_DD(godziny/10+'0', HH); // wypisanie pierwszej cyfry godzin pod adresem HH SEND_CHAR(godziny%10+'0'); // wypisanie drugiej cyfry godzin pod kolejnym adresem SEND_CHAR(':'); SEND_CHAR(minuty/10+'0'); // wypisanie pierwszej cyfry minut pod adresem MM SEND_CHAR(minuty%10+'0'); // wypisanie drugiej cyfry minut pod kolejnym adresem SEND_CHAR(':'); SEND_CHAR(licznik/100+'0'); // wypisanie pierwszej cyfry sekund pod adresem SS SEND_CHAR((licznik%100)/10 + '0'); // wypisanie drugiej cyfry sekund pod kolejnym adresem SEND_CMD(DD_RAM_ADDR2); printString("Budzik W"); SEND_CHAR(2); SEND_CHAR(0); printString("czony"); }
// wyłączenie budzika w trybie zegara if ((budzon==1) && (((B2) == 0) || ((B3) == 0)) && (disp==1)) { if (buz) { // budzik akurat dzwonił talarm = buz = budzon = 0; BUZ_OFF; WDTCTL = WDTPW|WDTHOLD; } else // budzik nie dzwonił budzon = 0; SEND_CMD(DD_RAM_ADDR2); printString(" "); }
// wyłączenie procesora o ile jesteśmy w trybie zegara, ani nie dzwoni budzik if ((disp==1) && (! buz)) _BIS_SR_IRQ(CPUOFF); } |
Zegar działa poprawnie, a czas odmierzany jest dokładnie. Obsługa samego zegara oraz Timera A była dość prosta do zaprogramowania. Sporo czasu zajęła obsługa budzika i próby zaimplementowania jednoczesnej możliwości zmiany godziny budzika bez zatrzymywania zegara.
Ostatecznie zostało to wykonane w najprostszy możliwy sposób, który działa. Czyli, uwzględniono konfiguracje budzika w ramach tej samej funkcji obsługi przerwania Timera A.
Co ważne, samo przestawianie godziny budzika nie powinno powodować opóźnień czasu.
W trybie zegara i konfiguracji godziny budzika, funkcje zostały przydzielone klawiszom w taki sposób aby nie kolidowały one ze sobą. Ponadto, użycie aktywnego czekania po naciśnięciu klawisza zmiany godziny lub minuty budzika, zostało wyeliminowane – przeskok wartości następuje co przerwanie – 100 ms. Wszystko to odbywa się minimalnym kosztem zmniejszenia wygody użytkowania.
Pewnym problemem mogą być procedury obsługi przerwania watchdoga. W standardowej konfiguracji, uruchamia się ona co 2ms. Jej uruchamianie się może potencjalnie powodować opóźnienia w uruchamianiu przerwania Timer A, a w konsekwencji na opóźnianiu się zegara (zwłaszcza, że w przerwaniu watchdoga nie udało się uniknąć aktywnego czekania, które przy domyślnych ustawieniach zajmuje około 4*FREQ = 200 cykli procesora)
Jednak nie sądzę, aby mogło to znacząco wpłynąć na dokładność odmierzanego czasu.