ADC logarithmieren mit Assembler und Tiny10

#6642056
Lesenswert?

Hallo,
ich suche eine möglichst simple Lösung für folgendes Problem:
Eine Trimmerposition wird vom ADC als eine 8-Bit Zahl eingelesen. Nun 
möchte ich dabei eine log ähnliche Skalierung haben. Es geht um eine 
Timereinstellung per Trimmer, bei der ich am Anfang der Skala z.B. 3s, 
in der Mitte 30s und am Ende 300s habe. Das Ergebnis ist dann natürlich 
eine 16 bit Zahl.
Wie könnte man so etwas möglichst einfach und ressourcenarm auf einen 
Tiny 10 zusammentricksen?
Gast #6642084
Lesenswert?

Auch könnte man der Ergebnisraum in der Tabelle kompremieren, wenn man 
sich den zugehörigen Graph ansieht und resultierende Tabelle und weitere 
Tabellen unterteilt, um z.B. einen Zielbereich der ADC-Messwerte linear 
zu interpolieren.
Hier eine ganz einfache Form: f(x) = a*x + b; a,b E |R evtl. kann man 
a,b näherungsweise als z/n; z E |Z und n E |N darstellen.

Das könnte man in AVR8 Assembler einfacher Implementieren.
#6642103
Lesenswert?

Karl M. schrieb:
> Und die erste Frage ist, warum nur ein 1/2 atTiny und kein 1/1, wie ein
> atTiny85?
Weil der reicht und schön klein ist.

Karl M. schrieb:
> Hier eine ganz einfache Form: f(x) = a*x + b; a,b E |R evtl. kann man
> a,b näherungsweise als z/n; z E |Z und n E |N darstellen.
Auf einen Tiny10. ;-)

Karl M. schrieb:
> Was Du textlich beschreibst ist eher die Umkehrfunktion
> von log10(x) also exp10(x).
Schon klar. Nur die diesbezüglichen Potis werden ja auch logarithmisch 
genannt.

Aber ich sehe schon: Es wird wohl doch auf eine Tabelle hinauslaufen. 
Man kann die ja in 5er Schritten anlegen.
Ich hoffte halt auf eine schöne tricky Lösung, mit dem man eine solche 
Funktion annähern kann.
Gast #6642259
Lesenswert?

Andreas B. schrieb:
> Ich hoffte halt auf eine schöne tricky Lösung, mit dem man eine solche
> Funktion annähern kann.
In C gibt es da was von ELM-CHAN in seinem neuesten xprintf:
1
#include <math.h>
2
static int ilog10 (double n)  /* Calculate log10(n) in integer output */
3
{
4
  int rv = 0;
5

6
  while (n >= 10) {  /* Decimate digit in right shift */
7
    if (n >= 100000) {
8
      n /= 100000; rv += 5;
9
    } else {
10
      n /= 10; rv++;
11
    }
12
  }
13
  while (n < 1) {    /* Decimate digit in left shift */
14
    if (n < 0.00001) {
15
      n *= 100000; rv -= 5;
16
    } else {
17
      n *= 10; rv--;
18
    }
19
  }
20
  return rv;
21
}
22

23

24
static double i10x (int n)  /* Calculate 10^n */
25
{
26
  double rv = 1;
27

28
  while (n > 0) {    /* Left shift */
29
    if (n >= 5) {
30
      rv *= 100000; n -= 5;
31
    } else {
32
      rv *= 10; n--;
33
    }
34
  }
35
  while (n < 0) {    /* Right shift */
36
    if (n <= -5) {
37
      rv /= 100000; n += 5;
38
    } else {
39
      rv /= 10; n++;
40
    }
41
  }
42
  return rv;
43
}
Die sourcen sind auf
http://elm-chan.org/fsw/strf/xprintf.html
unter Downloads.
Wer tapfer und zäh ist, setzt sowas mit links in Assembler um :)
Wobei ich glaube, daß der ATTiny10 schon beim Einbinden der math.h 
platzt g
Gast #6642371
Lesenswert?

Andreas B. schrieb:
> Die resultierende Funktion bleibt dann aber immer noch linear

Eine 1 null mal geschoben ergibt 1, eine 1 6,6 mal geschoben ergibt 100 
mal so viel. Und das ist bestimmt nicht linear. Es bleibt allerdings 
noch etwas Skalierungsarbeit.

Christoph db1uq K. schrieb:
> Potenzreihen brauchen einen Multiplizierer, soweit ich weiß fehlt der
> dem Tiny.

