EE 109 - Spring 2025 Introduction to Embedded Systems

Lab 7

Analog-to-Digital Conversion and Pulse Width Modulation

Honor Statement

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.

Introduction

In this lab exercise you will learn how to 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. These input devices will be used to control the generation of a pulse width modulation (PWM) signal from the Arduino Uno. One of the 8-bit timers will be use to generate a PWM signal to control the position of a servo motor. By adjusting the position of the potentiometer you’ll be able to control the position of the servo. In addition, if the RIGHT button on the LCD is pressed the servo rotates to the full clockwise position. If the LEFT button is pressed the servo rotates to the full counter-clockwise position. Pressing the SELECT button returns control of the servo to the potentiometer.

To see a short video demonstrating the operation of lab, click here.

For a copy of the Lab 7 grading sheet, click here

Getting Started

For this lab you will need to get a bag of Lab 7 and 8 components from the instructors. This bag will include the potentiometer and servo motor used in this lab and some additional parts that will be used in Lab 8. The servo should come with a black plastic arm mounted on the servo motor’s shaft. If the arm is missing, ask one of the instructors to get one for you and then press it down onto the motor’s shaft. Having the arm on the servo shaft makes it much easier to see the rotation of the motor.

Set up the Lab 7 assignment the same as has been done previously by creating a lab7 folder in your ee109 folder.

If you look at the lab7.c file you will see that some of the code has been provided for you. At the start of the program add code to do the following:

Once this has been done you are ready to move on to Task 1 and start adding additional code to interface with some of the hardware.

Task 1: Write the ADC routines

The Arduino’s analog-to-digital converter (ADC) module is used in this lab to adjust the width of the PWM signal going to the LEDs. The ADC will be used to determine the position of the potentiomter and also to determine if one of the buttons on the LCD has been pressed.

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.

The adc.c file contains the template for two routines that you will have to write.

The adc_init routine must set up the proper values in the ADMUX and ADCSRA registers. Use the information in the ADC lecture slide set to determine which bits in the register must be set or cleared by the adc_init function.

Diagrams of the registers are shown below and are also provided on the Lab 7 grading rubric for you to fill in the values.

Important: When writing code to set or clear the bits in these registers by using the << shift operations, you can use the name of the bit as shown in the diagram. For example, to set the ADEN bit to a one, the following can be done.

    ADCSRA |= (1 << ADEN)

We strongly encourage you to use the symbolic bit names rather than the 0-7 bit number when doing set/clear/test bit operations.

Your code in adc_init should do the following:

  1. Set/clear the REFS[1:0] bits in ADMUX to select the high voltage reference. Using the AVCC reference is appropriate for this lab.

  2. Set or clear the ADLAR bit in ADMUX such that we will use 8-bit conversion results (not 10-bit).

  3. Set/clear the ADPS[2:0] bits in ADCSRA to select an appropriate prescalar value.

  4. Set the ADEN bit in ADCSRA to enable the ADC module.

The channel select bits in ADMUX should not be configured in the adc_init function. They will be set or cleared each time adc_sample is called according to the channel argument passed to that function.

The other bits should be left as zeros. The ADSC bit (shown below in red) will be set to a one in the adc_sample function, but in the initialization of the registers it should be left unchanged.

Image

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 adc_sample routine must do the following.

  1. Set/clear the MUX[3:0] bits in ADMUX to select the input channel as specified by the argument to the function. The correct bits to go in the MUX[3:0] bits are simply the lower four bits of the argument to the function so just use bit copying to fill these in.

  2. Set the ADSC bit in the ADCSRA register to a 1. This starts the conversion process.

  3. 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.

  4. Copy the 8-bit conversion result from the ADCH register and return it to the calling program.

Once you have finished editing the adc.c file to complete the adc_init and adc_sample routines, make sure to add adc.o to the OBJECTS line in the Makefile so it will get compiled when you do a “make” operation. Also make sure the adc.h file in the same folder with the adc.c.

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 (UP, DOWN, RIGHT, LEFT and SELECT) are instead interfaced through a multistage voltage divider creating a single analog signal that can be measured by the ADC.

Depending on which button is 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 which if any of the five buttons is pressed.

In the following task, we will find out what 8-bit number is returned by the adc_sample function for each of the five buttons that can be pressed. These numbers can then be used in your program to determine which button was pressed.

Task 2: Determine the LCD buttons values

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 top part of the program, add a call to adc_init to initialize the ADC module and make it ready to take samples. In the lab7.c there should be a place indicated for this function call to be added.

Define an “unsigned char” variable to hold the ADC conversion result, and also allocate a “char” buffer of at least 4 characters to use for displaying the value.

In lab7.c look for a while(1) loop that is present in the program just for doing Tasks 2 and 3. This loop is will be used to sample the ADC and print the conversion value on the LCD screen. Add the following code inside this loop.

  1. Call adc_sample with an argument of zero to read the value of the ADC channel 0 and store the returned value in the variable you define above.

  2. 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);

  3. Use your lcd_moveto function to move the cursor to the first character position on the first row.

  4. 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 7 grading sheet.

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.

