ADC

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:

Achematick of LED as LDR

Connect the above circuit to the AN0 channel (JP3-16) and use this small function:

function adc_test
    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.

thermistor circuit 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.

lm32 pin out

 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.

lm35 circuit diagram

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.

lm35 on flying leads

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.

// temperature conversion using the LM39
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.

lm35 offset circuit diagram 1

Offset Voltage here is generated by an Op Amp.

lm35 offset circuit diagram 2

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.

// A to D registers
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.

// initialises a single channel (pin) on Port B
// 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.

// simple, manual conversion
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

// Automatic sampling
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.