Tutorial 3, Part 1 – Functions and A/D conversion
The final product of this tutorial will be an LED whose brightness depends on a potentiometer setting. In this part, the A/D conversions from the potentiometer will change the blinking rate of the LED.
Many different concepts were lumped into this tutorial for the following reason: I like to feel a sense of accomplishment when I finish a tutorial. I don’t like going through numerous tutorials, only to realize that I’ve learned a couple of concepts. I will still separate the concepts into subsections, but they should feel like a part of one big project.
Analog-To-Digital Conversion: A/D conversion is useful for determining the value of an analog voltage. The setup builds on the design described in Tutorial 2. Added is a potentiometer, used as a variable voltage divider. In other words, connections on the potentiometer are made as follows:
- pin 1 – GND
- pin 2 – PORT A, PIN 0 (2)
- pin 3 – 5
When the potentiometer’s “wiper” is adjusted, voltage on pin 2 varies from 0 to 5v, providing a perfect way to test the A/D functionality of the microcontroller.
Time required for A/D:
To be able to perform an A/D conversion, the microcontroller input pin is connected to an internal capacitor. Magic (discussed in 18F4550 datasheet section 21) converts this capacitor voltage (which matches the voltage on the pin) to an accurate digital value. Unfortunately, this process is not instant. According to datasheet section 21.1, before the actual A/D conversion takes place, the acquisition time (Tacq – delay required before each A/D conversion), with a 5V microcontroller power source and (the maximum recommended) 2.5KOhm impedance on the input pin is approximately 1.05 milliseconds at room temperature + a .2uSec “Amplifier Settling Time” + .02uSec per degree above 25C =
1.25uSec with a (maximum recommended) 2.5KOhm impedance on the input pin. In ideal situations (<25deg.C operating temperature and no impedance on the input pin), the minimum Tacq is around .77uSec. Don't get excited about getting a 1.3Mhz polling rate though. I'll discuss the amount of time it takes to actually perform the A/D conversion shortly.
The A/D process, taken from the datasheet
- Configure the A/D module:
- Configure analog pins, voltage reference and digital I/O (ADCON1)
- Select A/D input channel (ADCON0)
- Select A/D acquisition time (ADCON2)
- Select A/D conversion clock (ADCON2)
- Turn on A/D module (ADCON0)
- Configure A/D interrupt (if desired):
- Clear ADIF bit
- Set ADIE bit
- Set GIE bit
- Wait the required acquisition time (if required).
- Start conversion:
- Set GO/DONE bit (ADCON0 register)
- Wait for A/D conversion to complete, by either:
- Polling for the GO/DONE bit to be cleared
- Waiting for the A/D interrupt
OR
- Read A/D Result registers (ADRESH:ADRESL);
- clear bit ADIF, if required.
- For next conversion, go to step 1 or step 2, as required. The A/D conversion time per bit is defined as Tad. A minimum wait of 3 Tad is required before the next acquisition starts.
MOVLW B'00001110';VSS,VDD ref. AN0 analog only
MOVWF ADCON1
TRISD is the register that determines the direction of the pins on PORT D. This is not the case for the other ports so the clrf TRISD instruction is just a reminder to set port direction before doing anything else with it. ADCON1 (page 262 in 18F4550 datasheet) is used to set the voltage reference of the pins. They may be set to either Vdd and Vss or a voltage provided on AN2 (4) and AN3 (5). The last 4 bits of ADCON1 are used to set the number of analog input pins. Up to 13 analog inputs may be enabled on the 44 pin devices. For now, only AN0 will be enabled.
CLRF ADCON0 ;clear ADCON0 to select channel 0 (AN0)
Only one Analog channel may be read at one time. ADCON0 (page 261 in 18F4550 datasheet) can be used to select the channel. It can also be used to enable the A/D converter module (something that needs to be done before any A/D conversions will take place) and to check whether an A/D conversion has completed. To select channel 0 (AN0) while keeping the A/D converter module off (until the configuration is done) we will set ADCON0 to all 0s using the CLRF instruction (Clear F register).
;ADCON2 setup: Left justified, Tacq=2Tad, Tad=2*Tosc (or Fosc/2)
MOVLW B'00001000'
MOVWF ADCON2
The acquisition time and A/D conversion clock need to be set in ADCON2 (page 263 in 18F4550 datasheet).
To set these parameters, you must first understand what they are. The A/D conversion clock (Tad) is the amount of time it takes the A/D module to convert one bit. It takes 11 Tad to perform the 10-bit A/D conversion (I’m not sure where the other Tad goes). The choices, selected by bits 2-0 of ADCON2, are in terms of the microcontroller clock (Tosc). According to the datasheet, Tad must be selected to be as short as possible but greater than the minimum Tad (.7us for the 18F4550 – p400, parameter 130). Since we are running at the default clock speed of 1Mhz, Tosc = 1/1,000,000 = 1us. This means that even the lowest selectable value of Tad, 2Tosc = 2us, is longer than .7us. A handy chart to figure out Tad based on microcontroller clock frequency has been provided for the lazy (source – datasheet):