Checkpoint: To receive the checkpoint credit, show one of the teaching staff the values on the LCD for each of the buttons pressed.

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.

Image

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 that are 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

Image

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Ω.

Installing the Potentiometer on the Breadboard

The first part of the circuit uses a potentiomter to adjust the duty cycle of a PWM signal generated by one of the Arduino TIMER modules. In this section we will install the potentiometer on the breadboard, connect it to the ADC input of the Arduino and confirm that the analog voltage from the potentiometer is being converted to a digital value. The schematic diagram for this part is shown below.

Image

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.

Image

Task 3: Confirm Operation of the Potentiometer

To confirm that you have the potentiometer connected properly, use the multimeter to check that the voltage on the slider pin can be changed by rotating the control.

If the voltage is +5V when rotated fully clockwise and 0 when fully counter clockwise, but you want it to be the other way (0 for CW, +5V for CCW), then all you have to do is swap the 5V and ground wires coming from the power and ground buses to the potentiometer and it will now go the other way.

Now that the potentiometer is producing the variable voltage, check to see that the voltage can be converted by the ADC into a number between 0 and 255. Your lab7.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 1 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 channel 1, 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.

NOTE: The while(1) loop that was used for Tasks 2 and 3 can now be deleted or commented out in your lab7.c file. The while(1) loop that is below that section of the file is the code that will do the rest of the lab assignment.

Using Timers to Produce Pulse Width Modulation Signals

In previous labs, the Timer/Counter modules were used to implements time delays so that certain tasks would happen at a known rate, such as the 0.1 second counting rate of a stopwatch. When using the Timer/Counter modules in this way, they only interact with your program, usually by interrupts. The Timer/Counters can also be configured to produce signals that appear on one or more of the I/O pins and can be used to control external devices.

Each of the three Timer/Counter modules on the Arduino can produce output signals on two separate signals lines. Connections can be made to these signals just like with any other output from the microcontroller. The diagram below shows which I/O pins are used by the Timer/Counter modules when they have been configured to produce an output signal.

Image

When working with the Timer/Counter modules the output signals are always referred to by the names shown above inside the boxes such as “OC0A” or “OC2B”. It’s important to be aware that that these signals share the connections to the microcontroller with the digital I/O lines we have been working with as shown above. For example OC2A is the same connection on the Arduino as the Port B, bit 3. At any one time, only one module, either the Timer/Counter or the digital I/O can be using the line. For example, if the Timer 1 is outputting a signal on the OC1A pin, then Port B, Bit 1 can not be used as a digital I/O by putting bits in the PORTB register.

One common use of the Timer/Counter modules in the Atmel microcontroller used on the Arduino Uno is to generate Pulse Width Modulation (PWM) signals. Please watch the video and review the slides that are linked on the Assignments page to learn more about pulse width modulation.

To generate PWM signals the timer has to perform two tasks. First it has to generate pulses with a constant period. The period of the pulses is the time from the 0→1 transition of one pulse until the next 0→1 transition. To generate pulses at a fixed period the timer has to count the clock signal until the pulse period has been reached and then reset back to zero and start over.

Image

The second task for the timer is to control the width of the pulse which is the amount of time during the pulse period that the output is in the high or logic 1 state. The pulse output goes to the one state at the beginning of the pulse period, then at some point during the pulse period the timer must terminate the pulse by setting the pulse output to zero for the rest of the pulse period.

When working with PWM signals, your program configures the timer to generate the signals with the desired pulse period at the start of the program and never changes this setting since the pulse period is fixed value. As the program operates, it modifies or “modulates” the pulse width as needed to generate the proper signal.

To control the servo motor we will be using Timer/Counter2, one of the 8-bit Timer/Counters. In the PWM mode we will be using, each period of the pulse is limited to the length of time it takes the timer to count from 0 to its maximum value of 255. The timer simply counts up to the maximum count value and then starts over. The relationship between count value, prescalar and time is given by

Image

We will use the prescaler to divide the 16Mhz clock by 1024, so the period of our pulses will be

Image

or

Image

For this task we will be using Timer2 in what is called “Fast PWM” mode and this is illustrated below. At the start of the pulse period (count = 0) the OC2A output signal, which appears on Arduino port D11 (Port B, pin 3), will go to the high state to start the pulse. Register “OCR2A” is used to store a value that determines the pulse width by telling the timer when to terminate the pulse. During each pulse period, the timer counts up incrementing the count value, and when it reaches the value in OCR2A the output signal goes back to zero while the timer continues to count up to 255. The counter then resets back to zero and the process is repeated for the next pulse.

Image

Your program will be have the job of changing the value in OCR2A in order to change the width of the pulse.

Task 4: Determine the PWM values for controlling TIMER2

Servos are designed to operate using a PWM signal with 20ms period, and the pulse width should only vary between 0.75ms to 2.25ms. In the task above we found that the longest pulse period we could get from the 8-bit TIMER2 was 16.4ms. While this pulse period isn’t optimal, it will work fine for controlling these servo motors.

