Forum: Mikrocontroller und Digitale Elektronik lineare interpolation auf dem PIC


von Alexander W. (wackazong)


Lesenswert?

Hallo, ich möchte folgende Funktion in Assembler auf einem PIC18F4685 
realisieren:

Input der Funktion: zwei 8-Bit Werte a und b sowie ein 4,5 oder 6 bit 
wert i

i ist ein step counter, der mir die werte zwischen a und b in i schritte 
unterteilen soll.

Beispiel: i ist 4-bit, a ist 10, b ist 156

wenn i 0 ist ist das ergebnis 10
wenn i 15 ist ist das ergebnis 156
wenn i 7 ist ist das ergebnis ca. 73

Diese Funktion möchte ich möglichst schnell in Assembler realisieren. 
Hat hier jemand eine idee?

Danke, ALEXander.

von Yob (Gast)


Lesenswert?

Hi, habe ich dich richtig verstanden? Du möchtest einfach alle Werte 
zwischen a & b in einer bestimmten Schrittweite aufteilen. Dabei noch 
die Frage ob dir der einzel Wert reicht oder ob du alle Schritte haben 
möchtest?

Bsp: a = 30 | b = 192 | i = 7

192-30 => 162/7 => ~23+30 => 53 Endwert | reicht

oder

i = 0 |  30
i = 1 | 192
i = 2 | 111
i = 3 |  84
i = 4 |  71
i = 5 |  62
i = 6 |   .
i = 7 |   .
i = 8 |   .
.
.
.

Bzw. wie sieht genau deine Formel aus die du dem PIC bei bringen 
möchtest?
ASM dauert natürlich etwas aber ist kein problem, ich hab schon nen 
Polynomfit 2. Ordnung gemacht und der dauert bei nem 8Bit (damals 
16F876) ohne HardwareMultiplikation auch net lange.

Gruß Yob

von Alexander W. (wackazong)


Lesenswert?

Hallo,

genau, ich will alle Werte zwischen a und b in 16/32/64 schritte 
aufteilen und dann für den schritt i den entsprechenden wert haben:

Hi, habe ich dich richtig verstanden? Du möchtest einfach alle Werte
zwischen a & b in einer bestimmten Schrittweite aufteilen. Dabei noch
die Frage ob dir der einzel Wert reicht oder ob du alle Schritte haben
möchtest?

Bsp: a = 30 | b = 192 | i ist 0-15

(die werte stimmen nur ungefähr, aber es ist glaube ich klar wie das 
gemeint ist).

Berechnen kann ich das auch, aber effizient in Assembler auf dem PIC 
implementieren ist natürlich etwas anderes...

i = 0 |  30
i = 1 |  40
i = 2 |  51
i = 3 |  62
i = 4 |  72
i = 5 |  83
i = 6 |   .
i = 7 |   .
i = 8 |   .
.
.
.
i = 15 | 192


Danke und gruss, ALEXander.

PS: Hier noch eine Emailantwort von einem Kollegen zum Thema

This is a proportionality problem:

- i is delimited by 0 and 2^j-1, j=4,5 or 6 : ex. j=4 then i is between 
0 and 2^4-1=15
- x then can be calculated as follows: x = (b-a)*i / (2^j-1) + a

example : j=4, b=233, a =10

1)  i=0 ==> x=a=10
2)  i=15 ==> x=(233-10) * 15 / 15 + 10 = 223+10=233
3)  i=7 ==> x=223*7/15 + 10 = 114

Das ist schon mal gut, aber wie programmiert man das effizient in 
Assembler?

von Michael U. (amiga)


Lesenswert?

Hallo,

hab hier jetzt keinen Nerv für Formeln, aber:

Endwert (192) - Startwert (10)
einfach, Subtraktion

(Endwert-Startwert) (182) / Wertigkeit (16/32/64) -> Schrittweite,
einfach, nur Rechts schieben

Schrittweite * i (x) + Startwert -> Ergebnis.

Schrittweite * i ist recht problemlos,
Start-/Endwert für die Subtraktion und die Teilung / 16 passend 
erweitern je nach nötiger Genauigkeit und im Ergbebis dann wieder 
passend teilen.
Sind dann auch alles nur Schiebeoperationen.

Gruß aus Berlin
Michael

von Alexander W. (wackazong)


Lesenswert?

Hmm, ich versteh zwei Sachen immer noch nicht:

Wie mache ich aus einer Subtraktion eine Schiebeoperation?

Und was heisst hier erweitern?

Wäre sehr dankbar für einen Tipp,
ALEXander.

von pic (Gast)


Lesenswert?

