Forum: Mikrocontroller und Digitale Elektronik Mehrere ADC-Pins bei Atmega8 benutzen


von Vaio (Gast)


Lesenswert?

Hi,
für eine Anwendung soll der µC drei Spannungen umwandeln und verarbeiten 
können. Als Test sollten diese nurmal auf ein LCD Display ausgegeben 
werden. Ich hab den Code aus dem AVR-Tutorial dieser Seite dafür 
umgeschrieben, allerdings funktioniert die Anwendung nicht, da die 
angezeigten Werte nicht stimmen, bzw. sich gegenseitig beeiflussen. Hier 
der Code:

.include "m8def.inc"

; .def definiert ein Synonym (Namen) für ein µC Register
.def temp1     = r16         ; allgemeines temp Register, zur 
kurzfristigen Verwendung
.def temp2     = r17
.def adlow     = r20         ; Ergebnis des ADC'
.def adhigh    = r21         ; Ergebnis des ADC'
.def ztausend  = r23         ; Zehntausenderstelle des ADC Wertes
.def tausend   = r24         ; Tausenderstelle des ADC Wertes
.def hundert   = r25         ; Hunderterstelle des ADC Wertes
.def zehner    = r26         ; Zehnerstelle des ADC Wertes
.def temp5 = r28       ; LCD-Routines
.def temp6 = r29       ; LCD-Routines
.def temp7 = r30       ; LCD-Routines


           ldi temp1, LOW(RAMEND)      ; LOW-Byte der obersten 
RAM-Adresse
           out SPL, temp1
           ldi temp1, HIGH(RAMEND)     ; HIGH-Byte der obersten 
RAM-Adresse
           out SPH, temp1

           ldi temp1, 0xFF    ; Port D = Ausgang
           out DDRD, temp1

           rcall lcd_init     ; Display initialisieren
           rcall lcd_clear    ; Display löschen

           ; ADC initialisieren: ADC0, Vcc als Referenz, Single 
Conversion, Vorteiler 128
         ldi     temp1, (1<<REFS0)                   ; Kanal 0, interne 
Referenzspannung 5V
         out ADMUX, temp1
       ldi     temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
         out     ADCSRA, temp1


Main:
       rcall lcd_clear
       rcall ADI_1
       rcall ADW
       rcall ADI_2
       rcall ADW
       rcall ADI_3
       rcall ADW
       rjmp Main

ADW:
    clr     temp1
  clr     temp5
  clr     ztausend
  clr     tausend
  clr     hundert
  clr     zehner
  clr     adlow
  clr     adhigh


    rcall sample_adc
  rcall wait_adc



; ADC einlesen:

    in      adlow, ADCL         ; immer zuerst LOW Byte lesen
    in      adhigh, ADCH        ; danach das mittlerweile gesperrte High 
Byte


    ldi     ztausend, '0'-1     ; Ziffernzähler direkt als ASCII Code
    ; bzgl. '0'-1 siehe Beitrag "Hilfe zu AVR-Tutorial ADC"
  rcall Z_ztausend

  rcall Z_tausend

  rcall Z_hundert

  rcall Z_zehner

  rcall Print
  ret



delay:                               ; 5ms Pause (bei 4 MHz)
           ldi  temp1, $21
WGLOOP0_:   ldi  temp2, $C9
WGLOOP1_:   dec  temp2
           brne WGLOOP1_
           dec  temp1
           brne WGLOOP0_
           ret                          ; wieder zurück

ADI_1:
       ldi     temp1, (1<<REFS0)                   ; Kanal 0, interne 
Referenzspannung 5V
         out ADMUX, temp1
       ldi     temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
         out     ADCSRA, temp1
       ret
ADI_2:
       ldi     temp1, (1<<REFS0)                   ; Kanal 0, interne 
Referenzspannung 5V
         ldi temp1,2
       out ADMUX, temp1
       ldi     temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
         out     ADCSRA, temp1
       ret
ADI_3:
       ldi     temp1, (1<<REFS0)                   ; Kanal 0, interne 
