Hallo,
ich habe mal gelesen, dass man in C möglichst modular programmieren
soll.
Nun habe ich mittels eines Arduino Uno eine kleine
Geschirrspül-Steuerung in C fertiggestellt, und zwar so, dass die
Parameter für die einzelnen Spülgange (Vorspülen, Hauptspülen,
Klarspülen etc.) in einem Array aus Structelementen gespeichert werden.
Dies übernimmt meine Funktion Parameter_Lader, die im SetUp einmal
aufgerufen wird und sich dabei der global vereinbarten
Array-of-Struct-Variablen bedient. Meine Programmfunktion greift nun auf
diesen Struct-Array zu und übergibt bestimmte Structelemente je nach
Programmvariante (Normal oder Intensiv) den eigentlichen
Steuerfunktionen (Wasserzulauf, Spülen, Abpumpen).
Klappt soweit alles gut.
Nur eben: modular programmiert ist das nicht.
Daher meine Frage: Wie kann ich einen Array-of-Structs von einer
Funktion an die andere übergeben? Gibt es da einen Weg - oder gar
mehrere?
Vielen Dank für die Antworten!
Modularier schrieb:> Arrays werden mit einem Pointer übergeben. Für weitere Fragen am besten> auch den Quellcode posten.
Bitte zusätzlich die Größe des Arrays übergeben. So kann man in der
Funktion selbst auf deinen Überlauf testen. Sonst tappt man dort nämlich
im Dunkeln.
CompressorBoy schrieb:> ich habe mal gelesen, dass man in C möglichst modular programmieren> soll.
Jein. Bei größeren Projekten wird es unübersichtlich, wenn man zuviel
mit globalen Variablen arbeitet, das stimmt. Aber:
1) ist das bei kleinen Projekten unpoblematisch.
2) ist es auch nur Kosmetik, wenn Du ohnehin nur eine solche Struktur
hast und die dann überall mit Pointern weiterreichst. Es wird dann
nämlich sowieso immer auf demselben globalen Objekt gearbeitet, also
kein sonderlicher Unterschied.
Etwas anderes wäre es erst, wenn Deine eigentliche Waschfunktion (hier:
einzel_spuel_vorgang) eben nicht den Pointer auf das "hier ist alles
drin"-Gott-Objekt kriegt, also nicht das ganze array of structs, sondern
nur die jeweils benötigten structs (mit nem Pointer darauf).
Beispiel, wie ich das meine. Dein struct-Array enthält in Komponente 0
die Parameter fürs Vorspülen, in 1 die fürs Hauptspülen und in 2 die
fürs Klarspülen. Diese Indices dann natürlich mit defines oder enums,
nicht mit magic numbers.
Dann könnte das so aussehen, wobei Dein Parameterstruct ist (die
Returnwerte können Fehlercodes sein):
Nop schrieb:> 2) ist es auch nur Kosmetik, wenn Du ohnehin nur eine solche Struktur> hast und die dann überall mit Pointern weiterreichst. Es wird dann> nämlich sowieso immer auf demselben globalen Objekt gearbeitet, also> kein sonderlicher Unterschied.
Doch es macht einen Unterschied. Einerseits in der Übersichtlichkeit und
Lesbarkeit des Codes aber auch technisch, sobald ein Zeiger auf const
übergeben wird.
Nop schrieb:> Etwas anderes wäre es erst, wenn Deine eigentliche Waschfunktion (hier:> einzel_spuel_vorgang) eben nicht den Pointer auf das "hier ist alles> drin"-Gott-Objekt kriegt, also nicht das ganze array of structs, sondern> nur die jeweils benötigten structs (mit nem Pointer darauf).>> Beispiel, wie ich das meine. Dein struct-Array enthält in Komponente 0> die Parameter fürs Vorspülen, in 1 die fürs Hauptspülen und in 2 die> fürs Klarspülen. Diese Indices dann natürlich mit defines oder enums,> nicht mit magic numbers.
Wenn man das so machen will, kann man auch gleich einzelne Variablen
erstellen und entsprechend benennen.
Ich habe den TO so verstanden, dass das Programm individuell
zusammengestellt werden soll, also z.B. Vorspülen nicht zwingend
vorkommen muss. Daher ist die Idee mit dem Array nicht falsch. Eine
Alternative wäre, ein Array aus Zeigern (Liste) zu erstellen, die auf
die Variablen zeigen.
Meine Güte, ist ja schon 'ne Menge los hier!
Vielen Dank für die Antworten, ich muss sie erst mal sichten.
Da jemand nach mehr Code-Info gefragt hatte - hier ist sie:
Der PARAMETER_LADER steht im SetUp (mit ein paar #defines zur besseren
Lesbarkeit des Programms):
1
voidPARAMETER_LADER(){
2
3
par[laugenbehaelter_leeren]={
4
0fuellhoehe,
5
0spuelzeit,
6
heizung_aus};
7
8
par[laugenbehaelter_spuelen_3]={
9
3fuellhoehe,
10
0spuelzeit,
11
heizung_aus};
12
13
par[vorspuelen_kalt]={
14
2fuellhoehe,
15
20spuelzeit,
16
heizung_aus};
17
18
.
19
.
20
.
21
22
par[klarspuelen_warm]={
23
2fuellhoehe,
24
20spuelzeit,
25
heizung_an};}
Typdefinintion und Festlegung einer Arrayvariablen dieses Typs sind
global:
1
typedefstruct{
2
uintwasserstand;
3
ulongspueldauer;
4
boolheizung;}parameter;
5
6
parameterpar[20];
Das eigentliche Spülprogramm steht in der Schleife:
1
voidNORMALPROGRAMM(){
2
3
uintprog[]={
4
laugenbehaelter_leeren,
5
hauptspuelen_mittel,
6
klarspuelen_kalt,
7
laugenbehaelter_spuelen_2,
8
klarspuelen_warm,
9
programm_ende};
10
11
uintcount;
12
13
uintstartverzoegerung=3sec;
14
delay(startverzoegerung);
15
16
digitalWrite(Hauptstromversorgung,AN);
17
18
while(prog[count]<programm_ende){
19
WASSERZULAUF(par[prog[count]]);
20
count++;}
21
22
digitalWrite(Hauptstromversorgung,AUS);
23
while(1);}
während die miteinander verketteten Spülfunktionen sind:
CompressorBoy schrieb:> parameter par[20];
Woher kommt die 20? Magic Number?
CompressorBoy schrieb:> uint count;
Nicht initialisierte Variable. Oder setzt das Arduino-Framework die
Variable auf null?
CompressorBoy schrieb:> if (par.wasserstand)> goto EndWasserzulauf;> ...>> EndWasserzulauf: SPUELEN (par);
Warum nicht
1
if(!par.wasserstand){
2
...
3
}
4
5
SPUELEN(par);
Die ganze Sache ist noch etwas unübersichtlich. Du benutzt für drei
Verschiedene Dinge den Gleichen Datentyp (parameter). Auch die Funktion
WASSERZULAUF tut nicht nur das, was sie verspricht, nämlich die Maschine
mit Wasser füllen. Sie ruft auch noch SPUELEN auf, die wiederum ABPUMPEN
aufruft. Besser wäre folgende Struktur:
1
voidWaschprogrammDurchfuehren(...){
2
WasserFuellen(...);
3
Spuelen(...);
4
Abpumpen(...);
5
}
Noch was: Identifier in Grossbuchstaben sind in C normalerweise für
defines reserviert. Dies ist keine Pflicht, wird aber von der grossen
Mehrheit so gemacht.
CompressorBoy schrieb:> typedef struct {> uint wasserstand;> ulong spueldauer;> bool heizung;} parameter;
Sicher, daß Du die Spüldauer auf 1ms genau bis 49d angeben mußt?
Und den Wasserstand auf 1ml genau bis 65l.
Bei der Heizung würde ich dagegen wählbar machen, ob 50, 55 oder 65°C.
3 * uint8_t sollten reichen:
Wasser: 0,1l .. 25l
Zeit: 1min .. 255min
Temperatur: aus, 50, 55, 60°C
CompressorBoy schrieb:> ich habe mal gelesen, dass man in C möglichst modular programmieren> soll.
Das Beispiel mit dem struct ist wirklich nicht das, was es sein sollte.
Also: modular programmieren besteht im Wesentlichen daraus, daß man
nicht aalles wahllos in eine Quelle hineinwirft. In jeder Firmware gibt
es Programmteile, die sich um Dinge unterschiedlichen Niveaus kümmern.
Das nimmt man auseinander und verpackt es in separate Quellen.
Mal ein Beispiel:
- Startupcode
- Konfigurationsmodul (Pinbelegungen, Takt usw.)
- Low Level Treiber für "Hände und Füße", also z.B. einer für den Motor,
ein anderer für das Spülwasser-Ventil, einer für die Regelung der
Waschtemperatur, einer für die Bedienung der Anzeige, einer für das
Abfragen und Entprellen der Knöpfe und so weiter.
- ein Modul für das Organisieren des Waschvorgangs, also Abläufe
organisieren
- ein Modul für Kommandogabe und Darstellung, also was man wie an
welcher Stelle im Programmablauf machen kann und Organisation dessen,
was man dabei auf dem Display zu sehen kriegt
- ein Modul für die Systemzeit und Zeitüberwachungen
- main mit der Grundschleife
So ungefähr geht sowas. Das Prinzip dabei ist, daß eben nur diejenigen
Module sich mit den Einzelheiten der HW abgeben, die dafür auch gedacht
sind. Der Modul für die Abläufe sollte z.B. überhaupt keine HW direkt
anfassen, sondern lediglich "Kommandos" erteilen, wie z.B. Motor_Ein()
oder Heizung_auf(45.5); (°Celsius) - und wie der Motortreiber den Motor
einschaltet, ist sein Bier und interessiert den Rest der ganzen Firmware
überhaupt nicht.
W.S.
Nop schrieb:> CompressorBoy schrieb:>> ich habe mal gelesen, dass man in C möglichst modular programmieren>> soll.>> Jein. Bei größeren Projekten wird es unübersichtlich, wenn man zuviel> mit globalen Variablen arbeitet, das stimmt. Aber:
Solang die Variablen nur im Modul "global" sind (static), ist das völlig
unproblematisch. Nur globale Variablen, die übers ganze Projekt
manipulierbar sind, sind böse.
led.c
1
staticbooltoggleMode=false;
2
staticuint32_ttogglePeriodMs=1000;
3
//...
4
5
voidled_init();
6
voidled_enableToggleMode(uint32_tperiodMs);
7
//...
toggleMode und togglePeriodMs sind nur in der led.c, also im Modul,
erreichbar. Schön gekapselt. So spart man sich auch irgendwelche Zieger
auf eine Struktur mit den Membervariablen des Moduls. Gleiches gilt für
static functions.
Mein Grundgedanke bei der Programmerstellung war, es bezüglich künftiger
Änderungen flexibel zu gestalten in der Form, dass weitere Spülprogramme
aus einzeln parametrierten Spülmodulen zusammengestellt werden können,
um eben auch künftigen Anforderungen gerecht werden zu können.
Nur dachte ich eben, dass es vielleicht eleganter ist, wenn der
Parameter_Lader seine Struct-Elemente nicht global herum flottieren
lässt, sondern sie als return-Wert direkt derjenigen Funktion übergibt,
welche sie braucht.
CompressorBoy schrieb:> Mein Grundgedanke bei der Programmerstellung war, es bezüglich künftiger> Änderungen flexibel zu gestalten in der Form, dass weitere Spülprogramme> aus einzeln parametrierten Spülmodulen zusammengestellt werden können,> um eben auch künftigen Anforderungen gerecht werden zu können.>> Nur dachte ich eben, dass es vielleicht eleganter ist, wenn der> Parameter_Lader seine Struct-Elemente nicht global herum flottieren> lässt, sondern sie als return-Wert direkt derjenigen Funktion übergibt,> welche sie braucht.
Warum brauchst du überhaupt diese Parameter_lader? Die ganze
parameter-Struktur kann auch statisch angelegt werden:
> Hat static die gleiche Wirkung wie const?
Nein, denn sonst gäbe es keine Unterschiedlichen Begriffe dafür.
> Und kann man struct-Variablen const-ant machen?
Ja.
CompressorBoy schrieb:> Und kann man struct-Variablen const-ant machen?
Auf die Frage "was heißt const" zu Antworten "das sind Konstanten", ist
übrigens ein hervorragender Weg, in einem Bewerbungsgespräch umgehend
durchzufallen.
CompressorBoy schrieb:> Sind sie doch auch in dem Sinne, dass sie festgesetzt sind und während> des Programmlaufs nicht mehr geändert werden können.
Durchgefallen ;-)
1
externconstintx;
bedeutet ausdrücklich nicht, dass x konstant ist. Sondern nur, dass x
in C kein Wert direkt zugewiesen werden darf. Aber vielleicht mit
EEPROM-Write oder mit einem Cast oder mit memcpy.... .Natürlich musst Du
das auch mit Deinem Linker klären.
Bei Read-Only-Registern ist sogar
Peter D. schrieb:> Sicher, daß Du die Spüldauer auf 1ms genau bis 49d angeben mußt?> Und den Wasserstand auf 1ml genau bis 65l.> Bei der Heizung würde ich dagegen wählbar machen, ob 50, 55 oder 65°C.>> 3 * uint8_t sollten reichen:> Wasser: 0,1l .. 25l> Zeit: 1min .. 255min> Temperatur: aus, 50, 55, 60°C
Der Herr Sonnenschein mal wieder in seiner typischen arroganten Art.
Lg Heiner
Achim S. schrieb:> Durchgefallen ;-)
Au weia :-) ...
Aber immerhin habe ich was dazugelernt.
Na ja, ich bin in C ja noch ein Newbie, und was Kenntnissen noch nicht
da ist, kann ja noch kommen.
Klar ist: da C eine hardwarenahe Sprache ist, die auf dieser Ebene viele
Dinge ansprechen kann, muss man die (Mikrocontroller-)Hardware gut genug
kennen.
Ich werde daran arbeiten...
Solche Ablaufsteuerungen würde ich mit einer Statemaschine (switch/case)
aufbauen, quasi analog einem Schrittschaltwerk.
Ist ein Parameter 0, wird der entsprechde Schritt übersprungen.
D.h. man hat eine Parameterliste, die das switch/case einfach der Reihe
nach durchackert (den Index hochzählt).
Peter D. schrieb:> D.h. man hat eine Parameterliste, die das switch/case einfach der Reihe> nach durchackert (den Index hochzählt).
Wenn man das in eine while-Schleife verpackt, kann man sogar effektiv
mit gotos in der Statemachine herumspringen, ohne jemals die
Zeichenfolge "goto" tippen zu müssen. Ein Hoch auf die strukturierte
Programmierung. (;
"extern const volatile struct io_pin" ?
Sowas ähnliches habe ich letztens gebastelt, da lokale Registeradressen
nur über externe Speicherzugriffe erreichbar waren, deren Adressraum zur
Compilezeit nicht feststelt.
Eric B. schrieb:> Dann sind es aber keine Variablen mehr ;-)
Direkt nach dem Durchfallen des Vorgängers mit demselben Ansatz nochmal
durchzufallen, ist wohl die hohe Kunst des Durchfallens. ^^
Denkanstoß: Ein struct, das auf IO-Register mappt, welche readonly sind,
sich aber durchaus ändern können. Statusregister beispielsweise, oder
AD-Wandler-Datenregister.
Nop schrieb:> Eric B. schrieb:>> Dann sind es aber keine Variablen mehr ;-)>> Direkt nach dem Durchfallen des Vorgängers mit demselben Ansatz nochmal> durchzufallen, ist wohl die hohe Kunst des Durchfallens. ^^
Haste Recht. Lektion für heute: zuerst den Draht bis zum Ende
durchlesen, dann antworten :-)
Wenn ich hier "Statemaschine" lese, wird mir klar, wieviel ich noch
lernen muss.
Gibt es (relativ) leicht verständliche Bücher, die diese und ähnliche
Programmiertechniken in Bezug auf Mikrocontroller darlegen?
Nop schrieb:> Wenn man das in eine while-Schleife verpackt, kann man sogar effektiv> mit gotos in der Statemachine herumspringen, ohne jemals die> Zeichenfolge "goto" tippen zu müssen. Ein Hoch auf die strukturierte> Programmierung. (;
Unsere Cash-Cow ist ein Applikations-Code mit etwa 500k LOC. Und
irgendwo 1(!) goto (sprintf-Clone glaube ich). Im Rahmen der
Zertifizierung habe ich hochnotpeinlich eingeräumt, dieses goto bewusst
belassen zu haben und mich geweigert, es umzuschreiben.
Was war das für ein Triumpf des Firmwerkers, der wenige Tage zuvor in
seinen etwa 100k LOC alle Gotos (mehrere 100!!!) durch while / case
ersetzt hat. Er war sauber! Und mit den auskommentierten Originalzeilen
war es sogar lesbar (zumindest so viel oder wenig wie zuvor).
CompressorBoy schrieb:> Wenn ich hier "Statemaschine" lese
Statusmaschine heisst eigentlich nur, ich bin in diesem State (z.B.
aufheizen oder auf Taste warten) dann passiert das (Temperatur erreicht,
Timeout oder Taste gedrückt) und dann wechsle ich in den State ...
Es gibt verschiedenste Ausführungen und Paradigmen, Darstellungsweisen
und Notationen.
Unabhängig davon gibt es aber zwei grundsätzlich Arten der
Implementierung
a) Eventgesteuert
b) SPS-Loop
Bei a) hast Du je Statusmaschine eine Task mit Endlosschleife, die auf
Events wartet (auch Timeout ist ein Event) und dann irgendwas tun.
Bei b) hast Du je Statusmaschine eine Funktion, die zyklisch ohne
blockierende Aktionen nacheinander durchlaufen werden.
Die Unterschiede und Konsequenzen sind so gravierend, dass Du mit beiden
Konzepten Erfahrungen sammeln solltest, und intensiv mit b ;-)
Achim S. schrieb:> Durchgefallen ;-)> extern const int x; bedeutet ausdrücklich nicht, dass x konstant ist.
Vorsicht, mein Lieber. Wir sind hier nicht bei der PC-Programmierung,
sondern bei Mikrocontrollern. Und da kann es dir durchaus passieren, daß
du bei solchen Variablen schlichtweg auf Stein beißt, weil sowas auch
mal im Flash stehen kann.
Noch deftiger wird das Ganze, wenn du ne Harvard-Architektur hast, wo du
für etwaige Zugriffe auf den Flash (=Befehlsspeicher) nen völlig anderen
Maschinenbefehl (oder ne Hardware-Einrichtung wie bei den PIC's)
brauchst als bei RAM-Zugriffen.
Ich halte vom inflationären Gebrauch von const nichts, ein ähnliches
gilt für sowas wie static. Schlimme unsystematische
Programmiergewohnheiten kann man dadurch auch nicht besser machen.
W.S.
W.S. schrieb:> Vorsicht ....weil sowas auch mal im Flash stehen kann.Achim S. schrieb:> Natürlich musst Du das auch mit Deinem Linker klären.W.S. schrieb:> für etwaige Zugriffe auf den Flash (=Befehlsspeicher) nen völlig anderen> Maschinenbefehl (oder ne Hardware-Einrichtung wie bei den PIC's)> brauchst als bei RAM-Zugriffen.Achim S. schrieb:> Sondern nur, dass x in C kein Wert direkt zugewiesen> werden darf. Aber vielleicht mit EEPROM-Write
Wenn der Compiler das Lesen nicht hinkriegt, kann er keine Variable
dahinlegen, auch keine const.
Achim S. schrieb:> Durchgefallen ;-)> extern const int x; bedeutet ausdrücklich nicht, dass x konstant ist.
Vorsicht in der Kurve. Wenn in dem C-File, wo x wirklich deklariert ist,
x nicht als const int, sondern als int deklariert ist, dann sind das
inkompatible Typen, und somit man ist im undefined behaviour.
Quelle mit Verweis auf die relevanten Parts des C-Standards:
http://stackoverflow.com/questions/27724453/declaring-a-global-variable-extern-const-int-in-header-but-only-int-in-sourc
extern const ist kein Weg, wie man im Falle globaler Variablen einem
anderen Modul die Variable als read-only zur Verfügung stellen kann.
Will man das machen, braucht man eine getter-Funktion.
W.S. schrieb:> Ich halte vom inflationären Gebrauch von const nichts, ein ähnliches> gilt für sowas wie static.
Ich setze const immer ein, wenn ich Funktionen habe, die einen Pointer
akzeptieren, aber die Daten nur lesen. Dann sieht man nämlich direkt im
Prototypen schon, daß die Funktion keine Seiteneffekte auf die
referenzierten Daten hat.
Static setze ich auch exzessiv ein, wenn ich klarmachen will, daß eine
Funktion nur im Filescope existiert, weil das dem Compiler ein
aggressiveres Inlining ermöglicht. Und bei globalen Variablen, um
klarzumachen, daß die nur in dem File relevant sind.