Was ist für dich schnell, für mich brauchst du eine 16bit 
Multiplication,
um das halbwegs vernünftig abzubilden.

von Wolfgang Mües (Gast)


Lesenswert?

Nehmen wir mal an, Du willst den Weg von a nach b in 16 Schritten 
zurücklegen. Dann ist die Schrittweite ja (b-a)/16.

b-a kann man ganz einfach rechnen.
Das "durch 16" macht man etwas trickreicher:

Zunächst mal rechnet man nicht mit 8 bit Zahlen, sondern mit 16 bit. Die 
unteren 8 bit sind die Nachkommastellen. Also etwa so:

aa = a * 256;
bb = b * 256;

Damit hast Du beide Zahlen um 8 bit nach links geschoben.
Effizienter ist es in der Regel in C durch

aa = (a << 8);

Wenn Du dann die Differenz d = b-a berechnet hast,
musst Du sie auch um 8 bit schieben:

dd = (d << 8);

Und weil Du in 16 Schritten arbeiten willst, musst Du diese Differenz 
dann durch 16 teilen. Das ist wieder 4 bit nach rechts schieben:

dd = (dd >> 4);

oder in Kurzform:

dd = ((b-a) << 4);

Jetzt addierst Du in einer Schleife 16mal dd auf aa rauf, dann bist Du 
bei bb angekommen.

Der 8-bit-Wert, den Du haben willst, ist dann das obere Byte von aa, 
also
(aa >> 8);

von Yob (Gast)


Lesenswert?

Ich schließe mich Wolfgang an, nur mit dem Zusatz das du statt 256 mit 
100 multiplizieren musst.

Ich würde es so machen:

movf   a,w
subwf  b,w
mullw  D'100'

rlf    PRODL, f
bcf    PRODL, 0
btfsc  STATUS, C
incf   PRODH, f
clrf   STATUS, C   //:2

rlf    PRODL, f
bcf    PRODL, 0
btfsc  STATUS, C
incf   PRODH, f
clrf   STATUS, C   //:4

rlf    PRODL, f
bcf    PRODL, 0
btfsc  STATUS, C
incf   PRODH, f
clrf   STATUS, C   //:8

rlf    PRODL, f
bcf    PRODL, 0
btfsc  STATUS, C
incf   PRODH, f
clrf   STATUS, C   //:16

rlf    PRODL, f
bcf    PRODL, 0
btfsc  STATUS, C
incf   PRODH, f
clrf   STATUS, C  //:32

rlf    PRODL, f
bcf    PRODL, 0
btfsc  STATUS, C
incf   PRODH, f
clrf   STATUS, C  //:64

in PRODL steht dann der kleineste Schritt *100

Bsp: 156-10=146 -> 146/64= 2,28125 -> PIC-Prog => 146*100=14600 ->
     146000/64=228 (binäres rechnen) -> in PRODL = 228

dann Wert sichern und zum aufaddieren würd ich die Multiplikation wählen 
wegen 1 Cycle + sicherung und dann ne Tabelle im PIC anlegen mit den 
Table befehlen (TBLWT).

Grüße Yob

von Alexander W. (wackazong)


Lesenswert?

Das sieht doch schon mal super aus, vielen Dank.

Eine Tabelle anlegen ist auch gut, allerdings werden die a und b Werte 
sich immer mal wieder ändern, so dass ich dann die werte neu berechnen 
muss.

Vielen Dank, stürze mich gleich mal auf GPASM,
ALEXander.

von Yob (Gast)


Lesenswert?

Nuja, a & b können sich ja ruhig ändern. Ist nur die Frage wielange die 
Interpolation dauern darf.

Subtraktion
Multiplikation
Schieben
Tabelle mit allen Zwischenwerten anlegen

Denke das musst dann mal probieren & testen. Feinschliff kann man ja 
noch machen. Wenn du das kleine Programm soweit hast und alles passt, 
wärs klasse wenn du es mit in die Codesammlung posten würdest ^^. Bis 
dato helfen wir gern.

Yob.

von Alexander W. (wackazong)


Lesenswert?

Klar, mache ich dann. Jetzt werd ich erst mal testen.

Gruss, ALEXander.

von Wolfgang Mües (Gast)


Lesenswert?

Warum man allerdings die Werte mit 100 multiplizieren soll, verschließt 
sich meinem Intellekt. Vielleicht kann Yob das mal erklären? Das mit der 
notwendigen Tabelle kann er ja gleich mit erklären...

Ich schreibe mal kurz den vollständigen Algorithmus in C hier hin:

// Eingangsparameter
uint8 startwert = 10;
uint8 endwert = 150;
uint8 logstep = 4;    // fuer 2 ^ 4 == 16 steps

