kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




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;
Skalierung von x nach u: Zunächst auf den Bereich 0..1 skalieren, dann um den Faktor 65535 strecken.

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