Learn, Implement and Share

Note

This article is a part of Arduino / ATmega328p Embedded C Firmware Programming Tutorial. Consider exploring the course home page for articles on similar topics.

Arduino Tutorial Embedded C Register Level Arduino Master Class

Arduino Tutorial Embedded C Register Level Arduino Master Class

Also visit the Release Page for Register Level Embedded C Hardware Abstraction Library and Code for AVR.

License

Copyright (c) 2014-2020 Arnab Kumar Das https://www.arnabkumardas.com/ You may freely modify and share this project and code, as long as you keep this notice intact (including the links). Licensed under the Creative Commons BY-SA 3.0 license:

http://creativecommons.org/licenses/by-sa/3.0/

Disclaimer: To the extent permitted by law, Arnab Kumar Das provides this work without any warranty. It might be defective, in which case you agree to be responsible for all resulting costs and damages.

Introduction

In this tutorial, we will learn how to interface a light sensor/photoresistor (LDR / Light Decreasing Resistance, or light-dependent resistor, or photo-conductive cell) with Arduino / Atmega328p. For this tutorial, we will be using ADC, USART, and GPIO hardware peripheral of the Arduino / Atmega328p. The concepts learned in this tutorial can also be applied to other projects involving ADC.

This tutorial has two parts. In the first part, we will understand how the LDR works, and in the second part, we will be controlling an LED connected to one GPIO pin based on the amount of light falling on the LDR.

The programming and code in this tutorial can be done in both Atmel Studio and Arduino IDE.

What You Will Learn

  • How to Program and Interface LDR with Arduino?
  • How to use LDR as Light Sensor with AVR ATmega328p?
  • How to use ADC to read Light Intensity in Arduino/ATmega328p?
  • How to Program ADC, UART and GPIO to interface Sensors in Arduino?

Prerequisite

  • Background knowledge of ADC
  • Background knowledge of USART
  • Background knowledge of GPIO

Hardware Bill of Materials

Software Bill of Materials

  • Atmel Studio / Arduino IDE
  • Drivers

Hardware Description

LDR

photoresistor / LDR is a passive component that decreases resistance with respect to receiving luminosity (light) on the component’s sensitive surface. The resistance of a photoresistor decreases with an increase in incident light intensity. The resistance varies in the range of Ω to MΩ and based on this LDRs can be used in different configurations to sense the light intensity and use that to control devices like street lights or perform a complex task.

Schematic Design

In this tutorial/experiment, we will be using the LDR in a voltage divider configuration where the variable resistance of the LDR will change the voltage drop distribution in the voltage divider and that can be further captured by the ADC.

Voltage Divider Circuit with LDR
Voltage Divider Circuit with LDR

If we connect two resistors in series and give a voltage VCC to one end and GND to another ends there will be a current flowing I and the voltage drop at each resistor will be IR1 and IR2. The voltage at VO will be equal to IR2. The total voltage drop at both the resistor will be equal to VCC.

Voltage Divider Formula for LDR
Voltage Divider Formula for LDR

Based on the above formula voltage VO will vary if we keep R2 constant and change R1. In the case of R1 as LDR, the resistance will change when there is a change in light intensity and thus the voltage at VO will also change.

First, we will be connecting the LDR to a multimeter to know its resistance when no light falls on it. Mine was 27K so I choose resistor R2 of a value of 25K to keep the current flowing through the voltage divider low all the time. We will be using 5V for the VCC, so the maximum current that can flow through the voltage divider will be 0.0002A which will keep the power consumption low.

Once we have chosen a resistor R2 we have to connect the voltage divider to the Arduino as shown in the schematic. V0 of the voltage divider will be connected to the A0 pin of Arduino.

Schematic of LDR / Light Sensor Interfacing with Arduino
Schematic of LDR / Light Sensor Interfacing with Arduino
LDR / Light Sensor Interfacing with Arduino UNO
LDR / Light Sensor Interfacing with Arduino UNO

Part 1: Reading Analog Voltage VO of the Voltage Divider on Serial Terminal using ADC and USART

Programming

The below code will read the raw 10-bit ADC value at pin A0 / ADC0 / PC0 and further convert them into voltage and print it in the serial terminal.

