Hallo,
mit aktuell 10.000 geschriebenen Zeilen in 12 Modulen wird die Blaue
Pille langsam voller (63kb) und die Software komplexer. Klar, man tüfelt
sich so einiges zurecht. Aber ob das immer die die ideale Lösung ist?
Gibt ja tausende Programmierer, die immer wieder die gleichen Probleme
lösen.
Beispiel: Tastendruck. Drückt man die wird die aktuelle GPS Position
samt aller Daten des Strings auf SD Karte gesichert. Prellt natürlich
wie Hulle das Teil, beim Drücken und Loslassen.
Übergeordnet läuft ein 1s Timer INT, der das Tastenflag entgegen nimmt
und die Taste nach 2s wieder freischaltet für neue Drücke (fallende
Flanken)
fTastBlocked : Globale Freischaltung, ob Tastendruck in aktuellem State
erlaubt ist
fTaste Enable: Bit, was im Timer Int abgefragt wird.
fLeftKey: Bit, was der Statemachine im Hauptteil zeigt, dass Taste
gedrückt wurde. Initiiert dann die Speicherung und setzt Bit zurück.
Genauer gesagt entkopple ich die Vorgänge auf Hardwareebene, so dass die
Statemachine damit umgehen kann. Die soll mit der Hardware nämlich nix
zu tun haben, bedient nur die API der einzelnen Module und fragt die
Interface Bits ab, die diese bereit stellen. Gibt auch Leute die alles
über das Abschalten der INTS machen, ich arbeite lieber mit Semaphoren,
damit das gar nicht nötig wird.
Ich frage mich ob es Bücher gibt, die solche und ähnliche Sachen
behandeln?
Vielleicht auch wie man die Module strukturiert, damit das Ganze
übersichtlich bleibt? Bei 12 Modulen mit Headern, mehreren Daten
liefernden Baugruppen und jedes mit eigenem Interface (API) wird das
schon langsam unübersichtlich.
1
/* Tastendruck empfangen */
2
voidEXTI3_IRQHandler(void)
3
{
4
if(!fTasteBlocked){
5
if(EXTI_GetITStatus(EXTI_Line3)==SET){
6
if(fTasteEnable){
7
/* Snapshot der GPS Daten sichern */
8
GPSnapshot=GPS;
9
SetLED(ROT,ENABLE);
10
fleftKey=true;
11
fTasteEnable=false;
12
}
13
}
14
}
15
EXTI_ClearITPendingBit(EXTI_Line3);
16
}
17
18
voidTIM3_IRQHandler()
19
{
20
#define RELOAD_1s 10
21
staticuint8_tDivider1s=RELOAD_1s;
22
staticuint8_ttaste_blocker=0;
23
24
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
25
{
26
/* Clear IRQ Flag */
27
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
28
29
/* ------ 1s Takt Durchlauf ----- */
30
if(!(--Divider1s))
31
{
32
Divider1s=RELOAD_1s;
33
/* Hier wird die Berechnung der Wegstecke durchgeführt */
Christian J. schrieb:> wird die Blaue> Pille langsam voller
evtl. da
Christian J. schrieb:> if (fGPSValidFix && (GPS.speedkmh > 5.0))> {> /* Teilstrecken aufsummieren */> Tageskilometer += (GPS.speedkmh / 3600);> }
floating point verwendet wird? Ich hoffe nicht, oder?
M3 ohne FPU schrieb:> floating point verwendet wird? Ich hoffe nicht, oder?
Da wird sogar double und vieles mehr verwendet.... wir sind hier nicht
bei den 8-Bit-Geklapper-Arduinos! Auch wenn die FPU noch nachgeliefert
wird... die 411er "Schwarze Pille" sind unterwegs.
39200 Zyklen:
Christian J. schrieb:> Ich frage mich ob es Bücher gibt, die solche und ähnliche Sachen> behandeln?
Ja, die gibt es. Nur leider kaum mundgerecht für Deine Architektur. Das
wäre in etwa so, wie "mechanische Maschinen konstruieren allgemein". Es
ist schon ein Unterschied, ob Du 8 Leerzeichen-Tabs wie bei Linux
verwendest oder eher 3-4.
Du kannst jeden einzelnen Aspekt nehmen und dort "forschen".
Tastenentprellungen gibt es sicher tausenfach, Peters ist die beste mir
bekannte, aber beliebig viele Alternativen sind im konkreten Einsatzfall
genauso gut. Wenn Du seine Routinen und deren Hintergründe verstehst,
kannst Du Dir Deine optimal stricken.
Genauso bei Modulen: Z.B. hier
https://www.duckware.com/bugfreec/index.html wird ein schönes Konzept
vorgestellt. Mit vielen anderen schönen Dingen. Verwendet habe ich es
nie, aber viel davon gelernt.
Beispiel Namensgebung: Solange Du nur wenige Zeilen hast, gelten andere
Gesetze als wenn Dein Code sich noch mehrfach verdoppelt. Die meiste
Literatur ist eher im akademischen Maßstab, also lange erklärende Namen
für überschaubare Projekte.
Fürs Programmiertechniken allgemein gibt es einige Klassiker, z.B.
"Writing solid Code", "Code Complete".
Am meisten Hilft der Austausch mit anderen und der Code fremder Leute.
A. S. schrieb:> Genauso bei Modulen: Z.B. hier> https://www.duckware.com/bugfreec/index.html wird ein schönes Konzept> vorgestellt. Mit vielen anderen schönen Dingen. Verwendet habe ich es> nie, aber viel davon gelernt.
Habe ich mir mal als Bookmark gespeichert, das liest sich doch schon gut
beim drüberfliegen. Und A.S. ist ja ein Gott der Z80er und anderer
Maschinchen :-)
Inzwischen schreibe ich Code (außer Kommentare) nur noch in Englisch, da
es beschissen klingt denglisch zu schreiben und vieles da kürzer ist.
Darüber hinaus vermute ich aber mal, dass jetzt, wo es größer wird ein
RTOS und eine größere CPU wie die 4er Cortexe wohl die bessere Wahl
gewesen wären, denn um manches möchte ich mich nicht mehr kümmern, wie
das Task Scheduling.
Zudem fehlt mir die Rechenpower einer FPU, was bei den vielen
Berechnungen mit Geo Koordinaten leider schmerzhaft sichtbar wird. Das
summiert sich ganz schön alles inzwischen.
>Gibt ja tausende Programmierer, die immer >wieder die gleichen Probleme lösen
Wenn es dabei um Architektur geht, sind "Design Pattern" das passende
Suchwort.
Geht ja schon los bei den globalen Variablen!
Davon habe ich so einige in den Modulen, Structs die viele Daten halten
und auf die von überall zugegriffen werden muss.
1. Man definiert die als extern und kann von jedem Modul direkt
reingreifen
2. Man definiert die als static und schreibt sich Mini-Funktionen, die
den Zugriff einkapseln, so dass von außen z.b. nur Read-Only erlaubt
ist.
Bei globalen Vars braucht es auch keine Übergabe als Functions
Parameter, die sind direkt sichtbar. Aber wirklich schön ist das nicht.
Rein gefühlt ist also 2. ein Weg, der vermutlich von vielen beschritten
wird. Erzeugt mehr Code aber darum geht es heute eh nicht mehr, lesbar
ist wichtiger als die paar KB mehr.
Wolfgang schrieb:> Christian J. schrieb:>> 39200 Zyklen:>> ...>> Die Rechnung in der Form brauchst du hoffentlich nicht für Berechnung> von GPS Teilstrecken.
Nee, Teilstrecken rechne ich aus der Speed aus dem NMEA Satz aus und
liege auf 200 Autobahn und Stadt-kilometer 0,5% neben dem Tacho damit.
Jede 1,00000 Sekunde wird ein Bruchteil aufaddiert (Meter / Sekunde) auf
eine Summe und das ist sogar sehr genau, wenn man etwas nachkalibriert.
Bei mir waren es +350 CPU Zyklen, die der Timer Int verschoben werden
musste. War auch erstaunt wie genau das ist. Das Beitian GPS hat aber
noch ein Odeometer drin, was Strecken sehr genau misst aber dazu müsste
ich das mit Kommandos also on-the-fly konfigurieren und bespielen. Habe
ich aber nicht vor.
Die Rechnung brauche ich als Anzeige wie weit ich von einem POI weg bin,
den ich gespeichert habe. Es gibt eine mit 8000 Zyklen und eine mit
39.000 Zyklen, die ab ca 50km wirksam wird. Da wird die einfache zu
ungenau wegen der Erdkrümmung. Gibt ja noch eine Anzeige, die mir zeigt
wie ich fahren muss, damit ich da hin komme. Das BP Board ist restlos
ausgelastet aber es geht prima bisher.
Christian J. schrieb:> Da wird sogar double und vieles mehr verwendet.... wir sind hier nicht> bei den 8-Bit-Geklapper-Arduinos!
Super, wenn du das Zeugs gegen ein wenig Hirnschmaz ersetzt, reicht dir
die Pille weitere 5 Jahre locker aus.
Christian J. schrieb:> Da wird sogar double und vieles mehr verwendet.... wir sind hier nicht> bei den 8-Bit-Geklapper-Arduinos! Auch wenn die FPU noch nachgeliefert> wird... die 411er "Schwarze Pille" sind unterwegs.
Keine Ahnung haben, nach Hilfe mit Büchern schreien aber groß die Klappe
aufreißen. Na Mahlzeit. Da ist jede Buchempfehlung Perlen vor die Säue,
denn eine gewisse Offenheit andere Dinge zu lernen fehlt.
Hannes J. schrieb:> Keine Ahnung haben, nach Hilfe mit Büchern schreien aber groß die Klappe> aufreißen.
Vielleicht hast du ja auch keine Ahnung - außer vom Pöbeln. Was der
schreibt ist nämlich Blödsinn. Da gehe ich nicht mal drauf ein.
Christian J. schrieb:> Zudem fehlt mir die Rechenpower einer FPU, was bei den vielen> Berechnungen mit Geo Koordinaten leider schmerzhaft sichtbar wird. Das> summiert sich ganz schön alles inzwischen.
Das liegt nur daran, daß Du ignorierst, daß jede Operation Zeit
benötigt. Viele, die das nicht beachten, wundern sich dann, warum ihr
ARM-Bolide um keinen Zacken schneller ist, als vorher der AVR.
Ein Programmierer sollte ein ungefähres Verständnis dafür haben, welche
Operationen teuer sind. Man kann dann mit wenig Aufwand das
Zeitverhalten optimieren und plötzlich ist die CPU nur noch am Schlafen,
wo sie vorher am Limit war.
Z.B. kann man konstante Vorberechnungen aus Schleifen herausziehen oder
teure Berechnungen nur so oft machen, wie nötig.
Auch Hardwarezugriffe, z.B. LCD-Ausgaben können die CPU massiv
ausbremsen. Da der Mensch eh nicht beliebig schnell lesen kann, kann man
leicht die Anzahl der Ausgaben auf ergonomische Werte reduzieren.
A. S. schrieb:> Fürs Programmiertechniken allgemein gibt es einige Klassiker, z.B.> "Writing solid Code", "Code Complete".
"Clean Code" wäre auch noch zu empfehlen.
Keiner N. schrieb:> "Weniger Schlecht Programmieren" ist auch eine tolle Lektüre.
Das Buch gibt es ja wirklich. Lustiger Titel. Allende deswegen bestelle
ich es mal.
Peter D. schrieb:> . Man kann dann mit wenig Aufwand das> Zeitverhalten optimieren und plötzlich ist die CPU nur noch am Schlafen,> wo sie vorher am Limit war.> Z.B. kann man konstante Vorberechnungen aus Schleifen herausziehen oder> teure Berechnungen nur so oft machen, wie nötig.
Peter Danegger schätze ich mal? Ja, sowas mache ich ja auch schon... nur
habe ich hier sehr viele Rechnungen bei meinem GPS Projekt. Nahezu jede
Routine, einschließlich der Display Ausgaben hat vorne eine schnelle
Prüfung, ob sich überhaupt etwas verändert hat. SetPixel(x,y) setzt
automatisch den Bildschirm Puffer auf ungültig. Ich rase ja alle
Routinen in der State Machine ab, Mehrfachdurchläufe erzeugen keine
doppelten Sachen, zb das Setzen von Power/Off Bits für die Sensoren.
Alles ist gegeneinander verriegelt, ON gibt es nur wenn vorher ein OFF
da war usw. Fixkomma habe ich überlegt aber da wird man malle bei wenn
man sowas durchziehen will. sin, cos, sqrt usw. erwarten nunmal double
als Eingabe. Hat man erstmal die libc mit drin ist sie auch drin, ob
einmal benutzt oder zehnmal.
Ziel ist es nur dann Rechenzeit zu erzeugen, wenn die Sensoren Daten
liefern und auch nur wenn diese Daten anders sind als die letzten. Mal
eben ne Quersumme bilden über einen NMEA String ist ja nicht schwer, ist
die gleich wird direkt weiter gefahren zur nächsten Sache.
Stromaufnahme ist aktiv 60mA zu ca 7-8mA wenn alles inaktiv ist. Das
lohnt schon bei Batteriezellen.
Ist beim ARM eigentlich der 32 Bit Zugriff effizienter als der auf
uint16 oder uint8? Ich habe bisher immer die kleinste Größe genommen,
hätte aber Platz, erst 4kb belegt von 20 abzgl Stack. Alles was nur geht
lokal, an Platz soll es nicht mangeln im RAM.
PS: das ist ein Hobbyprojekt, kein Kommerzielles! Ich habe nur seit 25
Jahren Spass dran diesen schwarzen Kästen etwas beizubringen, Software
ist eine Art Kunst für mich und sie soll schön sein. Schwer zu
erklären.....
Peter D. schrieb:> Z.B. kann man konstante Vorberechnungen aus Schleifen herausziehen
Sowas kann aber wiederum auch der Compiler selbst (common subexpression
elimination, gab's schon vor 30 Jahren).
Aber da ist man bei einem Punkt, der bei "Embedded" noch wichtiger ist
als sonst: man sollte sich das Resultat seines Compilers auch
gelegentlich mal anschauen. Dazu muss man keineswegs in der Lage sein,
fließend "Assembler zu sprechen", aber wenigstens grundlegend verstehen
sollte man das. Vor allem an zeitkritischen Stellen kann man auf diese
Weise u.U. Designfehler erkennen, die zu "pessimiertem" Code geführt
haben.
Sowas hier sieht vielleicht nicht schön aus, C++ kann sowas von Haus aus
die Daten kapseln aber bei C versuche ich jedes Modul nur mit einer API
nach außen reden zu lassen.....
Christian J. schrieb:> Ist beim ARM eigentlich der 32 Bit Zugriff effizienter als der auf> uint16 oder uint8?
Siehe voriger Beitrag: schau dir sowas einfach mal an. Aber ja,
insbesondere an Stellen, wo der Compiler nicht sicher erkennen kann, ob
sowas wie definiertes Wrap-Verhalten eines uint8_t (255 + 1 => 0)
relevant ist oder nicht, wird er zusätzliche Befehle einbauen müssen um
bspw. eine Zahl in einem 32-bit-Register auf 8 Bit einzuschränken.
Christian J. schrieb:> aber bei C versuche ich jedes Modul nur mit einer API nach außen reden> zu lassen.
Das klingt ein bisschen nach "reiner Lehre". Gerade im Embedded-Bereich
ist es oft sinnvoll, brauchbare Kompromisse zwischen dieser und
kompletter Schludrigkeit ("machen wir einfach mal alles global, dann
kann man es von überall her benutzen") zu finden.
Jörg W. schrieb:> ("machen wir einfach mal alles global, dann> kann man es von überall her benutzen")
Die Erfahrung zeigt mir, dass ich dann aber zu viele Fehler rein
baue.... :-( Wozu Funktionsparameter? Machen wir doch alles gleich
global, dann kann jeder Koch in dem Brei rumpfuschen.
Christian J. schrieb:> Die Erfahrung zeigt mir
… dass du irgendwie meinen Beitrag nicht zu Ende gelesen hast.
Du sollst das ja keineswegs so machen, sondern du sollst einen für dich
sinnvollen Weg zwischen diesen beiden Extremen finden.
Jörg W. schrieb:> Siehe voriger Beitrag: schau dir sowas einfach mal an. Aber ja,> insbesondere an Stellen, wo der Compiler nicht sicher erkennen kann, ob> sowas wie definiertes Wrap-Verhalten eines uint8_t (255 + 1 => 0)> relevant ist oder nicht, wird er zusätzliche Befehle einbauen müssen um> bspw. eine Zahl in einem 32-bit-Register auf 8 Bit einzuschränken.
Ok, ich nehme Dich beim Wort. Wird zwar ne Menge Arbeit aber dann werde
ich (fast) alles lokale auf "int" bzw uint setzen.... Ne Durchlauf
Analyse mache ich derzeit mit einem Pin und einem Oszi... einfach aber
tut es auch.
Wir werden schon den Mittelweg finden :-)
Christian J. schrieb:> Wird zwar ne Menge Arbeit aber dann werde ich alles lokale auf "int" bzw> uint setzen.
Du sollst dir insbesondere erstmal ansehen, was der Compiler draus
macht.
So als Daumenregel (vor allem für neuen Code): [u]intN_t nimmt man, wenn
man die explizite Anzahl von Bits haben muss, oder wenn es wirklich auf
Speicherplatz ankommt (Array).
int oder unsigned nimmt man, wenn das jetzt sowieso piepegal ist
(Schleifenzähler von 0 bis 31 oder solche Dinge). Man könnte das auch
noch portabel als "uint_fast8_t" dann schreiben (dann wird es bei
Portierung auf einen AVR nur ein Byte), aber die Mühe macht sich wohl
selten jemand.
Jörg W. schrieb:> Du sollst dir insbesondere erstmal ansehen, was der Compiler draus> macht.
Letzte Frage vor dem WE, weil ich gleich nach Berlin muss...
Warum frisst der Compiler hier beides? Mit und ohne Sternchen vor der
Funktion? Bei volatile ist mir das klart, da muss ich explizit casten,
da der Compiler das nicht wissen kann. Aber hier geht beides....die
Funktion wird sicherlich als doppelt erkannt nehme ich an, ich kann den
ASM Code leider nicht einsehen, das sind nur Zahlenpakete in den .s
Dateien wegen einer bestimmten Einstellung bei EmBitz.
edit: erzeugt so sogar 8 Bytes weniger Code als wenn ich erst eine
Variable bespiele und die dann verwende.
1
/* Hier wird die Berechnung der Wegstecke durchgeführt */
Hmm, vielleicht mal die C-Grundlagen? ;-)
Die Funktion liefert einen Zeiger. Der * dereferenziert ihn, und du
vergleichst das dereferenzierte Ergebnis mit 5.0 (sinnvoll).
Ohne * hast du einen Zeiger, den vergleichst du mit 5.0. Der Zeiger
wird dabei implizit in einen Integer umgewandelt (das könnte eigentlich
eine Warnung bringen, wenn man ausreichendes Warn-Level aktiviert), und
der wird dann gegen 5.0 verglichen (wofür er noch implizit in einen
double gewandelt wird, was aber wohl niemand warnwürdig finden würde).
Das klingt wenig sinnvoll, denn sehr wahrscheinlich ist jeder Zeiger
außer dem Nullzeiger größer als 5 …
Jörg W. schrieb:> Das klingt wenig sinnvoll, denn sehr wahrscheinlich ist jeder Zeiger> außer dem Nullzeiger größer als 5 …
Grad getestet... die Ergebnisse sind beide richtig. Mit * und ohne. Es
steht der richtige Wert im Display.
Aber mit * erscheint mir deutlich sinnvoller. Ein Array Name wird ja
auch als Zeiger erkannt, wenn man ihn als Parameter übergibt zb in
printf. Da schreibe ich ja auch kein & vor. Nur bei &array[nn].
Aber da bin ich nicht ganz sicher.... muss mir das nochmal genau
anschauen bevor ich da auf Glatteis gehe.
PS: In meiner Firma dürfen keine Zeiger verwenden werden in der
Steuersoftware für Geräte, verboten per Coding Direktive :-)
Kommt das richtige raus, ein * wird angemeckert.... lieferte auch nur
einen Zeiger auf einen struct im struct.
Christian J. schrieb:> Grad getestet... die Ergebnisse sind beide richtig. Mit * und ohne.
Wundert mich aber.
> xsprintf(oledbuf,"G %02u:%02u:%02u", GPS_GetStamp()->tm_hour,> GPS_GetStamp()->tm_min,> GPS_GetStamp()->tm_sec);
Das würde ich so nicht machen. In C gibt es m. E. keine Möglichkeit, die
Funktion GPS_GetStamp() als seiteneffektfrei zu deklarieren (in C++ wäre
das eine als "const" deklarierte Funktion), sodass der Compiler sie hier
wirklich dreimal aufrufen müsste.
Also entweder ganz pragmatisch doch ein paar Dinge als globale Variablen
ablegen, oder aber die Funktion einmal rufen, ihren Wert in einer
Zeigervariablen speichern und diese dreimal dereferenzieren. Dann ist
dem Compiler sicher klar, dass sich der Zeiger zwischen den drei
Abfragen nicht ändern kann.
Christian J. schrieb:> Nahezu jede> Routine, einschließlich der Display Ausgaben hat vorne eine schnelle> Prüfung, ob sich überhaupt etwas verändert hat.
Ich mache das etwas anders. Z.B. will ich eine ergonomisch Anzeige, d.h.
2..5 Meßwerte/s. Dazu setze ich einen Timerinterrupt auf, der mir alle
200ms ein Flag setzt. Dieses Flag stößt dann in der Mainloop die
Berechnungen der Anzeigewerte und die Ausgabe an. In der Zwischenzeit
kann ich dann andere Tasks ausführen.
Sind in der LCD-Ausgaberoutine viele Wartezeiten nötig, kann man das
auch in einen Timerinterrupt auslagern. Die Ausgaberoutinen schreiben
erstmal alles in einen Schadowspeicher, den dann der Timerinterrupt ans
LCD sendet.
Christian J. schrieb:> Ich frage mich ob es Bücher gibt, die solche und ähnliche Sachen> behandeln?
in den meisten Büchern wird ja nicht konkret auf solche speziellen
Aufgaben eingegangen, die liefern abstrakte Lösungen wie Design Patterns
und die muss man dann immer noch selber auf seine Aufgaben abbilden.
> Darüber hinaus vermute ich aber mal, dass jetzt, wo es größer wird ein> RTOS und eine größere CPU wie die 4er Cortexe wohl die bessere Wahl> gewesen wären, denn um manches möchte ich mich nicht mehr kümmern, wie> das Task Scheduling.
Ich bin selber auch ein Fan von RTOS Lösungen, aber die haben auch ihre
Tücken und ein RTOS kostet mehr Ressourcen:
- Taskwechsel beim preemptiven MT kostet Zeit für das Register sichern,
beim Einsatz einer FPU kostet es mehr weil zusätzlich die FPU Register
dazukommen.
- Stack: jede Task hat einen eigenen, da hat man Verschnitt durch den
Sicherheitsbereich den man lassen sollte. Kann man optimieren, aber eine
neue Funktion aufgerufen und es kann knallen.
- API sollte threadsafe sein, das kostet zusätzlich für Mutex
Mechanismen.
- C-Runtime grösser: Beim gcc sollte man die newlib verwenden, die
newlib-nano ist nicht threadsafe und nicht reentrant. Wenn man die nano
verwendet, dann darf man einige Funktionen der C-Lib nicht verwenden
(printf, malloc u.a.). FreeRTOS z.B. hat dafür auch Vorkehrungen, aber
beim Einsatz von Third Party Software muss man dann darauf achten das
die sich auch daran hält. Mit newlib wird die Codesize gleich 10-20 kB
größer, auf einem F103C8 mit 64 kB Flash wird es dann schnell eng.
-> mit 128 kB Flash macht RTOS schon Spaß, wenn man mit den 20 kB RAM
hinkommt.
> Zudem fehlt mir die Rechenpower einer FPU, was bei den vielen> Berechnungen mit Geo Koordinaten leider schmerzhaft sichtbar wird.
Der F411 bietet da schonmal genug Flash/RAM für den Einsatz eines RTOS,
die double Berechnung wird aber nur durch den höheren Takt schneller,
weil die FPU nur float beschleunigt (dazu hatte stefanus kürzlich einen
Thread eröffnet). Für double in Hardware müsste es dann schon ein M7
sein (F7 oder H7 wie ST seine Serien nennt). Da würde ich aber auch die
Frage stellen ob die Berechnung nicht so umgestellt werden können, das
double nur da wo wirklich nötig genutzt werden.
Als gute Alternative für solche Anwendungen kann ich immer wieder nur
Mbed empfehlen: das eingebaute RTOS kann man per Konfig abklemmen und
'bare-metal' verwenden. Man hat trotzdem (Software)Timer und den Luxus
z.B. von EventQueues. Damit kann man sehr elegant ereignisgesteuerte
Abläufe bauen. Etwas modifiziert aus dem EventQueue Beispiel:
1
EventQueuequeue;// creates a queue with the default size
2
InterruptInbtnSave(PB_12,PullUp);// save button
3
DigitalOutledSave(PC_13);
4
5
voidwriteFile()
6
{
7
// write to SD
8
ledSave=0;
9
}
10
11
voidbtnSaveFn()
12
{
13
ledSave=1;
14
queue.call(writeFile);
15
}
16
17
intmain()
18
{
19
// attach function to btnSave
20
btnSave.rise(&btnSaveFn);
21
22
// events are simple callbacks
23
queue.call(printf,"called immediately\n");
24
queue.call_in(2000ms,printf,"called in 2 seconds\n");
25
queue.call_every(1000ms,printf,"called every 1 seconds\n");
26
27
// events are executed by the dispatch_forever method
28
queue.dispatch_forever();
29
}
Mit der Q wird die Dateiausgabe von der ISR entkoppelt. Mit der
steigenden Flanke wird die Funktion writeFile() in die Q geworfen,
ausgeführt wird sie in main im dispatch_forever(); Dispatch kommt hier
Dank C++ und callbacks ohne elendig lange switch-case Konstrukte aus,
die Q enthält den Funktionszeiger und der Compiler passt auf das man da
gültige Signaturen verwendet.
Das ist ein kooperativer Ansatz, da sollten einzelne Aktionen die
Abarbeitung nicht ausbremsen. Trotzdem reagiert das System in dem Fall
auf den save Button wenn der per Interrupt reinkommt.
So kann man evtl. sogar tickless arbeiten, das spart dann Strom weil die
CPU viel schlafen kann. Macht Mbed auch automatisch, da muss man
allerdings testen ob die Genauigkeit der delta T für die Berechnungen
noch reicht.
Und dieser Code läuft auf F103 oder F411 oder H743.
Jörg W. schrieb:>> Grad getestet... die Ergebnisse sind beide richtig. Mit * und ohne.>> Wundert mich aber.
Ich stehe grad etwas unter Druck, da ich in einer halben Stunde im Auto
sitzen muss und dann 3 Tage weg bin....
Mit * steht da nichts drin im Display, ohne das * ist alles richtig! Das
muss ich korriogieren, das Testen ist nicht soeinfach, da ich da
jedesmal aufs Dach muss die Platine aus dem Fenster halten für GPS.
Warum das so richtig ist ergründe ich nächste Woche. Danke auch an den
Peter und Johannes, ist gelesen worden aber ich sitze auf einem heissen
Eisen, will heute abend ja mit meiner Liebsten zu abend dinieren und
nicht im Stau versauern... und die hat gar kein Verständnis, wenn ich da
den Laptop raushole und mich verkrieche mit den Sourcen :-)
Hier das Gleiche bei GetSpeed muss ein * vor, bei GetNMEAStr darf keiner
vor sein, sonst steh t da nur Murks auf der SD Karte..... letzterer
liefert einen char* zurück. Der erste einen double*.
Uhhh.... da habt ihr mich ja auf eine böse Falle gebracht und alles weil
ich keine Werte kopieren wollte, sondern nur Zeiger übergeben.
1
/* Gültigen Datenstring NMEA wegschreiben */
2
if(GPS_GetDataValid()&&(*GPS_GetSpeed()>=6.0))
3
{
4
/* f_write benutzt intern einen 512 Byte Puffer */
5
//int nr = f_printf(&Fil,"%s",(char*)&GPS.nmeastring);
Christian J. schrieb:> Hat man erstmal die libc mit drin ist sie auch drin, ob einmal benutzt> oder zehnmal.
Wie meinst Du das? Funktionen, die sich entsprechen (cos und sind z.b)
nutzen die gleichen Funktionen. Aber sonst gilt: jede Funktion wird
separat eingefügt.
Christian J. schrieb:> aber bei C versuche ich jedes Modul nur mit einer API nach außen reden> zu lassen.....
Wieso casts? Und wieso ptr? Das sieht so wenig sinnvoll aus. Und besser
als .c anhängen.
Poste einfach Mal zusammenhängenden Code eines Bereiches, der dich
interessiert. Dann bekommst Du viele Meinungen aber auch viele neue
Sichtweisen.
Christian J. schrieb:> Ich habe CB Funk an Bord, das darf man noch
Jein. Eigentlich nicht mehr ohne "Freisprecheinrichtung", einzelne
Bundesländer haben diese Regelung wiederum ausgesetzt. Wirrwar.
OK, genug OT.
Johannes S. schrieb:> Da würde ich aber auch die Frage stellen ob die Berechnung nicht so> umgestellt werden können, das double nur da wo wirklich nötig genutzt> werden.
Gerade bei Geo-Informations-Daten ist man allerdings schnell an dem
Punkt, wo die Fehler bei single precision relevant werden können (hängt
natürlich davon ab, was man genau mit der Rechnung anstellt).
Peter D. schrieb:> Sind in der LCD-Ausgaberoutine viele Wartezeiten nötig, kann man das> auch in einen Timerinterrupt auslagern.
So, muss noch ne Stunde warten.... also Kiste wieder an.
Die Black Pills lagen grad in der Post. Blöderweise ist das Pinning ein
anderes und der M4 hat eine sehr andere StdPreiphLib, die RTC ist nicht
nur ein Unixtimer, sondern "lesbar" usw. Der NVIC ist wohl auch nicht
ganz gleich.
Vermutlich würde bei einem Lochraster Umbau dann gar nichts mehr
funktionieren und ich müsste wieder eine Hardware nach der anderen zum
Leben erwecken bzw zig Änderungen im Code machen an den Hardware
Einstellungen. Lassen wir das, dazu fehlt mit der Nerv aber 96kb RAM und
256kb Flash sind schon geiler als 20kb. Wenn die FPU aber nur auf float
wirkt nicht gut, das würde mir bei einigem zu viele Summenfehler
erzeugen. Fahre ich nach Berlin sind das 360km in 4h. 4x60x60 = 14.400
Sekunden. Und jede Sekunde werden ein paar Meter dazu addiert.
Dennoch bleibt die Frage offen warum das mit dem *Function.... so ist
und nicht anders. Warum *Blabla()... nichts anzeigt im Display und
Blabla() das Richtige. Obwohl double* Blabla(....) definiert ist. Wenn
es in einem f_printf verwendet wird. Compilieren tut beides.
@Peter: Solche Techniken verwende ich auch. Da ist nirgendwo ein Delay
wo er nicht hingehört, alles wird über 4 Timer gesteuert, wovon einer
als 1/10s in seinem Handler einfache Zaehler Vars hat. Die Main Loop
brettert überall durch und klingelt nur diverse Bits ab. Das Display
wechselt alle 5s die Anzeige, alles Timer gesteuert.
Der einzige Delay ist beim Einschalten des GPS Moduls drin, das muss
erst hochfahren, sonst geht sowieso nichts, was sollte er auch tun ohne
Daten?
A. S. schrieb:> Wieso casts? Und wieso ptr? Das sieht so wenig sinnvoll aus. Und besser> als .c anhängen.
Casts, weil volatile Vars das verlangen, sonst gibt es Warnungen....
volatile discards.... blabla. Dem Compiler musst du da genau sagen dass
Du weisst was Du tust. Und so wird es aus einem Zeiger auf volatile int
var
ein (int*)&var .... und Pointer verwende ich aus purem Irrsin, weil es
ja nicht nötig ist, dass Kopien von Daten erzeugt werden die eh da sind.
(Irgendwann mal gelernt, gelesen...) Jedes Teil wird im Debugger genau
angeschaut, bei Embitz kannste auch von Zeigern auf Zeigern deren
Referenzierung anzeigen lassen, purer Luxus.
struct a = struct b erzeugt eine Kopie, muss ja nicht sein. Die
Datenbasis ist etwas verschachtelt, das ist ein struct of struct und
wird daher vom Caller mit a.b->c angesprochen.
Christian J. schrieb:> Dennoch bleibt die Frage offen warum das mit dem *Function.... so ist> und nicht anders. Warum *Blabla()... nichts anzeigt im Display und> Blabla() das Richtige.
Der Name einer Funktion liefert dessen Addresse zurück. Funktionen
werden aufgerufen, indem ihre Adresse angesprungen wird. Deswegen macht
da eine doppelte Indirektion keinen Sinn.
> *Blabla()
Liefert vermutlich die Adresse einer temporären Pointer-Variable, welche
die Adresse der Funktion Blabla() enthält.
https://www.geeksforgeeks.org/address-function-c-cpp/
Stefan ⛄ F. schrieb:> Liefert vermutlich die Adresse einer temporären Pointer-Variable, welche> die Adresse der Funktion Blabla() enthält.
Mal in Ruhe drüber nachdenken. Das ist ja bei arrays auch so. Nur der
Name ist seine Adresse als Übergabeparameter. Das & davor nur dann, wenn
ich einzelne Zellen haben will. Ich finde Pointer schöner :-)
*(wurzel+i) sieht eben cooler aus als ein [i].
Gesehen wurde auch schon in Netzcode bei meinen Recherchen:
char* func (....) {
char text[10];
......
char* p = text;
.....
.....
return (p);
}
Da haben sich schon manche tot gesucht, warum das zufällig mal
funktioniert aber dann nicht mehr :-)
Ähm... wie kriegt man denn so einen Schneemann in seinen Namen?
Um die eigentliche Frage zu beantworten:
Buchtipps zur Softwarearchitektur auf Embedded Systemen wirst Du hier im
Forum nicht bekommen. Hier scheint eine gewisse Literaturfeindlichkeit
zu herrschen in Bezug auf alles, was über Datenblätter hinausgeht.
Ich suche selbst noch, kann Dir da auch nicht weiterhelfen.
Ich kann da nur eine Liste von Büchern geben, wo es nichts dazu
drinsteht.
Walter T. schrieb:> Hier scheint eine gewisse Literaturfeindlichkeit> zu herrschen in Bezug auf alles, was über Datenblätter hinausgeht.
Das hat nichts mit Literaturfeindlichkeit zu tun, sondern mit:
> Ich suche selbst noch, kann Dir da auch nicht weiterhelfen.> Ich kann da nur eine Liste von Büchern geben, wo es nichts dazu> drinsteht.
Es geht den meisten hier wohl genau so.
Stefan ⛄ F. schrieb:> Es geht den meisten hier wohl genau so.
Außerdem hat es natürlich was damit zu tun, dass da viel an Erfahrung
drin steckt. Die bekommt man nicht so sehr vom Lesen, sondern in erster
Linie vom Machen.
Insofern ist Christian ja auf dem richtigen Dampfer: er macht was, und
mag auch über das, was er macht, diskutieren. Das dürfte der beste Weg
sein, an der Erfahrung anderer teilzuhaben. Es ist ja nicht so, dass
hier niemand bereit wäre, seine Erfahrungen zu teilen.
Jörg W. schrieb:> Außerdem hat es natürlich was damit zu tun, dass da viel an Erfahrung> drin steckt. Die bekommt man nicht so sehr vom Lesen, sondern in erster> Linie vom Machen.
Wobei es schon etwas strange ist was ich gestern gemacht habe.. Damit
das auch am Schreibtisch funktioniert und nicht nur im Auto gestern eine
Runde um den Block gefahren und alles in eine Datei geloggt. Per Define
wird dann umgeschaltet von GPS Modul auf von SD karte einlesen, die
Zeilen landen im UART Buffer, immer wieder von vorne. Und da ich im
Kreis gefahren bin gibt es auch keine Sprünge in den Wegberechnungen. Da
inzwischen aber 64300 Bytes Code im Debug Mode mit -Og vorliegen (55kb
im Release mit -Os) hat das alles seine Grenzen. Komme ich drüber geht
es nur noch über die UART2 auf den Laptop per ftdi und xprintf, die
schon heraus geführt ist... wie beim Arduino.
Und das austesten von Code geschieht ja wohl wie bei Dir, man baut sich
kleine Testroutinen und steppt dann da durch.
Christian J. schrieb:> 55kb im Release mit -Os
Auch Code mit -Os kann man dem Debugger füttern. ;-) Mach ich seit 20
Jahren so. Man muss nur mit den Optimierungs-Artefakten leben lernen.
Jörg W. schrieb:> Auch Code mit -Os kann man dem Debugger füttern. ;-) Mach ich seit 20> Jahren so. Man muss nur mit den Optimierungs-Artefakten leben lernen.
Und wie? Der springt im Source doch ständig hin und her, Routinen die es
nicht mehr gibt usw. usw. Das sieht doch total chaotisch aus.
Christian J. schrieb:> Und wie?
Ich sag ja: man muss sich an die Artefakte gewöhnen und sich auf das
konzentrieren, was man eigentlich finden will. Ggf. halt paar
Debug-Zwischenwerte kurz in volatile-Variablen hinterlegen, die man sich
mit dem Debugger ansehen kann. Oder mal irgendwo ein "__asm
volatile("nop"::);" rein – auf das kann man immer einen Breakpoint im
Debugger setzen. ;-)
Der Vorteil: man debuggt dann auch das, was man releasen will und nicht
irgendwas anderes.
Single-steps bringen einen sowieso meist nicht richtig weiter, besser
ein paar "strategische Breakpoints" platzieren, um die Problemstelle
einzukreisen.
rµ schrieb:> Hier würde GPS_GetSpeed() anstatt GPS.speedkmh ohne * sicher zu einem> anderen Ergebnis führen als mit.
Mit * kommt das Richtige hier heraus, ohne Sternchen wird zwar
aktzeptiert aber es steht nur Müll drin. Habe -Wall eingeschaltet, werde
ggf. noch mehr Warnungen aktivieren. Vergleich Pointer gegen Float
sollte er merken.
In allen xprintf und f_printf dagegen geht es nur ohne *, da wird der *
schon angemerkt beim comnpilieren.
Ich denke da aber später drüber nach. Bedanke mich erstmal in die Runde
und steige jetzt mit dem Logger in den Wagen, mal schauen was er so
macht unterwegs. War in Versuchung das Netbook auf den Beifahrersitz zu
legen mit dem St-Link in der Platine aber es muss ja nicht sein, dass
sie mich aus dem Wrack schneiden und einer dann sagt "Ein Ingenieur auf
Testfahrt....." R.I.P. Aber hauptsache er hat seine "Problempunkte"
vorher noch erkannt :-(
Walter T. schrieb:> Buchtipps zur Softwarearchitektur auf Embedded Systemen wirst Du hier im> Forum nicht bekommen.
Es ist ein Fehler, zu denken, daß sich Embedded völlig von anderen
Programmieraufgaben unterscheidet.
Im Gegenteil, aus den bereits genannten Büchern kann man sehr viel für
die MC-Programmierung lernen und mitnehmen.
Für die Grundlagen, wie die einzelnen Hardwareeinheiten (Timer, ADC
usw.) funktionieren, sind die Datenblätter die beste Quelle.
Die Datenblätter der alten AVRs finde ich sehr gut aufgebaut und
informativ. Die neuen Microchip Datenblätter sind da schon ein
empfindlicher Rückschritt.
Christian J. schrieb:> aber es steht nur Müll drin.
Dann zeig halt den Quelltext, wie er nicht funktioniert.
Wird schon irgendwo ein Fehler sein...
Zu float und double:
Wenn du nicht den enormen Zahlenbereich von double brauchst, sondern die
Genauigkeit, fährst du mit Festkommazahlen wahrscheinlich besser als mit
double. Schneller und weniger Code.
Man sollte natürlich wissen, welche Zahlenbereiche man nutzt, sonst geht
es schief.
Peter D. schrieb:> Im Gegenteil, aus den bereits genannten Büchern kann man sehr viel für> die MC-Programmierung lernen und mitnehmen.
Ich habe nicht behauptet, dass man nichts mitnehmen könnte. "Clean Code"
und "weniger schlecht programmieren" sind auf jeden Fall lesenswert.
Nur zum Thema "Architektur" - also Software-Planung - steht in beiden
nichts drin. Beide sind mehr in der Richtung "wie Du das, was Du eh
machst, etwas ordentlicher machst".
Alle "Design Pattern"-Bücher die ich kenne, sind Java-lastig und lassen
sich meines Erachtens weniger auf µC anwenden. Zumindest habe ich noch
nie das Bedürfnis gehabt, kleine Fabriken in C nachzubilden.
> Mit * kommt das Richtige hier heraus, ohne Sternchen wird zwar> aktzeptiert aber es steht nur Müll drin.
1
double*GPS_GetSpeed(){
2
return((double*)&GPS.speedkmh);
3
}
4
5
if(fGPSValidFix&&(*GPS_GetSpeed()>5.0))
6
{
7
...
8
}
Hier rufst du die Funktion GPS_GetSpeed() auf, welche die Adresse von
GPS.speedkmh zurück liefert. Mit dem * wird die Adresse de-referenziert,
also die Variable GPS.speedkmh gelesen.
Das hat jetzt gar nichts mit meiner vorherigen Erklärung zu tun, wo ich
schrieb dass der Name einer Funktion dessen Adresse liefert. Hier wird
die Funktion aufgerufen, so dass sich das Sternchen auf deren
Rückgabewert bezieht.
1
void*zeiger=GPS_GetSpeed;// Adresse der Funktion
2
3
double*zeiger=GPS_GetSpeed();// Ergebnis der Funktion = Adresse von GPS.speedkmh
4
doublewert=*zeiger;// Wert von GPS.speedkmh
5
doublewert=*GPS_GetSpeed();// Wert von GPS.speedkmh
Stefan ⛄ F. schrieb:> void* zeiger = GPS_GetSpeed; // Adresse der Funktion
void* ist hier natürlich etwas feige.
Ein Zeiger auf eine Funktion könnte so aussehen:
1
double*GPS_GetSpeed()
2
{
3
staticdoubled=130.0;
4
return&d;
5
}
6
...
7
// fGPS_Speed ist ein Zeiger auf eine Funktion, die
8
// double* liefert, keine Parameter bekommt und mit der Adresse
9
// der Funktion GPS_GetSpeed initialisiert wird:
10
double*(*fGPS_Speed)()=GPS_GetSpeed;
11
12
// Funktion über den Zeiger aufrufen, und das zuweisen worauf
Klaus W. schrieb:> void* ist hier natürlich etwas feige.
Ich wollte es einfach halten. Er hat ja eh nicht vor, die Funktion
indirekt über einen Zeiger aufzurufen.
Christian J. schrieb:> Casts, weil volatile Vars das verlangen, sonst gibt es Warnungen....> volatile discards.... blabla.
Eigentlich ist das ganze Interface wenig sinnvoll. Ja, es ist akademisch
richtig, gekapselt etc. Doch konkret:
a) Header sollten nach internem und externem getrennt sein. Wenn die
internas in einer einzigen .c abgehandelt werden, dann gehört z.B.
nmea_info_t auch in gps.c. Oder in eine separate Header (Kapselung ist
hier in C einfacher als in C++, da internas intern bleiben können)
Also z.B. ifc_gps.h für das interface und gps.h für internas (oder
direkt in gps.c). Das hilft, den Scope der Variablen sauber zu trennen.
b) Es gibt zig Möglichkeiten für Daten im Interface. Üblich sind
* Funktionen (Speed(), Getter, Setter, Werte beim Zugriff manipulieren
zu können)
* globale Variablen (Gps.Speed, das einfachste)
* Callback-Funktionen (wo eine eigene Funktion an die API übergeben
wird, die bei Änderungen aufgerufen wird)
* Kombinationen davon (z.B. nur der Pointer ist global)
Du hast jetzt 8 Funktionen, die feste Zeiger zurückliefern. Diese
Abstraktion ist nutzlos: Feste Pointer sind äquivalent zu globalen
Variablen. Also entweder Inhalte zurück oder die Struktur GPS direkt
oder per Ptr öffentlich machen. (Wenn Deine Ptr auch zum schreiben
dienen, dann ist das ganz böse und eher Obfuscation als Kapselung. Wenn
nicht, dann sollten sie const werden)
Wichtig bei SW ist es, "Brüche" zu vermeiden. Wenn ich in einem anderen
Modul erst myPtr = GPS_GetSpeed() mache und später Y=*myPtr, dann suche
ich mir einen Wolf, wo denn GPS.speedkmh verwendet wird.
Ja, durch den Bruch kann ich Speed intern von kmh in meilen ändern, ...
aber so wirklich einfacher wird so eine Änderung dann doch nicht.
Der Code an sich ist aber gut, zeugt von viel Erfahrung, sinnvolle
Namen, Struktur, ... ich habe viele hochbezahlte SW-Entwickler
(Embedded-Ingenieure) gesehen, die lange nicht so gut sind.
P.S.: Ungewöhnlich scheint mir zu sein, dass scheinbar der Code
hauptsächlich Interrupt ausgeführt wird. Normalerweise hat man eine
Loop, in die rohe oder decodierte Uart-Zeichen per Puffer reinfallen und
abgearbeitet werden. Oder, falls zeichengenau reagiert werden muss, dass
man zwar decodiert im Interrupt, und auch reagiert (z.B. Ackn senden),
aber das eigentliche Update in der Loop erfolgt. Vielleicht erklärt das
Deine vielen Volatiles. Oft ein Grund, warum "es manchmal spinnt aber
nur wenn ... ".
Volatile sollte sich auf ADCs, Uarts, TimerTicks beschränken (Register
des µC), der Rest mit Flip/Flops (Interrupt Flipped, Main Flopped) oder
Änderungen synchron in der Loop. Und falls RTOS, dann nur Disable
Dispatcher, nicht DI.
A. S. schrieb:> Ungewöhnlich scheint mir zu sein, dass scheinbar der Code> hauptsächlich Interrupt ausgeführt wird. Normalerweise hat man eine> Loop, in die rohe oder decodierte Uart-Zeichen per Puffer reinfallen und> abgearbeitet werden. Oder, falls zeichengenau reagiert werden muss, dass> man zwar decodiert im Interrupt, und auch reagiert (z.B. Ackn senden),> aber das eigentliche Update in der Loop erfolgt. Vielleicht erklärt das> Deine vielen Volatiles.
Grüße von der Autobahntankstelle... mann ist das klein auf dem Handy :-(
Ich habe 1s Zeit, da dachte ich mach mal gleich alles :-)
Sonntag mehr! Sonst kriege ich Ärger von meiner Oberwelle...
Gruss & 73,
Christian
Walter T. schrieb:> Alle "Design Pattern"-Bücher die ich kenne, sind Java-lastig und lassen> sich meines Erachtens weniger auf µC anwenden.
Ich hätte da "Making Embedded Systems" von Elicia White anzubieten. Das
ist sehr "architekturlastig".
Dann noch "Embedded Controller" von Rüdiger Asche. Das geht auch auf
allgemeines Systemdesign ein, hat aber ein bisschen mehr den Schwerpunkt
bei RTOS, Synchronisation und Netzwerk.
Christopher J. schrieb:> Ich hätte da "Making Embedded Systems" von Elicia White anzubieten. Das> ist sehr "architekturlastig".
Mist. Das habe ich da. Ich habe es gelesen. Zweimal. Und vergessen, dass
ich es gelesen habe. Aber es stecken Zettel mit meinen Notizen drin. Und
laut denen ist es sehr gut.
Christopher J. schrieb:> "Embedded Controller" von Rüdiger Asche.
Ist es empfehlenswert?
Die lokale Universitätsbibliothek hat es ausleihbar, aber wer weiss,
wann man da wieder drankommt.
Christian J. schrieb:> Beispiel: Tastendruck. Drückt man die wird die aktuelle GPS Position> samt aller Daten des Strings auf SD Karte gesichert. Prellt natürlich> wie Hulle das Teil, beim Drücken und Loslassen.>> Übergeordnet läuft ein 1s Timer INT, der das Tastenflag entgegen nimmt> und die Taste nach 2s wieder freischaltet für neue Drücke (fallende> Flanken)> void EXTI3_IRQHandler(void)> if (!fTasteBlocked)...> void TIM3_IRQHandler()> {...> /* Hier wird die Berechnung der Wegstecke durchgeführt */
Huch. Also irgendwie habe ich den Eindruck, daß du alles in einen Topf
schmeißt und mittlerweile nicht mehr umrühren kannst - weil er zu voll
ist.
Gerade bei den Cortexen brauchst du doch für sowas nie und nimmer
separate Timer und Interrupts, da reicht es, wenn du dir eine Systemuhr
mit dem Timertick baust, die sowohl die Tasten abfragt, als auch selbige
entprellt und obendrein mit der Event-Verwaltung zusammenarbeitet, so
daß du verzögerte Events und Timeouts haben kannst, ohne daß sowas
codemäßig und rechenzeitmäßig merkbar zu Buche schlägt. Arbeite also
lieber mit Events, also ereignisgsteuert. Und sowas wie die Berechnung
der nächsten Position und Aktualisieren der Anzeige sollte doch eher aus
der Grundschleife in main() erledigt werden. Ich sag's mal so: schneller
als alle 200..300 Millisekunden braucht das alles garnicht zu sein.
Und was das Gleitkomma_Rechnen betrifft: Ich habe mittlerweile recht
gute Erfahrungen gemacht mit dem Sinus nach Pedersen. Der arbeitet in
float, also mit 24 Bit Mantisse und ist dabei recht schnell. Das einzige
Langsame daran ist die eine enthaltene Division.
W.S.
W.S. schrieb:> Gerade bei den Cortexen brauchst du doch für sowas nie und nimmer> separate Timer und Interrupts, da reicht es
So,
Gruss aus dem Osten. Ich habe mir mal das Buch "Making embedded systems"
bestellt nach den Rezensionen, die sehr gut waren. Danke für den Tip!
Und bei WS verzweifle ich oft.... er macht es immer anders, immer
dagegen. Er versteht nicht, dass ich die Dinge nutze weil sie da sind!
Einfach so! Weil das Hobby ist und kein Beruf (mehr). Wozu habe ich 8
Timer wenn ich sie nicht nutze? Wozu eine I2C Statemachine wenn es doch
auch per Software geht?
Und dass eine Wegstreckenrechnung sogar auf die Mikrossekunde genau sein
muss.... ich weiss es. 378 km zu 384km auf dem Tacho gestern. Aber
lassen wir es ... es geht immer anders. Hauptsache es funktioniert!
Sin, cos gibt es mit LUT Tabellen, ohne eine einzige fp Berechung...
Vorteil aber auch wohl code mächtiger.
Hans-Georg L. schrieb:> oder bei Bruce Powel Douglass
Von diesem Autor ist "Design Patterns for Embedded Systems in C", dessen
Name erst einmal vielversprechend ist, als umfangreiche Leseprobe im
Netz zu finden.
Wobei der erste Eindruck nicht so dolle ist. Es sieht so aus, als baue
er C++ in C nach. Zumindest gehört zu den ersten Themen (Seiten 14ff.)
das Nachbauen von Polymorphie mittels virtual function table.
Walter T. schrieb:> Christopher J. schrieb:>>> "Embedded Controller" von Rüdiger Asche.>> Ist es empfehlenswert?
Meiner Meinung nach ist es das, sonst hätte ich es ja nicht erwähnt. Es
geht halt nicht so tief auf Architektur ein, deckt dafür aber eben viele
andere Aspekte moderner Mikrocontroller ab. Ich würde sagen, dass es
gerade für Leute die aus der AVR-Ecke kommen durchaus sehr lesenswert
ist.
Walter T. schrieb:> Hans-Georg L. schrieb:>>> oder bei Bruce Powel Douglass>> Von diesem Autor ist "Design Patterns for Embedded Systems in C", dessen> Name erst einmal vielversprechend ist, als umfangreiche Leseprobe im> Netz zu finden.> Wobei der erste Eindruck nicht so dolle ist. Es sieht so aus, als baue> er C++ in C nach. Zumindest gehört zu den ersten Themen (Seiten 14ff.)> das Nachbauen von Polymorphie mittels virtual function table.
Ich habe das Buch zwar nicht gelesen aber die Nutzung solcher "funtion
tables" zur Erstellung von Interfaces ist eine absolut gängige Praxis in
der Embedded-Welt. Man muss nur mal in das Treiberdesign im Linux-Kernel
schauen, der strotzt nur so davon und naja, es hat sich eben bewährt.
"C and the 8051, Programming for Multitasking"
Von Thomas W. Schultz
ISBN 0-13-753815-4
Dieses Buch vermittelt die Grundlagen für praxisnahes Programmieren. Die
grundsätzlichen Maßnahmen gelten auch für andere uC Familien. In dem
Buch fand sich einiges Interessantes. Die Absicht des Autors ist
hauptsächlich uC Multitasking Denkweise zu fördern und lässt auch HW
Design nicht zu kurz kommen.
Die Hauptsache ist, die Programme so zu konzipieren, daß niemals
Blockaden auftreten. Man sollte langsame Peripherien so ansteuern, daß
das Hauptprogramm nie aufgehalten wird. Wo es geht mit DMA, ISRs und
Puffer arbeiten, so, daß alles entkoppelt ist und niemals der Ablauf des
uC blockiert wird. Wo es geht und notwendig ist mit State Machines den
Programmablauf zu entkoppeln und mit kleinen Time Slices operieren. Dann
geht es auch ohne RTOS.
In 8-Bittern hat sich das immer bewährt und die Steuerprogramme mit den
ich zu tun hatte, funktionierten immer zügig ohne merkbare
Verzögerungen.
Einen eindrucksvollen Beweis dafür lieferte ein AVR Arduino Funkbaken
Steuerprogramm von mir wo Morse Kennung, RS232 Communications, HW
Peripheriesteuerung, Analog u.a. alles gleichzeitig abgearbeitet wurde
ohne das Morse Code senden in irgendeiner Weise zu beeinträchtigen.
Auch 8-Bitter können beeindruckende Real-Time Arbeitsleistung zeigen.
Immerhin hat ein armseliger Arduino bei 16MHz weit über 10MIPS
Arbeitsleistung. Damit lässt sich schon etwas anfangen.
ARM, MIPS u.ä. haben mehr Sinn wenn hochkomplexe Steuerstacks oder
Farb-LCD betrieben werden müssen. Für Wald und Wiesen Ablaufsteuerungrn
wie sie in vielen Geräten vorkommen, reichen 8-Bitter immer noch Dicke
solange man nicht Internet macht. Was mich und meine Projekte betrifft
habe ich schon lange keinen 32-Bitter mehr gebraucht.
Abgesehen davon sind mir 5V Systeme oft praktisch bequemer. 3.3V ist oft
eine große PIA;-) Es muß ja nicht immer alles "hochmodern" sein.
8-Bitter tun es oft auch noch. Es ist schade, daß so oft 8-Bitter als
Schnee von gestern abgetan werden. AVR oder PIC ist tot, hört man hier
oft. Auch sind die 8-Bitter datenblattmäßig oft viel übersichtlicher und
bare-metal Programmieren der internen Peripherieregister ist angenehmer.
Who needs Cube and Co. really.;-)
But, what do I know? Als Fossil des letzten Jahrhunderts sehe ich
gewisse Dinge eben gelassener.
Schönes Wochenende noch!
Gerhard
Gerhard O. schrieb:> Auch sind die 8-Bitter datenblattmäßig oft viel übersichtlicher und> bare-metal Programmieren der internen Peripherieregister ist angenehmer.> Who needs Cube and Co. really.;-)
Das liegt daran, dass die (mir bekannten) Bit Controller aus einer Hand
kommen, währen die (mir bekannten) 32 Bit Controller aus Modulen
unterschiedlicher Hersteller zusammen gewürfelt wurden.
Dieses Stückwerk erfordert mehr Konfiguration, um die einzelnen Module
zur Zusammenarbeit zu bringen und es bringt mehr überraschende
Seiteneffekte mit sich.
Nur so als Beispiel:
Wenn ich bei einem 8 Bit AVR die I²C Schnittstelle einschalte, dann muss
ich mir un die Konfiguration der I/O Pins keinen Kopf machen, denn I²C
ist I²C.
Bei STM32 muss ich hingegen den Port aktivieren, die I/O Pins
konfigurieren, das I²C Modul aktivieren, konfigurieren dann zum Schluss
starten. Dann kommtes hier teilweise auch noch auf die richtige
Reihenfolge an. Mit Interrupts und DMA wäre es nochmal komplexer.
Dazu kommt, dass das Datenblatt/Refrence Manual bei STM32 nicht nur in
zweo Dokumente aufgesplittet ist, sondern dort jedes Modul einzeln für
sich betrachtet beschrieben ist. Die Abhängigkeiten untereinander
ergeben sich häufig nur durch Lesen zwischen den Zeilen oder
Ausprobieren.
Wenn du bei einem 8 Bit AVR hingegen eine Funktion nutzen willst,
springst du zu dem entsprechenden Kapitel und das steht wirklich alles,
was man dazu wissen muss. Auf alle anderen relevanten Kapitel wird mit
Kommentar verwiesen - ein Punkt der seit der Übernahme durch Microchip
leider schlechter geworden ist.
Die Komplexität und die schwere verständlichkeit der Doku hat ein Tool
wie Cube MX regelrecht notwendig gemacht. Nicht für alle, aber für
viele.
Walter T. schrieb:> Hans-Georg L. schrieb:>> oder bei Bruce Powel Douglass>> Von diesem Autor ist "Design Patterns for Embedded Systems in C", dessen> Name erst einmal vielversprechend ist, als umfangreiche Leseprobe im> Netz zu finden.>> Wobei der erste Eindruck nicht so dolle ist. Es sieht so aus, als baue> er C++ in C nach. Zumindest gehört zu den ersten Themen (Seiten 14ff.)> das Nachbauen von Polymorphie mittels virtual function table.
Er verwendet in seinen Buch "structured programming" und wenn man
zusammengehörige Daten oder ein interface in einer C-Struktur
zusammenfasst sieht das auf den ersten Blick schon ähnlich wie eine C++
Klasse aus.
Ich habe das Buch "Weniger Schlecht Programmieren" (978-3897215672)
bekommen und schon einige Seiten gelesen.
So "unterhaltsam" wie das Cover verspricht finde ich es nicht. Die
Ratschläge darin erscheinen mir Sinnvoll, wenngleich mir das Allermeiste
selbstverständlich vorkommt.
Mir fallen aber direkt zwei Anfänger-Programmierer ein, denen ich das
Buch dringend nahelegen würde, wenn sie noch in der Firma wären.
Das Buch kann man gut auf der Zugfahrt zur Arbeit lesen, oder beim
Entspannen im Park. Man braucht dabei nicht am Rechner zu sitzen.
---
Unabhängig von Büchern möchte ich hervor heben, dass ich die wichtigsten
Dinge nicht aus Büchern sondern von anderen Programmierern gelernt habe.
Jeder hat einen eigenen Stil beim Organisieren, Kommunizieren,
Dokumentieren und Programmieren. Im Team entstehen dadurch unweigerlich
Reibungspunkte, an denen alle Wachsen und sich verbessern, wenn sie dazu
bereit sind, sich gegenseitig zuzuhören und auf die Bedürfnisse der
anderen einzugehen.
Ein im Team gemeinsam erarbeiteter Stil, der gewisse Abweichungen
toleriert, ist wichtiger, als alle Patterns und Raffinessen der
Programmiersprache zu kennen. Der erfolgreiche Stil ist in jedem Team
ein anderer, soviel kann ich euch nach 25 Jahren in 4 Firmen versichern.
Es gibt nicht das eine optimale Patentrezept. Deswegen betrachtet bitte
jedes Lehrbuch dazu (egal wie angesehen sein Autor ist) lediglich als
Anregung, nicht als Doktrin.
---
Noch ein Tipp: Falls du an Projekten arbeitest, die langfristig (>3
Jahre) gefplegt werden, dann mache dich nicht von einer bestimmten IDE
abhängig. Betrachte die IDE als besseren Texteditor, aber gestalte das
Projekt so, dass du es jederzeit ohne IDE am besten sogar unabhängig vom
Betriebssystem bauen kannst. Denn die guten effizienten Arbeitsmittel
wechseln schneller, als man denkt.
Visual Studio Code geht genau diesen Weg. Es versucht gar nicht erst,
die Kommandozeilentools zu ersetzen, es ruft sie lediglich auf. Jede
andere IDE kann das auch irgendwie, mann muss sich nur die Mühe machen,
es herauszufinden. Die Mühe lohnt sich.
In 3 von 4 Firmen wo ich arbeite galt die Regel: Jeder darf die IDE
nehmen, die ihm beliebt, solange sie das Projekt für die Anderen nicht
kaputt macht. Und das haben wir auch gemacht - der gemischte Betrieb ist
praktikabel.
Stefan ⛄ F. schrieb:> Die Komplexität und die schwere verständlichkeit der Doku hat ein Tool> wie Cube MX regelrecht notwendig gemacht. Nicht für alle, aber für> viele.
So, bin wieder da. Und in der Wildnis der Brandenburger Wälder kamen mir
auch ein paar Ideen bei 15km Marsch....
char* MyFunction(...) {
return *(Variable, Array etc)
};
MyFunction = Adresse der Funktion
MyFunction() = Rückgabewert der Funktion
*MyFunction = ???
Und das mit den Verständnisproblemen sieht man im ST Forum. Vor allem
wenn Module gekoppelt werden müssen zb über DMA, ganz besonders der I2C
DMA ist ein Brief mit 7 Siegeln.
Der nächste Kampf wird der STOP Mode sein, denn daraus wacht er aktuell
nicht mehr auf über einen PinSource_10 Interrupt über EXTI10_15_IRQn.
Derzeit habe ich einen stetig höheren Stromverbrauch je mehr Clocks ich
abschalte vor dem normalen Sleep Mode. Von 25mA (inklusive der Rest
Elektronik, die nicht abschaltbar ist) bis 29mA wandert er hoch. Dachte
jeder Clock weniger wäre auch weniger Strom. Aber dem war nicht so.
Christopher J. schrieb:> Walter T. schrieb:>> Christopher J. schrieb:>>>>> "Embedded Controller" von Rüdiger Asche.>>>> Ist es empfehlenswert?>> Meiner Meinung nach ist es das, sonst hätte ich es ja nicht erwähnt. Es> geht halt nicht so tief auf Architektur ein, deckt dafür aber eben viele> andere Aspekte moderner Mikrocontroller ab. Ich würde sagen, dass es> gerade für Leute die aus der AVR-Ecke kommen durchaus sehr lesenswert> ist.
Sehr herzlichen Dank für die netten Worte, Christopher!
Im Buch selber ist (noch, das kam erst später) nicht erwähnt, dass das
Material durch einen blog ergänzt wird:
http://www.ruediger-asche.de/Blog
...etwas verwaist, müsste ich mal wieder updaten.
Der Beispielcode ist für Jene/n frei und ohne jegliche Einschränkungen
herunterladbar.
Die Frage ob Buch oder nicht muss Jede/r für sich selbst beantworten.
Für mich war die Arbeit daran i.W. ein braindump von (damals) 20 Jahren
aktiver Embedded Entwicklung. In
https://www.google.de/books/edition/Embedded_Controller/8ra8DQAAQBAJ?hl=de&gbpv=1&dq=asche+embedded&printsec=frontcover
lässt sich ins Buch hineinschnuppern, um nicht die Katze im Sack kaufen
zu müssen.
Für weitergehende Fragen darf man/frau mich gerne per PM kontaktieren.
P.S. Ja, das ist eine Art von Werbung, deswegen würde ich an dieser
Stelle gerne hinzufügen, dass es (für die an Büchern interessierten
Forumsmitglieder) auch sehr gute andere Bücher gibt. Das Problem ist
allerdings (wie in diesem thread schon sichtbar), dass Jede/r LeserIn
eine eigene Sichtweise und eigene Erwartungen ans Thema hat, die kein
einzelnes Buch in Gänze erfüllen kann.
Jörg W. schrieb:> Das klingt danach, als würdest du Strom über IO-Ports ziehen oder> speisen.
Vermutlich liegt es eher daran, dass ich eben nicht alle Ports auf AIN
gestellt habe. Aber wie soll man auch SPI und I2C Ports legen? Hi-Z wäre
für mich logisch.
Hallo,
weiss zufällig jemand, warum das Aufwachen aus dem STOP Mode im Debugger
klappt und im Release Mode nicht mehr? Ich lasse über GPIOB, Pin10 einen
INT auslösen, der die CPU wieder aufwecken soll.
Klappt auch alles prima... im Debug Mode. Klopfe ich auf die Platine
meldet er ADXL345 per INT2 Pin eine steigende Flanke an PB10 und weiter
gehts.
Im Release Mode wacht er nicht mehr auf :-(
STOp Mode:
Christian J. schrieb:> weiss zufällig jemand, warum das Aufwachen aus dem STOP Mode im Debugger> klappt und im Release Mode nicht mehr?
Sowas riecht immer verdammt nach einem vergessenen "volatile" – ich
nehme mal an, dass du zwischen Debug- und Release-Mode mit
unterschiedlichen Optimierungseinstellungen arbeitest.
Das erklärt natürlich das Problem nicht komplett, aber möglicherweise
wacht er ja tatsächlich auf und du bemerkst es nur aufgrund eines
solchen Problems nicht?
Jörg W. schrieb:> Sowas riecht immer verdammt nach einem vergessenen "volatile" – ich> nehme mal an, dass du zwischen Debug- und Release-Mode mit> unterschiedlichen Optimierungseinstellungen arbeitest.
Da bin ich grad dran, sowas hatte ich schonmal mit der SPI, die lief
auch nur im Debug Mode. Nach dem Aufwachen soll eine LED angehen, die
berühmte rote Debug LED, tut sie leider nicht.
Christian J. schrieb:> Nach dem Aufwachen soll eine LED angehen
Bau doch mal die entsprechenden Befehle zum Befummeln des LED-Pins
direkt in die ISR. So viel ist das ja wohl nicht. Dann siehst du aber
zumindest, ob die ISR gerufen wird oder nicht.
Jörg W. schrieb:> Bau doch mal die entsprechenden Befehle zum Befummeln des LED-Pins> direkt in die ISR.
Wird gemacht.... life sozusagen....
Au weia...
bin\Release\f103.map|1|
Program size (bytes): 65124|
Data size (bytes): 280|
BSS size (bytes): 2916|
----------------|
Total size (bytes): 68320 (R/W Memory: 3196)|
== Build finished: 0 errors, 0 warnings (0 minutes, 7 seconds) ===|
Nein, der INT wird leider nicht ausgelöst :-(
habe den Release mal mit -Og kompiliert genauso wie den Debug... uhh...
ich hasse diese Sachen.
Ist das hier überhaupt richtig?
SYSTICK_ISR_OFF; /* Systick aus */
PWR_ClearFlag(PWR_FLAG_WU);
PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);
Erkenntnis 1: 300 Bytes vor Ende des Flashs läuft der Debugger nicht
mehr, geht in den HardFault Handler. Schätze mal der braucht auch etwas
Platz-
Erstmal was löschen damit wieder mehr Platz ist....
geht alles schief bleibt es beim Sleep. Sind zwar 12mA mehr statt der
herrlichen 1,5mA aber dann ist es eben so.
Christian J. schrieb:> Erkenntnis 1: 300 Bytes vor Ende des Flashs läuft der Debugger nicht> mehr, geht in den HardFault Handler.
Das wundert mich allerdings. Der Debugger als solches sollte sich nicht
für den Flash-Verbrauch interessieren.
Jörg W. schrieb:> Das wundert mich allerdings. Der Debugger als solches sollte sich nicht> für den Flash-Verbrauch interessieren.
Ich sitze ja grad dran... das Board lässt sich ja nach dem Flash des
Debug Code auch mit Resetknopf starten und arbeitet mit abgezogenem sdw
Stecker. Naja, und da klappt alles prima.... grundsätzlich kannste das
also so machen aber das erklärt das Problem nicht.
Falls noch jemand eine Idee hat... ich gebe auf. Mit Sleep bin ich bei
14mA und da klappt alles. Man muss nur alles vorher abschalten was geht.
Den CPU Takt runter nehmen weiss ich nicht wie das gehen soll. Wäre ja
auch denkbar, solange er im Sleep ist.
edit: Ok, gelöst und erledigt: Mit RCC_DeInit() vor dem Sleep (HSE auf
8Mhz) fällt der Stromverbrauch ebenfalls auf nur noch 3mA. reicht total
aus, quetschen wir die Zitrone daher nicht weiter aus.
Christian J. schrieb:> Nein, der INT wird leider nicht ausgelöst :-(>> habe den Release mal mit -Og kompiliert genauso wie den Debug... uhh...> ich hasse diese Sachen.>> Ist das hier überhaupt richtig?>> SYSTICK_ISR_OFF; /* Systick aus */> PWR_ClearFlag(PWR_FLAG_WU);> PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);
Da du mit der HAL arbeitest sollte du sie nicht umgehen :
HAL_SuspendTick();
PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);
// Sleep
HAL_ResumeTick();
Alexander S. schrieb:> Hans-Georg L. schrieb:>> Leider ist, wie so oft kein Probekapitel dabei.>> Also ich sehe in dem google-Link fast alles von Kapitel 1 (und 2).
Sorry, da habe ich mich überspitzt ausgedrückt aber da geht es auch noch
nicht so richtig ans eingemachte ;-) Mich interessiert immer an einem
Buch was es von anderen unterscheidet oder besser macht.
Ich wollte auf keinem Fall von dem Buch abraten. Wer aus den beiden
Kapiteln mehr lernt wie aus den Datenblättern soll es sich kaufen.
Früher war ich Dauerkunde bei allen Fachbuchhandlungen in MA und HD da
konnte man im gesamten Buch blättern und sich dann entscheiden.
Hans-Georg L. schrieb:> HAL_SuspendTick();> PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);> // Sleep>> HAL_ResumeTick();
Mit der SPL (bin zu alt für HAL :-) Und da bräuchte ich mal die Sourcen,
habe kein CubeMX installiert.
Kurz:
1. Es ist unerklärlich, warum der nicht wieder aufwacht. Er müsste es!
2. Mit Sleep in einer Schleife bei Minimaltakt erreicht man fast das
Gleiche.
Da im release kein Debug möglich ist außer einer LED wird es wohl für
immer ein Geheimnis bleiben. Und an den Asm komme ich nicht ran, wegen
der LTO. Die Einstellung macht nur Zahlen aus den .s Dateien.
Nabend,
ich hole den nochmal hoch :-) Also den Thread.
Mein Projekt hat aktuell 12 Module, darunter einige für die Hardware,
andere sind Anwendung und im man.lc werden alle zusammen in einer recht
großen Statemachine verquickt.
Manche Module möchte ich wieder verwenbar machen, sie sind nicht mit
anderen verwoben, steuern die Hardware und liefern Daten nach außen.
Wie kapsel ich diese Daten nun "read only"? Getter und Setter kenne ich,
irgendwie uncool.
Mein modul.h
extern const double* GPS_Speed; /* Geschwindigkeit in kmh */
extern const float* GPS_Latitude; /* Breiten in Dezimalgrad */
extern const char* GPS_LatStr; /* Breite als float String
*/
extern const float* GPS_Longtitude; /* Länge in Dezimalmgrad als
float */
extern const char* GPS_LongStr; /* Länge als String */
extern const char* GPS_NmeaString; /* Aktueller NMEA String */
extern const struct tm* GPS_Time; /* Aktuelle GPS Zeit */
Das soll das Interface für die Caller sein, nicht aber der Struct mit
vielen Daten der jetzt im modul.c static verborgen ist und nicht mehr
sichtbar.
Im modul.c kriegen die Vars eine feste Zuweisung. Damit will ich
verhindern, dass ich jemals auf die idee komme in den Vars anderer
Module zu pfuschen, absichtlich oder zufällig.
Wird das so gemacht? Der Caller würde mit
float MyLon = *GPS_Longtitude;
sich die Werte holen, kann sie aber nicht beschreiben, da sonst
Fehlermeldung kommt.
Ok, C++ kann das sowieso aberhier ist nunmal C.
(Wegen volatile und verwendung im Interrrupt etwas komplexere
Adressierung)
1
constdouble*GPS_Speed=(double*)&GPS.speedkmh;/* Geschwindigkeit in kmh */
2
constfloat*GPS_Latitude=(float*)&GPS.latitude;/* Breiten in Dezimalgrad */
3
constchar*GPS_LatStr=(char*)&GPS.latstr;/* Breite als float String */
4
constfloat*GPS_Longtitude=(float*)&GPS.longtitude;/* Länge in Dezimalmgrad als float */
5
constchar*GPS_LongStr=(char*)&GPS.longstr;/* Länge als String */
Christian J. schrieb:> Da wird sogar double und vieles mehr verwendet.... wir sind hier nicht> bei den 8-Bit-Geklapper-Arduinos! Auch wenn die FPU noch nachgeliefert> wird... die 411er "Schwarze Pille" sind unterwegs.
Trotzdem sollte man mit FPU zB sqrtf und nicht die double-Variante sqrt
benutzen.
Mampf F. schrieb:> Trotzdem sollte man mit FPU zB sqrtf und nicht die double-Variante sqrt> benutzen.Mampf F. schrieb:> Trotzdem sollte man mit FPU zB sqrtf und nicht die double-Variante sqrt> benutzen.
Die Aussage ist nicht pauschal zu sehen. Es ist doch völlig egal wie
lange etwas braucht, wenn genug Zeit da ist? 1 Rechnung alle 10s muss
ich machen und die so genau wie möglich, da die Zahlen sehr klein sind,
die immer wieder addiert werden und sich Fehler daher immer weiter
fortsetzen.
z.b.
double distance = 6378.388 * (acos(sin(lat1) * sin(lat2) + cos(lat1) *
cos(lat2) * cos(lon2 - lon1)));
Braucht 38900 Clocks ohne FPU. Teste es aber gern mal in allen Varianten
mit FPU die Tage. Bin selbst neugierig, seit der F401er läuft.
Also wenn Du das so machst, solltest Du in jedem Fall keine
Einzelvariablen benutzen, sondern eine Struktur, in der Du die
zusammengehörigen Variablen gruppierst. Damit machst Du auch den Zugriff
modularer:
Ruediger A. schrieb:> const GPSDATASTRUCT * glbROGPSStructCopy = (const GPSDATASTRUCT> *)&glbRWGPSStruct;
Danke! Hätte ich auch drauf kommen können :-)
Nur einen Hinweis dazu: übliche Konvention ist, dass man
ALLES_IN_GROSSBUCHSTABEN nur bei Makros benutzt, um diese sofort als
einen Makro erkennen zu können. Alle anderen Namen sollte man daher
eigentlich nicht so schreiben.
Aber da der Mod und Experte grad hier ist.. wie erklärt es sich, dasss
beim Flashen it EBlink, der wirklich gut ist und nicht so oft abstürtzt
oder ausgestöpselt werden muss die CPU daran gehindert wird, dass der
reset Knopf funktioniert? Denn der Flasher startet zwar das soeben
geflashte programm sofort aber reset wird erst wieder nach Power ON/OFF
wirksam. Das nervt etwas.
Die Option "dr" habe ich rausgenommen aus dem Steuerfeld, ändert aber
auch nix. Ich dachte reset sei nicht maskierbar?
Christian J. schrieb:> Mampf F. schrieb:>> Trotzdem sollte man mit FPU zB sqrtf und nicht die double-Variante sqrt>> benutzen.>> Die Aussage ist nicht pauschal zu sehen. Es ist doch völlig egal wie> lange etwas braucht, wenn genug Zeit da ist? 1 Rechnung alle 10s muss> ich machen und die so genau wie möglich, da die Zahlen sehr klein sind,> die immer wieder addiert werden und sich Fehler daher immer weiter> fortsetzen.
Ja du hast recht - eigentlich hätte ich schreiben wollen, dass es auch
noch die ...f-varianten gibt, die explizit auf Float rechnen.
Viele wissen das nicht :)
Christian J. schrieb:> Ich dachte reset sei nicht maskierbar?
Ich kenne deinen konkreten ARM nicht so genau, aber beim SAM4E haben wir
auf jeden Fall schon mal den Reset-Anschluss umdefiniert, um stattdessen
nur einen Interrupt auszulösen. Motivation dabei war, dass wir die
interne RTC weiter laufen lassen wollten, um Datum und Uhrzeit für eine
SD-Karte zu behalten, was mit einem echten Reset nicht gegangen wäre.
Allerdings musste man das wirklich explizit so einstellen. Kann mir
nicht so recht vorstellen, warum der Debugger das abklemmen sollte.
Kann EBlink aber nur empfehlen! Flasher und Debugger in einem. Damit
wird fast jedes BluePill zu einer 128kb Variante, wobei man ALLE mit
einer 128kb Datei testen muss !!! Bei manchen verkackte der Verify
leider in dem zusätzlichen Speicher.
Christian J. schrieb:> Flasher und Debugger in einem.
Was soll daran besonderes sein? Läuft doch bei den Cortexen eh alles
über SWD (Serial Wire Debug).
Jörg W. schrieb:> Was soll daran besonderes sein? Läuft doch bei den Cortexen eh alles> über SWD (Serial Wire Debug).
Wenn Du Dich früher mit st-link und st-link DGB herum geschlagen hast
weisst du was ich meine. Strikte prüfung der core-id, damit kannste ne
Menge Chinakracher gleich vergessen. Ewiges An und Abstecken des Sticks
wenn zwischen Release Flash und Debug gewechselt wird. Jetzt kann der
Stick die ganze Zeit dran bleiben und man steuert den DGB über die cmd
Shell, wenn man mal eben resetten will. Oder halt aus der IDE heraus.
Aber im Stop Mode und Sleep.... da ist die swd nunmal weg. Das bleibt,
es sei denn man fügt 1s Normalbetrieb zu, dann muss man schnell reset
drücken vorher.
Da ich seit jeher auf sehr vielen Hochzeiten tanze, habe ich diese
Gartenzaun-Mentalität der Hersteller nie wirklich gemocht. Ich debugge
seit 30 Jahren mit GDB, und seit ich mit ARMs arbeite, ist das
Bindeglied dann ein OpenOCD. Damit kann ich mit einem AtmelICE (von
denen wir einige hier haben) genauso gut einen STM debuggen wie ich mit
einem STlink einen Atmel (jetzt Microchip) SAMxx debuggen kann, und
beide zusammen gehen halt auch genauso schön mit einem einfachen FT2232.
Da uns die dünnen 10poligen Kabel, wie sie durch die
Cortex-M-Debug-Schnittstellen-Empfehlung (schönes Wort :) vorgegeben
sind und vom AtmelICE umgesetzt wurden, dann aber zu fragil waren, haben
wir auf den nächsten Boards gleich einen FT2232 mit eindesignt. Dessen
zweiter Kanal kann prima als Consolen-UART fungieren.
Jörg W. schrieb:> Nur einen Hinweis dazu: übliche Konvention ist, dass man> ALLES_IN_GROSSBUCHSTABEN nur bei Makros benutzt, um diese sofort als> einen Makro erkennen zu können. Alle anderen Namen sollte man daher> eigentlich nicht so schreiben.
Definiere "üblich?"
So ziemlich alle Strukturnamen in Segger Middleware sind all capitals,
außerdem einige in lwip, freertos und openssl (grobe Durchsicht).
Umgekehrt kann ich auch in all diesen Codebasen keine konsequente
Benamung von Macros durch all caps erkennen.
Wo findet sich diese Konvention?
Und vermeide den cast.
Ein volatile wegzukapseln ist gefährlich, ein const hinzu unnötig.
Pointer-Casts wirklich nur wenn unbedingt notwendig, sie sind das
gefährlichste mit in C.
Ruediger A. schrieb:>> ALLES_IN_GROSSBUCHSTABEN nur bei Makros benutzt, um diese sofort als>> einen Makro erkennen zu können.
Das mag zu Zeiten wo es noch keine Farben gab in den Editoren so gewesen
sein. heute ist es längst nicht mehr üblich. Ich mache das so weil ich
es vor 20 Jahren gelernt habe. Aber ne Konvention war das eher nicht.
Heute ist eben alles bunt :-)
A. S. schrieb:> Pointer-Casts wirklich nur wenn unbedingt notwendig, sie sind das> gefährlichste mit in C.
Geht aber nicht anders, wenn Du mit volatile arbeitest und es musst.
Dann muss alles gecastet werden, sonst wird gemeckert. Sieht doof aus,
ist aber so.
Christian J. schrieb:> Ruediger A. schrieb:>>> ALLES_IN_GROSSBUCHSTABEN nur bei Makros benutzt, um diese sofort als>>> einen Makro erkennen zu können.>> Das mag zu Zeiten wo es noch keine Farben gab in den Editoren so gewesen> sein. heute ist es längst nicht mehr üblich. Ich mache das so weil ich> es vor 20 Jahren gelernt habe. Aber ne Konvention war das eher nicht.>> Heute ist eben alles bunt :-)
Das Zitat kam nicht von mir, sondern von Jörg. Ich habe es auch schon
angezweifelt. Bitte nicht falsch zitieren, Danke!
Ruediger A. schrieb:> Definiere "üblich?"
Bei vielem, was mir in 30+ Jahren C untergekommen ist, inklusive vieler
style guides einschließlich meiner Erinnerung nach Misra.
OK, eine zweite übliche Verwendung von ALL_CAPS hatte ich vergessen
oben: enum-Elemente.
A. S. schrieb:> Und vermeide den cast.>> Ein volatile wegzukapseln ist gefährlich, ein const hinzu unnötig.> Pointer-Casts wirklich nur wenn unbedingt notwendig, sie sind das> gefährlichste mit in C.
Irgendwie scheinst Du das nicht richtig verfolgt zu haben. Der cast ist
ganz genau das, worauf es Christian ankommt. Er will dieselbe Struktur
für einen Kontrollpfad RW haben, für den Anderen RO (vom Compiler her
abgefangen).
Ob das kosher ist oder nicht, beurteile ich nicht, ich habe nur seinen
Ansatz modularisiert.
A. S. schrieb:> Ein volatile wegzukapseln ist gefährlich, ein const hinzu unnötig.> Pointer-Casts wirklich nur wenn unbedingt notwendig, sie sind das> gefährlichste mit in C.
Und was ist daran gefährlich?
C ist eine gefährliche Sprache und war es immer. Weil sich da eben alles
miteinander verbinden lässt. PASCAL war da viel strikter und auch
teilweise schöner. Allein schon, dass Schleifen nicht geprüft werden auf
eine Verletzung der Grenzen ist schon gefährlich. Darum hat jedes Array
bei mir auch 1 Element mehr. char text[10] hat eben die Indizes 0 bis 9
und nicht 1 bis 10 oder 0 bis 10. Schöne Falle.
Der Cast dient doch bloss dem Compiler dazu, dass er weiss wie die Vars
im Struct zu adressieren sind. Wenn er das nicht wissen kann muss man
ihm es mitteilen, dass man weiss was man tut.
Da der Cortex sowas Nettes hat wie den HardFault Handler wo er bei mir
schon oft genug war, wenn ich Speicher überschrieben habe. Dann
entgleist meist alles richtig.
Christian J. schrieb:> Der Cast dient doch bloss dem Compiler dazu, dass er weiss wie die Vars> im Struct zu adressieren sind.
Nein. Der Cast macht aus const ein volatile oder aus ABC ein def.
Deine Casts sind mit 99% Wahrscheinlichkeit Designfehler oder unnötig.
Ruediger A. schrieb:> Ob das kosher ist oder nicht, beurteile ich nicht, ich habe nur seinen> Ansatz modularisiert.
Ging auch nicht an Dich, nur an Christian.
Dein Pointer ist richtig und gut und von mir auch empfohlen
A. S. schrieb:> die Struktur GPS direkt oder per Ptr öffentlich machen.
(Mit dem Rat vorher, privates und öffentliches zu trennen)
A. S. schrieb:> Deine Casts sind mit 99% Wahrscheinlichkeit Designfehler oder unnötig.
Ich würde den Scheiss gerne weglassen aber es geht nicht. Ich brauche
VOLATILE, sonst läuft das Programm nicht mehr, weil ich mal so eben 7-8
ISR habe wo ne Menge drin passiert.
Christian J. schrieb:> da oben steht doch wieso sie da sind. Weil es Warnungen gibt!
Warnungen einfach "weg zu casten" ohne zu verstehen, warum es sie
eigentlich gibt, ist immer die schlechteste aller Möglichkeiten.
Christian J. schrieb:> Die Rechnung brauche ich als Anzeige wie weit ich von einem POI weg bin,> den ich gespeichert habe. Es gibt eine mit 8000 Zyklen und eine mit> 39.000 Zyklen, die ab ca 50km wirksam wird. Da wird die einfache zu> ungenau wegen der Erdkrümmung.
Das erzeugt in mir die Frage, warum jemand wissen will ob das Ziel nun
50,000 oder 50,001 km Luftlinie weit weg ist.(Zumal man auf der
Luftlinie sowieso fast nie zum Ziel kommt)
Es sei denn, das sind Zielangaben fuer das Militaer, die wuerden aber
hoffentlich nicht hier solche Fragen stellen.
Mich stoert z.B. schon die Anzeige 50,1 km, die ist nutzlos.
Erst unterhalb von 5km interessieren mich halbe km, erst unterhalb von 2
km interessieren mich 10tel km.
Aber jeder wie er mag.
wendelsberg
Christian J. schrieb:> const nmea_info_t* roGPS = (const nmea_info_t *)&GPS;
könntest Du auch so umschreiben:
const volatile nmea_info_t* roGPS = (const volatile nmea_info_t *)&GPS;
Würde vermutlich die Fehlermeldung aus deinem Screenshot auch
eliminieren. Ist bei manchen Compilern auch die sicherere Variante,
nicht in sehr lange Debuggingsessions zu taumeln (da gebe ich A.S.
recht).
Jörg W. schrieb:> Warnungen einfach "weg zu casten" ohne zu verstehen, warum es sie> eigentlich gibt, ist immer die schlechteste aller Möglichkeiten.
So geht es und anders klappt es nicht.
Deklaration im Header:
Christian J. schrieb:> Wie kapsel ich diese Daten nun "read only"? Getter und Setter kenne ich,> irgendwie uncool.
Manchmal ist es recht effizient, den Nutzer der Daten zu
benachrichtigen, wenn sich was ändert.
(Als design pattern ist es das observer pattern.)
Das sieht so aus, daß sich der Nutzer eines Moduls bei diesem anmeldet
(subscribe) und dann immer benachrichtigt wird, wenn neue Daten
anliegen.
Im einfachsten Fall macht man das mit einer callback-Funktion. Die liegt
beim Nutzer der Daten und beim Anmelden am Modul wird die Adresse dieser
Funktion übergeben.
Wenn jetzt das Modul merkt, daß sich die Daten ändern, ruft es für alle
angemeldeten Nutzer jeweils deren callback-Funktion auf mit den neuen
Daten.
Braucht der Nutzer die Daten nicht mehr, kann er sich meist abmelden
(unsubscribe).
Das hat einige Vorteile:
- saubere Trennung zwischen Nutzer und Modul
- der Nutzer muß nicht dauernd nach neuen Daten fragen (polling)
- recht effizient (Übergabe der Daten über einen Funktionszeiger und
meist Zeiger auf die Daten)
- der Nutzer hat volle Kontrolle, was er mit den Daten anstellt, weil er
die callback-Funktion definiert
Man muß im Kopf behalten, daß die callback-Funktion je nach Umsetzung
des Moduls evtl. in einem anderen Threadkontext ausgeführt wird.
Neben der callback-Funktion kann man sich auch noch andere Arten der
Benachrichtigung bauen, z.B. eine pipe zu jedem Benutzer oder gar ein
TCP-Stream (netzwerkfähig), oder ähnliches.
Beispiel zu der callback-Funktion in Standard-C++ (schnell
zusammengebaut als Demo, ein paar Sachen fehlen wie z.B. Thread zum
Schluß beenden, join etc.) im Anhang.
Da wird in einem Modul die aktuelle Uhrzeit ermittelt (als time_t, also
Sekunden seit 1.1.1970 00:00) und bei jeder Änderung ausgeliefert.
Ein Nutzer im Hautprogramm abonniert erst, wartet ein bißchen und lässt
sich in der Zeit mehrmals benachrichtigen und meldet sich dann wieder
ab.
Im Modul werden alle Nutzer (im Beispiel nur einer) in einem std::set
gehalten und reihum benachrichtigt, indem ihre callback-Funktionen
aufgerufen werden.
Ähnliches lässt sich natürlich auch in C bauen, auch auf kleinen
Controllern.
(Gegebenenfalls muß man sich Gedanken über Synchronisation machen beim
Zugriff auf gemeinsam genutzte Daten machen, Mutex....)
Ruediger A. schrieb:> könntest Du auch so umschreiben:>> const volatile nmea_info_t* roGPS = (const volatile nmea_info_t *)&GPS;
Siehe Screenshot, wenn ich es ändere. Das hat mich schonmal wahnsinnig
gemacht. Das voltile steckt ja schon in den Defintion drin, das wäre ja
doppelt gemoppelt.
Christian J. schrieb:> Ich brauche VOLATILE, sonst
Das glaube ich gerne.
Aber wenn so eine volatile variable auf den restlichen Code losgelassen
wird, zumal ohne volatile, dann ist meist etwas faul, was sich meist in
"manchmal spinnt" zeigt.
Auch kostet es meist nichts, das volatile im Interface zu lassen, da die
Zugriffe von außerhalb meist einzeln sind. Und da wo es mehrere
nacheinander sind, ändert sich das Verhalten grundlegend, wenn der
Compiler Mal neu lädt, Mal nicht (was er darf und tut!)
Üblich ist also eher, dass wenn, ein Element von normal zu const
volatile gecastet wird.
Christian J. schrieb:> Siehe Screenshot, wenn ich es ändere.
Ja, aber das ist das, was ich oben schrieb. Du hast die Warnung nur weg
gecastet, einfach druff gekloppt, bis sie weg war.
Der Grund für die Warnung (dass strcpy mit einem volatile-Argument gar
nicht umgehen kann) hat sich dadurch natürlich in keiner Weise geändert.
Wenn dein Code trotzdem funktioniert, solltest du dich jetzt fragen, ob
es überhaupt sinnvoll ist, die komplette struct als "volatile" zu
qualifizieren, oder ob es nicht nur einzelne Elemente der struct sind,
die das brauchen.
Klaus W. schrieb:> - der Nutzer muß nicht dauernd nach neuen Daten fragen (polling)
Nur ganz kurz: So mache ich das auch. Ein Flag ist blitzschnell
abgefragt. Über einen Struct wird ebenso schnell eine Prüfsumme gelegt,
wenn er neu bespielt wurde. 100 Byte einfach aufaddieren geht fix. Die
ändert sich wenn die Daten sich ändern. Der Caller fragt dann nur noch
das Flag ab ob die Prüfsumme anders ist und fliegt weiter im Code, wenn
nichts ist.
Alle Funktionen, die ich schreibe haben diese Prüfung im Kopf drin.
Display wird nur neu aufgebaut, wenn da ein neues Pixel drin ist oder
eben weg.
Tat sich nix geht es direktz wieder raus. Anders kriegste eine
Statemachine nicht effizient ohne dass immer wieder das Gleiche
berechnet wird, obwohl sich nichts verändert hat. Was geht wird im INT
erledigt, wobei das Grenzen hat, irgendwann blickt man nicht mehr
durch,wer da noch alles wen unterbricht.
In C++ ist das eleganter aber leider nicht meine Welt.
Jörg W. schrieb:> Ja, aber das ist das, was ich oben schrieb. Du hast die Warnung nur weg> gecastet, einfach druff gekloppt, bis sie weg war.
Ja, sehe ich ein. Ist leider wahr. Befasse ich mich gerne heute abend
noch mit.
Muss das sein, wenn alle Inhalte in einer ISR gewonnen werden? Sich zu
jeder zeit ändern können? Da muss ich eh drauf aufpassen, dass nicht ein
Zugriff kommt und im Zugriff was Neues eingeschrieben wird.
static volatile nmea_info_t GPS; /* PRIVAT: Datenregister */
Christian J. schrieb:> Ruediger A. schrieb:>> könntest Du auch so umschreiben:>>>> const volatile nmea_info_t* roGPS = (const volatile nmea_info_t *)&GPS;>> Siehe Screenshot, wenn ich es ändere. Das hat mich schonmal wahnsinnig> gemacht. Das voltile steckt ja schon in den Defintion drin, das wäre ja> doppelt gemoppelt.
Verstehe ich nicht. Hast Du in deiner Strukturdefinition jeden einzelnen
member auch als volatile deklariert, nicht nur die Struktur?
Im Aufruf
strcpy((char *)&DailyData.lat, roGPS->latstr);
würde sich der Compiler ja bei Parameter 2 nur dann beschweren, wenn der
Strukturmember latstr volatile wäre; dass die übergeordnete Struktur
volatile ist, würde ja beim dereferenzieren über Bord gehen.
Wie genau sieht deine Strukturdefinition aus?
Ruediger A. schrieb:> Wie genau sieht deine Strukturdefinition aus?
Ich muss jetzt zur Arbeit. Heute abend werde ich mal alles sichern und
dann alle volatiles entfernen. Und dann sehe ich ja was passiert. Das
Projekt ist zu komplex inzwischen, dass ich mir erlauben kann da alles
wieder ab zu schießen.
Und wenn es nicht mehr läuft nehme ich Stück für Stück die volatiles
wieder rein. Die benutze ich nur, wenn ISR Globale Variablen verändern.
Das ist ja wohl auch richtig so. Nur hat das eben Auswirkungen....
Einige sind sicherlich zuviel, ganz sicher....
Gut erklärt:
https://stackoverflow.com/questions/246127/why-is-volatile-needed-in-c
Wikipedia:
In C und C++ spezifiziert dieser Qualifizierer, dass sich der Wert der
Variable jederzeit ohne expliziten Zugriff im Quelltext ändern kann.
Dies geschieht beispielsweise durch andere Prozesse, Threads oder
externe Hardware.[1] Bei der Generierung des Maschinen-Codes aus einem
in C oder C++ geschriebenen Programm verhindert die Kennzeichnung einer
Variablen als volatile eine in diesem Fall die Funktionalität
beeinträchtigende Optimierung, so dass das Programm immer auf den
tatsächlich in der Hardware vorhandenen Wert zugreift.[2
1
*Deklaration:Typen*/
2
3
typedefstruct
4
{
5
_BoolfGPSDataValid;/* Dateh sind gültig */
6
structtmtime;/* Alle Zeitangaben */
7
intheading;/* Kompassrichtung bzgl. Norden */
8
floatlongtitude;/* Länge */
9
floatlatitude;/* Breite */
10
charlongstr[12];/* Länge als String */
11
charlatstr[12];/* Breite als String */
12
doublespeedkmh;/* Geschwindigkeit in kmh */
13
charNmeaStr[100];/* NMEA String Kopie */
14
}nmea_info_t;
15
16
/* --- Interface Variablen -------------- */
17
18
/* Read Only Feld auf die Elemente des Structs */
19
externconstnmea_info_t*roGPS;
20
21
externvolatilenmea_info_tGPSnapshot;/* Schnappschuss (Kopie des nmea_info Struct) */
22
externvolatile_BoolfGPSValidFix;/* 1= GPS Daten sind gültig */
So, wieder online.
Mit dem volatile stehe ich auf Kriegsfuss, daher wird es bisher nach
Giesskanne bei Globals verwendet.
Ein globale Var MyVar kriegt im INT einen Wert aus der Hardware
zugewiesen.
Das Hardwareregister ist wie alle volatile.
MyVar = USART->SR;
Irgendwo im Main steht
while (!MyVar) {......}
Kommt vor. DFer Compiler kann den Ausdruck aber weg optimieren, da in
der While Schleife nichts steht, was mit MyVar in Verbindung steht. Also
weg damit. Dass MyVar in einem anderen Modul bespielt wird sieht er ja
nicht, die ist extern definiert.
Und das gilt für alle Flags, die daher alle volatile sind. Die
Datenstruktur selbst müsste es vermutlich nicht sein. Alles was drin
steht wird nur ausgelesen, nirgendwo Schleifen die auf was warten.
Also erstmal heute abend alle volatiles weg und alle Casts auch. Wird ne
Heidenarbeit werden. Und dann durch debuggen auf -Og mit Testdaten, was
dann passiert, ob vielleicht Ausdrücke weg sind usw.
Christian J. schrieb:> Und das gilt für alle Flags, die daher alle volatile sind.
Für die ist das ja sinnvoll, aber für deine Strings sehr wahrscheinlich
nicht.
Christian J. schrieb:> Mit dem volatile stehe ich auf Kriegsfuss, daher wird es bisher nach> Giesskanne bei Globals verwendet.
Ich vermute, Dein eigentliches Probleme ist eine
"Inter-Task-Synchronisation". Also wie Daten zwischen Tasks ausgetauscht
werden. Deine Daten sind "asynchron" und vermutlich gegelentlich
"inkonsistent".
Du hast zwar keine Tasks bzw. Multitasking, kein RTOS. Du nutzt
stattdessen die Interrupts als Tasks.
Das ist ein eigenes Thema für sich, von daher liegt das momentan
vielleicht nicht an, aber Du solltest Dir angewöhnen, die Interrupts
klein zu halten und die eigentliche Verarbeitung der Information in die
SPS-Loop zu legen.
Das Stichwort: Die Daten müssen konsistent sein und snychron. Und das
sind sie immer dann, wenn sie in einer SPS-Loop verarbeitet werden. Das
ist der Grund, warum die SPS-Loop noch immer State of the Art ist.
Selbst wenn für GUI oder Kommunikation separate Tasks im RTOS bereit
stehen.
In der Regel machen wenige erprobte Elemente die Synchronisation (Egal
ob Interrupt oder Tasks):
- Flags/Events (gesetzt in einem, gelöscht in anderem)
- Nachrichtenpuffer (Messages)
- Ringpuffer (z.B. von und zum Uart-Interrupt)
Die sind dann entsprechen "Thread-Safe" oder hier "interruptfest". Und
nur für ganz wenige andere Dinge nutzt man dann Disable/Enable-Interrupt
(bzw. im RTOS: -Dispatcher)
A. S. schrieb:> Ich vermute, Dein eigentliches Probleme ist eine> "Inter-Task-Synchronisation". Also wie Daten zwischen Tasks ausgetauscht> werden.
Genau das bringt es auf den Punkt !!! Es ist nur ein Hobbyprojekt aber
mir macht das halt Spass.
Die Datenquellen sind alle asynchron, sowohl das GPS als auch der Gyro.
Ich werde daher auch einen Teil des UART INT auslagern in die Mail Loop.
DFie Ints sollen nur die Vars voll schaufeln und mehr nicht.
Natürlich kann es passieren, dass gerade wenn der String zerlegt wird
ein neuer reinkommt, da nur ein Buffer da ist.Die Auswertung kann zu
jeder Zeit unterbrochen werden. Daher arbeite ich ja mit einem Shadow
Buffer, der erst bespielbar wird, wenn Main es so sagt. Solange fallen
die Daten alle ins Datengrab, was aber egal ist. Es kommen ja laufend
neue.
Ja, das sind genau die Sachen, die mir noch fehlen, wo der Austauch halt
nicht da ist.
Ich nutze 3 Timer.... eigentlich Quark. Der Systick würde ausreichen
daraus 3 weitere Software Timer zu erzeugen....
Christian J. schrieb:> Daher arbeite ich ja mit einem Shadow Buffer, der erst bespielbar wird,> wenn Main es so sagt.
Du kannst für sowas auch zwischen zwei Puffern umschalten. Einen, der
aktuell beschrieben wird und einen, der nach dem Setzen des "Es sind
Daten da"-Flags ausgelesen und geparst wird. Solange die Ausleseroutine
schneller ist als die nächsten Daten herein tröpfeln, geht dir dann
nichts verloren.
Jörg W. schrieb:> Du kannst für sowas auch zwischen zwei Puffern umschalten. Einen, der> aktuell beschrieben wird und einen, der nach dem Setzen des "Es sind> Daten da"-Flags ausgelesen und geparst wird. Solange die Ausleseroutine> schneller ist als die nächsten Daten herein tröpfeln, geht dir dann> nichts verloren.
Würde es mir ersparen den Buffer zu kopieren. Einfach einen Zeiger auf
den Struct ständig umschalten.... Ich muss das hier alles erstmal
ausdrucken.... da sind viele gute Ideen dabei.
Sollte ja eigentlich arbeiten aber notepad++´ist ja installiert :-)
Christian J. schrieb:> DFie Ints sollen nur die Vars voll schaufeln und mehr nicht.
Oder stattdessen ein Ringpuffer.
Oder ein Fifo für Messages. Messages sind dann ganze Pakete, wenn die
Loop langsam und die Start-Ende-Erkennung einfach ist.
Christian J. schrieb:> Einfach einen Zeiger auf den Struct ständig umschalten.
Da du in der struct auch die Flags hast (soweit ich verstehe), schaltest
du keinen Zeiger auf diese um. Stattdessen sowas:
1
#define RX_BUFSIZE 100
2
structgpsdata
3
{
4
volatileboolrx_complete;
5
charbuff[2][RX_BUFSIZE];
6
intbuf_idx;// 0 oder 1
7
intwrite_idx;// nur in ISR benutzt, nicht volatile
8
};
9
10
structgpsdatagpsdata;
11
12
// ...
13
voidUART_Handler(void)
14
{
15
switch(event)
16
{
17
caseRX_DATA:
18
{
19
charc=(char)UART->RXD;// hier Typecast: Übergang von Hardware-
A. S. schrieb:> Oder stattdessen ein Ringpuffer.
Lohnt bei GPS Daten nicht. Da ist es wumpe ob mal eine fehlt.
Jörg W. schrieb:> gpsdata.buf_idx = ~gpsdata.buf_idx; // Puffer umschalten
Das klappt?
gpsdata.buf_idx = !gpsdata.buf_idx;
Macht aus 0 eine 1 und daraus eine 0.
Bin ein Fan von sowas hier
idx = (idx + 1) & BUFSIZE mit BUFSIZE 4,8,16,32....
damit wird eine if then else eingespart, das Ding rotiert nur, egal
welchen Müll es reinschreibt.
if c=='$' setzt idx = 0 da es nur ein einziges $ in NMea String gibt und
genau dafür gemacht wurde.
if '\n' ersetze ich durch die Fähigkeit der Uart Lücken zu erkennen.
Kommt kein Zeichen mehr für t=8 Bit Zeit wird ein INT ausgelöst, da
beginne ich die Zerlegung drin. Selbst lange Sequenzen mit GNTXT werden
pausenlos ausgegeben.
Entfällt aber hier jetzt auch, da das ein eigener INT ist... dann müsste
ich wieder "Daten trennen".
Christian J. schrieb:> Bin ein Fan von sowas hier> idx = (idx + 1) & BUFSIZE mit BUFSIZE 4,8,16,32....
Du meinst aber sicher "(BUFSIZE - 1)".
Dann solltest du aber unbedingt noch einen Test darauf einfügen, dass
BUFSIZE auch wirklich 2^N ist.
Jörg W. schrieb:> gpsdata.rx_complete = false; // "wir arbeiten dran™"> int read_idx = ~gpsdata.buf_idx; // Lesepuffer entgegengesetzt zu> // Schreibpuffer> char *mybuf = &gpsdata.buf[read_idx]
Tut es das wirklich ??? Der INT schaltet den Buf um, ständig. Und was
ist wenn er das tut, während Main grad seinen Buf abarbeitet und der ihm
den unten denm Füssen wegzieht?
Ich meinte
idx = (idx + 1) % BUFSIZE // Modulo
Benutze ich seit jeher für alle Arten von rollierenden Pointern.
Idealerweise glatte Zahlen, damit aus dem Dividieren ein Schieben wird.
Also 2^N Vielfache. Der GCC ist clever genug /4 als N >> 2 umzuwandeln.
Christian J. schrieb:> Und was ist wenn er das tut, während Main grad seinen Buf abarbeitet und> der ihm den unten denm Füssen wegzieht?
Wenn du nicht sicherstellen kannst, dass main schnell genug ist, musst
du das verriegeln. Aber bei der dünnen Rate, mit der NMEA-Records
reintröpfeln, denke ich nicht, dass man das hier wirklich machen muss.
> idx = (idx + 1) % BUFSIZE
OK, das funktioniert zumindest auch für nicht 2^N Größen, braucht dann
halt nur wirklich 'ne Division.
Christian J. schrieb:> A. S. schrieb:>>> Oder stattdessen ein Ringpuffer.>> Lohnt bei GPS Daten nicht. Da ist es wumpe ob mal eine fehlt.
Es geht um den uart-interrupt: statt der Dekodierung im Interrupt steht
da sowas wie RBuffAdd(rbUart1, c);
Und in der Mainloop sowas wie while(RBuffGet(rbUart1, &c)){...}
Der Vorteil: die Funktionen für add und get sind minimal und rasend
schnell, die Auswertung dann synchron zur Loop. Keine Flags, kein
umschalten, kein volatile.
Und jeder der embedded macht, versteht den Mechanismus beim Überfliegen
(weil er Standard ist).
Ganz lesenswert für Leute wie mich:
Interrupt Techniken:
https://homepages.thm.de/~hg6458/semsts/Interrupt-Techniken_Schoendube.pdf
Hinten auch das Thema Daten-Pufferung und Konsistenz von asynchronen
Tasks.
".....Als eine solche Standardisierung sei hier die Cortex
Mikrocontroller Familie und der mit ihr verbundene CMSIS genannt. Bei
diesem Framework ist es fur den Programmierer in der Regel nicht mehr
notwendig, spezielle Kenntnisse uber die Hardware zu besitzen. Es werden
ediglich Libary Funktionen aufgerufen, die die Hardware korrekt
konfigurieren und ansteuern."
A. S. schrieb:> Und in der Mainloop sowas wie while(RBuffGet(rbUart1, &c)){...}
Sowas vermeide ich, da ich nach dem Round-Robin Prinzip verfahre.
Nirgendwo darf etwas warten oder stehenbleiben. Die Main fetzt immer mit
Vollgas durch und reagiert nur auf Flags, verriegelt oder entriegelt
was. Kein einziger Delay ist drin (außer Einschaltwartemomente) Wenn
etwas nicht da ist wird es ignoriert, ist es da schaltet eine Weiche um
bis es fertig ist und dann zurück.
Christian J. schrieb:> A. S. schrieb:>> Und in der Mainloop sowas wie while(RBuffGet(rbUart1, &c)){...}>> Sowas vermeide ich, da ich nach dem Round-Robin Prinzip verfahre.> Nirgendwo darf etwas warten oder stehenbleiben. Die Main fetzt immer mit> Vollgas durch und reagiert nur auf Flags, verriegelt oder entriegelt> was. Kein einziger Delay ist drin (außer Einschaltwartemomente)
Dann ist das ein Missverständnis (A oder/und B).
Die SPS rauscht durch und darf nicht warten. Das ist richtig. Und ja,
"SPS-Loop" ist Round-Robin.
Falsch ist jedoch Deine Annahme, dass die while-Schleife die Main-Loop
langsamer macht. Das Gegenteil ist der Fall. Zudem ist ein Takt im
Interrupt teurer als in der Main-Loop (und dort teurer als in der GUI
oder Background-Task)
A) das while wartet nicht sondern wertet die Zeichen aus, die in dieser
Mainloop empfangen wurden.
B) Du verarbeitest im Interrupt, verlangsamst also die Main-Loop
genauso. Die komplexeren Interrupts erfordern aber mehr
Kontextsicherung. Zudem musst Du synchronisieren. Somit verlangsamst Du
die Main-Loop mehr als mit dem while.
Zwischenbericht:
Alle volatile entfernt bis auf die Bool Flags, die in den Ints deren
Status mitteilen. Alle Casts entfernt... und die Anwendung läuft noch
:-) Code sieht aber lesbarer aus, wenn das *)& Zeugs weg ist.
Jetzt kommt die Auslagerung der Ints Lasten dran.
Schon schlanker...
N'Abend,
ich habe zahlreiche der Vorschläge umgesetzt aus den obigen Kommentaren
und die Geschichte läuft noch immer. Auch einen Umschaltpuffer für die
Datensätze wird gerade realisiert und die INTs alle entschlackt.
Daher schließe ich das hier mal ab und bedanke mich ganz herzlich für
die guten Tips mein Programm besser zu machen. Sind sogar einige
Kilobyte bei weniger geworden.
Herzlichen Dank !!!
Christian J. schrieb:> Sind sogar einige Kilobyte bei weniger geworden.
thumbs up!
Ich vermute, dass allein der Verzicht auf die zwangsweisen
"Pessimierungen" durch zu viele "breit gestreute" volatile da gut helfen
kann.
Ok,
ich habe deine Lösung noch etwas schlanker gemacht ... es wird nur ein
Zeiger nach außen geliefert auf den Buffer, der grad gefüllt wurde.
Sonst müsste ich Variablen rausziehen aus dem Modul nach außen, obwohl
die gar nicht raus müssen. zb der Buffer kann static bleiben.
Ist übrigens ! richtig, nicht ~. Letztes gibt negative Werte, aus 0 wird
-1, was ja auch richtig ist. ~
volatile Flags haben keine negativen Auswirkungen, daher sind die alle
volatile. Sonst müsste ich wieder aufpassen, wenn ich statt if ein while
verwende.
Christian J. schrieb:> Ist übrigens ! richtig, nicht ~.
OK, überredet. :-)
War einfach "trocken" hingeschrieben, ohne irgendwelche Tests oder
Verifizierung. Aber du hast die Idee ja gut aufgreifen können.
schreiben.
Nur steht auf der SD Karte bei Letzerem eine riesengroße Zahl in dem
File, obwohl der Double Wert richtig war.
Und im Fall 1 steht der Richtige drin. Nicht so einfach zu verstehen.
Bleibt noch die Möglichkeit, dass das nur mit %uL klappt.... aber da es
jetzt stimmt mit dem Zwischenwert lasse ich es auch so.
Jörg W. schrieb:
Ok, unsigned int oder UINT tun es besser.
> Hast du denn kein Gleitkomma-printf? Ist ja auf'm ARM eigentlich nicht> unüblich.
Das ist die Funktion der Chan Fat, die hat sowas nicht :-( Daher rühre
ich auch nicht mehr drin rum. Im Debugger stimmt das alles. Er hat erst
dieses Jahr sein schlankes xprintf auf float erweitert, also brandneu.
Umgehung wäre mit xsprintf.... wenn es denn sein müsste. Aber wen
interessieren schon die hundert Meter Stellen auf dem Tacho, die sind eh
ungenau.
fprintf mit float kannste extra reinholen über die newlib nano branch.
Knallt dir gleich 4-5 Kilo mehr Code rein.
FreeRTOS würde dein Projekt deutlich erleichtern.
Hast du dir das schon mal angeschaut?
Das wird sogar von CubeMX unterstützt, und von ST gibt es einen tollen
Online-Kurs dazu:
https://www.youtube.com/playlist?list=PLnMKNibPkDnFeFV4eBfDQ9e5IrGL_dx1Q
Ok, der Typ spricht ein furchtbares Englisch, und der Ton ist zeitweise
unterirdisch schlecht, aber ich hab nach dieser Video-Reihe meine ersten
Programme mit FreeRTOS geschrieben, und wünschte mir, den Schritt
bereits früher getan zu haben.
Nach den Videos zum Einstieg gehts dann hier hier ins Eingemachte:
https://freertos.org/Documentation/RTOS_book.html
Viel Erfolg!
Christian J. schrieb:> Das ist auch angedacht, obwohl bare-metal auch Spass macht.
Ja, das gibts 2 Ansichten:
Spass am Programmieren an sich -> Bare metal (wird leider meist nie
fertig)
Ergebnis-orientiert -> Cube & Consorten...
Ich bin eher Cube-Fan.
Christian J. schrieb:> Harry L. schrieb:>>> FreeRTOS würde dein Projekt deutlich erleichtern.>> Das ist auch angedacht, obwohl bare-metal auch Spass macht
Ist freeRTOS nicht auch bare-metal?
A. S. schrieb:> Christian J. schrieb:>> Harry L. schrieb:>>>>> FreeRTOS würde dein Projekt deutlich erleichtern.>>>> Das ist auch angedacht, obwohl bare-metal auch Spass macht>> Ist freeRTOS nicht auch bare-metal?
Edelstahl! ;-)
Christian J. schrieb:> Harry L. schrieb:>> FreeRTOS würde dein Projekt deutlich erleichtern.>> Das ist auch angedacht, obwohl bare-metal auch Spass macht.
Oder vielleicht embOS? ;-)
https://www.segger.com/products/rtos/embos/Ruediger A. schrieb:> So ziemlich alle Strukturnamen in Segger Middleware sind all capitals, ...> Wo findet sich diese Konvention?
Bei uns im DevelopersHandbook.pdf ;-).
Hier mal ein Auszug:
"Structures and unions
Structures and unions should be typed as shown below. Also, the data
type MUST be written using ALL upper case characters. Context will make
it obvious that all upper case characters in front of a variable or
function must mean that it’s a data type as opposed to a constant or
macro.
typedef struct {
int a;
...
} GUI_FONT;
"
Was ich damit sagen möchte, ich denke es nicht so relevant, ob die
Macros jetzt groß oder klein geschrieben sind sondern das man sich auf
eine Konvention einigt, diese durchgehend befolgt und auch dokumentiert.
Harry L. schrieb:> Spass am Programmieren an sich -> Bare metal (wird leider meist nie> fertig)
Traurig wenn du Projekte ohne FreeRTOS und Cube niemals fertig bekommst.
Aber zur allgemeinen Regel würde ich ein solches Totalversagen jetzt
nicht erklären.
Cyblord -. schrieb:> Traurig wenn du Projekte ohne FreeRTOS und Cube niemals fertig bekommst.> Aber zur allgemeinen Regel würde ich ein solches Totalversagen jetzt> nicht erklären.
Klar, dass mit dem Auftauchen des "Cyblord" Beleidigungen, Abwertungen
und Pöbeleien wieder Einzug in einen Thread halten. Hat bisher absolut
nichts Sinnvolles hier beigetragen aber "Hoppla... hier bin ich!".
Vielleicht mal an der Sozialkompetenz arbeiten? Oder den privaten Frust
nicht im Netz auslassen?
Was hier fehlt ist ein Blockierfilter, wie ihn alle sozialen Netze
inzwischen haben und die Anmeldepflicht unter einem einzigen Account.