C-Code Frage: const Variable einer Funktion übergeben für static Array-Dimension

Hallo,

ich würde gerne einer Funktion die Dimension für ein static Array übergeben. Leider bekomme ich die Fehlermeldung:

Arduino: 1.6.4 (Linux), Platine: "Arduino Mega or Mega 2560, ATmega2560 (Mega 2560)"

/opt/arduino-1.6.4/hardware/tools/avr/bin/avr-g++ -c -g -Os -Wall -Wextra -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -mmcu=atmega2560 -DF_CPU=16000000L -DARDUINO=10604 -DARDUINO_AVR_MEGA2560 -DARDUINO_ARCH_AVR -I/opt/arduino-1.6.4/hardware/arduino/avr/cores/arduino -I/opt/arduino-1.6.4/hardware/arduino/avr/variants/mega /tmp/build3892027074857907477.tmp/_150614_PV_Sensor_V001.cpp -o /tmp/build3892027074857907477.tmp/_150614_PV_Sensor_V001.cpp.o 
_150614_PV_Sensor_V001.ino: In function 'int analogReadSmooth(int, byte, byte, byte)':
_150614_PV_Sensor_V001.ino:136:30: error: variable-sized object 'adcRaw' may not be initialized
_150614_PV_Sensor_V001.ino:136:30: error: storage size of 'adcRaw' isn't constant
variable-sized object 'adcRaw' may not be initialized

Meine Funktion:

int analogReadSmooth(int pin, byte cnt, byte bits, const byte smooth){
  static const byte a = smooth;
  //const byte a = 10;       // so ginge es
  static int adcRaw[a] = { 0 }; // Array init.
  static byte index = 0;
  static unsigned long total = 0;

  // subtract the last reading:
  total = total - adcRaw[index];
  // read from the sensor:
  adcRaw[index] = analogReadBit(pin, cnt, bits); // ADC-Pin, Samples(1-255), 'Resolution'-Bit (10-12)
  // add the reading to the total:
  total = total + adcRaw[index];
  // advance to the next position in the array:
  index = index + 1;

  // if we're at the end of the array...
  if (index >= smooth)
    // ...wrap around to the beginning:
    index = 0;

  // calculate the average:
  return (total / smooth);
}

Geht das überhapt? Vielen Dank! :slight_smile:

Nein. Const heißt da lediglich, dass die Funktion die Variable nicht verändern kann. Das ist vor allem bei Zeigern und Referenz-Parametern sehr wichtig.

Aber die Array Größe muss bei statischen Arrays trotzdem zur Compile-Zeit bekannt sein.

Wenn ich das richtig sehe, willst du einen Messwert glätten, indem du immer mit den vorangegangenen X Messwerten den Mittelwert bildest.

Das ist vom Code her im Prinzip richtig, aber dein Array funktioniert so nicht. Du musst den scope der Funktion beachten. Sie initialisiert beim ersten Aufruf das array (nehmen wir an, da wurde smooth mit Wert 5 übergeben). Jetzt kommt ein anderer Aufrufer und überträgt smooth = 6. Dann bekommst du nen Index Überlauf.

smooth ist nur innerhalb der funktion konstant. Du kannst aber nicht garantieren, was reinkam.

Du willst die Funktion ja sicherlich mehrfach mit verschiedenen Pins und Glättungen verwenden. Dann musst du sie in eine Klasse packen und der Klasse übergibst du im Konstruktor die ganzen Parameter, die du bisher in der Funktion hast (die sollen ja sicher alle immer gleich bleiben). Und dann rufst du die Funktion nur noch ohne Parameter auf (die entsprechenden Werte kommen dann aus den Klassenvariablen).

Für einen anderen Pin mit anderer Glättung instanzierst du dann einfach ein neues Objekt.

Snippet der Verwendung sieht dann so aus

AnalogSmoothReader reader1(PIN1, CNT, BITS, SMOOTH1);
AnalogSmoothReader reader2(PIN2, CNT, BITS, SMOOTH2);

