mikrocontroller.net

Forum: Projekte & Code Lookup Tablegenerator für NTC-Widerstände (Linux)


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Ralph S. (jjflash)


Angehängte Dateien:

Bewertung
3 lesenswert
nicht lesenswert
NTCTABLE 0.1
----------------------------------------

NTCTABLE ist ein Programm zum Genierieren von sogenannten Loopup 
Tabellen fuer NTC Temperatursensoren. NTC's haben nichtlineare 
Kennlinien die das Auswerten dieser Sensoren erschweren.

Eine mathematische Formel (die einen natuerlichen Logarithmus enthaellt) 
liegt zum Umrechnen fuer NTC's vor, jedoch ist der Speicherbedarf 
relativ gross, sodass ein Berechnen in einem kleinen Controller nicht in 
Frage kommt.

Eine Moeglichkeit NTC's auszuwerten, ist es, Abschnitte zwischen 
bestimmten Punkten ihrer Kennlinie als linear anzusehen, und Werte 
zwischen 2 Punkten zu interpolieren.

NTCTABLE generiert eine solche Tabelle und stellt die Auswertefunktion 
der Tabelle zur Verfuegung, hierbei wird angenommen, dass ein NTC 
Widerstand folgendermassen verschaltet ist:
             +U
              ^
              |
              |
             +-+
             | |  Pullup Widerstand
             | |
             +-+
              |
              o-------> zum ADC
              |
             +-+
             | |  NTC
             | |
             +-+
              |
              |
             ---

NTCTABLE ist ein Kommandozeilenprogramm fuer Linux und erwartet 
mindestens folgende Parameter:
  -r value     | R25 Widerstandswert NTC
  -R value     | Popupwiderstand gegen Referenzspannung
  -b value     | Betawert NTC (B 25/85)
Optional koennen weitere Parameter angegeben werden:
  -a           | Sourcedatei fuer AVR-Conroller erstellen (Tabelle wird im "PROGMEM" abgelegt)
  -l           | Lookup-Table mit 32 Punkten (statt 16), erzielt etwas bessere Genauigkeit

Die Ausgabe von NTCTABLE erfolgt auf dem Standardausgabegeraet (in aller 
Regel der Monitor) und kann mittels Pipe umgeleitet werden.

Bsp.:
  Datei ntc10k.c erstellen für:
Ebay-Artikel Nr. 252632407824

  NTC-Sensor 10K Ohm, B(eta)wert 3950, Pullupwiderstand 4,7 kOhm
  Ausgabe fuer ATMEL-AVR Controller
  NTC Kennlinie in 32 Stuetzpunkte unterteilt

ntctable -r 10000 -R 4700 -b 3950 -a -l > ntc10k.c

------------------------------------

Auch wenn die Kommandozeile nur 16 oder 32 Stützpunkte unterstützt, 
könnten im Programm die Stützpunkte für höhere Genauigkeit erhöht 
werden. Hier ist zu beachten, dass die Stützpunkte immer eine 2er Potenz 
sein muß, weil hierdurch ein Zugriff auf die Tabelle und ein 
Interpolieren sehr erleichtert wird (das Adressieren eines Wertes 
innerhalb der Tabelle erfolgt nur durch Schiebeoperationen).

------------------------------------

ntctable wird übersetzt mit:

gcc -lm ntctable.c -o ntctable

