Forum: Mikrocontroller und Digitale Elektronik uC Programmiertechniken - Gibt es da Bücher?


von Christian J. (Gast)


Lesenswert?

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
void EXTI3_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
void TIM3_IRQHandler()
19
{
20
#define RELOAD_1s         10
21
    static uint8_t Divider1s = RELOAD_1s;
22
    static uint8_t taste_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 */
34
            if (fGPSValidFix && (GPS.speedkmh > 5.0))
35
            {
36
                /* Teilstrecken aufsummieren */
37
                Tageskilometer += (GPS.speedkmh / 3600);
38
            }
39
40
           /* Rote LED zeigt Tastendruck an */
41
            if (!fTasteEnable) {
42
                /* Nach 2s Taste wieder freischalten */
43
                if (++taste_blocker>2) {
44
                    SetLED(ROT,DISABLE);
45
                    taste_blocker = 0;
46
                    fTasteEnable = true;
47
                    fLeftKey = true;        /* Globales Flag setzen */
48
                }
49
            }
50
        }
51
    }
52
}

von M3 ohne FPU (Gast)


Lesenswert?

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?

von Christian J. (Gast)


Lesenswert?

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:
1
/*
2
Berechnung des Abstandes zweier Koordinatenpunkte
3
dist = 6378.388 * acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1))
4
*/
5
6
#define GENAUE_BERECHNUNG
7
double CalcDistance(double lat1, double lon1, double lat2, double lon2)
8
{
9
#define PI 3.141592654
10
#ifdef GENAUE_BERECHNUNG
11
    /* Umrechnug in rad */
12
    lon1 = (lon1 * PI) / 180;
13
    lat1 = (lat1 * PI) / 180;
14
    lon2 = (lon2 * PI) / 180;
15
    lat2 = (lat2 * PI) / 180;
16
17
    double distance = 6378.388 * (acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1)));
18
    return (distance);
19
#else
20
    /* Rad Umrechnung ist hier schon mit drin */
21
    double lat = (lat1 + lat2) / 2 * 0.01745;
22
    double dx = 111.3 * cos(lat) * (lon1 - lon2);
23
    double dy = 111.3 * (lat1 - lat2);
24
    double distance = sqrt(dx*dx + dy*dy);
25
    return distance;
26
#endif
27
}

von A. S. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Jasson (Gast)


Lesenswert?

>Gibt ja tausende Programmierer, die immer >wieder die gleichen Probleme lösen

Wenn es dabei um Architektur geht, sind "Design Pattern" das passende 
Suchwort.

von Wolfgang (Gast)


Lesenswert?

Christian J. schrieb:
> 39200 Zyklen:
> ...

Die Rechnung in der Form brauchst du hoffentlich nicht für Berechnung 
von GPS Teilstrecken.

von Christian J. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von M3 ohne FPU (Gast)


Lesenswert?

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.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

Christian J. schrieb:
> Und A.S. ist ja ein Gott der Z80er und anderer
> Maschinchen :-)

?? Wenn dann ein anderer A.S.

von Christian J. (Gast)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Keiner N. (nichtgast)


Lesenswert?

"Weniger Schlecht Programmieren" ist auch eine tolle Lektüre.

von Stefan F. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Christian J. (Gast)


Lesenswert?

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 */
2
            if (fGPSValidFix && (*GPS_GetSpeed() > 5.0))
3
            {
4
                /* Teilstrecken aufsummieren */
5
                Tageskilometer += (GPS.speedkmh / 3600);
6
            }

