Project 15: GPIO Control
Project 15: GPIO Control¶
Control LEDs and buttons using GPIO (General Purpose Input/Output).
Learning Objectives¶
- Understand GPIO input/output concepts
- LED control (digital output)
- Button reading (digital input)
- Understand pull-up/pull-down resistors
- Learn debouncing techniques
Prerequisites¶
- Arduino basic structure (setup, loop)
- Bit operation basics
1. GPIO Concepts¶
What is GPIO?¶
GPIO (General Purpose Input/Output) are general-purpose digital pins on an MCU that can be freely configured as input or output through programming.
Two modes of GPIO pins:
Output Mode (OUTPUT)
+-------------------------------------+
| MCU outputs voltage to the pin |
| - HIGH: 5V (or 3.3V) |
| - LOW: 0V (GND) |
| Example: Turn LED on/off, relay |
+-------------------------------------+
Input Mode (INPUT)
+-------------------------------------+
| MCU reads voltage on the pin |
| - HIGH: Above threshold (~3V) |
| - LOW: Below threshold (~1.5V) |
| Example: Read button, sensor |
+-------------------------------------+
Arduino Uno GPIO Pins¶
Arduino Uno Pin Layout:
+------------------------------------------------+
| |
| Digital Pins: 0 ~ 13 (14 total) |
| - 0, 1: Serial communication (TX, RX) |
| - 2, 3: External interrupt capable |
| - 3, 5, 6, 9, 10, 11: PWM output capable (~) |
| - 13: Built-in LED connected |
| |
| Analog Pins: A0 ~ A5 (6 total) |
| - Analog input capable (ADC) |
| - Can also be used as digital I/O |
| |
+------------------------------------------------+
2. Digital Output: LED Control¶
Basic Circuit Connection¶
LED Connection Methods:
Method 1: Pin -> Resistor -> LED -> GND (sinking)
+------+
| Pin 9|---[330R]---[LED]---GND
+------+
* LED turns on with HIGH output
Method 2: VCC -> LED -> Resistor -> Pin (sourcing)
VCC---[LED]---[330R]---| Pin 9|
+------+
* LED turns on with LOW output
Resistor Value Calculation:
R = (V_supply - V_led) / I_led
= (5V - 2V) / 10mA
= 300R -> Use 330R (standard value)
Basic LED On/Off¶
// led_basic.ino
// Most basic LED control
const int LED_PIN = 9; // LED connected pin
void setup() {
// Set pin mode to output
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// Turn LED on (HIGH = 5V output)
digitalWrite(LED_PIN, HIGH);
delay(1000); // Wait 1 second
// Turn LED off (LOW = 0V output)
digitalWrite(LED_PIN, LOW);
delay(1000);
}
Setting Up Circuit in Wokwi¶
Build the circuit in Wokwi (https://wokwi.com) as follows:
- Add Arduino Uno
- Add LED (search in parts list)
- Add resistor (330 ohms)
- Connections:
- Pin 9 -> Resistor -> LED anode (+, long leg)
- LED cathode (-, short leg) -> GND
Sequential LED Lighting¶
// led_sequence.ino
// Light multiple LEDs sequentially
const int LED_PINS[] = {9, 10, 11, 12};
const int NUM_LEDS = 4;
void setup() {
// Set all LED pins to output
for (int i = 0; i < NUM_LEDS; i++) {
pinMode(LED_PINS[i], OUTPUT);
}
}
void loop() {
// Turn on sequentially
for (int i = 0; i < NUM_LEDS; i++) {
digitalWrite(LED_PINS[i], HIGH);
delay(200);
}
// Turn off sequentially
for (int i = 0; i < NUM_LEDS; i++) {
digitalWrite(LED_PINS[i], LOW);
delay(200);
}
}
Creating LED Patterns¶
// led_patterns.ino
// Various LED patterns
const int LED_PINS[] = {9, 10, 11, 12};
const int NUM_LEDS = 4;
void setup() {
for (int i = 0; i < NUM_LEDS; i++) {
pinMode(LED_PINS[i], OUTPUT);
}
}
// Set all LED states
void setLEDs(int pattern) {
for (int i = 0; i < NUM_LEDS; i++) {
digitalWrite(LED_PINS[i], (pattern >> i) & 1);
}
}
// Knight Rider pattern
void knightRider() {
// Left
for (int i = 0; i < NUM_LEDS; i++) {
setLEDs(1 << i);
delay(100);
}
// Right
for (int i = NUM_LEDS - 2; i > 0; i--) {
setLEDs(1 << i);
delay(100);
}
}
// Blink pattern
void blinkAll(int times, int delayMs) {
for (int i = 0; i < times; i++) {
setLEDs(0x0F); // All on
delay(delayMs);
setLEDs(0x00); // All off
delay(delayMs);
}
}
// Fill pattern
void fillPattern() {
for (int i = 0; i < NUM_LEDS; i++) {
setLEDs((1 << (i + 1)) - 1); // 0001, 0011, 0111, 1111
delay(200);
}
for (int i = NUM_LEDS; i > 0; i--) {
setLEDs((1 << i) - 1);
delay(200);
}
}
void loop() {
knightRider();
delay(500);
blinkAll(3, 200);
delay(500);
fillPattern();
delay(500);
}
3. Digital Input: Button Reading¶
Button Circuit Connection¶
Button Connection Methods:
Method 1: External pull-down resistor
VCC (5V)
|
[Button]
|
+-----+-----+
| |
[Pin] [10k]
|
GND
- Button not pressed: Pin = LOW (resistor pulls to GND)
- Button pressed: Pin = HIGH (connected to VCC)
Method 2: External pull-up resistor
VCC (5V)
|
[10k]
|
+-----+-----+
| |
[Pin] [Button]
|
GND
- Button not pressed: Pin = HIGH (resistor pulls to VCC)
- Button pressed: Pin = LOW (connected to GND)
Method 3: Internal pull-up resistor (recommended)
[Pin]---[Button]---GND
- Use pinMode(pin, INPUT_PULLUP)
- No external resistor needed
- Button not pressed: HIGH
- Button pressed: LOW
Basic Button Reading¶
// button_basic.ino
// Control LED with button
const int BUTTON_PIN = 2;
const int LED_PIN = 13;
void setup() {
// Use internal pull-up resistor
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
// Read button state
int buttonState = digitalRead(BUTTON_PIN);
// Turn on LED when button pressed (LOW = pressed)
if (buttonState == LOW) {
digitalWrite(LED_PIN, HIGH);
Serial.println("Button pressed!");
} else {
digitalWrite(LED_PIN, LOW);
}
delay(10); // Short delay
}
Button Toggle¶
// button_toggle.ino
// Toggle LED state with each button press
const int BUTTON_PIN = 2;
const int LED_PIN = 13;
bool ledState = false;
bool lastButtonState = HIGH; // HIGH is default with pull-up
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
bool currentButtonState = digitalRead(BUTTON_PIN);
// When button is pressed (HIGH -> LOW transition)
if (lastButtonState == HIGH && currentButtonState == LOW) {
ledState = !ledState; // Toggle LED state
digitalWrite(LED_PIN, ledState);
}
lastButtonState = currentButtonState;
delay(10);
}
4. Debouncing¶
The Bouncing Problem¶
Physical buttons cause multiple contact bounces when pressed, creating noise.
Actual button signal (bouncing):
Button press Button release
| |
HIGH -----+ +-+ +-+ +-+ +--------
| | | | | | | |
LOW +---+ +-+ +--------+ +-+
^^^ bouncing noise ^^^
Duration: Approximately 1-50ms
Software Debouncing¶
// debounce_software.ino
// Software debouncing
const int BUTTON_PIN = 2;
const int LED_PIN = 13;
bool ledState = false;
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // 50ms
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
Serial.begin(9600);
}
void loop() {
bool reading = digitalRead(BUTTON_PIN);
// Detect state change
if (reading != lastButtonState) {
lastDebounceTime = millis(); // Reset timer
}
// Confirm after stable for set time
if ((millis() - lastDebounceTime) > debounceDelay) {
static bool buttonState = HIGH;
if (reading != buttonState) {
buttonState = reading;
// Button press confirmed (HIGH -> LOW)
if (buttonState == LOW) {
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
Serial.println(ledState ? "LED ON" : "LED OFF");
}
}
}
lastButtonState = reading;
}
Creating a Button Class¶
// button_class.ino
// Reusable button class
class Button {
private:
int pin;
bool lastState;
bool currentState;
unsigned long lastDebounceTime;
unsigned long debounceDelay;
public:
Button(int p, unsigned long delay = 50) {
pin = p;
debounceDelay = delay;
lastState = HIGH;
currentState = HIGH;
lastDebounceTime = 0;
}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
// Update and detect press
bool pressed() {
bool reading = digitalRead(pin);
if (reading != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != currentState) {
currentState = reading;
if (currentState == LOW) {
lastState = reading;
return true; // Button pressed!
}
}
}
lastState = reading;
return false;
}
// Current state (is being held)
bool isPressed() {
return currentState == LOW;
}
};
// Usage example
Button btn1(2);
Button btn2(3);
const int LED1 = 12;
const int LED2 = 13;
void setup() {
btn1.begin();
btn2.begin();
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
Serial.begin(9600);
}
void loop() {
if (btn1.pressed()) {
digitalWrite(LED1, !digitalRead(LED1));
Serial.println("Button 1 pressed");
}
if (btn2.pressed()) {
digitalWrite(LED2, !digitalRead(LED2));
Serial.println("Button 2 pressed");
}
}
5. Practice Project: LED Controller¶
Project Overview¶
Control 4 LEDs with 2 buttons: - Button 1: Change pattern - Button 2: Change speed
// led_controller.ino
// Control LED pattern and speed with buttons
const int LED_PINS[] = {9, 10, 11, 12};
const int NUM_LEDS = 4;
const int BTN_PATTERN = 2; // Pattern change button
const int BTN_SPEED = 3; // Speed change button
// State variables
int currentPattern = 0;
const int NUM_PATTERNS = 4;
int speedLevel = 1; // 0=fast, 1=normal, 2=slow
const int SPEEDS[] = {50, 150, 300};
// Debouncing variables
bool lastBtnPattern = HIGH;
bool lastBtnSpeed = HIGH;
unsigned long lastDebouncePattern = 0;
unsigned long lastDebounceSpeed = 0;
const unsigned long debounceDelay = 50;
// Pattern timing
unsigned long lastPatternUpdate = 0;
int patternStep = 0;
void setup() {
for (int i = 0; i < NUM_LEDS; i++) {
pinMode(LED_PINS[i], OUTPUT);
}
pinMode(BTN_PATTERN, INPUT_PULLUP);
pinMode(BTN_SPEED, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("LED Controller");
Serial.println("BTN1: Change pattern, BTN2: Change speed");
}
void setLEDs(byte pattern) {
for (int i = 0; i < NUM_LEDS; i++) {
digitalWrite(LED_PINS[i], (pattern >> i) & 1);
}
}
// Pattern 0: Sequential lighting
void pattern0() {
setLEDs(1 << (patternStep % NUM_LEDS));
}
// Pattern 1: Knight Rider
void pattern1() {
int pos = patternStep % (NUM_LEDS * 2 - 2);
if (pos >= NUM_LEDS) {
pos = NUM_LEDS * 2 - 2 - pos;
}
setLEDs(1 << pos);
}
// Pattern 2: Fill
void pattern2() {
int step = patternStep % (NUM_LEDS * 2);
if (step < NUM_LEDS) {
setLEDs((1 << (step + 1)) - 1);
} else {
setLEDs((1 << (NUM_LEDS * 2 - step)) - 1);
}
}
// Pattern 3: Alternating blink
void pattern3() {
if (patternStep % 2 == 0) {
setLEDs(0b0101); // LED 0, 2
} else {
setLEDs(0b1010); // LED 1, 3
}
}
void updatePattern() {
switch (currentPattern) {
case 0: pattern0(); break;
case 1: pattern1(); break;
case 2: pattern2(); break;
case 3: pattern3(); break;
}
patternStep++;
}
bool checkButton(int pin, bool& lastState, unsigned long& lastTime) {
bool reading = digitalRead(pin);
if (reading != lastState) {
lastTime = millis();
}
if ((millis() - lastTime) > debounceDelay) {
if (reading == LOW && lastState == HIGH) {
lastState = reading;
return true;
}
}
lastState = reading;
return false;
}
void loop() {
// Button 1: Change pattern
if (checkButton(BTN_PATTERN, lastBtnPattern, lastDebouncePattern)) {
currentPattern = (currentPattern + 1) % NUM_PATTERNS;
patternStep = 0;
Serial.print("Pattern: ");
Serial.println(currentPattern);
}
// Button 2: Change speed
if (checkButton(BTN_SPEED, lastBtnSpeed, lastDebounceSpeed)) {
speedLevel = (speedLevel + 1) % 3;
Serial.print("Speed: ");
Serial.println(SPEEDS[speedLevel]);
}
// Update pattern
if (millis() - lastPatternUpdate >= SPEEDS[speedLevel]) {
lastPatternUpdate = millis();
updatePattern();
}
}
Wokwi Circuit Setup¶
Parts list:
- Arduino Uno x1
- LED x4 (red, yellow, green, blue)
- 330 ohm resistor x4
- Push button x2
Connections:
- LED: Pins 9, 10, 11, 12 -> Resistor -> LED -> GND
- Button 1: Pin 2 <-> GND
- Button 2: Pin 3 <-> GND
6. Direct Register Control¶
Controlling registers directly instead of Arduino functions is much faster.
LED Control with Registers¶
// register_gpio.ino
// Direct GPIO control with registers
void setup() {
// DDRB: Port B Direction Register (pins 8-13)
// Set bits 1-4 to output (pins 9-12)
DDRB |= 0b00011110;
// DDRD: Port D Direction Register (pins 0-7)
// Set bits 2, 3 to input (pins 2, 3)
DDRD &= ~0b00001100;
// PORTD: Enable pull-up
PORTD |= 0b00001100;
Serial.begin(9600);
}
void loop() {
// PIND: Port D Input Register
// Check button 1 (pin 2)
if (!(PIND & 0b00000100)) { // Bit 2
// All LEDs on
PORTB |= 0b00011110;
Serial.println("All ON");
}
// Check button 2 (pin 3)
if (!(PIND & 0b00001000)) { // Bit 3
// All LEDs off
PORTB &= ~0b00011110;
Serial.println("All OFF");
}
// LED shift pattern
static unsigned long lastUpdate = 0;
static byte ledPattern = 0b00000010; // Start from pin 9
if (millis() - lastUpdate > 200) {
lastUpdate = millis();
PORTB = (PORTB & ~0b00011110) | ledPattern;
// Shift left
ledPattern <<= 1;
if (ledPattern > 0b00010000) {
ledPattern = 0b00000010;
}
}
}
Port Mapping Reference¶
Arduino Uno Port Mapping:
Port B (PORTB, DDRB, PINB):
Bit 0: Pin 8
Bit 1: Pin 9
Bit 2: Pin 10
Bit 3: Pin 11
Bit 4: Pin 12
Bit 5: Pin 13 (Built-in LED)
Port D (PORTD, DDRD, PIND):
Bit 0: Pin 0 (RX)
Bit 1: Pin 1 (TX)
Bit 2: Pin 2
Bit 3: Pin 3
Bit 4: Pin 4
Bit 5: Pin 5
Bit 6: Pin 6
Bit 7: Pin 7
Port C (PORTC, DDRC, PINC):
Bits 0-5: A0-A5
7. Practice Project: Reaction Time Game¶
// reaction_game.ino
// Press button when LED turns on and measure reaction time
const int LED_PIN = 13;
const int BUTTON_PIN = 2;
enum GameState { WAITING, READY, PLAYING, RESULT };
GameState state = WAITING;
unsigned long ledOnTime = 0;
unsigned long reactionTime = 0;
unsigned long waitStart = 0;
int bestTime = 9999;
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.begin(9600);
Serial.println("=== Reaction Time Game ===");
Serial.println("Press button to start");
}
void loop() {
bool buttonPressed = (digitalRead(BUTTON_PIN) == LOW);
switch (state) {
case WAITING:
// Press button to start game
if (buttonPressed) {
state = READY;
waitStart = millis();
Serial.println("\nGet ready...");
// Wait for button release
while (digitalRead(BUTTON_PIN) == LOW);
}
break;
case READY:
// Turn on LED after random time
if (millis() - waitStart > random(2000, 5000)) {
digitalWrite(LED_PIN, HIGH);
ledOnTime = millis();
state = PLAYING;
Serial.println("GO!");
}
// Too early press = fail
if (buttonPressed) {
Serial.println("Too early! Try again.");
state = WAITING;
}
break;
case PLAYING:
// Measure time when button pressed
if (buttonPressed) {
reactionTime = millis() - ledOnTime;
digitalWrite(LED_PIN, LOW);
state = RESULT;
}
// Timeout (3 seconds)
if (millis() - ledOnTime > 3000) {
digitalWrite(LED_PIN, LOW);
Serial.println("Too slow! (> 3 seconds)");
state = WAITING;
}
break;
case RESULT:
Serial.print("Reaction time: ");
Serial.print(reactionTime);
Serial.println(" ms");
if (reactionTime < bestTime) {
bestTime = reactionTime;
Serial.print("New best time: ");
Serial.print(bestTime);
Serial.println(" ms!");
}
Serial.println("\nPress button to play again");
// Wait for button release
while (digitalRead(BUTTON_PIN) == LOW);
delay(500);
state = WAITING;
break;
}
delay(10);
}
Exercises¶
Exercise 1: Binary Counter¶
Count from 0 to 15 with 4 LEDs. Increment by 1 each button press.
Exercise 2: Traffic Light¶
Implement a traffic light with red-yellow-green LEDs. - Red: 3 seconds - Red+Yellow: 1 second - Green: 3 seconds - Yellow: 1 second - Repeat
Exercise 3: Multiple Buttons¶
Control different LEDs with 3 buttons. All blink when pressed simultaneously.
Exercise 4: Morse Code Input¶
Short button press = dot (.) Long button press = dash (-) Display entered Morse code on serial monitor
Key Concepts Summary¶
| Function | Description |
|---|---|
pinMode(pin, mode) |
Set pin mode (INPUT, OUTPUT, INPUT_PULLUP) |
digitalWrite(pin, val) |
Digital output (HIGH, LOW) |
digitalRead(pin) |
Read digital input |
| Concept | Description |
|---|---|
| Pull-up resistor | Keep input pin HIGH, LOW when button pressed |
| Pull-down resistor | Keep input pin LOW, HIGH when button pressed |
| Debouncing | Button noise removal technique |
| Registers | DDRx (direction), PORTx (output), PINx (input) |
Next Step¶
After mastering GPIO control, proceed to the next document: - 17. Serial Communication - UART communication and debugging