von HildeK (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Ich möchte deine Arbeit nicht schmälern, aber das passt doch auch dazu: 
http://www.sebulli.com/ntc/index.php?lang=de

von Ralph S. (jjflash)


Bewertung
0 lesenswert
nicht lesenswert
Irgendwann einmal hatte ich genau eine solche Seite benutzt (vllt. war 
es auch diese) und hatte nur noch die Tabelle, leider.

Also habe ich mir das rückwarts angesehen, wie ein solcher Generator 
wohl funktionieren müßte.

Also habe ich das in C geschrieben und kann das jetzt (mein 
Programmierrechner ist meist offline) auf der Konsole generieren, sehe 
die Abweichung und kann das auch gleich als Code für AVR generieren.

Hätte ich nicht geschrieben, wenn ich die Seite wieder gefunden hätte 
(grundsätzlich habe ich das für die Codesammlung für ATtiny44 - auch 
hier bei Projekte & Code gemacht, damit dort alles in einem "Paket" ist)

von Name H. (hacky)


Bewertung
0 lesenswert
nicht lesenswert
Sinnvollerweise ist der Festwiderstand gleich gross wie der NTC 
Widerstand bei der Temperatur, die interessiert.
Alternativ kann man auch ein Polynom fitten.

: Bearbeitet durch User
von HildeK (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Jetzt ist G. schrieb:
> Sinnvollerweise ist der Festwiderstand gleich gross wie der NTC
> Widerstand bei der Temperatur, die interessiert.

Wenn du einen möglichst linearen Verlauf der Teilerspannung über der 
Temperatur haben willst, also zur Temperaturmessung, dann gibt es einen 
optimalen Widerstandswert.
Bei einem 1k KTY81 habe ich den mal zu etwa 2k5 bestimmt.

Wenn du nur auf eine feste Solltemperatur regeln willst, dann magst du 
recht haben. Wahrscheinlich (hab es nicht nachgerechnet) ist dann die 
Empfindlichkeit am größten.

von Ralph S. (jjflash)


Bewertung
0 lesenswert
nicht lesenswert
Jetzt ist G. schrieb:
> Sinnvollerweise ist der Festwiderstand gleich gross wie der NTC
> Widerstand bei der Temperatur, die interessiert.

... und deshalb kann der Pullupwiderstand eben auch variieren und muß 
nicht zwangsläufig den Widerstandswert von R25 haben.

Pullup macht man immer dann gerne genausogroß, wenn man in etwa 
Zimmertemperaturen messen mag und über den am NTC abfallenden 
Spannungswert die Temperatur ermittelt (Linearisierung der Kennlinie).

Bei der Lookup Tabelle wird aber der Widerstandswert ermittelt und 
daraus die Temperatur errechnet.

Im C Programm für den PC sind Funktionen erhalten, mit denen man zu 
jeder Temperatur die Abweichung feststellen kann und man so mit dem 
Pullup "spielen" kann um zu sehen, wo der größte Fehler entstehen wird.

von Karsten B. (karstenbrandt)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

ich möchte diesen alten Thread nochmal ausgraben. Einen neuen zu 
eröffnen finde ich nicht sinnvoll, da die Fragestellung hier gut 
platziert ist.

Ich beschäftige mich auch grad mit dem Thema NTC und Look-Up-Table.
Auf der Seite

https://preis-ing.de/extras/alle-berechnungen-im-schnellzugriff/automatisches-erzeugen-einer-ntc-tabelle/?points=32&unit=0.1&resolution=10+Bit&circuit=pulldown&resistor=10000&r25=10000&beta=3480&tmin=10&tmax=80

gibt es einen LUT-Generator, der auch gut funktioniert. Ich benötige die 
Werte aber in K. Daher möchte ich den Quellcode modifizieren. Bekomme 
ich aber hin.
Mich interessiert die Berechnung des NTC-Widerstandes R(T) (=resistance 
im Java-Quellcode der Seite):
resistor bzw. Rp ist der Pull-Down-Widerstand
n  die Auflösung des ADB in Bit
max_adc = 2^n
ADC bzw. adc_v der gewandelte ADC-Wert

Es gibt zwei Gleichungen, wie der Widerstand aus dem ADC-Wert berechnet 
wird (s. Anhang):

GL. I  : Die Herleitung von mir aus der elektrotechnischen Betrachtung
GL. II : Aus dem Java-Quellcode der Seite. Für mich nicht 
nachvollziehbar und herleitbar.

Das Rechenergebnis der beiden Gleichungen sind in dem Tabellenausschnitt 
im Anhang zu sehen. Die Widerstandswerte sind identisch. Jedoch um eine 
Zeile (1 LSB) versetzt.

Den Rest des Java-Codes ist nachvollziehbar und identisch mit meinen 
Betrachtungen Zur Berechnung des Widerstandes für die 
Pull-Up-Konfiguration sowie die Temperaturberechnung aus R(T).

Kann mit jemand vielleicht erklären, wie Gleichung II zustande kommt?

Vielen Dank

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Ralph S. schrieb:
> Eine mathematische Formel (die einen natuerlichen Logarithmus enthaellt)
> liegt zum Umrechnen fuer NTC's vor, jedoch ist der Speicherbedarf
> relativ gross, sodass ein Berechnen in einem kleinen Controller nicht in
> Frage kommt.

Blödsinn: das macht man zur Compilezeit, da rechnet der µC gar nichts.

von Ralph S. (jjflash)


Bewertung
-1 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Blödsinn: das macht man zur Compilezeit, da rechnet der µC gar nichts.

Wenn du Berechnungen zur Compilezeit vornimmst und diese Werte in einem 
konstanten Array ablegst, dann hast du genau das, was hier vorliegt:

Eine Lookup-Table.

Oben erkläre ich haarklein, warum ich die Berechnungen NICHT zur 
Laufzeit vornehmen möchte.

Du zitierst mich hier:

Wilhelm M. schrieb:
> sodass ein Berechnen in einem kleinen Controller nicht in
>> Frage kommt.

und gibst mir gleichzeitig Recht während du "blödsinn" zu mir schreibst.

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Ralph S. schrieb:
> Wenn du Berechnungen zur Compilezeit vornimmst und diese Werte in einem
> konstanten Array ablegst, dann hast du genau das, was hier vorliegt:
>
> Eine Lookup-Table.

Sage ich doch. LUTs generiert man zur Compile-Zeit.

> Oben erkläre ich haarklein, warum ich die Berechnungen NICHT zur
> Laufzeit vornehmen möchte.

Genau: LUTs generiert man zur Compile-Zeit.

Irgendwie sprichst Du wirr.

Du berechnest die LUT mit einem Hilfsprogramm außerhalb der µC und 
generierst ein C-File, in dem die Werte hardcoded drin stehen.

Ich spreche dagegen davon, kein extra Tool zur Generierung des C-Files 
zu benutzen, sondern die LUT im µC-Code, aber zur Compile_zeit dieses 
Codes zur erzeugen. Vorteil: man hat alles an einer Stelle und braucht 
keine extra Tool mit extra Code.

von Ralph S. (jjflash)


Bewertung
0 lesenswert
nicht lesenswert
... mach mal!

Wenn der Präprozessorxode dann halbwegs übersichtlich ist würd ich das 
übernehmen, ansonsten erstell ich das mit dem Miniprogramm. Unterm 
Strich zählt für mich die generierte Codegröße. Aber (keine Irinie, 
keine Beleidigung): ich würde gerne einen Code von dir sehen...

von Schütze (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Wenn man die 2 Schritte nicht haben möchte, könnte man auch ein Script 
schreiben dass einem beim Aufruf des Makes einen Header generiert...

Bei CPP könnte man sich auch noch constexpr überlegen. Dann hat man 
wirklich alles zusammen im Code stehen.

von Wilhelm M. (wimalopaan)


Bewertung
2 lesenswert
nicht lesenswert
Ralph S. schrieb:
> ... mach mal!

So auf die Schnelle ... man kann es wohl noch etwas hübscher gestalten, 
doch dazu wäre ggf. mehr zusätzliche Dinge nötig. In meinem eigenen Code 
wäre nur noch eine Lambda-Expression (x-Zeilen) und die Meta-Funktion 
zur Erzeugung des PGM-Arrays (1 Zeile wie unten), der Rest ist natürlich 
allgemeingültig in einem Header.

Liefert beides denselben Code (Deine Variante ist V1).

Ok, stimmt nicht ganz: Deine ist etwas durch den Funktionsaufruf länger, 
Du könntest Sie aber auch als static inline in einer Header-Datei 
packen.

BTW: ich würde mich nicht darauf verlassen, dass adc_value immer im 
zugelassenen Wertebereich ist. Also: entweder maskieren (wie bei mir) 
oder zusichern oder einen uint_t<10> verwenden.

Allerdings sind die Daten der LUT bei meinem Code nicht wirklich 
sinnvoll. Deine Berechnung komplett zu übernehmen, war mit jetzt zu 
mühsam.

Ich weiß schon, welche Kommentare jetzt kommen:

1) Es soll doch C sein.
2) Soo viel Code.
3) Unlesbar.

