ADC (Analogue to Digital Conversion)
The Analogue to Digital module on the PIC32 is very flexible and hence reasonably complex. This means there are a fair few registers that need configuring before use. The built in ADC conversion words will do the basics but for more complex control the registers will need to be accessed directly. Some examples of this are given at the end of this text.
A to D is a two stage process, first a sample is taken and then that sample is converted ready to be presented to a variable. To set up the ADC the following is used:
openadc <sample><list of inputs>
<sample> Is a number form 1 to 31 and will determine how long the sampling time is. This value is multiplied by the conversion time which is fixed at approximately 1.5uS. This if a sample of 10 is used then the sample time is 15uS.
<list of inputs> This is a list of all of the ADC channels that are required to be open, for example 0,8,2 would open 3 channels, channel 0, channel 8 and channel 2. Port B is used as the ADC channels:
| ADC Channel | Port Pin |
| AN0 | RB0 |
| AN1 | RB1 |
| AN2 | RB2 |
| AN3 | RB3 |
| AN4 | RB4 |
| AN5 | RB5 |
| AN6 | RB6 |
| AN7 | RB7 |
| AN8 | RB8 |
| AN9 | RB9 |
| AN10 | RB10 |
| AN11 | RB11 |
| AN12 | RB12 |
| AN13 | RB13 |
| AN14 | RB14 |
| AN15 | RB15 |
The port pin is automatically set to an input when the channel is opened.
To carry out a simple test enter the following:
openadc 31 0 // opens channel 0 for ADC
print adc(0) // returns the value on that pin (JP3-16) on the BV513
The value returned could be anything, try connecting it to ground and then 3V3. Even placing a finger on the pin will produce a different result.
IMPORTANT The maximum voltage allowed on this port is 3.3V, damage may occur if it is connected to the 5V supply.
A simple Practical Example
All LED's believe it or not are light sensitive, not very much but enough to show up on the ADC values, try this:

Connect the above circuit to the AN0 channel (JP3-16) and use this small function:
openadc 31 0 // opens AN0
while key?(2) = 0 // continues until a key is pressed
print adc(0)
wait 250
wend
endf
You should notice a change in values when the LED is covered by the hand, replace the LED with an LDR (light dependant resistor) for more dramatic results.
ADC to Voltage
By default the reference is taken from the +3.3V rail, on the BV513, this is passed through a 100R resistor and smoothed with a 2.2uF capacitor so the voltage may be very slightly less, say 3.28V but this marginal difference will have no effect for the majority of likely applications. To obtain a voltage rather then a digital number simply divide the reference voltage by 1023 and multiply by the AD value e.g.
3.3 / 1023 = 3.22mV or 0.00322V
This constant can be applied to the result, so if the AD reading was say 150, then the voltage would be:
0.00322 * 150 = 0.483V
All this of course can be done in the program. To obtain accurate results another channel can be used to read a voltage reference to calibrate this constant first. This could even be done prior to each reading if required.
Measuring Temperature
There are a few electronic devices that vary in resistance as the temperature varies. Well, all device do but some are designed to, the main one being a thermistor.
![]() |
Some thermistors look like this. |
By applying a voltage in a circuit similar to the above, the change in resistance can be made to vary a voltage (Vout) that can then be measured by the ADC. Unfortunately thermistors are not particularly linear in operation and in order to make sense of the measurement calibration will be required, i.e. mapping voltage to temperature.
There is also another very low cost device on the market that has been around for many years. This is an integrated circuit with the part number LM35. This will output a voltage proportional to the temperature and is accurate to 0.5 degrees C. This level of accuracy is quite good when compared to similar parts (LM60 for example at +-2.5 degrees C). There are various options available for the LM35 that output in Fahrenheit for example but for this project the LM35DZ is used that outputs a voltage proportional to the Celsius scale.
Circuit
The LM35 looks like a transistor but it is in fact an integrated circuit. The pin designations are below.

The LM35 has a wide operating voltage range form 4V to 30V. This means that it will not work from the 3V3 supply but it will work from the 5V supply. The data sheet has a very complicated way of saying that 10mV represents 1 degree C, thus if a voltage of 0.240V is read from Vout the temperature is 24 degrees C. This fact will be used when reading the A to D to determine the temperature.

The LM35 comes in a small package like a transistor and the diagram shows the connections looking form the underside.
C1 = 100uF
R1 = 100R
The large capacitor and resistor are to smooth out the noise form this device. This smoothing can also be done in software by reading the temperature many (100 or so) times over a period (say 1 second) and averaging the result. Without this it is surprising how the temperature reading varies from instant to instant.