// Rechenwerte
uint8 steps = (0x01 << logstep);
uint16 startwert16 = startwert << 8;
uint16 increment16 = (endwert - startwert) << (8-logstep);

// Ausgabeschleife
for (; steps; steps--) {
   ausgabe(startwert16 >> 8);
   startwert16 += increment16;
}
// Ausgabe des Endwerts
ausgabe(startwert16 >> 8);


Wobei ich jetzt mal davon ausgehe, das Schieben schneller als 
Multiplizieren ist... das o.a. Assemblerprogramm läßt mich daran 
zweifeln. Also vielleicht doch lieber das "<<" durch eine Multiplikation 
ersetzen?
Die Ausgabe der oberen 8 bits des aktuellen Schleifenwerts kann man aber 
sicherlich durch Direktzugriff auf das Byte lösen. Und die 
Schiebeoperation << 8 sicherlich auch durch direktes Laden des oberen 
Bytes + Nullen des unteren Bytes.

Ich würde erst mal schauen, was der Compiler denn so aus dem Code macht.

von Alexander W. (wackazong)


Lesenswert?

Hast Du evtl vergessen, den Endwert auch zu schieben mit << 8 ? Oder am 
besten man schiebt erst die Differenz, oder?

Und wieso schiebst Du dann mit (8-logstep) nach links? was macht das "8 
-" dort?

Vielen Dank für die Aufklärung,
ALEXander.

von Wolfgang Mües (Gast)


Lesenswert?

Hallo Alexander,

genau. Man schiebt die Differenz. Deswegen ist endwert-startwert auch in 
Klammern gesetzt. Den Endwert als solchen braucht man nur in der 
Differenzberechnung.

Die "8" ist die selbe 8 wie beim Endwert und beim Startwert.
Wenn ich den Increment um 8 schieben würde, dann würde ich mit einem 
Schleifendurchlauf auf den Endwert kommen.

Wenn ich aber 16 Schleifendurchläufe haben will bis zum Endwert, dann 
darf ich bei jedem Schleifendurchlauf nur 1/16 der Differenz dazu 
addieren. Also ist der Increment = Differenz / 16. Durch 16 ist einfach 
ein Schieben nach rechts um 4 Stellen.

Wenn ich also erst 8 Stellen nach links schieben soll und dann 4 nach 
rechts, komme ich also auf

uint16 increment16 = (endwert - startwert) << (8-logstep);

Alles klar jetzt?

von Yob (Gast)


Lesenswert?

Hm, leider seh ich in deinem C-Programm nicht durch, Wolfgang. Ich muss 
allerdings dazu sagen das ich auch kein C behersche. Ich habe deinen 
ersten Post wie folgt verstanden.

Startwert = a = 10   (damit wir alle die selben Werte & Bezeichnungen 
haben)
Endwert   = b = 150  (Grundprinzip: (b-a)/i=kleinste Schrittweite [s])
Steps     = i = 16

Du wolltest mit 16-Bit Zahlen rechnen um die Zahlen für die 
Nachkommastellen mit zuberechnen und somit eine Genauer lineare 
Interpolation zuerreichen.

1. von 8Bit auf 16-Bit -> *256 also x8 nach links schieben oder einfach 
ein zweites leeres 8Bit register vor definieren.

a=10 -> 10*256=2.560|10 -> B'0000.1010' 2.560 -> 0000.1010|0000.0000=>aa
b=150 -> 150*256=38.400|150 -> B'1001.0110' 38.400-> 
1001.0110|0000.0000=>bb


2. Differenz bilden von b,a & auch 16 Bit-Wert durch x8 links schieben

150-10 = 140 -> 140*256=35.840 -> B'1000.1100|0000.0000'

3. Die Differenz durch die Anzahl der Teilschritte (Steps) durch x4 
rechts
schieben

35.8400:16=2240 -> B'0000.1000|1100.0000' -> kleinste Schrittweite
so nun kann man die kleinste Schrittweite als Dezimalwert wie folgt 
auslegen. HighByte B'0000.1000'=8 LowByte B'1100.0000'=192 => 8,192 oder 
nur 8,000.

Dieses Ergebnis ist aber "falsch" oder sehr ungenau!
Denn: 150-10=140 -> 140:16=8,75 ! Ebenso wenn man nachrechnet!

16x8,192=131,072 !

Zudem wird der 16-Bit von a&b am Anfang garnicht benötigt!

---

