5. A Light Meter

Background

This project will integrate a light-dependent resistor (LDR) with the ATmega328p. This gives us an excuse to explore the Analog-to-Digital (ADC) converter peripheral. We’ll map the full-scale range of the sensor and display it on 8 LED’s, thereby making a crude light detector.

Testing the light meter

The Theory

An ADC bridges the digital and analog worlds

Anytime an analog sensor is used, an ADC is present. Their ubiquitous !

An ADC allows us to measure analog quantities such as distance, pressure, temperature, sound or brightness.

Part of sensor conditioning is converting this analog quantity into a voltage (many sensors already do this for us). What we need to do is scale it down to Vcc then use the ADC.

Introduction to ADC Hardware

The Atmega328p uses a 10-bit successive approximation register (SAR) ADC. The comparator keeps comparing until the voltage at the input channel is smaller than a step size.

Since the ADC module actually occupies a large chunk of silicon, the Atmega328p only has one ADC. Fear not! Multiple I/O’s can share the ADC via multiplexing.

Hardware Connections

8 LED’s with current-limiting resistors connected on PORTB.

A voltage divider consisting of LDR on upper leg and a 10k on the bottom leg. The halfway point connects to PC0.

NOTE: You can configure R2 in the voltage divider to exploit the full range of the LDR using

The LDR’s resistance drops when exposed to more light, thus the ADC reads a higher voltage. We convert it’s sensor reading into a voltage using a voltage divider.

The Software

main.c reads the voltage on the ADC pin and maps it to the series of LEDs. ADC reading is also transmitted via UART.

The Showcase

Apologies for the potato quality. I was being stingy with the export settings in Premiere Pro.

Circuit assembled on a breadboard

BONUS: Interrupt-driven light sensor

Let’s convert the light sensor to be interrupt driven. The full file is available in the GitHub repository. This requires we do the following:

  1. Include <avr/interrupt.h>.
  2. Enable global interrupts using sei().
  3. Enable the ADC Conversion Complete Interrupt bit (ADIE) in ADCSRA.
  4. Implement the ADC_vect Interrupt Service Routine (ISR) to handle the data when a conversion finishes.
  5. Switch the ADC to Free Running mode (ADATE in ADCSRA and clearing ADCSRB), which automatically triggers the next conversion as soon as the current one finishes. This removes the need for a delay or manual triggering inside the while(1) loop.