Forum: Compiler & IDEs Kooperatives Multitasking


von Malte _. (malte) Benutzerseite


Angehängte Dateien:

Lesenswert?

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:
1
void rs232_task(void) {
2
u08 sendbuf[14] = "M;VVVVV;AAAA\n\0";
3
UBRRL = UARTNUMBER;    //set speed
4
UCSRC = (1<<URSEL) | (1<<UCSZ1) | (1<<UCSZ0); //set 8Bit mode
5
UCSRB = 1<<RXEN | 1<<TXEN;   //TX enabled, RX enabled
6
//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
void rs232_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
u08 k = 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?

von Dirk (Gast)


Lesenswert?

Sichert der Eintrittscode von void sched(void) nicht schon selber einige 
selbst verwendete Register auf den Stack?

von Malte _. (malte) Benutzerseite


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

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

von Malte _. (malte) Benutzerseite


Lesenswert?

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
register unsigned char sreg asm("r0");
2
sreg = SREG;
3
asm volatile ("push r0");
4
//der bisherige code
5
asm volatile ("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.

von A.K. (Gast)


Lesenswert?

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

von Malte _. (malte) Benutzerseite


Angehängte Dateien:

Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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

von Malte _. (malte) Benutzerseite


Lesenswert?

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.

von Malte _. (malte) Benutzerseite


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

Ja, bei AVRs mit Stackpointer >8Bit muß jede Änderung atomar gemacht 
werden.

Der AVR ist nunmal ein 8-Bitter (nicht negativ gemeint).


Peter

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.