AVR®: Read an I/O as an Input to Turn on an LED

Last modified by Microchip on 2023/11/10 11:09

In this Lesson:

  • Set up macros to make code more readable.
  • Poll the status of the GPIO connected to the switch.
  • Debug in MPLAB® X IDE using the I/O view, seeing bits in the PIN, DDRB, and PORTB registers change as we single step through the code.

Procedure

Macros

Define some macros to make our code more readable.

The previous lesson on flashing an LED gave us the following code...

#define F_CPU 16000000UL
#include
<avr/io.h>
#include
<avr/delay.h>


int main(void) {
    DDRB |= (1 << PB5); // set PB5 as output pin
   
   while (1) {
       
       //PORTB |= (1<< PORTB5);
       PINB |= (1 << PINB5);
      
       _delay_ms(500);
    }
}

Let's add some macros to make it more readable. 

In embedded C, the #define directive is used to define a macro, which is essentially a symbol or identifier that represents a certain value or expression.

The syntax for #define is as follows:

#define identifier value

where "identifier" is the name of the macro and "value" is the value or expression that the macro represents.

When the preprocessor encounters the #define directive, it replaces all occurrences of the identifier in the code with the corresponding value. This is done before the code is compiled, so it allows for simple substitution of values or expressions throughout the code.

For example, if you wanted to define a macro to represent the value of pi, you could use the following #define directive:

#define PI 3.14159

Then, anywhere in your code where you use the identifier PI, it will be replaced with the value 3.14159. This can be useful for simplifying code, making it more readable, and making it easier to make global changes to values or expressions throughout the codebase.  Note that we did this previously with the F_CPU value.

So, our LED flashing code with the #defines added, looks like below, and note that we added an LED off state that we'll need in the next iteration of code where we poll the status of a GPIO pin connected to a switch.

#define F_CPU 16000000UL
#include
<avr/io.h>
#include
<avr/delay.h>

#define LED_ON  PORTB |= (1<<PORTB5)
#define LED_OFF PORTB &= ~(1<<PORTB5)
#define LED_TOGGLE  PINB |= (1<<PINB5)


int main(void) {
    DDRB |= (1 << PB5); // set PB5 as output pin
   
   while (1) {
       
       //PORTB |= (1<< PORTB5);
       PINB |= (1 << PINB5);
      
       _delay_ms(500);
    }
}

Switch Status

Poll the status of a GPIO-connected switch.

To find out which pin the switch is connected to, we will again review the schematic from the ATmega328PB Xplained Mini User Guide.  As shown below, the switch is connected to Pin PB7.  When the switch is pressed, the pin will read LOW because it will be directly connected to ground.  When not pressed, the pin will read HIGH because it will be connected to VCC_TARGET through the 100k resistor.

Switch from Schematic

To configure pin PB7 as an input, we will take a look at the datasheet.  The information we need is shown in red...

Configuring the Pin

Translating this into code looks like...

DDRB &= ~(1<<DDB7); //set PB7 as an input pin

Now we need some control logic in our code so that when the button is pressed our code will turn on the LED and when not pressed, the LED will turn off. 

if(!(PINB & (1<<PINB7))) //If PINB7 is low
       {    
            LED_ON;
        }
       else
        {
            LED_OFF;
        }

Combine the Code

Putting it all together and programming the device.

Here's the final version of the code for this lesson.  Enter it into the main.c file.  Note that the delay references have been removed because this example does not use the delay function.

#include <avr/io.h>

#define LED_ON  PORTB |= (1<<PORTB5)
#define LED_OFF PORTB &= ~(1<<PORTB5)
#define LED_TOGGLE  PINB |= (1<<PINB5)


int main(void) {
    DDRB |= (1 << PB5); // set PB5 as output pin
   DDRB &= ~(1<<DDB7); //set PB7 as an input pin
   
   while (1) {
       
       if(!(PINB & (1<<PINB7))) //If PINB7 is low
       {    
            LED_ON;
        }
       else
        {
            LED_OFF;
        }      
      
    }
}

Select the button indicated below to make and program the target device.

Program the Device

Your application code is working correctly if you press the button, the LED lights up.  When you release the button, the LED is off.


Debugging

Use MPLAB X IDE debugging features to get another look at what is happening in our code.

To do this we will need to change the project properties to debug with debugWIRE.

Right-click on the project and select Properties at the bottom of the screen.

Properties Menu Selection

When the dialog box opens, select mEDGB on the left and then Communications in the Option categories.  Finally, if not already selected, choose debugWIRE as the interface.  Hit Enter

Communication Interface Selection

Now select the Debug Project button on the top menu.

Debug Button

Select YES when the dialog box appears.

Debug ISP

The application should now be running in Debug mode.  Press the Pause button to stop it temporarily.

Pause Debug Session

Finally, go to the Watch menu toward the bottom of your screen and create new watches for DDRB, PINB, and PORTB.  From here you can step through the program using F7 and observe the values in the watch window change.  Try pressing the button while stepping as well so you can see that both branches of the control logic affect the registers.

Watch Window

 To take the MPLAB X IDE out of debug mode, select the Stop button.

Stop Debugger

Previous Lesson:

Flashing an LED at a Specific Frequency

Next Lesson:

Using Pin Change Interrupts