Dazu:

1) Die meisten der hier vorgestellten µC-Programme kann man wohl als C 
oder C++ übersetzen. Sollte das wegen der strengeren UB-Regeln von C++ 
mal nicht möglich sein, kann man natürlich das Programm auch gemischt 
aus C/C++ zusammen setzen.

2) Man schreibt sich einmal(!) ein template zu Generierung von 
Pgm-Tabellen, packt das in einen Header und gut ist. Hier ist eben nur 
alles zu sehen, inkl. dem Closure zur Compile-Zeit-Berechnung.

3) Das kommt auf das Auge des Betrachters an.


Mit dem Boilerplate-Code erhält man eben ganz klare Sicherheits- und 
Bequemlichkeitsvorteile: die Größe der LUT ist einfach parametrierbar 
wie auch etwa die signifikanten Bits des Adc: ändert man an einer 
Schraube, so bleibt alles weitere richtig und sinnvoll.
#include <mcu/avr.h>
#include <array>
#include <cmath>

//#define V1

#ifdef V1
const int PROGMEM ntctable[] = {
  1597, 1305, 1013, 851, 735, 643, 565, 495, 
  430, 368, 306, 242, 175, 98, 4, -133, 
  -270
};
 
// Diese Funktion ist unsicher (index out-of-range)