Die ist definiert als
1
/* Liefert die aktuelle Geschwindigkeit zurück */
2
double* GPS_GetSpeed() {
3
    return ((double*)&GPS.speedkmh);
4
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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 …

von Christian J. (Gast)


Lesenswert?

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.
1
    ssd1306_SetCursor(0,26);
2
    xsprintf(oledbuf,"G %02u:%02u:%02u", GPS_GetStamp()->tm_hour,
3
                                         GPS_GetStamp()->tm_min,
4
                                         GPS_GetStamp()->tm_sec);
5
    ssd1306_WriteString(oledbuf,Font_11x18,White);

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von jemand (Gast)


Lesenswert?

constexpr in C++, oder?
const bedeutet doch erstmal, dass eine Methode das Objekt nicht 
verändert

von Johannes S. (Gast)


Lesenswert?

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
EventQueue queue;        // creates a queue with the default size
2
InterruptIn btnSave(PB_12, PullUp); // save button
3
DigitalOut  ledSave(PC_13);
4
5
void writeFile() 
6
{
7
    // write to SD
8
    ledSave = 0;
9
}
10
11
void btnSaveFn()
12
{
13
    ledSave = 1;
14
    queue.call(writeFile);
15
}
16
17
int main()
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.

von Christian J. (Gast)


Lesenswert?

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.
1
     xsprintf(oledbuf,"N %s",GPS_GetLatStr());  //(char*)&GPS.latstr);
2
        ssd1306_WriteString(oledbuf,Font_11x18,White);
3
        xsprintf(oledbuf,"E %s",GPS_GetLongStr()); //(char*)&GPS.longstr);
4
        ssd1306_SetCursor(0,45);
5
        ssd1306_WriteString(oledbuf,Font_11x18,White);

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);
6
                    int nr = f_printf(&Fil,"%s",GPS_GetNMEAStr());
7
                    if (nr < 0) print_sd_error(res);

von Johannes S. (Gast)


Lesenswert?

Fahr vorsichtig und Finger weg vom Handy :)

von Christian J. (Gast)


Lesenswert?

Johannes S. schrieb:
> Fahr vorsichtig und Finger weg vom Handy :)

Ich habe CB Funk an Bord, das darf man noch und die LKW fahrer sind 
nette Leute :-)

von A. S. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

A. S. schrieb:
> Wieso casts? Und wieso ptr? Das sieht so wenig sinnvoll aus. Und besser
> als .c anhängen.

So, bin weg..... alles später....

von Christian J. (Gast)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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/

von Christian J. (Gast)


Lesenswert?

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?

von Walter T. (nicolas)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Christian J. (Gast)


Lesenswert?

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.

von (Gast)


Lesenswert?

Christian J. schrieb:
> 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 */
2
             if (fGPSValidFix && (*GPS_GetSpeed() > 5.0))