/**
\file main.c
\brief Code to Read Analog Data from ADC and Print in Serial Terminal

\verbatim

Created: 22-11-2020 03:20:10 PM
Author : Arnab Kumar Das
Website: www.ArnabKumarDas.com
Microcontroller Supported: ATmega48A/PA/88A/PA/168A/PA/328/P or Arduino UNO/NANO/MINI

Copyright (c) 2014-2020 Arnab Kumar Das
You may freely modify and share this code, as long as you keep this
notice intact (including the links above).
Licensed under the Creative Commons BY-SA 3.0 license:

  http://creativecommons.org/licenses/by-sa/3.0/

Disclaimer: To the extent permitted by law, Arnab Kumar Das provides this work
without any warranty.  It might be defective, in which case you agree
to be responsible for all resulting costs and damages.

                +-\/-+
          PC6  1|    |28  PC5 (A5/ADC5)
RXD  (D0) PD0  2|    |27  PC4 (A4/ADC4)
TXD  (D1) PD1  3|    |26  PC3 (A3/ADC3)
     (D2) PD2  4|    |25  PC2 (A2/ADC2)
PWM  (D3) PD3  5|    |24  PC1 (A1/ADC1)
XCK  (D4) PD4  6|    |23  PC0 (A0/ADC0)
          VCC  7|    |22  GND
          GND  8|    |21  AREF
          PB6  9|    |20  AVCC
          PB7 10|    |19  PB5 (D13)
OC0B (D5) PD5 11|    |18  PB4 (D12)
OC0A (D6) PD6 12|    |17  PB3 (D11) PWM
     (D7) PD7 13|    |16  PB2 (D10) PWM
     (D8) PB0 14|    |15  PB1 (D9)  PWM
                +----+

\endverbatim

*/ 

#include <avr/io.h>
#include "avr_adc.h"
#include "avr_usart.h"
#include <util/delay.h>


int main(void)
{
	/* ADC Configuration and Init */
	ADC_ConfigData ADC_Data;
	ADC_Data.Prescaler = ADC_PRESCALER_128;
	ADC_Data.VoltageReference = ADC_VOLTAGE_REFERENCE_AVCC;
	ADC_Init(ADC_Data);
	
	/* USART Configuration and Init */
	USART_ConfigData USART_Data;
	USART_Data.BaudRate = 9600;
	USART_Data.DataBit = USART_DATA_BIT_EIGHT;
	USART_Data.ParityBit = USART_PARITY_BIT_NO;
	USART_Data.StopBit = USART_STOP_BIT_ONE;
	USART_Data.UsartMode = USART_MODE_ASYNCHRONOUS;
	USART_Init(USART_Data);
    
	/* Local Variables */
	uint16_t ADC_RawData = 0x0000;
	float ADC_Voltage = 0.0f;
	
    while (1) 
    {
		/* Read ADC Value at Pin PC0 (A0/ADC0) */
		ADC_RawData = ADC_ReadPin(ADC0);
		
		/* Convert the raw ADC value to Voltage */
		ADC_Voltage = (float)( ADC_RawData * 5 ) / 1023;
		
		/* Print the data in terminal */
		USART_TransmitString("\nVoltage at ADC0 Pin = ");
		USART_WaitTillTransmitFree();
		USART_TransmitFloat(ADC_Voltage,3);
		
		/* Wait for 500ms */
		_delay_ms(500);
    }
}

The first programming step is to initialize the ADC with the right Prescaler and voltage reference. The Prescaler selected for the ADC clock is 128 so ADC runs at 16MHz/128 = 125KHz. The voltage divider is connected to 5V, which is the max voltage Arduino / Atmega328p can handle as analog input, so the voltage reference for ADC conversion is set to AVCC / 5V.

	/* ADC Configuration and Init */
	ADC_ConfigData ADC_Data;
	ADC_Data.Prescaler = ADC_PRESCALER_128;
	ADC_Data.VoltageReference = ADC_VOLTAGE_REFERENCE_AVCC;
	ADC_Init(ADC_Data);

