mikrokontroller, irq

Fórumok

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.

Hozzászólások

  1. gettimeofday() fugvenyben tiltsd ki a timer IRQ -t amig a timeofday valtozohoz hozzafersz.
  2. miutan gettimeofday() fuggvenyben atmasolod a timeofday valtozot, ellenorizd le, hogy a masolt ertek megegyezik e timeofday ertekeve. Ha nem, masold ujra.

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

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 );
}

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.

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

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.

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.

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.

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.


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.

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.

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.

É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.. ;)

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);
}

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.

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

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.

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

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

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;
}