value1 = reader1.analogReadAndSmooth();
value2 = reader2.analogReadAndSmooth();

Kannst du damit was anfangen?

Das array musst du dann wohl mit einer festen Maximalgröße initialisieren (smooth darf nicht größer werden), weil wie Serenifly schon schreibt zur compilezeit klar sein muss, wie groß es wird.

Würde dir nicht eine feste Glättung reichen (Anzahl der Werte konstant)?

Vielen Dank für die schnellen Antworten!

Also Array Größe soll zur Laufzeit fest sein und ist zur Compile-Zeit bekannt, wollte die Funktion nur gerne flexibel gestalten, und in mehreren Programmen mit versch. Array Größen unterbringen können ohne ein globales
#define adcSmoothArryGroesse 10 // oder ähnliches

Gibt es da eine elegante Möglichkeit einen const Wert einmalig für die Arry-Erstellung and die FCN zu übergen?

Und bisher wollte ich nur einen Analog-Pin mit der Funktion glätten. An mehrere Pins hatte ich bisher noch nicht gedacht, wäre dann sicherlich mein nächster Gedanke gewesen. :wink:

Mit Klassen habe ich schon etwas rumprobiert, denke das könnte ich hinbekommen!

Vielen Dank noch mal!

Hallo,

vielleicht ist die Methode dieser Mittelwertbildung für Dich interessant. Die Exceltabellen, auch von Gunther, hänge ich mit ran. Zur Verdeutlich und rumspielen welche Faktoren wie wirken.

Mittelwertbildung mit Tiefpaß

Ist eine richtig geile Sache. :slight_smile:

Tiefpassfilter.zip (85.3 KB)

Vielen Dank für den Hinweis zum 'digitalen Tiefpassfilter'!!

der 1. Link gibt bei mir einen Fehler, denke Du meinst den hier:
http://forum.arduino.cc/index.php?topic=323737.msg2236895#msg2236895

Da werde ich mal ein paar Vergleiche anstellen. Aber hinsichtlich Ram und CPU-Auslastung ist die im Link vorgestellte Variante kaum zu schlagen! TOP! :wink:

jim_beam:
der 1. Link gibt bei mir einen Fehler, denke Du meinst den hier:
Mittelwert bilden... - Deutsch - Arduino Forum

Da werde ich mal ein paar Vergleiche anstellen. Aber hinsichtlich Ram und CPU-Auslastung ist die im Link vorgestellte Variante kaum zu schlagen! TOP! :wink:

Der von dir gepostete Filter hat durchaus Existenzberechtigung. Ehrlich gesagt verwende ich die Methode mit dem Array sehr gerne: Dabei sollte aber das Array nur aus bytes bestehen und die größe einer 2er-Potenz. Das hat den Vorteil, dass die Modulo-Operation beim Nullen des Pointers durch eine bitweise Verundung und die Division am Ende durch ein einfaches bitschieben realisiert werden kann. (Ich hoffe das war nicht zu technisch ausgedrückt :wink: )
Die Tiefpassfunktion von GunterB ist was Codezeilen und Speicher angeht bestimmt nicht zu schlagen, hat aber zwei signifikante Nachteile: 1. Kleine Änderungen werden NIE erfasst. Ist der aktuelle Filtval z.B. 10 und newVal ist danach IMMER 11, Filtval wird sich nie ändern. Das liegt daran, dass die Division nur mit Integern rechnet. Und sobald man double/float nimmt wird eh alles wieder ineffizient.
2. Wenn FF+1 nicht eine zweierpotenz ist, muss eine aufwändige Divisionsfunktion aufgerufen werden, die die Sache verlangsamt.

Hallo,

also da muß ich mich mal für Gunther stark machen. :slight_smile:

a) wenn man kleinste Eingangsänderungen sehen möchte, dann ist jede Filterung sinnlos
b) kann man durch Änderung des Filterfaktors die Glättung beeinflussen
c) wenn die Funktion einmal gefüttert ist, kommt hinten mit jeden neuen Eingangswert ein neuer gefilterter Mittelwert raus.

