Guten Tag,
ich habe eine Frage in Richtung Programmierung / Best Practice.
Ich möchte am RaspberryPi einen Heizstab ansteuern. Dafür würde ich
gerne ein PWModuliertes Signal nutzen. Ich möchte hinterher die Vorgabe
der Heizleistung aus einer Regelung erhalten, hier zum Test mit
Konstante. Die Heizleistungsvorgabe kann durch die Regelung über die
Stellwertgrenzen gehen, daher die Begrenzung.
1
#include<wiringPi.h>
2
3
#include<stdio.h>
4
#include<stdlib.h>
5
#include<stdint.h>
6
7
8
#define PWM_PIN01 = 1
9
#define MAX_LOAD_POWER = 3000
10
#define PWM_CLK = 4
11
#define MAIN_FREQ = 192000000
12
#define PWM_RANGE = 512
13
14
15
voidpwm_setup(void);
16
intcheck_heatpower(intheat2pwm);
17
intheat2pwm(intheat2pwm);
18
19
20
voidpwm_setup(void)
21
{
22
if(wiringPiSetupGpio()==-1)
23
exit(1);
24
25
pinMode(PWM_PIN01,PWM_OUTPUT);
26
pwmSetClock(MAIN_FREQ/PWM_RANGE/PWM_CLK);
27
pwmSetRange(PWM_RANGE);
28
29
return0;
30
}
31
32
33
intcheck_heatpower(intheat2pwm)
34
{
35
if(heat2pwm>MAX_LOAD_POWER)
36
{
37
returnMAX_LOAD_POWER;
38
}
39
returnheat2pwm;
40
}
41
42
intheat2pwm(intheat2pwm)
43
{
44
returnheat2pwm/MAX_LOAD_POWER*(PWM_RANGE-1);
45
}
46
47
intmain()
48
{
49
pwm_setup();
50
pwmWrite(heat2pwm(check_heatpower(2000)));
51
return;
52
}
Ich habe versucht, die Funktionen zu trennen und möglichst wenig Inhalt
reinzupacken. Ist dies so wünschenswert (zB heat2pwm könnte ich auch
direkt ind pwm write reinschreiben oder check_heatpower in heat2pwm)?
Ist der Aufruf auch so ok (mit den funktionen verschachtelt, kommt es
mir merkwürdig vor).
Bin leider lange nicht mehr am programmieren gewesen :)
Für ein paar Hinweise wäre ich sehr dankbar ;)
Theoretisch ok, pwmWrite will wohl noch die einzig verfügbare Pinnummer.
Praktisch würde ich heat2pwm nicht als Parameternamen in check_heatpower
verwenden wenn er schon als Funktion anderweitig auftaucht.
Irgendwie mutet es ein wenig strange an, wenn eVariablen wie Funktionen
z.B. "heat2pwm" heißen...
Du solltest übrigens generell deine Rechnung ändern, denn wenn der
Compiler von links nach rechts rechnet, was ist dann
heat2pwm/MAX_LOAD_POWER in 2999 von 3000 Fällen? Richtig: es ist 0 und
deshalb kommt in dieser Rechnung von 0 bis 2999 das Ergebnis 0 heraus
und bei nur 3000 das Ergebnis 511.
Abhilfe schafft eine Umstellung (und zur Sicherheit Klammern):
1
return(heat2pwm*(PWM_RANGE-1))/MAX_LOAD_POWER;
Die Formel ist aber im Grunde falsch (wie so viele ADC-Berechnungen mit
Faktoren wie 255, 511, 1023, usw):
1
returnheat2pwm/MAX_LOAD_POWER*(PWM_RANGE-1);
Denn hier bekommst du nur und genau bei heat2pwm = 3000 den PWM-Wert
511, aber dafür von heat2pwm = 0..5 den PWM-Wert 0.
Korrekt wäre es, den Wertebereich des Sollwerts auf 0..2999 zu begrenzen
und mit 512 zu skalieren:
1
if(heat2pwm>MAX_LOAD_POWER-1)
2
{
3
returnMAX_LOAD_POWER-1;
4
5
:
6
:
7
return(heat2pwm*PWM_RANGE)/MAX_LOAD_POWER;
Ich würde da übrigens deutsche Namen verwenden, so lange das kein
internationales Projekt wird. Denn es gibt im Englischen keine
"heatpower" in dem Sinne, wie sie hier verwendet wird. Und mir ist auch
rätselhaft, wie man eine "heat" in eine "PWM" umrechnen kann...
Matthias schrieb:> Ich habe versucht, die Funktionen zu trennen und möglichst wenig Inhalt> reinzupacken. Ist dies so wünschenswert?
Ja, es verbessert die Lesbarkeit des Codes.
Nur wenn du wirklich einen Performance-Engpass hast, der die Funktion
beeinträchtigt, würde ich versuchen, den Quelltext für die Maschine zu
optimieren.
Falls dein Gerät Strom in Größenordnung von Kilowatt verheizt, beachte
die Vorschriften des Netzbetreibers. Man darf nämlich nicht beliebig
große Leistungen per PWM steuern.
Steve van de Grens schrieb:> Falls dein Gerät Strom in Größenordnung von Kilowatt verheizt
Könnten natürlich auch 3000 mW sein. Denn diese Information bzw. die
Skalierung ist in irgendeinem anderen Teil der Doku zu finden.
Danke jedenfalls für die vielen Anmerkungen zum besagten Codeausschnitt.
Und keine Sorge, ich möchte einen gekauften Leistungssteller
(Phasenanschnittsteuerung) mit PWM EINGANG ansteuern. Die
Leistungselektronik und Netzverträglichkeit habe ich somit eingekauft.
LG
Matthias schrieb:> ich möchte einen gekauften Leistungssteller (Phasenanschnittsteuerung)> mit PWM EINGANG ansteuern.
Nicht alles, was man kaufen kann, darf so ohne weiteres auch betrieben
werden. Und zum Thema gibt es durchaus Regelwerke, die simple
Phasenanschnittgeräte über 200W verbieten:
- VDE-AR-N+4100
- TAB 2007
- TAEV 2016
> Die Leistungselektronik und Netzverträglichkeit habe ich somit> eingekauft.
Welches Gerät ist es denn? Kann das evtl. mehr als nur simplen
Phasenanschnitt?
Ehrlich gesagt hätte ich diesen Code komplett in die Mainfunktion
gepackt. Du hast einige extrem einfache Funktionen definiert, die Du nur
an einer einzigen Stelle aufrufst. Dadurch produzierst du einen ziemlich
substanzlosen Codeschaum (damit meine ich die Syntax, nicht die
Funktion). Best Practice: Wenn du etwas kurz formulieren kannst, ohne
dass die Verständlichkeit verloren geht, dann mach es auch. Funktionen
lohnen sich erst ab einer gewissen Komplexität und/oder mehreren
Aufrufpunkten. Hingegen lohnen sich Sourcecode-Kommentare immer :-)
Was die Sprache angeht, möchte ich Lothar widersprechen. Deutsche
Variablennamen klingen irgendwie immer nach Volkshochschul-Basic-Kurs
aus den 80ern. Bei dem Mischmasch aus englisch-stämmigen
C-Schlüsselwörtern und deutschen Namen und Kommentaren sträuben sich mir
die Haare, aber das ist natürlich nur mein persönliches Empfinden. Aber
es gibt immer eine gewisse Wahrscheinlichkeit, dass dein Code irgendwann
mal in einem englischsprachigen Forum landet. Wir lesen hier ja oft
genug Sourcecodes mit französischen oder spanischen Variablennamen, und
dann geht die Raterei los.
Und btw, du willst wirklich einen RPi dafür nehmen? Mehrere CPU-Cores,
Gigabyte Speicher und ein Multiuser-Multitasking-Betriebssystem, um
einen Tauchsieder zu regeln? Ein Arduino-Nano-Klon vom Chinesen könnte
das mit einem Bruchteil der Ressourcen, und der C-Code wäre fast der
selbe.
Matthias schrieb
> int heat2pwm (int heat2pwm)> {> return heat2pwm / MAX_LOAD_POWER * (PWM_RANGE-1);> }
Kompiliert das überhaupt? Die Funktion heat2pwm bekommt eine int
gleichen Namens übergeben. Spätestens bei
return heat2pwm / …
wüßte ich ja nicht mehr so recht, ob hier eine Funktion rekursiv
aufgerufen werden soll (dann wäre aber die Zeile fehlerhaft), oder ob
hier die übergebene int gemeint ist…
Vancouver schrieb:> Ehrlich gesagt hätte ich diesen Code komplett in die Mainfunktion> gepackt.
Ja, da stimme ich dir zu! Ein paar sinnvolle Kommentarzeilen zusammen
mit einer Leerzeile strukturieren auch.
> Was die Sprache angeht, möchte ich Lothar widersprechen. Deutsche> Variablennamen klingen irgendwie immer nach Volkshochschul-Basic-Kurs> aus den 80ern. Bei dem Mischmasch aus englisch-stämmigen> C-Schlüsselwörtern und deutschen Namen und Kommentaren sträuben sich mir> die Haare, aber das ist natürlich nur mein persönliches Empfinden.
Naja, wie du sagst: persönliches Empfinden.
Jemand der die Sprache gerade lernt ist vermutlich dankbar, wenn er
zwischen den (englischen) Schlüsselwörtern und Bibliotheksfunktionen und
den deutsch geschriebenen Variablennamen und eigenen Funktionen
einfach(er) unterscheiden kann. Ich hätte mich damals jedenfalls bei den
Codebeispielen deutlich leichter getan.
Dem Compiler jedenfalls ist es völlig egal ... 😀
Sebastian S. schrieb:> Matthias schrieb>> int heat2pwm (int heat2pwm)>> {>> return heat2pwm / MAX_LOAD_POWER * (PWM_RANGE-1);>> }>> Kompiliert das überhaupt? Die Funktion heat2pwm bekommt eine int> gleichen Namens übergeben. Spätestens bei>> return heat2pwm / …>> wüßte ich ja nicht mehr so recht, ob hier eine Funktion rekursiv> aufgerufen werden soll (dann wäre aber die Zeile fehlerhaft), oder ob> hier die übergebene int gemeint ist…
Stell dir mal folgendes vor:
1
uint8_tmyFunkyVariable=10;
2
...
3
uint8_tmyFamousFunction(uint8_tmyFunkyVariable){
4
returnmyFunkyVariable/2;
5
}
6
7
...
8
9
intmain(void){
10
...
11
uint8_taHalfOfFunkyStuff=myFamousFunction(38);
12
...
13
}
Welchen Wert wird aHalfOfFunkyStuff haben am Ende? 5 oder 19? Auch das
wird fehlerfrei compilieren, genauso wie das von dir angesprochene ;)
M. K. schrieb:>> Welchen Wert wird aHalfOfFunkyStuff haben am Ende? 5 oder 19? Auch das> wird fehlerfrei compilieren, genauso wie das von dir angesprochene ;)
Danke für das kleine Rätsel, kann es so aus dem Stehgreif nicht
beantworten. Was sich vielleicht auch durch meine (Rück-) Frage erahnen
ließ … mein Bauchgefühl und etwas Kopf sagt mir 19.
Aber den Punkt den ich machen wollte ist der, dass die Lesbarkeit des
Codes leidet, wenn Funktionen und Variable den gleichen Namen und
Schreibweise (groß/klein) haben. Und umschifft, wenn konsequent
angewendet, auch die Notwendigkeit die Frage 5 oder 19 zu beantworten.
Eine mögliche Fehlerquelle weniger .
Sorry, aber die Benennung ist doch irreführend. Und damit meine ich
**falsch**.
check_heatpower bekommt eine heatpower und gibt eine zurück. Da ist kein
"pwm" in der Funktion.
heat2pwm bekommt ebenfalls eine heatpower. Die sollte ebenfalls nicht
2pwm heißen, geht aber noch irgendwie.
Über den Rest kann man sicher geteilter Meinung sein. Ich finde
einerseits check_heatpower eher klein/klein,
pwmWrite(heat2pwm(check_heatpower(2000))) das Gegenteil. pwmWrite macht
ja keine generelle PWM. Sondern steuert die Heizung an. Ich hatte da
eine Funktion (z.b. SetHeat) die mit 2000 aufgerufen wird, die Grenzen
(auch 0) testet und dann pwmWrite aufruft. Das hängt aber stark vom
restlichen Code und deinem Style ab
Sebastian S. schrieb:> Danke für das kleine Rätsel, kann es so aus dem Stehgreif nicht> beantworten. Was sich vielleicht auch durch meine (Rück-) Frage erahnen> ließ … mein Bauchgefühl und etwas Kopf sagt mir 19.
Richtig. idR haben nämlich lokale Bezeichner das höhere Gewicht als
(globale) Bezeichner, die darüber stehen.
Sebastian S. schrieb:> Aber den Punkt den ich machen wollte ist der, dass die Lesbarkeit des> Codes leidet, wenn Funktionen und Variable den gleichen Namen und> Schreibweise (groß/klein) haben. Und umschifft, wenn konsequent> angewendet, auch die Notwendigkeit die Frage 5 oder 19 zu beantworten.> Eine mögliche Fehlerquelle weniger .
Dann schreib das auch so, dann weiß man direkt was du meinst ;)
Ich sehe das übrigens ähnlich. Bezeichner mit identischen Namen sollte
man vermeiden. Übrigens, mein Beispiel war noch etwas kniffliger als
deines: Der Aufruf einer Funktion unterscheidet sich ja doch schon
erheblich vom Aufruf einer Variablen in C. Spannender ist da der Aufruf
zweier Variablen mit selben Namen. So wie ich es geschrieben habe ist
die globale Version innerhalb der Funktion gar nicht erreichbar ;)
a) Funktionen mache ich nur dann, wenn ich den enthaltenen Code
tatsächlich oder vermutlich zukünftig mehr als einmal benötige
b) wenn ich das Resultat einer Funktion/Methode innerhalb eines
Programmblocks oder einer eigenen Funktion mehrfach benötige, kopiere
ich deren Resultat in eine lokale Variable und nutze diese so oft wie
notwendig
Frank E. schrieb:> a) Funktionen mache ich nur dann, wenn ich den enthaltenen Code> tatsächlich oder vermutlich zukünftig mehr als einmal benötige
Ich mach das in erster Linie der Übersichtlichkeit halber. Wenn ich
Funktionen zukünftig auch verwenden will oder dies als sinnvoll erachte
mache ich sogar Klassen daraus ;)
Frank E. schrieb:> a) Funktionen mache ich nur dann, wenn ich den enthaltenen Code> tatsächlich oder vermutlich zukünftig mehr als einmal benötige>> b) wenn ich das Resultat einer Funktion/Methode innerhalb eines> Programmblocks oder einer eigenen Funktion mehrfach benötige, kopiere> ich deren Resultat in eine lokale Variable und nutze diese so oft wie> notwendig
Klingt wie etwas, was der Compiler für mich macht.