Hallo Leute :)
Ich habe ein Designproblem und hoffe, dass ihr mir etwas auf die Sprünge
helfen könnt.
Folgende Ausgangssituation:
Der uC (stm32f100) liest in einer Timer ISR (alle 800us) einen Wert von
extern ein. Es handelt sich um eine Position, die wirklich äquidistant
abgetastet werden muss, da Geschwindigkeit, Beschleunigung etc daraus
noch berechnet werden soll.
Der Wert wird in einen Puffer geschrieben, der so aussieht:
1
typedef struct {
2
uint16_t raw[500];
3
int32_t abs[500];
4
uint16_t pos; // AKTUELLE!! Position im Puffer, da liegt also der letzte Datensatz
5
uint8_t done; // flag zeigt an, ob Puffer bearbeitet wurde.
6
uint8_t overrun; // flag zeigt an, ob overrun aufgetreten ist
TIM2->SR &= ~0x0001; // Pending Interrupt Bit löschen
23
}
In der main werden, wenn die tim2Flag gesetzt ist, ein paar Berechnungen
angestellt und - falls aktiviert - ein UART Puffer erstellt (dieser
enthält 1000 Positionswerte und wird dann blockweise geschickt).
Laut eurem Wiki sollten diese lange dauernden Prozeduren nicht in die
ISR, deswegen die Flag.
Normalerweise dauert die main mit allem drum und dran weit weniger lang
als 800us. Aber wenn der UART Puffer voll ist, dauert die Sendeprozedur
(wird auch in der main aufgerufen) etwa 90ms. In dieser Zeit läuft der
Timer Interrupt etwa 112 Mal.
Die struct ist jetzt nur eine Idee. Vorher war es so, dass einfach eine
globale Variable mit TIM2->CNT in der Timer ISR erstellt wird.
Im oben angesprochenen Fall (UART Puffer soll gesendet werden) würden so
111 Werte verworfen werden. Das ist nicht gut.
Daher die Idee mit der struct, in der wirklich alle Werte gespeichert
werden, solange die UART sendet und dann, wenn die main Schleife wieder
läuft abgearbeitet werden.
Es gibt drei Funktionen, die an die Werte müssen:
1
void positionCalc;
2
void doPID();
3
void fillLog();
positionCalc berechnet aus den Rohwerten des TIM2 die echte, absolute
Position. Dieser Wert wird für doPID benötigt. doPID macht eine
Positionsregelung.
fillLog soll sowohlauf die absoluten (in positionCalc berechneten), als
auch auf die Rohwerte (TIM2->CNT aus der ISR) zugreifen können.
So sieht es momentan aus, ich hoffe das war ausführlich genug, aber
nicht zu sehr geschwafelt.
Jetzt mein Problem:
In positionCalc wird also der Puffer von 0 bis counterBuffer.pos
durchgegangen, die absolute Position berechnet und in die struct an den
selben Positionsindex (also counterBuffer.abs[counterBuffer.pos]=...)
geschrieben.
LogFill wird danach ausgeführt und möchte, je nach eingestelltem Log
Modus die Rohdaten (counterBuffer.raw), die Absolutdaten
(counterBuffer.abs) oder beides haben.
Also wird der Puffer erneut durchlaufen und die benötigten Werte werden
ausgelesen.
Ist das vollständig passiert, wird die done Flag gesetzt.
In der ISR wird ja geprüft, ob der Puffer bearbeitet wurde (done Flag),
und fängt dann wieder von vorne an.
Für mich ist die große Frage: was passiert, wenn zwischen den beiden
Funktionen postitonCalc und logFill ein weiterer Interrupt auftritt. In
der ISR wird die Pufferposition dann um 1 erhöht, ein neuer raw Wert
wird reingeschrieben. Die Logfill funktion bekommt das dann noch mit und
kann einen abs Wert in den Puffer schreiben. Dann wird allerdings die
done Flag gesetzt -> logFill wird nie mitbekommen, dass zwischen den
beiden Funktionen noch ein Wert dazu kam.
Anders herum - ich setze die done Flag bereits nach der Berechnung,
sieht logFill nur einen einzigen Wert, weil ja der Puffer resettet
wurde. Das wäre noch schlechter.
Ich dachte daran den Interrupt für die Zeit auszuschalten, aber es ist
äußerst wichtig, dass die Position zu äquidistanten Zeitpunkten
abgetastet wird, da sie sich unter Umständen ziemlich schnell ändern
kann und ich somit große Fehler in der Geschwindigkeitsberechnung
bekommen könnte.
Gibt es eine elegante Lösung für das Problem hier - und: wie wird das
sonst gemacht? Es wird ja häufiger vorkommen, dass die main Schleife
etwas länger dauert..
Ich hoffe, mein Anliegen wird klar. Ich würde mich über Hilfe sehr
freuen!
Danke fürs Lesen :)
Hallo Jan,
du willst die Daten asyn. verarbeiten, dazu verwendet man einen FiFo der
als Ringpuffer implementiert wird.
Es gibt ein Array für die Daten und je ein Lese- und ein Schreibzeiger.
In unserem Fall als Index auf die Position im Array.
Dann noch einige Funktionen, so solltest Du den Puffer auch
strukturieren, zum:
Funktion: Lesen eines Datums aus dem Puffer, Lesezeiger erhöhen
Funktion: Schreiben eines Datums in den Puffer, Schreibzeiger erhöhen.
Funktion: Puffer leer (lesen), wenn (Schreibzeiger == Lesezeiger) ist.
Funktion: Puffer voll (schreiben, wenn (Schreibzeiger+1 == Lesezeiger)
ist.
Hier noch ein Beispiel in Peters "Software UART mit FIFO":
Beitrag "Software UART mit FIFO"
Die Implementierung ist sehr schnell und einfach zu verstehen.
.
Okay, danke für die Idee + den Link.
Würde ich mein Datenfeld dann zweidimensional machen (abs + raw Werte),
oder zwei getrennte Puffer anlegen?
z.B. ein Puffer für die raw Daten: Schreib-Zeiger, der auf das als
nächstes zu beschreibene Feld zeigt und ein Lese-Zeiger, der auf das
nächste zu lesende Element zeigt. Aber die Daten werden ja mehrmals
gelesen. Einmal in der positionCalc Funktion (da dann in einen zweiten
Puffer schreiben?) und dann auch in der fillLog Funktion. Jetzt wird nur
der Inhalt nicht gelöscht, sondern ringsrum geschrieben. Aber das
Problem ist doch ähnlich wie oben?
Zuerst kommt positionCalc. Es wird der Puffer durchgegangen, zu jedem
raw Wert der abs Wert berechnet und in einen zweiten Puffer geschrieben.
Dann kommt der Interrupt, ein zusätzlicher Wert kommt dazu. Kein
Problem, der wird irgendwann später verarbeitet.
Aber jetzt kommt die FillLog Funktion. Die kann zwar jetzt den neuen
Puffer mit den abs Werten auslesen (lese Zeiger), aber der Lese-Zeiger
der raw Werte steht ja schon irgendwo. Aber ich muss ja auch noch die
vorherigen Daten (nochmal) auslesen (zum zweiten Mal..).
Nimmt man dann einen zusätzlichen (lese-) Zeiger - für jede Funktion
einen, oder speichert man die in positionCalc bereits ausgelesenen Daten
in einem Array, das allerdings dann dynamisch erzeugt werden müsste,
denn ich weiß ja nicht, wie viele Daten gelesen werden können. Dann
bräuchte ich nur einen Lesezeiger.
Die zweite Möglichkeit ist sicherlich nicht sinnvoll.
Danke & schöne Grüße.
Noch eine kleine Anmerkung:
Ich kann auch beides in eine Funktion packen, sodass nur einmal gelesen
werden muss. Aber ich möchte eigentlich die Funktionen calc / logging
voneinander trennen, um z.B. das logging auszuschalten.
Hi,
da du einen STM32F100 benutzt kannst du den DMA für das versenden des
UART Puffers benutzen, damit läuft die Übertragung im Hintergrund und du
kannst normal weiterarbeiten. In dieser Zeit darfst du nat. nicht in den
UART Puffer schreiben. Lösung u.U. zwei Puffer mit der halben Größe und
dann jeweils abwechselnd beschreiben, musst nat. schauen wie du mit der
Zeit hinkommst...
Viele Grüße,
Narfie
Auch eine Möglichkeit, die ich in Betracht ziehen werde.
Momentan probiere ich aber einen FIFO Puffer mit einem Schreibzeiger und
mehreren Lesezeiger zu implementieren, komme aber dank nicht vorhandenen
Klassen arg durcheinander.
Es besteht in C nicht die Möglichkeit, eine Funktion einer struct
zuzuordnen, so wie in C++ eine Klasse Funktionen enthält, oder?
Jan K. schrieb:> Auch eine Möglichkeit, die ich in Betracht ziehen werde.>> Momentan probiere ich aber einen FIFO Puffer mit einem Schreibzeiger und> mehreren Lesezeiger zu implementieren, komme aber dank nicht vorhandenen> Klassen arg durcheinander.
Hä?
Ein C++ Konstrukt
1
classA
2
{
3
public:
4
voidfoo();
5
6
private:
7
intmember;
8
};
9
10
voidA::foo()
11
{
12
this->member=2;
13
}
lässt sich in C schreiben als
1
structA
2
{
3
intmember;
4
};
5
6
voidfoo(structA*this)
7
{
8
this->member=2;
9
}
Die ersten C++ Compiler (und wohl auch einige heutige noch), haben genau
nach solchen (bzw. ähnlichen) Schemata ein C++ Programm nach C
übersetzt, um es dann dem bereits vorhandenem C Compiler vorzuwerfen.
Also das ist kein wirklich gutes Argument. Wenn dus mit Klassen
schreiben kannst, kannst du es auch ohne. Klassen sind bei dieser
Aufgabenstellung syntaktischer Zucker - mehr nicht.
Habe mich damit jetzt mal vertraut gemacht, bis auf die Übergabe von &A
ist es ja wirklich gleich handzuhaben.
Jetzt ist allerdings die Frage, ob man das überhaupt so machen sollte,
oder ob man das "einfach so" runterschreibt. Also einen zweiten
Lesezeiger "manuell" erstellen und gut is.
Sollte man in C (speziell im embedded Bereich) trotzdem versuchen die
Funktionen zu kapseln und Klassen mit structs nachzuahmen?
(sorry für die blöden Fragen, aber ich habe C++ gelernt, da wurde von
Anfang an Richtung OOP gedacht..)
Danke + schöne Grüße!
Jan K. schrieb:> Habe mich damit jetzt mal vertraut gemacht, bis auf die Übergabe von &A> ist es ja wirklich gleich handzuhaben.>> Jetzt ist allerdings die Frage, ob man das überhaupt so machen sollte,> oder ob man das "einfach so" runterschreibt. Also einen zweiten> Lesezeiger "manuell" erstellen und gut is.
Du brauchst sowieso einen 2-ten Lesezeiger.
Klassen helfen dir an dieser Stelle genau gar nichts. Niente, Nada,
Nothing,
> Sollte man in C (speziell im embedded Bereich) trotzdem versuchen die> Funktionen zu kapseln und Klassen mit structs nachzuahmen?
Es gibt im Embedded Bereich, auf den kleinen µC nur wenige Dinge, die
von Klassen bzw. den damit dann zusammenhängenden Konzepten Vererbung
und Polymorphie, tatsächlich guten Gebrauch machen können. Ganz im
Gegenteil werden Verfahren dadurch oft aufgebläht, weil der Compiler
dann nicht mehr auf einen spezifischen Port hin optimieren kann, sondern
in Klassen dann vieles generisch gelöst wird. Templates helfen da zwar
ein wenig, aber meistens sind die Programme nicht so umfangreich, dass
sich das wirklich auszahlen würde, da ein großes Framework anzulegen.
Mir kommt als Lösung dieses Problems die beliebte Doppelbuffer Strategie
in den Sinn. In einen Buffer wird geschrieben, der andere verarbeitet.
Einfach, Fehlerunanfällig und effizient.
Grüsse
Ich muss diesen Thread nochmal hochholen.
Ich versuche gerade die ganze Puffer- bzw Uartgeschichte etwas zu
vereinfachen, momentan sieht es so aus:
In der ISR wird ein aktueller Wert vom Encoder Eingang gelesen und in
eine globale Variable gespeichert. Dann wird in der ISR die Funktion
berechnePosition() aufgerufen, die die absolute Winkelposition bestimmt.
Diese Position wird für die PID Regelung benötigt.
Der jeweils absolute Winkelwert wird in einen Ringpuffer gespeichert,
bei jedem Aufruf von berechnePosition(). Da die Winkelposition 32Bit
groß ist, der Puffer aber nur 16Bit (hat "historische" Gründe, habe
vorher nur Halbwörter puffern müssen...) wird sie in zwei 16 Bit
Ringpuffer geschrieben (hier werde ich nochmal überlegen, ob ich nicht
32Bit Puffer verwende, und wenn ich 16 Bit Daten puffern möchte, jeweils
zwei in ein 32 Bit Feld schreibe [oder macht der Compiler das
möglicherweise sogar automatisch hinterher?]).
Falls aktiviert wird dann die PID Funktion aufgerufen. In dieser wird
die Stellgröße zu Loggingzwecken in einen dritten Ringpuffer
geschrieben.
Bisher gibt es also 3 Ringpuffer (stellgröße, absWinkelHigh,
absWinkelLow), die ständig laufen. Die Größe beträgt jeweils 300
Elemente, das reicht aus, um eventuell auftretende Verzögerungen der
main (z.B. UART senden) auszugleichen.
Dann gibt es die Möglichkeit, das Logging einzuschalten. Wenn das
passiert, wird in der main die logging Funktion ausgeführt, die prüft,
ob die Puffer leer sind (bzw keine neuen Elemente hat). Ist das nicht
der Fall, wird das eigentliche zu sendende Array erstellt. Das ist 1042
Bytes groß, hat eine Checksumme, einen Anfangs- und einen Endframe zu
Identifizierung. Dazwischen befinden sich die Daten aus den einzelnen
Ringpuffern.
Ich verschicke das blockweise, damit ich nicht so viel Overhead habe.
Das bedeutet aber, dass ich einen weiteren, noch größeren UART Puffer
brauche (eben mit 1042 Elementen). Dieser wird dann, wenn er voll ist
verschickt. Noch mit der unglücklichen "Wartemethode", die die main
stilllegt. (aus diesem Thread :
Beitrag "Regelalgorithmus in ISR?" )
Mir kommen diese vielen Puffer so umständlich vor. Wenn ich eine weitere
Größe loggen wollte, müsste ich noch einen weiteren Ringpuffer benutzen.
Daher ist die Frage: Wie vereinfache ich diese Struktur?
Selbst, wenn ich die UART asynchron senden lasse, wie ihr mir das oben
beschrieben habt, benötige ich dennoch die "kleinen" Ringpuffer, da es
sein kann, dass die main trotzdem länger dauert, als eine
Interruptperiode lang ist. Z.B. wenn auf Eingaben von außen reagiert
werden soll.
"Baut" man überhaupt diese großen zu sendenden Arrays vorher zusammen
und schickt sie dann erst?
Danke fürs Lesen und kommende Denkanstöße =)
Noch eine Anmerkung zum 1. Post:
counterBuffer.pos hat einen Wertebereich von 0-500 (und nicht von 0-499)
da bei counterBuffer.pos!=500 (else Zweig) die Variable vor der
Verwendung incrementiert wird.
Ich habe so den Eindruck, daß du deine Aufgabenstellung einfach noch
nicht wirklich zu Ende gedacht und deswegen bislang nur Murks
zusammengeschrieben hast.
Also: Einen Puffer (auch einen Ringpuffer) benutzt man dazu, irgendetwas
zwischenzuspeichern, das zu einer Zeit hereinkommt, wo man nicht
vorhersehen kann, ob man genau zu diesem Zeitpunkt auch Zeit dafür
hat. Die vorhergesehene Verarbeitung ist also asynchron und SPÄTER.
Ein Interrupt-Programm benutzt man dafür, Dinge die genau jetzt und
sofort bearbeitet werden müssen, zu erledigen.
Wenn du also irgendwelche Inputwerte verarbeiten willst und dazu
innerhalb der Zeit, die sich eine Interrupt-Routine bei deiner Anwendung
maximal gönnen darf, keine Gelegenheit hast, dann merke die Rohwerte
eben in einem Ringpuffer und verarbeite sie später irgendwann.
Sowas wie
typedef struct {
uint16_t raw[500];
int32_t abs[500];
....etc.
sind kein Ringpuffer sondern Murks.
(btw: warum schreibst du die geschweifte Klammer hinter struct und nicht
auf den Beginn der neuen Zeile? Du willst wohl partout nicht den Block
optisch erkennen können?)
Also, ein Ringpuffer siehr eher so aus:
struct MyBlabla
{ int A;
float B;
bool C;
etc. etc.
};
struct MyBlabla Puf[512];
int Wrzeig;
int Rdzeig;
Mach dir lieber Gedanken über den Zeitfonds, also wie deine Anwendung
sich verhalten soll wenn der Puffer überlaufen sollte. Das ist
eigentlich das allerwichtigste an der ganzen Entwicklung. Das
Gegenteil, also Puffer leer, sollte ja kein Problem sein.
W.S.