Eigentlich muß man nur wissen, ob man eine Verzögerung zuläßt oder nicht. Wenn nicht, muß man ständig Daten sammeln abwarten, filtern und Mittelwert berechnen. Das wird immer komplett wiederholt. Mit dem Tiefpaß gibt es eine einmalige Anfangsverzögerung und ab dann wird mit jedem neuen Wert der vorn reinkommt, hinten ein neuer Mittelwert ausgespuckt der den Eingangswerten "entspricht". Sieht man in der Excel schön wie das abläuft. Sprungantwort Reaktion. Es läuft also ab da schneller ab. Eine gewisse Durchleitungsverzögerung bleibt erhalten, sicher, aber man erhält ständig neue gemittelte Werte. Eine große Abweichung schlägt nicht gleich durch. Deswegen filtert man ja.

Ich lasse zum Bsp. Gunthers Filterfunktion im setup mit genügend Daten füllen und dann erst geht mein Hauptprogramm los und ab dann erhalte immer frische Mittelwerte.

jim_beam:
Geht das überhapt? Vielen Dank! :slight_smile:

Es gibt immer Möglichkeiten. Ich sehe momentan zwei Möglichkeiten, das Problem der static-Deklaration zu lösen:

  1. Möglichkeit: RAM für das Array am Anfang der Funktion dynamisch reservieren
    In der Funktion selbst würdest Du kein "statisches Array" sondern nur einen "statischen Array-Pointer" reservieren und mit NULL initialisieren. Am Anfang der Funktion prüfst Du den Pointer auf NULL und falls er NULL ist, reservierst Du RAM für das Array, bevor die Funktion dann mit dem Array arbeiten kann. Vorteil: Normaler Funktionsaufruf im Programm, Nachteil: Die Prüfung des Pointers am Beginn der Funktion kostet jedesmal ein paar Takte Ausführungszeit.

  2. Möglichkeit: Template-Funktion
    Templates sind zwar eigentlich dafür vorgesehen, dass Du typenunabhängigen Code schreiben kannst, zum Beispiel denselben Algorithmus für byte, int, long und float, und erst im Code entscheidet sich dann, was davon tatsächlich verwendet werden soll, ohne dass Du denselben Algorithmus viermal implementieren müßtest. Aber Du kannst ein Template genau so gut verwenden, um eine statische Größenangabe zu übergeben und der Compiler erzeugt dann den tatsächlichen Code für die tatsächlich aufgerufene Funktion. Nachteil: Die Aufrufkonvention für das Template ändert sich etwas. Vorteil: keine Geschwindigkeitseinbuße gegenüber statischer Größendeklaration.

Ich misch mich einfach mal wieder ein :smiley:

Doc_Arduino:
Hallo,

also da muß ich mich mal für Gunther stark machen. :slight_smile:

a) wenn man kleinste Eingangsänderungen sehen möchte, dann ist jede Filterung sinnlos
b) kann man durch Änderung des Filterfaktors die Glättung beeinflussen
c) wenn die Funktion einmal gefüttert ist, kommt hinten mit jeden neuen Eingangswert ein neuer gefilterter Mittelwert raus.

Ich lasse zum Bsp. Gunthers Filterfunktion im setup mit genügend Daten füllen und dann erst geht mein Hauptprogramm los und ab dann erhalte immer frische Mittelwerte.

Ich sage auch nicht, dass Gunthers Funktion schlecht ist, sie ist in den meisten Fällen sehr praktisch und ich verwende sie auch sehr gern. In der Theorie ist sie sogar 1:1 der Nachbau eines klassischen RC Tiefpassfilters, das ist meistens genau das, was man haben will.

Zu c): Das tolle ist, das man ihn nicht mit Daten füllen muss bis er einsatzbereit ist, sondern einfach Filtval entsprechend auf den Startwert setzen kann.

