Skip to content

Activity 4.2.6 — Advanced Arduino Projects


Learning Objectives

By the end of this lesson, students will be able to:

  1. Use analog inputs with Arduino to read sensor values
  2. Implement serial communication for debugging and data logging
  3. Utilize Arduino libraries to extend functionality
  4. Build advanced projects with multiple inputs and outputs

Vocabulary

Vocabulary (click to expand)
Term Definition
Analog Input A continuous voltage signal (0-5V) converted to digital value (0-1023)
map() Arduino function that converts a value from one range to another
Serial Monitor Arduino tool for displaying data and debugging
Library Pre-written code that adds functionality to Arduino
PWM (Pulse Width Modulation) Technique to simulate analog output using digital signals
Debouncing Filtering out multiple rapid signals from a mechanical switch

Part 1: Analog Input with Arduino

Digital signals are either ON or OFF. Analog signals can be any value between. Arduino's analog pins read voltages from 0V to 5V and convert them to numbers from 0 to 1023.

Reading Analog Sensors

// Basic analog read
int sensorValue = analogRead(A0);  // Reads 0-1023

// Using the value
if (sensorValue > 500) {
  // Do something when sensor reads > half
}

// Map to useful range
int mappedValue = map(sensorValue, 0, 1023, 0, 255);

Common Analog Sensors

Sensor What it measures Output range
Photoresistor Light level Varies with light
Thermistor Temperature Varies with temp
Potentiometer Rotation/position 0-1023
Force Sensitive Resistor Pressure Varies with force

Smoothing Readings

Analog sensors can be noisy. Averaging multiple readings helps:

// Simple averaging
int getSmoothReading(int pin) {
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += analogRead(pin);
    delay(1);  // Small delay between readings
  }
  return total / 10;  // Return average
}

Key insight: The map() function is essential for converting sensor readings to useful ranges. For example, mapping photoresistor (0-1023) to LED brightness (0-255).


Part 2: Serial Communication

Serial communication lets Arduino communicate with your computer. This is invaluable for debugging.

Basic Serial Functions

void setup() {
  Serial.begin(9600);  // Start communication at 9600 baud
}

void loop() {
  // Print a value
  int sensor = analogRead(A0);
  Serial.println(sensor);  // Print with newline

  // Print text and value
  Serial.print("Sensor: ");
  Serial.println(sensor);

  // Print formatted number
  Serial.println(sensor, DEC);  // Decimal
  Serial.println(sensor, HEX);  // Hexadecimal
  Serial.println(sensor, BIN);  // Binary

  delay(1000);  // Don't flood the monitor
}

Debugging with Serial

Use Serial to track what's happening in your code:

void loop() {
  int sensorValue = analogRead(A0);

  Serial.print("Raw: ");
  Serial.println(sensorValue);

  int speed = map(sensorValue, 0, 1023, 0, 255);

  Serial.print("Speed: ");
  Serial.println(speed);

  // Now we know the mapping worked!
}

Part 3: Using Libraries

Libraries are pre-written code that add functionality. Arduino includes many, and you can add more.

Built-in Libraries

Library Purpose
Servo Control servo motors
LiquidCrystal Control LCD displays
Wire I2C communication
SPI SPI communication
SoftwareSerial Additional serial ports

Using a Library

// Include the library
#include <Servo.h>

// Create an object
Servo myServo;

void setup() {
  // Attach servo to pin
  myServo.attach(9);
}

void loop() {
  // Use the library
  myServo.write(90);  // Move to 90 degrees
  delay(1000);
}

Part 4: Project 1 — Follow My Lead (Memory Game)

Build a Simon-says style memory game where players repeat a pattern of LED flashes.

Circuit

  • 4 LEDs (red, yellow, green, blue) on pins 5, 6, 7, 8
  • 4 Pushbuttons on pins 2, 3, 4, A0
  • Buzzer on pin 12

Code

#include <Servo.h>

// Game pins
const int LEDS[] = {5, 6, 7, 8};
const int BUTTONS[] = {2, 3, 4, A0};

// Game variables
int sequence[20];
int sequenceLength = 0;
int playerStep = 0;
int score = 0;
bool gameActive = false;

void setup() {
  // Setup LEDs as outputs
  for (int i = 0; i < 4; i++) {
    pinMode(LEDS[i], OUTPUT);
    pinMode(BUTTONS[i], INPUT_PULLUP);
  }
  Serial.begin(9600);
  randomSeed(analogRead(A1));  // Random seed

  // Start game
  startNewGame();
}

