Forum: Mikrocontroller und Digitale Elektronik RTOS UART Semaphore


von Leopold N. (leo_n)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich bin gerade dabei, mir ein RTOS zu schreiben auf einem Atmega32 bzw. 
Atmega644 (sind ja fast gleich).

Nun habe ich das Problem, dass ich über UART Cursorpositionen mit 
zugehörigen Zeichen versende, welche dementsprechend nicht von einem 
anderen Thread unterbrochen werden dürfen, da sonst der Empfänger (PC) 
auf dem Terminal irgendwohin schreibt, bzw. nur Müll ankommt.

Deshalb habe ich eine uint8_t Variable eingeführt, welche signalisiert, 
ob der UART gerade belegt ist oder nicht.
Wenn ein Thread also etwas per UART senden möchte, muss er prüfen, ob 
diese Variable gesetzt ist oder nicht, je nach Zustand muss er warten 
oder kann den UART beschreiben (Ringpuffer).

Der zugehörige Code im Anhang...
Problem ist nun, dass, sobald mehr als ein Thread auf den UART zugreift 
(sprich, die Semaphore wird relevant), der Controller sich nach einer 
gewissen Zeit aufhängt (zumindest der UART). Ob wirklich der ganze µC 
steht, kann ich leider gerade nicht nachprüfen, da ich mein Oszi nicht 
zur Hand habe und auch keine LEDs.

Die Zeitdauer bis zum Aufhängen hängt von UART TX Buffer Size, der 
Anzahl gesendeter Zeichen und der Pause zwischen gesendeten Zeichen ab.
Der UART funktioniert einwandfrei, solange nur ein Thread darauf 
zugreift.
Der Kontextwechsel zwischen Threads hatte bisher auch prima geklappt.
Falls ihr Zweifel habt, schaut euch den Code an, alles im Anhang.

os.s = Timerinterrupt zum Kontextwechsel
os.c = OS Funktionen (Initialisierung, wait-Funktion, Thread 
erstellen,...)
all inclusive.c = Hardwarebedienung
diagnose.c = UART-Funktionen, um Daten formatiert an Terminal zu senden.



Wäre dankbar, wenn jemand sich Zeit nimmt, das alles anzuschauen, ich 
weiß gerade nicht weiter.

Grüße

von Dunno.. (Gast)


Lesenswert?

Schrumpf das doch mal auf ein minimalbeispiel zusammen. Streich alles 
raus was du nicht brauchst,  um das Problem zu reproduzieren.

 Vermutlich findest du das dabei schon,  wenn nicht,  wird zumindest die 
Hilfsbereitschaft größer sein.


Tipp am rande, led und oszi sind zwar ganz nett für simple Probleme oder 
timing-kritisches,  für deine art von Problem wäre ein debugger sehr 
viel hilfreicher.

von M.K. B. (mkbit)


Lesenswert?

Hab mal deinen Code überflogen, weil mir gerade mal wieder nach 
Rätselraten war. Ansonsten hat Dunno schon recht und du solltest das 
ganze eindampfen, wenn du erwartest, dass sich jemand deinen Code 
anschaut.

Willkommen bei nebenläufigen Threads mit geteilten Resourcen und allen 
Problemen, die sich damit neu ergeben (siehe auch 3) unten).
Kennst du dich mit Assembler auf dem Atmega aus und weißt welche 
Operationen atomar sind?

Das waren meine Erkenntnisse (kein Anspruch auf Richtigkeit und 
Vollständigkeit):

1) send_uart: Du gehst davon aus, dass beim Einstieg in ATOMIC_BLOCK 
uart_tx_buffer_full == 0 gegeben ist. Genau dazwischen kann aber der 
anderen Thread drankommen und den Puffer wieder vollmachen. Dann würdest 
du über den Puffer hinausschreiben. Den Check if(uart_tx_head == 
uart_tx_tail) würde ich außerdem zur Sicherheit in if(uart_tx_head >= 
uart_tx_tail) umwandeln, damit sich der Schaden in Grenzen hält oder 
noch besser einen Fehler melden, wenn (uart_tx_head > uart_tx_tail) ist. 
Vielleicht ist das aber über deine Semaphore abgesichert.

2) Wo wechselst du eigentlich zwischen den Threads? Fehlt der Code? 
Ansonsten wäre es komisch, wenn du Threadwechsel ohne Sicherung der 
Register durchführst.