int ntc_gettemp(uint16_t adc_value) {
  int p1 = pgm_read_word(&(ntctable[ (adc_value >> 6)    ]));
  int p2 = pgm_read_word(&(ntctable[ (adc_value >> 6) + 1]));
  return p1 - ( (p1-p2) * (adc_value & 0x003f) ) / 64;
}

#else 

namespace AVR::NTC {
    template<auto Size, typename ValueType = int, auto AdcBits = 10>
    struct Lut {
        //[  Die folgenden Definitionen sind nur convenience, kannst Du alles weglassen
        inline static constexpr size_t sizeMax{256}; // maximum number of intervals
        inline static constexpr size_t table_size = Size + 1; 
        static_assert(Size <= sizeMax, "lut too big");

        static_assert(AdcBits <= 16);        
        using adc_type = std::conditional_t<(AdcBits <= 8), uint8_t, uint16_t>;
        inline static constexpr adc_type adc_mask = (uint32_t)(1 << AdcBits) - 1;
        
        inline static constexpr uint8_t size_bits = log10(table_size + 0.5) / log10(2.0);
        static_assert(AdcBits > size_bits);
        inline static constexpr uint8_t adc_shift = AdcBits - size_bits;
        
        using value_type = ValueType;
        using size_type = std::conditional_t<(table_size <= 256), uint8_t, uint16_t>;
        //]
        static inline constexpr size_type size() {
            return Size;
        }
    private:
        // In diesem closure werden die lut-Daten berechnet: der Einfachheit halber habe ich einfach nur sin() genommen.
        // Alles, was Du hier berechnest, wird ausschließlich zur Compile-Zeit berechnent. 
        // Davon findet sich nichts im späteren Code.
        static constexpr auto init_data = []{
            std::array<value_type, table_size> t{};
            for(size_type i{0}; i < t.size(); ++i) {
                t[i] = std::numeric_limits<value_type>::max() * sin(1.1 * i); // do whatever you want
                t[i] = pow(t[i], 1.0);
            }
            return t;
        }();
        // Das folgende template kapselt den PGM-Zugriff
        template<typename> struct Pgm;
        template<auto... II>
        struct Pgm<std::index_sequence<II...>> {
            inline static value_type first(size_type i) {
                assert(i < size());
                return pgm_read_word(&data[i]);
            }
            inline static value_type second(size_type i) {
                assert(i < size());
                return pgm_read_word(&data[i + 1]);
            }
        private:
            inline static constexpr value_type data[] PROGMEM = {init_data[II]...}; 
        };
        // Dies ist der entscheidende Meta-Funktions-Aufruf, um die Pgm-Daten zu generieren.
        using pgm = Pgm<std::make_index_sequence<Size>>; 
    public:
        // Die korrespondierende Funktion zu Deiner ntc_gettemp() Funktion
        static inline value_type get(adc_type v) {
            static_assert((adc_mask >> adc_shift) < size());
            v &= adc_mask;
            const size_type index = v >> adc_shift;
            const value_type p1 = pgm::first(index);
            const value_type p2 = pgm::second(index);
            return p1 - ((p1 - p2) * v) / 64;
        }
    };
}

