Analog I/O 2
Why Another Analog IO Tutorial?
The last tutorial taught how to get rudimentary analog input on the PIC and communicate it in a very basic way to a computer. The techniques in that tutorial, however, don't allow for using more than one sensor, or inputting it into a standard terminal. This here tutorial will build on what we did previously to enable better formatting of the output. It will also help us utilize the full 10 bit resolution of the ADC. Lastly (though I probably won't present them in this order) I will introduce the VERY useful world of macros.
Formatting
The first thing we need to do is format text as ASCII. ASCII is a set of numeric codes that represent print characters. Look at http://www.asciitable.com for the table of ASCII definitions. If you take a look at the table you can see that the numbers all start at 0x30 and go sequentially from there. This makes it very easy to convert a decimal number to its ASCII representation, you just add 0x30.
Macros
I'll use this opportunity (converting decimal numbers to ASCII) to introduce macros. Macros are what you're invoking every time you call an instruction: movf is a macro, so is call, so are all the other instructions. When you invoke a macro the compiler just replaces the invocation with the macro instructions. Think of macros as a kind of shorthand--rather than rewriting a procedure every time you just invoke a name that implies the procedure. Macros are different from subroutines in that the compiler will replace each invocation with the text of the macro, making the amount of memory used proportional to the number of times the macro is called, whereas a subroutine only goes into memory once, since it's basically a kind of goto statement. The advantage of macros is that you can pass them argument, while subroutines must assume the placement of the operands--which is usually W, but when more operands are needed, they must be stored in other variables.
Macros are easy. They start with the name of the macro in the first column, followed by the word MACRO and then a list of the arguments, delimited by commas. The macro is ended by the endm statement and in between goes the procedure. When you need to operate on one of the arguments you just use its name as a variable. Here's a simple macro that adds 0x30 to a number x and stores the result back in x. This is all we need to convert a number to its ascii equivalent. Note that the arguments are read as memory addresses, like pointers, rather than having their values copied into local variables.
ENCDA MACRO x
movlw 0x30 ; convert to 0-9 in ASCII
addwf x, F
endm
Now any time you want to encode a number into its ASCII representation you call ENCDA number and the number is encoded and stored in itself.
Now Really. Formatting
Now that you can easily ASCII encode a number, you need to be able to convert the two registers ADRESL and ADRESH (the ADC results) into a decimal number, because that's what we want to see on the other end. Luckily there's a subroutine that does just this in the Microchip Picdem 2 Plus Demo Board. This assumes that there are 5 holder variables already created: Tenk, Thou, Hund, Tens, Ones. It also assumes that ADRESL has been moved to a variable NumL, and ADRESH has been moved to NumH. Here it is:
; Binary (16-bit) to BCD. From Microchip PICDEM 2 PLUS DEMO BOARD
; xxx = highest possible result
bin16_bcd:
; Takes number in NumH:NumL
; Returns decimal in TenK:Thou:Hund:Tens:Ones
swapf NumH,W
andlw 0x0F
addlw 0xF0
movwf Thou
addwf Thou,F
addlw 0xE2
movwf Hund
addlw 0x32
movwf Ones
movf NumH,W
andlw 0x0F
addwf Hund,F
addwf Hund,F
addwf Ones,F
addlw 0xE9
movwf Tens
addwf Tens,F
addwf Tens,F
swapf NumL,W
andlw 0x0F
addwf Tens,F
addwf Ones,F
rlcf Tens,F
rlcf Ones,F
comf Ones,F
rlcf Ones,F
movf NumL,W
andlw 0x0F
addwf Ones,F
rlcf Thou,F
movlw 0x07
movwf TenK
movlw 0x0A ; Ten
Lb1:
decf Tens,F
addwf Ones,F
btfss STATUS,C
bra Lb1
Lb2:
decf Hund,F
addwf Tens,F
btfss STATUS,C
bra Lb2
Lb3:
decf Thou,F
addwf Hund,F
btfss STATUS,C
bra Lb3
Lb4:
decf TenK,F
addwf Thou,F
btfss STATUS,C
bra Lb4
return
I made sure to clearly mark the assumptions it makes, which I think is a good idea any time a subroutine uses a variable beside W.
Previously we just put ADRESL in W, which only allows us to play with the least significant 8 bits of the A/D conversion. Since bin16_bcd assumes NumL and NumH, let's change our ADC_Get function to put ADRESH and ADRESL in NumH and NumL, so the whole sub looks like this:
; Gets the analog value of a pin and puts it in NumH and NumL
ADC_Get
bsf ADCON0, GO ; start conversion
adc_wait ; don't move on until the conversion is done.
btfsc ADCON0, DONE
goto adc_wait
movff ADRESH, NumH
movff ADRESL, NumL
return
Transmitting the Data
Transmitting the data is pretty simple. We'll send the TenK digit, then the Thou digit, etc. We also want to label each number put out so that we could have more than one number being transmitted and PD could separate them with a [route] object. We also want an End Of Line (EOL) character so delineate the end of each individual transmission and display the nubmers in a column in hyperterminal. Finally, we want to add a character that delineates the tag for route from the number itself. Here's the variable definitions for the delimeter, EOL, and bin16_bcd:
; Define Constants
DELIMETER equ 0x02
EOL equ 0x03
; Define variables
char equ 0x04
NumH equ 0x05
NumL equ 0x06
TenK equ 0x07
Thou equ 0x08
Hund equ 0x09
Tens equ 0x0A
Ones equ 0x0B
; initialize variables and constants
movlw ' '
movwf DELIMETER
movlw '\n'
movwf EOL
Now we'll write a macro that sends a number to PD (or hyperterminal). It takes 6 arguments: the tag character and 5 digits.
Send_arg MACRO tag, dig1, dig2, dig3, dig4, dig5
movf tag, W
call UART_Put
movf DELIMETER, W
call UART_Put
movf dig1, W
call UART_Put
movf dig2, W
call UART_Put
movf dig3, W
call UART_Put
movf dig4, W
call UART_Put
movf dig5, W
call UART_Put
movf EOL, W
call UART_Put
endm
Finishing Up
Our main loop will get the A/D conversion, convert it into decimal, encode that into ASCII, and send the result. Here's the final code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; psio-1.asm
; Dumps values of pins to the UART for reading from Pure Data.
;
; by Ian Smith-Heisters
; November 18th, 2004: Started
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
title "Parallel Serial IO"
List p=18f452,f=inhx32
#include <p18f452.inc>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;The config aliases are already defined in the 18f452 header file.
__CONFIG _CONFIG1H, _OSCS_ON_1H & _XT_OSC_1H ; External Clock on OSC1 & OSC2
__CONFIG _CONFIG2L, _BOR_ON_2L & _BORV_20_2L & _PWRT_OFF_2L ; Brown out reset on at 2.0V, no power-up timer
__CONFIG _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H ; watchdog off, postscaler count to 128
__CONFIG _CONFIG3H, _CCP2MX_ON_3H ; CCP2 pin Mux enabled. What is this?
__CONFIG _CONFIG4L, _STVR_ON_4L & _LVP_ON_4L & _DEBUG_OFF_4L ; Stack under/overflow reset on, LVP on, debug off
__CONFIG _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L ; all protection off
__CONFIG _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
__CONFIG _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L
__CONFIG _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
__CONFIG _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
__CONFIG _CONFIG7H, _EBTRB_OFF_7H
org 0x0000
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Variable Definitions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Define Constants
COUNT1 equ 0x00
COUNT2 equ 0x01
DELIMETER equ 0x02
EOL equ 0x03
; Define variables
char equ 0x04
NumH equ 0x05
NumL equ 0x06
TenK equ 0x07
Thou equ 0x08
Hund equ 0x09
Tens equ 0x0A
Ones equ 0x0B
; initialize variables and constants
movlw ' '
movwf DELIMETER
movlw '\n'
movwf EOL
; Set port D to all output
clrf TRISD
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Macro Definitions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Send_arg MACRO tag, dig1, dig2, dig3, dig4, dig5
movf tag, W
call UART_Put
movf DELIMETER, W
call UART_Put
movf dig1, W
call UART_Put
movf dig2, W
call UART_Put
movf dig3, W
call UART_Put
movf dig4, W
call UART_Put
movf dig5, W
call UART_Put
movf EOL, W
call UART_Put
endm
ENCDA MACRO x
movlw 0x30 ; convert to 0-9 in ASCII
addwf x, F
endm
; call initialization subs
call UART_Init
call ADC_Init
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Main loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Main
movlw 'a'
movwf char
call ADC_Get
call bin16_bcd ; assumes NumH, NumL, TenK, Thou, Hund, Tens, Ones
ENCDA TenK ; coerce into ASCII encoding
ENCDA Thou
ENCDA Hund
ENCDA Tens
ENCDA Ones
Send_arg char, TenK, Thou, Hund, Tens, Ones
incf char, F ; increment char this is pointless now, but will be useful for
; using more than one ADC pin.
goto Main
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Subroutine Definitions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
#include <UART.inc>
; Initialize analog input
ADC_Init
; Set port A to AN0 input
bsf TRISA, AN0
; Set port A to be an analog input see pp.183 and 184 in the datasheet
movlw B'00000001'
movwf ADCON0
movlw B'11001110'
movwf ADCON1
return
; A basic delay, used for flashing LEDs.
Delay
setf COUNT1
setf COUNT2
Loop1 decfsz COUNT1, F
goto Loop1
setf COUNT1
decfsz COUNT2, F
goto Loop1
return
; Gets the analog value of a pin and puts it in NumH and NumL
ADC_Get
bsf ADCON0, GO ; start conversion
adc_wait ; don't move on until the conversion is done.
btfsc ADCON0, DONE
goto adc_wait
movff ADRESH, NumH
movff ADRESL, NumL
return
; Binary (16-bit) to BCD. From Microchip PICDEM 2 PLUS DEMO BOARD
; xxx = highest possible result
bin16_bcd:
; Takes number in NumH:NumL
; Returns decimal in TenK:Thou:Hund:Tens:Ones
swapf NumH,W
andlw 0x0F
addlw 0xF0
movwf Thou
addwf Thou,F
addlw 0xE2
movwf Hund
addlw 0x32
movwf Ones
movf NumH,W
andlw 0x0F
addwf Hund,F
addwf Hund,F
addwf Ones,F
addlw 0xE9
movwf Tens
addwf Tens,F
addwf Tens,F
swapf NumL,W
andlw 0x0F
addwf Tens,F
addwf Ones,F
rlcf Tens,F
rlcf Ones,F
comf Ones,F
rlcf Ones,F
movf NumL,W
andlw 0x0F
addwf Ones,F
rlcf Thou,F
movlw 0x07
movwf TenK
movlw 0x0A ; Ten
Lb1:
decf Tens,F
addwf Ones,F
btfss STATUS,C
bra Lb1
Lb2:
decf Hund,F
addwf Tens,F
btfss STATUS,C
bra Lb2
Lb3:
decf Thou,F
addwf Hund,F
btfss STATUS,C
bra Lb3
Lb4:
decf TenK,F
addwf Thou,F
btfss STATUS,C
bra Lb4
return
end
That should be enough to get you started. It's easy to add more sensors and give them different tags so they can be transmitted in parallel. Building the sensors themselves is up to you.