The acquisition time parameter, used to reduce software overhead, is the amount of Tad cycles to delay before the actual A/D conversion starts. As I mentioned earlier, there should be a .2uSec delay to allow the amplifier to settle and another ~1uSec capacitor charging delay. It is possible to either keep track of this 1.2uSec delay in the program code or to simply set the A/D Acquisition Time Select bits (5:3) to an even multiple (2-20) of Tad to avoid having to worry about it in the program (set these bits to 0 to keep track in software). Again, the lowest selectable value on the A/D Acquisition Time Select bits (2Tad) is longer than the necessary delay.
Last, but not least, bit 7 (ADFM bit) of ADCON2 selects whether the 10 bit A/D result will be left-justified or right-justified. Since the microcontroller registers are only 8 bits, two registers are dedicated to hold the result (ADRESH:ADRESL):
. . .ADRESH . . : . . ADRESL. . .
7 6 5 4 3 2 1 0 : 7 6 5 4 3 2 1 0
X X X X X X X X . X X . . . . . . <-Left Justified
. . . . . . X X . X X X X X X X X <-Right Justified
BSF ADCON0,ADON ;Enable A/D Conversion Module
The configuration is complete. To enable the A/D converter module, we will use the BSF (Bit Set F – place a 1 in a specified bit of a specified register) instruction to set bit 0 (ADON) in the ADCON0 register to 1. We will skip step 2 of the above instructions until a later point (Interrupt tutorial) and continue to step 3 – Start conversion.
MainLoop:
BSF ADCON0,GO_DONE ;Start A/D Conversion
BTFSC ADCON0,GO_DONE ;Loop here until A/D conversion completes
GOTO $-2
Setting the GO_DONE bit of ADCON0 (bit1) starts the A/D conversion. GO_DONE stays set until the conversion is over. Therefore, we use the BTFSC (Bit Test on an F register, Skip next instruction if Clear) instruction to wait for this bit to clear. If the bit is set, BTFSC continues to the next instruction – GOTO $-2 which brings it back 2 bytes from the current position ($ used as symbol for address of current instruction). The reason we want the program execution to move back 2 bytes (and not 1) is because all the instructions on this microcontroller are 16 bits (so all GOTO instructions must just multiples of 2).
BTG PORTD,RD1 ;Toggle PORT D PIN 1 (20)
Once the A/D conversion is complete, the LED output pin is toggled.
In the last tutorial, the following delay code was used:
Delay:
DECFSZ Delay1,1 ;Decrement Delay1 by 1, skip next instruction if Delay1 is 0
GOTO Delay
DECFSZ Delay2,1
GOTO Delay
GOTO MainLoop
The problem with this code is that it must be in the main loop of the program to work correctly. Function calls allow code to be written separately and called from the main loop without it actually being there. There only needs to be one change to make Delay a function. It must return. No big surprises here – the RETURN instruction replaces the GOTO MainLoop instruction.
Now, the instruction CALL Delay will execute the delay code until it sees a RETURN instruction. Then, it will bring program execution back to the instruction after CALL Delay.
One last thing needs to be changed in the Delay function. As of now, the code delays for a constant amount of time. A way to vary this delay according to the A/D results is as follows:
DelayAD: ;Delay function based on A/D result
MOVFF ADRESH,Delay2 ;move 8 MSb from A/D result to Delay2
;Make sure no overflows occur
INFSNZ Delay2,F
DECF Delay2,F
Delay:
DECFSZ Delay1,1 ;Decrement Delay1 by 1, skip next instruction if Delay1 is 0
GOTO Delay
DECFSZ Delay2,1
GOTO Delay
RETURN
At first, the INFSNZ and DECF instructions may look redundant, but they serve a very important purpose. Try taking them out to see what happens:
Instead of staying constantly on at one extreme and blinking slowly at the other, the LED blinks slowly at both extremes. This is because the DECFSZ instructions in the Delay loop decrement Delay2 before testing it. When the A/D result is brought to 0 (Delay should be minimal), the DECFSZ instruction makes Delay2 overflow to 255, making the delay very long. To prevent this, we increment Delay2 by 1 unless it is already at a maximum (hence the DECF).
Putting all the code together, you get something like this:
Files: tutorial3.asm