using lut = AVR::NTC::Lut<16>;
#endif

volatile uint16_t adc; // fake adc

int main() {
#ifdef V1
    return ntc_gettemp(adc);
#else
    return lut::get(adc);
#endif
}

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Ralph S. schrieb:
>> ... mach mal!
>
> So auf die Schnelle ...

Ups, das war zu schnell. Etwas besser, nun für alle primitiven DT:
#include <mcu/avr.h>
#include <array>
#include <cmath>

//#define V1

#ifdef V1
const int PROGMEM ntctable[] = {
  1597, 1305, 1013, 851, 735, 643, 565, 495, 
  430, 368, 306, 242, 175, 98, 4, -133, 
  -270
};
 
// Diese Funktion ist unsicher (index out-of-range)

int ntc_gettemp(uint16_t adc_value) {
  int p1 = pgm_read_word(&(ntctable[ (adc_value >> 6)    ]));
  int p2 = pgm_read_word(&(ntctable[ (adc_value >> 6) + 1]));
  return p1 - ( (p1-p2) * (adc_value & 0x003f) ) / 64;
}

#else 

namespace AVR::NTC {
    template<auto Size, typename ValueType = int, auto AdcBits = 10>
    struct Lut {
        //[  Die folgenden Definitionen sind nur convenience, kannst Du alles weglassen
        inline static constexpr size_t sizeMax{256}; // maximum number of intervals
        inline static constexpr size_t table_size = Size + 1; 
        static_assert(Size <= sizeMax, "lut too big");

        static_assert(AdcBits <= 16);        
        using adc_type = std::conditional_t<(AdcBits <= 8), uint8_t, uint16_t>;
        inline static constexpr adc_type adc_mask = (uint32_t)(1 << AdcBits) - 1;
        
        inline static constexpr uint8_t size_bits = log10(table_size + 0.5) / log10(2.0);
        static_assert(AdcBits > size_bits);
        inline static constexpr uint8_t adc_shift = AdcBits - size_bits;
        
