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...
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.
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.
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.
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…
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 ... 😀
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 ;)
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
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.
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
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 ;)
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.