3
             {
4
                 /* Teilstrecken aufsummieren */
5
                 Tageskilometer += (GPS.speedkmh / 3600);

Hier würde GPS_GetSpeed() anstatt GPS.speedkmh ohne * sicher zu einem 
anderen Ergebnis führen als mit.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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 :-(

von Peter D. (peda)


Lesenswert?

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.

von Klaus W. (mfgkw)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

> 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
double wert = *zeiger;             // Wert von GPS.speedkmh 
5
double wert = *GPS_GetSpeed();     // Wert von GPS.speedkmh

von Klaus W. (mfgkw)


Lesenswert?

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
  static double d = 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
13
  // ihr Rückgabewert zeigt:
14
  double geschwindigkeit = *fGPS_Speed();

von Stefan F. (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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

von Christopher J. (christopher_j23)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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.

von Hans-Georg L. (h-g-l)


Lesenswert?

Schau mal auf der Seite von Jack Ganssle, der hat auch einige Bücher zu 
dem Thema geschrieben.

http://www.ganssle.com/articles.htm

oder bei Bruce Powel Douglass

https://www.bruce-douglass.com/books

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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.

von Christopher J. (christopher_j23)


Lesenswert?

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.

von Gerhard O. (gerhard_)


Lesenswert?

"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

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

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.

von Hans-Georg L. (h-g-l)


Lesenswert?

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.

von Stefan F. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christian J. schrieb:
> bis 29mA wandert er hoch

Das klingt danach, als würdest du Strom über IO-Ports ziehen oder 
speisen.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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.

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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:
1
#define SYSTICK_ISR_OFF     SysTick->CTRL  &= ~SysTick_CTRL_TICKINT_Msk
2
#define SYSTICK_ISR_ON      SysTick->CTRL  |= SysTick_CTRL_TICKINT_Msk
3
void CPU_STOP() {
4
5
    SYSTICK_ISR_OFF;                                    /* Systick aus */
6
    PWR_ClearFlag(PWR_FLAG_WU);
7
    PWR_EnterSTOPMode(PWR_Regulator_ON,PWR_STOPEntry_WFI);
8
    /* Chrrrr.... */
9
10
    SystemInit();
11
    SYSTICK_ISR_ON;                                      // Systick ein
12
    RCC_GetClocksFreq(&RCC_Clocks);                      // Clocks holen
13
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);     // Quartz als Clock Basis
14
    SysTick_Config(RCC_Clocks.SYSCLK_Frequency/1000);
15
}

mit
1
   ........
2
   /* EXTI Line an Taste Pin 10 anbinden */
3
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource10);
4
5
    /* EXTI Line einstellen */
6
    EXTI_InitStructure.EXTI_Line    = EXTI_Line10;
7
    EXTI_InitStructure.EXTI_Mode    = EXTI_Mode_Interrupt;
8
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
9
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
10
    EXTI_Init(&EXTI_InitStructure);
11
12
    //configure NVIC
13
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
14
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 15;
15
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x15;
16
    NVIC_Init(&NVIC_InitStructure);
17
18
    /* Freischalten */
19
    EXTI_ClearITPendingBit(EXTI_Line10);
20
    NVIC_EnableIRQ(EXTI15_10_IRQn);
21
22
    initialized = true;
23
24
/* Handler für Pins 10 - 15 */
25
void EXTI15_10_IRQHandler() {
26
27
    /* Wir brauchen nur Pin 10 (Bewegung erkannt INT2 ADXL345 */
28
    if(EXTI_GetITStatus(EXTI_Line10) != RESET) {
29
30
        EXTI_ClearITPendingBit(EXTI_Line10);
31
    }
32
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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?

: Bearbeitet durch Moderator
von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dann verhindert der Debugger schätzungsweise, dass der Prozessor sich 
überhaupt schlafen legt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christian J. schrieb:
> Ist das hier überhaupt richtig?

Da bin ich überfragt, so firm bin ich bei STM32 nicht.

von Christian J. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

Erkenntnis 2: Der Debugger verhindert den Stop nicht, das Multimeter 
geht auf 1,5mA die der Rest braucht, die CPU ist quasi komatös.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Hans-Georg L. (h-g-l)


Lesenswert?

Ruediger A. schrieb:
...

> 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.
Hab mir das mal angesehen und das Inhaltsverzeichnis sieht gut aus.
Leider ist, wie so oft kein Probekapitel dabei. Das Kapitel z.B. über 
Faults und ihre (Laufzeit ?) Behandlung hätte mich, wegen dem langen 
Vorwort dazu, schon interessiert ;-) Kap 10.2 -> Stackanalyse

von Hans-Georg L. (h-g-l)


Lesenswert?

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();

von Alexander S. (alesi)


Lesenswert?

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

von Hans-Georg L. (h-g-l)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.
1
        Disable_Timer2_ISR();
2
        Disable_Timer3_ISR();
3
        RCC_DeInit();
4
5
        /* Warte auf INT2 Interrupt ... */
6
        fShakeflag = false;
7
        while (!fShakeflag) {
8
            CPU_Sleep();
9
        }
10
11
        /* INT2: Bewegung erkannt */
12
        SystemInit();
13
        Enable_Timer2_ISR();
14
        Enable_Timer3_ISR();

von Christian J. (Gast)


Lesenswert?

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
const double* GPS_Speed = (double*)&GPS.speedkmh;             /* Geschwindigkeit in kmh */
2
const float*  GPS_Latitude = (float*)&GPS.latitude;          /* Breiten in Dezimalgrad */
3
const char*   GPS_LatStr = (char*)&GPS.latstr;            /* Breite als float String */
4
const float*  GPS_Longtitude = (float*)&GPS.longtitude;        /* Länge in Dezimalmgrad als float */
5
const char*   GPS_LongStr = (char*)&GPS.longstr;           /* Länge als String */
6
const char*   GPS_NmeaString = (char*)&GPS.nmeastring;        /* Aktueller NMEA String */
7
const struct tm*  GPS_Time = (struct tm*)&GPS.time;          /* Aktuelle GPS Zeit */

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Christian J. schrieb:
>
> 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 */
>
>
>
1
> const double* GPS_Speed = (double*)&GPS.speedkmh;             /* 
2
> Geschwindigkeit in kmh */
3
> const float*  GPS_Latitude = (float*)&GPS.latitude;          /* Breiten 
4
> in Dezimalgrad */
5
> const char*   GPS_LatStr = (char*)&GPS.latstr;            /* Breite als 
6
> float String */
7
> const float*  GPS_Longtitude = (float*)&GPS.longtitude;        /* Länge 
8
> in Dezimalmgrad als float */
9
> const char*   GPS_LongStr = (char*)&GPS.longstr;           /* Länge als 
10
> String */
11
> const char*   GPS_NmeaString = (char*)&GPS.nmeastring;        /* 
12
> Aktueller NMEA String */
13
> const struct tm*  GPS_Time = (struct tm*)&GPS.time;          /* Aktuelle 
14
> GPS Zeit */
15
>

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:
1
const GPSDATASTRUCT * glbROGPSStructCopy = (const GPSDATASTRUCT *)&glbRWGPSStruct;

von Christian J. (Gast)


Lesenswert?

Ruediger A. schrieb:
> const GPSDATASTRUCT * glbROGPSStructCopy = (const GPSDATASTRUCT
> *)&glbRWGPSStruct;

Danke! Hätte ich auch drauf kommen können :-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

Gott, das sieht ja gleich viel unleserlicher aus :-) Gut, dass man mit 
Search & Replace das alles in einem Rutsch machen kann.
1
if (fGPSDataValid) {
2
                    if ((roGPS->speedkmh < 6.0)
3
                     /* Autobahn Richtgeschwindigkeit */
4
                     || ((roGPS->speedkmh >= 130.0) && (roGPS->speedkmh <= 145.0))
5
                     /* Stadtverkehr Tempolimit */
6
                     || ((roGPS->speedkmh >= 50.0) && (roGPS->speedkmh <= 60.0)))
7
                        LEDStatus[ROT] = ON;
8
                    else
9
                        LEDStatus[ROT] = OFF;
10
                }

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?

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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?

