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


von Ralph S. (jjflash)


Angehängte Dateien:

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:
1
             +U
2
              ^
3
              |
4
              |
5
             +-+
6
             | |  Pullup Widerstand
7
             | |
8
             +-+
9
              |
10
              o-------> zum ADC
11
              |
12
             +-+
13
             | |  NTC
14
             | |
15
             +-+
16
              |
17
              |
18
             ---

NTCTABLE ist ein Kommandozeilenprogramm fuer Linux und erwartet 
mindestens folgende Parameter:
1
  -r value     | R25 Widerstandswert NTC
2
  -R value     | Popupwiderstand gegen Referenzspannung
3
  -b value     | Betawert NTC (B 25/85)
Optional koennen weitere Parameter angegeben werden:
1
  -a           | Sourcedatei fuer AVR-Conroller erstellen (Tabelle wird im "PROGMEM" abgelegt)
2
  -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:
https://www.ebay.de/itm/20PCS-10K-5-Thermistor-Resistor-NTC-MF52AT-10K-10K-Ohms-B-3950-1/252632407824?hash=item3ad2109f10:g:AMIAAOSwyttZyS3u

  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)


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)


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 Purzel H. (hacky)


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)


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)


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:

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)


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)


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)


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)


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)


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)


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.
1
#include <mcu/avr.h>
2
#include <array>
3
#include <cmath>
4
5
//#define V1
6
7
#ifdef V1
8
const int PROGMEM ntctable[] = {
9
  1597, 1305, 1013, 851, 735, 643, 565, 495, 
10
  430, 368, 306, 242, 175, 98, 4, -133, 
11
  -270
12
};
13
 
14
// Diese Funktion ist unsicher (index out-of-range)
15
16
int ntc_gettemp(uint16_t adc_value) {
17
  int p1 = pgm_read_word(&(ntctable[ (adc_value >> 6)    ]));
18
  int p2 = pgm_read_word(&(ntctable[ (adc_value >> 6) + 1]));
19
  return p1 - ( (p1-p2) * (adc_value & 0x003f) ) / 64;
20
}
21
22
#else 
23
24
namespace AVR::NTC {
25
    template<auto Size, typename ValueType = int, auto AdcBits = 10>
26
    struct Lut {
27
        //[  Die folgenden Definitionen sind nur convenience, kannst Du alles weglassen
28
        inline static constexpr size_t sizeMax{256}; // maximum number of intervals
29
        inline static constexpr size_t table_size = Size + 1; 
30
        static_assert(Size <= sizeMax, "lut too big");
31
32
        static_assert(AdcBits <= 16);        
33
        using adc_type = std::conditional_t<(AdcBits <= 8), uint8_t, uint16_t>;
34
        inline static constexpr adc_type adc_mask = (uint32_t)(1 << AdcBits) - 1;
35
        
36
        inline static constexpr uint8_t size_bits = log10(table_size + 0.5) / log10(2.0);
37
        static_assert(AdcBits > size_bits);
38
        inline static constexpr uint8_t adc_shift = AdcBits - size_bits;
39
        
40
        using value_type = ValueType;
41
        using size_type = std::conditional_t<(table_size <= 256), uint8_t, uint16_t>;
42
        //]
43
        static inline constexpr size_type size() {
44
            return Size;
45
        }
46
    private:
47
        // In diesem closure werden die lut-Daten berechnet: der Einfachheit halber habe ich einfach nur sin() genommen.
48
        // Alles, was Du hier berechnest, wird ausschließlich zur Compile-Zeit berechnent. 
49
        // Davon findet sich nichts im späteren Code.
50
        static constexpr auto init_data = []{
51
            std::array<value_type, table_size> t{};
52
            for(size_type i{0}; i < t.size(); ++i) {
53
                t[i] = std::numeric_limits<value_type>::max() * sin(1.1 * i); // do whatever you want
54
                t[i] = pow(t[i], 1.0);
55
            }
56
            return t;
57
        }();
58
        // Das folgende template kapselt den PGM-Zugriff
59
        template<typename> struct Pgm;
60
        template<auto... II>
61
        struct Pgm<std::index_sequence<II...>> {
62
            inline static value_type first(size_type i) {
63
                assert(i < size());
64
                return pgm_read_word(&data[i]);
65
            }
66
            inline static value_type second(size_type i) {
67
                assert(i < size());
68
                return pgm_read_word(&data[i + 1]);
69
            }
70
        private:
71
            inline static constexpr value_type data[] PROGMEM = {init_data[II]...}; 
72
        };
73
        // Dies ist der entscheidende Meta-Funktions-Aufruf, um die Pgm-Daten zu generieren.
74
        using pgm = Pgm<std::make_index_sequence<Size>>; 
75
    public:
76
        // Die korrespondierende Funktion zu Deiner ntc_gettemp() Funktion
77
        static inline value_type get(adc_type v) {
78
            static_assert((adc_mask >> adc_shift) < size());
79
            v &= adc_mask;
80
            const size_type index = v >> adc_shift;
81
            const value_type p1 = pgm::first(index);
82
            const value_type p2 = pgm::second(index);
83
            return p1 - ((p1 - p2) * v) / 64;
84
        }
85
    };
86
}
87
88
using lut = AVR::NTC::Lut<16>;
89
#endif
90
91
volatile uint16_t adc; // fake adc
92
93
int main() {
94
#ifdef V1
95
    return ntc_gettemp(adc);
96
#else
97
    return lut::get(adc);
98
#endif
99
}