Früher (tm) als die ALU nur einen Addierer erhielt, wurde eine 
Multiplikation mit ein paar Zeilen Assembler auf Additionen und 
Schieberei zurück geführt. Das sollte heutzutage auch noch funktionieren 
;-)
(Firma: Wilhelm.Consulting) #6642393
Lesenswert?

ich glaube das war jetzt der 2. hit bei google...

https://www.johndcook.com/blog/2021/03/24/log10-trick/

log10 x ≈ (x – 1)/(x + 1)

Da wird angegeben, dass du so in der größenordnung von 1/3...3 bleiben 
sollst.

damit wäre log10 ( 100 * (ADC / 100) ) nichts anderes wie 
2+log10(ADC/100)
Das dann ausmultipliziert

log10 ADC = 2+(ADC-100)/(ADC+100)

Hab das schnell mal in octave angesehen... für Werte über 20 gar nicht 
schlecht.

Wenn du also die untern 10% "abscheiden" kannst (also alles unter einem 
ADC-Wert von 20 entspricht 3s), dann wäre das eigentlich eine elegante 
Lösung.

73
#6642443
Lesenswert?

Ist zwar immer interessant, Mathetricks zu sehen, aber hier geht es doch 
um einen überschaubaren Wertebereich, der in einen ebenso überschaubaren 
Ergebnisbereich überführt werden soll. Dazu kommt noch der bescheidene 
Controller und da bleibt doch nur eine Tabelle. Selbst wenn der 
Controller für jeden neuen Wert alle Zeit der Welt hätte, würde man sich 
doch hier nicht mit großartiger Rechnerei aufhalten. Rechnen kann z.B. 
Exel :-)
Gruß Rainer
(Firma: Wilhelm.Consulting) #6642449
Lesenswert?

Dergute W. schrieb:
> Moin,
>
> Hans W. schrieb:
>> Hab das schnell mal in octave angesehen... für Werte über 20 gar nicht
>> schlecht.
>
> Mag ja sein, aber eine Division ist aufm kleinen Prozessor halt schon
> arg schlecht...
>
> Gruss
> WK

there is not such a thing as a free lunch :)

Laut der App-Note: 
https://ww1.microchip.com/downloads/en/AppNotes/doc0936.pdf kostet die 
16-bit Division gerade mal 39 Wörter und 255 Takte... Das wäre für mich 
noch i.O.

In sehr, sehr vielen Situation hast du aber ohnehin irgendwo eine 
Division.
Damit ist die Zusätzliche dann vom Platzverbrauch fast gratis.

73
Gast #6642491
Lesenswert?

Andreas B. schrieb:

> ich suche eine möglichst simple Lösung für folgendes Problem:
> Eine Trimmerposition wird vom ADC als eine 8-Bit Zahl eingelesen. Nun
> möchte ich dabei eine log ähnliche Skalierung haben.

Was spricht dagegen, einfach einen Trimmer mit logarithmischer Kennlinie 
zu verwenden?

Es gibt schließlich Leute, die so viele davon über haben, dass sie die 
sogar massenhaft für kleines Geld verkaufen müssen...
#6642566
Lesenswert?

>eine log ähnliche Skalierung
ich denke, das war die falsche Formulierung. Es geht um die 
Umkehrfunktion dazu, also eine exponentielle Funktion.
Wenn ich das Ergebnis eines 8 Bit ADC auch noch logarithmiere, um z.B. 
eine dB-Skala zu erhalten, bekomme ich eine miserable Dynamik. 8 Bit * 
6,02 dB sind 48 dB Dynamikumfang. Hier sollen aber aus 8 Bit 16 werden. 
Eine Zahl zwischen 0 und 255 soll auf 0 bis 65535 aufgebläht werden.
Gast #6642585
Lesenswert?

Christoph db1uq K. schrieb:

> ich denke, das war die falsche Formulierung. Es geht um die
> Umkehrfunktion dazu, also eine exponentielle Funktion.
> Wenn ich das Ergebnis eines 8 Bit ADC auch noch logarithmiere, um z.B.
> eine dB-Skala zu erhalten, bekomme ich eine miserable Dynamik. 8 Bit *
> 6,02 dB sind 48 dB Dynamikumfang. Hier sollen aber aus 8 Bit 16 werden.
> Eine Zahl zwischen 0 und 255 soll auf 0 bis 65535 aufgebläht werden.

