Programmiertechnik im Zusammenhang mit der Implementierung von Optimierern und Fuzzy-Reglern auf einem Mikrocontroller.
Registrierung
registrieren.zip - Download der nachfolgenden Programmbeispiele.
- Schon häufiger wurde von der Technik Gebrauch gemacht, Objekt a in anderen Objekt b zu registrieren.
- Dies diente immer dem Zweck, dass bestimmte Methoden und Attribute von a in b verfügbar gemacht werden sollten.
- Technisch besagt es nicht weiter, als dass ein Zeiger auf a in b gespeichert wird (C++), bzw. eine Referenz (Java).
- In elementarster Gestalt sieht das Registrieren folgendermaßen aus:
|
...in C++:
class A
{
public:
int holeKennung()
{
return 15678;
}
};
Code 0-1: Quellcode zu A.h
class B
{
private:
A* a_zeiger;
public:
void registriere(A* a_zeiger)
{
this->a_zeiger = a_zeiger;
}
void zeigeKennung()
{
cout<<a_zeiger->holeKennung()<<endl;
}
};
Code 0-2: Quellcode zu B.h
#include<iostream>
using namespace std;
#include "A.h"
#include "B.h"
int main()
{
A a;
B b;
b.registriere(&a);
b.zeigeKennung();
}
Code 0-3: Quellcode zu Hauptprogramm.cpp
...in Java:
public class A
{
public int holeKennung()
{
return 15678;
}
}
Code 0-4: Quellcode zu A.java
public class B
{
private A a_referenz;
public void registriere(A a_referenz)
{
this.a_referenz = a_referenz;
}
public void zeigeKennung()
{
System.out.println(a_referenz.holeKennung());
}
}
Code 0-5: Quellcode zu B.java
public class Hauptprogramm
{
public static void main(String[] args)
{
A a = new A();
B b = new B();
b.registriere(a);
b.zeigeKennung();
}
}
Code 0-6: Quellcode zu Hauptprogramm.java
Repräsentation von Fließkommavariablen als Integer-Werte auf einem Mikrocontroller
param.zip - Download des nachfolgend beschriebenen Programms.
- In dem genetischen Algorithmus zur Optimierung (evolutionärer Optimierer) der Regelparameter eines PID-Rgelers, wurden in Regelungssysteme die Parameter als Integerwerte repräsentiert und bei Bedarf wieder in double-Werte umgerechnet.
- Dort war dies geschehen, um die Bits der Speicherrepräsentation der Parameter in der Integer-Darstellung als "Gene" zu betrachten und entsprechend zu manipulieren.
- Durch die Möglichkeit, sie je nach Erfordernis einmal als Integer-Werte und ein anderes mal als double-Werte zu verarbeiten, eröffnet sich die Möglichkeit, manche Berechnungen Ressourcen-sparend mit der Integer-Zahlen-Repräsentation durchzuführen und andere, wenn es unvermeidbar ist, in der double-Wert-Repräsentation.
- Das erwähnte Optimierer-Beispiel bildet die Fließkommazahlen zudem auf den ganzen Integer-Zahlenbereich ab, wodurch eine maximale Genauigkeit erreicht wird.
- Die einheitliche Skalierung der Parameter auf den maximalen Umfang des verwendeten Integer-Datentyps erlaubt sowohl bei der Implementierung des evolutionären Optimierers, als auch bei der Implementierung des Fuzzy-Reglers Einsparungen und Vereinheitlichungen bei der Manipulation der Parameter.
- Im folgenden soll nun eine spezielle C++-Klasse für den Mikrocontroller entwickelt werden, die nur dem Zweck dient, eine solche hybride (sowohl als Integer, als auch als double-Zahl) Repräsentation von Variablen und Parametern in kontrollierter Weise zu ermöglichen.
|
Vorüberlegungen
- Durch die Verwendung der Darstellung uint16_t einer Variablen u kann auch unter C/C++ auf jeder Plattform ein gleicher Zahlenbereich garantiert werden:
- umin=0, umax=65535
- Soll eine double-Zahl x in optimaler Weise durch eine uint16_t - Zahl u repräsentiert werden, so kann dies folgendermaßen geschehen, wenn man den Minimalwert xmin und den Maximalwert xmax für x kennt:
- Transformation: u = (uint16_t)( ((x-xmin)*65535.0)/(xmax-xmin) );
- Rücktransformation: x = xmin + ((double)u*(xmax-xmin))/65535.0;
|
Bild 0-1: Skalierung von x nach u: Zunächst auf den Bereich 0..1 skalieren, dann um den Faktor 65535 strecken.
- Bei einer Optimierung und auch bei anderen Anwendungen kommt es vor, dass der Definitionsbereich (xmin, xmax) eines Parameters x zur Programmlaufzeit geändert werden muß.
- Dies kann z.B. dann der Fall sein, wenn die Rasterung eines Parameters um ein gefundenes Optimum herum weiter verfeinert werden soll, um das Optimum noch genauer zu bestimmen.
- In diesem Fall ist es notwendig, den aktuellen Wert aus der Integer-Repräsentation heraus zunächst wieder mit Hilfe der alten Grenzen xmin,xmax in eine double-Zahl zu zurück zu transformieren und im Anschluß mit Hilfe der neuen Grenzen (xmin_neu, xmax_neu) eine neue Transformation durchzuführen.
- Voraussetzung ist aber, dass auch für die neuen Grenzen gilt: xmin_neu<=x<=xmax_neu.
- Die Abfolge der Berechnungen lautet dann:
- x = xmin + ((double)u*(xmax-xmin))/65535.0;
- u = (uint16_t)( ((x-xmin_neu)*65535.0)/(xmax_neu-xmin_neu) );
|
- Im folgenden wird nun die Klasse Param entwickelt, die Parameter in obiger Weise als Integer-Repräsentation speichern kann.
- Dabei werden die mit der jeweiligen Integer-Repräsentation verbundenen double-Werte als Zeiger in einem Array registriert.
- Auf diese Weise wird eine redundante Datenhaltung und damit die Gefahr inkonsistenter Daten, zumindest für die double-Werte vermieden.
- Das Testprogramm registriert zwei double-Werte und führt eine Transformation und eine Rücktransformation aus.
- Aufgrund der Rasterung im Integer-Zahlenbereich entsteht dabei eine leichte Änderung der Parameter.
- Auf dem PC wäre es naheliegend, die maximale Anzahl der Parameter dem Konstruktor zu übergeben und den benötigten Speicher dynamisch (mit
new ) zu allokieren.
- Da aber für die Mikrocontroller-Programme keine dynamische Allokierung möglich ist, wird sie dort durch eine define-Anweisung festgelegt.
|
#define PANZ 10
class Param
{
public:
double* p[PANZ]; //Array mit Zeigern auf die registrierten double-Parameter.
double pmin[PANZ]; //Minimaler Wert jedes Parameters.
double pmax[PANZ]; //Maximaler Wert jedes Parameters.
uint16_t u[PANZ]; //Integer-Repräsentation der einzelnen Parameter.
void aktualisiereInt(int nr) //double-Repräsentation des ausgewählten Parameters auf die Integer-Rep. übertragen.
{
u[nr] = (uint16_t)( ((*p[nr]-pmin[nr])*65535.0)/(pmax[nr]-pmin[nr]) );
}
void aktualisiereDouble(int nr) //int-Repräsentation des ausgewählten Parameters auf die double-Rep. übertragen.
{
*p[nr] = pmin[nr] + ((double)u[nr]*(pmax[nr]-pmin[nr]))/65535.0;
}
void registriere(int nr,double* x, double xmin, double xmax) //Neuen Parameter merken
{
p[nr] = x;
pmin[nr] = xmin;
pmax[nr] = xmax;
aktualisiereInt(nr);
}
bool aendereDefinitionsbereich(int nr, double xmin_neu, double xmax_neu)
{
if(*p[nr]<xmin_neu || *p[nr]>xmax_neu)
return false;
pmin[nr] = xmin_neu;
pmax[nr] = xmax_neu;
aktualisiereInt(nr);
return true;
}
};
Code 0-7: Quellcode der Klasse Param.h
#include<iostream>
#include<stdint.h>
using namespace std;
#include "Param.h"
Param param;
int main()
{
double y=0.7;
double t=310.45;
cout<<"y="<<y<<endl;
cout<<"t="<<t<<endl;
param.registriere(0,&y,-1.0,1.0);
param.registriere(1,&t,0.0,1000.0);
param.aktualisiereInt(0);
param.aktualisiereInt(1);
param.aktualisiereDouble(0);
param.aktualisiereDouble(1);
cout<<"y="<<y<<endl;
cout<<"t="<<t<<endl;
}
Code 0-8: Quellcode des Programms Test.cpp
y=0.7
t=310.45
y=0.699977
t=310.445
Code 0-9: Ausgabe des Programms Test.cpp