Best Practise anhand Codebeispiel

OP #7399715
Lesenswert?

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
void pwm_setup(void);
16
int check_heatpower (int heat2pwm);
17
int heat2pwm (int heat2pwm);
18

19

20
void pwm_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
  return 0;
30
}
31

32

33
int check_heatpower (int heat2pwm)
34
{
35
  if (heat2pwm > MAX_LOAD_POWER)
36
  {
37
    return MAX_LOAD_POWER;
38
  }
39
  return heat2pwm;
40
}
41

42
int heat2pwm (int heat2pwm)
43
{
44
  return heat2pwm / MAX_LOAD_POWER * (PWM_RANGE-1);  
45
}
46

47
int main()
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 ;)

Moderator (Firma: Titel) Persönliche Seite #7399735
Lesenswert?

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
  return heat2pwm / 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
    return MAX_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...

Gast #7399782
Lesenswert?

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.

OP #7399796
Lesenswert?

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

Moderator (Firma: Titel) Persönliche Seite #7399801
Lesenswert?

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?

#7399813
Lesenswert?

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.

#7399833
Lesenswert?

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…

#7399925
Lesenswert?

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

#7399983
Lesenswert?

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_t myFunkyVariable = 10;
2
...
3
uint8_t myFamousFunction(uint8_t myFunkyVariable){
4
  return myFunkyVariable/2;
5
}
6

7
...
8

9
int main(void){
10
  ...
11
  uint8_t aHalfOfFunkyStuff = 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 ;)

#7400092
Lesenswert?

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 .

#7400127
Lesenswert?

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

#7400251
Lesenswert?

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

(Firma: Q3) #7400258
Lesenswert?

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

#7400351
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren