Vill bara berätta lite om vad jag sysslar med just nu. Jag håller på att rita på en prototyp till en produkt som ska användas i elbilar. Den ska logga ett antal DC-spänningar.
Den AVR microcontroller jag ska använda har en 12-bitars AD-omvandlare med intern referens på 2.56V. Jag kan alltså mäta exakta spänningar mellan 0 och 2.55 volt. För att kunna mäta större spänningar behövs en spänningsdelare på varje AD-ingång. Det handlar om många AD ingångar och det är inte roligt att hålla på att kalibrera dessa med en trimpot på varje ingång.

Jag använder vanliga motstånd med 5% tolerans som spänningsdelare och håller mig inom marginalen så att jag kommer under 2.56 volt in vid max U-in. Vid kalibrering ansluter jag alla ingångar till en känd stabil spänning, t.ex. 12.00 Volt. Jag låter processorn läsa av värdena och dividera det ideala värdet 12.00 V med dessa. kvoten (ett 16-bitars tal) sparas sedan i processorns EEPROM.
När jag sedan ska avläsa ett spänningsvärde multiplicerar jag det avlästa värdet med kvoten - och vips - så har jag fått fram det exakta värdet!
värdet 2.56 V motsvaras internt av heltalet 65536. Antag att spänningsdelaren är 1/16 - t.ex 150KΩ + 10KΩ ...
då är ideala 12.00 volt in = 0.75 volt på AD-ingången --> avläst värde 19200.
antag att resistorena avviker en del och spänningen in blir 0.6965 volt istället. Då kommer AD-omvandlaren läsa värdet 17808...
För att kompensera det inlätsta värdet måste jag alltså ta det och multiplicera med 19200/17808 = 1.0781671
Jag konstruerade en enkel algoritm (med bara 17 AVR-RISK instruktioner!) som skapar ett 16-bitars binaltal där MSB (bit 15) har värdet 1 och bitarna efter har värdena 0.5 - 0.25 - 0.125 - 0.0625 - osv... dvs negativa potenser på 2. Med detta tal kan jag representera allt från 0 till 1.99997 (eller 0.000 0000 0000 0000 till 1.111 1111 1111 1111 binärt) vilket duger bra i det här läget.
Division handlar ju om att subtrahera för att kolla om resultatet är positivt och i så fall skriva dit en siffra på rätt position i resultatet, och sedan ta resten och göra om subtraktionen tills du har tillräckligt många decimaler (minns divisionen från mellanstadiet)
Algoritm:

EDIT: Ovanstående flödesschema förutsätter att båda talen är väldigt höga - annars förlorar man i noggrannhet. Därför har jag lagt till en loop i början (se kod nedan) som multiplicerar upp talen till högsta möjliga 16-bitars värde innan divisionen startar. Kanske ett klumpigt sätt att ta hand om problemet på, men det var det enklaste jag kunde komma på just nu utan att göra om algoritmen helt och hållet.
Kod för AtTiny25:
Kod: Markera allt
; standardsetting
.INCLUDE "tn25def.inc" ; Attiny25
; ------------------EXEMPELTAL--------------------
; Testprogram för att testa subrutinerna binaldiv
; och binalmult
;
.EQU TALJ = 27955 ; A : Exempel på värden som
.EQU NAEMN = 31578 ; B : ska divideras.
; resultatet ska bli ca 27955/31578=0,88527
; resultatet blir i verkligheten 0,88531494140625 D
; eller 0.111 0001 0101 0010 Binärt
;-----------------------------------------------------
; definition av variabler
; Aritmetiska register
.DEF Tmp = r16
.DEF Bit= r17
.DEF Al= r18
.DEF Ah= r19
.DEF Bl= r20
.DEF Bh= r21
.DEF Cl= r22
.DEF Ch= r23
.DEF Dl= r24
.DEF Dh= r25
.CSEG
.ORG 0000
rjmp Reset
; ------------------------------------------------------------------
; Här startar huvudprogrammet ( testprogram)
;
; testprogrammet utför först en division ( D = A / B )
; och sedan en miltiplikation B * D för att återfå värdet
; som fanns i A för att jämföra med ursprungliga värdet i A.
;-------------------------------------------------------------------
.ORG 0x10
Reset:
ldi Tmp , LOW(RAMEND) ; Initiera stacken
out SPL, Tmp
;----------MAIN-------------
ldi Al,LOW(TALJ)
ldi Ah,HIGH(TALJ)
ldi Bl,LOW(NAEMN)
ldi Bh,HIGH(NAEMN)
push Al ; spara A
push Ah
push Bl ; spara B
push Bh
rcall Binaldiv ; beräkna D = A / B
; nu ska B*D bli A om det stämmer...
; testar det här nedan
pop Ah ; ta fram gamla sparade B till A
pop Al
mov Bl,Dl ; flytta resultatet D till B
mov Bh,Dh
rcall binalmult ; beräkna nu C = A * B
pop Ah ; återkalla värdet som var i A från början.
pop Al ; för att kunna jämföra med resultatet
; resultatet C ska nu likna
; innehållet i A
Slut:
rjmp slut
;---------------------SLUT PÅ MAIN-----------------------------
;-------------SUBRUTIN BINAL DIVISION ----------------------
; D = A / B ( D = Dl:Dh A = Al:Ah, B = Bl:Bh, C = Cl: Ch )
;
; division av två sextonbitars tal med ett 16-bitars decimaltal
; som resultat från 0.000000000000000 till
; 1.111111111111111 (0-1.99998)
;
;-------------------------------------------------------------------
Binaldiv:
; D behöver ej nollställas i förväg
ldi Bit, 16 ; kör 16 varv
;högsta upplösning börjar vid bit 15
shifta:
cpi Ah,128 ; kolla bit 7
brcc loop_bdiv
cpi Bh,128 ; kolla bit 7
brcc loop_bdiv
;shifta båda talen vänster
lsl Al
rol Ah
lsl Bl
rol Bh
rjmp shifta
loop_bdiv:
lsl Dl ; flytta vänster tidigare bitar i resultatregistret D
rol Dh
; kolla om B=0... KANSKE ONÖDIGT?
;cpi Al,0
;brne ej_noll
;cpi Ah,0
;breq negativ
;ej_noll:
mov Cl,Al ; C = A (vissa processorer har instruktionen movw som är 16-bitars)
mov Ch,Ah
sub Cl,Bl ; C = C - B
sbc Ch,Bh
brcs negativ ; hoppa om resultatet är negativt
; positiv
inc Dl ; sätt etta I Dl's bit 0 (denna shiftas sedan åt vänster)
mov Al,Cl ; A = C (spara resten i A)
mov Ah,Ch
negativ:
lsr Bh ; shifta höger summan i B för att jämföra med resten i A i nästa varv.
ror Bl ; (lsr flyttar LSB till Carry. ror shiftar in carry till MSB )
dec Bit ; räkna ner antal bitar kvar
brne loop_bdiv ; loopa
ret ; återvänd från subrutin
;----------SUBRUTIN BINAL MULTIPLIKATION ----------------
; här kommer multiplikationsprogrammet som behövs för att
; kunna skapa ett korrekt läsvärde av en inläst spänning.
;------------------------------------------------------------------
binalmult:
;-----------------------------------------------------
; multiplicering av 16-bitars heltalet A med
; binalen B med formatet x.xxx xxxx xxxx xxxx xxxxB
; resultatet i hamnar i C. Bit används som räknare.
; -----------------------------------------------------
clr Cl ; nollställ först C
clr Ch
ldi Bit,16 ;antal bitar multiplikation
binmul_loop:
lsl Bl ; plocka ut MSB i B till carry-
rol Bh ; flaggan.
brcc bmul_nolla ; biten var en nolla
; en etta - addera!
add Cl,Al
adc Ch,Ah
brcs bimul_overflow
bmul_nolla:
lsr Ah ; dela A med 2
ror Al
dec Bit
brne binmul_loop
ret
bimul_overflow:
; vid overflow sätts resultatet till max
; och carry = 1
ldi Ch,0xFF
ldi Cl,0xFF
ret
;--------------------------------KOD SLUT-----------------------
Processorn kan programmeras enligt följande:
1) vid reset läses en byte av i EEPROM som är satt till ett visst värde om kretsen redan är kalibrerad. Om den är det går man vidare till huvudprogrammet omedelbart.
2) om inte, skall kretsen kalibreras. Indikera t.ex. på display eller med lysdios (t.ex. blinkar) att kretsen väntar på kalibrering.
3) anslut en exakt spänningskälla på ingången (före spänningsdelaren förstås)
4) ge signal utifrån till kretsen (t.ex en knapp till en digital ingång) när spänningen är påsatt.
5) kresten läser in det analoga värdet och gör en grovkoll att värdet är ungefär rätt (annars ges ett felmeddelande)
6) kretsen dividerar sedan det "rätta" (ideala) värdet med det inlästa med ovan skrivna algoritm.
7) det 16-bitars decimaltalet lagras i EEPROM som kalibreringsvärde. Har man flera AD-ingångar går man igenom processen för varje ingång och lagrar data i EEPROM.

i huvudprogrammet läser man sedan in ett värde på ingången och multiplicerar med decimaltalet i EEPROMET. Det gör du enlkelt genom att shifta höger läsvärdet och addera (eller inte addera beroende på etta eller nolla) för varje bit man betar av på decimaltalet. Resultatet blir ett exakt kalibrerat läsvärde.
(ett binärt decimaltal kallas egentligen för binaltal, men jag har skrivit decimaltal för att man ska fatta - deci= 1/10 bi = 1/2 )
Pedagogiskt exempel på omvandling av ett decimaltal till binaltal:
Uttryck talet π med ett binärt tal med 12 binaler. π ≈3.1416
3 är heltalsdelen = 11B.
resten = 0.1416 ska delas upp i binaler:
.1416-0.5 (2^-1) (neg=0)
.1416-0.25 (2^-2) (neg=0)
.1416-0.125 (2^-3)(pos=1) = 0.0166 rest
.0166-.0625 (2^-4)(neg=0)
.0166-.03125 (2^-5)(neg=0)
.0166- 0.015625 (2^-6) (pos=1) = 0.000975 rest
.000975 - 0.0078125 (2^-7) (neg=0)
.000975 - 0.00390625 (2^-8)(neg=0) ehh.. den gubben var inte meningen 2^-8
.000975 - 0.001953125 (2^-9) (neg=0)
.000975 - 0.0009765625 (2^-10) (neg=0)
.000975 - 0.000488281250 (2^-11) (pos=1) = 0.00048671875 rest
.000486718750 - .000244140625 (2^-12) (pos=1) = 0.000242578125 rest
Svar π ≈ 11.001001000011 (avvikelse max + 2^-12 dvs. +.00025)