von Wilhelm M. (wimalopaan)


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:
1
#include <mcu/avr.h>
2
#include <array>
3
#include <cmath>
4
5
//#define V1
6
7
#ifdef V1
8
const int PROGMEM ntctable[] = {
9
  1597, 1305, 1013, 851, 735, 643, 565, 495, 
10
  430, 368, 306, 242, 175, 98, 4, -133, 
11
  -270
12
};
13
 
14
// Diese Funktion ist unsicher (index out-of-range)
15
16
int ntc_gettemp(uint16_t adc_value) {
17
  int p1 = pgm_read_word(&(ntctable[ (adc_value >> 6)    ]));
18
  int p2 = pgm_read_word(&(ntctable[ (adc_value >> 6) + 1]));
19
  return p1 - ( (p1-p2) * (adc_value & 0x003f) ) / 64;
20
}
21
22
#else 
23
24
namespace AVR::NTC {
25
    template<auto Size, typename ValueType = int, auto AdcBits = 10>
26
    struct Lut {
27
        //[  Die folgenden Definitionen sind nur convenience, kannst Du alles weglassen
28
        inline static constexpr size_t sizeMax{256}; // maximum number of intervals
29
        inline static constexpr size_t table_size = Size + 1; 
30
        static_assert(Size <= sizeMax, "lut too big");
31
32
        static_assert(AdcBits <= 16);        
33
        using adc_type = std::conditional_t<(AdcBits <= 8), uint8_t, uint16_t>;
34
        inline static constexpr adc_type adc_mask = (uint32_t)(1 << AdcBits) - 1;
35
        
36
        inline static constexpr uint8_t size_bits = log10(table_size + 0.5) / log10(2.0);
37
        static_assert(AdcBits > size_bits);
38
        inline static constexpr uint8_t adc_shift = AdcBits - size_bits;
39
        
40
        using value_type = ValueType;
41
        using size_type = std::conditional_t<(table_size <= 256), uint8_t, uint16_t>;
42
        //]
43
        static inline constexpr size_type size() {
44
            return Size;
45
        }
46
    private:
47
        // In diesem closure werden die lut-Daten berechnet: der Einfachheit halber habe ich einfach nur sin() genommen.
48
        // Alles, was Du hier berechnest, wird ausschließlich zur Compile-Zeit berechnent. 
49
        // Davon findet sich nichts im späteren Code.
50
        inline static constexpr auto init_data = []{
51
            std::array<value_type, table_size> t{};
52
            for(size_type i{0}; i < t.size(); ++i) {
53
                t[i] = std::numeric_limits<value_type>::max() * sin(1.1 * i); // do whatever you want
54
                t[i] = pow(t[i], 1.0);
55
            }
56
            return t;
57
        }();
58
        // Das folgende template kapselt den PGM-Zugriff
59
        template<typename> struct Pgm;
60
        template<auto... II>
61
        struct Pgm<std::index_sequence<II...>> {
62
            static_assert(sizeof(value_type) <= 4);
63
            static_assert(std::is_fundamental_v<value_type>);
64
            inline static value_type first(size_type i) {
65
                assert(i < size());
66
                if constexpr(sizeof(value_type) == 1) {
67
                    return value_type{pgm_read_byte(&data[i])};
68
                }
69
                if constexpr(sizeof(value_type) == 2) {
70
                    return value_type{pgm_read_word(&data[i])};
71
                }
72
                if constexpr(sizeof(value_type) == 4) {
73
                    return value_type{pgm_read_dword(&data[i])};
74
                }
75
            }
76
            inline static value_type second(size_type i) {
77
                assert(i < size());
78
                if constexpr(sizeof(value_type) == 1) {
79
                    return value_type{pgm_read_byte(&data[i + 1])};
80
                }
81
                if constexpr(sizeof(value_type) == 2) {
82
                    return value_type{pgm_read_word(&data[i + 1])};
83
                }
84
                if constexpr(sizeof(value_type) == 4) {
85
                    return value_type{pgm_read_dword(&data[i + 1])};
86
                }
87
            }
88
        private:
89
            inline static constexpr value_type data[] PROGMEM = {init_data[II]...}; 
90
        };
91
        // Dies ist der entscheidende Meta-Funktions-Aufruf, um die Pgm-Daten zu generieren.
92
        using pgm = Pgm<std::make_index_sequence<Size>>; 
93
    public:
94
        // Die korrespondierende Funktion zu Deiner ntc_gettemp() Funktion
95
        static inline value_type get(adc_type v) {
96
            static_assert((adc_mask >> adc_shift) < size());
97
            v &= adc_mask;
98
            const size_type index = v >> adc_shift;
99
            const value_type p1 = pgm::first(index);
100
            const value_type p2 = pgm::second(index);
101
            return p1 - ((p1 - p2) * v) / 64;
102
        }
103
    };
104
}
105
106
using lut = AVR::NTC::Lut<16>;
107
#endif
108
109
volatile uint16_t adc; // fake adc
110
111
int main() {
112
#ifdef V1
113
    return ntc_gettemp(adc);
114
#else
115
    return lut::get(adc);
116
#endif
117
}

