Program description

What does it do?

The analogue program reads an analogue value from the potentiometer, or one of the other analogue devices on the CHRP board, and displays the result as a binary number on the LEDs.

New concepts

The analogue program demonstrates the use of a return value from a function as shown by the following code:

			
	PORTB = adConvert(ADCH1);
			

The function adConvert is called and passed the constant representing the input channel. After conversion, PORTB is assigned the value representing the analogue input.

Logical operations

This program also demonstrates the use of logical operations to selectively clear and set bits within a register.

Control registers for microcontroller peripherals often contain bits that need to be manipulated separately. Writing to a register re-writes all 8 bits in the register. Logical operations can set or clear individual bits, or combinations of bits, while leaving other bits in their current states.

Analogue programming activity

The Analogue program demonstrates the process of converting an external analogue potential into a binary value, the use of logical instructions to modify single bits within a register, and how to use variables returned from a function call.

What you should know before starting

Analogue to digital conversion

If you measured the potentiometer potentials in the Apply your skills portion of the Input activity, you would have seen a very limited form of analogue-to-digital conversion, or quantization (converting a variable input into a specific, fixed quantities). Varying the potentiometer's position produced a variable output potential that was sensed as either a logic zero or logic one by the digital input of the microcontroller. The microcontroller's input circuit determined the point at which the input was recognized as a zero or as a one.

A-D%20Conversion

An analogue-to-digital (A-D) converter is a circuit that converts an input potential (with an infinite range of states) into a digital number with a limited number of states. An 8-bit A-D converter divides the 5V input range into 256 states as shown in the graph, above. A 10-bit A-D converter would divide the 5V input range into 1024 states.

The microcontroller used in the CHRP board has an on-board 10-bit A-D converter, but for most purposes an 8-bit conversion provides enough accuracy. Dividing a 5V signal into 256 states results in an input sensitivity of about 19.5mV per step. That is, for every 19.5mV input change, the output of our 8-bit A-D converter will change by one step. Each step is represented by a number from 0 to 255. A 1V input would be represented as the digital number 51, for example.

Microcontroller related information

The PIC16F886 microcontroller hardware contains a 10-bit ADC (analogue-to-digital converter) which is connected to 11 of the Port A and Port B input pins through an input selector. The A-D converter is a separate circuit from the microcontroller core, and must be powered up and initialized before a conversion can begin. It can be powered down at any time between conversions to reduce the microcontroller's current consumption.