Bei meinem Asm-Programm bilde ich zuerst die Differenz mit den beiden 
8-Bit Werten da dies ja so vorgeben sind muss man diese nicht größer 
machen. Die Differenz multipliziere ich mit 100 damit ich einen 16-Bit 
Wert erhalte. Multiplaktion weil diese Hardwaresitig geschiet und nur 1 
Zyklus/Cycle brauch, schneller gehts nicht. Die 100 gibt auch gleich die 
Anzahl der Stellen nach dem Komma an.

150-10=140 -> 140*100=14.000 -> B'0011.0110|10110000'

Dann teile ich die Differenz durch die Anzahl der Teilschritte (16) 
durch rechtsschieben (ganz großes sry, im o.a. Code ist das schieben 
falsch, poste ich noch mal, richtig).

14.000:16=875 -> B'0000.0011|0110.1011' so kann man die 875 auf 8,75 
auflösen wenn wieder durch 100 teilt in 100er 10er 1er für eine Ausgabe 
auf einem LCD z.B. Im Nach hinein betracht geht deine Variante ebenso, 
Wolfgang wenns nicht genau sein muss.

Tabelle: Bei einer linearen Interpolation möchte man ja die ganzen 
Messwerte zwischen 2 Punkten haben und kann man in einer Tabelle im PIC 
anlegen mit den Table-Befehlen laut Datenblatt was ich gesehen habe, 
mehr war damit nicht gemeint. Ich weis nicht was Alex noch mit dem 
Werten machen möchte.

Rechtschieben richtig:
------------------------

       rrf    PRODH, f
       bcf    PRODH, 7
       btfsc  STATUS, C
       goto   n1
       rrf    PRODL, f
       bcf    PRODL, 7
       goto   n2

n1     rrf    PRODL, f
       bsf    PRODL, 7
       bcf    STATUS, C

n2


Yob.

von Wolfgang Mües (Gast)


Lesenswert?

Hallo Yob,

im Grunde genommen hast Du meinen Ansatz schon gut verstanden, nur bei 
der Betrachtung der Differenz bist Du aufs falsche Gleis geraten:

> 2. Differenz bilden von b,a & auch 16 Bit-Wert durch x8 links schieben
> 150-10 = 140 -> 140*256=35.840 -> B'1000.1100|0000.0000'

Soweit noch richtig.

> 3. Die Differenz durch die Anzahl der Teilschritte (Steps) durch x4
> rechts schieben
> 35.8400:16=2240 -> B'0000.1000|1100.0000' -> kleinste Schrittweite

Auch noch richtig.

> so nun kann man die kleinste Schrittweite als Dezimalwert wie folgt
> auslegen. HighByte B'0000.1000'=8 LowByte B'1100.0000'=192 => 8,192

Nee, das ist jetzt falsch. Das kann man so nicht behaupten. Wenn Du 
diese Zahl (2240) wieder in Dezimal umrechnen willst, musst Du sie durch 
256 teilen, also die vorherige Multiplikation wieder rückgängig machen.
Das gibt 2240/256 = 8.75. Nach 16 Schritten gibt das 8.75 x 16 = 140.

Solange Du beim Schieben nach rechts keine 1en herausschiebst, ist das 
Verfahren exakt. D.h. mit einer Erweiterung um n bits kann man eine 
Schleife über 2^n Steps ohne Genauigkeitsverlust fahren.

Tabelle: was Alex mit den Werten machen will, weiss ich nicht. Da er es 
aber möglichst schnell haben will, geht es wahrscheinlich um sofortige 
Ausgabe auf einen Port.

Gruß
Wolfgang

von Wolfgang Mües (Gast)


Lesenswert?

Alex,

wer hat Dich eigentlich getrieben, einen PIC zu nehmen?
Die CPU ist ja fürchterlich! Und mit den Assemblerbefehlen wächst mit 
jeder Zeile der Frust!

Hast Du schon mal den MSP430 von TI probiert?
Da ist die CPU und der C-Compiler so gut, dass Du in der Regel auch 
komplexe Funktionen ohne Geschwindigkeitsverlust in C realisieren 
kannst.
Für den ungeübten Anfänger ist der MSP430 um Längen besser geeignet.

von Alexander W. (wackazong)


Lesenswert?

Salü,

