Område 4 · lektion 20
Kalibrering
Ett råvärde är ett tal mellan 0 och 1023. Det är inte en temperatur, inte en ljusstyrka och inte ett avstånd – det är bara ett tal. Den här guiden handlar om steget från talet till en storhet med enhet, och om vad som kan gå fel på vägen. Tre nya saker i språket kommer in här, och alla tre för att mätningen kräver dem: loopen, arrayen och din första egna funktion.
Mål
- Du kan lägga en rät linje genom två uppmätta punkter och räkna om ett råvärde till en storhet med enhet.
- Du kan skriva en egen funktion med returtyp och parameter, och säga varför omvandlingen ska ligga i en funktion och inte upprepas.
- Du kan förklara vad
map()gör med decimalerna och vad den gör med värden utanför sitt område, och rätta båda felen. - Du kan skilja ett slumpmässigt fel från ett systematiskt, veta vilket av dem ett medelvärde tar bort, och mäta bruset i ditt eget bygge.
Genomgång
Två punkter räcker
En kalibrering är ett svar på frågan: vilken storhet svarar det här råvärdet mot? Svaret får du genom att ställa din givare bredvid något du litar på – en referens – läsa av båda samtidigt, och skriva ned paret. Gör det två gånger vid två olika värden, så har du två punkter. Genom två punkter går exakt en rät linje.
Linjen genom punkterna (x1, y1) och (x2, y2)
har lutningen
k = (y2 − y1) / (x2 − x1)
och skrivs
y = y1 + k · (x − x1)
Här är x råvärdet och y storheten. Ingen
matematik utöver det behövs.
Räknat exempel. En NTC i en delare, avläst mot en referenstermometer på två temperaturer:
| Punkt | Referensen visade | Råvärdet var |
|---|---|---|
| 1 | 20,0 °C | 569 |
| 2 | 30,0 °C | 456 |
Lutningen blir
k = (30,0 − 20,0) / (456 − 569) = 10,0 / (−113) = −0,0885 °C per steg
Minustecknet är väntat: NTC:ns resistans faller när det blir varmare, så råvärdet faller också. Linjen blir
T = 20,0 + (−0,0885) · (rå − 569)
Kontrollera den mot punkt 2 innan du går vidare:
T = 20,0 − 0,0885 · (456 − 569) = 20,0 − 0,0885 · (−113) = 20,0 + 10,0 = 30,0 °C
Och prova ett värde mitt emellan, rå = 512:
T = 20,0 − 0,0885 · (512 − 569) = 20,0 + 5,04 = 25,0 °C
Hur långt utanför punkterna linjen håller
NTC:ns verkliga samband är ingen rät linje – det såg du i förra guiden. Linjen är alltså en approximation, och frågan är hur bra. Tabellen nedan jämför tvåpunktslinjen med de råvärden en NTC på 10 kΩ i en 10 kΩ-delare faktiskt ger, räknade ur databladet.
| Verklig temperatur | Råvärde | Linjen säger | Fel |
|---|---|---|---|
| 0 °C | 788 | 0,6 °C | +0,6 |
| 10 °C | 684 | 9,8 °C | −0,2 |
| 15 °C | 627 | 14,9 °C | −0,1 |
| 20 °C | 569 | 20,0 °C | kalibreringspunkt |
| 25 °C | 512 | 25,0 °C | 0,0 |
| 30 °C | 456 | 30,0 °C | kalibreringspunkt |
| 35 °C | 403 | 34,7 °C | −0,3 |
| 40 °C | 354 | 39,0 °C | −1,0 |
| 50 °C | 270 | 46,5 °C | −3,5 |
Läs tabellen nedifrån och upp. Tjugo grader utanför kalibreringsområdet är linjen 3,5 grader fel. Tio grader utanför är den en grad fel. Inuti området, mellan 20 och 30, är felet mindre än en tjugondels grad.
Regeln som följer: lägg dina två punkter i vardera änden av det område du faktiskt ska mäta i, inte där det är bekvämt att mäta. Ska instrumentet visa rumstemperatur räcker kranvatten och kroppsvärme. Ska det visa ugnstemperatur duger de inte alls.
- Den heldragna kurvan är vad givaren faktiskt gör, räknad ur databladets tabell.
- Den streckade linjen är din tvåpunktskalibrering.
- De två punkterna är där du mätte. Där stämmer linjen per definition exakt.
- Avståndet mellan linjen och kurvan är felet, och det växer åt båda hållen bort från punkterna. Det är samma tal som står i tabellen ovan.
Din första egna funktion
Omvandlingen från råvärde till grader behövs på mer än ett ställe. Redan i nästa guide ska samma tal både skrivas ut på seriemonitorn och styra ett servo, och att skriva samma formel två gånger är att skapa två ställen att rätta när kalibreringen ändras. Det är precis det en funktion löser.
I Python skrev du def ravarde_till_grader(ravarde): och lät
tolken lista ut resten. C++ vill veta två saker till: vad funktionen
lämnar tillbaka och vad varje parameter är för sorts värde.
// Kalibreringen, uppmätt mot en referenstermometer. Byt mot dina egna tal.
const int RA_VID_20 = 569; // ravardet da referensen visade 20,0 grader
const int RA_VID_30 = 456; // ravardet da referensen visade 30,0 grader
const float TEMP_20 = 20.0;
const float TEMP_30 = 30.0;
float ravardeTillGrader(int ravarde) {
float k = (TEMP_30 - TEMP_20) / (RA_VID_30 - RA_VID_20);
return TEMP_20 + k * (ravarde - RA_VID_20);
}
Rad för rad:
floatallra först är returtypen – vad anropet lämnar tillbaka. En temperatur har decimaler, alltsåfloat. Hade funktionen inte lämnat något alls hade det ståttvoid, som ivoid setup().ravardeTillGraderär namnet. Döp funktioner efter vad de gör, inte efter vad de innehåller.(int ravarde)är parametern, med sin typ. Inuti funktionen ärravardeen helt vanlig variabel som innehåller en kopia av det du skickade in. Ändrar du den händer ingenting utanför.returnskickar tillbaka värdet och avslutar funktionen på stället. Allt efter enreturnsom körs blir aldrig kört.
Anropet ser ut som vilket annat anrop som helst:
int ravarde = analogRead(A0);
float grader = ravardeTillGrader(ravarde);
Kontrollera typerna en gång, för det är där nybörjarfelen sitter.
TEMP_30 - TEMP_20 är 10.0, ett flyttal.
RA_VID_30 - RA_VID_20 är -113, ett heltal. När
ett flyttal delas med ett heltal räknar C++ i flyttal, så k
blir -0.0885 och inte 0. Hade båda talen
varit heltal hade svaret blivit 0, och funktionen hade lämnat
20,0 vad du än matade in. Det är områdets vanligaste tysta fel.
Var funktionen ska stå
Skriv dina egna funktioner ovanför setup().
Arduino-miljön är visserligen snäll och klarar oftast att du skriver dem
längst ned, men i vanlig C++ måste en funktion vara känd innan den
används. Att alltid lägga dem överst är en vana som fungerar i båda
världarna och kostar ingenting.
map() och vad den gör med decimalerna
map() är Arduinos färdiga omskalning. Den räknar precis det
här:
ut = (x − in_min) · (ut_max − ut_min) / (in_max − in_min) + ut_min
Alltså exakt samma räta linje som ovan, uttryckt med två punkter i varje ände i stället för med lutning. Bekvämt – med en enda hake, och den är stor.
map() räknar i heltal. Alla fem talen är
heltal och divisionen är en heltalsdivision, som kapar decimalerna i
stället för att avrunda dem. Räkna igenom det klassiska exemplet:
map(512, 0, 1023, 0, 5)
= (512 − 0) · (5 − 0) / (1023 − 0) + 0 = 512 · 5 / 1023 = 2560 / 1023 = 2,50…
och heltalsdivisionen kapar det till
2
Inte 2,5. Utsignalen kan bara bli 0, 1, 2, 3, 4 eller 5 – sex möjliga värden ur 1024 möjliga insignaler. Nästan hela din upplösning kastades bort på en rad som såg rimlig ut.
Ett annat kort kan svara 3 På ditt Uno-kort blir svaret 2, varje gång – Arduino-kärnan för AVR trunkerar. Kör raden själv och se det, det står som första steg i Prova själv nedan. Byter du senare till ett ESP32-kort blir samma rad 3: den kärnan lägger till halva nämnaren före divisionen och avrundar därmed i stället för att kapa. Poängen är densamma i båda fallen: 2,5 finns inte bland de möjliga svaren, och ett svar som inte går jämnt ut kan skilja ett steg mellan två kärnor. Räkningarna längre ned i guiden är valda så att de går jämnt ut och därför ger samma svar på båda.
Två utvägar finns, och båda används. Den första är att skala till en finare enhet i stället för till en grov:
map(512, 0, 1023, 0, 5000) = 512 · 5000 / 1023 = 2 560 000 / 1023 = 2502
2502 millivolt, alltså 2,502 V. Nu ryms hela upplösningen, eftersom
utområdet har fler steg än inområdet. Regeln:
map() är bra så länge utområdet är minst lika stort som
inområdet.
Den andra utvägen är att inte använda map() alls när svaret
ska ha decimaler, utan räkna med float som i funktionen ovan.
För en temperatur i grader är det nästan alltid rätt val. För en
servovinkel i hela grader är map() rätt val, eftersom svaret
ändå ska bli ett heltal.
map() begränsar ingenting
Den andra hälften av fällan är att map() inte bryr sig om
sina egna gränser. Ligger insignalen utanför in_min … in_max
räknar formeln glatt vidare och lämnar ett svar utanför
ut_min … ut_max.
Anta att du kalibrerat ditt instrument på råvärdena 200 till 800 och mappar dem till en servovinkel 0 till 180 grader. En dag blir det varmare än du någonsin mätte, och råvärdet blir 900:
map(900, 200, 800, 0, 180) = (900 − 200) · 180 / (800 − 200) = 700 · 180 / 600 = 126 000 / 600 = 210
Tvåhundratio grader. Åt andra hållet, med råvärdet 100:
map(100, 200, 800, 0, 180) = (100 − 200) · 180 / 600 = −18 000 / 600 = −30
Minus trettio grader. Vad som händer sedan beror på vad du matar talet till, och inget av alternativen är bra:
- Till ett servo: biblioteket klipper visserligen vinkeln till 0–180, så servot går inte sönder. Men visaren står blickstilla i sitt ändläge och visar 180 för varje värde över området. Instrumentet ljuger tyst, och du kan inte se på visaren om det är 35 grader eller 60.
- Till en display eller till
Serial: talet skrivs ut precis som det är. "Temperatur: 63 grader" i ett rum där det är 24. - Till ett villkor: ett larm som ska gå vid en gräns kan slå till eller utebli helt fel, och felet syns bara i det ovanliga fallet – alltså precis då det spelar roll.
constrain() är motmedlet. Den tar ett värde och två gränser
och lämnar värdet klippt till intervallet:
const int RA_MIN = 200; // ravardet vid kalibreringens ena ande
const int RA_MAX = 800; // ravardet vid den andra
int ravarde = analogRead(A0);
int begransat = constrain(ravarde, RA_MIN, RA_MAX);
int vinkel = map(begransat, RA_MIN, RA_MAX, 0, 180);
constrain(900, 200, 800) ger 800, och vinkeln blir då 180 –
samma slutresultat som servobiblioteket ändå hade gett, men nu
med ditt beslut i din kod i stället för i ett bibliotek
vars beteende du hade fått slå upp.
Bättre än att bara klippa är att klippa och säga till. Riktiga
instrument gör det: en multimeter visar OL, en våg blinkar.
Ditt bygge kan göra samma sak med en rad:
if (ravarde > RA_MAX || ravarde < RA_MIN) {
Serial.println("utanfor omradet");
}
Brus: en enda avläsning darrar
Läs samma stillastående givare sexton gånger i rad och du får sällan sexton lika tal. Typiskt kan det se ut så här:
511 513 512 510 514 512 511 513
Det är brus: små slumpmässiga störningar från omvandlaren själv, från matningen och från allt elektriskt i rummet. Det går inte att koppla bort. Men det går att räkna bort, eftersom bruset lika gärna drar uppåt som nedåt: lägger du ihop många avläsningar tar avvikelserna delvis ut varandra.
Regeln är att medelvärdet av N prov har ett slumpmässigt fel som är √N gånger mindre än en enskild avläsnings. Med sexton prov:
√16 = 4, alltså en fjärdedel av bruset
Att komma ned ytterligare en fjärdedel kräver sexton gånger fler prov:
√256 = 16, och 256 · 104 µs = 26 600 µs = 27 ms
Sexton prov kostar däremot bara
16 · 104 µs = 1 664 µs = 1,7 ms
Det är därför sexton eller trettiotvå är de tal du ser överallt: de tar bort merparten av bruset för nästan ingen tid alls, och steget därefter kostar mycket och ger lite.
Att läsa sexton gånger kräver en loop. Så här ser en
for-loop ut i C++:
const int GIVARE_PIN = A0;
const int ANTAL_PROV = 16;
int medelvarde() {
long summa = 0;
for (int i = 0; i < ANTAL_PROV; i++) {
summa = summa + analogRead(GIVARE_PIN);
}
return summa / ANTAL_PROV;
}
Parentesen efter for innehåller tre delar åtskilda av
semikolon: startvärde (int i = 0),
villkor att fortsätta (i < ANTAL_PROV)
och vad som sker efter varje varv (i++, som
betyder i = i + 1). Loopen körs alltså med
i lika med 0, 1, 2 … 15 – sexton varv. Att räkna från 0 och
använda < i stället för <= är standard,
och det passar ihop med hur arrayer numreras, vilket är nästa stycke.
summa är en long och inte en int,
och det är inte pedanteri. Sexton avläsningar på högst 1023 ger
16 · 1023 = 16 368
vilket ryms i en int, som slutar vid 32 767. Men sextiofyra
avläsningar ger
64 · 1023 = 65 472
och då har talet vikt runt till ett negativt värde utan ett ord om saken.
Skriv long från början så är frågan avklarad oavsett hur många
prov du väljer.
Arrayen: att spara proven och mäta sitt eget brus
Medelvärdet ovan behövde ingen array – summan räckte. Men medelvärdet berättar inte hur mycket givaren darrar, och det är ett tal du vill ha: det är gränsen för vad ditt instrument kan visa, och det är ett mätvärde som hör hemma i dokumentationen. För att räkna på proven i efterhand måste de sparas, och sexton tal som hör ihop sparas i en array.
const int GIVARE_PIN = A0;
const int ANTAL_PROV = 16;
int prov[ANTAL_PROV]; // sexton platser for int, numrerade 0 till 15
void fyllProv() {
for (int i = 0; i < ANTAL_PROV; i++) {
prov[i] = analogRead(GIVARE_PIN);
}
}
int spridning() {
int minsta = prov[0];
int storsta = prov[0];
for (int i = 1; i < ANTAL_PROV; i++) {
if (prov[i] < minsta) { minsta = prov[i]; }
if (prov[i] > storsta) { storsta = prov[i]; }
}
return storsta - minsta;
}
Tre saker skiljer en C++-array från en Python-lista, och alla tre går att
läsa ut ur raden int prov[ANTAL_PROV];:
- Längden är bestämd när arrayen skapas och kan aldrig
ändras. Det finns inget
append. Antalet måste dessutom vara känt vid kompileringen, vilket är skälet attANTAL_PROVärconst. - Alla platser har samma typ. En array av
intrymmer baraint. - Numreringen börjar på 0. Sexton platser heter
prov[0]tillprov[15]. Det finns ingetprov[16]– men C++ säger inte ifrån om du skriver det. Det är arbetsminnet bredvid arrayen som läses eller skrivs över, och symtomet blir en helt annan variabel som ändrar sig av sig själv. Python hade kastat ettIndexError; C++ gör ingenting.
Med de två funktionerna på plats kan bygget rapportera både sitt mätvärde och sin egen osäkerhet:
fyllProv();
long summa = 0;
for (int i = 0; i < ANTAL_PROV; i++) {
summa = summa + prov[i];
}
int medel = summa / ANTAL_PROV;
Serial.print(medel);
Serial.print(" spridning ");
Serial.println(spridning());
Är spridningen sex steg är det, med kalibreringen från början av guiden,
6 · 0,0885 = 0,53 °C
mellan största och minsta enskilda avläsning. Efter medelvärdesbildning över sexton prov är den slumpmässiga delen en fjärdedel av det:
0,53 / 4 = 0,13 °C
Det talet är svaret på frågan "hur många decimaler får jag skriva ut?" Två decimaler vore att påstå mer än instrumentet vet. En decimal är ärligt.
Medelvärde tar bort brus, inte ett systematiskt fel
Det här är områdets viktigaste begreppsskillnad, och den som blandas ihop oftast.
- Ett slumpmässigt fel drar lika ofta åt ena hållet som åt det andra. Det syns som ett tal som darrar. Ett medelvärde tar bort det, med √N.
- Ett systematiskt fel drar åt samma håll varje gång. Det syns inte alls – talet står stadigt, det står bara fel. Ett medelvärde av tusen felaktiga avläsningar är exakt lika fel som en enda. Bara en kalibrering hjälper.
Exempel på ett systematiskt fel som kalibreringen fångar. Ditt fasta motstånd är märkt 10 kΩ men är i verkligheten 10,4 kΩ, vilket ryms väl inom en femprocentig tolerans. Vid 25 °C, med en NTC på 10,0 kΩ, blir spänningen
Uut = 5 · 10 000 / (10 400 + 10 000) = 50 000 / 20 400 = 2,451 V
i stället för 2,500 V, alltså ett råvärde på
rå = 2,451 · 1023 / 5 = 501, i stället för 512
Elva steg fel, varje gång, åt samma håll. Översatt till temperatur:
11 · 0,0885 = 0,97 ≈ 1,0 °C
En hel grad. Och tvåpunktskalibreringen tar bort hela felet, eftersom båda punkterna mättes upp med samma motstånd på plats. Det är hela skälet att man kalibrerar den färdiga kedjan i stället för att räkna på delarna var för sig.
Exempel på ett systematiskt fel som kalibreringen inte fångar. Referenstermometern du kalibrerade mot visar en halv grad för lite. Då blir båda dina punkter en halv grad för låga, linjen genom dem likaså, och varje avläsning ditt bygge gör är en halv grad för låg. Ingen mängd prov, ingen bättre kod och ingen tredje kalibreringspunkt mot samma termometer hittar det. Bara en bättre referens gör det. Det är därför industrins kalibreringar går tillbaka till en normal som i sin tur är kalibrerad, och det är därför du ska skriva i dokumentationen vilken referens du använde.
En trevlig egenskap: matningen räknar bort sig själv Omvandlarens referens är kortets 5 V, och din spänningsdelare matas från samma 5 V. Sjunker matningen till 4,8 V sjunker både spänningen på pinnen och det som ett steg är värt, i samma proportion – och råvärdet blir oförändrat. Kalibrerar du direkt från råvärde till grader, som i den här guiden, försvinner alltså matningens fel av sig självt. Går du omvägen över volt och räknar med 5,00 V som om det vore sanning, får du tillbaka felet som ett systematiskt fel på några procent. Den kortare vägen är den bättre.
Felkällorna, samlade
| Felkälla | Slag | Så syns den i mätvärdet | Botemedel |
|---|---|---|---|
| Brus i omvandlare, matning och ledningar | slumpmässigt | talet darrar några steg fram och tillbaka | medelvärde av N prov, felet delas med √N |
| Glapp i kopplingsdäcket | slumpmässigt, hoppande | enstaka helt orimliga tal mellan normala | tryck ned benen, byt hål, sålla bort ytterlägen |
| Tolerans i det fasta motståndet | systematiskt | syns inte – alla värden lika mycket fel | tvåpunktskalibrering av hela kedjan |
| Referensen visar fel | systematiskt | syns inte alls | bättre referens. Inget annat hjälper |
| Mätning utanför kalibreringsområdet | systematiskt, växande | felet växer ju längre bort du kommer | kalibrera i det område du mäter i, och constrain |
| Givarens tröghet | dynamiskt | värdet släpar efter vid snabba ändringar | vänta in värdet, och redovisa svarstiden |
| Egenuppvärmning i givaren | systematiskt | något för hög temperatur, stadigt | större motstånd i delaren, alltså mindre ström |
I industrin och i vardagen
I industrin: en trycktransmitter i en processanläggning kalibreras i exakt två punkter, och de har egna namn: nollpunkt och spann. Nollpunkten ställs med givaren öppen mot atmosfären, spannet mot en referens som lämnar ett känt tryck. Båda talen skrivs in i ett kalibreringsprotokoll med datum, instrumentets serienummer och referensens identitet – och referensen är i sin tur kalibrerad mot en normal, som är kalibrerad mot en nationell normal. Kedjan kallas spårbarhet, och den finns för att lösa exakt det problem din referenstermometer hade.
Storleksordningen på vad som står på spel: en transmitter för 0–10 bar vars nollpunkt glidit 0,1 bar ger ett fel på
0,1 / 10 = 1 % av mätområdet, i varje avläsning
Det låter lite. I en dosering som blandar två komponenter efter tryck betyder en procent åt fel håll att varje sats blir fel, och det syns inte i mätvärdet – det syns i produkten, veckor senare. Därför är kalibreringsintervall inskrivna i underhållsplanen, inte något man gör när man misstänker något.
I vardagen: badrumsvågen kalibrerar sin nollpunkt varje gång du använder den. Den nollställer sig själv medan den står tom – det är kalibreringspunkt ett, och den sköts åt dig. Men den har ingen andra punkt, och därför kan dess spann glida utan att någonting ser konstigt ut. Vågen visar fortfarande noll när den är tom och ett rimligt tal när du står på den.
Det går att testa på två minuter. Väg dig. Ta upp en hantel eller en vattendunk du vet väger 10,0 kg och väg dig igen. Skillnaden ska bli 10,0 kg. Visar den 9,4 kg är spannet
(10,0 − 9,4) / 10,0 = 0,06 = 6 % fel
och på en person som väger 70 kg betyder det
70 · 0,06 = 4,2 kg
Fyra kilo, systematiskt, i varje vägning – utan att någonting darrar eller ser trasigt ut. Det är samma sorts fel som trycktransmitterns spann, mätt med samma sorts tvåpunktstanke, och det är därför en våg som används i handeln måste kontrolleras med kända vikter och inte bara nollställas.
Prova själv
- Kör raden
Serial.println(map(512, 0, 1023, 0, 5));isetup(). Ditt Uno-kort ska svara 2, och att se det med egna ögon är hela poängen: trunkeringen är inget påstående i en guide utan något du just mätt. Kör sedanSerial.println(map(512, 0, 1023, 0, 5000));och jämför. Skriv en mening om varför den andra raden är användbar och den första inte är det. - Skriv
medelvarde()ur guiden och skriv ut både en enskildanalogReadoch medelvärdet på samma rad, fyra gånger i sekunden. Titta i en halv minut på en stillastående givare. Vilken av de två kolumnerna darrar mest? - Lägg till arrayen och
spridning(). Anteckna spridningen i två lägen: med kortet på USB från datorn, och med kortet på en powerbank eller ett batteri. Skiljer det? - Kalibrera din NTC i två punkter. Punkt ett: ett glas kranvatten med en vanlig termometer i. Punkt två: samma glas efter att du hällt i varmt vatten. Vänta en minut vid varje punkt så att givaren hinner med, och anteckna par av referens och råvärde.
- Skriv
ravardeTillGrader()med dina egna två punkter och låt bygget skriva ut grader med en decimal. Kontrollera mot termometern vid en tredje temperatur som ligger mellan dina två punkter. Skriv ned skillnaden – det är din kalibrerings verkliga fel. - Ändra
TEMP_20ochTEMP_30frånconst floattillconst int, och skriv20och30utan decimal. Ladda upp och se vad funktionen svarar nu, vid tre olika råvärden. Räkna ut varför innan du ändrar tillbaka. - Mät en fjärde punkt långt utanför dina två, till exempel iskallt vatten. Jämför vad linjen säger med vad termometern säger, och jämför avvikelsen med tabellen i guiden.
Kontrollfrågor
Du mäter rå = 800 vid 5,0 °C och rå = 400 vid 45,0 °C. Vad blir lutningen, och vad visar rå = 600?
k = (45,0 − 5,0) / (400 − 800) = 40,0 / (−400) = −0,100 °C per steg.
Linjen blir T = 5,0 − 0,100 · (rå − 800), och vid rå = 600:
T = 5,0 − 0,100 · (−200) = 5,0 + 20,0 = 25,0 °C. Mitt
emellan, som sig bör när punkten ligger mitt emellan.
Vad blir map(1023, 0, 1023, 0, 180), och vad blir
map(1100, 0, 1023, 0, 180)?
Den första blir 1023 · 180 / 1023 = 180, alltså precis
toppen. Den andra blir 1100 · 180 / 1023 = 198 000 / 1023 =
193,5…, alltså 193 – utanför det utområde du själv angav.
map() kontrollerar ingenting; det gör
constrain().
Din givare darrar sex steg. Du går från 16 till 64 prov. Hur mycket bättre blir det, och vad kostar det?
√64 / √16 = 8 / 4 = 2, alltså hälften så stort
slumpmässigt fel. Kostnaden är 64 · 104 µs = 6,7 ms i
stället för 1,7 ms. Dubbelt så bra för fyra gånger tiden – och nästa
halvering kostar fyra gånger till. Det är därför man sällan går över
några tiotal prov.
Ditt bygge visar konsekvent 2 grader för mycket. Hjälper det att läsa 256 prov i stället för 16?
Nej, inte det minsta. Ett fel som är lika stort och åt samma håll varje gång är systematiskt, och ett medelvärde av felaktiga tal är lika felaktigt som talen. Kalibrera om, och kontrollera först att referensen du kalibrerar mot är den du tror.
Varför står det long summa och inte
int summa?
Därför att summan växer med antalet prov. 16 · 1023 = 16 368 ryms i en
int, men 64 · 1023 = 65 472 gör det inte – en
int tar slut vid 32 767 och viker runt till ett negativt
tal utan varning. Symtomet är ett medelvärde som plötsligt blir negativt
när du ökade antalet prov.
Vad händer om du skriver prov[16] i en array som
deklarerats int prov[16];?
Platserna heter 0 till 15, så prov[16] ligger utanför.
C++ säger ingenting, varken vid kompilering eller vid körning – du läser
eller skriver i minnet bredvid arrayen. Symtomet är att en helt annan
variabel ändrar sig av sig själv, vilket är ett av de svåraste felen
att hitta. Låt loopens villkor vara i < ANTAL_PROV och
problemet uppstår aldrig.
Varför ska omvandlingen ligga i en funktion i stället för att skrivas där den behövs?
Därför att den behövs på mer än ett ställe – i nästa guide både på
seriemonitorn och på servot – och två kopior av samma formel betyder två
ställen att rätta när kalibreringen ändras. Det är också självdokumenterande:
ravardeTillGrader(medel) säger vad som händer, medan samma
formel utskriven mitt i loop() bara ser ut som räkning.
Vanliga fel
- Kalibreringen räknas i heltal och blir 0.
(30 - 20) / (456 - 569)med bara heltal blir10 / -113, som i heltalsdivision är 0. Funktionen lämnar då samma tal oavsett insignal. Skriv30.0och20.0, eller deklarera konstanterna somfloat. map()används på ett värde med decimaler. Utområdet är mindre än inområdet, decimalerna kapas, och upplösningen försvinner. Skala till en finare enhet eller räkna ifloat.constrain()saknas. Bygget fungerar hela vintern och gör något orimligt första varma dagen, eller tvärtom. Ett fel som bara dyker upp utanför kalibreringsområdet är det svåraste att upptäcka genom att prova.- Kalibreringspunkterna ligger för nära varandra. Två punkter som skiljer sig med tre grader ger en lutning som mest består av brus. Lägg punkterna i vardera änden av mätområdet.
- Kalibreringen görs om efter varje ändring i kopplingen. Rätt – men den gäller kedjan som helhet. Byter du det fasta motståndet, givaren, eller flyttar till en annan analog pinne, är kalibreringen inte längre giltig. Skriv datum bredvid dina två punkter.
- Fler decimaler skrivs ut än mätningen bär. Är
spridningen en halv grad är den andra decimalen ren fantasi.
Serial.print(grader, 1)är rätt precision för det här bygget. - Medelvärdet används mot ett systematiskt fel. Symtomet är att bygget blir tystare men lika fel. Fråga alltid först: darrar talet, eller står det stilla på fel ställe?
- Arrayens längd står som ett tal på flera ställen.
int prov[16];på en rad ochi < 20i loopen på en annan. Använd sammaconst inti båda, så kan de inte komma isär.
Vidare
Tre kapitel i språkreferensen öppnar med den här guiden: Loopar, Funktioner och Arrayer. De är guidens uppslagsdel – här står de i den ordning mätproblemet krävde dem, där står de i sin egen ordning med alla detaljer samlade.
Nästa guide är Datablad. Där byter vi ut de givare du kan mäta upp själv med en multimeter mot sådana där talen måste hämtas ur tillverkarens dokument – och där ett bibliotek för första gången är rätt svar på en givare.