The A-D conversion process is performed by guessing (intelligently, though) using a successive approximation register, D-A converter and a comparator. Think about it as homing in on the analogue value by repeatedly guessing the voltage, comparing its guess to the input, and reducing its possibilities by half each time. (eg. If I'm thinking of a number between 1 and 10, and you have to guess it, a good first guess would be 5. If the number was higher, your next guess might be 7, and so on.) Using this method, it takes 10 A-D converter clocks (one guess per clock) to arrive at the final output value. The A-D converter clock is independent of the microcontroller clock and typically runs at a slower rate.

The 10-bit digital conversion result is stored in two registers and can be configured to be read out as a right- or left-justified number. This program will only use the eight most significant bits of the result, providing a possible 256 states, rather than the full 1024 states a 10-bit conversion would provide.

Create the analogue program

Create the analogue project using the same files as the previous projects except for CHRP3.c and the main Analogue.c file. Create the CHRP3.c file using this code:


/*==============================================================================
	CHRP 3.1 (PIC16F886) hardware initialization and user functions.
==============================================================================*/

#include	"xc.h"				// XC compiler general include file

#include	"stdint.h"			// Include integer definitions
#include	"stdbool.h"			// Include Boolean (true/false) definitions

#include	"CHRP3.h"			// Include user-created constants and functions

void initPorts(void)
{
	// TODO - Initialize oscillator, ports, and add (optional) user functions.

	// Initialize user ports and peripherals.

	ANSEL = 0b00011111;			// Make RA0,1,2,3, and RA5 analogue inputs
	ANSELH = 0b00000000;		// Make all PORTB pins digital I/O
	ADCON0 = 0b01000000;		// Set 4MHz conversion clock, A/D off,
	ADCON1 = 0b00000000;		// left justified result, VDD reference

	OPTION_REG = 0b01010111;	// PORTB pull-ups on, TMR0 internal div 256

	// Set port directions for I/O pins: 0 = Output, 1 = Input

	TRISA = 0b00101111;			// Set runLED, IR LEDs as outputs in PORTA
	TRISB = 0b00000000;			// Set PORTB for LED output
	TRISC = 0b10110000;			// Set receive and transmit lines for IR
								// demodulator U5 and LED11, servo outputs

	// Set starting I/O conditions.

	PORTA = 0;					// Turn off all PORTA outputs, turn on run LED
	PORTB = 0;					// Turn off all PORTB LEDs
	PORTC = 0;					// Turn off all PORTC outputs

	// Enable interrupts, if needed.
}
		

The difference between this file and the previous versions of CHRP3.c is the setup of the A-D converter device as well as the initialization of some of the I/O pins as analogue inputs.

Create the Analogue.c program file

Create the Analogue.c file using this code:


/*==============================================================================
	Project: Analogue
	Version: 3.1				Date: November 24, 2014
	Target: CHRP3				Processor: PIC16F886

	Analogue input and display program. Converts the analogue value from the
	selected input to an 8-bit number and displays it on the LEDs. Demonstrates
	passing and return of variables between functions, bit manipulation through
	AND and OR logic operations, and in-line assembly code instructions.
==============================================================================*/

#include    "xc.h"              // XC compiler general include file

#include    "stdint.h"          // Include integer definitions
#include    "stdbool.h"         // Include Boolean (true/false) definitions

#include    "CHRP3.h"            // Include user-created constants and functions

/*==============================================================================
	A/D conversion function. Pass the channel to be converted using one of the
	definitions in CHRP3.h. Returns the most significant 8-bits of the result.
==============================================================================*/

unsigned char adConvert(unsigned char chan)
{
	ADON = 1;					// Turn A-D converter on
	ADCON0 = (ADCON0 & 0b11000011);	// Change the conversion channel by wiping
	ADCON0 = (ADCON0 | chan);		// CHS bits and ORing with selected channel
	NOP();						// Wait to allow the input to stabilize
	GO_DONE = 1;				// Start the conversion

	while(GO_DONE);				// Wait for the conversion to finish (GO_DONE=0)
	
	ADON = 0;					// Turn A-D converter off and
	return (ADRESH);			// return the upper 8-bits of the result
}

/*==============================================================================
	Main program code
==============================================================================*/

int main(void)
{
    // Initialize I/O and peripherals
    initPorts();

    // Set starting conditions

    while(1)
    {
		PORTB = adConvert(ADVR1);		// Display VR1 input value on PORTB LEDs
										// See CHRP3.h for other possible inputs
		__delay_ms(100);
    }
}
		

Download the program into the CHRP and verify its operation by turning the potentiometer. The Port B LEDs will output the binary number corresponding to the position of the potentiometer.

How the program works

In the CHRP3.c file:


	ANSEL = 0b00011111;			// Make RA0,1,2,3, and RA5 analogue inputs
	ANSELH = 0b00000000;		// Make all PORTB pins digital I/O
	ADCON0 = 0b01000000;		// Set 4MHz conversion clock, A/D off,
	ADCON1 = 0b00000000;		// left justified result, VDD reference
		

As discussed in the Output and Input activities, the TRISA register configures the pins of Port A to be inputs or outputs. The ANSEL register configures which Port A pins will be analogue inputs — ANSELH does the same for Port B. The ADCON0 and ADCON1 registers control the operation of the A-D converter (see the microcontroller datasheet for all of the ADC setup and channel selection details).

The adConvert function

The adConvert function accepts a channel parameter, and, after A-D conversion finishes, returns the conversion result.

		
unsigned char adConvert(unsigned char chan)
{
	ADON = 1;					// Turn A-D converter on
	ADCON0 = (ADCON0 & 0b11000011);	// Change the conversion channel by wiping
	ADCON0 = (ADCON0 | chan);		// CHS bits and ORing with selected channel
	NOP();						// Wait to allow the input to stabilize
	GO_DONE = 1;				// Start the conversion

	while(GO_DONE);				// Wait for the conversion to finish (GO_DONE=0)
	
	ADON = 0;					// Turn A-D converter off and
	return (ADRESH);			// return the upper 8-bits of the result
}
		

The first line of the function declaration starts with the return type unsigned char, indicating that the function will return an 8-bit character to the code that called adConvert. The calling code is also expected to supply a channel selector, in the form of an 8-bit character that will be stored in the local chan variable.

adConvert begins by powering up the on-chip A-D converter using the ADON = 1; statement. Two logical operations, an AND, represented by &, followed by an OR, represented by |, are used to selectively clear and then set the A-D channel selection bits in the ADCON0 registers. The Logical operations section, below, explains exactly how this works.

After the channel is written to ADCON0, the NOP(); statement provides a time delay (necessary at higher clock speeds) to ensure an accurate conversion. After selecting or switching channels, the A-D input circuit (essentially a capacitor) needs time to charge/discharge to the value on the newly-selected channel.

Finally, setting the GO_DONE bit starts the conversion process. When the conversion is complete, the A-D converter circuit will reset the GO_DONE bit, but this won't happen until 10 A-D clock cycles have passed.

The while(GO_DONE); condition uses a logical inference to wait until the conversion is done. A logic level of 1 is considered to be true, while a 0 level is considered false. While conditions execute while true — shown by the while(1) statement that makes up the main processing loop — meaning the condition is evaluated and assigned a value of 1 if true. In this case, since the GO_DONE bit is already logic 1 while the conversion is in progress, we can use the state of the bit itself instead of evaluating the condition.

So, since GO_DONE is 1, we can use the logical shortcut:

	
	while(GO_DONE);
		

..instead of writing:

	
	while(GO_DONE == 1);
		

Once the conversion is finished, the A-D converter is turned off, and the high byte of the result, stored in the ADRESH register, is returned to the calling function. The ADRESH (A-D RESult High) register is one of two registers that holds the 10-bit conversion result. By left-justifying the result and reading only the high byte, the program retrieves only the most significant eight bits of the conversion. Since ten bits of accuracy are not required for this program, processing an eight bit result is more convenient for our display.

The main code then displays the 8-bit conversion result on the eight LEDs:


    while(1)
    {
		PORTB = adConvert(ADVR1);		// Display VR1 input value on PORTB LEDs
										// See CHRP3.h for other possible inputs
		__delay_ms(100);
    }
		

Logical operations

Logical gate instructions can be used to modify some of the bits in a register without changing other bits. This is important when the state of a register is not known, and writing to the register might over-write bits that were pre-set, pre-cleared or modified by other functions.

To see how this works, take a look at the following logic gate truth tables. Imagine that the A input is under our control (through the program), and the B input is the value already in a register. Divide each truth table into a top half (in which A is 0), and a bottom half (in which A is 1).

AND Gate
A B X
0 0 0
0 1 0
1 0 0
1 1 1
OR Gate
A B X
0 0 0
0 1 1
1 0 1
1 1 1

If you compare the AND gate's X output while A is 0, and again while A is 1, you can see the following pattern that the AND gate produces:

when A=0, X=0, and,

when A=1, X=B.

So, using an AND operation, we can either clear the bit (by making A=0), or to leave the bit value unchanged (by making A=1).

Similarly, for the OR truth table, we can use the A input to either leave the register unchanged (A=0), or set the register (A=1).

Now, let's look at the actual gate operation that takes place in the adConvert function. The middle four bits contain the A-D channel selector. Assume that the channel is set to 1 (0001) before we pass the channel 2 value (0010). Here's what happens:


   AND constant (A)   11000011
   ADCON0 (B)         01000101
                      ========
   Result (X)         01000001
   		

Any bits in the register ANDed with a 1 will remain unchanged. The bits ANDed with a 0 will be cleared.


  OR constant (A)     00001000	(from chan)
  ADCON0 (B)          01000001
                      ========
  New Result  (X)     01001001
  		

Any bits in the register ORed with a 0 will remain unchanged. The bits ORed with a 1 will be set.

By using logical operations such as AND and OR, we can change as many (or as few) bits in a register as we need to, without modifying any of the other bits that need to remain unchanged.

Test your knowledge

  1. Look up the A-D converter channel definitions in CHRP3.h. Which A-D channels correspond to phototransistors Q1 and Q2?
  2. What value would you need to AND with 01001111 to clear only the two least-significant bits?
  3. What value would you need to OR with 00001010 to set the three most-significant bits?
  4. How can you tell if a function returns data to the calling code?

Apply your skills

  1. Rotate the potentiometer all the way counterclockwise. Record the conversion value. Next, rotate the potentiometer all the way clockwise. Record this value as well. Are the values what you expected? What binary value should correspond to the mid-way position? Does it?
  2. Binary numbers are not meaningful to people who don't understand binary. Come up with a method to convert the potentiometer's input value into a bar-graph display on the LEDs.
  3. If you have installed the phototransistors (Q1, Q2), the temperature sensor (T1), or the potential divider resistors (R11 and R12), change the A-D converter input to one of these devices and monitor the conversion output on the LEDs. For the phototransistors, what binary quantities correspond to 'light' and 'dark'. For the temperature sensor, determine what quantity represents the current temperature. (Look up the MCP9701 temperature sensor's data sheet to learn about its offset.) The potential divider's output value represents the potential supplied to the CHRP circuit's pre-regulated DC input, and its resistors have been carefully chosen to divide and scale the value to a meaningful result.