Assessment Model Answers
A working reference sketch for each assessment, checked to compile for the Uno. Use them to check your own bench set-up and to mark. A learner's sketch does not need to match: mark the behaviour, the evidence and the explanation against the criteria grid.
Contents
Code-Correction Exercise
The given sketch with only the faults fixed. Six faults cover the five classes. Open the brief.
Fault inventory answer
| Line | Fault | Class |
|---|---|---|
| 2 and 12 | ButtonPin declared, buttonPin used | Capitalisation |
| 6 | PinMode instead of pinMode | Capitalisation |
| 6 | INPUT, but the button is wired to GND (floating input) | Pin mode vs wiring |
| 7 | Missing ; after pinMode(indicatorPin, OUTPUT) | Semicolon |
| 13 | if (pressed = true) assigns instead of comparing | = versus == |
| 15 | Missing } before else | Braces |
What to check
- Line numbers refer to the given sketch on the brief (line 1 is the comment).
- With if (pressed = true) left in, the LED stays on whether or not the button is pressed.
- Tidy-ups such as adding const are fine but are not faults.
Reference sketch
// Workshop door-bell indicator - MODEL ANSWER
// Each fixed line says which fault it was.
byte buttonPin = 2; // capitalisation: was ButtonPin, but loop() uses buttonPin
byte indicatorPin = 13;
void setup() {
pinMode(buttonPin, INPUT_PULLUP); // capitalisation: was PinMode
// pin mode: was INPUT, but the button goes to GND
pinMode(indicatorPin, OUTPUT); // semicolon: the ; was missing
Serial.begin(9600);
}
void loop() {
bool pressed = digitalRead(buttonPin) == LOW; // pressed reads LOW
if (pressed == true) { // = versus ==: = stores, == compares
digitalWrite(indicatorPin, HIGH);
} else { // braces: the } before else was missing
digitalWrite(indicatorPin, LOW);
}
}Practical Task A: Extractor Fan Speed Selector
One button, the 50 ms button code from the brief, and a speed variable that drives both LEDs. The same logic as Code Along 16.1-16.3. Open the brief.
What to check
- Serial shows Fan: OFF (0%) once at reset, then exactly one line per press.
- Ten quick presses from OFF end at LOW (10 steps: three full rounds plus one).
- D6 is a Timer0 pin, so the scope shows about 980 Hz (not 490 Hz). LOW reads 40 % duty.
- Named constants for the speeds are nice but not required - plain 0, 102 and 255 with comments are fine for a first task.
- A learner sketch may differ in names and layout. Mark the behaviour and the explanation, not a match with this code.
Reference sketch
// Task A: Extractor fan speed selector - MODEL ANSWER
// Each press of the button steps OFF -> LOW -> HIGH -> OFF.
const byte buttonPin = 2; // push button, D2 to GND
const byte runLed = 5; // running LED (330 ohm resistor)
const byte fanPin = 6; // fan LED on a PWM pin (330 ohm resistor)
const int SPEED_OFF = 0; // the three speeds
const int SPEED_LOW = 1;
const int SPEED_HIGH = 2;
int speed = SPEED_OFF; // the current speed
bool lastReading = HIGH; // HIGH = not pressed (from the brief)
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(runLed, OUTPUT);
pinMode(fanPin, OUTPUT);
Serial.begin(9600);
showSpeed(); // start at OFF and print it once
}
void loop() {
// Button code from the brief
bool reading = digitalRead(buttonPin);
if (reading != lastReading) { // the button has just changed
if (reading == LOW) { // just pressed: next speed
speed = speed + 1;
if (speed > SPEED_HIGH) {
speed = SPEED_OFF; // after HIGH comes OFF
}
showSpeed();
}
delay(50); // let the bounce die away
lastReading = reading;
}
}
// Make the LEDs match the speed and print ONE line.
// Called only when the speed changes, so Serial never floods.
void showSpeed() {
if (speed == SPEED_OFF) {
analogWrite(fanPin, 0); // 0 %
digitalWrite(runLed, LOW);
Serial.println("Fan: OFF (0%)");
} else if (speed == SPEED_LOW) {
analogWrite(fanPin, 102); // 40 % of 255
digitalWrite(runLed, HIGH);
Serial.println("Fan: LOW (40%)");
} else {
analogWrite(fanPin, 255); // 100 %
digitalWrite(runLed, HIGH);
Serial.println("Fan: HIGH (100%)");
}
}Practical Task B: Cabinet Over-Temperature Alarm
Uses the readCelsius() function given on the brief, two thresholds and a broken-sensor check. A delay(500) is allowed in this task. Open the brief.
What to check
- Unplugged thermistor: A0 is pulled to 5 V and reads 1023, so SENSOR FAULT shows and the alarm turns on.
- The 0 / 1023 check must come before readCelsius() - with 1023 the formula divides by zero.
- Between 28 °C and 30 °C the alarm keeps whatever it was doing - watch it stay on while the sensor cools.
- offsetC is where a learner puts a calibration correction from their table.
- Check the driver: D8 to a 1 kΩ base resistor, emitter to GND, buzzer and LED (with its resistor) between +5 V and the collector.
Reference sketch
// Task B: Cabinet over-temperature alarm - MODEL ANSWER
// Thermistor divider on A0: 10k from 5 V to A0, thermistor from A0 to GND.
// D8 -> 1k -> NPN base. The transistor switches the buzzer and red LED.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
const byte sensorPin = A0;
const byte alarmPin = 8; // to the transistor's 1k base resistor
const float alarmOnC = 30.0; // switch ON at or above this
const float alarmOffC = 28.0; // switch OFF at or below this
const float offsetC = 0.0; // calibration correction from your table
LiquidCrystal_I2C lcd(0x27, 16, 2); // try 0x3F if the screen stays blank
bool alarmOn = false; // remembers the alarm between passes
void setup() {
pinMode(alarmPin, OUTPUT);
lcd.init();
lcd.backlight();
Serial.begin(9600);
}
void loop() {
int raw = analogRead(sensorPin);
// Broken or unplugged sensor: alarm ON.
if (raw <= 0 || raw >= 1023) {
alarmOn = true;
digitalWrite(alarmPin, HIGH);
lcd.setCursor(0, 0);
lcd.print("Temp: --- ");
lcd.setCursor(0, 1);
lcd.print("SENSOR FAULT ");
Serial.println("SENSOR FAULT");
delay(500);
return; // skip the rest of loop() this time
}
float celsius = readCelsius(raw) + offsetC;
// Two thresholds. Between 28 and 30 nothing changes.
if (celsius >= alarmOnC) {
alarmOn = true;
}
if (celsius <= alarmOffC) {
alarmOn = false;
}
if (alarmOn) {
digitalWrite(alarmPin, HIGH);
} else {
digitalWrite(alarmPin, LOW);
}
// Row 1: temperature. Spaces at the end wipe old characters.
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(celsius, 1);
lcd.print(" C ");
// Row 2: the state.
lcd.setCursor(0, 1);
if (alarmOn) {
lcd.print("ALARM ");
} else {
lcd.print("NORMAL ");
}
Serial.println(celsius, 1);
delay(500); // two updates a second is plenty
}
// Given on the brief: turns the A0 reading into degrees C.
float readCelsius(int raw) {
float resistance = 10000.0 / (1023.0 / raw - 1.0); // thermistor in ohms
float invT = 1.0 / 298.15 + log(resistance / 10000.0) / 3950.0;
return 1.0 / invT - 273.15; // kelvin to C
}Practical Task C: Greenhouse Vent Controller
Built on the Capstone 1 sketch with the loop() layout from the brief: one millis() timer for the DHT11, the button checked on every pass. Open the brief.
What to check
- Unplugging the DHT11 shows ERR and keeps the last good values, so the vent does not move in AUTO.
- checkButton() is the Task A button code. Its 50 ms wait and the DHT11 read (about 25 ms every 2 s) are both short, so the button still answers straight away.
- LCD row 2 here reads the mode then the vent (AUTO vent OPEN). Any short wording is fine if both rows stay clean.
- Servo power: the servo's red wire goes to the separate 5 V supply, not the Uno, with the grounds joined.
Reference sketch
// Task C: Greenhouse vent controller - MODEL ANSWER
// Built on the Capstone 1 sketch: DHT11 on D2, I2C LCD on A4/A5.
// Added: servo vent on D9 (own 5 V supply, common GND), mode button D3 to GND.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <Servo.h>
const byte dhtPin = 2;
const byte buttonPin = 3;
const byte servoPin = 9;
const int VENT_CLOSED = 0; // servo angles
const int VENT_OPEN = 90;
const float openC = 28.0; // AUTO: open at or above this
const float closeC = 26.0; // AUTO: close at or below this
const int MODE_AUTO = 0; // the three modes
const int MODE_OPEN = 1;
const int MODE_CLOSED = 2;
LiquidCrystal_I2C lcd(0x27, 16, 2);
DHT dht(dhtPin, DHT11);
Servo vent;
int mode = MODE_AUTO;
bool ventOpen = false;
bool dhtOk = false; // was the last DHT reading good?
float tempC = 0; // last GOOD readings
float humidity = 0;
unsigned long lastRead = 0;
bool lastReading = HIGH; // button code from Task A
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
vent.attach(servoPin);
vent.write(VENT_CLOSED);
lcd.init();
lcd.backlight();
dht.begin();
Serial.begin(9600);
showLcd();
}
// The loop() layout given on the brief.
void loop() {
checkButton(); // every pass: the button always answers
if (millis() - lastRead >= 2000) { // every 2 s, no delay()
lastRead = millis();
readDHT();
updateVent();
showLcd();
}
}
void checkButton() { // the button code from Task A
bool reading = digitalRead(buttonPin);
if (reading != lastReading) { // the button has just changed
if (reading == LOW) { // just pressed: next mode
mode = mode + 1;
if (mode > MODE_CLOSED) {
mode = MODE_AUTO;
}
updateVent();
showLcd();
}
delay(50); // let the bounce die away
lastReading = reading;
}
}
void readDHT() {
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) {
dhtOk = false; // keep the last good values
Serial.println("DHT read failed");
} else {
dhtOk = true;
tempC = t;
humidity = h;
}
}
void updateVent() {
bool wantOpen = ventOpen; // default: leave it where it is
if (mode == MODE_OPEN) {
wantOpen = true;
} else if (mode == MODE_CLOSED) {
wantOpen = false;
} else if (dhtOk && tempC >= openC) { // AUTO and warm: open
wantOpen = true;
} else if (dhtOk && tempC <= closeC) { // AUTO and cool: closed
wantOpen = false;
} // failed read: no change
if (wantOpen != ventOpen) { // move only on a change
ventOpen = wantOpen;
if (ventOpen) {
vent.write(VENT_OPEN);
} else {
vent.write(VENT_CLOSED);
}
}
}
void showLcd() {
// Row 1: 27.0C 61% (ERR when the last read failed)
lcd.setCursor(0, 0);
lcd.print(tempC, 1);
lcd.print("C ");
lcd.print(humidity, 0);
lcd.print("% ");
if (dhtOk) {
lcd.print(" ");
} else {
lcd.print("ERR ");
}
// Row 2: AUTO OPEN
lcd.setCursor(0, 1);
if (mode == MODE_AUTO) {
lcd.print("AUTO ");
} else if (mode == MODE_OPEN) {
lcd.print("OPEN ");
} else {
lcd.print("CLOSED ");
}
if (ventOpen) {
lcd.print("vent OPEN");
} else {
lcd.print("vent SHUT");
}
}Final Project: Individual Microcontroller System
Not a model answer - every project is different. This is the desk-fan example from the brief, built to the minimum scope, to show the expected size and level. Open the brief.
What to check
- Minimum scope check: thermistor (analogue) and button (2 inputs), motor through a transistor (actuator), LCD (interface), AUTO/ON/OFF (states), millis() (timing).
- About 120 lines is a realistic size for a beginner final project. Bigger is not better.
- A broken sensor is treated as hot so the fan runs - a sensible fail-safe a learner could explain.
Reference sketch
// Final project EXAMPLE - one possible project, not the only answer.
// Desk fan: a thermistor sets the speed in AUTO; a button picks AUTO, ON or OFF.
// Thermistor divider on A0, button D2 to GND, I2C LCD on A4/A5.
// Motor on D5 through an NPN transistor (1k base resistor, flyback diode
// across the motor) and its own supply with a common GND.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <math.h>
const byte sensorPin = A0; // input 1: analogue
const byte buttonPin = 2; // input 2: mode button
const byte motorPin = 5; // actuator: PWM pin to the transistor
const int MODE_AUTO = 0;
const int MODE_ON = 1;
const int MODE_OFF = 2;
const float startC = 25.0; // AUTO: fan starts here...
const float fullC = 32.0; // ...and is at full speed here
const unsigned long updateMs = 500;
const unsigned long debounceMs = 30;
LiquidCrystal_I2C lcd(0x27, 16, 2);
int mode = MODE_AUTO;
float celsius = 0;
int duty = 0;
unsigned long lastUpdate = 0;
bool stableBtn = HIGH;
bool lastBtn = HIGH;
unsigned long changedBtn = 0;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(motorPin, OUTPUT);
lcd.init();
lcd.backlight();
}
void loop() {
checkButton();
if (millis() - lastUpdate >= updateMs) { // twice a second, no delay()
lastUpdate = millis();
celsius = readCelsius();
setFan();
showLcd();
}
}
void checkButton() {
bool reading = digitalRead(buttonPin);
if (reading != lastBtn) {
changedBtn = millis();
lastBtn = reading;
}
if (millis() - changedBtn >= debounceMs && reading != stableBtn) {
stableBtn = reading;
if (stableBtn == LOW) { // a new press: next mode
mode = mode + 1;
if (mode > MODE_OFF) {
mode = MODE_AUTO;
}
setFan();
showLcd();
}
}
}
float readCelsius() { // Beta equation from the thermistor lesson
int raw = analogRead(sensorPin);
if (raw <= 0 || raw >= 1023) {
return 99.0; // broken sensor: treat as hot, fan runs
}
float resistance = 10000.0 / (1023.0 / raw - 1.0);
float invT = 1.0 / 298.15 + log(resistance / 10000.0) / 3950.0;
return 1.0 / invT - 273.15;
}
void setFan() {
if (mode == MODE_ON) {
duty = 255;
} else if (mode == MODE_OFF) {
duty = 0;
} else { // AUTO: warmer = faster
duty = map(celsius * 10, startC * 10, fullC * 10, 0, 255);
duty = constrain(duty, 0, 255);
}
analogWrite(motorPin, duty);
}
void showLcd() {
lcd.setCursor(0, 0);
lcd.print("Temp ");
lcd.print(celsius, 1);
lcd.print(" C ");
lcd.setCursor(0, 1);
if (mode == MODE_AUTO) {
lcd.print("AUTO ");
} else if (mode == MODE_ON) {
lcd.print("ON ");
} else {
lcd.print("OFF ");
}
lcd.print("fan ");
lcd.print(duty * 100L / 255);
lcd.print("% ");
}