das ganze Projekt baut auf auf der MidiBox Hardware Plattform 
(http://www.ucapps.de). Daher.

Was will ich damit machen? Folgendes:

Ich habe an den PIC RGB-LEDS angeschlossen, die zwischen zwei Farben in 
einer bestimmten Anzahl Schritten hin und her blinken sollen. Jede Farbe 
(RGB) hat eine Tiefe von 1 Byte. Das gibt mir zwei Farbwerte von jeweils 
1 Byte (das Ganze dann drei Mal für RG und B), zwischen denen ich in 16, 
32 oder 64 Schritten wechseln will, was dann ein Blinken ergibt. Für die 
14, 30 oder 62 Zwischenwerte muss ich dann halt interpolieren, aber 
total genau braucht es nicht zu sein, lieber schnell.

MSP430 schau ich gerne mal an, danke für den Tip. C ist mir auch viel 
lieber als Assembler, wenn ich ehrlich bin.

Grüsse, ALEXander.

von Yob (Gast)


Lesenswert?

Hm, entweder seh ich es nicht oder das ganze is zusimple. Wenn du 
Schrittweite wieder durch 256 teilst ist das Ergebnis am Ende trotzdem 8 
statt 8,75 & das ganze wäre doppelter Aufwand weil man dann einfach 
gleich 140:16 rechnet in dem 4 mal schiebt, was dann auch schneller ist.

2240
1120          :2
 560          :4
 280          :8
 140          :16
  70          :32
  35          :64
  17,5 -> 17  :128
   8,5 -> 8   :256  Endergebnis = 8 (gibt ja kein Komma!)

Ich will durch 16 -> 16=2^4 -> also 4 mal >> fertig

Ob man es in C oder Asm macht sollte heute eigentlich egal sein, hoffe 
ich da die Compiler doch ausgereift sein sollten. In C hat man halt 
weniger Zeilen zuschreiben theoretisch.

Grüße Yob

von Wolfgang Mües (Gast)


Lesenswert?

Yob,

Du scheinst da einen Block zu haben. Mal sehen, ob ich ihn Dir 
transparent machen kann:

Wir haben eine Differenz, die im Bereich 0-255 liegt. Also kleine ganze 
Zahlen. Um interpolieren zu können, müssen wir die Differenz teilen (in 
unserem Beispiel durch 16 für 16 Schritte). Dadurch bekommen wir 
Nachkommastellen. Das kann man in Integer nicht mehr rechnen.

Also nehmen wir vor der Rechnung alles mit 256 mal und erzeugen uns 
damit 8 binäre Nachkommastellen.

Dazu rechnen wir den Startwert und die Differenz mal 256. Jetzt können 
wir die Differenz durch 16 teilen, ohne dass wir Genauigkeit einbüßen. 
Denn die 8 bits, die wir uns nach dem Komma verschafft haben, sind mehr 
als die 4 bits, die wir durch die notwendige Division durch 16 (wegen 
der 16 Schritte) wieder verlieren.

Jetzt machen wir die Iteration, und unser Ergebnis holen wir uns aus den 
oberen 8 bits unseres 16bit Additionsergebnisses.

Ich weiss im Moment eigentlich nicht so richtig, wo Du das 
Verständnisproblem hast. Könnte es sein, dass Du übersehen hast, dass 
die Additionen während der Interpolationen 16 bit breit sind, und der 
Nachkommaanteil immer erhalten bleibt?

Gruß
Wolfgang

von Master S. (snowman)


Lesenswert?

ich habe nicht alles genau durchgelesen, aber kommarechnen mache ich 
persönlich nur dann, wenn's wirklich nötig ist (und schlussendlich 
erwünscht). wie wär's wenn man zuerst multipliziert und dann dividiert?

1. bilde differenz (a-b) und speichere sie in einer 8bit-variable
2. multipliziere diese variable mit i -> 16bit-ergebnis
3. schiebe dieses 16bit-ergebnis
4. beachte nur die unteren 8bit dieses neun ergebnisses und addieren b 
dazu
5. diesen 8bit-wert zurück geben

das dürfte nur ein paar cyklen brauchen. ...oder habe ich einen 
denkfehler gemacht?!?

von Yob (Gast)


Lesenswert?

Ah, jetzt hats geklingelt. Ja ich habe die Addition der beiden 16-Bit 
Werte von Startwert & Differenz übersehen. Ich hatte angenommen das die 
Different wieder auf 8-Bit verringert wird. Dadurch kommt die große 
Abweichung bei der Schrittweite dann.

Deine Ergebnisse:

Addi   :256     Dezimal als Vergleich

2560  10  10,00
4800  19  18,75
7040  28  27,50
9280  37  36,25
11520  45  45,00
13760  54  53,75
16000  63  62,50
18240  72  71,25
20480  80  80,00
22720  89  88,75
24960  98  97,50
27200  107  106,25
29440  115  115,00
31680  124  123,75
33920  133  132,50
36160  142  141,25
38400  150  150,00

Joar so reichts eigentlich vollkommen aus.

Jetzt is der Knoten raus.

Gruß Yob.

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.