Elektronische-Motordrosselklappe nach Funktion 6. Grades ansteuern

Hallo alle zusammen,

im Rahmen meiner Masterarbeit habe ich einen Verbrennungsmotor mit einer elektronischen Drosselklappe
ausgestattet. Die Steuerung und Regelung habe ich mit einem Arduino Mega 2560 und einem passenden
Motortreiber von Pololu bereits umgesetzt.
Die Prüfstandelektronik sendet mit eine analoges lineares Spannungssignal (SOLL-Signal) an das Arduino, das parallel
die tatsächliche Öffnung der Drosselklappe (IST-Signal) mithilfe eines Poti vergleicht. Dieses Signal ist ebenfalls
analog und linear. Die Drosselklappe wird durch diese lineare Beziehung ohne Probleme angesteuert! :slight_smile:

Jetzt zu meinem Problem:
Die Drosselklappe soll nun nicht mehr linear von 0° bis 90° geöffnet werden, sondern Progressiv. Durch Messungen
erhielt ich eine Kennlinie nach einem Polynom 6. Grades. Nur leider funktioniert jetzt leider nichts mehr und ich
habe nach etlichen Versuchen keine Idee mehr an was es liegen kann. :~

Ich komme aus dem Maschinenbau, daher war ich schon sehr froh überhaupt so weit gekommen zu sein, für einen
richtigen programmier-Pro sollte der Fehler hoffentlich leicht zu finden sein…

Vielen Dank schon einmal für eure Hilfe :slight_smile:

In der Drosselklappe ist eine Feder verbaut, die die Klappe automatisch fast vollständig schließt, daher wird
das PWM-Signal mit immer kleiner werdender Regelabweichung zurück genommen um die Drosselklappe an
einer Stelle zu halten.

Programmtext:

int
PWM_A = 8,
DIR_A = 9,
signal_soll,
signal_ist,
signal_soll_skaliert,
signal_ist_skaliert,
signal_soll_berechnet,
regelabweichung;

float
potenz6,
potenz5,
potenz4,
potenz3,
potenz2,
summand6,
summand5,
summand4,
summand3,
summand2,
summand1;

const float FACTOR6 = 3.2445916909115E-14;
const float FACTOR5 = 6.26984473115432E-11;
const float FACTOR4 = 4.14486340076514E-08;
const float FACTOR3 = 8.05562633887646E-06;
const float FACTOR2 = 0.00229807982179864;
const float FACTOR1 = 1.35441912656097;

void setup() {
pinMode(PWM_A, OUTPUT);
pinMode(DIR_A, OUTPUT); // Direction pin on channel A
pinMode(signal_soll,INPUT); // Soll-Signal
pinMode(signal_ist,INPUT); // Potenziometer Ist-Signal
}

