Időzítők, számlálók
A fejezet tartalma:- Az időzítés szerepe a beágyazott rendszerekben
- Időzítők/számlálók a MKL25Z128VLK4 mikrovezérlőben
- A Timer objektumosztály
- Mintapélda: Reakcióidő mérése Timer használatával (08_reaction_time)
- A Timeout objektumosztály
- Mintapélda: LED állapotváltás Timeout használatával (08_timeout_helloworld)
- A Ticker objektumosztály
- Mintapélda: Nyomógomb pergésmentesítése Ticker használatával (08_button_debouncer)
- A FRDM-KL25Z valós idejű órája (RTC)
- Működtetési lehetőségek és szoftveres beállítások
- A gcc4mbed build rendszer és a rendszerszintű kód módosítása
- Az RTC kezelése a Time programkönyvtár használatával
- Mintapélda: Az RTC működésének ellenőrzése (08_rtc_test)
- Mintapélda: Az RTC kipróbálása (08_rtc_proba)
- Mintapélda: az RTC használata (08_rtc_example)
- A WakeUp objektumosztály
- Mintapélda: WakeUp használata (08_wakeup)
Az időzítés szerepe a beágyazott rendszerekben
A beágyazott rendszerek időben kell, hogy reagáljanak a bekövetkező eseményekre, vagy előre megadott ütemezés szerint kell, hogy végezzék feladataikat (pl. adatgyűjtés/adatmegjelenítés azonos időközönként). Ebből következően a beágyazott rendszereknek képeseknek kell lennie az alábbiakra:- Időtartam mérése
- Idő-alapú események generálása, ami lehet egyszeri vagy ismétlődő
- Megfelelő sebességgel reagálni az előre nem meghatározható időben bekövetkező eseményekre
Ebből következik, hogy szükségünk van olyan eszközökre és módszerekre, amelyek lehetővé teszik a hatékony idő-alapú tevékenység végzését. Főbb elemei ennek az eszköztárnak az előző fejezetben tárgyalt megszakítások valamint az időzítők/számlálók, amelyekkel ebben a fejezetben foglalkozunk. A számláló (counter) olyan digitális áramkör, amellyel feszültségimpulzusokat tudunk leszámlálni. Ha a leszámlálandó impulzusok nem külső forrásból származnak, hanem ismert, állandó frekvenciájú jelet vezetünk a számláló bemenetére, akkor pedig időzítőről (timer) beszélünk, amely lehetővé teszi számunkra az idő mérését, illetve a feladatok ütemezését.
Ütemezést tulajdonképpen már használtunk a legelső LED villogtató projektünknél is, amelyben wait(0.5) függvényhívásokkal arra utasítottuk a mikrovezérlőt, hogy 0.5 másodpercig várakozzon a LED állapotváltások között. Az ilyen várakoztatás roppant egyszerűen, akár üres programciklusokkal is megvalósítható, ám a blokkoló jellege miatt nem hatékony. Komolyabb programjainkban nyilván azt szeretnénk majd, hogy a mikrovezérlő a várakozások során más feladattal is foglalkozhasson, s az időmérés a háttérben történjen. Ezzel el is érkeztünk a hardveres számlálók és időzítők használatához...
Számlálókat gyakran használnak a digitális áramkörökben. Ezek többnyire sorbakötött bistabil billenőáramkörök, melyek mindegyike egy-egy bitnyi információt tárol. Egy n-bites számláló 2n-1 állapotot képes felvenni. Például az alábbi ábrán látható 8 bites számláló 0000 0000-tól 1111 1111-ig, azaz 0-tól 255-ig számol.