von A. S. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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!

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

Ohne Cast :-(

Yupp, so geht es prima!
1
/* -------------------- Interface Variablen  --------------------- */
2
3
static volatile nmea_info_t GPS;        /* PRIVAT: Zentrales Datenregister */
4
5
/* Interface Read Only auf die Elemente des Basis Structs */
6
const nmea_info_t* roGPS = (const nmea_info_t *)&GPS;
7
8
volatile nmea_info_t GPSnapshot;        /* Inhalt NMEA bei Tastendruck */
9
volatile _Bool fGPSValidFix  = false;   /* 1: Gültiger Fix vorhanden */
10
volatile _Bool fGPSDataValid = false;   /* 1: Gültiger Datensatz */
11
12
/* -------------------- Private Variablen  --------------------- */

von Christian J. (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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)

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von wendelsberg (Gast)


Lesenswert?

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

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

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:
1
typedef struct
2
{
3
    _Bool   fGPSDataValid;   /* Dateh sind gültig */
4
    struct  tm time;         /* Alle Zeitangaben */
5
    int     heading;         /* Kompassrichtung bzgl. Norden */
6
    float   longtitude;      /* Länge */
7
    float   latitude;        /* Breite */
8
    char    longstr[12];     /* Länge als String */
9
    char    latstr[12];      /* Breite als String */
10
    double  speedkmh;        /* Geschwindigkeit in kmh */
11
    char    NmeaStr[100];    /* NMEA String Kopie */
12
} nmea_info_t;
13
14
/* --- Interface Variablen -------------- */
15
16
/* Read Only Feld auf die Elemente des Structs */
17
extern const nmea_info_t* roGPS;

Defintion
1
/* -------------------- Interface Variablen  --------------------- */
2
3
static volatile nmea_info_t GPS;        /* PRIVAT:  Datenregister */
4
5
/* Interface Read Only auf die Elemente des Basis Structs */
6
const nmea_info_t* roGPS = (const nmea_info_t *)&GPS;

Caller ohne Casting:
1
     /* Keine Auswertung des ADXL Sensors bei
2
                   höherer Geschwindigkeit*/
3
                if (roGPS->fGPSDataValid && (roGPS->speedkmh>20))
4
                {
5
                    adxl_ClearInterrupts();
6
                }  /* ADXL Sensor auswerten */

von Klaus W. (mfgkw)


Angehängte Dateien:

Lesenswert?

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

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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 */

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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?

von Christian J. (Gast)


Lesenswert?

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
typedef struct
4
{
5
    _Bool   fGPSDataValid;   /* Dateh sind gültig */
6
    struct  tm time;         /* Alle Zeitangaben */
7
    int     heading;         /* Kompassrichtung bzgl. Norden */
8
    float   longtitude;      /* Länge */
9
    float   latitude;        /* Breite */
10
    char    longstr[12];     /* Länge als String */
11
    char    latstr[12];      /* Breite als String */
12
    double  speedkmh;        /* Geschwindigkeit in kmh */
13
    char    NmeaStr[100];    /* NMEA String Kopie */
14
} nmea_info_t;
15
16
/* --- Interface Variablen -------------- */
17
18
/* Read Only Feld auf die Elemente des Structs */
19
extern const nmea_info_t* roGPS;
20
21
extern volatile nmea_info_t GPSnapshot;     /* Schnappschuss (Kopie des nmea_info Struct) */
22
extern volatile _Bool fGPSValidFix;         /* 1= GPS Daten sind gültig */
23
extern volatile _Bool fNewDataAvailable;

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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)