Da würde ich dann eine simple Quadrierung empfehlen. Kostet auf einem 
Tiny ohne Hardware-Multiplizierer mit einer generischen Multiplikation 
in Assembler 40 Takte.

Die bestmögliche Alternative mit Lookup-Tabelle wurde bereits genannt. 
Kostet halt 0,5k Flash, der Zugriff auf einen einzelnen Wert ist dafür 
statt in 40 in (schlimmstenfalls) 7 Takten zu erledigen.
#6642721
Lesenswert?

Christoph db1uq K. schrieb:
> nein, quadratisch ansteigend, mit jedem Linksschieben verdoppelt sich
> die Zahl.
Ah, ok, da hatte ich Dich falsch verstanden. Du meinstest die Anzahl der 
Schiebeoperationen als Argument. Dann ist es exponentiell, richtig.

c-hater schrieb:
>> Zwei Dinge. Zeig mir mal einen Trimmer mit log. Kennlinie
>
> Siehe Anhang.
Das hätte ich jetzt allerdings auch nicht gedacht, daß es so etwas gibt. 
Wermutstropfen: Ich habe die Trimmer schon. Es ist auch Baugröße 3314J 
(soll ja klein sein).

Mario M. schrieb:
> Dann kann er der Tiny gleich noch durch einen '555 ersetzen und spart
> sich die Programmiererei.
Wenn es nur der Timer wäre. Aber auch das wäre ja unsportlich. ;-)

Mario M. schrieb:
> Du musst einfach folgende Formel berechnen:
Ja, der Tiny freut sich drüber. ;-)

c-hater schrieb:
> Die bestmögliche Alternative mit Lookup-Tabelle wurde bereits genannt.
> Kostet halt 0,5k Flash,
Das wäre mir schon zu teuer. Eine Tabelle in 5er Schritten reicht. Dann 
sind es nur 51 Bytes. Nur die beiden letzten Werte liegen dann über 255 
(wenn man 300s als max nimmt). Das kann man dann extra abfragen (ADC 
Wert >253) und das eine Bit im Ergebnis danach setzen. 50 Positionen bei 
einem solchen Trimmer einzustellen, muß man erst mal vormachen.

Danke an alle für die Vorschläge. Ich denke nochmal drüber nach.
Als sinnvolle Möglichkeiten sehe ich hier die altbewährte Tabelle (mit 
o.g. Trick) oder das schieben als Argument mit anschließender 
Multiplikation.

Für die Multiplikation mit Faktor 1.5 statt 2 fällt mir da gerade ein, 
daß man dazu den nach rechts geshifteten Wert addieren könnte. Ebenso 
Faktor 1.25 mit Addition des 2x geshifteten Wertes. Hmm, das eröffnet 
wieder schöne Möglichkeiten der Multiplikation....
#6642768
Lesenswert?

