Viele PICs haben ein sogenanntes Analog-to-Digital-Converter-Modul, mit dem es möglich ist,
Spannungen einzulesen. Im Falle des 16F877a hat der ADC 8 Kanäle, dh. die Ports A0-A7 können softwaregesteuert an
den ADC gelegt werden. Die Auflösung beträgt dabei 10 Bit, dh. das Ergebnis einer Wandlung ist eine Zahl
zwischen 0 und 1023. Jörg Bredendiek (sprut) hat ein schönes
Lernbeispiel
zur Benutzung des ADC veröffentlicht.
Sein Programm wurde an das Deltawave-Board angepasst und ist auch zur Erarbeitung der Ansteuerung eines LCD-Displays
sehr hilfreich. Das Archiv adc_sprut.zip
enthält den Assembler-Quelltext und das Hex-File zum direkten Brennen.
Die Spannung wird am PORTA,0 (gelb) gemessen.
Im Versuchsaufbau wurde ein 10k-Potentiometer als Spannungsteiler zwischen Masse (schwarz) und +5V (rot) verwendet
Quelltext
; sprut-Programm von mk an Deltawave 16f877a angepasst
list p=16f877a
;**************************************************************
;* Pinbelegung
;* ----------------------------------
;* PORTA: 0 Interupt disable
Ini_opt Equ B'00000010' ; pull-up
; für LCD-Pins
#define LcdE PORTD,3 ; enable Lcd
#define LcdRw PORTD,2 ; read Lcd
#define LcdRs PORTD,1 ; Daten Lcd (nicht control)
#define LcdPort PORTD ; Datenbus des LCD (obere 4 Bit)
;********************************************************
;mk------------------------------------------------------
org 0x00
RESET: clrf STATUS
movlw 0x00
movwf PCLATH
goto main
org 0x10
; Das Programm beginnt mit der Initialisierung
main
;mk------------------------------------------------------
Init bsf STATUS, RP0 ; Bank 1
movlw Ini_opt ; pull-up on
movwf OPTION_REG
movlw B'00000000' ; PortD alle outputs
movwf TRISD
bcf STATUS, RP0 ; Bank 0
clrf PORTD
movlw Ini_con ; Interupt disable
movwf INTCON
; ADC initialisieren
; ADC einschalten
BSF ADCON0, 0 ; ADON=1
; ADC-Eingang AN0 auswählen
BCF ADCON0, 5 ; ADCHS2=0
BCF ADCON0, 4 ; ADCHS1=0
BCF ADCON0, 3 ; ADCHS0=0
; ADC speed für 5 ... 20 MHz einstellen
BSF ADCON0, 7 ; ADCS1=1
BCF ADCON0, 6 ; ADCS0=0
; Daten rechtsbündig
BSF STATUS,RP0 ; Bank1
clrf ADCON1
BSF ADCON1, 7 ; ADFM=1
BCF STATUS,RP0 ; Bank0
;Display initialisieren
call InitLcd
Mainloop
call ADC ; Spannung messen nach f1,f0
call mV ; Wandlung in Millivolt nach f1,f0
call B2D ; Wandlung in dezimal nach ST,SH,SH,SE
call Ausgabe ; anzeigen am LCD
goto Mainloop
;*****************************************************
; Spannung mit ADC messen
; Ergebnis nach F1,f0
ADC
BSF ADCON0, 2 ; ADC starten
ADCloop
BTFSC ADCON0, 2 ; ist der ADC fertig?
GOTO ADCloop ; nein, weiter warten
movfw ADRESH ; obere 2 Bit auslesen
movwf f1 ; obere 2-Bit nach U1H
bsf STATUS,RP0 ; Bank1
movfw ADRESL ; untere 8 Bit auslesen
bcf STATUS,RP0 ; Bank0
movwf f0 ; untere 8-Bit nach U1L
return
;*********************************************************************
;16 bit Adition, C-Flag bei Überlauf gesetzt
Add16 ; 16-bit add: f := f + xw
movf xw0,W ; xw0 nach W
addwf f0,F ; f0 := f0 + xw0
movf xw1,W ; xw1 nach W
btfsc STATUS,C ; fall ein Überlauf auftrat:
incfsz xw1,W ; xw1+1 nach W
addwf f1,F ; f1 := f1 + xw1
return ; fertig
;*****************************************************
; 16 Bit Subtraktion, bei Überlauf (neg. Ergebnis) ist C gesetzt
Sub16 ; 16 bit f:=f-xw
clrf Fehler ; extraflags löschen
movf xw0, w ; f0:=f0-xw0
subwf f0, f
btfsc STATUS,C
goto Sub16a
movlw 0x01 ; borgen von f1
subwf f1, f
btfss STATUS,C
bsf Fehler, C ; Unterlauf
Sub16a
movf xw1,w ; f1:=f1-xw1
subwf f1 ,f
btfss STATUS,C
bsf Fehler, C ; Unterlauf
bcf STATUS, C ; C-Flag invertieren
btfsc Fehler, C
bsf STATUS, C
return
;*****************************************************
; Division durch 2 wird w-mal ausgeführt
; die zu dividierende Zahl steht in xw
Div2
movwf counter ; Anzahl der Divisionen speichern
Div2a ; 16 bit xw:=xw/2
bcf STATUS, C ; carry löschen
rrf xw1, f
rrf xw0, f
decfsz counter, f ; fertig?
goto Div2a ; nein: noch mal
return
;*****************************************************
; Wandlung des ADC-Wert in Millivolt (binär)
; Der ADC-Wert steht in f1,f0
; Ergebnis steht in f1,f0
mV
; zunächst die Multiplikation mal 5
movfw f0
movwf xw0
movfw f1
movwf xw1
call Add16 ; f := 2xADC
call Add16 ; f := 3xADC
call Add16 ; f := 4xADC
call Add16 ; f := 5xADC
; ADC * 5 nach xw kopieren
movfw f0
movwf xw0
movfw f1
movwf xw1 ; xw := 5xADC
; xw durch 64 dividieren (6 mal durch 2)
; dann ist xw = 5xADC/64
movlw 6
call Div2
call Sub16 ; f := 5xADC - 5xADC/64
; xw auf 5xADC/128 verringern
movlw 1
call Div2
call Sub16 ; f := 5xADC - 5xADC/64 - 5xADC/128
return ; fertig
;*****************************************************
; Wandlung einer Binärzahl (< 10000) in eine Dezimalzahl
; Die Binärzahl steht in f1,f0
; die Dezimalstellen werden in ST (Tausender), SH (Hunderter),
; SZ (Zehner) und SE (Einer) gespeichert im BCD-Code
B2D
; Test auf Tausender 1000d = 0x03E8
movlw 0x03
movwf xw1
movlw 0xE8
movwf xw0
call B2Da
movwf ST
; Test auf Hunderter 100d = 0x0064
clrf xw1
movlw 0x64
movwf xw0
call B2Da
movwf SH
; Test auf Zehner 10d = 0x000A
clrf xw1
movlw 0x0A
movwf xw0
call B2Da
movwf SZ
movfw f0
movwf SE
return
B2Da
clrf counter
B2Sb incf counter, f ; wie oft abgezogen?
call Sub16 ; f:=f-xw
btfss STATUS, C ; zu oft abgezogen?
goto B2Sb ; nein: noch einmal
call Add16 ; f:=f+xw
decf counter, w ; weil immer 1 zuviel gezählt wird
return
;*****************************************************
; Anzeige der Dezimalzahl am LCD mit 'mV'
; input: ST, SH, SZ, SE dezimalstellen im BCD-Code
Ausgabe
movlw B'10000000' ; 1. Zeile
call OutLcdControl
movlw '0' ; 30h = '0011 0000'
iorwf ST, w ; BCD -> ASCII
call OutLcdDaten ; zum LCD
movlw '0'
iorwf SH, w
call OutLcdDaten
movlw '0'
iorwf SZ, w
call OutLcdDaten
movlw '0'
iorwf SE, w
call OutLcdDaten
movlw ' '
call OutLcdDaten
movlw 'm' ; 'mA' anhängen
call OutLcdDaten
movlw 'V'
call OutLcdDaten
return
;*****************************************************
;+++LCD-Routinen**************************************
;*****************************************************
;LCD initialisieren, Begrüßung ausgeben
InitLcd
movlw D'255' ; 250 ms Pause nach dem Einschalten
movwf loops
call WAIT
movlw B'00110000' ; 1
movwf LcdPort
bsf LcdE
nop
bcf LcdE
movlw D'50' ; 50 ms Pause
movwf loops
call WAIT
movlw B'00110000' ; 2
call Control8Bit
movlw B'00110000' ; 3
call Control8Bit
movlw B'00100000' ; 4
call Control8Bit
movlw B'00000001' ; löschen und cusor home
call OutLcdControl
movlw B'00101000' ; 5 function set, 4-bit 2-zeilig, 5x7
call OutLcdControl
movlw B'00001000' ; 6 display off
call OutLcdControl
movlw B'00000110' ; 7 entry mode, increment, disable display-shift
call OutLcdControl
movlw B'00000011' ; 8 cursor home, cursor home
call OutLcdControl
movlw B'00001100' ; 9 display on, Kursor aus , Blinken aus
call OutLcdControl
return
;*****************************************************
; ein Steuerbyte 8-bittig übertragen
Control8Bit
movwf LcdPort
bsf LcdE
nop
bcf LcdE
movlw D'10'
movwf loops
call WAIT
return
;*****************************************************
; darauf warten, daß das Display bereit zur Datenannahme ist
LcdBusy
bsf STATUS, RP0 ; make Port D4..7 input
movlw B'11110000'
iorwf TRISD, f
bcf STATUS, RP0
BusyLoop
bcf LcdRs
bsf LcdRw ; Lesen
bsf LcdE
nop
movf LcdPort, w
movwf LcdStatus
bcf LcdE
nop
bsf LcdE ; Enable
nop
bcf LcdE
btfsc LcdStatus, 7 ; teste bit 7
goto BusyLoop
bcf LcdRw
bsf STATUS, RP0 ; make Port B4..7 output
movlw B'00001111'
andwf TRISD, f
bcf STATUS, RP0
return
;*****************************************************
; aus W ein Byte mit Steuerdaten zum Display übertragen
OutLcdControl
movwf LcdDaten
call LcdBusy
movf LcdDaten, w
andlw H'F0'
movwf LcdPort ; Hi-teil Daten schreiben
bsf LcdE
nop
bcf LcdE ; Disable LcdBus
swapf LcdDaten, w
andlw H'F0'
movwf LcdPort ; Lo-teil Daten schreiben
bsf LcdE
nop
bcf LcdE ; Disable LcdBus
return
;*****************************************************
; aus W ein Datenbyte zum Display übertragen
OutLcdDaten
movwf LcdDaten
call LcdBusy
movf LcdDaten, w
andlw H'F0'
movwf LcdPort ; Hi-teil Daten schreiben
bsf LcdRs ; Daten
bsf LcdE ; Enable LcdBus
nop
bcf LcdE ; Disable LcdBus
swapf LcdDaten, w
andlw H'F0'
movwf LcdPort ; Lo-teil Daten schreiben
bsf LcdRs ; Daten
bsf LcdE
nop
bcf LcdE ; Disable LcdBus
bcf LcdRs ;
return
;*****************************************************
;Zeitverzögerung um loops * 1 ms
; 10 MHz externer Takt bedeutet 2,5 MHz interner Takt
; also dauert 1 ms genau 2500 Befehle
; 250 Schleifen a 10 Befehle sind 2500 Befehle = 1 ms
WAIT
top movlw .250 ; timing adjustment variable (1ms)
movwf loops2
top2 nop ; sit and wait
nop
nop
nop
nop
nop
nop
decfsz loops2, F ; inner loops complete?
goto top2 ; no, go again
;
decfsz loops, F ; outer loops complete?
goto top ; no, go again
retlw 0 ; yes, return from subWAIT
end
Links