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
Wstęp
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.
Realizacja
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).
1.
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 f
timerA
= 4 MHz / 8 = 500 kHz
2.
CCTL0 = CCIE; – włączenie przerwań od CCR0
3.
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 / f
timerA
= 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:
1.
Włącza procesor.
2.
Zwiększa wartość zmiennej licznik o 1, zmienna ta przechowuje liczbę zliczonych
dotychczas dziesiętnych części sekundy.
3.
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).
4.
Odczytuje aktualny stan klawiszy, klawisze sterują tu ustawianiem budzika oraz jego
włączaniem/wyłączaniem.
5.
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)
f
WDT
source
]
•
"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:
T
WDT
=
WDT_dividier
f
WDT
source
•
"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,
MAXH ⩾MAXL
•
"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 T
WDT
>
}
Implementacja
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
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);
}
Wnioski
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.