void loop() {
  if (gameActive) {
    // Check player input
    for (int i = 0; i < 4; i++) {
      if (digitalRead(BUTTONS[i]) == LOW) {
        delay(50);  // Debounce

        // Light LED
        digitalWrite(LEDS[i], HIGH);
        delay(200);
        digitalWrite(LEDS[i], LOW);

        // Check if correct
        if (i == sequence[playerStep]) {
          playerStep++;
          Serial.print("Correct! Step: ");
          Serial.println(playerStep);

          // Check if sequence complete
          if (playerStep >= sequenceLength) {
            Serial.println("Sequence complete!");
            delay(1000);
            addToSequence();
          }
        } else {
          // Wrong button - game over
          Serial.print("Wrong! Game over. Score: ");
          Serial.println(score);
          gameOver();
        }

        // Wait for button release
        while (digitalRead(BUTTONS[i]) == LOW) {
          delay(10);
        }
      }
    }
  }
}

void startNewGame() {
  sequenceLength = 0;
  score = 0;
  playerStep = 0;
  gameActive = true;
  Serial.println("=== NEW GAME ===");
  addToSequence();
  playSequence();
}

void addToSequence() {
  sequence[sequenceLength] = random(0, 4);
  sequenceLength++;
  score = sequenceLength - 1;
  Serial.print("New sequence length: ");
  Serial.println(sequenceLength);
}

void playSequence() {
  Serial.println("Playing sequence...");
  for (int i = 0; i < sequenceLength; i++) {
    digitalWrite(LEDS[sequence[i]], HIGH);
    delay(300);
    digitalWrite(LEDS[sequence[i]], LOW);
    delay(200);
  }
  playerStep = 0;
  Serial.println("Your turn!");
}

void gameOver() {
  gameActive = false;
  // Flash all LEDs to indicate game over
  for (int j = 0; j < 3; j++) {
    for (int i = 0; i < 4; i++) {
      digitalWrite(LEDS[i], HIGH);
    }
    delay(200);
    for (int i = 0; i < 4; i++) {
      digitalWrite(LEDS[i], LOW);
    }
    delay(200);
  }
  delay(2000);
  startNewGame();
}

Modification Challenges

  • Add difficulty levels that change speed
  • Add sound effects for each LED
  • Add a "high score" display
  • Add a two-player mode

Part 5: Project 2 — Race Countdown

Create a countdown timer with LEDs that counts down 3-2-1-GO, then lights a green LED.

Circuit

  • 3 Red LEDs on pins 5, 6, 7
  • 1 Green LED on pin 8
  • Buzzer on pin 12

Code

// Race Countdown Timer

const int RED1 = 5;
const int RED2 = 6;
const int RED3 = 7;
const int GREEN = 8;
const int BUZZER = 12;

void setup() {
  pinMode(RED1, OUTPUT);
  pinMode(RED2, OUTPUT);
  pinMode(RED3, OUTPUT);
  pinMode(GREEN, OUTPUT);
  pinMode(BUZZER, OUTPUT);

  // Start countdown
  startCountdown();
}

void loop() {
  // Wait here after countdown - could add race start logic
  delay(100);
}

void startCountdown() {
  // All off initially
  digitalWrite(RED1, LOW);
  digitalWrite(RED2, LOW);
  digitalWrite(RED3, LOW);
  digitalWrite(GREEN, LOW);

  delay(1000);

  // 3
  digitalWrite(RED3, HIGH);
  tone(BUZZER, 440, 200);  // A4
  delay(1000);
  digitalWrite(RED3, LOW);

  // 2
  digitalWrite(RED2, HIGH);
  tone(BUZZER, 440, 200);
  delay(1000);
  digitalWrite(RED2, LOW);

  // 1
  digitalWrite(RED1, HIGH);
  tone(BUZZER, 440, 200);
  delay(1000);
  digitalWrite(RED1, LOW);

  // GO!
  digitalWrite(GREEN, HIGH);
  tone(BUZZER, 880, 500);  // Higher pitch for GO
}

Modification Challenges

  • Add a start button to trigger countdown
  • Add random false start detection
  • Add multiple racers (separate countdown timers)
  • Add lap time recording

Part 6: Project 3 — Remote Control Servo

Control a servo's position using a potentiometer.

Circuit

  • Servo motor on pin 9
  • Potentiometer on A0

Code

#include <Servo.h>

Servo myServo;
const int POT = A0;
const int SERVO_PIN = 9;

void setup() {
  myServo.attach(SERVO_PIN);
  Serial.begin(9600);
}

void loop() {
  // Read potentiometer (0-1023)
  int potValue = analogRead(POT);

  // Map to servo range (0-180 degrees)
  int angle = map(potValue, 0, 1023, 0, 180);

  // Move servo
  myServo.write(angle);

  // Debug output
  Serial.print("Pot: ");
  Serial.print(potValue);
  Serial.print(" -> Angle: ");
  Serial.println(angle);

  delay(20);  // Small delay for stability
}

Modification Challenges

  • Add limits to the range (e.g., 45-135 degrees)
  • Add multiple servos controlled by multiple pots
  • Add LED indicators for position ranges
  • Add auto-sweep mode with button toggle

Part 7: Project 4 — Ball Drop (Reaction Timer)

Test reaction time by detecting when an object falls past a sensor.