von Thorben (Gast)


Lesenswert?

Ich mach das mit dieser Tabelle, funktioniert tadellos und macht keine 
Arbeit.


http://afug-info.de/Download/tab/NTC/


https://www.youtube.com/watch?v=beVNphAA0l0

von Wilhelm M. (wimalopaan)


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 ;-) ):
1
#include <mcu/avr.h>
2
#include <array>
3
#include <cmath>
4
5
//#define V1
6
7
#ifdef V1
8
const int PROGMEM ntctable[] = {
9
  1597, 1305, 1013, 851, 735, 643, 565, 495, 
10
  430, 368, 306, 242, 175, 98, 4, -133, 
11
  -270
12
};
13
 
14
// Diese Funktion ist unsicher (index out-of-range)
15
16
int ntc_gettemp(uint16_t adc_value) {
17
  int p1 = pgm_read_word(&(ntctable[ (adc_value >> 6)    ]));
18
  int p2 = pgm_read_word(&(ntctable[ (adc_value >> 6) + 1]));
19
  return p1 - ( (p1-p2) * (adc_value & 0x003f) ) / 64;
20
}
21
22
#else 
23
24
namespace AVR::NTC {
25
    template<auto Size, typename ValueType = int, auto AdcBits = 10>
26
    struct Lut final {
27
        Lut() = delete;
28
        //[  Die folgenden Definitionen sind nur convenience, kannst Du alles weglassen
29
        inline static constexpr size_t sizeMax{256}; // maximum number of intervals
30
        inline static constexpr size_t table_size{Size + 1}; 
31
        static_assert(Size <= sizeMax, "lut too big");
32
33
        static_assert(AdcBits <= 16);        
34
        using adc_type = std::conditional_t<(AdcBits <= 8), uint8_t, uint16_t>;
35
        inline static constexpr adc_type adc_mask{(uint32_t)(1 << AdcBits) - 1};
36
        
37
        inline static constexpr uint8_t size_bits{static_cast<uint8_t>(log10(table_size + 0.5) / log10(2.0))};
38
        static_assert(AdcBits > size_bits);
39
        inline static constexpr uint8_t adc_shift{AdcBits - size_bits};
40
        
41
        using value_type = ValueType;
42
        using size_type = std::conditional_t<(table_size <= 256), uint8_t, uint16_t>;
43
        //]
44
        static inline constexpr size_type size() {
45
            return Size;
46
        }
47
    private:
48
        // In diesem closure werden die lut-Daten berechnet: der Einfachheit halber habe ich einfach nur sin() genommen.
49
        // Alles, was Du hier berechnest, wird ausschließlich zur Compile-Zeit berechnent. 
50
        // Davon findet sich nichts im späteren Code.
51
        inline static constexpr auto init_data{[]{
52
            std::array<value_type, table_size> t{};
53
            for(size_type i{0}; i < t.size(); ++i) {
54
                t[i] = std::numeric_limits<value_type>::max() * sin(1.1 * i); // do whatever you want
55
                // ...
56
            }
57
            return t;
58
        }()};
59
        // Das folgende template kapselt den PGM-Zugriff
60
        template<typename> struct Pgm;
61
        template<auto... II>
62
        struct Pgm<std::index_sequence<II...>> final {
63
            Pgm() = delete;
64
            static_assert(sizeof(value_type) <= 4); // wegen pgm_read_dword()
65
            static_assert(std::is_fundamental_v<value_type>); // UDT benötigen einen ctor, der einen PGM-ptr akzeptiert, daher hier nicht betrachtet
66
            inline static std::pair<value_type, value_type> get(size_type i) {
67
                assert(i < size());
68
                if constexpr(sizeof(value_type) == 1) {
69
                    return {pgm_read_byte(&data[i]), pgm_read_byte(&data[i + 1])};
70
                }
71
                else if constexpr(sizeof(value_type) == 2) {
72
                    return {pgm_read_word(&data[i]), pgm_read_word(&data[i + 1])};
73
                }
74
                else if constexpr(sizeof(value_type) == 4) {
75
                    return {pgm_read_dword(&data[i]), pgm_read_dword(&data[i + 1])};
76
                }
77
            }
78
        private:
79
            inline static constexpr value_type data[] PROGMEM {init_data[II]...}; 
80
        };
81
        // Dies ist der entscheidende Meta-Funktions-Aufruf, um die Pgm-Daten zu generieren.
82
        using pgm = Pgm<std::make_index_sequence<Size>>; 
83
    public:
84
        // Die korrespondierende Funktion zu Deiner ntc_gettemp() Funktion
85
        static inline value_type get(adc_type v) {
86
            static_assert((adc_mask >> adc_shift) < size());
87
            v &= adc_mask;
88
            const size_type index{static_cast<size_type>(v >> adc_shift)};
89
            const auto [p1, p2] {pgm::get(index)};
90
            return p1 - ((p1 - p2) * v) / value_type{64};
91
        }
92
    };
93
}
94
95
using lut = AVR::NTC::Lut<16>;
96
#endif
97
98
volatile uint16_t adc; // fake adc
99
100
int main() {
101
#ifdef V1
102
    return ntc_gettemp(adc);
103
#else
104
    return lut::get(adc);
105
#endif
106
}

von Ralph S. (jjflash)


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)


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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.