void loop() {

signal_soll = analogRead(A3); // liest Soll-Signal ein (160 - 760 = 0.8 - 3.7V)
signal_ist = analogRead(A4); // liest Ist-Signal ein (60 - 936 = 0.30 - 4.55V)

// Signale auf gleichen Wertebereich (0 - 900) skalieren
signal_soll_skaliert = map (signal_soll, 160, 760, 0, 900);
signal_ist_skaliert = map (signal_ist, 60, 925, 0, 900);

//Rechungen für das Poly. 6. Grades
potenz6 = pow(signal_soll_skaliert,6);
potenz5 = pow(signal_soll_skaliert,5);
potenz4 = pow(signal_soll_skaliert,4);
potenz3 = pow(signal_soll_skaliert,3);
potenz2 = pow(signal_soll_skaliert,2);

summand6 = FACTOR6 * potenz6;
summand5 = FACTOR5 * potenz5;
summand4 = FACTOR4 * potenz4;
summand3 = FACTOR3 * potenz3;
summand2 = FACTOR2 * potenz2;
summand1 = FACTOR1 * signal_soll_skaliert;

// signal_soll nach Poly. 6. Grades berechnet
signal_soll_berechnet = summand6 - summand5 + summand4 - summand3 - summand2 - summand1;

/* Das Polynom in anderer Schreibweise zur verdeutlichung
signal_soll_berechnet = (0.000000000000032445916909115 * potenz6) - (0.0000000000626984473115432 * potenz5) + (0.0000000414486340076514 * potenz4)

  • (0.00000805562633887646 * potenz3) - (0.00229807982179864 * potenz2) + (1.35441912656097 * signal_soll_skaliert);
    */

//Regelabweichung aus SOLL-Wert und IST-Potistellungen berechnen
regelabweichung = signal_soll_berechnet - signal_ist_skaliert;

if ( regelabweichung < 0 )
{
if ( regelabweichung < -30 ) {
digitalWrite(DIR_A, LOW);
analogWrite(PWM_A, 80);
}
if ( regelabweichung >= -30 && regelabweichung < -20) {
digitalWrite(DIR_A, LOW);
analogWrite(PWM_A, 70);
}
if ( regelabweichung >= -20 && regelabweichung < -10) {
digitalWrite(DIR_A, LOW);
analogWrite(PWM_A, 60);
}
if ( regelabweichung >= -10 && regelabweichung < -5) {
digitalWrite(DIR_A, LOW);
analogWrite(PWM_A, 50);
}
if ( regelabweichung >= -5 && regelabweichung < 0 ) {
digitalWrite(DIR_A, LOW);
analogWrite(PWM_A, 40);
}
}

else
{
if ( regelabweichung > 30 ) {
digitalWrite(DIR_A, HIGH);
analogWrite(PWM_A, 120);
}
if ( regelabweichung <= 30 && regelabweichung > 20) {
digitalWrite(DIR_A, HIGH);
analogWrite(PWM_A, 100);
}
if ( regelabweichung <= 20 && regelabweichung > 10) {
digitalWrite(DIR_A, HIGH);
analogWrite(PWM_A, 80);
}
if ( regelabweichung <= 10 && regelabweichung > 5) {
digitalWrite(DIR_A, HIGH);
analogWrite(PWM_A, 60);
}
if ( regelabweichung <= 5 && regelabweichung >= 0 ) {
digitalWrite(DIR_A, HIGH);
analogWrite(PWM_A, 45);
}
}
}

hi,

probier' doch mal (so ganz ohne hardware dran), ob der atmega mit solchen zahlen überhaupt rechnen kann. einfach auf dem seriellen monitor ergebnisse ausgeben lassen.

gruß stefan

hey,

danke für die schnelle Antwort :)

Daran habe ich auch schon gedacht aber noch nicht getestet, probiere es am Montag gleich mal aus!

Aber theoretisch sollte es das Mega schon können oder?

Hallo,
jaaa- so 6 bis 7 Stellen nach dem Komma sollte er schon schaffen.
Gruß und Spaß
Andreas

Ich habe es mal in Visual Studio gemacht. Der Fehler kommt hier:

signal_soll_berechnet = summand6 - summand5 + summand4 - summand3 - summand2 - summand1;

Die Summanden haben Werte aber dann kommt 0 heraus. Da das floats sind, sollte er glaube ich in float rechnen und das Ergebnis dann auf int casten.

Ich habe aber auch mal probiert die Summanden auf long long zu casten (geht nur auf dem PC). Das bringt auch nichts. Verstehe nicht so recht was da abläuft.

Das hier scheint aber zu gehen:

	float val = summand6 - summand5;
	cout << val << endl;
	signal_soll_berechnet =  val + summand4 - summand3 - summand2 - summand1;
	cout << signal_soll_berechnet << endl;

Visual C++ Syntax. Nicht Arduino Code

Diese langen Zahlenkonstanten könnten dem Compiler für die 8-Bit Controller auch Probleme bereiten, sie werden aber wohl richtig in float umgewandelt . Ich habe gerade mal folgendes getestet:

