Da ich gerade an ein projekt arbeite, das messdaten aufnimmt und sie
weiterverarbeitet, binn ich auf ein problem gestoßen, welches an den
ADCs vorliegt.
1. Der ADC beinflusst macht fehler, wenn man von einen kanal zum anderen
wechselt.
2. Der ADC zeigt offsetwerte an.
3. Die meswerte wackeln und kurze impulse stören die messung.
Am folgenden code, zeige ich die problemlösung.
Zuerst die constanten festlegen
1 | const Vref = (1 shl REFS1);
|
2 |
|
3 | ADSR_int = (1 shl ADPS1) or (1 shl ADPS1) or (1 shl ADPS0) or // Frequenzvorteiler
|
4 | (1 shl ADEN) or (1 shl ADIE); // ADC aktivieren interrupt aktivieren
|
5 |
|
6 | ADSR_en = (1 shl ADPS1) or (1 shl ADPS1) or (1 shl ADPS0) or // Frequenzvorteiler
|
7 | (1 shl ADEN); // ADC aktivieren interrupt aktivieren
|
8 |
|
9 | ADSR_off = (1 shl ADPS1) or (1 shl ADPS1) or (1 shl ADPS0); // Frequenzvorteiler
|
10 |
|
11 | integrationsschritte = 2; // je größer die zahl ist, destso länger die integration und
|
12 | // damit die tiefpasswirkung
|
Hier wird einfach der wandler vorbereitet und noch ist alles normal,
außer das die temporären werte gelöscht werden.
1 | procedure adc_set;
|
2 | var i:byte;
|
3 | begin
|
4 | ADMUX:= Vref; // ref 1.1V da nur singleconversion
|
5 | ADCSRA:=ADSR_en; // vorteiler setzen und adc aktivieren
|
6 | for i:=1 to 3
|
7 | do // temp auf null setzen
|
8 | adc_temp[i]:=0;
|
9 | end;
|
Jetzt die ADC routine.
Um möglichst ruhig zu arbeiten,wird der ADC im interruptmodus gebracht
und
die CPU schlafen gelegt.
Wenn man die ADC flags setzt, geht die CPU in den schlaf und startet
dabei den ADC. Sobald die wandlung fertig ist, wacht die CPU auf und
springt auf die ISR, um von dort zurück in den normalen ablauf zu
springen.
So da ich aber auch noch energiesparen möchte, schalte ich den ADC (
nach der wandlung ) wider aus.
D.h wenn ich die routine erneut anlaufe, muß ich den ADC neu starten und
einen dummyread machen, weil das erste ergebniss fehlerhaft ist.
Nachdem das erfolgt ist, setze ich den ADC auf den Groundeingang und
messe, welchen offset der wandler hat und speichere ihn.
Nach ende der messung, wird er von dem ermittelten wert abgezogen.
Wenn mich dann das zappel nicht stört kann ich jetzt den wert
zurückgeben.
Nun gibt es aber situationen, wo störungen auf dem messignal sind, die
stören.
Jetzt könnte ich ein R-C glied als filter vor dem eingang schalten.
Nur das kostet Zwie bauteile mehr und kostet platz. Besonders, wenn ich
eine sehr hohe zeitkonstante haben möchte.
Anderseits, habe ich ja eine CPU, die das auch per software lösen kann.
Meist wird vorgeschlagen, den mittelwert zu bilden.
also
1 | mittelwert:=(wert[1]+.....wert[n])/n
|
Nachteil ist, man benötigt eine zusätliche schleife und der wert ist
nicht so glatt , oder man benötigt eine sehr große schleife.
Aber um das R-C glied nachzubilden, welcher ja ein integrator ist,
können wir den rollenden mittelwert bilden.
Hier hängt die filterwirkung nur von der größe des teilerfaktors ab.
https://de.wikipedia.org/wiki/Gleitender_Mittelwert
Das endergebnis ist ein sauber laufender wert.
1 |
|
2 |
|
3 | procedure ADC_Interrupt(); org IVT_ADDR_ADC;
|
4 | begin
|
5 | end;
|
6 |
|
7 | var adc_temp:array[1..3] of real;
|
8 |
|
9 | function adc_r(ch:byte):word;
|
10 | var ad_offs,r,
|
11 | MC_temp:word;
|
12 | temp:real;
|
13 |
|
14 | begin
|
15 | SREG_I_bit:= 1; // interrupt muß an sein
|
16 | MC_temp:=MCUCR or (1 shl SE) or ( 1 shl SM0); // das startet die AD-Wandlung
|
17 | ADCSRA:=ADSR_int; // vorteiler setzen und adc aktivieren
|
18 |
|
19 | ADMUX:=Vref or %1101; // dummyread gegen ground
|
20 | MCUCR := MC_temp;
|
21 | asm sleep; end;
|
22 | MCUCR := MC_temp;
|
23 | asm sleep; end;
|
24 | ad_offs:=ADCl;
|
25 | ad_offs:=((ad_offs or ADCh shl 8)) and %1111111111; // offset gegen null ermitteln
|
26 |
|
27 | ADMUX:=Vref or ch; // hier erst den echten wert messen
|
28 |
|
29 |
|
30 | MCUCR :=MC_temp; // das startet die AD-Wandlung
|
31 | asm sleep; end;
|
32 | ADCSRA:=ADSR_off; // den adc wider aus machen, wegen dem stromverbrauch
|
33 | r:=ADCl;
|
34 | r:=((r or ADCh shl 8) - ad_offs) and %1111111111; // wert minus offset
|
35 | adc_temp[ch]:=adc_temp[ch]+(r - adc_temp[ch]) / integrationsschritte ; // rollender mittelwert
|
36 | result:=word(adc_temp[ch]);
|
37 |
|
38 | end;
|
Ein nachteil hat die sache, mit dem sleep , ich muß kontrollieren, ob
ein anderer interrupt den prozessor aufgeweckt hat.
Bei meinem projekt spielt das keine rolle, weil der pinchange vorher
kommt und ich , beim wandeln, genau weis , daß es der richtige ist.
Noch eines, wenn ich den wandler nicht ausschalten brauche, kann man
die ADC read in zwei teile aufspalten.
der erste teil , das dummyread und das lesen gegen ground wird dann in
die adc-vorbereitungsroutine geschoben und nur der offsetwert , GLOBAL,
gespeichert.
Dann bleibt nur noch das übrig
1 | function adc_r(ch:byte):word;
|
2 | var r:word;
|
3 |
|
4 | begin
|
5 | SREG_I_bit:= 1; // interrupt muß an sein
|
6 | MCUCR :=MCUCR or (1 shl SE) or ( 1 shl SM0); // das startet die AD-Wandlung
|
7 | asm sleep; end;
|
8 | dem stromverbrauch
|
9 | r:=ADCl;
|
10 | r:=((r or ADCh shl 8) - ad_offs) and %1111111111; // wert minus offset
|
11 | adc_temp[ch]:=adc_temp[ch]+(r - adc_temp[ch]) / integrationsschritte ; // rollender mittelwert
|
12 | result:=word(adc_temp[ch]);
|
13 | end;
|