von Christian J. (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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 :-)
1
nmea_info_t buffer[2];
2
nmea_info_t* AktBufPtr = buffer[0];
3
4
void BufSwitch()
5
{
6
   static byte switch = 0;
7
   BufPtr == buffer[switch];
8
   switch = !switch ? 1:0;
9
}

von A. S. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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
struct gpsdata
3
{
4
   volatile bool rx_complete;
5
   char buff[2][RX_BUFSIZE];
6
   int buf_idx; // 0 oder 1
7
   int write_idx; // nur in ISR benutzt, nicht volatile
8
};
9
10
struct gpsdata gpsdata;
11
12
// ...
13
void UART_Handler(void)
14
{
15
   switch (event)
16
   {
17
      case RX_DATA:
18
      {
19
         char c = (char)UART->RXD; // hier Typecast: Übergang von Hardware-
20
                                   // Domain (uint8_t) auf Zeichen (char)
21
         if (gpsdata.write_idx < RX_BUFSIZE)
22
         {
23
            gpsdata.buf[gpsdata.buf_idx][gpsdata.write_idx] = c;
24
            gpsdata.write_idx++;
25
         }
26
         if (c == '\n') // EOR
27
         {
28
            gpsdata.write_idx = 0;
29
            gpsdata.buf_idx = ~gpsdata.buf_idx; // Puffer umschalten
30
            gpsdata.rx_complete = true; // Meldung an main loop
31
         }
32
      }
33
      break;
34
35
      // ...
36
   }
37
}
38
39
// ...
40
41
   // main loop
42
   if (gpsdata.rx_complete)
43
   {
44
      gpsdata.rx_complete = false; // "wir arbeiten dran™"
45
      int read_idx = ~gpsdata.buf_idx; // Lesepuffer entgegengesetzt zu
46
                                       // Schreibpuffer
47
      char *mybuf = &gpsdata.buf[read_idx];
48
      // Parsen des Strings ab *mybuf
49
   }