Zu a) und b):
Das Problem liegt an einer anderen Stelle: je größer FF ist, desto größer muss auch die Änderung sein, damit sie überhaupt Einfluss auf das Ergebnis hat. Es gibt einen unterschied zwischen "überhaupt keinen" und einen "sehr kleinen" Einfluss. Das liegt daran, dass der Tiefpass nur aus zwei Werten seinen neuen berechnet. Den alten und den aktuellen Messwert. Ist eine Änderung im Messwert so klein, das Filtval sich nicht ändert, wird eine gleich große Änderung sich auch nicht in Zukunft auf Filtval auswirken. (Anm: Prinzip der Quantisierung - wer in Physik aufgepasst hat, weiß was ich meine.) Aber: eigentlich sollte sich eine noch so kleine Änderung auch auswirken dürfen, wenn sie lange genug anliegt. Das ist hier aber nicht der Fall. Egal wie oft Filtern() aufgerufen wird, 10 mal, 1000 mal, 10^10^10 mal, die Änderung wird unterschlagen. Warum? Der Grund ist die Integer-Division, die den Rest einfach abschneidet.
Beispiel:
Filterfaktor FF = 20 - ein realistischer Wert
Aktueller FiltVal = 15
NewVal = 35 - also eine Änderung um 20, das ist soviel wie der FF.
FiltVal= ((FiltVal * FF) + NewVal) / (FF +1);
(15*20 + 35)/21 = 15,95 abgerundet also immer noch 15
Insgesamt gilt: Ist | newVal - FiltVal | <= FF, wird die Änderung unterschlagen.
FF gibt also nicht nur den Zeitlichen Faktor an, sondern auch die - meistens unerwünschte - Mindeständerungs-Grenze!
Das ist, und das kann niemand bestreiten, ein Nachteil der Funktion. Dieses Problem hat der Integrierer nicht, auch mit int. Rechnet man mit double, macht das natürlich überhaupt nichts aus, und die Funktion von Gunther ist perfekt.

Was ich sagen möchte: Ich will nicht Gunthers Funktion schlecht machen, ich will nur Eigenschaften der Funktion klarstellen, die in manchen Fällen kritisch für die Funktionsweise sind. Dazu gehören der erwähnte Nachteil der Division, aber auch die Vorteile des kurzen Codes, des niedrigen RAM-Verbrauchs und die schnelle Konfigurierbarkeit.
Um die Auswirkung der "Restabschneidung" gering zu halten und trotzdem nicht mit float oder double arbeiten zu müssen, kann man übrigens einen Trick anwenden: Einfach mit Werten rechnen, die (selbst und deren Änderungen) ein vielfaches größer als FF sind. Notfalls einfach die Messwerte mit einer Konstante multiplizieren und an der Stelle, wo Filtval gebraucht wird, einfach durch die Konstante teilen. So bleibt FiltVal im Verhältnis zu FF groß und die Fehler bei der Division klein.
Edit: hier ist ein Interessanter Artikel zum Thema (englisch).

Gruß,
Marv

Templates hatte ich ganz vergessen. Das wurde sogar kürzlich hier besprochen:
http://forum.arduino.cc/index.php?topic=326712.0

Da mit einem Klassen Template, aber das geht auch mit Funktionen

Hallo,

okay, habe verstanden was Du meinst.
Ich füttere den Filter mit Integerzahlen, dass Ergebnis, der Mittelwert, wird erst später umgerechnet. Für mich passt dass wunderbar.
Ich finde aber deinen Filterfaktor 20 sehr hoch. Wenn man sich den Einheitssprung damit betrachtet kommt man erst nach 100 Werten auf 0,99x. Ich verwende um eine gemessene analoge Spannung zu glätten einen Filterfaktor von 2. Damit hätte ich nach 16 konstanten neuen Werten meinen Eingangswert am Ausgang.

Okay, es kommt auch darauf an wie stark das Eingangsignal überhaupt schwankt und wie sehr man es glätten möchte. Es kommt bestimmt immer darauf an was man machen möchte und welches Filterverfahren dafür geeignet ist. So habe ich das jetzt aufgenommen. :slight_smile: