Hallo,
ich habe mir einen kooperativen Scheduler geschrieben (siehe Anhang). Im
Simulator (Avr-Studio 3.5) sah mit einem sehr einfachem Test auch alles
gut aus. Jetzt habe ich in der Praxis aber das Problem, dass Daten
durcheinander kommen.
Der Test soll in einem Task eine LED blinken lassen und in einem
weiteren einen Text per RS232 ausgeben.
Dies funktioniert auch, nur das zwei Zeichen an der RS232 Ausgabe
wiederholt falsch sind: Anstelle von "Testtest\n\r" erhalte ich
"T#.ttest\n\r" an der seriellen Schnittstelle.
Zusätzlich zu dem Quellcode noch:
Die Funktion sched ist in der Headerdatei als
void sched(void) _attribute_ ((naked));
deklariert.
Der RS232 task sieht wie folgt aus:
//only in op_mode==0 we are willing to recive commands from the interface
7
//in all other modes, we send out the mode, voltage and current every 0,25sec
8
9
for(;;){
10
rs232_print("Testtest\n\r\0");
11
}
12
13
for(;;){
14
if(op_mode==0){
15
//TODO: add eeprom read/write
16
17
}else{
18
//Sending format: M;VVVVV;AAAA\n
19
sendbuf[0]=op_mode+48;
20
sendbuf[1]=';';
21
//TODO: add voltage into sendbuf
22
sendbuf[7]=';';
23
//TODO: add current into sendbuf
24
rs232_print(sendbuf);
25
waitms_sched(250);
26
}
27
}
28
}
29
30
voidrs232_print(u08*text){//Sends a string over rs232
31
/*Please note that this function uses the sched()
32
If you use this function from more than one task, it might happen
33
that the output of the different tasks get mixed.
34
However I use this function only within the rs232_task().
35
*/
36
u08k=0;
37
while(*(text+k)!=0){//for every character of *text
38
while((UCSRA&(1<<UDRE))==0){//wait until the USART can send data
39
sched();
40
}
41
UDR=*(text+k);
42
k++;
43
if(k==0){//security out if the string was not null terminated
44
break;
45
}
46
}
47
}
Ich wüsste gerne ob ich in dem Scheduler irgenwas übersehen habe, was
ich hätte sichern müssen. Ich habe meines wissens alle Register
gesichert, die laut avr-libc
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage
als "Call-saved registers" bezeichnet werden.
Also habe ich hierbei was übersehen?
> Sichert der Eintrittscode von void sched(void) nicht schon selber einige> selbst verwendete Register auf den Stack?
Nein, dafür sorgt _attribute_ ((naked)) in der Headerdatei. Das
Assemblerlisting der Datei zeigt auch keinen Code im "prologue" und
"epilogue" Abschnitt.
Du sicherst nen Haufen Register, aber das wichtigste nicht, das SREG !
Wenn der Scheduler den Watchdog triggert, dann kannst Du den auch gleich
weglassen, sowas ist Unsinn.
Der Watchdog darf nur in einer einzigen Task getriggert werden, die das
korrekte Funktionieren des gesamten Programms überwacht !
Peter
Allen ein frohes neues Jahr.
> Du sicherst nen Haufen Register, aber das wichtigste nicht, das SREG !
Ok, ich wusste nicht, dass sich Funktionen darauf verlassen das dies
nach einem Unterfunktionsaufruf weiterhin im gleichem Zustand ist.
Ich habe mein Program um
1
registerunsignedcharsregasm("r0");
2
sreg=SREG;
3
asmvolatile("push r0");
4
//der bisherige code
5
asmvolatile("pop r0");
6
SREG=sreg;
erweitert.
Das brachte aber nicht den gewünschten Erfolg. Also habe ich das
Programm nochmal im AVR-Studio simuliert. Heraus kam, dass durch die
Zeile
memcpy_P(&subprog,&schedlist[sched_task_l].execute,sizeof(subprog));
der Text überschrieben wurde.
Das scheint wohl daran zu liegen, dass in Normalfall der Compiler etwas
Platz auf dem Stack für lokale Variablen reserviert, was durch mein
_attribute_ ((naked)) aber nicht geschah.
Mal sehn wie sich das Problem halbwegs elegant lösen lässt.
> Wenn der Scheduler den Watchdog triggert, dann kannst Du den auch gleich> weglassen, sowas ist Unsinn.> Der Watchdog darf nur in einer einzigen Task getriggert werden, die das> korrekte Funktionieren des gesamten Programms überwacht !
Ich dachte mir, dass auf diese Weise sichergestellt ist, dass kein Task
hängt und sched() nicht länger aufruft. Einen zusätzlichen kontroll
Task, der bei einem erkannten Fehler in eine Endlosschleife ohne sched()
geht und somit einen Reset provoziert kann es so ja immer noch geben.
In der "naked" Funktion dürfen mangels Stack-Frame nur Variablen benutzt
werden, die garantiert in Registern landen (erzeugten Code
kontrollieren). Komplexe Funktionen ggf. als Unterprogramm
implementieren, und auch dort kontrollieren, dass der Compiler dieses
nicht klammheimlich inlined (macht GCC sehr gerne, vor allem wenn als
"static" deklariert).
So der problematische Code ist jetzt eine normale Unterfunktion.
Bei dem Restlichen hoffe ich, dass jetzt garantiert ist dass er in
Registern landet. Das war mir vorher nicht so bewusst.
Mein Testprogram funktioniert jetzt wie erwartet. Den neunen Quellcode
habe ich angehängt, falls jemand Interesse hat.
Malte __ wrote:
> Ich dachte mir, dass auf diese Weise sichergestellt ist, dass kein Task> hängt und sched() nicht länger aufruft. Einen zusätzlichen kontroll> Task, der bei einem erkannten Fehler in eine Endlosschleife ohne sched()> geht und somit einen Reset provoziert kann es so ja immer noch geben.
Wenn der Kontroll-Task aber nun nicht mehr aufgerufen wird, weil sich
irgendwas verklemmt hat ?
Ist ungefähr so, wie ein Schließer als Notschalter, bei dem vergessen
wurde, den Stecker reinzustecken.
Daher müssen Notschalter immer Unterbrecher sein.
Daher müssen Kontrol-Tasks immer resetten, wenn sie nicht mehr
aufgerufen werden.
Peter
Peter Dannegger wrote:
> Wenn der Kontroll-Task aber nun nicht mehr aufgerufen wird, weil sich> irgendwas verklemmt hat ?>> Ist ungefähr so, wie ein Schließer als Notschalter, bei dem vergessen> wurde, den Stecker reinzustecken.> Daher müssen Notschalter immer Unterbrecher sein.>> Daher müssen Kontrol-Tasks immer resetten, wenn sie nicht mehr> aufgerufen werden.
Ja da ist was drann. Dann werde ich den Watchdog Reset in einen eigenen
Thread verlagern.
Ein gemeinen Fehler habe ich noch in meinem Schduler gefunden:
An den beiden Stellen an denen der SP beschrieben wird, müssen vorher
eventuell andere laufende Interrupts mit cli() abgeschaltet werden.
Am besten in der Form:
1
sreg2=SREG;
2
cli();
3
SP=schedlist_p[sched_task_l];
4
SREG=sreg2;
Ohne dies verhielt sich mein Program leider manchmal recht merkwürdig.
Mal lief es mehere Minuten, nach einer kleinen Änderung wurden dann
plötzlich "Geisterzeichen" auf dem LCD Display sichtbar oder das
Programm sprang plötzlich zur Adresse 0x00.
Eine einfache Stack Überwachung (Initialisieren des RAMs mit einem
Muster) gibt es jetzt auch, da ich zunächst dachte das obige Verhalten
hinge mit einem Stack-Überlauf zusammen.