1. ábra: Egyszerű 8-bites számláló
Ha a számláló bemenetére ismert és stabil frekvenciájú jelet vezetünk, akkor a számlálónk időzítőként használható. Például 1 MHz-es órajel (periódusidő = 1 µs) esetén a számlálónk mikroszekundumokat számlál.
Időzítők/számlálók a MKL25Z128VLK4 mikrovezérlőben
A Freescale KL25 Sub_Family Reference Manual szerint az MKL25Z128VLK4 mikrovezérlő az alábbi számlálókkal, illetve időzítőkkel rendelkezik:- Systick 24-bites (vissza)számláló, amely az ARM Cortex-M mikrovezérlő mag része (a Systick számláló megvalósítása az ARM Cortex-M0 és M0+ magú mikrovezérlőknél opcionális, de a legtöbb gyártó beépíti). Többnyire ezt a számlálót használják az RTOS operációs rendszerek a feladatok ütemezéséhez.
- Timer0, Timer1, Timer2 16-bites számlálók, amelyekhez 2-6 db impulzus-szélesség modulációra (PWM) használható csatorna is tartozik. Ezekkel a számlálókkal a PWM című fejezetben már találkoztunk.
- Periodic Interrupt Timer (PIT) modul,
ami 2 db 32 bites időzítővel rendelkezik, amelyek egy 64 bites
időzítővé is összekapcsolhatók. A PIT
modul eredetileg periodikus megszakítások előállítására szolgál, de az mbed
környezetben a két számlálót
sorbakötve a bekapcsolás, illetve az utolsó RESET óta eltelt idő
mikroszekundumos felbontású mérésére használjuk (az us_ticker()
függvény ahhoz hasonlóan
működik, ahogy az Arduino környezetben a millis() függvény, csak
ott
milliszekundumos felbontással mérjük az eltelt időt). Az mbed
környezeteben a wait(), wait_ms(), wait_us()
függvények is a PIT modult
használó us_ticker() függvény
felhasználásával végzik az eltelt idő ellenőrzését a megadott
késleltetési idő kivárása közben.
- Low-Power Timer (LPTMR) 16-bites időzítő/számláló. Az LPTMR kisfogyasztású időzítő egyik legfontosabb jellemzője, hogy mindegyik energiatakarékos módban üzemképes marad, s külső események számlálására vagy belső órajellel időzítésre használható. A 16-bites számláló bemenetéhez előszámlálót vagy zajszűrőt is konfigurálhatunk. Az előosztás mértéke kettő hatványai szerinti, 2 - 65536 közötti szám lehet. Az mbed környezetben az LPTMR időzítő foglalt, a Ticker (periodikus megszakítás), illetve a WakeUp (felébresztés "mélyalvás" energiatakarékos módból) objektumosztályok használják.
- Real-Time Clock (RTC)
modul, melyben található 32-bites másodperc számláló, 16-bites
előszámláló, 16-bites időkompenzációs regiszter és egy 32-bites
regiszter az ébresztés beállítására. A FRDM-KL25Z
kártya esetén az RTC órajelét az OpenSDA
áramkör biztosítja, s az MKL25Z128VLK4
mikrovezérlő PTC1 kivezetését (RTC_CLKIN)
lefoglalja. Az RTC modul
óráját mbed környezetben a
beépített rtc_time API
függvények segítségével írhatjuk/olvashatjuk.
A Timer objektumosztály
A Timer objektumosztály segítségével absztrakt időmérőket hozhatunk létre, amellyel pl. a két esemény között eltelt időt mérhetjük meg, mikroszekundumos felbontással.1. táblázat: A Timer objektumosztály tagfüggvényei
| Függvény |
Használat |
|---|---|
| Timer név |
Létrehoz egy "név" nevű
absztrakt Timer objektumot. |
| start() |
Elindítja az időmérést |
| stop() |
Megállítja az időmérést |
| reset() |
Nullázza az időmérőt |
| read() |
Másodpercekben adja meg az
eltelt időt. |
| read_ms() | Ezredmásodpercekben adja meg az eltelt időt. |
| read_us() | Milliomod másodpercekben adja meg az eltelt időt. |
Mintapélda: Reakcióidő mérése Timer használatával
Az alábbi programban, amely Albertas Galin: "Reaction_timer" c. programjának átirata, a Timer absztrakt időzítőt stopperként használjuk, s egy LED felgyulladása és a válaszként lenyomott nyomógomb bekapcsolása között eltelt időt mérjük vele (a felhasználó reakcióideje). Az eredményt az alapértelmezett soros porton keresztül íratjuk ki.Hardver követelmények:
- FRDM-KL25Z kártya
- A D3 (PTA12) bemenet és a GND közé egy nyomógombot kell kötni az alábbi ábra szerint.

