Időzítők, számlálók

A fejezet tartalma:

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:
A fenti feladatok közben konfliktushelyzet is kialakulhat: például egy külső esemény akkor kér kiszolgálást, amikor éppen egy ütemezett feladatot végez a rendszer. Ez azt jelenti, hogy a fenti követelmények még egy újabbal bővülnek: a beágyazott rendszereknek meg kell tudni különböztetni a sürgős kiszolgálást igénylő eseményeket a kevésbé sürgős feladatoktól.

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:

Összefoglalva: Az mbed környezetben az MKL25Z128VLK4 mikrovezérlő minden számlálója/időzítője adott feladatot lát el. A Systick időzítőt tehát csak akkor használhatjuk fel saját céljainkra, ha nem használjuk az mbed RTOS operációs rendszert. Timer0, Timer1, Timer2 közül csak azt használhatjuk fel saját célra, amelyek PWM csatornáit nem használjuk. PIT és LPTMR pedig csak akkor használhatók, ha nem használjuk sem a wait függvényeket, sem a WakeUp objektumosztályt, sem az alábbiakban tárgyalandó Timer, Timeout, Ticker objektumosztályokat.

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.
Megjegyzés: A read_ms() és read_us() tagfüggvények visszatérési értéke int típusú, ezért az absztrakt időzítők 231-1 µs-nál (kb. 2147 s) hosszabb időtartam mérésére nem alkalmasak.

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:

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
Megjegyzés: Az időzítések kezelése int32_t típusú változókban történik, ezért 231-1 µs-nál (kb. 2147 s) hosszabb időzítéseket ne adjunk meg!

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:
2. lista: A 08_timeout_helloworld/main.cpp program listája

#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
Megjegyzés: Az időzítések kezelése int32_t típusú változókban történik, ezért 231-1 µs-nál (kb. 2147 s) hosszabb időzítéseket ne adjunk meg!

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:
3. lista: A 08_button_debouncer/main.cpp program listája

#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:
  1. 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.
  2. Külső órajel bevezetése
    4.a ábra: Külső 32 kHz-es órajel bevezetése a PTC1 bemenetre

  3. 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. Belső órajel bevezetése
    4.b ábra: Belső órajel (PTC3) átvezetése a PTC1 bemenetre

Mivel mindkét megoldás leköti a targetáramkör ezen kivezetéseit a más célú felhasználás elől, a gcc4mbed build rendszer alapértelmezetten nem nem definiálja az RTC perifériát a KL25Z céláramkör esetében.

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
A függvények bonyolult paraméterezésének megértéséhez egy-egy rövid mintapéldát is bemutatunk.

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:

4. táblázat: Az strftime() függvénynél használható formátumelemek listája               
%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:
  1. 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.
  2. 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.
  3. 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).
  4. 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:

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):

RTC működésének ellenőrzése - Sikeres teszt
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:

RTC működésének ellenőrzése - Hiba eset
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:
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)!

Hardver követelmények:
5. lista: A 08_rtc_proba/main.cpp program listája
#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:
6. lista: A 08_rtc_example/main.cpp program listája

#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:
7. lista: A 08_wakeup/main.cpp program listája

    #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.