On a practical note it was found that attaching the LM35 to a flying lead made it easier to experiment with. The supply must be taken from the +5V as the device operates form 4V to 30V according to the datasheet.
constant MAX_V# 3.3
constant MAX_CONV# 1023
function adc_temp
tempv#=(MAX_V#/MAX_CONV#)*adc(1)
print format$("#.##",tempv#*100)
endf
This is the basic code that is needed to display the temperature to 2 decimal places. In the above circuit diagram the output of the LM35 is connected to Analogue Channel 1 and so this will need initialising first.
openadc 31 1
adc_temp
The above would display the temperature to screen.
Amplification and Offset
The LM35 connected to a 5V supply will have a temperature range of 0 to 85 degrees. More often than not it is quite likely that sub zero temperatures will be needed, for measuring air temperature a range of -20 to +40 is more sensible To operate the LM35 over a sensible temperature range an offset is required.
At the same time if amplification is used then a more accurate reading from an ADC can be obtained (within the limits of the LM35). The offset is required for negative temperatures, instead of connecting the LM35 to ground, it is connected to an offset voltage then –ve temperatures will not attempt to produce negative voltages.
As an example given an offset of 1070mV a reading of 22 degrees C would be 1.070 + 0.22V = 1.29V and it follows then that a reading of -20 will be: 1.070V - .2V = 870mV To represent temperatures from -20 to say +50 using this offset would produce a range of 870mV to 1570mV. This of course can be used directly into the ADC but it would be better to produce a range that represented the above to give 0 to 3V3 as near as possible. This would give the most resolution. Two circuits are presented, the first only differs in the way the offset voltage is generated.

Offset Voltage here is generated by an Op Amp.

Offset voltage here is generated by two diodes. The output of this circuit can be taken into one of the AD channels. For really accurate results it may also be worthwhile experimenting with using a second AD channel connected to the offset voltage. In this way any voltage variations due to the ambient temperature of fluctuations in supply could be compensated for.
Register Access
As previously mentioned the ADC module on the PIC is very complex because it offers so may options. It is impractical to cover all of these in a few BASIC statements. Should any more complex requirements be needed then the registers will need to be accessed directly, blow gives an idea of how this may be done.
The information for this has been taken from the PIC32 family datasheet.
constant CLR 4
constant SET 8
constant INV 12
constant AD1CON1 0xbf809000
constant AD1CON2 0xbf809010
constant AD1CON3 0xbf809020
constant AD1CHS 0xbf809040
constant AD1PCFG 0xbf809060
constant AD1CSSL 0xbf809050
constant ADC1BUF0 0xbf809070
Some of the above registers will be used, the names chosen follow the same names as those defined in the PIC32 datasheet so this should make it easier to follow.
// e.g. if RB5 is used then: adc_init 5
function adc_init(chan)
pokei(AD1PCFG)=0 // all digital
pokei(AD1PCFG+SET)=lshift(1,chan)
pokei(AD1CHS+SET)=lshift(chan,16)
pokei(AD1CON1)=0
pokei(AD1CSSL)=0
pokei(AD1CON2)=0
pokei(AD1CON3)=0x0002 // max conversion for 80MHz
pokei(AD1CON1)=0x8000
endf
This is the initialisation routine which will initialise just 1 channel, this actually is taken more or less directly from the code in the datasheet and is designed for the 80MHz clock rate. Just for interest the C instruction:
AD1CHS = 5 << 16;
is the Basic instruction
pokei(AD1CHS+SET)=lshift(chan,16)
Where chan=5 the conversion is a two step process, bit 1 of AD1CON1 is set to begin the sampling process, after a period, this bit is cleared and the conversion begins, when complete (indicated by AD1CON1 bit 0) it is ready to be read from the buffer.
function adc_conv
pokei(AD1CON1+SET)=2
wait 100
pokei(AD1CON1+CLR)=2
while and(peeki(AD1CON1),1)=0 : wend // wait to finish
result peeki(ADC1BUF0)
endf
This function will return the value for the selected channel.
Automatic Conversion
It is just a small step to be able to eliminate the 100 ms wait by utilizing the automatic conversion feature. The set up changes that are required are:
pokei(AD1CON1)=0x00e0 // automatic sampling
pokei(AD1CON3)=0x1f02 // sample time = 3Tad
pokei(AD1CON1+SET)=0x8000 // turn on adc
These changes are needed to the set up
function adc_conv
pokei(AD1CON1+SET)=2 // start sampling
while and(peeki(AD1CON1),1)=0 : wend // wait to finish
result peeki(ADC1BUF0)
endf
This is the automatic sampling code, which is actually simpler than the manual code.