Referenzspannung 5V
         ldi temp1,3
       out ADMUX, temp1
       ldi     temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)
         out     ADCSRA, temp1
       ret


sample_adc:
    sbi     ADCSRA, ADSC        ; den ADC starten
  ret

wait_adc:
    sbic    ADCSRA, ADSC        ; wenn der ADC fertig ist, wird dieses 
Bit gelöscht
    rjmp    wait_adc
  ret
Z_ztausend:
    inc     ztausend
    subi    adlow, low(10000)   ; -10,000
    sbci    adhigh, high(10000) ; 16 Bit
    brcc    Z_ztausend

    subi    adlow, low(-10000)  ; nach Unterlauf wieder einmal addieren
    sbci    adhigh, high(-10000); +10,000

    ldi     tausend, '0'-1      ; Ziffernzähler direkt als ASCII Code
  ret
Z_tausend:
    inc     tausend
    subi    adlow, low(1000)    ; -1,000
    sbci    adhigh, high(1000)  ; 16 Bit
    brcc    Z_tausend

    subi    adlow, low(-1000)   ; nach Unterlauf wieder einmal addieren
    sbci    adhigh, high(-1000) ; +1,000

    ldi     hundert, '0'-1      ; Ziffernzähler direkt als ASCII Code
  ret
Z_hundert:
    inc     hundert
    subi    adlow, low(100)     ; -100
    sbci    adhigh, high(100)   ; 16 Bit
    brcc    Z_hundert

    subi    adlow, low(-100)    ; nach Unterlauf wieder einmal addieren
    sbci    adhigh, high(-100)  ; +100

    ldi     zehner, '0'-1       ; Ziffernzähler direkt als ASCII Code
  ret
Z_zehner:
    inc     zehner
    subi    adlow, low(10)      ; -10
    sbci    adhigh, high(10)    ; 16 Bit
    brcc    Z_zehner

    subi    adlow, low(-10)     ; nach Unterlauf wieder einmal addieren
    sbci    adhigh, high(-10)   ; +10

    subi    adlow, -'0'         ; adlow enthält die Einer, Umwandlung in 
ASCII
  ret

Print:
       mov temp5, ztausend     ; Zeichen anzeigen
           rcall lcd_data

           mov temp5, tausend     ; Zeichen anzeigen
           rcall lcd_data

           mov temp5, hundert     ; Zeichen anzeigen
           rcall lcd_data

           mov temp5, zehner     ; Zeichen anzeigen
           rcall lcd_data

       mov temp5, adlow     ; Zeichen anzeigen
           rcall lcd_data

       ldi temp5, $0A
       rcall lcd_data

       rcall delay
       ret

.include "lcd-routines.asm"            ; LCD-Routinen werden hier 
eingefügt


Lässt man  bei Main, den Tail für die dritte und zweite Wandlung weg, 
klappt es. Wo liegt denn der Fehler, oder kann man mit dem Atmeg8 
einfach keine drei ADC-Pins in einer Anwendung benutzen?

Mfg Vaio

von dunno.. (Gast)


Lesenswert?

ich bin zwar nicht so firm in sachen ASM,
aber ich hatte vor kurzem noch das gleiche problem in C.

ich konnte einen AD-wert bekommen, wollte ich mehrere haben, wurden die 
gegenseitig beeinflusst.

die lösung des problems war der taktvorteiler des ADC. du musst eben nen 
ziemlich geringen takt einstellen, für den adc..

näheres weiß ich grad nicht auswendig, datenblatt wird helfen

mfg

von dunno.. (Gast)


Lesenswert?

ps: im tutorial stehts ja auch, 50 bis 200khz solltest du aus dem 
vorteiler herausbekommen.

http://www.mikrocontroller.net/articles/AVR-Tutorial:_ADC

von spess53 (Gast)


Lesenswert?

Hi

Wo kommen die zu messenden Spannungen her? Wenn die Impedanz der Quellen 
zu hoch ist, können sich die Kanäle beeinflussen.