        using value_type = ValueType;
        using size_type = std::conditional_t<(table_size <= 256), uint8_t, uint16_t>;
        //]
        static inline constexpr size_type size() {
            return Size;
        }
    private:
        // In diesem closure werden die lut-Daten berechnet: der Einfachheit halber habe ich einfach nur sin() genommen.
        // Alles, was Du hier berechnest, wird ausschließlich zur Compile-Zeit berechnent. 
        // Davon findet sich nichts im späteren Code.
        inline static constexpr auto init_data = []{
            std::array<value_type, table_size> t{};
            for(size_type i{0}; i < t.size(); ++i) {
                t[i] = std::numeric_limits<value_type>::max() * sin(1.1 * i); // do whatever you want
                t[i] = pow(t[i], 1.0);
            }
            return t;
        }();
        // Das folgende template kapselt den PGM-Zugriff
        template<typename> struct Pgm;
        template<auto... II>
        struct Pgm<std::index_sequence<II...>> {
            static_assert(sizeof(value_type) <= 4);
            static_assert(std::is_fundamental_v<value_type>);
            inline static value_type first(size_type i) {
                assert(i < size());
                if constexpr(sizeof(value_type) == 1) {
                    return value_type{pgm_read_byte(&data[i])};
                }
                if constexpr(sizeof(value_type) == 2) {
                    return value_type{pgm_read_word(&data[i])};
                }
                if constexpr(sizeof(value_type) == 4) {
                    return value_type{pgm_read_dword(&data[i])};
                }
            }
            inline static value_type second(size_type i) {
                assert(i < size());
                if constexpr(sizeof(value_type) == 1) {
                    return value_type{pgm_read_byte(&data[i + 1])};
                }
                if constexpr(sizeof(value_type) == 2) {
                    return value_type{pgm_read_word(&data[i + 1])};
                }
                if constexpr(sizeof(value_type) == 4) {
                    return value_type{pgm_read_dword(&data[i + 1])};
                }
            }
        private:
            inline static constexpr value_type data[] PROGMEM = {init_data[II]...}; 
        };
        // Dies ist der entscheidende Meta-Funktions-Aufruf, um die Pgm-Daten zu generieren.
        using pgm = Pgm<std::make_index_sequence<Size>>; 
    public:
        // Die korrespondierende Funktion zu Deiner ntc_gettemp() Funktion
        static inline value_type get(adc_type v) {
            static_assert((adc_mask >> adc_shift) < size());
            v &= adc_mask;
            const size_type index = v >> adc_shift;
            const value_type p1 = pgm::first(index);
            const value_type p2 = pgm::second(index);
            return p1 - ((p1 - p2) * v) / 64;
        }
    };
}

using lut = AVR::NTC::Lut<16>;
#endif

volatile uint16_t adc; // fake adc

int main() {
#ifdef V1
    return ntc_gettemp(adc);
#else
    return lut::get(adc);
#endif
}

von Thorben (Gast)


Bewertung
-1 lesenswert
nicht lesenswert

von Wilhelm M. (wimalopaan)


Bewertung
3 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Wilhelm M. schrieb:
>> Ralph S. schrieb:
>>> ... mach mal!
>>
>> So auf die Schnelle ...
>
> Ups, das war zu schnell.

Aller guten Dinge sind drei (vergiss den vorigen Beitrag ;-) ):
#include <mcu/avr.h>
#include <array>
#include <cmath>

//#define V1

#ifdef V1
const int PROGMEM ntctable[] = {
  1597, 1305, 1013, 851, 735, 643, 565, 495, 
  430, 368, 306, 242, 175, 98, 4, -133, 
  -270
};
 
// Diese Funktion ist unsicher (index out-of-range)

int ntc_gettemp(uint16_t adc_value) {
  int p1 = pgm_read_word(&(ntctable[ (adc_value >> 6)    ]));
  int p2 = pgm_read_word(&(ntctable[ (adc_value >> 6) + 1]));
  return p1 - ( (p1-p2) * (adc_value & 0x003f) ) / 64;
}

#else 

namespace AVR::NTC {
    template<auto Size, typename ValueType = int, auto AdcBits = 10>
    struct Lut final {
        Lut() = delete;
        //[  Die folgenden Definitionen sind nur convenience, kannst Du alles weglassen
        inline static constexpr size_t sizeMax{256}; // maximum number of intervals
        inline static constexpr size_t table_size{Size + 1}; 
        static_assert(Size <= sizeMax, "lut too big");