: Bearbeitet durch Moderator
von Christian J. (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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

von Christian J. (Gast)


Lesenswert?

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.

von A. S. (Gast)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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...
1
/* ----------------------------------------------------
2
    UART1 Interrupt Service Routine
3
----------------------------------------------------- */
4
5
void USART1_IRQHandler()
6
{
7
    /* Neues Byte wurde empfangen vom GPS Modul */
8
    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
9
    {
10
        char val = USART_ReceiveData(USART1);
11
        /* Wenn $ dann beginnt neuer NMEA String */
12
        if (val=='$')
13
            NmeaBuf_Ptr = 0;
14
        NMEAStrBuf[NmeaBuf_Ptr] = val;
15
        /* Pointer auf nächstes Byte */
16
        NmeaBuf_Ptr = (NmeaBuf_Ptr + 1) % BUFSIZE;
17
        /* String terminieren */
18
        NMEAStrBuf[NmeaBuf_Ptr] = 0;
19
        SetLED(BOARD,ENABLE);
20
    }
21
22
    /* ------------------------------------------------------- */
23
24
    /* Timeout, letztes Byte wurde empfangen */
25
    if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET)
26
    {
27
        NmeaBuf_Ptr = 0; /* Reset RX Pointer */
28
        SetLED(BOARD,DISABLE);
29
    }
30
31
    /* Alle Error Flags löschen */
32
    ClearPendingIT();
33
34
}

von Christian J. (Gast)


Lesenswert?

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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch Moderator
von Christian J. (Gast)


Lesenswert?

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.
1
/* -------------------- Interface Variablen  --------------------- */
2
3
static nmea_info_t GPS;        /* PRIVAT:  Datenregister */
4
5
/* Interface Read Only auf die Elemente des Basis Structs */
6
const nmea_info_t* roGPS = (const nmea_info_t *)&GPS;
7
8
volatile _Bool fGPSValidFix  = false;       /* 1: Gültiger Fix vorhanden */
9
volatile _Bool fNewDataAvailable = false;   /* Neue Daten im Struct vorhanden */
10
volatile _Bool fNewNMEAToParse = false;
11
12
/* -------------------- Private Variablen  --------------------- */
13
14
static char NMEAStrBuf[2][BUFSIZE];         /* 2 Buffer, abwechselnd beschrieben */
15
static int gps_buf_idx = 0;                 /* Zeiger auf aktiven Buffer */
16
static int NmeaBuf_Ptr = 0;
17
18
/* Extern Interface */
19
char *BufToRead = NMEAStrBuf[0];

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

Eine seltsame Geschichte, die zu den Dingen gehört, die man nicht glaubt 
aber die so sind:

DailyData.Tageskilometer vom Typ double.
1
int Strecke = (int)DailyData.Tageskilometer;
2
f_printf(&fs1,"Fahrstrecke gesamt  : %u km\r\n",Strecke);

könnte man auch als
1
f_printf(&fs1,"Fahrstrecke gesamt  : %u km\r\n",(int)DailyData.Tageskilometer);

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Hmm, aber ein "int" als "%u" auszugeben, hat schon was. ;-)

Hast du denn kein Gleitkomma-printf? Ist ja auf'm ARM eigentlich nicht 
unüblich.

von Christian J. (Gast)


Lesenswert?

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.

von Harry L. (mysth)


Lesenswert?

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!

: Bearbeitet durch User
von Christian J. (Gast)


Lesenswert?

Harry L. schrieb:
> FreeRTOS würde dein Projekt deutlich erleichtern.

Das ist auch angedacht, obwohl bare-metal auch Spass macht.

von Harry L. (mysth)


Lesenswert?

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.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

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?

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

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! ;-)

von Til S. (Firma: SEGGER) (til_s)


Lesenswert?

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.

von Cyblord -. (cyblord)


Lesenswert?

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.

von Christian J. (Gast)


Lesenswert?

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.

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