Before starting to write the code to control the position of the servo motor, first figure out what range of values will be placed in the OCR2A register to generate the PWM signal as specified above. You can use the equation below with the prescalar set to 1024 to find the count value to generate the 0.75ms wide pulse, and then repeat the steps to find the count value to generate the 2.25ms wide pulse.

Image

These are the counts for the minimum and maximum pulse widths you will be using, and the potentiometer will be used to change the OCR2A value between these limits. Turning the potentiometer one way will cause the program to increase the value in OCR2A making the pulse wider, turning it the other will decrease the value to make the pulse narrower.

Now that you have the range of OCR2A values that can be used, you can add code to read the potentiometer with the ADC and convert the result to the value that goes in OCR2A. The number that goes in the OCR2A register has a linear relationship with the potentiometer value. Linear equations are based on two points, and you have the two points:

Therefore the two points for the linear equation are (0, max OCR2A) and (255, min OCR2A).

Using these two points, determine a linear equation for mapping the ADC values to the OCR2A values. Once you have figured out the equation, write code to implement it, but remember to only use fixed point variables and operations. Do not use floating point operations or variables.

Task 5: Configure and test the PWM output

The PWM signal to control the servo will be generated using TIMER2 in “Fast PWM” mode. Add the following function to your program to initialize Timer/Counter2 to operate as described above for PWM generation. In the code we want to initialize the PWM signal to a pulse with that is halfway between the minimum and maximum width used by the servo motor. This will cause the servo to rotate to its midpoint when the program starts. Determine an intial value of OCR2A that is midway between the two values you determined above and use that value below in the code where is says “INITIAL_PWM” on the line that sets the OCR2A value.

    void timer2_init(void)
    {
        TCCR2A |= (0b11 << WGM20);  // Fast PWM mode, modulus = 256
        TCCR2A |= (0b10 << COM2A0); // Turn D11 on at 0x00 and off at OCR2A
        OCR2A = INITIAL_PWM;        // Initial pulse width (calculate this)
        TCCR2B |= (0b111 << CS20);  // Prescaler = 1024 for 16ms period
    }

IMPORTANT: Make sure to have a call to this function somewhere at the start of your program. Also add code to set the DDR bit for Port B, bit 3 to a one to make it the PWM pulse output.

In the main loop of your progam add code to do the following.

Once you have this code running, connect an oscilloscope to the PB3 (D11) output and observe the PWM signal. You should be able to see the pulse width change as you rotate the potentiometer. Check to make sure that

Task 6: Button inputs to control the motor

Your program to control the servo motor will need to run in three modes. The “VARIABLE” mode was described above where the value in OCR2A is controlled by the potentiometer setting. However if either the LEFT or RIGHT button is pressed, it switches to the “LEFT” or “RIGHT” mode. In these modes, if the LEFT button was pressed, the motor should immediately rotate to the full counter-clockwise position and remain there regardless of the input from the potentiometer. If the RIGHT button was pressed it should rotate to the full clockwise position. The program should stay in one of these three modes until the SELECT, LEFT or RIGHT is pressed to switch it to a different mode.

Add code to the main loop of the program as follows.

Connecting the Servo Motor

Get the servo motor from the bag of Lab 7 components that you acquired at the start of this lab. The cable to the motor has a three conductor connector with three wires going into it, red for power, black for ground and yellow for the PWM signal.

Image

When connecting the motor to your Arduino, first disconnect the power to your Arduino before connecting the motor. Using a piece of breadboard hookup wire, make a connection from the +5V on the Arduino to the red wire by inserting the end of the wire into the connector socket for the red wire as shown in below. Do the same to connect the Arduino ground to the black wire, and from the Port B, bit 3 (D11) output pin to the yellow wire.

Image

Once you reconnect the power to your Arduino the motor should move to the initial position as set by your PWM signal.

Try rotating the potentiometer and see if the motor responds correctly. Check to see that you can move the motor through an arc of about 150 degrees.

Results

When your program is running you should be able to confirm the following

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 Lab7_Answers.txt file. The Lab7_Answers.txt file and all source code (lab7.c, adc.c, acd.h, lcd.c, lcd.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, including the servo motor, 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.

Review Questions

Answer the following questions and submit them in a separate text file (“Lab7_Answers.txt”) along with your source code.

  1. Review the conceptual operation of an ADC and use the given parameters to find the desired value.

    1. Your 8-bit ADC has Lo and Hi reference voltages of 1V and 4V, what digital number would result from sampling a 2.0V?
    2. Your 5-bit ADC has Lo and Hi reference voltages of 0V and 5V, what input voltage would yield a digital number of 12?
    3. 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?
  2. Tammy Trojan has determined that the servo motor can be controlled by TIMER2 even though the PWM period is 16.4ms rather than the preferred 20ms. Billy Bruin also wants to control a similar servo motor but feels it must use a PWM signal with a 20ms period so he will have to use the 16-bit TIMER1 to produce the signal. Note: When TIMER1 is used for PWM, the OCR1A register determines the pulse period, and OCR1B register determines the pulse width. What values will Billy have to use for the TIMER1 prescalar, the OCR1A value to give a 20ms pulse period, and the values for OCR1B to give the minimum pulse width of 0.75ms and the maxumum pulse width of 2.25ms?