The second programming step is to initialize the USART to print out the required data. In this case, we will be printing the ADC data. In the below configuration the USART is initialized to 9600baud, 8-bit, no parity, one stop bit configuration.

	/* USART Configuration and Init */
	USART_ConfigData USART_Data;
	USART_Data.BaudRate = 9600;
	USART_Data.DataBit = USART_DATA_BIT_EIGHT;
	USART_Data.ParityBit = USART_PARITY_BIT_NO;
	USART_Data.StopBit = USART_STOP_BIT_ONE;
	USART_Data.UsartMode = USART_MODE_ASYNCHRONOUS;
	USART_Init(USART_Data);

We also need two local variables to store the 10-bit ADC value and analog voltage.

	/* Local Variables */
	uint16_t ADC_RawData = 0x0000;
	float ADC_Voltage = 0.0f;

The next step is to continuously ready the analog value at pin A0 and convert it to voltage. Once we have the voltage we can then print it to the serial terminal using USART. This can run in an infinite loop every 500ms.

     while (1) 
    {
		/* Read ADC Value at Pin PC0 (A0/ADC0) */
		ADC_RawData = ADC_ReadPin(ADC0);
		
		/* Convert the raw ADC value to Voltage */
		ADC_Voltage = (float)( ADC_RawData * 5 ) / 1023;
		
		/* Print the data in terminal */
		USART_TransmitString("\nVoltage at ADC0 Pin = ");
		USART_WaitTillTransmitFree();
		USART_TransmitFloat(ADC_Voltage,3);
		
		/* Wait for 500ms */
		_delay_ms(500);	
    }

Execution

We have to connect the Arduino according to the schematic and program the Arduino. You can use Arduino’s serial programming, USB ASP, Atmel ICE, etc for flashing the Arduino. After the programming, we have to connect to the COM port of the Arduino and we will be able to see the output. The print should come every 500ms.

If you change the light intensity you should be able to see similar output as shown below.

Ateml Studio Terminal Window Connected to Arduino
Atmel Studio Terminal Window Connected to Arduino | High Light Intensity
Ateml Studio Terminal Window Connected to Arduino
Atmel Studio Terminal Window Connected to Arduino | Room Light Intensity
Ateml Studio Terminal Window Connected to Arduino
Atmel Studio Terminal Window Connected to Arduino | Low Light Intensity
Atmel Studio Terminal Window Connected to Arduino and Reading ADC Value / Voltage Reading
Atmel Studio Terminal Window Connected to Arduino and Reading ADC Value / Voltage Reading
Reading Output Voltage VO of the Voltage Divider on Serial Terminal

Conclusion

Part 1 of the tutorial helped in understanding the relation of light intensity to the output voltage of the voltage divider. We also learned how an LDR works as a light sensor.

Part 2: Using LDR as Light Sensor and Controlling a LED using ADC and GPIO

Programming

The below code will read the voltage divider output and switch on a LED when it is dark below a threshold.

/**
\file main.c
\brief Code to Read Light Sensor / LDR value and Switch on a LED when it is Dark

\verbatim

Created: 22-11-2020 04:45:10 PM
Author : Arnab Kumar Das
Website: www.ArnabKumarDas.com
Microcontroller Supported: ATmega48A/PA/88A/PA/168A/PA/328/P or Arduino UNO/NANO/MINI

Copyright (c) 2014-2020 Arnab Kumar Das
You may freely modify and share this code, as long as you keep this
notice intact (including the links above).
Licensed under the Creative Commons BY-SA 3.0 license:

  http://creativecommons.org/licenses/by-sa/3.0/

Disclaimer: To the extent permitted by law, Arnab Kumar Das provides this work
without any warranty.  It might be defective, in which case you agree
to be responsible for all resulting costs and damages.

                +-\/-+
          PC6  1|    |28  PC5 (A5/ADC5)
RXD  (D0) PD0  2|    |27  PC4 (A4/ADC4)
TXD  (D1) PD1  3|    |26  PC3 (A3/ADC3)
     (D2) PD2  4|    |25  PC2 (A2/ADC2)
PWM  (D3) PD3  5|    |24  PC1 (A1/ADC1)
XCK  (D4) PD4  6|    |23  PC0 (A0/ADC0)
          VCC  7|    |22  GND
          GND  8|    |21  AREF
          PB6  9|    |20  AVCC
          PB7 10|    |19  PB5 (D13)
OC0B (D5) PD5 11|    |18  PB4 (D12)
OC0A (D6) PD6 12|    |17  PB3 (D11) PWM
     (D7) PD7 13|    |16  PB2 (D10) PWM
     (D8) PB0 14|    |15  PB1 (D9)  PWM
                +----+

\endverbatim

*/ 

#include <avr/io.h>
#include "avr_gpio.h"
#include "avr_adc.h"


int main(void)
{
	/* ADC Configuration and Init */
	ADC_ConfigData ADC_Data;
	ADC_Data.Prescaler = ADC_PRESCALER_128;
	ADC_Data.VoltageReference = ADC_VOLTAGE_REFERENCE_AVCC;
	ADC_Init(ADC_Data);
	
	/* GPIO Pin PB5 (D13) Configuration and Init */
	GPIO_InitPin(13,GPIO_MODE_OUTPUT);
	
	/* Local Variables */
	uint16_t ADC_RawData = 0x0000;	

	while (1)
	{
		/* Read ADC Value at Pin PC0 (A0/ADC0) */
		ADC_RawData = ADC_ReadPin(ADC0);
		
		if (ADC_RawData < 400)
		{
			GPIO_WritePinHigh(13);
		}
		else
		{
			GPIO_WritePinLow(13);
		}
		
	}
}

The first programming step is to initialize the ADC with the right Prescaler and voltage reference. The Prescaler selected for the ADC clock is 128 so ADC runs at 16MHz/128 = 125KHz. We are connecting the voltage divider to 5V, which is the max voltage Arduino / Atmega328p can handle as analog input. So we selected the voltage reference for ADC conversion as AVCC / 5V.

	/* ADC Configuration and Init */
	ADC_ConfigData ADC_Data;
	ADC_Data.Prescaler = ADC_PRESCALER_128;
	ADC_Data.VoltageReference = ADC_VOLTAGE_REFERENCE_AVCC;
	ADC_Init(ADC_Data);

In the second step, the GPIO pin PB5 / D13 is initialized as an output pin.

	/* GPIO Pin PB5 (D13) Configuration and Init */
	GPIO_InitPin(13,GPIO_MODE_OUTPUT);

We also need a local variable to store the 10-bit ADC value.

	/* Local Variables */
	uint16_t ADC_RawData = 0x0000;

In the last step in an infinite loop, we read the ADC output at pin A0, and if the value is less than 400 we write the GPIO pin D13 on Arduino HIGH which turns the onboard LED. If the light intensity increases and the ADC value is more than 400 then the LED turns off or the pin goes LOW.

	while (1)
	{
		/* Read ADC Value at Pin PC0 (A0/ADC0) */
		ADC_RawData = ADC_ReadPin(ADC0);
		
		if (ADC_RawData < 400)
		{
			GPIO_WritePinHigh(13);
		}
		else
		{
			GPIO_WritePinLow(13);
		}
		
	}

Execution

We have to connect the Arduino according to the schematic and program the Arduino. You can use Arduino’s serial programming, USB ASP, Atmel ICE, etc for flashing the Arduino. After the programming, we have to vary the light intensity falling on the LDR and monitor the onboard LED at pin D13. When the light intensity goes low the LED should switch on.

LDR / Light Sensor Interfacing Practical | LED Glows when LDR in Darkness

Conclusion

Part 2 of the tutorial helped in using an environmental parameter to control a device. We were able to use the light intensity falling on the light sensor / LDR to decide if the LED need to be on. This knowledge of sensing and control can be used in complex projects and applications.

Download‎


Crazy Engineer

MAKER - ENGINEER - YOUTUBER

5 Comments

filmi full izle · January 17, 2021 at 5:25 am

You have observed very interesting points! ps decent site. Dory Noll Tiffany

    Crazy Engineer · January 18, 2021 at 2:39 pm

    Thank you for spending time on the website. Hopefully, this article has helped you.

Ankit Dobariya · January 22, 2021 at 6:23 pm

pls give the avr_adc.h and avr_usart.h code.
pls pls…..

    Crazy Engineer · January 23, 2021 at 11:24 pm

    The attachments are available on the same page with all the code and header files.

acv888 · January 27, 2023 at 8:49 pm

I’m working on a project to control the colors on a TFT-Display via lux values and this helped me immensely. Thank you very much for your effort.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.