MfG Spess

von Vaio (Gast)


Lesenswert?

Ich hab mal alle Vorteiler durchprobiert, hat aber leider nix 
gebracht.??

Trotzdem schon mal danke für den Tipp;)

Vaio

von Vaio (Gast)


Lesenswert?

@spess53
Zum Testen waren sind die Spannungen erstmal "inter" gewesen, sprich 
einfach an GND, bzw. VCC geklemmt.

von spess53 (Gast)


Lesenswert?

Hi

Dein Programm ist auch noch fehlerhaft:

ADI_2:
       ldi temp1, (1<<REFS0)      ; Kanal 0, interne Referenzspannung 5V
       ldi temp1,2
       out ADMUX, temp1

Du lädst erst '1<<REFS0' nach temp1 und überschreibst das dann mit 2. 
Dadurch ist deine Referenzspannungeinstellung weg.

Außerdem ist es unnötig für jeden Kanal (welche benutzt du eigentlich) 
ein eigenes Unterprogramm mit einer Initialisierung zu benutzen. Besser 
du schreibst ein Unterprogramm, dem die Kanalnummer per Register 
übergeben.

MfG Spess

von Anja (Gast)


Lesenswert?

Vaio schrieb:
> ADI_3:
>        ldi     temp1, (1<<REFS0)                   ; Kanal 0, interne
> Referenzspannung 5V
>          ldi temp1,3
>        out ADMUX, temp1

mit  " ldi     temp1, (1<<REFS0)" schaltest Du die Referenzspannung ein 
und
mit  " ldi temp1,3" wieder aus ??????

Gruß Anja

von Vaio (Gast)


Lesenswert?

Ich hab den Code jetzt so umgwandelt und jetzt geht es mehr oder 
weniger:
ADI_1:
       ldi     temp1,64                  ; Kanal 0, interne 
Referenzspannung 5V
         out ADMUX, temp1
       ldi     temp1,135
         out     ADCSRA, temp1
       ret
ADI_2:
       ldi     temp1,65                   ; Kanal 1, interne 
Referenzspannung 5V
       out ADMUX, temp1
       ldi     temp1,135
         out     ADCSRA, temp1
       ret
ADI_3:               ; Kanal 2, interne Referenzspannung 5V
         ldi temp1,66
       out ADMUX, temp1
       ldi     temp1,135
         out     ADCSRA, temp1
       ret

Ich benutze die Pins 23-25, sprich PC0-PC2

von Michael A. (Gast)


Lesenswert?

Zwischen dem Setzen von ADMUX und dem Start der Wandlung könnte man noch 
mal probieren, dem Analogteil noch ein bisschen Zeit zu gönnen, damit 
sich der Eingangspegel vom S&H genau genug einstellen kann.

MfG

von spess53 (Gast)


Lesenswert?

HI

>Ich hab den Code jetzt so umgwandelt und jetzt geht es mehr oder
>weniger:

Eher mehr oder eher weniger?

>       ldi     temp1,135
>       out     ADCSRA, temp1

Diese Schreibweise ist total unverständlich. Bleibe bitte bei dieser:
1
ldi     temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0)

Das mit dem ein Unterprogramm ist dir wohl zu kompliziert? Hier mal 
ein Beispiel wie das aussehen kann:
1
          ; Kanal in r16
2
messung:    push r17
3
            in r17, ADMUX
4
            andi r17,0b11110000 ; MUX-Bits ausblende
5
            or r17,r16          ; mit Kanal verbinden
6
            out ADMUX,r17       ; Kanal setzen
7
8
            sbi ADCSRA,ADSC     ; Wandlung starten
9
messung10:  sbic ADCSRA,ADSC    ; Ende abwarten
10
            rjmp messung10
11
            pop r17
12
            ret                 ; Ergebnis in ADC

Übrigens sind Unterprogramme, die nur ein, zwei Befehle enthalten nicht 
sehr sinnvoll. Spart keinen oder kaum Flash und kostet Rechenzeit.

MfG Spess

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.