Nerräkningstimer kontra system-timer, tankar
Postat: 27 februari 2017, 12:43:40
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.