        static_assert(AdcBits <= 16);        
        using adc_type = std::conditional_t<(AdcBits <= 8), uint8_t, uint16_t>;
        inline static constexpr adc_type adc_mask{(uint32_t)(1 << AdcBits) - 1};
        
        inline static constexpr uint8_t size_bits{static_cast<uint8_t>(log10(table_size + 0.5) / log10(2.0))};
        static_assert(AdcBits > size_bits);
        inline static constexpr uint8_t adc_shift{AdcBits - size_bits};
        
        using value_type = ValueType;
        using size_type = std::conditional_t<(table_size <= 256), uint8_t, uint16_t>;
        //]
        static inline constexpr size_type size() {
            return Size;
        }
    private:
        // In diesem closure werden die lut-Daten berechnet: der Einfachheit halber habe ich einfach nur sin() genommen.
        // Alles, was Du hier berechnest, wird ausschließlich zur Compile-Zeit berechnent. 
        // Davon findet sich nichts im späteren Code.
        inline static constexpr auto init_data{[]{
            std::array<value_type, table_size> t{};
            for(size_type i{0}; i < t.size(); ++i) {
                t[i] = std::numeric_limits<value_type>::max() * sin(1.1 * i); // do whatever you want
                // ...
            }
            return t;
        }()};
        // Das folgende template kapselt den PGM-Zugriff
        template<typename> struct Pgm;
        template<auto... II>
        struct Pgm<std::index_sequence<II...>> final {
            Pgm() = delete;
            static_assert(sizeof(value_type) <= 4); // wegen pgm_read_dword()
            static_assert(std::is_fundamental_v<value_type>); // UDT benötigen einen ctor, der einen PGM-ptr akzeptiert, daher hier nicht betrachtet
            inline static std::pair<value_type, value_type> get(size_type i) {
                assert(i < size());
                if constexpr(sizeof(value_type) == 1) {
                    return {pgm_read_byte(&data[i]), pgm_read_byte(&data[i + 1])};
                }
                else if constexpr(sizeof(value_type) == 2) {
                    return {pgm_read_word(&data[i]), pgm_read_word(&data[i + 1])};
                }
                else if constexpr(sizeof(value_type) == 4) {
                    return {pgm_read_dword(&data[i]), pgm_read_dword(&data[i + 1])};
                }
            }
        private:
            inline static constexpr value_type data[] PROGMEM {init_data[II]...}; 
        };
        // Dies ist der entscheidende Meta-Funktions-Aufruf, um die Pgm-Daten zu generieren.
        using pgm = Pgm<std::make_index_sequence<Size>>; 
    public:
        // Die korrespondierende Funktion zu Deiner ntc_gettemp() Funktion
        static inline value_type get(adc_type v) {
            static_assert((adc_mask >> adc_shift) < size());
            v &= adc_mask;
            const size_type index{static_cast<size_type>(v >> adc_shift)};
            const auto [p1, p2] {pgm::get(index)};
            return p1 - ((p1 - p2) * v) / value_type{64};
        }
    };
}

using lut = AVR::NTC::Lut<16>;
#endif

volatile uint16_t adc; // fake adc

int main() {
#ifdef V1
    return ntc_gettemp(adc);
#else
    return lut::get(adc);
#endif
}

von Ralph S. (jjflash)


Bewertung
0 lesenswert
nicht lesenswert
Als erstes (wirklich nur als Anmerkung):

Ich trete nicht in einen Wettstreit der Programmierer ein oder bin ein 
Verfechter der "wahren Lehre" des Programmierens. 1000 Wege führen nach 
Rom (das Rom in Italien und nicht das read only memory : - ) ).

Es gibt für die meisten Programmieraufgaben viele Lösungen und es ist 
erstaunlich wie kreativ manche Problemlösungen angegangen werden (von 
naserümpfend bis genial).

@wilhelm

Deine gehört zu den interessanten.

Wilhelm M. schrieb:
> Ich weiß schon, welche Kommentare jetzt kommen:
>
> 1) Es soll doch C sein.
> 2) Soo viel Code.
> 3) Unlesbar.

