Sida 1 av 2

Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:00:39
av bos
Jag har en PIC12F510 som har en 8-bit ADC som jag ska använda. Matningsspänningen är 5V, och då det jag ska mäta är upp till och med 12V så behöver jag skala ner mätvärdet med 2.4 (12/5) och detta gör jag med externa komponenter, så ADC-ingången får spänning inom rätt område (iallafall i teorin).

Efter att ha ritat ut problemet på papper så kom jag fram till att varje bit från ADC:n motsvarar ca. 0.02mV (5/256), men eftersom detta värde är nerskalat med konstanten 2.4 måste detta kompenseras. Ekvationen blir därför (5*24) / (256*10), vilket blir 3/64.

Koll: Mätvärde 6.42V blir 2.67V nerskalat. 2.67 / (5/256) = 136.7 = 137, vilket är det värde ADC:n kommer att avge. 137 * (3/64) = 6.42, så min tes stämmer.

MCU:n har add, sub, shift left och shift right, men ingen divisionsoperator, och det är här mitt problem dyker upp:

Kod: Markera allt

137 = 00000000 10001001
 x 3 = 00000001 10011011 (add tre gånger)
/64 = 00000000 00000110 (64 = 2^6, alltså skift höger 6 ggr)
Det blir alltså rätt volttal med hjälp av min konstant, men jag vet inte hur jag ska plocka ut decimalen. Jag nöjer mig med en decimals nogrannhet, så 6.4 räcker gott och väl.

Tips?

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:06:04
av victor_passe
gångra 137 med 10 och gör sedan samma sak.

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:08:15
av Swech
Du kan ju gångra med 30 istället för 3
Då får du 64.2 som resultat.
Sen är det "bara" att sortera ut 1 tal och 10-100 tal

Swech

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:09:43
av tecno
gångra ?

ett nytt ord eller?

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:11:04
av bearing
Gör en "8bit x 8 bit = 16 bit"-multiplikation med 120. Den höga byten kommer få resultatet 0-120, vilket kommer motsvara 0-12V med en decimal. Då slipper du skiftningen. Ifall din spänningsdelare inte stämmer riktigt, så att max på skalan egentligen motsvarar t.ex. 12,1V, är det bara att kompensera för det genom att istället multiplicera med 121.

EDIT: om din spänningsdelare är justerbar m.h.a. t.ex. trimpotentiometer skulle du ju kunna ordna så att max invärde är 12,8V, och på så sätt få resultatet i mjukvaran genom att bara skifta ADC-resultatet ett steg höger.

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:15:38
av bos
Intressanta tips!

Jag kör på *30-varianten till att börja med då den känns mest handgriplig just nu, men sen ska jag prova 8bit x 8bit.

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:17:36
av victor_passe
Nej, att multiplicera heter gångra och dra roten ur heter rotera...

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:18:21
av Swech
Gångra = att ta ett tal gånger ett annat 2*3 gångra 2 med 3...

http://sprakmy.wordpress.com/2007/09/10/gangra/
" Ett ord som jag lagt märke till de senaste veckorna är verbet gångra istället för multiplicera. Intressant och praktiskt ord, mindre krångligt än multiplicera, men mer vardagligt förstås. Ordet har faktiskt kommit med i SAOL, dock med beteckningen vardagligt. Så, ett lite kul vardagsord, men inget som passar i akademiska uppsatser precis."

Men ok då... multiplicera :D

Hmmm att ta minus.... blir det "subba" då?

Swech

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:20:14
av TomasL
Ni har fel alla, det heter ju plutteficera.

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 27 februari 2011, 22:42:41
av 4kTRB
Kan den här sidan vara till hjälp?
http://avtanski.net/projects/math/

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 28 februari 2011, 03:16:39
av bos
Assemblerkunskaperna var lite ringrostiga, men efter en stunds mnemonic-läsande blev jag varm i kläderna igen och svängde hop följande:

Kod: Markera allt

        ; The external voltage is scaled down by a 2.4 factor using a
        ; voltage divider, and then fed into the 8-bit ADC-port. After
        ; converting, the value has to be scaled up again.
        ;
        ; 8-bit ADC voltage: 5V => each bit is 5/256 (~0.02mV).
        ;
        ; Scaling up 24/10 (= 2.4), the equation becomes:
        ;     (24*5)/(10*256) = (3/64)
        ;
        ; Also, one decimal is wanted so we'll multiply the constant
        ; with 10. Thus, the resulting constant is:
        ;     (30/64) = (15/32)
        ;
        ; Test case: ADC-returns value 137. We will multiply this value
        ; with 15 and then divide it by 32. Multiply by 15 is done by
        ; shifting left 4 times (= *16) and then substracting the value
        ; once.

        ; Clear the 16-bit place holder
        clrf    BYTE_H
        clrf    BYTE_L

        ; Load the test case
        movlw   .137
        movwf   ADC_VALUE
        movwf   BYTE_L

        ; Multiply by 16
        ; The nice thing here is that if MSB of BYTE_L is set, it will
        ; go into carry, and then rotated into BYTE_H.
        rlf     BYTE_L
        rlf     BYTE_H
        rlf     BYTE_L
        rlf     BYTE_H
        rlf     BYTE_L
        rlf     BYTE_H
        rlf     BYTE_L
        rlf     BYTE_H

        ; Subtract the value itself
        movfw   ADC_VALUE
        subwf   BYTE_L

        ; If BYTE_L was smaller, then this subtraction will set
        ; the Borrow bit (inverse of Carry).
        btfss   STATUS, C
         decf  BYTE_H

        ; Multiply by 15 is done. Only thing left is shifting it right
        ; five times (= dividing by 32).
        rrf     BYTE_H
        rrf     BYTE_L
        rrf     BYTE_H
        rrf     BYTE_L
        rrf     BYTE_H
        rrf     BYTE_L
        rrf     BYTE_H
        rrf     BYTE_L
        rrf     BYTE_H
        rrf     BYTE_L

        ; BYTE_L now contains a single integer in the range 00..99, where
        ; the value is the actual measured voltage (with one decimal).
        
        ; We will now split the integer into 'tens' and 'ones'. This is
        ; achieved by subtracting 10 from the value until a Borrow occurs.
        movlw   .10
        clrf    TENS
