Nerräkningstimer kontra system-timer, tankar
Nerräkningstimer kontra system-timer, tankar
Jag hamnar ofta i situationer där en funktion ska utföras - men att utförandet tar en viss tid.
T.ex. ska strömmen slås på en extern enhet och den ska hinna starta upp. Tänk el-motor som sedan ska hinna varva upp.
Jag använder en 32 bitars µC på 50MHz och jag kan helt enkelt inte leva med att den står still under tiden den väntar på att den externa enheten "kommer igång". Jag förordar ju också kraftigt att alla Delay() i programmering är en styggelse som inte får hända.
Sedan satt jag och funderade lite på om inte det kunde löna sig med en system-timer i ett givet projekt, á la Arduino.
Fördelen är att man enkelt kan kolla tiden just nu och spara i en variabel med ett offset (Ska_Hända_När = Systemtid() + 20 sekunder).
I mainloop kan man sedan köra den vanliga:
if(Systemtid() >= Ska_Hända_När)
{
// Do whatever...
}
Men det finns ett problem med den lösningen: variabeln som utgör Systemtimer ska vara ett visst minimalt antal bitar för att få ett tidsförlopp som inte upprepar sig för ofta. Jag ämnar att använda en upplösning på 1ms, oavsett om min systemtimer kör med den hastighet eller inte varför en 32-bitars variabel "håller" lite drygt 49 dygn.
Under de 49 dygn fungerar det riktigt bra på detta sätt men sedan?
Ja, om man lägger till offset'en och kommer över vad variabeln klarar rullar den runt till noll + lite - men detta kommer då att utlösa en för tidig time-out. Ok, en gång per 49 dygn kan vara till att leva med, herregud, den rapade ju bara till...
Men det kan också vara en katastrof som kan hända pga. denna kända bugg/"feature" - och då har man planerat en katastrof.
Samtidig är en trög 8-bitar inte snabb på 32-bitars matte så det är ett problem också.
Det som är spiken i kistan för användandet av en systemtimer är att man inte! kan vara 100% säker på att main-loop kommer till avkänningen varenda timer-klick, hade man det kunde man ha använd:
if(Systemtid() == Ska_Hända_När)
{
}
Detta hade löst alla problem med systemtimern - men det fungerar ju inte!
================================================
Sedan finns den lösningen jag använder mig av: en nerräkningstimer.
Jag använder en timer-interrupt på "valfri" men fast frekvens. ISR'n som hör till räknar ner ett antal variabler om de är högre än noll, t.ex:
if(Delay_General) Delay_General--;
if(Delay_Key) Delay_Key--;
Såklart det antal jag behöver och är det två (eller fler) delay som inte kan köra samtidig kan jag använda samma variabel.
De är såklart deklarerat som volatile och har en storlek som medger att de kan hålla de tider jag kan behöva som mest.
Ett WORD (16 bit) kan hålla upp till 65535 ms - alltså 65,535 sekunder - vilket ofta räcker mycket långt.
För en 8-bit µC är en 16-bit variabel inte fullt så arbetstung som en 32-bit men viktigast av allt:
Det finns ingen buggar i detta sätt! Det fungerar likadan varje gång.
Sedan har jag en ovana att använda lite olika timer-tider, ibland 1ms, ibland 10ms. Under utvecklingen sker det att jag byter hastigheten.
Därför har jag en definition av "SYSTEM_CLOCK_SPEED" som kan vara 100 eller 1000 eller vad jag vill.
Jag gör en macro:
#define MILISEC(X) (((X) * SYSTEM_CLOCK_SPEED) / 1000)
När jag sedan anger tider är det enkelt:
Delay_General = MILISEC(250); // För 250 ms delay.
På detta sätt kan timerns hastighet ändras och en omkompilering av koden ger samma delay hela tiden utan att man ska pilla och ändra.
================================================
Det finns såklart möjlighet att använda hårdvaru-timer om man har hårdvara nog till detta, det är dock oftast på mer avancerade system detta kan finnas och det är inte där Arduino befinner sig.
Sedan kan hårdvara-timers vara svåra att få långsamma nog om man behöver längre tider.
T.ex. ska strömmen slås på en extern enhet och den ska hinna starta upp. Tänk el-motor som sedan ska hinna varva upp.
Jag använder en 32 bitars µC på 50MHz och jag kan helt enkelt inte leva med att den står still under tiden den väntar på att den externa enheten "kommer igång". Jag förordar ju också kraftigt att alla Delay() i programmering är en styggelse som inte får hända.
Sedan satt jag och funderade lite på om inte det kunde löna sig med en system-timer i ett givet projekt, á la Arduino.
Fördelen är att man enkelt kan kolla tiden just nu och spara i en variabel med ett offset (Ska_Hända_När = Systemtid() + 20 sekunder).
I mainloop kan man sedan köra den vanliga:
if(Systemtid() >= Ska_Hända_När)
{
// Do whatever...
}
Men det finns ett problem med den lösningen: variabeln som utgör Systemtimer ska vara ett visst minimalt antal bitar för att få ett tidsförlopp som inte upprepar sig för ofta. Jag ämnar att använda en upplösning på 1ms, oavsett om min systemtimer kör med den hastighet eller inte varför en 32-bitars variabel "håller" lite drygt 49 dygn.
Under de 49 dygn fungerar det riktigt bra på detta sätt men sedan?
Ja, om man lägger till offset'en och kommer över vad variabeln klarar rullar den runt till noll + lite - men detta kommer då att utlösa en för tidig time-out. Ok, en gång per 49 dygn kan vara till att leva med, herregud, den rapade ju bara till...
Men det kan också vara en katastrof som kan hända pga. denna kända bugg/"feature" - och då har man planerat en katastrof.
Samtidig är en trög 8-bitar inte snabb på 32-bitars matte så det är ett problem också.
Det som är spiken i kistan för användandet av en systemtimer är att man inte! kan vara 100% säker på att main-loop kommer till avkänningen varenda timer-klick, hade man det kunde man ha använd:
if(Systemtid() == Ska_Hända_När)
{
}
Detta hade löst alla problem med systemtimern - men det fungerar ju inte!
================================================
Sedan finns den lösningen jag använder mig av: en nerräkningstimer.
Jag använder en timer-interrupt på "valfri" men fast frekvens. ISR'n som hör till räknar ner ett antal variabler om de är högre än noll, t.ex:
if(Delay_General) Delay_General--;
if(Delay_Key) Delay_Key--;
Såklart det antal jag behöver och är det två (eller fler) delay som inte kan köra samtidig kan jag använda samma variabel.
De är såklart deklarerat som volatile och har en storlek som medger att de kan hålla de tider jag kan behöva som mest.
Ett WORD (16 bit) kan hålla upp till 65535 ms - alltså 65,535 sekunder - vilket ofta räcker mycket långt.
För en 8-bit µC är en 16-bit variabel inte fullt så arbetstung som en 32-bit men viktigast av allt:
Det finns ingen buggar i detta sätt! Det fungerar likadan varje gång.
Sedan har jag en ovana att använda lite olika timer-tider, ibland 1ms, ibland 10ms. Under utvecklingen sker det att jag byter hastigheten.
Därför har jag en definition av "SYSTEM_CLOCK_SPEED" som kan vara 100 eller 1000 eller vad jag vill.
Jag gör en macro:
#define MILISEC(X) (((X) * SYSTEM_CLOCK_SPEED) / 1000)
När jag sedan anger tider är det enkelt:
Delay_General = MILISEC(250); // För 250 ms delay.
På detta sätt kan timerns hastighet ändras och en omkompilering av koden ger samma delay hela tiden utan att man ska pilla och ändra.
================================================
Det finns såklart möjlighet att använda hårdvaru-timer om man har hårdvara nog till detta, det är dock oftast på mer avancerade system detta kan finnas och det är inte där Arduino befinner sig.
Sedan kan hårdvara-timers vara svåra att få långsamma nog om man behöver längre tider.
Re: Nerräkningstimer kontra system-timer, tankar
Rätt sätt att hantera det är att låta den wrappa, och använda modulär aritmetik för att göra jämförelsen.
Det innebär (om man kodar i C) att
- Använd "unsigned" typ så att overflow/underflow är definierat
- Beräkna och jämför intervall-längder, inte tidsstämplar
Dvs, för att det ska bli rätt, om du vill ha något som ska hända om 20 sekunder så gör
Det hanterar korrekt att systemtid/starttid wrappar runt.
Det innebär (om man kodar i C) att
- Använd "unsigned" typ så att overflow/underflow är definierat
- Beräkna och jämför intervall-längder, inte tidsstämplar
Dvs, för att det ska bli rätt, om du vill ha något som ska hända om 20 sekunder så gör
Kod: Markera allt
unsigned int start_tid = systemtid()
....
if (systemtid() - start_tid >= 20sekunder) {
...
}
- Jan Almqvist
- Inlägg: 1581
- Blev medlem: 1 oktober 2013, 20:48:26
- Ort: Orust
Re: Nerräkningstimer kontra system-timer, tankar
Blir inte detta villkor falskt under 20 sekunder vart 49:e dygn?
Re: Nerräkningstimer kontra system-timer, tankar
Nej. Säg att tiden är i s och start_tid är 0xffffffff, om t.ex. systemtid() wrappar och blir 0x00000000. då är differensen fortfarande 1. Det är det som är vitsen att köra med unsigned int att skillnaden mellan två tal blir den samma oavsett om du lägger på en offset på båda talen (om offseten är samma givetvis för båda).
edit: läste ditt inlägg igen och ser att man kan tolka det på fler sätt. Ja du har rätt att om vi sparar undan start_tid och kontrollerar med vår if-sats så kommer den att bli uppfylld felaktigt om klockan hunnit wrappa runt flera varv. Där måste man ju ha med en kontroll att vi redan har utfört funktionen en gång under första systemklockecykeln och inte skall utföra den fler gånger utan omstart av vår funktion.
edit: läste ditt inlägg igen och ser att man kan tolka det på fler sätt. Ja du har rätt att om vi sparar undan start_tid och kontrollerar med vår if-sats så kommer den att bli uppfylld felaktigt om klockan hunnit wrappa runt flera varv. Där måste man ju ha med en kontroll att vi redan har utfört funktionen en gång under första systemklockecykeln och inte skall utföra den fler gånger utan omstart av vår funktion.
- Jan Almqvist
- Inlägg: 1581
- Blev medlem: 1 oktober 2013, 20:48:26
- Ort: Orust
Re: Nerräkningstimer kontra system-timer, tankar
Visst har det varit en diskussion om detta tidigare?
Jag får inte ihop det, vi utgår från denna kod:
För enkelhets skull är både start_tid och systemtid() 0 och inte förrän efter 20s blir skillnaden >= 20.
Men, efter 49 dygn så wrappar systemtid() och börjar om på 0 eller hur?
Då blir välmindre än 20 igen?
Jag får inte ihop det, vi utgår från denna kod:
Kod: Markera allt
unsigned int start_tid = systemtid();
Kod: Markera allt
if (systemtid() - start_tid >= 20sekunder) {
Då blir väl
Kod: Markera allt
systemtid() - start_tid
Re: Nerräkningstimer kontra system-timer, tankar
Det stämmer nog ja, men man får väl komplettera med en flagga om det är en engångshändelse, då klarar man sig. Är det ett intervall så puttar man ju fram det 20s hela tiden och då funkar det också.
Re: Nerräkningstimer kontra system-timer, tankar
Ja, det är en begränsning.. du måste uppfylla två saker för att det ska funka:
1. Ingen väntetid/intervall kan vara längre än en wrap-around (om det är 32-bitars uint för millisekunder så är det 49 dygn)
2. Du måste kolla minst en gång per wrap-around tid, helst oftare.
I nästan alla tillämpningar är det inget problem.
För att förtydliga, start_tid är inte tänkt att vara _processorns_ starttid, utan tiden du börjar vänta på ditt event. Säg att du har en knapp som tänder en lampa och du 20 sekunder senare vill släcka den, då sätter du start_tid när du trycker knappen och kollar sen för att släcka den.
-M
1. Ingen väntetid/intervall kan vara längre än en wrap-around (om det är 32-bitars uint för millisekunder så är det 49 dygn)
2. Du måste kolla minst en gång per wrap-around tid, helst oftare.
I nästan alla tillämpningar är det inget problem.
För att förtydliga, start_tid är inte tänkt att vara _processorns_ starttid, utan tiden du börjar vänta på ditt event. Säg att du har en knapp som tänder en lampa och du 20 sekunder senare vill släcka den, då sätter du start_tid när du trycker knappen och kollar sen för att släcka den.
-M
- lillahuset
- Gått bort
- Inlägg: 13969
- Blev medlem: 3 juli 2008, 08:13:14
- Ort: Norrköping
Re: Nerräkningstimer kontra system-timer, tankar
Då kommer jag dragande med min gamla trotjänare igen.
Kod: Markera allt
/**
* @brief check if *timer has timed out
* @brief initialise *timer by a call with ticks = 0
* @param timer: pointer to timer variable
* @param ticks: number of ticks to wait
* @retval return 0 if not timeout, !0 if timeout
* @date 2012-12-14
*/
int timeout(uint32_t *timer, uint32_t ticks)
{
uint32_t t, diff;
int tout;
tout = 0;
t = getTicks();
diff = t - *timer;
if (0 == ticks || diff >= ticks) {
*timer = t;
tout = 1;
}
return tout;
} /* timeout */
- Jan Almqvist
- Inlägg: 1581
- Blev medlem: 1 oktober 2013, 20:48:26
- Ort: Orust
Re: Nerräkningstimer kontra system-timer, tankar
Mycket bra lösning. Du skriver kod som "De gamle" på samma sätt gör jag efter att ha lärt mig att man får inte slarva när det gäller säkerhetskritiska tillämningar.
Tummen upp!
PS. Du ditt sätt att skriva kod tyder på att du även använder Misra?
Tummen upp!
PS. Du ditt sätt att skriva kod tyder på att du även använder Misra?
- lillahuset
- Gått bort
- Inlägg: 13969
- Blev medlem: 3 juli 2008, 08:13:14
- Ort: Norrköping
Re: Nerräkningstimer kontra system-timer, tankar
Jan Almqvist: Vad som helst som hämtar något som räknas upp i lagom takt.
Jag brukar ha någon "systemtimer" som räknas upp mellan typ 100kHz och 10Hz beroende på behov.
hummel: Jag har tittat lite på Misra och kanske tagit lite intryck. Min filosofi har alltid varit att eftersom jag är måttligt smart gäller det att skriva kod som är lätt att förstå.
Jag brukar ha någon "systemtimer" som räknas upp mellan typ 100kHz och 10Hz beroende på behov.
hummel: Jag har tittat lite på Misra och kanske tagit lite intryck. Min filosofi har alltid varit att eftersom jag är måttligt smart gäller det att skriva kod som är lätt att förstå.
Re: Nerräkningstimer kontra system-timer, tankar
Du har helt rätt vad gäller att skriva kod. tänk om fler gjorde på det sättet!
- Swech
- EF Sponsor
- Inlägg: 4694
- Blev medlem: 6 november 2006, 21:43:35
- Ort: Munkedal, Sverige (Sweden)
- Kontakt:
Re: Nerräkningstimer kontra system-timer, tankar
Jag kör med nedräkningsvariabler men
låter ISRen även dela ned så man får
0.01 sek 100hz
0.1 sek 10hz
1.0 sek 1hz
så körs nedräkningen av variablerna i respektive kategori.
Med 8 bitar så brukar man få tillräcklig upplösning på det man vill ha.
t.ex. 0.3 sek knappfördröjning -> räkna ned från 30 i 0.01hz
5 sek tänd diod. -> räkna ned från 50 i 0.1hz
1 minut fördröjning -> räkna ned från 60 i 1.0hz
Swech
låter ISRen även dela ned så man får
0.01 sek 100hz
0.1 sek 10hz
1.0 sek 1hz
så körs nedräkningen av variablerna i respektive kategori.
Med 8 bitar så brukar man få tillräcklig upplösning på det man vill ha.
t.ex. 0.3 sek knappfördröjning -> räkna ned från 30 i 0.01hz
5 sek tänd diod. -> räkna ned från 50 i 0.1hz
1 minut fördröjning -> räkna ned från 60 i 1.0hz
Swech
Re: Nerräkningstimer kontra system-timer, tankar
Icekapp> Jag förordar ju också kraftigt att alla Delay() i programmering är en styggelse som inte får hända.
Det är ett felaktigt påstående.
I en enkel tillämpning kan det vara fullt tillräckligt att använda enkla fördröjningar med delay().
Det är ett felaktigt påstående.
I en enkel tillämpning kan det vara fullt tillräckligt att använda enkla fördröjningar med delay().
-
- EF Sponsor
- Inlägg: 920
- Blev medlem: 26 maj 2014, 12:54:35
- Ort: Karlskoga
Re: Nerräkningstimer kontra system-timer, tankar
Det finns dessutom inget som säger att delay() innebär en busy-loop och att allt annat stannar.
Ingår det i det gamla sättet att programmera att man inte skriver en enda kommentarsrad förutom några kryptiska taggar till doxygen så är jag mer emot det sättet än att använda delay()
Ingår det i det gamla sättet att programmera att man inte skriver en enda kommentarsrad förutom några kryptiska taggar till doxygen så är jag mer emot det sättet än att använda delay()