zu 1.

Es ist fast egal ob das C oder C++ ist (auch wenn ich auf einem kleinen 
Mikrocontroller C++ als nicht so angenehm empfinde). Allerdings 
funktioniert der Generator auch mit dem SDCC der kein C++ kann (für 
MCS-51, STM8 und für PIC interessant).

zu 2.

Wenn etwas gut ist, funktioniert und das erzeugte Binary klein ist, ist 
die Sourcecodelänge relativ uninteressant.

zu 3.

Der Quellcode wird hier durch die Zeilenumbrüche durch die Darstellung 
von mikrocontroller.net schlechter lesbar. In meinem Codeeditor wird das 
besser.

Jeder kommt mit dem Code, den er selbst erarbeitet hat am besten 
zurecht. Unterm Strich zählt die Handhabbarkeit für denjenigen der es 
benutzt, der Vorteil deiner Lösung ist, dass es kein weiteres 
zusätzliches Tool (wie bspw. den Generator) benötigt.

----------------------------------------------------

Für Temperaturerfassungen werde ich weiterhin den Generator verwenden, 
allerdings behalte ich deinen Lösungsansatz im Kopf da ich glaube, dass 
er für andere Aufgabenstellungen eine elegante Lösung sein kann.

von Wilhelm M. (wimalopaan)


Bewertung
0 lesenswert
nicht lesenswert
Ralph S. schrieb:
> Wilhelm M. schrieb:
>> Ich weiß schon, welche Kommentare jetzt kommen:
>>
>> 1) Es soll doch C sein.
>> 2) Soo viel Code.
>> 3) Unlesbar.
>
> zu 1.
>
> Es ist fast egal ob das C oder C++ ist (auch wenn ich auf einem kleinen
> Mikrocontroller C++ als nicht so angenehm empfinde).

Wie Du sicher weißt, ist es bei mir umgekehrt ;-)

> Allerdings
> funktioniert der Generator auch mit dem SDCC der kein C++ kann (für
> MCS-51, STM8 und für PIC interessant).

Wobei Dein Generator ja nicht auf dem µC laufen soll.

Für meine C++-Lösung ist natürlich ein C++-Cross-Compiler für den 
Ziel-µC erforderlich. Da ich allerdings auch Zugriff auf den EDG habe, 
und der als Target auch C hat, kann ich via C++ -> EDG --> C --> SDCC -> 
µC-Code auch Code für µC erzeugen, für die es keinen C++-Cross-Compiler 
gibt.

> zu 2.
>
> Wenn etwas gut ist, funktioniert und das erzeugte Binary klein ist, ist
> die Sourcecodelänge relativ uninteressant.

Jein: je weniger Code man zu lesen hat, desto besser. Frei nach dem 
Motto: nur eine gelöschte Codezeile ist eine gute Codezeile.

Da helfen die Abstraktionsmöglichkeiten von C++ allerdings enorm.

> zu 3.

...

> Jeder kommt mit dem Code, den er selbst erarbeitet hat am besten
> zurecht. Unterm Strich zählt die Handhabbarkeit für denjenigen der es
> benutzt, der Vorteil deiner Lösung ist, dass es kein weiteres
> zusätzliches Tool (wie bspw. den Generator) benötigt.

Erstes das, wie Du sagst. Und die Flexibiltät bzw. Sicherheit durch 
Generizität (ist für mich der entscheidende Punkt).


> Für Temperaturerfassungen werde ich weiterhin den Generator verwenden,
> allerdings behalte ich deinen Lösungsansatz im Kopf da ich glaube, dass
> er für andere Aufgabenstellungen eine elegante Lösung sein kann.

Ich benutze das gerade auf den kleinen µC sehr gerne, etwa für meine 
BLDC-Controller, um dort LUTs für Cosinus mit den unterschiedlichsten 
Periodendauern abzulegen. Flash ist ja meistens genug vorhanden.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.