EE109 – Fall 2022: Introduction to Embedded Systems
Analog to Digital Conversion
This is an individual assignment. The code you write, use for the demo and submit should be wholly your own and not produced by working in teams or using, in part or in whole, code written by someone else (found online, from a fellow student, acquaintance, etc.). You will be asked to verify this when you submit your code on Vocareum by signing your name in the honor.txt file on Vocareum. Penalties for violation include a 0 on the assignment and a potential further deduction or referral to Student Judicial Affairs.
In this lab exercise you will use the Arduino's analog-to-digital (ADC) conversion capability to interface the microcontroller to a variable resistor, also known as a "potentiometer", and also to the buttons on the LCD module. The goal is to be able to implement a very simple video game on the LCD using the potentiometer and the buttons as the player's control. Writing the program will involve learning how to configure the ADC module by setting various register bits, how to initiate a conversion and how to draw characters on the LCD screen based on the conversion results.
Recommended viewing: Video on analog-to-digital conversion
Recommended reading: Chapter 7 in "Make: AVR Programming" on Analog-to-Digital Conversion
For a copy of the Lab 6 grading sheet click here.
Set up your Lab 6 assignment the same way we have done previous in labs by making a lab6 folder in your ee109 folder. You can copy the Makefile, lcd.c and lcd.h from Lab 5 to the lab6 folder.
From the class website download the file "lab6.zip" and extract the contents into the lab6 folder. The Zip file contains four files:
lab6.c- a template for the main part of the Lab 6 program
adc.c- a template for the ADC routines
adc.h- header file for the ADC routines
Lab6_Answers.txt- Edit this file to answer the review questions at the end of this web page, and upload to Vocareum.
In previous labs we put the routines for the LCD in a separate file, lcd.c, with the function definition in lcd.h. For using the ADC, we now want to do the same thing by having the ADC functions and definitions in the files adc.c and
Make sure to update your Makefile to replace lab5.o with lab6.o, and also add adc.o to the OBJECTS line.
The ADC Module
Information on using the ADC module is available in a lecture slide set. A more in-depth review of this material is provided in a separate document "Using the Atmel ATmega328P Analog to Digital Conversion Module" available on the class web site. Refer to any of these resources for information on configuring the various registers in the module and how to do the conversions.
Task 1: Write the ADC routines
adc.c files contains the template for two routines that you will have to write.
- adc_init() - This routine initializes the ADC module. It only needs to be called once at the start of the program but must be called before making any calls to the routine that takes the samples.
- adc_sample() - This routine has one argument, the channel number (0 to 5) to use for the ADC sample. It starts an analog-to-digital conversion on the specified channel, waits for it to complete and then returns the 8-bit value to the calling program.
The adc_init routine must set up the proper values in the ADMUX and ADCSRA registers. Diagrams of the registers are provided on the Lab 6 grading rubric for you to fill in the values.
- Set/clear the REFS[1:0] bits in ADMUX to select the high voltage reference. Using the AVCC reference is appropriate for this lab.
- Set or clear the ADLAR bit in ADMUX such that we will use 8-bit conversion results (not 10-bit).
- Set/clear the ADPS[2:0] bits in ADCSRA to select an appropriate prescalar value.
- Set the ADEN bit in ADCSRA to enable the ADC module.
- The other bits should be left as zeros. The ADSC bit (shown below in red) will be set to a one later in your code, but in the initialization of the registers is should be a zero.
The adc_sample routine is called to acquire a sample of the analog signal from the ADC. The channel to sample is specified as an argument from 0 to 5 when calling the adc_sample routine. The adc_sample routine is declared as a "unsigned char" function and the return value contains the result of the conversion and can be saved in an unsigned char variable. For example, to acquire a sample from ADC channel 2 and put the value in the variable
data, the code would be
data = adc_sample(2);
The routine should do the following.
- Set/clear the MUX[3:0] bits in ADMUX to select the input channel as specified by the argument to the function. Hint: Use bit copying.
- Set the ADSC bit in the ADCSRA register to a 1. This starts the conversion process.
- Enter a loop that tests the ADSC bit each time through the loop and exits the loop when ADSC is 0. This indicates that the conversion is now complete.
- Copy the 8-bit conversion result from the ADCH register and return it to the calling program.
The LCD Buttons
The buttons on the LCD use the ADC to determine which, if any, are being pressed. This is different than how buttons in previous labs were interfaced to the Arduino. Rather than having all the LCD buttons produce an individual digital signal, all five of them are instead interfaced through a multistage voltage divider creating a single analog signal that can be measured by the ADC. Depending on which button was pressed, or none, this circuit produces a different voltage between 0 and 5 volts, and this signal is attached to the ADC Channel 0 input to the Arduino. By using the ADC to convert this voltage to a number it's easy to determine if one of the five buttons was pressed.
Task 2: Determine the LCD buttons value
When a button is pressed the analog voltage on ADC channel 0 changes to a unique value that identifies the button that was pressed. However in order for your program to determine which button was pressed you have to know what the ADC output is for each button. The easiest way to do that is to write code to loop continuously reading the ADC result and writing the value to the LCD.
Use the ADC routines you just wrote to give the program the ability to use the ADC to read the button's analog signal. In the while(1) loop of the main routine add code to do the following.
adc_samplewith an argument of zero to read the value of the ADC channel 0 and store the returned value in a variable.
- Use the snprintf routine to format the ADC result into a string of numerical characters that can be displayed. (e.g. something like snprintf(buf, 5, "%4d", adc_result);
- Use your lcd_moveto function to move the cursor to the first character position on the first row.
- Use your lcd_stringout function to print the string on the display
Once the program is running, try pressing each button and see what value is displayed. You can record the values shown for each of the buttons on the Lab 6 grading sheet. These values will be used in later tasks. You should note that the ADC conversion results may not always be exactly the same each time a button is pressed. Due to electrical noise and thermal effects it may return, for example, 124 one time, 125 another and 126 yet another time. Your code must take this into account when working with the ADC results.
The Variable Resistor
For this lab assignment one of the inputs to the ADC of the Arduino will come from a variable resistor or potentiometer (or "pot" for short) hooked up to be a voltage divider. A potentiometer as shown below is a three-terminal device that has a fixed resistance between two of its wires and a third wire, sometimes called the "slider" that can be adjusted to give anywhere from zero to the full resistance between the slider wire and the other two. In short, the potentiometer operates as a voltage divider (two resistors in series) where the resistance in the numerator can be adjusted by dialing/spinning the "pot". Turn it fully one direction and you will see 0V on pin 2. Turn it fully in the other direction and you will see VS (5V) on pin 2. At any position in between you should the voltage on pin 2 scale linearly.
In schematic diagram above of a potentiometer, the total resistance between the top and bottom terminals of the potentiometer is given by R. Internally R can be viewed as two resistors R1 and R-R1 and in series so the resistance between the top and bottom terminals is fixed at R. Changing the position of the potentiometer's control changes the resistance of R1. Since the value of R is fixed, as R1 increases, R-R1 must decrease by an equal amount, and similarly if R1 goes down R-R1 must go up.
When hooked up as a voltage divider as above, we can see that V is given by
As the position of the slider is adjusted the value of R1 can change from R (making V=VS) to zero (making V=0). By adjusting the slider we can get any voltage from 0 to V=VS to appear on the slider terminal that will be connected to the ADC input.
Get the potentiometer from the parts provided in your kit and examine it. The resistance between the two fixed terminals (R in the above) is marked on the top next to the round control that is rotated. It should say "102" which is the resistance in the same format as used for marking fixed resistors: 1, 0, and two more zeros = 1000Ω.
The circuit is shown below. Note +5V and ground to the potentiometer are connected to your Arduino board's 5V and GND connectors.
On the potentiometer, the two pins that are on a line parallel to the sides of the device correspond to the top and bottom pins as shown above. The slider pin is the one offset from the other two.
- Insert the potentiometer into your breadboard so that all three pins are in different 5-hole connection strips as shown below.
- Make a connection from the slider pin's 5-hole strip to the Arduino A3 (ADC channel 3) input.
- Connect one of the other two pins (either one) to the 5V source on your Arduino board.
- Connect the remaining pin to ground.
To confirm that you have it connected properly, use the multimeter to check that the voltage on the slider pin can be changed by rotating the control.
- Turn on the DMM and set it for measuring DC volts.
- Hook the black lead of the DMM to the Ardunio ground, and hook the red lead to the slider pin which is connected to A3.
- Plug in your Arduino's USB cable into your computer so there is power to the Ardunio and the Arduino is providing 5V power to the breadboard.
- Set the DMM to measure DC volts and try rotating the pot with your screwdriver. The indicated voltage should change between +5V and ground as you rotate the pot.
Task 3: Check potentiometer and ADC routines
Before trying to use the potentiometer in the next part of this lab we recommend you check to see if it and the ADC routines are working. Your
lab6.c file should still have the code to read the ADC value on channel 0 for the buttons. Change the channel number in the
adc_sample function call to use channel 3 where the potentiometer is connected. Flash the new code to your Arduino, and then try rotating the potentiometer back and forth. The program should be displaying the numerical values of its conversions of the voltage on channel3, and these should be close to 0 at one end of the rotation and close to 255 at the other. If you are not seeing this, you will need to fix the problem before using the potentiometer and ADC code in the next task.
Task 4: Write the game
The game you are constructing works in the following manner.
- When your program starts it draws an 'X' character at a random position on the first row of the LCD.
- The 'X' character can be moved left or right by pressing the corresponding buttons on the LCD. Each time one of these buttons is pressed, the 'X' should move one column but stop when it hits the left or right side of the display.
- The position of the potentiometer determines the position of a caret '^' character on the second row. As the potentiometer is turned between the limits of its travel, the '^' should move back and forth between column 0 and column 15 of the second row.
- When the '^' is positioned under the 'X', and left there for two seconds, a message is printed on the screen saying you won the game.
- Once the game has been won, the program can just loop continuously doing nothing. It does not have to go back and start a new game.
At the start of the game the program should do the following.
- Initialize both the LCD and the ADC
- Initialize the random number generation routines by calling the
rand_init()function. This function is provided for you in the lab6.c file. IMPORTANT: The
rand_init()function uses the ADC so make sure you have called the
adc_init()function before calling
rand_init()or it will not work.
- Display a splashscreen as was done in Lab 5 identifying which lab this is and showing your name.
- Each time the game is played the 'X' should be in a different position on the screen. To do this, first use the
rand()function to generate a random number. Each time the
rand()function is called it returns a number between 0 and 32767 (0x7FFF). Your program should then convert this to a number between 0 and 15 and use that as the initial position of the 'X' on the first row. Note: In order to use the
rand()function, the program must have the line "
#include <stdlib.h>" near the top of the program.
Once the above steps have been done the program should enter an infinite loop for the playing of the game. Each time through the loop the following should be done.
- Use adc_sample to see if either the left or right button has been pressed.
- If a button was pressed change the position of the 'X':
- Erase the 'X' at the old position by moving there and writing a space (' ') character.
- Write an 'X' at the new position.
- Delay at least 100ms to allow the user to get their finger off the button.
- Use adc_sample to acquire a new 8-bit value for the position of the potentiometer.
- The 8-bit value must be converted to a column number between 0 and 15. To do this, think about the range of inputs and desired outputs. The ADC produces values 0-255 while we want 0-15. Furthermore, we want consecutive ADC values (as you slowly turn the pot) to produce the same output value. For example ADC value 0,1,2,...,n-1 should all produce output column 0 on the LCD screen for our ^, then n,n+1,n+2...,2n-1 should produce output column 1 on the LCD. Determine n and then think about a simple mathematical operation to convert the input ADC result to the desired output column.
- If the column number for the '^' has changed, erase the '^' at the old position and write a new one at the new position.
- Delay for 10ms
- Check if the '^' and the 'X' are in the same column
- If so, increment a count of how many 10ms intervals they have been in the same column. If the count indicates that it has been there for 2 seconds, print a message that the player has won the game. The program can then enter an infinite loop to stop the game play.
- If not, reset the count to zero. This handles the case where the '^' was under the 'X' briefly but then moved on to another column.
When your program is running you should be able to
- Display your splash screen.
- Confirm that the 'X' starts in a random position each time the program is started.
- Move the 'X' left and right using the buttons on the LCD.
- Turn the potentiometer control back and forth with the screwdriver and see the '^' move smoothly from column 0 to column 15. It should not skip any columns or spend significantly more time in column than another.
- Move the '^' under the 'X' for a time less than 2 seconds several times and the game should not indicate a victory.
- Move the '^' under the 'X' for 2 seconds and the message about winning the game should appear.
Once you have the assignment working demonstrate it to one of the instructors. The answers to the review questions below should be edited into the Lab6_Answers.txt file. The Lab6_Answers.txt file and all source code (lab6.c, lcd.c, lcd.h, adc.c, adc.h and Makefile) must be uploaded to the Vocareum web site by the due date. See the Assignments page of the class web site for a link for uploading.
Please make sure to save all the parts used in this lab in your project box. These will be needed for labs throughout the rest of the semester. We suggest also saving all the pieces of wire you cut and stripped since those will be useful in later labs.
Answer the following questions and submit them in a separate text file ("Lab6_Answers.txt") along with your source code.
- Review the conceptual operation of an ADC and use the given parameters to find the desired value.
- Your 8-bit ADC has Lo and Hi reference voltages of 1V and 4V, what digital number would result from sampling a 2.0V?
- Your 5-bit ADC has Lo and Hi reference voltages of 0V and 5V, what input voltage would yield a digital number of 12?
- Your 6-bit ADC has a Lo reference voltages of 2V. A voltage is sampled at 2.4V and the digital result is 26. What was the Hi reference voltage used by the ADC?
- We want you to feel comfortable reading documentation to understand the operation of a device. To that end, open the ATmega328/P datasheet linked on our website from the Tools and Links Page and go to chapter 24 regarding the ADC. In our current lab, the ADC only takes samples when commanded through the ADSC bit in the ADCSRA register. However, the ADC can be configured to take continuous samples based on some other triggering source. Read over the register bit descriptions of the ADCSRA register (especially bit 5 = ADATE) and the ADCSRB register (especially bits ADTS[2:0]) described in sections 24.9.2 and 24.9.4, respectively. In addition, in section 24.4, directly above Figure 24-4 is the text:
"In Free Running mode, a new conversion will be started immediately after the conversion completes, while ADSC remains high."
Now consider what effect setting the ADATE bit of ADCSRA to a 1 will have, assuming you don't modify ADCSRB from its default reset value. Briefly describe what the ADC module will do.