The full sketches for all 15 sessions, ready to copy into the Arduino IDE.
Free for parents and teachers.
// Blink - the first program: the built-in LED on the board turns on and off nonstop
// Every Arduino sketch is built from two functions: setup, which runs once, followed by loop
void setup() { // runs only once, the moment the board gets power
pinMode(LED_BUILTIN, OUTPUT); // LED_BUILTIN is pin 13 on the board, and OUTPUT means the pin pushes voltage out
}
void loop() { // runs over and over without end, as long as the board has power
digitalWrite(LED_BUILTIN, HIGH); // HIGH raises the pin to 5 volts, and the LED turns on
delay(500); // waits half a second. delay stops everything, nothing else happens in the meantime
digitalWrite(LED_BUILTIN, LOW); // LOW returns the pin to 0 volts, and the LED turns off
delay(500);
}
// A blinking LED: turns on, waits, turns off, waits, and repeats forever in a loop
// The LED’s long leg goes to pin 8, the short leg goes through a 220 ohm resistor to GND
int ledPin = 8; // store the pin number in a variable, so it can be changed in one place only
void setup() {
// set the pin as an output: the Arduino sends voltage out through it to the LED
pinMode(ledPin, OUTPUT);
}
void loop() {
digitalWrite(ledPin, HIGH); // HIGH means 5 volts on the pin, so the LED turns on
delay(800); // wait 800 milliseconds, that is 0.8 seconds in which nothing else happens
digitalWrite(ledPin, LOW); // LOW means 0 volts, and the LED turns off
delay(800);
}
// A real traffic light: three lights and a pedestrian button.
// The key idea - check the button while waiting, not only at the end of the wait.
int red = 2, yellow = 3, green = 4; // the three lights on pins D2, D3, D4
int button = 7; // the button on pin D7, with a 10 kilo-ohm resistor to GND
void setup() {
// the three lights are OUTPUT: the Arduino pushes voltage to them
pinMode(red, OUTPUT);
pinMode(yellow, OUTPUT);
pinMode(green, OUTPUT);
pinMode(button, INPUT); // the button is INPUT: the Arduino reads from it
}
// our own function that turns all three lights off, so two are never on together
void allOff() {
digitalWrite(red, LOW);
digitalWrite(yellow, LOW);
digitalWrite(green, LOW);
}
void loop() {
// green for cars
allOff(); digitalWrite(green, HIGH);
// delay stops everything, so we don’t wait 4 seconds in one go
// but 40 short waits, and between each one we check the button
for (int i = 0; i < 40; i++) { // about 4 seconds, with a button check
if (digitalRead(button) == HIGH) { pedestrian(); return; } // a press ends the wait
delay(100);
}
allOff(); digitalWrite(yellow, HIGH); delay(1000); // yellow for one second: warning
allOff(); digitalWrite(red, HIGH); delay(3000); // red for three seconds
}
// a separate function for crossing mode: short yellow, then long red
void pedestrian() {
allOff(); digitalWrite(yellow, HIGH); delay(800);
allOff(); digitalWrite(red, HIGH); delay(4000); // safe crossing time
}
// Dimmer: the potentiometer sets how brightly the LED glows
// This time we read an analog value, not just on or off
int pot = A0; // the middle leg of the potentiometer is connected to analog pin A0
int led = 9; // a PWM pin, so it can also give in-between brightness levels
void setup() {
pinMode(led, OUTPUT);
Serial.begin(9600); // opens a connection to the Serial Monitor so we can see the readings
}
void loop() {
int value = analogRead(pot); // analogRead returns a number from 0 to 1023
// map translates between the two scales: from a reading of 0 to 1023 to a brightness of 0 to 255
int bright = map(value, 0, 1023, 0, 255);
analogWrite(led, bright); // 0 to 255: how strongly the pin pushes voltage to the LED
Serial.println(value);
delay(20); // a short wait between readings, so the screen doesn’t get flooded
}
// Automatic night light: a light sensor (LDR) turns on an LED when the room is dark
// The idea: the controller decides on its own when to turn on, with nobody pressing a button
int ldr = A0; // the light sensor is connected to analog pin A0. In the simulator it is on A1
int led = 9; // the LED is connected to pin 9 through a resistor
int threshold = 400; // the darkness threshold. Tune it to what you see in the Serial Monitor
void setup() {
pinMode(led, OUTPUT);
Serial.begin(9600); // opens a channel to the computer so we can see the sensor values
}
void loop() {
int light = analogRead(ldr); // the light level as a number from 0 to 1023
Serial.println(light); // sends the value to the screen, which is how you find the right threshold
// here is the whole program: less light than the threshold means dark, so turn it on
if (light < threshold) digitalWrite(led, HIGH); // dark, so on
else digitalWrite(led, LOW); // bright, so off
delay(100); // a short rest, otherwise the readings run too fast and the light flickers
}
// A three-key piano: each button plays a different note on the buzzer.
// The note plays as long as the button is held, and stops the moment it is released.
int buzzer = 8;
int keyC = 2, keyE = 3, keyG = 4; // the three keys: C · E · G
void setup() {
// INPUT means the pin only reads voltage from the button, and does not send voltage out
pinMode(keyC, INPUT);
pinMode(keyE, INPUT);
pinMode(keyG, INPUT);
// the 10k resistor on each button pulls the pin to GND, so when nobody presses, the reading is LOW
}
void loop() {
// an if - else if chain: only one note plays at a time, the first one found pressed
if (digitalRead(keyC) == HIGH) tone(buzzer, 262); // 262 Hz - the note C
else if (digitalRead(keyE) == HIGH) tone(buzzer, 330); // 330 Hz - the note E
else if (digitalRead(keyG) == HIGH) tone(buzzer, 392); // 392 Hz - the note G
else noTone(buzzer); // without noTone the sound would stay stuck even after release
}
// RGB lamp: mix red, green and blue to get any color you want.
// Each channel gets an intensity from 0 to 255 through PWM.
int rPin = 9, gPin = 10, bPin = 11; // the three channels on PWM pins (marked with a ~)
// our own function: takes three intensities and lights all three colors together
void setColor(int r, int g, int b) {
analogWrite(rPin, r); // 0 is fully off, 255 is full
analogWrite(gPin, g);
analogWrite(bPin, b);
}
void setup() { } // empty: analogWrite doesn’t require pinMode
void loop() {
// four fixed colors, each stays for 700 milliseconds
setColor(255, 0, 0); delay(700); // red
setColor(0, 255, 0); delay(700); // green
setColor(0, 0, 255); delay(700); // blue
setColor(255, 80, 0); delay(700); // orange - a little green mixed into the red
// the loop raises red and blue together, step by step, so the color slowly creeps toward purple
for (int i = 0; i <= 255; i++) { // smooth fade
setColor(i, 0, i);
delay(6); // tiny delay: 256 steps times 6 is about a second and a half
}
}
// Digital name tag: the screen shows two fixed lines of text
// I2C saves wires: just SDA to A4 and SCL to A5 carry all the data
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// 0x27 is the screen’s address on the I2C line, 16 columns and 2 rows
LiquidCrystal_I2C lcd(0x27, 16, 2); // if the screen stays blank, try address 0x3F
void setup() {
// init wakes the screen, backlight turns on the light behind the letters
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0); // column 0, row 0: counting starts at zero, not one
lcd.print("Team CHIPS!");
lcd.setCursor(0, 1); // without moving to the second row, the next text would overwrite the first
lcd.print("We build things");
}
// the text stays on the screen without refreshing it, so loop stays empty
void loop() { }
// Weather station: a DHT11 sensor measures the air temperature and humidity
// and the numbers show on an LCD connected with just two I2C wires
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h> // the library that knows how to talk to a DHT sensor
#define DHTPIN 2 // the sensor’s OUT pin is connected to pin D2
#define DHTTYPE DHT11 // the sensor type: the blue one is a DHT11, not a DHT22
DHT dht(DHTPIN, DHTTYPE); // set up the sensor: which pin and which model
LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C address, two rows of 16 characters
void setup() {
lcd.init();
lcd.backlight(); // without the backlight the text is written but barely visible
dht.begin(); // wake the sensor once at the start
}
void loop() {
// the temperature comes out as a number with a decimal point, so float and not int
float t = dht.readTemperature();
float h = dht.readHumidity();
lcd.setCursor(0, 0); // position the cursor: column 0, top row
lcd.print("Temp: ");
lcd.print(t, 1); // the 1 means one digit after the decimal point
lcd.print((char)223); // the degree-symbol character on the screen
lcd.print("C "); // the spaces erase a leftover digit from the previous reading
lcd.setCursor(0, 1); // same idea on the bottom row
lcd.print("Hum: ");
lcd.print(h, 0); // zero digits after the decimal point: a rounded humidity percentage
lcd.print("% ");
delay(2000); // a DHT11 sensor needs about two seconds between readings
}
// A servo arm controlled by a joystick: the stick’s position becomes the servo’s angle
// The Servo library sends the servo the pulses that tell it which angle to turn to
#include <Servo.h>
Servo arm; // an object that represents the arm’s servo
int joyX = A0; // the joystick’s X axis, connected to analog pin A0
void setup() {
arm.attach(6); // the servo’s orange wire is on pin 6; brown to GND and red to 5V
}
void loop() {
int x = analogRead(joyX); // a value from 0 to 1023 depending on the stick’s position
// map stretches the range 0 to 1023 into 0 to 180 degrees, and write takes an angle, not a speed
int angle = map(x, 0, 1023, 0, 180);
arm.write(angle);
delay(15); // a short wait that lets the arm finish moving before the next reading
}
// Digital safe: type a 4-digit code on the keypad,
// the screen shows stars, and if the code is right the servo opens the lock.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <Servo.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C screen at address 0x27, 16 characters on 2 rows
Servo lockServo; // the servo that turns the lock’s bolt
// 4 by 4 keypad: the table says which character sits at each row and column crossing
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {9, 8, 7, 6}; // the keypad’s four row pins
byte colPins[COLS] = {5, 4, 3, 2}; // the keypad’s four column pins
// the library scans row against column and works out which button was pressed
Keypad pad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
String secret = "1234"; // the secret code, change it to your own
String input = ""; // this collects what has been typed so far
int buzzer = 12; // buzzer on pin 12
void setup() {
lcd.init(); lcd.backlight(); // start the screen and turn on its backlight
lockServo.attach(11); // the servo is connected to pin 11, which supports PWM
lockServo.write(0); // an angle of 0 degrees is the locked position
showEnter();
}
// our own function: clears the screen and asks for a new code
void showEnter() {
lcd.clear();
lcd.print("Enter code:");
lcd.setCursor(0, 1); // move to the second row, where the stars will appear
}
void loop() {
char key = pad.getKey(); // returns a button’s character, or zero if no button is pressed
// if nothing was pressed, just exit and check again right away, so the safe is always listening
if (!key) return;
if (key == '*') { input = ""; showEnter(); return; } // the star key clears everything and starts over
input += key; // add the digit to the end of the string
lcd.print("*"); // show a star so nobody can see the code
tone(buzzer, 900, 60); // a short beep that makes the press feel registered
// only once 4 digits have piled up, compare what was typed to the secret code
if (input.length() == 4) {
if (input == secret) openSafe();
else wrongCode();
input = ""; // clear it so we can try again
}
}
void openSafe() {
lcd.clear(); lcd.print("OPEN :)");
tone(buzzer, 660, 120); delay(150); tone(buzzer, 990, 200); // two rising tones, sounds like opening
lockServo.write(90); // 90 degrees turns the bolt and opens it
delay(4000); // the safe stays open for 4 seconds and then locks by itself
lockServo.write(0);
showEnter();
}
void wrongCode() {
lcd.clear(); lcd.print("WRONG!");
tone(buzzer, 150, 500); // a low, long tone sounds like a mistake
delay(1200);
showEnter();
}
// Stage 1: reads an RFID card and prints its UID in the Serial Monitor
// Every card in the world has a unique UID, and here you find out your card’s UID
#include <SPI.h> // SPI is the fast communication channel between the Arduino and the reader
#include <MFRC522.h> // the library that runs the RC522 RFID module
MFRC522 rfid(10, 9); // SDA(SS) to pin 10, RST to pin 9
void setup() {
Serial.begin(9600); // exactly the same speed must be chosen in the Serial Monitor window
SPI.begin(); // starts the SCK, MOSI, MISO lines that lead to the reader
rfid.PCD_Init(); // wakes the reader chip and gets it ready to scan
Serial.println("Scan a card...");
}
void loop() {
// if there’s no new card at the reader, return exits right away and starts a new pass of loop
if (!rfid.PICC_IsNewCardPresent()) return;
if (!rfid.PICC_ReadCardSerial()) return; // copies the card’s UID into memory
Serial.print("UID: ");
// the UID is made of several bytes, so we need a loop that prints them one after another
for (byte i = 0; i < rfid.uid.size; i++) {
Serial.print(rfid.uid.uidByte[i], HEX); // HEX is the usual way to write a UID
Serial.print(" ");
}
Serial.println();
rfid.PICC_HaltA(); // puts the card to sleep, so the next tap counts as a new card
}
// Stage 2: a real RFID lock - only the authorized card lights green, all the others light red
// This is exactly how a building access badge works: compare the UID to an approved list
#include <SPI.h>
#include <MFRC522.h>
MFRC522 rfid(10, 9); // SDA(SS) to pin 10, RST to pin 9
int greenLed = 2, redLed = 3; // green for approved on pin D2, red for rejected on pin D3
// paste YOUR UID from stage 1 here:
// paste here the four bytes you saw in the Serial Monitor in stage 1
// 0x means the number is written in hexadecimal, exactly as it was printed
byte allowed[4] = {0xA1, 0xB2, 0xC3, 0xD4};
void setup() {
SPI.begin(); // starts the fast communication lines to the reader
rfid.PCD_Init(); // wakes the reader chip
pinMode(greenLed, OUTPUT);
pinMode(redLed, OUTPUT);
}
void loop() {
// no card at the reader, so return ends this pass and starts over
if (!rfid.PICC_IsNewCardPresent()) return;
if (!rfid.PICC_ReadCardSerial()) return; // copies the UID into memory
// start by assuming the card is approved, and look for one byte that proves otherwise
bool ok = true;
for (byte i = 0; i < 4; i++)
if (rfid.uid.uidByte[i] != allowed[i]) ok = false; // a single different byte is enough to reject it
// the decision: green for an authorized card, red for any other card
if (ok) { digitalWrite(greenLed, HIGH); delay(1500); digitalWrite(greenLed, LOW); }
else { digitalWrite(redLed, HIGH); delay(1500); digitalWrite(redLed, LOW); }
rfid.PICC_HaltA(); // puts the card to sleep, otherwise the same tap would be read again and again
}
// Drawing on an 8 by 8 LED matrix with the MAX7219 chip:
// shows a smiley, and every so often it winks with one eye.
#include <LedControl.h>
// three wires run 64 LEDs: DIN is pin 11, CLK is 13, CS is 10
LedControl lc = LedControl(11, 13, 10, 1); // the last number: how many modules are chained together
// each byte is one row of the matrix, and each bit is one LED: 1 is on, 0 is off
// the B at the start says the number is written in binary, so you can see the drawing right in the code
byte smile[8] = {
B00111100,
B01000010,
B10100101,
B10000001,
B10100101,
B10011001,
B01000010,
B00111100
};
// the exact same drawing, with one tiny change that makes the wink
byte wink[8] = {
B00111100,
B01000010,
B10110101, // here we changed one bit, and that’s the eye closing
B10000001,
B10100101,
B10011001,
B01000010,
B00111100
};
// our own function: takes a whole drawing and sends it row by row
void draw(byte img[8]) {
for (int r = 0; r < 8; r++) lc.setRow(0, r, img[r]); // setRow lights 8 LEDs in one command
}
void setup() {
lc.shutdown(0, false); // wake the module from sleep mode, without this the display stays dark
lc.setIntensity(0, 6); // brightness from 0 to 15: too high is blinding and draws a lot of current
lc.clearDisplay(0); // clear leftover drawing from the previous run
}
void loop() {
draw(smile); delay(1200); // the smiley stays on the screen for a long time
draw(wink); delay(250); // the wink is short, so the eye looks like it closed for a moment
}
// Flood guard: a water sensor is read over and over, and if the water rises above a threshold
// the LED turns on and the buzzer sounds a two-tone siren
int sensor = A0; // the water sensor on analog pin A0
int buzzer = 8; // buzzer on pin D8
int led = 12; // warning LED on pin D12
int threshold = 600; // the threshold for the decision - tune it using the numbers in the Serial Monitor
void setup() {
pinMode(led, OUTPUT); // only the LED needs pinMode - tone handles the buzzer by itself
Serial.begin(9600); // opens the channel to the computer so we can see the sensor values
}
void loop() {
int level = analogRead(sensor); // analogRead returns a number from 0 to 1023 - how wet it is
Serial.println(level); // print it so we can choose a threshold that fits real water
if (level > threshold) { // there’s water! The value passed the threshold
// siren: two alternating tones together with the LED
// delay stops everything, so during this time the sensor isn’t read
digitalWrite(led, HIGH);
tone(buzzer, 1400, 150); // a high tone, 1400 Hz for 150 milliseconds
delay(200);
digitalWrite(led, LOW);
tone(buzzer, 900, 150); // a low tone - the difference between the two makes the siren
delay(200);
} else {
// no water - return everything to quiet and keep measuring
digitalWrite(led, LOW);
noTone(buzzer);
}
}
// === Team project: ___________ ===
// A skeleton for every final project: input, then a decision, then output
int sensorPin = A0; // your sensor. An analog value, so an A pin and not a numbered pin
int outPin1 = 8; // your output: an LED, buzzer or relay
int threshold = 500; // the decision threshold. Calibrate it using the numbers in the Serial Monitor
void setup() {
pinMode(outPin1, OUTPUT); // without this line digitalWrite doesn’t really push voltage
Serial.begin(9600); // 9600 must be the same number in the Serial Monitor too
Serial.println("Project start"); // a sign of life: no message means the code didn’t upload to the board
}
void loop() {
// 1) input: analogRead returns a number from 0 to 1023
int value = analogRead(sensorPin);
Serial.print("sensor="); // print before the decision, to see the real number
Serial.println(value);
// 2) decision: compare what was measured to the threshold we set
if (value > threshold) {
// 3) output: only here do we change something in the world
digitalWrite(outPin1, HIGH);
} else {
digitalWrite(outPin1, LOW); // it’s important to turn it off, otherwise the output stays on forever
}
delay(100); // delay stops everything. 100 is enough so the screen isn’t flooded with lines
}
// debugging method: isolate, print, replace
// Not working? Print every step, test one part by itself,
// and only then swap a wire or a part to find the culprit.
// Skeleton for demo night: at power-on the board runs a short self-test,
// blinks three times, and only then does your project run.
int statusLed = 13; // the built-in LED on the board at pin 13, no wiring needed
void setup() {
pinMode(statusLed, OUTPUT);
selfTest(); // the audience sees right away that the board is alive and the code is running
// paste your project’s setup lines here
}
// our own function: we call it by name from setup, even though it’s written below
// delay stops everything, and that’s fine here because the project hasn’t started yet
void selfTest() {
for (int i = 0; i < 3; i++) { // three blink repetitions
digitalWrite(statusLed, HIGH); delay(120);
digitalWrite(statusLed, LOW); delay(120);
}
}
void loop() {
// the main code of your project goes here
// demo tip: make sure something visible happens at least once every ten seconds,
// otherwise the audience thinks the project is stuck
}