3) Außerdem hast du noch folgendes Designproblem, was dein System 
ausbremsen könnte und auch wird -> Murphys Law.
Beispiel:
Thread 1: uart_reserve erfolgreich
Thread 2: Hängt in uart_reserve und verbrät CPU Zeit in der Schleife.
Thread 1: uart_free; uart_reserve erfolgreich
Thread 2: Hängt in uart_reserve und verbrät CPU Zeit in der Schleife.
Wenn du Pech hast (Timing abhängig), dann kommt Thread 2 niemals mehr 
aus der uart_reserve raus.
Deine Semaphore ist damit nicht fair (das heißt wirklich so), weil nicht 
der Thread drankommt, der am längsten wartet. In der Schleife schaltet 
außerdem der ATOMIC_BLOCK regelmäßig die Interrupts ab, wodurch die Uart 
ausgebremst wird.

Ich hoffe du verstehst, was ich dir sagen will, sonst einfach fragen.

von Leopold N. (leo_n)


Lesenswert?

Erstmal vielen vielen Dank fürs Durchlesen und Eindenken.
Finde ich sehr lieb von Dir.


M.K. B. schrieb:
> send_uart: Du gehst davon aus, dass beim Einstieg in ATOMIC_BLOCK
> uart_tx_buffer_full == 0 gegeben ist. Genau dazwischen kann aber der
> anderen Thread drankommen und den Puffer wieder vollmachen. Dann würdest
> du über den Puffer hinausschreiben. Den Check if(uart_tx_head ==
> uart_tx_tail) würde ich außerdem zur Sicherheit in if(uart_tx_head >=
> uart_tx_tail) umwandeln, damit sich der Schaden in Grenzen hält oder
> noch besser einen Fehler melden, wenn (uart_tx_head > uart_tx_tail) ist.
> Vielleicht ist das aber über deine Semaphore abgesichert.

Genau dieser Fall sollte über semaphore_uart abgesichert sein.
Der UART TX Puffer ist ein Ringpuffer, dass heißt, dass uart_tx_tail 
durchaus größer als uart_tx_head sein kann.

M.K. B. schrieb:
> 2) Wo wechselst du eigentlich zwischen den Threads? Fehlt der Code?
> Ansonsten wäre es komisch, wenn du Threadwechsel ohne Sicherung der
> Register durchführst.

Das passiert in dem os.s File mit einem Timer (20kHz) und Assembler.

M.K. B. schrieb:
> 3) Außerdem hast du noch folgendes Designproblem, was dein System
> ausbremsen könnte und auch wird -> Murphys Law.
> Beispiel:
> Thread 1: uart_reserve erfolgreich
> Thread 2: Hängt in uart_reserve und verbrät CPU Zeit in der Schleife.
> Thread 1: uart_free; uart_reserve erfolgreich
> Thread 2: Hängt in uart_reserve und verbrät CPU Zeit in der Schleife.
> Wenn du Pech hast (Timing abhängig), dann kommt Thread 2 niemals mehr
> aus der uart_reserve raus.
> Deine Semaphore ist damit nicht fair (das heißt wirklich so), weil nicht
> der Thread drankommt, der am längsten wartet. In der Schleife schaltet
> außerdem der ATOMIC_BLOCK regelmäßig die Interrupts ab, wodurch die Uart
> ausgebremst wird.

Das stimmt und war mir auch bewusst.
Allerdings sende ich generell nicht pausenlos, sondern immer in 
Intervallen (ca 100ms Pause min.).
Geht dabei vor allem darum, am Terminal dem User ein Interface zur 
Verfügung zu stellen.






Ich habe das Problem (oder zumindest einen Teil) gefunden.
Schaut euch mal die ISR(ADC_vect) in all_inclusive.c ganz unten an.
Ich habe diese ISR inzwischen umgeschrieben, und nun funktionierts.
Ich vermute, ich habe mir durch die while()-Schleife irgendeinen 
Blödsinn eingehandelt. Wie das passieren konnte, weiß ich noch nicht, 
das schaue ich mir morgen an.
Aber falls ihr Ideen habt, schreibts gerne.

Viele Grüße
Leopold

von Leopold N. (leo_n)


Angehängte Dateien:

Lesenswert?

M.K. B. schrieb:
> 2) Wo wechselst du eigentlich zwischen den Threads? Fehlt der Code?
> Ansonsten wäre es komisch, wenn du Threadwechsel ohne Sicherung der
> Register durchführst.