Arduino Leonardo

void setup() {
  Serial.begin(9600);
}

void loop() {
  double value = 0.000000000000032445916909115;

  char buffer[40];
  
  dtostrf(value, 20, 16, buffer);  
  Serial.print(buffer);  
  
  Serial.print("   ");
  
  dtostrf(10000000.0 * value, 20, 16, buffer);
  Serial.println(buffer);

  delay(2000);
}

Gibt pro Durchlauf
0.0000000000000324 0.0000003244591700
aus.

Arduino Due

#include <stdio.h>

void setup() {
  Serial.begin(9600);
}

void loop() {
  double value = 0.000000000000032445916909115;

  char buffer[40];
  
  sprintf(buffer, "%.16e", value);
  Serial.println(buffer);    

  delay(2000);
}

Gibt 3.2445916909115000e-14 aus,

genauso wie Visual Studio mit

int _tmain(int argc, _TCHAR* argv[])
{
    double value = 0.000000000000032445916909115;

    printf_s("%.16e\r\n", value);

	return 0;
}

@ ArduFE: Also wenn ich deine Ergebnisse richtig interpretiere spuckt der DUE das richtige Ergebnis raus. der Leonardo kanns nicht?!

Das ist glaube ich das gleiche. Nur eine andere Darstellung. Der Due hat 8 Byte doubles, während float wie auf den AVR Prozessoren nur 4 Byte hat. Deshalb ist das dort genauer. Aber das sollte prinzipiell auch auf dem AVR gehen. Solche Berechnung wurden hier auch schon an anderer Stelle gezeigt.

Der Fehler tritt wie gesagt auch auf dem PC auf. Probier mal das:

float val = summand6 - summand5;
signal_soll_berechnet =  val + summand4 - summand3 - summand2 - summand1;

Damit kam in Visual C++ was raus, während das Ergebnis als komplette Rechnung 0 war. Ich habe es aber nur mit einem festen Anfangswert getestet!

Und geb an der der Stelle die Zwischenergebnisse von val und signal_soll_berechnet aus

Serenifly: Das ist glaube ich das gleiche. Nur eine andere Darstellung. Der Due hat 8 Byte doubles, während float wie auf den AVR Prozessoren nur 4 Byte hat.

Richtig. Ich wollte nur sichergehen, dass der Compiler für 8-Bit nicht die Zahlen irgendwo bei 0.000000 abschneidet.

Bit den 8-Bit Controllern sind float und double das gleiche. Mehr als die 6-7 Stellen kriegst du damit nicht hin. Brauchst du aber wahrscheinlich nicht.

Außerdem unterscheidet sich die Ausgabe. Serial.print() liefert anscheinend überall nur Fragezeichen, wenn man versucht Fliesskommawerte auszugeben. Deshalb die Funktion aus der AVR-Bibliothek, die der Due aber nicht kennt, dort geht aber sprintf, wie auf großen Computern.

Auch ich kann mich nur dem Vorschlag anschliessen, erstmal möglichst viele Zwischenergebnisse auszugeben. Optimieren kannst du später noch.

ArduFE: Serial.print() liefert anscheinend überall nur Fragezeichen, wenn man versucht Fliesskommawerte auszugeben. Deshalb die Funktion aus der AVR-Bibliothek, die der Due aber nicht kennt, dort geht aber sprintf, wie auf großen Computern.

Float geht normalerweiße mit print() :~ Es gibt da sogar extra eine Formattierung für die Anzahl der Nachkommastellen: http://arduino.cc/en/Serial/Print

dtostrf() ist ein relativ leichtgewichtiger Ersatz für die fehlende Float Unterstützung in der Standard Version von printf() (das kann zwar mit einem Compiler Schalter aktivieren, aber die Arduino IDE unterstützt das leider nicht). Das hat aber nichts mit das Print Klasse des Arduino zu tun.