van egy mikrokontroller (atmega), amire keszul program. egy elmeleti(bb) jellegu problema, a kovetkezo" peldan keresztul: csinalunk egy egyszeru" timer-irq alapu, posix-szeru" gettimeofday()-jellegu realtime ora implementaciot, imigyen:
struct timeval
{ uint32_t tv_sec;
uint16_t tv_msec; /* usec helyett msec, eleg jo lesz ez is */
};
struct timeval timeofday={0,0};
ISR(TIMER0_OVF_vect)
{
static uint16_t cnt=0,inc;
cnt+=20; /* 20:9 == 32768*1000 : 14745600 */
/* == (cycle/irq) * finest_resolution : F_CPU */
inc=cnt/9;
cnt=cnt%9;
timeofday.tv_msec += inc;
if ( timeofday.tv_msec >= 1000 )
{ timeofday.tv_sec += (uint32_t)(timeofday.tv_msec/1000);
timeofday.tv_msec %= 1000;
}
}
int gettimeofday(struct timeval *tv)
{
tv->tv_sec = timeofday.tv_sec;
tv->tv_msec = timeofday.tv_msec;
return(0);
}
...
void main(void)
{
TCNT0 = 0x00;
TCCR0 |= 0x05;
TIMSK |= _BV(TOIE0);
sei(); /* by default, TIMER0_OVF_vect is called in every 32768th cycle */
...
struct timeval tv;
...
gettimeofday(&tv);
...
}
nomarmost kerdes, hogy abszolute biztosra hogyan kell ezt feljavitani? ugye ha a program kivancsi az idore, megkerdezi a gettimeofday()-t, ami belepakolja szepen a &tv strukturaba.
a gettimeofday()-ban viszont elofordulhat hogy az irq-t pont a ket ertekadas kozott hivja meg (vagy az egyik ertekadast szakitja felbe, ugye az uint32_t miatt mar az elso" sem atomi egy 8/16 bites vezerlon), ekkor nagy baromsagok is tortenhetnek (1 masodperces vagy akar 65536 sec ~1 napos tevedes is). ha viszont a gettimeofday()-ban letiltom/ujraengedem az strukturafeltoteses ertekadasok elott/utan a megszakitasokat (TIMSK &= ~_BV(TOIE0); illetve TIMSK |= _BV(TOIE0);), akkor meg lehet hogy irq-t vesztek, ami meg azert gaz mert \mu-c alatt ugye ritkan hasznal az ember blocking tipusu hivasokat, tehat pl egy fo"ciklus porgeseben ahol ido"t kell merni vmi miatt, ott a gettimeofday() kihivasa lesz a kontroller minden 3ik utasitasa, tehat az irq mondjuk az cycle-ido" 1/3-1/5-e'ben tiltva lesz.
szoval ma'r egy sima ora is ekkora zavarokat okoz, nem is beszelve az olyanokrol mint uart-nal, vagy ilyesmi (foleg ha irq-t kerunk a tx-re). timer-nel meg csakcsak nem dol ossze a vilag egy jarulekos drift miatt, de egy uart-nal mar nagyon nagy gazok lehetnek (adatvesztes, deadlock).
ezekre van valami jo veze'rcsel?
a.
- 6148 megtekintés
Hozzászólások
- gettimeofday() fugvenyben tiltsd ki a timer IRQ -t amig a timeofday valtozohoz hozzafersz.
- miutan gettimeofday() fuggvenyben atmasolod a timeofday valtozot, ellenorizd le, hogy a masolt ertek megegyezik e timeofday ertekeve. Ha nem, masold ujra.
- A hozzászóláshoz be kell jelentkezni
gettimeofday() fugvenyben tiltsd ki a timer IRQ -t amig a timeofday valtozohoz hozzafersz.
igy veszthetu"nk irq-t?
igazabol ez is egy nagy kerdes... vagyis ez is egy szuk keresztmetszet... vagy mi.
tfh, hogy a TCNT0 pont akkor csordul tul, ket ertekadas kozott. akkor ha ujra engedelyezzuk, akkor meghivja a irq signal handlert...?
- A hozzászóláshoz be kell jelentkezni
Hardver fuggo. Ki kell probalni, elveszik e az esemeny ilyen esetben. Alltalaban amugy nem, de lattam mar 1-2 hulye hardvert -t.
- A hozzászóláshoz be kell jelentkezni
hm, de viszont a masik otlet az jo. pl ez mennyire eletkepes?
struct some_irq_data
{ ...;
}
int some_irq_counter=0;
struct some_irq_data sirqd;
ISR(SOME_IRQ_vect)
{
some_irq_counter++;
do_something_with_data(&sirqd);
}
get_some_irq_info(some_irq_related *sir)
{
int cnt;
do
{ cnt=some_irq_counter;
...
sir->...=sirqd.... /* useful part */
...
} while ( cnt != some_irq_counter );
}
- A hozzászóláshoz be kell jelentkezni
A szamlalos megoldas is jo. Ja, es ne felejts el volatile tipus modositot tenni azokra a valtozokra amit IRQ -bol modositasz.
- A hozzászóláshoz be kell jelentkezni
volatile tipus modositot
akkor ez vegulis csak a some_irq_counter lenne, mert a masik (some_irq_data) ha modosul, akkor a counter is modosul, azt viszont ellenorizzuk. az "userspace"-ba meg ugyis a wrapperen keresztul megy ki, annak mar nem kell volatile-nek lennie.
- A hozzászóláshoz be kell jelentkezni
Ugyan nem kerted,de azert megugatnam a kododat. Praktikus lenne az IRQ -ban csak egy szamlalot novelgetni, es a gettimeofday() -ben elvegezni a szamitasokat. Igy jelentosen csokkanne az IRQ futasideje, azaz sokkal kevesebb idejig tartana fel az alacsonyabb prioritasi IRQ -kat. Masreszt nem biztos, hogy a / es a % megvalositasa reentrans. Ha ezekre a fordito fuggvenyt hiv, es pont egy futo / vagy % hivasba szakit bele, mindenfele ronda dolgok tortenhetnek.
- A hozzászóláshoz be kell jelentkezni
Praktikus lenne az IRQ -ban csak egy szamlalot novelgetni
hat, a 16bites ma'r kb ~2 perc alatt tulcsordulna. 32bites az ugyan kihuzza egy darabig (~110 nap), de lehet hogy ez is keves egyreszt masreszt meg 32bites valtozo novelese is gazos lehet 8/16-biten ha megszakad, illetve 32bites szamlalobol 32bites idot szamolni novekmenyes algoritmus nelkul viszont problema mert az mar 64bit kell. az ugye meg overkill egy ilyen kontrolleren ;)
btw miert baj ha az irq eltart egy darabig? irq-bol irq-t csak nem hiv ki a kontroller, gondolom van annak vmi egyszeru queue-ja, ami ezt lekezelni (nem tunik hw szempontbol bonyolultnak).
- A hozzászóláshoz be kell jelentkezni
btw miert baj ha az irq eltart egy darabig?
Általában nem jó programozási szokás, különösen mikrokontrolleren nem, ahol sokszor real-time-jellegű dolgokat is kell csinálni.
Ha van egy 10 utasításból álló kódod, amit egy 8 utasításból álló irq rutin meg tud szakítani, akkor 10-18 utasításnyi idő alatt fog a kód végrehajtódni. Ha az irq rutin 250 utasításnyi ideig tart (pl. az osztás sok mikrokontrolleren nem elemi utasítás, ezért baromi sokáig tud futni), akkor érezhetően kiszámíthatatlanná válik a 10 utasításos kód futási ideje, mert lehet, hogy 26x annyi ideig fog futni.
- A hozzászóláshoz be kell jelentkezni
Akkor eleg, ha 109 naponkent a fociklusod mindenkeppen lefuttet egy getimeofday() -t. Azaz ha van gettimeofday() hivas, akkor atszamolod a szamlalot, es ezt valahol (globalis valtozo jelzed). Ha az IRQ latja, hogy x ideig (109 nap?) nem volt ilyen hivas, beallit egy masik valtozot (tick_loss_warn=1), amire te valahol egy loop -ban hivsz egy gettimeofday() -t.
Igy az IRQ gyors es tulcsordulni sem fogsz. Ugyanakkor nehezebb az eleted, mert gondoskodnod kell a megfelelo "pollozasrol" a programodban. Mondjuk amig debuggolsz, az IRQ egyszeruen tudja ellenorizni, hogy tick_loss_warn==1 mennyi ideig all fent. Ha tul sokaig, tud hibat logolni. Ha nem akarsz rendesen debuggolni, hivhatsz az IRQ -bol is gettimeofday() -t. Ennek persze szep mellekhatasai lehetnek, mert ugye nem lehet elore megmondani mikor fordul elo, hogy az IRQ eztra hosszu ideig fut.
- A hozzászóláshoz be kell jelentkezni
btw miert baj ha az irq eltart egy darabig?
Addig nem baj, amig valamit nem kell idore csinalni. De pl ha az UART rx IRQ -janak le kell futnia ha torik, ha szakad 10mS -kel a karakter vetelehez kepest ahhoz, hogy a kovetkezo karaktert ne veszitsd el, akkor kezdodik a moka. Ha vannak nagyobb prioritasu IRQ -jaid, ezek feltarthatjak UART RX IRQ -t. Ergo alltalaban menni fog, kiveve amikor ugy jonnek ki egyeb megszakitasok. Na, az ilyet szep debuggolni.
- A hozzászóláshoz be kell jelentkezni
Ha ezekre a fordito fuggvenyt hiv, es pont egy futo / vagy % hivasba szakit bele, mindenfele ronda dolgok tortenhetnek.
ha van verem, akkor mi lehet a baj egy effele osszetettebb muveletnel? ugyertem, nyilvan nem tud mindent a mikrokontroller (pl 32 bites aritmetikat), de azokat meg lehet valositani sima klasszikus vermeleses fv-hivasokkal. verem pedig van irq-k eseteben is.
- A hozzászóláshoz be kell jelentkezni
Ha csak vermen dolgozik, akkor reentrans azaz semmi baj sincs. Max annyi, hogy az IRQ verem hasznalata jelentosen megno, es nem latszik elso ranezesre mitol. Ergo konnyu elszabni a verem meretet.
- A hozzászóláshoz be kell jelentkezni
if ( timeofday.tv_msec >= 1000 )
{ timeofday.tv_sec++;
timeofday.tv_msec = 0;
}
Ha elég a msec, akkor ne trükközz. Ha nem elég, akkor a usec-kel trükközhetsz:-)
Kérdésedre válaszolva, PIC32-ben lehet asm("di") - asm("ei") párost rakni. (disable interrupts, enable interrupts, atomic módon)
Az a pár ciklus, ami alatt ezek lefutnak, nem veszthetsz interrupt-ot, de nem is szerencsés, ha másik belepofázik.
--
"SzAM-7 -es, tudjátok amivel a Mirage-okat szokták lelőni" - Robi.
- A hozzászóláshoz be kell jelentkezni
Ha elég a msec, akkor ne trükközz. Ha nem elég, akkor a usec-kel trükközhetsz:-)
az kell, mert az inc az neha 2, neha 3 ezeken a frekvenciakon. ha mindig 0 v 1 lenne, akkor persze igy is jo lenne.
- A hozzászóláshoz be kell jelentkezni
Ami így első blikkre: amit főprogramban és interruptban használt változókat volatilenek kell deklarálni.
AVR libcben van makró a globális megszakítások tiltására, engedélyezésére. sei() cli(), ezek az SREG I bitjét FIXME setelik, clearelik.
Édesanyám azt tanította, hogy az ISR-ek rövidek legyenek, az elején cli(), a végén pedig sei() vel. Ez sok szopástól megkímél.
Attól, hogy a globális interruptokat letiltod, a flagek beállnak, és az interrupt elhagyásával sorra hívódnak meg a közben beállt flagű ISR-ek.
- A hozzászóláshoz be kell jelentkezni
Attól, hogy a globális interruptokat letiltod, a flagek beállnak, és az interrupt elhagyásával sorra hívódnak meg a közben beállt flagű ISR-ek.
ez jol hangzik; erre van valami referencia/utala's valahol az avr/atmega doksik kornyeken?
- A hozzászóláshoz be kell jelentkezni
Similarly, if one or more interrupt conditions occur while the global interrupt
enable bit is cleared, the corresponding Interrupt Flag(s) will be set and remembered until the
global interrupt enable bit is set, and will then be executed by order of priority.
Ez az Atmega8 adatlap, Reset and Interrupt handling fejezet 15.oldal.
- A hozzászóláshoz be kell jelentkezni
koszonom, 1000 koszonet.
meg majd arra lennek kivancsi, hogy ez egyedi tiltasnal (RXCIE, TXCIE bitek, pl egy uart-nal) is igy van-e'. de valszeg a cli()/sei() is eleg lesz a gyakorlatban.
- A hozzászóláshoz be kell jelentkezni
Ahonnan kivettem a mondatot ott az előző mondatban erről volt szó, hogy így van.
- A hozzászóláshoz be kell jelentkezni
Édesanyám azt tanította, hogy az ISR-ek rövidek legyenek, az elején cli(), a végén pedig sei() vel. Ez sok szopástól megkímél.
Csak a teljesseg kedveert.
- Egyreszt ezt a legtobb HW automatikusan megteszi.
- Masreszt nem veletlenul talaltak fel, hogy az IRQ -knak van prioritasa, es megszakithatjak egymast. Ezzel a megoldassal ezt a prioritasos rendszert szepen agyonvered lehetetlenne teve, hogy a nagyobb prioritasu IRQ -nak kisebb legyen a kesleltetese.
- Harmadreszt egy ilyen kodert ha rendes OS fut a rendszeren, vagy a realtime rendszert kell csinalni, siman a dunaba lonek. Nem mindig, de legtobbszor.. ;)
- A hozzászóláshoz be kell jelentkezni
Ha nem akarnám letiltani az interruptot, akkor megvizsgálnám, hogy becsapott-e?
volatile int timer_irq;
ISR(TIMER0_OVF_vect)
{
timer_irq=1;
...
...
}
int gettimeofday(struct timeval *tv)
{
do {
timer_irq = 0; // "nem volt"
tv->tv_sec = timeofday.tv_sec;
tv->tv_msec = timeofday.tv_msec;
} while (timer_irq); // vagy mégis? Ekkor újracopy.
return(0);
}
- A hozzászóláshoz be kell jelentkezni
koszi, igen, fentebb mi is vmi hasonlora jutottunk. sot, lehet hogy ez jobb is mint egy szamlalos megoldas, mert az (emeletben ugyan, de) tulcsordulhat. vegul egyebkent segedvaltozo nelkul igy lett megoldva:
volatile struct timeval timeofday={0,0};
ISR(TIMER0_OVF_vect)
{
static uint16_t cnt=0;
uint16_t inc;
cnt+=20; /* 20:9 == 32768*1000 : 14745600 */
/* == (cycle/irq) * finest_resolution : F_CPU */
inc=cnt/9;
cnt=cnt%9;
timeofday.tv_msec += inc;
if ( timeofday.tv_msec >= 1000 )
{ timeofday.tv_sec += (uint32_t)(timeofday.tv_msec/1000);
timeofday.tv_msec %= 1000;
}
}
int gettimeofday(struct timeval *tv)
{
uint16_t msec;
do
{ msec=timeofday.tv_msec;
tv->tv_sec = timeofday.tv_sec;
tv->tv_msec = timeofday.tv_msec;
} while ( msec != timeofday.tv_msec );
return(0);
}
azaz kihasznalhatjuk, hogy az irq-ban az msec valtozo _biztos_ hogy megvaltozik.
a.
- A hozzászóláshoz be kell jelentkezni
timeofday.tv_sec += (uint32_t)(timeofday.tv_msec/1000);
timeofday.tv_msec %= 1000;
Nem ismerem ezt a cpu-típust, de ha az osztás/modulo nem _baromi_ gyors (külön assembly utasítás, pár ciklusos végrehajtással), akkor jobban jársz, ha ezt inkább if-fel csinálod meg:
if (timeofday.tv_msec >= 1000) {
timeofday.tv_sec++;
timeofday.tv_msec-=1000;
}
Ugyanez egyébként a /9-%9-re is igaz, bár oda kell több ág is (>=27, >=18, >=9).
- A hozzászóláshoz be kell jelentkezni
azon gondolkodtam, hogy mikor van olyan eset, amikor ez az if úgy fut le, hogy a '>='-ből a '>' lesz hatásos... ami azt jelenti, hogy a tv_msec++ még lefut, de az utána jövő if néha elmarad, de nem jutottam semmire. (Bár ez lehet az esti Málna-sörtől van)
Tippek, okos emberek?
Mert ha nem kell, akkor tényleg elég az if (msec == 1000) {msec = 0; sec++;}.
--
"SzAM-7 -es, tudjátok amivel a Mirage-okat szokták lelőni" - Robi.
- A hozzászóláshoz be kell jelentkezni
Az msec változót egy irq ciklus 0-3 közötti értékkel tudja növelni.
- A hozzászóláshoz be kell jelentkezni
ja, tényleg, nem ++, hanem valami mágiával számolja, hogy mennyit kell növelni... trükkös.
--
"SzAM-7 -es, tudjátok amivel a Mirage-okat szokták lelőni" - Robi.
- A hozzászóláshoz be kell jelentkezni
nem olvastam vegig teljesen, hogy irtak-e, de csak az msec-et mentsd es a fv-ben
bar annak lehet keves lesz 32 bit...
--
NetBSD - Simplicity is prerequisite for reliability
- A hozzászóláshoz be kell jelentkezni
bar annak lehet keves lesz 32 bit...
az, ma'r szobakerult vhol feljebb, kb ~110 nap alatt tulcsordul.
- A hozzászóláshoz be kell jelentkezni
ha a napokat kulon tarolod es offsetnek veszed?
igazabaol meg kellene nezni rendes os-ekben hoyan csinaljak
--
NetBSD - Simplicity is prerequisite for reliability
- A hozzászóláshoz be kell jelentkezni
ha a napokat kulon tarolod es offsetnek veszed?
jaja, de akkor a modulo-s/oszta'sos szamitgatasok szamaval ma'r ugyanott tartunk. akkor meg ma'r legyunk hasonloak mint a "nagy" rendszerek
igazabaol meg kellene nezni rendes os-ekben hoyan csinaljak
ja, de ott mondjuk pont ez nem szuk keresztmetszet. meg alt a klasszikus irq-khoz (ido, bill, eger, uart, ...) kapcsolodo dolgok, ott olyan kicsi a savszel, ott gondolom az effele dolgokat leszarja'k. ahol viszont mar nem trivialis (ether, hdd, ...), ott meg annyria komplex az egesz eleve, hogy nehezen ertheto meg.
(terv, hogy ilyen unistd-szeru" api-t csinalok az uart-ok ko"re', az azert nagyon leegyszerusitene sok i/o-igenyu fejlesztest es tesztele'st... a cel-funkcionalitas is olyan bonyolult, hogy ma'r desktopon is kellemesen sokaig tesztelne'd/debuggolna'd a cuccot; oszt ha van 1 darab led, amivel lehet debuggolni, akkor egy fokkal nehezebb a fejlesztes).
- A hozzászóláshoz be kell jelentkezni
Elég régi topik, de 8 bites AVR még egy darabig lesz, úgyhogy időtlen a téma. Ezt a megoldást eredetileg az Arduino libjeiből loptam. A trükk az, ahogy az overflow megtörténtét csekkeli. Amit én alakítottam rajta, az annyi, hogy az interrupt tiltott rész a lehető legrövidebb. Mivel lokális változókba mentünk, az akár regiszter is lehet, tehát összesen kb 6 órajel alatt lefut ha jó a compiler.
uint16_t overflowCounter;
ISR(TIMER1_OVF_vect) {
overflowCounter++;
}
uint32_t getTimeTicks()
{
uint16_t over;
uint16_t ctr;
uint8_t mask;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
over=overflowCounter;
ctr=TCNT1;
mask=TIFR1;
}
if(mask &_BV(TOV1)&&ctr<32000u)
{
over++;
}
uint32_t ret=over;
ret<<=16;
ret+=ctr;
return ret;
}
- A hozzászóláshoz be kell jelentkezni