Dazu ist mir gerade aufgefallen, dass das angehängte os.s File die alte 
Version ist (für den Atmega32), von der Funktion her tut beides 
eigentlich das gleiche.

Hier nochmal das aktuelle File.

Grüße

von Leopold N. (leo_n)


Angehängte Dateien:

Lesenswert?

Ich habe die ADC Routine jetzt geändert.
Jetzt habe ich ein anderes Problem:

Die ADC ISR speichert die Werte im Array adc_result[].
Ein Thread adc_handler() nimmt nun diese Werte und multipliziert sie mit 
Konstanten, um die richtigen Spannungswerte zu ermitteln.
Und hier stürzt das Programm ab.
Addition und Subtraktion kein Problem.

Code siehe Anhang --> Funktion adc_handler()
Jemand eine Idee?

von Leopold N. (leo_n)


Lesenswert?

Ok,

hab den Fehler:

Nur für die, die es interessiert:

In der ISR TIMER0_COMPA_vect im os.s File:

Nach dem Sichern von R1 habe ich eine Zeile vergessen:

eor r1, r1

Daraus resultiert natürlich eine Quatschberechnung in der ISR.


Grüße
Leopold

von Jan (Gast)


Lesenswert?

Soll das mal von anderen verwendet werden? Wenn ja, denk mal über eine 
einheitliche Sprache nach. Mischmasch ist furchtbar.

von Jacko (Gast)


Lesenswert?

Selbst das "eor r1, r1" kannst du mit

clr r1

bei gleichem Taktverbrauch aussagekräftiger formulieren.

Sowas hilft beim "optischen" Debuggen oft mehr, als ein
HW-Debugger im ausgedehnten SIM-Betrieb...

von Peter D. (peda)


Lesenswert?

Typisch behandelt man eine UART wie eine Datei, also:
- fopen
- UART benutzen
- fclose

D.h. sobald eine Task die UART benutzt, schlägt fopen einer anderen Task 
fehl.
Ein Konzept, wo mehrere Tasks in kurzem Wechsel die selbe UART benutzen, 
ist sehr ungebräuchlich und wird schnell im Chaos enden.

Soll die UART ein Display ansteuern, legt man einfach einen 
Displayspeicher im RAM an, wo jede Task reinschreiben kann und der 
Displaytreiber gibt dann zyklisch oder bei Änderung den Inhalt an das 
Display weiter. Die UART gehört dann exklusiv dem Displaytreiber.

: Bearbeitet durch User
von Eric B. (beric)


Lesenswert?

Vielleicht übersehe ich etwas, aber das hier kann doch nicht 
funktionieren?

Aus os.c
1
void wait_ms(uint16_t ms)
2
{
3
  cli();
4
  uint16_t st_ticks = systemtime_ticks;
5
[...]
6
  while(ms > systemtime_ticks)
7
  {
8
    
9
  }
10
}
 Da weder ms noch systemtime_ticks volatile sind, wird die 
while-schleife entweder gar nicht ausgeführt oder bis ins unendliche 
warten...

von Leopold N. (leo_n)


Lesenswert?

Peter D. schrieb:
> Typisch behandelt man eine UART wie eine Datei, also:
> - fopen
> - UART benutzen
> - fclose
>
> D.h. sobald eine Task die UART benutzt, schlägt fopen einer anderen Task
> fehl.
> Ein Konzept, wo mehrere Tasks in kurzem Wechsel die selbe UART benutzen,
> ist sehr ungebräuchlich und wird schnell im Chaos enden.
>
> Soll die UART ein Display ansteuern, legt man einfach einen
> Displayspeicher im RAM an, wo jede Task reinschreiben kann und der
> Displaytreiber gibt dann zyklisch oder bei Änderung den Inhalt an das
> Display weiter. Die UART gehört dann exklusiv dem Displaytreiber.

Genauso habe ich es ja programmiert.

von Leopold N. (leo_n)


Lesenswert?

Eric B. schrieb:
> Da weder ms noch systemtime_ticks volatile sind, wird die
> while-schleife entweder gar nicht ausgeführt oder bis ins unendliche
> warten...

Das sehe ich in os.h ehrlich gesagt anders.
Bei mir steht da:

volatile uint8_t systemtime_ticks = 0;

Und ms ist ein Übergabeparameter, der sich ja auch nicht ändert, da er 
von der aufrufenden Funktion in ein Register oder auf den Stack gelegt 
wird.

Grüße

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.