2. ábra: A nyomógomb bekötése
1. lista: A 08_reaction_time/main.cpp program listája
#include "mbed.h"
DigitalOut myled(LED_GREEN); //define LED
DigitalIn mybutton(D3,PullUp); //define pushbutton input with pull-up
Timer t; //define timer
int x; //x for random number storage
int main() {
myled = 1; //LED is initially off
printf("\r\nReaction test!\r\n");
printf("Push the button when the LED is on!\r\n");
while(1) {
while(!mybutton); //wait for button release
x=rand()%2000; //generate random number 0-2000
wait_ms(1000+x); //wait random time between 1 and 3 seconds
t.start(); //start the timer
myled=0; //LED on
while(mybutton); //wait for press
t.stop(); //stop the timer
myled=1; //LED off
wait_ms(20); //debounce delay
printf("The time taken was %f seconds\r\n", t.read());
t.reset(); //reset the timer
}
}
A programban lebegőpontos formátumú kiírást végzünk, ezért a Makefile-ban a NO_FLOAT_PRINTF értékét nullára kell állítani.
A 08_reaction_time projekt Makefile állománya:
PROJECT := 08_reaction_time
DEVICES := KL25Z
GCC4MBED_DIR := ../..
GCC4MBED_TYPE := Release
MBED_OS_ENABLE := 0
NEWLIB_NANO := 1
NO_FLOAT_SCANF := 1
NO_FLOAT_PRINTF := 0
include $(GCC4MBED_DIR)/build/gcc4mbed.mk
A nyomógomb bemenetet belső felhúzású módba konfiguráljuk, így nem kell
külső felhúzó ellenállást kötni a D3 (PTA12) bemenetre. A while(1)
feltételvizsgálattal kezdődő végtelen ciklus elején a nyomógomb
felengedésére történő várakozás megakadályozza a lassú felengedésből,
vagy az eleve lenyomva tartásból adódó téves időmérést. A LED
felkapcsolása előtt egy (ál)véletlen hosszúságú (1000-3000 ms
időtartamú) várakozás következik, majd elindítjuk a stoppert. A
nyomógomb lenyomásakor leállítjuk az időmérést és lekapcsoljuk a
LED-et, majd kiíratjuk az eredményt. Az újabb mérési ciklus megkezdése
előtt nulláznunk kell a stoppert a t.reset()
függvényhívással.
3. ábra: A 08_reaction_time program futási eredménye
A Timeout objektumosztály
A Timeout objektumosztály segítségével ébresztőórához hasonló időmérőt hozhatunk létre, amellyel adott idő elteltével visszahívási (callback) függvényt aktiválhatunk, a főprogram futását megszakítva. Mivel a visszahívási függvény programmegszakítási szinten aktiválódik, ügyelnünk kell rá, hogy a visszahívási függvény ne tartalmazzon blokkoló várakozást, terjedelmes adatfeldolgozást, printf kiíratást, memória allokációt. Megfontolandó az RTOS Timer használata a Timeout helyett, mert akkor nem megszakítási szinten történik a visszahívás.2. táblázat: A Timeout objektumosztály tagfüggvényei
| Függvény |
Használat |
|---|---|
| Timeout név |
Létrehoz egy "név" nevű Timeout objektumot. |
| attach(*fptr,time) |
A Timeout objektumhoz
rendel egy visszahívási függvényt és megadja a visszahívási időt. Az
időt lebegőpontos formátumban, másodpercben adjuk meg. |
| attach_us(*fptr,time) |
A Timeout objektumhoz rendel egy visszahívási függvényt és megadja a visszahívási időt. Az időt unsigned int formátumban, mikroszekundumokban adjuk meg. |
| detach() |
Megszünteti a visszahívási függvény hozzárendelését az időzítőhöz |
Mintapélda: LED állapotváltás Timeout használatával
Az alábbi programban, amely az mbed Handbook Timeout_HelloWorld mintapéldája, a Timeout absztrakt időzítőt arra használjuk, hogy egy visszahívási függvény segítségével adott idő eltelte után LED2 állapotát átváltsuk, miközben a főprogram végtelen ciklusában LED1 villogtatása zajlik folyamatosan.Hardver követelmények:
- FRDM-KL25Z kártya
#include "mbed.h"
Timeout flipper;
DigitalOut led1(LED1,1); // LED_RED
DigitalOut led2(LED2,1); // LED_GREEN
void flip() { // callback function
led2 = !led2; // flip state of LED_GREEN
}
int main() {
flipper.attach(&flip, 2.0); // setup flipper to call flip after 2 seconds
// spin in a main loop. flipper will interrupt it to call flip
while(1) {
led1 = !led1; // blink LED_RED
wait(0.2); // by 2.5 Hz (0.2s ON, 0.2s OFF)
}
}
A programban nem használunk sem extra könyvtárakat, sem lebegőpontos kiírást, ezért az "alapértelmezett" Makefile-t használjuk.
A 08_timeout_helloworld projekt Makefile állománya:
PROJECT := 08_timeout_helloworld
DEVICES := KL25Z
GCC4MBED_DIR := ../..
GCC4MBED_TYPE := Release
MBED_OS_ENABLE := 0
NEWLIB_NANO := 1
NO_FLOAT_SCANF := 1
NO_FLOAT_PRINTF := 1
include $(GCC4MBED_DIR)/build/gcc4mbed.mk
A program elindításakor (bekapcsolás, vagy RESET után) először csak LED1
(a piros LED) villog 2.5 Hz
frekvenciával. Kb. 2 másodperc után LED2
(a zöld LED) is bekapcsol a flip függvény meghívásának hatására, s az
újabb RESET-ig folyamatosan világít.A Ticker objektumosztály
A Ticker objektumosztály segítségével olyan "ébresztőórát" hozhatunk létre, amellyel periodikusan, adott időközönként aktiválhatjuk az objektumhoz rendelt visszahívási (callback) függvényt, a főprogram futását megszakítva. Mivel a visszahívási függvény programmegszakítási szinten aktiválódik, ügyelnünk kell rá, hogy a visszahívási függvény ne tartalmazzon blokkoló várakozást, terjedelmes adatfeldolgozást, printf kiíratást, memória allokációt.Nem árulunk el nagy titkot azzal, ha elmondjuk, hogy a Timeout és a Ticker objektumosztály lényegében megegyezik, kizárólag a visszahívási függvény aktiválásában térnek el: Timeout esetén csak egyszer történik aktiválás, a Ticker esetében pedig minden aktiváláskor "újra felhúzzuk az ébresztőt", ezért periodikusan ismétlődik az aktiválás.
A különbséget megfigyelhetjük, ha a 2. listában bemutatott programban a Timeout flipper; sort kicseréljük erre: Ticker flipper;
3. táblázat: A Ticker objektumosztály tagfüggvényei
| Függvény |
Használat |
|---|---|
| Ticker név |
Létrehoz egy "név" nevű Ticker objektumot. |
| attach(*fptr,time) |
A Ticker objektumhoz
rendel egy visszahívási függvényt és megadja a visszahívási időt. Az
időt lebegőpontos formátumban, másodpercben adjuk meg. |
| attach_us(*fptr,time) |
A Ticker objektumhoz rendel egy visszahívási függvényt és megadja a visszahívási időt. Az időt unsigned int formátumban, mikroszekundumokban adjuk meg. |
| detach() |
Megszünteti a visszahívási
függvény hozzárendelését az időzítőhöz |
Mintapélda: Nyomógomb pergésmentesítése Ticker használatával
Az alábbi programban egy Ticker időzítő segítségével periodikusan mintavételezzük egy nyomógomb állapotát. Ha a mintavételezések közötti időtartam nagyobb, mint a kontaktusok pergési ideje (10-15 ms), akkor a mintavételezéssel megoldhatjuk a nyomógomb pergésmentesítését. A nyomógombbal egy LED-et kapcsolgatunk ki és be. A nyomógomb kezelés vonatkozásban az alábbi program a Programmegszakítások fejezetben található 07_button_interrupt mintaprogram javított változatának tekinthető.Hardver követelmények:
- FRDM-KL25Z kártya
- A D3 (PTA12) bemenet és
a GND közé egy nyomógombot
kell kötni a 2. ábra szerint.
#include "mbed.h"
DigitalIn button(D3,PullUp); // Pusbutton input
DigitalOut led(LED_BLUE); // LED output (the blue LED)
Ticker sampler; // Ticker for button state sampling
volatile uint8_t button_state = 1; // Initially released
void button_check()
{
button_state = (button_state<<1) | (button & 1); // shift in button state
if((button_state & 3)==2) { // Check for H -> L transition
led = !led; // Switch LED state
}
}
int main()
{
led = 1; // LED off
sampler.attach(&button_check,0.02); // sample button state in each 20 ms
while (true) {
wait(1); // do nothing
}
}
A 08_button_debouncer projekt Makefile állománya:
PROJECT := 08_button_debouncer
DEVICES := KL25Z
GCC4MBED_DIR := ../..
GCC4MBED_TYPE := Release
MBED_OS_ENABLE := 0
NEWLIB_NANO := 1
NO_FLOAT_SCANF := 1
NO_FLOAT_PRINTF := 1
include $(GCC4MBED_DIR)/build/gcc4mbed.mk
A nyomógomb figyelésére a D3
kivezetést digitális bemenetként konfiguráljuk és bekapcsoljuk a belső
felhúzást. A bemenet állapotát 20 ms-onként mintavételezzük, s jobbról
beléptetjük a button_state
változó legalsó bitjébe (1: a
gomb felengedve, 0: a gomb
lenyomva). Amikor lenyomás történik, akkor a button_state változó utolsó
két
bitjén '10'
bitkombinációnak kell lennie: (button_state & 3)==2). A fenti
programban ehhez az eseményhez rendeltük a LED állapotának
átkapcsolását.A FRDM-KL25Z valós idejű órája (RTC)
Az MKL25Z128VLK4 mikrokontroller tartalmaz hardveres valós idejű óra (RTC) perifériát, azonban a FRDM-KL25Z fejlesztőkártya sajátos felépítése miatt annak használata különös figyelmet igényel.A FRDM-KL25Z kártyára gyárilag nem terveztek 32,768 kHz-es RTC kristályt az RTC számára A gyári elképzelés szerint az RTC működéséhez szükséges 32 kHz-es órajelet a kártyán található OpenSDA hibakereső/programozó áramkör mikrovezérlője állította elő a saját PTD6 kimenetén, amit a nyomtatott áramkörön az R24 pozíciójú átkötésen keresztül vezettek át a céláramkör PTC1 (RTC_CLKIN) bemenetére.
Ez a hardveres órajel-továbbítás csak a korai Freescale OpenSDA firmware-ekkel, valamint az Mbed 20140530-as kiadású OpenSDA interface firmware-ével (amely még Windows 7 alatti környezethez készült) működött. A későbbi Mbed / DAPLink OpenSDA firmware-frissítésekből és az általunk is használt Segger JLink OB azonban a 32 kHz-es órajelkimenet szoftveres támogatása hiányzik. Ennek következtében a modern debugger firmware-t futtató FRDM-KL25Z kártyákon az RTC_CLKIN vonal órajel nélkül marad.
Működtetési lehetőségek és szoftveres beállítások
A FRDM-KL25Z kártya esetében az RTC hardveres órajel-ellátására két gyakorlati megoldás létezik:- Külső 32 kHz-es órajel bevezetése: Egy külső jelgenerátorból (esetünkben egy DS3231 óramodulból) származó 32,768 kHz-es órajelet kötünk közvetlenül a targetáramkör PTC1 (A5) lábára.
- Belső 32 kHz-es órajel átvezetése (PTC3 → PTC1): A mikrokontroller belső MCGIRCLK (32 kHz) óráját szoftveresen kivezetjük a PTC3 lábra, majd egy külső áthidaló vezetékkel összekötjük a PTC3 és PTC1 kivezetéseket.
4.a ábra: Külső 32 kHz-es órajel bevezetése a PTC1 bemenetre
4.b ábra: Belső órajel (PTC3) átvezetése a PTC1 bemenetre
A gcc4mbed build rendszer és a rendszerszintű kód módosítása
1. A DEVICE_RTC támogatás engedélyezése
Ha használni kívánjuk az RTC-t, a gcc4mbed/build mappában található KL25Z-device.mk konfigurációs állományban a PERIPHERALS_FOR_DEVICE makrólistát ki kell egészítenünk a DEVICE_RTC taggal:PERIPHERALS_FOR_DEVICE := DEVICE_RTC DEVICE_ANALOGIN DEVICE_ANALOGOUT ... stb.
2. A belső órajel-átvezetés beállítása (ez csak a PTC3 → PTC1 módszer esetén kell)
A belső órajel kiültetéséhez a rendszerindító kódot kell kiegészítenünk. A gcc4mbed\external\mbed-os\targets\TARGET_Freescale\TARGET_KLXX\TARGET_KL25Z\device mappában található system_MKL25Z4.c forrásfájl SystemInit() függvényének az elejére be kell toldani az alábbi regiszterbeállításokat:/* =================================================================== */
/* RTC_CLKIN route (A KL26Z gyári mintája alapján) */
/* =================================================================== */
/* 1. Port C órajelének engedélyezése a MUX beállításokhoz */
SIM->SCGC5 |= SIM_SCGC5_PORTC_MASK;
/* 2. PTC1 konfigurálása: ISF=0, MUX=1 (RTC_CLKIN) */
PORTC->PCR[1] = (uint32_t)((PORTC->PCR[1] & (uint32_t)~(uint32_t)(
PORT_PCR_ISF_MASK |
PORT_PCR_MUX(0x07)
)) | (uint32_t)(
PORT_PCR_MUX(0x01)
));
/* 3. PTC3 konfigurálása: ISF=0, MUX=5 (CLKOUT) */
PORTC->PCR[3] = (uint32_t)((PORTC->PCR[3] & (uint32_t)~(uint32_t)(
PORT_PCR_ISF_MASK |
PORT_PCR_MUX(0x07)
)) | (uint32_t)(
PORT_PCR_MUX(0x05)
));
/* 4. CLKOUT kimenetre az MCGIRCLK (belső 32 kHz) rákötése */
SIM->SOPT2 = (SIM->SOPT2 & (uint32_t)~(uint32_t)SIM_SOPT2_CLKOUTSEL_MASK) |
SIM_SOPT2_CLKOUTSEL(0x04);
/* 5. RTC órajelforrás átállítása RTC_CLKIN-re (0b10) */
SIM->SOPT1 = (SIM->SOPT1 & (uint32_t)~(uint32_t)SIM_SOPT1_OSC32KSEL_MASK) |
SIM_SOPT1_OSC32KSEL(0x02);
/* =================================================================== */
Fontos megjegyzés: A fenti módosítások elvégzése után a lefordított Mbed könyvtárakat újra kell építenünk. Ezért a saját projektünk fordítása előtt a parancssorban ki kell adni a következő parancsot:
make clean-mbed
Ezt követően a projekt fordításakor a build rendszer az Mbed OS keretrendszert és a frissített system_MKL25Z4.c
állományt is újrafordítja, így az RTC API funkciói használhatóvá válnak.
💡 Megjegyzés az előfordított mintaprogramokhoz:
Az RTC-vel kapcsolatos mintaprogramokhoz mellékelt előfordított bináris állományokat a 2. opcióval fordítottuk le. Ez azt jelenti, hogy a kipróbálásukhoz nincs szükség külső órajel-generátorra: a mikrovezérlő a PTC3 kimeneten kiadja az RTC-hez szükséges 32 kHz-es órajelet. A mintaprogramok futtatásához elegendő egy áthidaló vezetékről gondoskodni a PTC3 és a PTC1 (Arduino A5) kivezetések között!
Az RTC kezelése a Time funkciók és a C könyvtár
használatával
Az RTC valósidejű óra kezeléséhez az alapértelmezett mbed API nem
tartalmaz C++
objektumosztályt. A korábbi Mbed Handbook által említett Time
felület valójában a standard C könyvtárből örökölt C függvényekből áll, melyekhez a céláramkörök RTC hardverének
kezelésére
(regiszterek beállítása, illetve kiolvasása) szolgáló Mbed HAL függvények társulnak. Az aktuális idő az 1970. január 1. óta eltelt másodperceket jelenti (UNIX timestamp), a tárolása time_t "timestamp" (időbélyeg) típusú változókban történik. Az idő kiíratáshoz, emberi léptékű kezeléshez a tm struktúra típust használjuk, amelyben külön-külön változókban szerepel az év, a hónap, a nap, az óra, a perc és a másodpercek.
3. táblázat: A Time függvények listája
| Függvény |
Használat |
|---|---|
| time(NULL) |
Az aktuális idő kiolvasása. A
visszatérési érték timestamp típusú |
| set_time(time_t t) |
Az aktuális idő beállítása |
| mktime(struct tm * t) |
Egy tm struktúra átalakítása timestamp formátumra. A paraméter tm struktúrára mutató pointer, a visszatérési érték timestamp típus. |
| localtime(time_t
* t) |
Egy timestamp átalakítása tm struktúrába (a visszatérési érték pointer!) |
| ctime(time_t
* t) |
Egy timestamp olvasható szöveggé
történő konverziója. Pl. “Wed Oct 28 11:35:37 2009\n” |
| strftime(char
* buffer, size_t max, const char * format, const struct tm * t) |
Egy tm struktúra testreszabható
formátumú szöveggé alakítása |
time - Az aktuális idő kiolvasása a targetáramkör RTC moduljából, amelyet előzőleg a set_time() segítségével állíthatunk be. Az "aktuális idő" az 1970. január 1. óta eltelt másodperceket jelenti (UNIX timestamp).
#include "mbed.h"
int main() {
set_time(1256729737); // Set RTC time to Wed, 28 Oct 2009 11:35:37
while(1) {
time_t seconds = time(NULL);
printf("Time as seconds since January 1, 1970 = %d\n", seconds);
wait(1);
}
}
set_time - az aktuális idő beállítása
#include "mbed.h"
int main() {
set_time(1256729737); // Set time to Wed, 28 Oct 2009 11:35:37
}
mktime - Egy tm struktúrát időbélyeggé alakít, mely az 1970. január 1. óta eltelt másodperceket jelenti (UNIX timestamp). Egyúttal a tm struktúra tm_wday (a hét napja) és tm_yday (az év napja) elemeit is aktualizálja. Ügyeljünk rá, hogy az évszámokat 1900-hoz képest kell megadni, s a hónapok számozása 0-val kezdődik!
#include "mbed.h"
int main() {
// setup time structure for Wed, 28 Oct 2009 11:35:37
struct tm t;
t.tm_sec = 37; // 0-59
t.tm_min = 35; // 0-59
t.tm_hour = 11; // 0-23
t.tm_mday = 28; // 1-31
t.tm_mon = 9; // 0-11
t.tm_year = 109; // year since 1900
// convert to timestamp and display (1256729737)
time_t seconds = mktime(&t);
printf("Time as seconds since January 1, 1970 = %d\n", seconds);
}
localtime - egy time_t típusú időbélyeg értékét egy (statikusan allokált) tm struktúrába konvertálja.
#include "mbed.h"
int main() {
time_t seconds = 1256729737;
struct tm *t = localtime(&seconds);
}
ctime - egy time_t időbélyeg formában megadott időpontot olvasható formátumú dátum/idő szöveggé alakít. Az eredmény ilyen formátumú lesz: “Wed Oct 28 11:35:37 2009\n”.
#include "mbed.h"
int main() {
time_t seconds = time(NULL);
printf("Time as a string = %s", ctime(&seconds));
}
strftime - egy tm struktúra formájában megadott időpont testreszabható formátumú szöveggé alakítása. Az strftime függvény abban különbözik a ctime függvénytől, hogy time_t helyett tm struktúrát vár, s nem kötött formátumú az eredmény, hanem általunk megadott formátumú.
#include "mbed.h"
int main() {
time_t seconds = time(NULL);
char buffer[32];
strftime(buffer, 32, "%I:%M %p\n", localtime(&seconds));
printf("Time as a formatted string = %s", buffer);
}
A formátum megadásához az alábbi szövegelemeket használhatjuk:| %S | Second (másodperc 00-59) |
| %M |
Minute (perc 00-59) |
| %H |
Hour (óra 0-23) |
| %d |
Day (nap 01-31) |
| %m |
Month (hónap 01-12) |
| %Y/%y |
Year (év 2009, illetve 09
formátumban) |
| %A/%a |
Weekday name (hét napjának neve
Monday, illetve Mon formátumban) |
| %B/%b |
Month name (hónap neve January,
illetve Jan formátumban) |
| %I |
12 Hour format (12 órás kijelzés
00-12) |
| %p |
AM vagy PM |
| %X |
Time (idő 14:55:02 formátumban) |
| %x |
Date (dátum 02/05/09 formában
February 5, 2009 esetén) |
Mintapélda: Az RTC működésének ellenőrzése
A korábban elvégzett rendszerszintű és hardveres módosítások helyességét az alábbi tesztprogram segítségével ellenőrizhetjük. A mintaprogram nemcsak az Mbed OS magas szintű időkezelő függvényeinek (set_time, time, ctime) működését mutatja be, hanem közvetlen betekintést nyújt a mikrokontroller RTC perifériájának belső regisztereibe is.A diagnosztikai kód a következő lépések szerint ellenőrzi a rendszert:
- A DEVICE_RTC makró ellenőrzése (#if defined): A fordítási feltételek kiértékelésével igazolja, hogy a build rendszerben érvényesült-e a DEVICE_RTC támogatás engedélyezése.
- Nyers hardverállapot kiolvasása: Az órajelkapu (SIM->SCGC6) bekapcsolása után még az Mbed API meghívása előtt kiolvassa a hardveres másodpercszámláló (RTC->TSR) és az állapotregiszter (RTC->SR) kezdőértékét. Ez kideríti, hogy a periféria érvénytelen állapotban (Time Invalid Flag) indult-e.
- Időbeállítás az Mbed API segítségével: A set_time() függvény meghívásával egy tetszőleges UNIX időbélyeget töltenünk a számlálóba. Ez a lépés sikeres működés esetén törli a hibajelző bitet és engedélyezi a számlálást (TCE bit).
- Folyamatos diagnosztikai hurok: A főhurokban másodpercenként kiíratjuk a hardveres regiszterek aktuális tartalmát és a magas szintű time() függvény által visszaadott, formázott dátumstringet. Így közvetlenül megfigyelhető, hogy a számláló lépked-e, valamint hogy a LED felvillanása jelzi-e a hurok futását.
Hardver követelmények:
- FRDM-KL25Z kártya
- 32 kHz-es órajel a PTC1 (A5) bemenetre kötve
4. lista: A 08_rtc_test/main.cpp program listája
#include "mbed.h"
Serial pc(USBTX, USBRX);
DigitalOut led(LED1, 1); // Aktív alacsony: 1 = kikapcsolva indul
int main() {
pc.baud(9600);
wait_ms(100);
// 1. Makró ellenőrzése
#if defined(DEVICE_RTC)
pc.printf("DEVICE_RTC definiált (%d)\r\n", DEVICE_RTC);
#else
pc.printf("HIBA: DEVICE_RTC nincs definialva!\r\n");
#endif
// 2. Órajelkapu és kezdőértékek (set_time előtt)
SIM->SCGC6 |= SIM_SCGC6_RTC_MASK;
pc.printf("Start TSR: %u | SR: 0x%02X\r\n", (unsigned int)RTC->TSR, (unsigned int)RTC->SR);
// 3. Mbed API időbeállítás a set_time() függvénnyel
set_time(1786606200);
pc.printf("Set TSR: %u | SR: 0x%02X\r\n\r\n", (unsigned int)RTC->TSR, (unsigned int)RTC->SR);
// 4. Folyamatos diagnosztikai hurok
while (1) {
time_t seconds = time(NULL);
// Egyetlen áttekinthető sorsor az állapotról
pc.printf("TSR: %u | SR: 0x%02X time: %s",
(unsigned int)RTC->TSR, (unsigned int)RTC->SR, ctime(&seconds));
led = !led; // Vizuális visszajelzés
wait_ms(1000); // 1 másodperces ütemezés
}
}
A 08_rtc_test projekt Makefile állománya
PROJECT := 08_rtc_test
DEVICES := KL25Z
GCC4MBED_DIR := ../..
GCC4MBED_TYPE := Release
MBED_OS_ENABLE := 0
NEWLIB_NANO := 1
NO_FLOAT_SCANF := 1
NO_FLOAT_PRINTF := 1
include $(GCC4MBED_DIR)/build/gcc4mbed.mk
Futási eredmények és diagnosztika
A program futtatása során két jellemző kimenettel találkozhatunk a soros terminálon:1. Sikeres működés (megfelelő órajel-ellátás és konfiguráció esetén):
Amennyiben a hardveres órajel (akár külső forrásból, akár a PTC3 -> PTC1 áthidalásról) rendelkezésre áll és a DEVICE_RTC támogatás engedélyezve van, a TSR regiszter értéke másodpercenként növekszik, az SR állapotregiszter pedig 0x10 értéket mutat (a TCE bit aktív, a TIF hiba törölve):
5. ábra: Sikeres RTC teszt futási eredménye
2. Hibás működés a hiányzó DEVICE_RTC makródefiníció esetén:
Amennyiben a KL25Z-device.mk állományban elmulasztottuk a DEVICE_RTC makró felvételét, a fordító nem építi be az RTC támogatás az Mbed API-ba. Ebben az esetben a tesztprogramunk jelzi a makró hiányát, a set_time() és time() függvények hívása hatástalan marad, és a számláló regiszter (TSR) értéke sem frissül:
6. ábra: A futási eredmény hiányzó RTC támogatást jelez
Mintapélda: Az RTC kipróbálása
A Time programkönyvtár függvényeinek használatára mutatunk néhány egyszerű példát. A programban az alábbi lépéseket próbáljuk ki:- RTC beállítása egy adott időpontra (itt 28 Oct 2009 11:35:37) a set_time() függvény segítségével.
- Az aktuális idő kiolvasása a time() függvénnyel.
- Dátum és idő kiíratása kötött formátumban a ctime() függvény használatával.
- Idő kiíratása általunk előírt formátumban a localtime() és strftime() függvények használatával
Hardver követelmények:
- FRDM-KL25Z kártya
- 32 kHz-es órajel a PTC1 (A5) bemenetre kötve
#include "mbed.h"
int main()
{
set_time(1256729737); // Set RTC time to Wed, 28 Oct 2009 11:35:37
while(1) {
time_t seconds = time(NULL);
printf("Time as seconds since January 1, 1970 = %d\n", seconds);
printf("Time as a basic string = %s", ctime(&seconds));
char buffer[32];
strftime(buffer, 32, "%I:%M %p\n", localtime(&seconds));
printf("Time as a custom formatted string = %s", buffer);
wait(1);
}
}
A 08_rtc_proba projekt Makefile állománya:
PROJECT := 08_rtc_proba
DEVICES := KL25Z
GCC4MBED_DIR := ../..
GCC4MBED_TYPE := Release
MBED_OS_ENABLE := 0
NEWLIB_NANO := 1
NO_FLOAT_SCANF := 1
NO_FLOAT_PRINTF := 1
include $(GCC4MBED_DIR)/build/gcc4mbed.mk
A program futási eredménye az alábbi ábrán látható:
7. ábra: A 08_rtc_proba program futási eredménye
Mintapélda: az RTC használata
Az előző programban csak egy önkényesen választott időpontra állítottuk be az RTC óráját. Az alábbi programmal tetszés szerinti időpontot, tehát a tényleges időt is megadhatjuk a soros porton keresztül. Sajnos, a FRDM-KL25Z kártya RTC órája csak korlátozottan használható (nincs sem telepes póttápegysége sem saját órejel generátora, hogy a kártya kikapcsolt állapotában is mérje az időt), de az alábbi programmal legalább beállítástól kikapcsolásig nyomon követhetjük az idő múlását. A kiíratásokhoz nyissunk egy terminálablakot és kapcsolódjunk az OpenSDA virtuális soros portjához az alapértelmezett formátummal és sebességgel (8N1, 9600 bps)!Az RTC beállításához egy TYYMMDDhhmmss parancsot kell kiküldenünk a terminálablakból, ahol:
T - a kötelező parancsbetű (T mint Time)
YY - az évszám utolsó két jegye (2016-ban ez 16)
MM - a hónap sorszáma (Január esetében 01)
DD - a nap (01 - 31 közötti szám)
hh - az óra (00 - 23 közötti szám)
mm - a perc (00 - 59 közötti szám)
ss - a másodperc (00 - 59 közötti szám)
Hardver követelmények:
- FRDM-KL25Z kártya
#include "mbed.h"
Serial pc(USBTX, USBRX);
DigitalOut myled(LED1,1);
Ticker myticker;
volatile uint8_t myflag = 0;
void processSerialCommand() {
if (pc.getc() == 'T') {
struct tm tme = {0};
int year, mon;
if (pc.scanf("%2d%2d%2d%2d%2d%2d", &year, &mon, &tme.tm_mday,
&tme.tm_hour, &tme.tm_min, &tme.tm_sec) == 6)
{
tme.tm_year = year + 100; // YY -> 20YY (1900-as eltolás)
tme.tm_mon = mon - 1; // 1-12 -> 0-11
set_time(mktime(&tme));
time_t t = time(NULL);
pc.printf("RTC set to: %s", ctime(&t)); // ctime tartalmaz újsort!
}
}
while (pc.readable()) pc.getc(); // Puffer ürítése
}
int main() {
set_time(1786606200);
myticker.attach([](){ myflag = 1; }, 5); // Ticker Lambda kifejezéssel
while (1) {
if (pc.readable()) {
processSerialCommand();
}
if (myflag) {
myflag = 0;
myled = !myled;
time_t mytime = time(NULL);
pc.printf("RTC time: %s", ctime(&mytime));
}
}
}
A 08_rtc_example projekt Makefile állománya:
PROJECT := 08_rtc_example
DEVICES := KL25Z
GCC4MBED_DIR := ../..
GCC4MBED_TYPE := Release
MBED_OS_ENABLE := 0
NEWLIB_NANO := 1
NO_FLOAT_SCANF := 1
NO_FLOAT_PRINTF := 1
include $(GCC4MBED_DIR)/build/gcc4mbed.mk
A program futási eredménye az alábbi ábrán látható. A kép felsó sorában
a beírt parancs is látható, melynek hatására a pirossal aláhúzott
válaszüzenet jelent meg.
8. ábra: A 08_rtc_example program futási eredménye
A WakeUp objektumosztály
Többé-kevésbé az időzítők témaköréhaz tartozik az Erik Olieman által írt WakeUp objektumosztály is, amely az LPTMR időzítő segítségével előre beállítható idő eltelével felébreszti a mikrovezérlőt az energiatakarékos "mélyalvás" üzemmódból. Ennek különösen telepes (elem vagy akkumulátor) táplálás esetén van jelentősége, hiszen ha tétlen állapotban lekapcsoljuk a mikrovezérlőt, jelentősen csökkenthetjük az energiaigényt, s akkor ugyanazzal az elemmel/akkumulátorral hosszabb üzemidőt érhetünk el.5. táblázat: A WakeUp objektumosztály tagfüggvényei
| Függvény |
Használat |
|---|---|
| set(time) |
Beállítja az időzítést az egész
másodpercekben megadott időre |
| set_ms(time) |
Beállítja az időzítést az
ezredmásodpercekben megadott időre |
| attach(*fptr) |
Visszahívási függvényt rendel az
időzítőhöz. Ha csak ébreszteni akarjuk az MCU-t, akkor nincs szükségünk
erre a funkcióra! |
| calibrate() |
A főoszcillátorhoz kalibrálja az
LPTMR óráját. Figyelem: a kalibrálás 100 ms blokkoló várakozással jár! |
⚠️ FONTOS KOMPATIBILITÁSI MEGJEGYZÉS
Figyelem! A GCC fordítóra és az Mbed OS rev142-re való áttérés miatt az eredeti WakeUp könyvtár architektúrája (C++ Callback rendszer, dinamikus NVIC_SetVector) működésképtelenné vált – nemcsak KL25Z-n, hanem más mikroverérlő kártyákon (pl. STM32 / NUCLEO) is.
A könyvtárat leegyszerűsítve átírtuk a FRDM-KL25Z kártyára, így sikerült ugyan működésre bírni, de csak jelentős korlátozásokkal:
- LPTMR periféria-ütközés: A KL25Z-n az LPTMR időzítőt a Ticker osztály is használja. Az eredeti könyvtár próbálta elmenteni és visszaállítani a regiszterek állapotát, az általunk "újraélesztett", lecsupaszított változat viszont teljesen kisajátítja az LPTMR számlálót, így a WakeUp könyvtár nem használható együtt a Ticker objektumosztállyal.
- Kieső funkciók: Az attach() / callback visszahívási rendszer és a calibrate() hatástalan, dummy függvények.
- Megszűnt hordozhatóság: A WakeUp könyvtárnak kizárplag a FRDM-KL25Z kártyát használó részét írtuk át, a többi (más hardvert használó) részt változatlanul hagytuk benne.
Mintapélda: WakeUp használata
Az alábbi programban egy LED-et villogtatunk úgy, hogy a LED kikapcsolt állapotában a mikrovezérlőt is "mélyalvás" állapotba helyezzük, amelyből a WakeUp segítségével ébresztjük fel, az időzítés letelte után. "Mélyalvás" állapotban az időzítők közül csak az LPTMR működik, ezért a WakeUp objektumosztály is ezt használja.Megjegyzés: A program elején egy rövid, pl. 200 ms-os várakozást (wait_ms(200);) kell beiktatni. Ez biztosítja hogy az LPTMR számláló órajel forrásául használt LPO (1 kHz) oszcillátor biztonságosan beinduljon és stabilizálódjon a deepsleep() alvási módba lépés előtt.
A 08_wakeup program lényegében Erik Olieman WakeUp mintaprogramja, minimális változtatásokkal. A program LED1-et (a FRDM-KL25Z kártyán ez LED_RED-et jelenti) 2 másodpercig kikapcsolt, majd 1 másodpercig bekapcsolt állapotban tartja. A program lefordításához a WakeUp programkönyvtárat importálni kellett, s a program elején a WakeUp.h fejléc állományt be kellett csatolni.Hardver követelmények:
- FRDM-KL25Z kártya
#include "mbed.h"
#include "WakeUp.h"
DigitalOut myled(LED1); //LED_RED
int main() {
//The low-power oscillator can be quite inaccurate on some targets
//this function calibrates it against the main clock
WakeUp::calibrate();
wait_ms(200);
while(1) {
myled = 1; //Switch LED off
WakeUp::set_ms(2000); //Set wakeup time for 2 seconds
deepsleep(); //Enter deepsleep mode
//-- zzZZ sleep here for 2 seconds -------------------
myled = 0; //Switch LED on after wakeup
wait(1); //Run state for 1 sec
}
}
A 08_wakeup projekt Makefile állománya:
PROJECT := 08_wakeup
DEVICES := KL25Z
GCC4MBED_DIR := ../..
GCC4MBED_TYPE := Release
USER_LIBS := ../../external/mbed-libs/WakeUp
MBED_OS_ENABLE := 0
NEWLIB_NANO := 1
NO_FLOAT_SCANF := 1
NO_FLOAT_PRINTF := 1
include $(GCC4MBED_DIR)/build/gcc4mbed.mk
Megjegyzés: A fenti programban
a deepsleep()
függvényhívással a mikrovezérlőt "mélyalvás" energiatakarékos módba
állítjuk, s itt a programfutás megáll. "Felébredés" (wakeup) után a
program a deepsleep()
függvényhívást követő első utasítással folytatódik.