sub10  
        subwf   BYTE_L                              ; Remove 10
        incf    TENS
        btfsc   STATUS, C                           ; Borrow?
         goto    sub10                              ; No, continue

        addwf   BYTE_L                              ; Yes, correct the values.
        decf    TENS

        ; Copy the remainder, and we're done.
        movfw   BYTE_L
        movwf   ONES

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 28 februari 2011, 04:22:13
av bearing
Ifall syftet med det här är att få variablerna TENS (som innehåller hela volt?) och ONES (innehåller decimaldelen?) verkar det som en stor omväg att först räkna ut ett ett tal som är tio gånger så stort som spänningen, och sedan i en loop dividera talet med 10 för att få fram TENS (kvoten), och till sist ONES (resten).

Multiplicera t.ex. ADC-värdet med 12 så blir höga delen TENS. Nollställ sedan höga delen och multiplicera med 10, så blir höga delen ONES.

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 28 februari 2011, 04:31:47
av bos
Intressant, och helt klart mycket snabbare än min variant (inte för att hastighet spelar någon roll i det här fallet, men likväl).

Kan du utveckla genvägen? Exempelvis hur du kom fram till den.

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 28 februari 2011, 05:29:35
av bearing
Jag betraktar ADC-resultatet som en bråkdel utav 1, eller hur man ska säga. Jag tänker att 0-256 motsvarar 0,00-1,00. Så om ADC-resultatet multipliceras med 120, kommer det ge ett tal (i höga byten) som varierar mellan 0 och 120 (eller, 0-119 egentligen, eftersom att bara 255 får plats i en byte), precis som när ett tal som varierar mellan 0 och 1 multipliceras med 120 ger ett tal som variera mellan 0 och 120.

Att först multiplicera med 12, och sedan multiplicera med 10 är ju som att multiplicera med 120, fast i två steg. När höga byten nollas efter första steget finns bara "decimaldelen" kvar, och den första av dom decimalerna tas fram genom att multiplicera med 10.

Tycker att förklaring blev lite rörig, jag hoppas att det gick fram hur jag resonerar.

Jag har även använt samma princip med AD-omvandlingar som ger fler än 8 bitar. Då har jag först sett till att AD-resultatet är 16 bitar (t.ex. med 10-bit AD genom addera 64 stycken omvandlingar för att få ett medelvärde på 64 omvandlingar), och sedan skalat om genom att multiplicera med lämplig faktor. På en microcontroller med inbyggd multiplikator är det här speciellt fiffigt, eftersom att man snabbt kan multiplicera med valfritt tal, och inte behöver hårdkoda en faktor som går att utveckla genom skiftningar för att det ska gå snabbt.

Re: Multiplikation med en decimalkonstant i 8bit-assembler

Postat: 28 februari 2011, 07:31:44
av bos
Jag förstår vad du menar men jag är inte riktigt med på hur magin bakom "gångra med 120" fungerar. Därför kan jag heller inte felsöka riktigt, vilket hade varit behövligt för nu när jag kört 5 olika testfall så får jag decimalfel på samtliga med din metod:

ADC 111: 5.2 min, 5.1 din (min 22 cykler slöare)
ADC 47: 2.2 min, 2.1 din (min 7 cykler slöare)
ADC 209: 9.7 min, 9.6 din (min 42 cykler slöare)
ADC 148: 6.9 min, 6.7 din (min 27 cykler slöare)
ADC 14: 0.6 min, 0.5 din (min 3 cykler snabbare)

Så "tyvärr" behåller jag nog min egen variant :)

Men det är ändå trevligt att se hur olika man löser saker och ting. Jag själv har generellt svårt att se och utföra genvägar, utan jag måste oftast gå den raka linjen i en top-down-algoritm utan avvikelser. Det brukar resultera i större mängd kod som - precis som du såg - kan behöva optimeras ordentligt.

(Hastigheten är för övrigt inte en relevant faktor i detta sammanhang, jag tog bara med den för skojs skull för att se hur stora skillnaderna blev)