So, jetzt habe ich mich mal dran gemacht. Als erstes mußte ich 
feststellen, daß der Tiny10 keine Tabellen aus dem Programmspeicher 
verarbeiten kann. Den Befehl LPM haben die kleinen nicht. :-(
Also mußte die Shiftmethode her. Hier mein Elaborat:
1
; exp of ADC
2
   lsr rADCvalue
3
   lsr rADCvalue
4
   inc rADCValue
5
   ldi rDelayL, 24
6
   clr rDelayH
7
expLoop:
8
   mov YL, rDelayL
9
   mov YH, rDelayH
10
   lsr YH
11
   ror YL               ; DIV 2
12
   lsr YH
13
   ror YL               ; DIV 4
14
   lsr YH
15
   ror YL               ; DIV 8
16
   lsr YH
17
   ror YL               ; DIV 16
18
   add rDelayL, YL
19
   adc rDelayH, YH
20
   lsr YH
21
   ror YL               ; DIV 32
22
   lsr YH
23
   ror YL               ; DIV 64
24
   add rDelayL, YL
25
   adc rDelayH, YH      ; eff * 1.078125
26
   dec rADCvalue
27
   brne expLoop
28
   ror rDelayL
29
   lsr rDelayH
30
   ror rDelayL
31
   lsr rDelayH          ; DIV 4
32
   clr rTemp2
33
   ldi rTemp, 3
34
   sub rDelayL, rTemp
35
   sbc rDelayH, rTemp2  ; SUB 3
Die Grundidee ist folgende:
Der gelesene ADC  Wert wird durch 4 geteilt und eins dazugezählt. Das 
ist die Anzahl der Schleifendurchgänge.
Anfangswert des Delays ist 24.
Dann wird in jedem Schleifendurchgang Delay mit 1.078125 multipliziert. 
(1/16 und 1/64 hinzugezählt)
Das Endergebnis wird nochmal durch 4 geteilt und 3 abgezogen.
Die Werte habe ich durch herumspielen mit einer Tabelle (Anhang) 
gefunden. Als Vergleich habe ich noch die Quadrate hinzugezogen wie von 
c-hater vorgeschlagen.
Getestet und paßt (soweit ich bis jetzt sehen kann).
Angehängte Dateien:
Gast #6642999
Lesenswert?

Mein Vorschlag:
1
.EQU VALUE_00 = (3)
2
.EQU VALUE_01 = 4
3
.EQU VALUE_02 = 5
4
.EQU VALUE_03 = 6
5
.EQU VALUE_04 = 7
6
.EQU VALUE_05 = 8
7
.EQU VALUE_06 = (10)
8
.EQU VALUE_07 = 12
9
.EQU VALUE_08 = 14
10
.EQU VALUE_09 = 16
11
.EQU VALUE_10 = 18
12
.EQU VALUE_11 = (20)
13
.EQU VALUE_12 = 22
14
.EQU VALUE_13 = 25
15
.EQU VALUE_14 = (30)
16
.EQU VALUE_15 = 35
17
.EQU VALUE_16 = 40
18
.EQU VALUE_17 = 45
19
.EQU VALUE_18 = (50)
20
.EQU VALUE_19 = 55
21
.EQU VALUE_20 = 60
22
.EQU VALUE_21 = 70
23
.EQU VALUE_22 = 80
24
.EQU VALUE_23 = (100)
25
.EQU VALUE_24 = 120
26
.EQU VALUE_25 = 140
27
.EQU VALUE_26 = 160
28
.EQU VALUE_27 = 180
29
.EQU VALUE_28 = (200)
30
.EQU VALUE_29 = 220
31
.EQU VALUE_30 = 250
32
.EQU VALUE_31 = (300)
33

34
; Call-Adresse der Funktion: _ValueFromTable
35
; Parameter in Register a (zulässiger Wertebereich 0 bis 31)
36
; Ergebnis in Register YH:YL
37

38
_ValueFromTable:
39
    cpi   a, 31
40
    brne  _ValueFromTableCalculate
41
    ldi   YL, Low (VALUE_31) 
42
    ldi   YH, High(VALUE_31) 
43
    
44
    rjmp  _ValueFromTableExit
45
  
46
_ValueFromTableCalculate:
47
    ldi   YL,  VALUE_30
48
    ldi   YH, 0
49
  
50
    ldi   ZL, Low (_ValueFromTableSub)
51
    ldi   ZH, High(_ValueFromTableSub)
52
    add   ZL, a
53
    adc   ZH, YH
54
    ijmp
55

56
_ValueFromTableSub:
57
    subi  YL, VALUE_01 - VALUE_00
58
    subi  YL, VALUE_02 - VALUE_01
59
    subi  YL, VALUE_03 - VALUE_02
60
    subi  YL, VALUE_04 - VALUE_03
61
    subi  YL, VALUE_05 - VALUE_04
62
    subi  YL, VALUE_06 - VALUE_05
63
    subi  YL, VALUE_07 - VALUE_06
64
    subi  YL, VALUE_08 - VALUE_07
65
    subi  YL, VALUE_09 - VALUE_08
66
    subi  YL, VALUE_10 - VALUE_09
67
    subi  YL, VALUE_11 - VALUE_10
68
    subi  YL, VALUE_12 - VALUE_11
69
    subi  YL, VALUE_13 - VALUE_12
70
    subi  YL, VALUE_14 - VALUE_13
71
    subi  YL, VALUE_15 - VALUE_14
72
    subi  YL, VALUE_16 - VALUE_15
73
    subi  YL, VALUE_17 - VALUE_16
74
    subi  YL, VALUE_18 - VALUE_17
75
    subi  YL, VALUE_19 - VALUE_18
76
    subi  YL, VALUE_20 - VALUE_19
77
    subi  YL, VALUE_21 - VALUE_20
78
    subi  YL, VALUE_22 - VALUE_21
79
    subi  YL, VALUE_23 - VALUE_22
80
    subi  YL, VALUE_24 - VALUE_23
81
    subi  YL, VALUE_25 - VALUE_24
82
    subi  YL, VALUE_26 - VALUE_25
83
    subi  YL, VALUE_27 - VALUE_26
84
    subi  YL, VALUE_28 - VALUE_27
85
    subi  YL, VALUE_29 - VALUE_28
86
    subi  YL, VALUE_30 - VALUE_29
87

88
_ValueFromTableExit:
89
    ret
Gast #6643625
Lesenswert?

Andreas B. schrieb:

> Als Vergleich habe ich noch die Quadrate hinzugezogen wie von
> c-hater vorgeschlagen.

Die hättest du natürlich noch durch ca. 13 teilen müssen. Dann wäre dir 
vielleicht aufgefallen, dass dann ziemlich genau dasselbe rauskommt wie 
bei deiner Nachbarspalte...

Und nein, man muss nicht wirklich durch 13 teilen, um durch 13 zu 
teilen. Man kann statt dessen auch mit dem 256fachen des Kehrwertes von 
13 multiplizieren und dazu dieselbe 8x8-Multiplikation verwenden wie für 
die Quadrierung, bloß halt zweimal und die Teilergebnisse addieren...

Man könnte sogar die Gesamtperformance (bezüglich der Genauigkeit) noch 
deutlich verbessern, indem man nicht gleich zu Beginn 2 Bit des 
Messwertes wegwirft, sondern erst später ein paar Bits. Und das alles 
könnte man mit konstanter Laufzeit tun. Die obendrein besser wäre, als 
die mittlere Laufzeit deines Codes...

Aber was soll's. Wenn's reicht, dann reichts halt. Man muss nicht lernen 
wollen, wie man sowas richtig macht...
#6643639
Lesenswert?

LostInMusic schrieb:
> Mein Vorschlag:
Eine schöne Variante für eine Tabelle auf den kleinen. Muß ich mir mal 
merken. Danke!

c-hater schrieb:
> Die hättest du natürlich noch durch ca. 13 teilen müssen.
Dann schau Dir mal die Tabelle genauer an (oder schau sie Dir überhaupt 
mal an): Auf der Hälfte hat die Funktion eben nicht 10% des 
Gesamtwertes. Das bei einer quadratischen Funktion aber auch nicht 
verwunderlich.
Überdies ist die Division durch 13 bereits in der folgenden 
Tabellenspalte.
Also bitte erst gucken, dann meckern.
Die Laufzeit ist mir übrigens relativ wurscht, weil der Trimmer nur beim 
Start ausgelesen wird.
Gast #6643975
Lesenswert?

>Dann wäre dir vielleicht aufgefallen, dass dann ziemlich genau dasselbe
>rauskommt wie bei deiner Nachbarspalte...

Mir fällt eher auf, dass in der Bereichsmitte (um A = 31) die Werte in 
der der quadratisch berechneten Spalte I ungefähr um den Faktor 2.5 zu 
groß sind. Was da übereinstimmt, sind nur die Werte am Bereichsanfang 
und -ende.

Ist aber auch klar: a e^(bx) und c x^2 + d sind zwei grundsätzlich 
verschiedene Funktionen. Wenn man die erste durch die zweite annähern 
will, wird es umso falscher, je größer der Dynamikumfang im Output ist. 
Hier beträgt er zwei Zehnerpotenzen; da ist der Fehler schon 
beträchtlich.
Gast #6644269
Lesenswert?

Andreas B. schrieb:

> c-hater schrieb:
>> Die hättest du natürlich noch durch ca. 13 teilen müssen.
> Dann schau Dir mal die Tabelle genauer an (oder schau sie Dir überhaupt
> mal an): Auf der Hälfte hat die Funktion eben nicht 10% des
> Gesamtwertes.

Doch, wenn du halt das Quadrat durch dreizehn teilst. Mach's doch 
einfach mal, dann siehst du, dass dann ziemlich genau dieselben Werte 
rauskommen wie in deiner vorletzten Spalte.

Da du scheinbar unfähig bist, das selber zu tun, habe ich dir das 
einfach mal abgenommen. Siehe Anhang.
Angehängte Dateien:
#6644651
Lesenswert?

c-hater schrieb:
> Da du scheinbar unfähig bist, das selber zu tun, habe ich dir das
> einfach mal abgenommen. Siehe Anhang.

Nochmal, ganz groß für Dich: Die Division durch 13 ist in der original 
Tabelle bereits enthalten.
Und immer noch ist die quadratische Funktion erheblich anders als die 
von mir errechnete e-funktion. Auch wenn die quadratische Funktion jetzt 
doppelt in der Tabelle steht, wird sie dadurch nicht besser.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren