24 Apr 2019 - tsp
Last update 15 Jul 2021
5 mins
GPIO code requires on to include the avr/io header
#include <avr/io.h>
I/O ports are controlled via the DDR
, PORT
and PIN
registers
for the respective banks (I/O bank A is controlled via DDRA
, PORTA
and PINA
and vice versa).
The DDR register is the data direction register
which defined which direction data is sent or read from. Setting the bit corresponding
to the given pin (Bit 0 in DDRA
is PA0, etc.) to zero sets the pin to be an
input, setting the pin to one sets it to be an input.
To configure all pins as input one could set
DDRA = 0x00;
If one just wants to set the PA0 to output but all other pins to input one would set
DDRA = 0x01;
To be more easy readable one could define constants for each Pin like
#define PIN0 0
#define PIN1 1
#define PIN2 2
#define PIN3 3
#define PIN4 4
#define PIN5 5
#define PIN6 6
#define PIN7 7
#define PIN0_OUT 0x01
#define PIN1_OUT 0x02
#define PIN2_OUT 0x04
#define PIN3_OUT 0x08
#define PIN4_OUT 0x10
#define PIN5_OUT 0x20
#define PIN6_OUT 0x40
#define PIN7_OUT 0x80
#define PIN0_IN 0x00
#define PIN1_IN 0x00
#define PIN2_IN 0x00
#define PIN3_IN 0x00
#define PIN4_IN 0x00
#define PIN5_IN 0x00
#define PIN6_IN 0x00
#define PIN7_IN 0x00
This would allow to define the directions as
DDRA = PIN0_OUT | PIN1_IN | PIN2_IN | PIN3_IN | PIN4_IN | PIN5_IN | PIN6_IN | PIN7_IN;
Second one can use the PORT
register to either write the current output state (for
an output pin) or the pullup settings. To enable internal pullups one sets
the respective PORT
bit to 1, to disable pullups one writes a 0. One can
use the same trick using defined to make code more readable:
#define PIN0_PULLUP 0x01
#define PIN1_PULLUP 0x02
#define PIN2_PULLUP 0x04
#define PIN3_PULLUP 0x08
#define PIN4_PULLUP 0x10
#define PIN5_PULLUP 0x20
#define PIN6_PULLUP 0x40
#define PIN7_PULLUP 0x80
#define PIN0_NOPULL 0x00
#define PIN1_NOPULL 0x00
#define PIN2_NOPULL 0x00
#define PIN3_NOPULL 0x00
#define PIN4_NOPULL 0x00
#define PIN5_NOPULL 0x00
#define PIN6_NOPULL 0x00
#define PIN7_NOPULL 0x00
This allows one to simply configure the pullups the same way as the I/O directions:
PORTA = PIN0_PULLUP | PIN1_PULLUP | PIN2_NOPULL | PIN3_NOPULL | PIN4_NOPULL | PIN5_NOPULL | PIN6_NOPULL | PIN7_NOPULL;
Of course one has to be careful to not overwrite current output values. To write
output values the PORT
register is used also. Writing a logical 0 pulls the
output low, writing a logical 1 pulls the output high.
To change just a single value one can use logical OR or AND
PORTA = PORTA | (1 << PIN1); // Enable PA1
PORTA = PORTA & (~(1 << PIN2)); // Disable PA2
To read inputs the PIN
register is used. To check if a given pin is asserted:
if(PINA & (1 << PIN4)) {
// Whatever we want to do if PA4 is set
}
/*
Note that every microcontroller has a
different set of I/O banks. An ATMEGA328P
would - for example - have no A bank and
thus no PORTA/DDRA/PINA registers.
*/
#define PA 0
#define PB 1
#define PC 2
#define PD 3
#define PIN0 0
#define PIN1 1
#define PIN2 2
#define PIN3 3
#define PIN4 4
#define PIN5 5
#define PIN6 6
#define PIN7 7
#define MODE_INPUT 0
#define MODE_OUTPUT 1
#define PULLUP 1
#define NOPULL 0
void gpioSetPinMode(uint8_t port, uint8_t pin, uint8_t mode, uint8_t initialValue) {
switch(port) {
case PA:
if(mode == MODE_INPUT) {
DDRA = DDRA & (~(1 << pin));
} else {
DDRA = DDRA | (1 << pin);
}
if(initialValue) {
PORTA = PORTA | (1 << pin);
} else {
PORTA = PORTA & (~(1 << pin));
}
return;
case PB:
if(mode == MODE_INPUT) {
DDRB = DDRB & (~(1 << pin));
} else {
DDRB = DDRB | (1 << pin);
}
if(initialValue) {
PORTB = PORTB | (1 << pin);
} else {
PORTB = PORTB & (~(1 << pin));
}
return;
case PC:
if(mode == MODE_INPUT) {
DDRC = DDRC & (~(1 << pin));
} else {
DDRC = DDRC | (1 << pin);
}
if(initialValue) {
PORTC = PORTC | (1 << pin);
} else {
PORTC = PORTC & (~(1 << pin));
}
return;
case PD:
if(mode == MODE_INPUT) {
DDRD = DDRD & (~(1 << pin));
} else {
DDRD = DDRD | (1 << pin);
}
if(initialValue) {
PORTD = PORTD | (1 << pin);
} else {
PORTD = PORTD & (~(1 << pin));
}
return;
}
}
uint8_t gpioReadPin(uint8_t port, uint8_t pin) {
switch(port) {
case PA: return (PINA & (1 << pin));
case PB: return (PINB & (1 << pin));
case PC: return (PINC & (1 << pin));
case PD: return (PIND & (1 << pin));
}
}
void gpioSetPin(uint8_t port, uint8_t pin, uint8_t value) {
switch(port) {
case PA: PORTA = ((value != 0) ? (PORTA | (1 << pin)) : (PORTA & (~(1 << pin)))); return;
case PB: PORTB = ((value != 0) ? (PORTB | (1 << pin)) : (PORTB & (~(1 << pin)))); return;
case PC: PORTC = ((value != 0) ? (PORTC | (1 << pin)) : (PORTC & (~(1 << pin)))); return;
case PD: PORTD = ((value != 0) ? (PORTD | (1 << pin)) : (PORTD & (~(1 << pin)))); return;
}
}
This article is tagged:
Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)
This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/