Foundations: the building blocks and the choices between them
Eight experiments, but only a handful of ideas underneath. Each idea is a choice you make when designing a device; this page is about what the options are and what each costs.
The execution model: a superloop
There is no operating system scheduling your program on the UNO. The Arduino core supplies main(), which calls init() (sets up timers, ADC), then your setup() once, then your loop() inside for (;;) forever. That shape is called a superloop: one thread, one infinite loop, all work done by polling inside it.
- State lives in global variables (they persist across
loop()calls) or in the hardware itself (an output pin keeps its level until written again). - Each pass of loop() is: read inputs, decide, write outputs, maybe communicate. Every sketch in your file has this shape; reading a sketch means identifying those four parts.
- On the ESP32, the same
setup()/loop()API runs inside a FreeRTOS task, and Wi-Fi and Bluetooth run in other tasks. You still write a superloop, but the radio stacks work in the background. - Consequence: anything that blocks inside
loop()stops everything else in your program. That is the main source of limits in your lab code (see Timing below).
A pin is an interface with rules
Think of a GPIO pin as a 1-bit port to the outside world with a direction. pinMode(pin, OUTPUT) makes the chip drive the wire; INPUT makes it only listen. The software side is trivial. The constraints are physical, and they are what you design around:
| Rule | What it means for design | Where it shows up |
|---|---|---|
| Logic level | UNO/MEGA pins are 5 V; ESP32 pins are 3.3 V and are not 5 V tolerant. A 5 V signal into an ESP32 needs a divider or level shifter. | Exp 3 to 8 on ESP32; HC-05 RX divider; SIM800L RX divider |
| Current budget | A pin can supply about 20 mA safely (ATmega). LEDs need a series resistor; motors, relays and coils need a driver. | every LED has 220 Ω; relay module has a transistor |
| Common ground | A voltage only means something relative to a reference. Two devices that exchange signals must share GND. | GSM on its own supply, servo on 5 V |
| Defined input level | An unconnected input floats and reads noise. Pull it to a known level with a resistor (external, or the chip’s internal pull-up). | Exp 1 button pull-down |
| Power budget | Some loads need far more current than USB or a regulator gives: servo stall, GSM transmit bursts (~2 A). | Exp 3, Exp 7 |
How information is encoded on a wire
Almost every sensor or actuator in this lab moves information in one of a few encodings. Recognising the encoding tells you which API you need.
| Encoding | Carries | Read or write with | In your lab |
|---|---|---|---|
| Level (digital) | 1 bit: HIGH or LOW | digitalRead() / digitalWrite() | LED, button, relay, LDR module DO, active buzzer |
| Voltage (analog) | a value on a continuous scale | analogRead() through the ADC | potentiometer wiper |
| Duty cycle (PWM) | a fraction 0 to 1, as on-time per period | analogWrite(), LEDC on ESP32 | LED brightness |
| Pulse width | a number carried in how long a pulse lasts | pulseIn(); libraries generate or decode it | HC-SR04 echo, servo command, DHT bits |
| Frequency | a value carried in how often the signal repeats | tone() | siren tones |
| Serial bytes | arbitrary data, one bit at a time at an agreed rate | Serial, SoftwareSerial, SerialBT | Serial Monitor, GSM AT commands, Bluetooth |
Analog in: quantisation
An ADC maps a voltage onto an integer. With n bits over a reference voltage Vref, the step size is Vref / 2ⁿ. The UNO/MEGA ADC is 10-bit over 5 V: 1024 codes, about 4.9 mV per step. The ESP32’s is 12-bit (0 to 4095) over roughly 3.3 V. More bits means finer steps, not better accuracy: noise and the reference still limit you.
Analog out: PWM
The UNO cannot output 2.5 V. It switches the pin fully on and off at a fixed frequency and varies the fraction of each period it is on. Anything slow compared with the switching (an eye looking at an LED, a motor’s inertia, an RC filter) sees the average: duty × 5 V. analogWrite(pin, v) takes v from 0 to 255 (8-bit duty). Most UNO/MEGA PWM pins run near 490 Hz.
Timing models: blocking, polling, interrupts
In a superloop the central design question is: what does the CPU do while it waits? Your lab code always picks the simplest answer, blocking. Knowing the alternatives is what lets you explain its limits.
| Model | How it works | Cost | In your lab |
|---|---|---|---|
| Blocking | call a function that waits: delay(1000), pulseIn(), while (!connected) | nothing else runs while waiting | everywhere: every sketch uses delay() |
| Polling with time checks | each loop pass compares millis() with a saved timestamp and acts when due | more code; loop must run often | not used; it is the usual fix |
| Interrupts | hardware calls a short function the instant an event happens (pin change, timer) | shared state needs care; handlers must be tiny | used inside libraries (SoftwareSerial, Servo timing), not in your code |
| Callbacks from a library | you register a function; the library calls it from its own processing | you must keep calling the library’s update | Exp 8: onLedPinChange() via ArduinoCloud.update() |
| RTOS tasks | independent threads scheduled by FreeRTOS | synchronisation, memory per task | ESP32 radio stacks run this way under your sketch |
Links between devices
Experiments 1, 5, 6, 7 and 8 move the same tiny payload, “LED on” or “LED off”, over five different links. Comparing them is the most useful design exercise in the lab.
| USB serial (Exp 1) | Bluetooth SPP (Exp 5) | Wi-Fi + HTTP (Exp 6) | GSM SMS/call (Exp 7) | MQTT via cloud (Exp 8) | |
|---|---|---|---|---|---|
| Topology | point to point, wired | point to point, paired | client to server on a LAN | device to phone via operator | device and app both clients of a broker |
| Addressing | the COM port | Bluetooth address, paired name | IP address and port 80 | phone number | device ID; topics/variables |
| Data unit | bytes | bytes | HTTP request/response text | SMS text up to 160 characters | MQTT messages |
| Reliability | reliable at short range | reliable link layer | TCP: reliable, ordered | store and forward by the network | TCP plus MQTT acknowledgements |
| Range | cable | about 10 m | one network | anywhere with 2G coverage | anywhere with internet |
| Security in your code | physical access | pairing only | none (plain HTTP) | SIM and phone number | TLS plus device credentials |
| Who starts the connection | PC | phone | browser connects in to the device | either side | device connects out to the broker |
Interaction patterns
Polling versus event-driven
- Why
- Polling (check every loop) is simple and deterministic, and is what Exp 1 to 7 do. Event-driven code reacts only when something changes, and is what Exp 8’s callback does.
- Alternative
- Interrupts for hardware events; callbacks for library events.
- Switch when
- Switch to events when inputs are rare and the CPU should sleep, or when you must not miss a short event.
Request-response versus publish-subscribe
- Why
- Exp 6 is request-response: the browser asks, the ESP32 answers. Exp 8 is publish-subscribe: changes are pushed through a broker to whoever subscribed.
- Alternative
- Request-response is simpler to reason about; pub/sub decouples senders from receivers and scales to many.
- Switch when
- Use pub/sub when many devices or many viewers share state, or when the device cannot accept inbound connections.
Device as server versus device as client
- Why
- In Exp 6 the ESP32 listens on port 80, so the controller must reach it. Behind a home router (NAT) nothing on the internet can, without port forwarding. In Exp 8 the ESP32 makes an outbound connection to the cloud, which NAT allows, and keeps it open.
- Alternative
- Port forwarding or a VPN to keep the server model.
- Switch when
- Any device that must be controlled from outside its network should be a client of a broker or cloud.
Decide at the edge versus in the cloud
- Why
- Exp 2 and 4 decide on the device (threshold in code): instant and works offline. Exp 8 lets a person decide in the cloud dashboard.
- Alternative
- Send raw readings up and decide in the cloud.
- Switch when
- Keep safety and real-time decisions at the edge; move decisions that need history, many devices, or a human to the cloud.
What the libraries hide
| Library | What you call | What it does for you |
|---|---|---|
| Arduino core | digitalWrite, analogRead, delay, Serial | register access, ADC sequencing, timers for millis/PWM, UART buffers and interrupts |
| DHT (Adafruit) | readHumidity() | the single-wire timing protocol, 40-bit decode, checksum, NaN on failure |
| ESP32Servo | attach(), write(angle) | configures an LEDC PWM channel at 50 Hz and converts angle to pulse width |
| BluetoothSerial | SerialBT.read() | the Bluetooth Classic stack, pairing, SPP/RFCOMM channel |
| WiFi | WiFi.begin(), WiFiServer | association, DHCP, the TCP/IP stack (lwIP), sockets |
| SoftwareSerial | sim.println() | a UART implemented in software on ordinary pins, timed by the CPU |
| ArduinoIoTCloud | ArduinoCloud.update() | TLS connection, MQTT, property sync, reconnects, callbacks |
In a viva, “what does this library do internally?” is answered from this table: name the protocol or hardware it manages and the problem that would be yours without it.
Toolchain and debugging
- Build: the IDE compiles C++ with the board’s toolchain (avr-gcc for UNO/MEGA, the Xtensa toolchain for ESP32) and links the core and libraries.
- Upload: a small bootloader on the chip receives the program over USB serial and writes it to flash. Wrong board or port means a failed upload; on some ESP32 boards you hold BOOT to enter download mode.
- Observe: the Serial Monitor is the only window into a running sketch. Every sketch in your file prints something at start; seeing it proves upload, reset and baud rate are right.
- Simulate: your circuit screenshots come from a simulator (the Wokwi interface). It is good for logic and wiring, but it does not model power limits, noise or a module’s real polarity. That is why some hardware photos differ from the simulation.
Viva questions
Why
Why do setup() and loop() exist instead of main()?
The core owns main(): it initialises the hardware, calls your setup() once, then calls loop() forever. This gives a fixed superloop structure, so a sketch only describes initialisation and the repeated body.
Why is the ESP32 used for experiments 3 to 8?
It has Wi-Fi and Bluetooth on chip, a faster dual-core CPU and more memory. The UNO would need extra modules plus 5 V to 3.3 V level shifting.
Why does every LED have a resistor but the relay has a module?
An LED only needs its current limited (a resistor). A relay coil needs more current than a pin can give and produces a voltage spike when switched off, so it needs a transistor and a flyback diode, which the module provides.
What if
What if two boards exchange serial data but do not share ground?
Each measures the signal against its own reference, so the levels are undefined: garbage data or no communication. Ground must be common.
What if you connect a 5 V sensor output straight to an ESP32 input?
You exceed the pin’s rating; it may work for a while or damage the pin. Divide it down or use a level shifter.
What if your loop() contains a 10-second delay?
Nothing else happens for 10 s: inputs are missed, serial input piles up in the buffer, and on Exp 8 the cloud connection is not serviced.
Design
How would you control a device in your home from a phone on mobile data?
Make the device a client of a cloud or MQTT broker (outbound connection, works behind NAT), as in Exp 8; not a server on the LAN as in Exp 6.
Polling or interrupts for a button?
Polling is fine if loop() runs often and nothing blocks. Use an interrupt if the loop has long blocking calls or the CPU sleeps, and debounce in either case.