Circuit

  • IR Sensor or photoresistor on pin 2
  • Start button on pin 3
  • 7-segment display or LEDs for score
  • Buzzer on pin 12

Code

// Ball Drop - Reaction Time Tester

const int SENSOR = 2;
const int START_BUTTON = 3;
const int BUZZER = 12;

const int LED1 = 5;
const int LED2 = 6;
const int LED3 = 7;

unsigned long startTime;
unsigned long reactionTime;
bool waitingForDrop = false;
bool measuring = false;

void setup() {
  pinMode(SENSOR, INPUT);
  pinMode(START_BUTTON, INPUT_PULLUP);
  pinMode(BUZZER, OUTPUT);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);

  Serial.begin(9600);
  Serial.println("Ball Drop - Reaction Timer");
}

void loop() {
  // Check for start button
  if (digitalRead(START_BUTTON) == LOW && !waitingForDrop && !measuring) {
    startGame();
  }

  // Detect ball drop
  if (measuring && digitalRead(SENSOR) == LOW) {
    // Ball passed sensor!
    measuring = false;
    reactionTime = millis() - startTime;

    Serial.print("Reaction time: ");
    Serial.println(reactionTime);

    displayResult(reactionTime);

    waitingForDrop = false;
  }
}

void startGame() {
  Serial.println("Wait for drop...");

  // Clear LEDs
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);
  digitalWrite(LED3, LOW);

  // Random delay before "drop" (2-5 seconds)
  delay(random(2000, 5000));

  // Signal drop
  tone(BUZZER, 1000, 100);
  delay(50);
  noTone(BUZZER);

  // Start timing
  startTime = millis();
  measuring = true;
  waitingForDrop = true;

  Serial.println("DROP!");
}

void displayResult(unsigned long time) {
  // Show result on LEDs
  // Fast: LED1 only
  // Medium: LED1 + LED2  
  // Slow: All three

  if (time < 300) {
    digitalWrite(LED1, HIGH);
    Serial.println("Fast!");
  } else if (time < 500) {
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    Serial.println("Medium");
  } else {
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    digitalWrite(LED3, HIGH);
    Serial.println("Slow");
  }

  // Success sound
  tone(BUZZER, 880, 200);
  delay(250);
  noTone(BUZZER);
}

Modification Challenges

  • Add a display showing actual milliseconds
  • Add high score tracking
  • Add multiple difficulty levels
  • Use servo to actually drop a ball (mechanical)

Part 8: Best Practices

Comment Your Code

// Good comments explain WHY, not just WHAT
// This calculates speed because we need it for the motor
int speed = map(sensorValue, 0, 1023, 0, 255);

Use Functions

// Better than one long loop
void updateDisplay() {
  // Display update code here
}

void readSensors() {
  // Sensor reading code here
}

Debounce Buttons

// Simple software debounce
int buttonState = digitalRead(BUTTON_PIN);
static int lastButtonState = HIGH;
static unsigned long lastDebounceTime = 0;

if (buttonState != lastButtonState) {
  lastDebounceTime = millis();
}

if (millis() - lastDebounceTime > 50) {
  // Button state is stable - use it
}

Use millis() for Timing

// Instead of delay(), use millis() for non-blocking timing
unsigned long previousMillis = 0;
const long interval = 1000;

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    // Do something every second
  }
}

Key insight: These practices will save you hours of debugging and make your code much easier to understand and modify.


Practice Problem — Combine Projects

Problem: Design a project that combines elements from at least two of the four projects above. For example: - Memory game with servo-controlled target - Countdown with analog speed control - Reaction timer with serial output

What would you build? List the components and write the key code sections.

Show Solution
Example: "Race Timer with Memory"

Combines: Race Countdown + Memory Game

Description: Two players race to repeat a pattern first.
Each player has 4 buttons and 4 LEDs. The Arduino shows
a pattern, and both players try to repeat it fastest.

Components:
- 2 sets of 4 LEDs (pins 5-8 and A1-A4)
- 2 sets of 4 buttons (pins 2-5 and A0-A3)
- Buzzer on pin 12

Key Code Structure:
1. Generate random 5-pattern sequence
2. Play pattern on BOTH players' LEDs simultaneously
3. Both players try to enter pattern on their buttons
4. First to complete wins
5. Race countdown starts first

This combines timing (Race Countdown) with memory (Follow My Lead).

Summary

  • Analog input reads continuous values (0-1023) from sensors
  • map() converts values between different ranges
  • Serial communication enables debugging and data logging
  • Libraries provide pre-built functionality (Servo, LiquidCrystal)
  • Best practices: comment code, use functions, debounce inputs, use millis()
  • Projects combine multiple concepts: inputs, processing, outputs, timing

Key Reminders

  • Always use Serial.println() for debugging
  • Test each feature separately before combining
  • Keep backup copies of working code
  • Use functions to organize code
  • Remember to #include libraries at the top

Custom activity — adapted from PLTW Digital Electronics