Language & I/O reference

Theory Reference

World-class language, types, snprintf, I/O and circuit reference for this course. Read matching sections before practical lessons.

About this reference

Use this page as a lookup while you build - language, types, snprintf, I/O, timing and starter circuits. Practical lessons still own wiring and full sketches. When a lesson says Read this first, jump to the linked topic below.

note

How to use this reference

Topics here cover Arduino sketch language, data types, snprintf text formatting, core I/O functions, timing, Serial, and the starter circuit ideas used early in the course. Hardware beyond this scope (I2C, DHT, RTC, ultrasonic, IR, RFID and similar) is taught in the dedicated lessons for those modules - those lessons still link here for language tools such as snprintf.

Uno Rev3 and UNO R4 Minima board photos are official Arduino® images from store.arduino.cc (CC BY-SA). Arduino® is a trademark of Arduino S.r.l.

Board guide: Uno R3 and R4 Minima

Know the board under your hands: power pins, I/O, on-board LEDs, and what changes if you use an Uno R4 Minima. This course defaults to Uno R3.

table

Arduino Uno R3

Classic Uno Rev3 with ATmega328P, USB-B, and 5 V logic. Default board for this course. Product overview: Arduino store - Uno Rev3 (store.arduino.cc/products/arduino-uno-rev3).

Arduino Uno Rev3 board
Uno R3 - locate 5V, 3.3V, GND, VIN, AREF, L LED and TX/RX LEDs.Photo: Arduino® official product photo (Uno Rev3).Arduino® is a trademark of Arduino S.r.l.
ItemUno R3
MicrocontrollerATmega328P @ 16 MHz
Memory32 KB Flash, 2 KB SRAM, 1 KB EEPROM
Logic / USB5 V logic; USB-B programming
Digital I/OD0-D13 (PWM on ~ : 3, 5, 6, 9, 10, 11)
AnalogueA0-A5, 10-bit ADC (0-1023) by default
VIN / barrelAbout 7-12 V recommended
I/O current designAbout 20 mA per pin (40 mA absolute max)
Built-in LED (L)Digital pin 13 / LED_BUILTIN
TX / RX LEDsFlash on USB serial activity
table

Arduino Uno R4 Minima

Same Uno form factor and 5 V class, Renesas RA4M1 MCU, USB-C, more memory and extras (DAC, RTC, CAN, HID). Select “UNO R4 Minima” in the IDE. Product overview: Arduino store - UNO R4 Minima (store.arduino.cc/products/uno-r4-minima).

Arduino Uno R4 Minima board
Uno R4 Minima - USB-C and a faster MCU; still 5 V logic on the Uno header layout.Photo: Arduino® official product photo (UNO R4 Minima).Arduino® is a trademark of Arduino S.r.l.
ItemUno R4 Minima
MicrocontrollerRA4M1 (Arm Cortex-M4) @ 48 MHz
Memory256 KB Flash, 32 KB SRAM, 8 KB EEPROM
Logic / USB5 V logic; USB-C programming
Digital I/O14 pins; 6 PWM
Analogue6 inputs; higher resolution available (up to 14-bit)
VIN / barrelAbout 6-24 V
I/O current designAbout 8 mA per pin - use drivers for heavier loads
Built-in LED (L)Digital pin 13 / LED_BUILTIN
ExtrasDAC, RTC, CAN (external transceiver), HID, SWD
Watch out: Do not assume classic Uno “20 mA per pin” on R4 Minima. Check current before driving LEDs or loads directly from a pin.
note

Power pins: 5V, 3.3V, VIN, AREF, GND

Shared ideas on Uno R3 and R4 Minima power headers.

What each pin is for

5VRegulated 5 V

Powers the MCU and many 5 V modules when the board is supplied from USB or VIN/barrel. Do not casually feed an external supply into the 5V pin.

3V33.3 V rail

Limited current for 3.3 V modules (about 50 mA on classic R3). Never put 5 V into a 3.3 V-only device.

GNDCommon ground

Every external circuit must share GND with the Uno.

VINBoard input

Raw input path (barrel jack). R3: about 7-12 V recommended. R4 Minima: about 6-24 V. Not a sensor logic output.

AREFADC reference

Default analogue range is about 0-5 V. Leave open until a lesson uses analogReference().

IOREFI/O voltage sense

Tells shields the board’s I/O voltage (5 V here). Rarely used in beginner wiring.

Watch out: Never apply more than 5 V to a digital or analogue I/O pin on these Unos.
table

Uno R3 versus R4 Minima

Same shield layout family; different MCU, memory, USB and pin-current budget.

FeatureUno R3Uno R4 Minima
MCU / clockATmega328P / 16 MHzRA4M1 / 48 MHz
Flash / SRAM32 KB / 2 KB256 KB / 32 KB
USBUSB-BUSB-C
VIN typical7-12 V6-24 V
I/O current design~20 mA~8 mA
API sketchesCourse defaultOften OK; pick correct board; watch libraries

1. Sketch structure and syntax

Every Uno sketch needs a clear shape: declarations, one-time setup, then a repeating loop. Syntax rules keep the compiler happy; a shared style keeps sketches readable and consistent with Arduino examples.

concept

Program structure: setup() and loop()

void setup() {
  /* once */
}

void loop() {
  /* forever */
}

Global declarations (constants, variables, objects) usually sit above setup. setup() runs once after reset or power-up for configuration such as pinMode and Serial.begin. loop() then runs for the life of the program: read inputs, decide, write outputs. Both functions must exist even if one body is empty.

Three stacked blocks: globals, setup once, loop repeating
Globals, setup once, loop forever - the sketch backbone.

Reset to repeating loop

After reset or power-up, setup runs once, then loop repeats until power is removed.

Flow from reset through setup then repeating loop
Reset -> setup once -> loop forever.
concept

Functions

returnType name(parameters) {
  // statements
}

A function packages work behind a clear name. Parts: return type, name, parameter list, body. void means no return value - the function only performs an action. Parameters are inputs. Keep functions short so each one is easy to test and reuse.

Return type, name, parameters and body for a void function
Return type, name, parameters, body. Call the name whenever you need the task.
note

Code conventions overview

A shared style keeps sketches organised, easy to mark, and closer to official Arduino examples and libraries. This course covers naming, braces, spacing and pin constants. Follow them unless a brief says otherwise - markers and teammates should recognise the shape of your code.

TopicCourse rule
NamescamelCase values; clear pin names; consistent const style
Braces{ on the same line; indent 2 spaces
PinsNamed constants - no magic numbers scattered in loop
CommentsIntent, units, safety
table

Naming conventions

Use clear, descriptive names-not single letters or vague labels. Match the style to the kind of name.

Prefer clear camelCase names versus vague short names
Clear names reduce bugs. Avoid single-letter names for important values.
ElementStyleGood examplesAvoid
VariablescamelCasesensorValue, ambientTemp, buttonStatex, n, val, data, temp, a
Pin / constantsSNAKE_CASELED_PIN, BUTTON_PIN, MAX_SPEEDp, pin, led, 13 (magic number)
FunctionscamelCasereadSensor(), calculateAverage(), updateDisplay()doStuff(), fn(), process()
Custom types / classesPascalCaseMotorController, ButtonStatething, myclass, data
Tip: Names are case-sensitive. ledPin and LEDPIN are different identifiers.

Good names versus weak names

A good name tells the next reader (or marker) what the value means without hunting through the sketch.

AvoidPreferWhy
int x = analogRead(A0);int sensorValue = analogRead(A0);Says what is stored, not just a letter
int temp = 22;float ambientTemp = 22.0;temp could mean temporary or temperature
const int pin = 2;const int BUTTON_PIN = 2;Names the role; UPPERCASE marks a fixed pin
digitalWrite(13, HIGH);digitalWrite(LED_PIN, HIGH);No magic number; one change updates every use
void doIt() { … }void updateAlarm() { … }Function names should say the action
bool flag;bool alarmActive;flag says nothing about the meaning
Tip: If you must use a short loop index, i or j is fine inside a small for loop. Everywhere else, prefer a name that reads like English.
syntax

Curly braces and indentation

void loop() {
  if (sensorValue > 500) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
}

Braces mark the start and end of a block: function bodies, if branches, loop bodies. Every opening brace needs a matching close.

Course brace style

2 spacesIndent size

Indent each nested block by 2 spaces (Arduino IDE default). Do not mix tabs and spaces in one file.

{Opening brace

Place { on the same line as the if, else, for, while, switch, or function declaration.

}Closing brace

Place } on its own line, lined up with the statement that opened the block.

Tip: Indentation helps humans; the compiler only cares that braces match.
syntax

Spacing guidelines

// Good
int total = count + 5;
if (value == HIGH) {
  digitalWrite(LED_PIN, HIGH);
}
analogWrite(LED_PIN, 128);

// Avoid
int total=count+5;
if(value==HIGH){
  digitalWrite(LED_PIN,HIGH);
}
analogWrite(LED_PIN,128);

Spacing stops code looking clumped and makes operators and conditions easier to scan.

Where to put spaces

= + ==Binary operators

One space before and after operators such as =, +, -, ==, >, && and ||.

if (Control keywords

Space after if, while, for and switch-but not between a function name and its (.

,Commas

Space after commas in argument lists: analogWrite(LED_PIN, 128);

syntax

Hardware pins and constants

// Good
const int BUTTON_PIN = 2;
const int RELAY_PIN = 7;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RELAY_PIN, OUTPUT);
}

// Avoid
void setup() {
  pinMode(2, INPUT_PULLUP);
  pinMode(7, OUTPUT);
}

Define pin numbers once at the top of the sketch with const int (preferred) or #define. Never scatter raw “magic numbers” through pinMode, digitalWrite or analogRead calls-changing a wire then means hunting every occurrence.

Tip: Name the role, not only the number: BUTTON_PIN is clearer than PIN2.
syntax

Semicolon

statement;

Most statements end with a semicolon. Missing semicolons are a common compile error and the compiler may report the problem on the next line. Do not put a semicolon immediately after if (...), for (...), or while (...) unless you intentionally want an empty statement.

Watch out: if (pressed); { ... } is a classic bug - the semicolon ends the if, so the braces always run.
syntax

Comments

// line comment
/* multi-line
   block comment */

Comments are ignored by the compiler. Use // for a single-line comment. Use /* ... */ for a multi-line block comment. Write comments that explain intent, units, pin roles and safety assumptions - not only what the next line obviously does.

Syntax checklist cards for case, semicolons, braces and comments
Case, semicolons, braces, comments - fix the first compiler error, then Verify again.

Comment habits

//Line comment

Rest of the line is ignored.

/* */Block comment

Can span several lines.

whyExplain why

Units, pin roles, safety - not 'increment i'.

2. Variables, types and arrays

Choose a type that fits the range you need, name values clearly, keep scope narrow, and know which snprintf format code matches each type.

concept

Variables

A variable names stored data the program can read and change. Prefer clear camelCase names such as sensorValue. Use const for pin numbers and other values that must not change after they are set. See Naming conventions for the full style table.

Side by side const pin versus changing flashCount variable
const for fixed pins and limits. Variables for values that update while running.
syntax

Variable declaration

int sensorValue = 0;

Declare before use: type, name, and optional initial value. Declaration allocates storage and tells the compiler how to interpret the bits. Initialise values you depend on - uninitialised locals can hold garbage.

PartMeaning
TypeHow the bits are interpreted (byte, int, ...)
NameHow you refer to the value in code
Initial valueStarting contents (recommended)
concept

Variable scope

Scope is where a name is visible. Globals above setup are visible to many functions. Locals inside a function or block exist only there. Prefer the narrowest useful scope so lifetimes stay clear and names do not collide. A local bool ledOn = false; inside loop is recreated every pass - bad if you expected it to remember a toggle.

Global region containing setup and loop local boxes
Globals are shared. Locals live only inside their braces.
type

Type: byte

byte pin = 9;  // 0 … 255

8-bit unsigned integer (0 to 255). Useful for pin numbers and small counters. Cannot store fractions. In snprintf, byte promotes and is usually printed with %d.

Tip: Pin numbers fit in byte. Prefer const byte ledPin = 9; then print with %d in snprintf.
type

Type: int

int x;  // about -32768 … 32767 on Uno

On the classic Uno (ATmega328P), int is 16-bit signed (about -32768 to 32767). Everyday integer type for many sketches and analogRead results. Watch for overflow. Format with %d or %i; use %02d when you need zero-padding.

type

Type: long / unsigned long

long x;
unsigned long t = millis();  // print with %lu

Wider integers (32-bit on the Uno). Use long or unsigned long when values may exceed int range. millis() returns unsigned long - store timestamps in unsigned long and format with %lu (signed long uses %ld).

type

Type: float

float v = reading * 5.0 / 1023.0;

Approximate decimal numbers for voltages and averages. Integer division discards the remainder unless at least one operand is floating-point (for example total / 4.0). Serial.println(float) works; inside snprintf on AVR Uno prefer dtostrf then %s (see Floats with dtostrf).

type

Type: bool

bool ledOn = false;
bool pressed = digitalRead(pin) == LOW;

Stores true or false. Ideal for flags, LED state, and named conditions. In Serial or snprintf you can print it with %d (shows 0 or 1), or print a label yourself.

Tip: Name booleans as questions or states: aPressed, alarmActive - not flag or temp.
type

Type: char

char command = Serial.read();
char label = 'A';

Holds one character (one byte). Character literals use single quotes: '1', 'A', '\n'. A sequence of characters in a char array forms a C string when it ends with a null terminator. Format with %c for one character, or %s for a whole C string.

table

Uno type summary

Match the range you need, then match the snprintf / print approach. Classic Uno (ATmega328P) sizes are shown.

Cards for byte int long unsigned long float bool and char with matching format hints
Choose a type for the range, then the matching format code when you compose text.
TypeTypical range (Uno)Course usesnprintf
byte0 to 255Pin numbers, small indexes%d (promotes)
intAbout -32768 to 32767Everyday integers, ADC raw%d or %i
unsigned int0 to 65535Non-negative counts%u
long32-bit signedLarge signed totals%ld
unsigned long0 to about 4.29e9millis(), flash counts%lu
floatApproximate decimalsVolts, averages, Celsiusdtostrf then %s
booltrue / falseFlags, pressed state%d (0/1) or labels
charOne characterSerial commands%c
char[]C string + nullLCD / Serial lines%s
Watch out: Passing a float with %d, or an unsigned long with %d, is undefined behaviour - you may see garbage. Match the specifier to the type.
concept

Arrays

int leds[] = {2, 3, 4, 5};
// leds[0] == 2

An array holds several values of one type under one name, accessed by index. The first index is 0. For four items, valid indices are 0-3. Never read or write past the last valid index - that is undefined behaviour and looks like random bugs.

Four cells leds 0 to 3 holding pin numbers 2, 3, 4 and 5
Four items means indices 0-3. leds[4] does not exist.

Bounds trap

The classic off-by-one error is i <= ledCount instead of i < ledCount.

Safe i less than ledCount versus buggy i less than or equal
i < ledCount stops at the last valid index.
Tip: const byte ledCount = sizeof(leds) / sizeof(leds[0]); keeps the length matched to the array.

3. Strings, buffers and snprintf

Compose fixed-width text for Serial, LCD and logs. Learn char buffers, snprintf format codes matched to data types, zero-padding, and the AVR float workaround with dtostrf.

concept

C strings and char buffers

char line[20];  // up to 19 chars + '\0'

A C string is a char array that ends with a null terminator (byte value 0, written '\0'). Serial.print(line) and lcd.print(line) stop at that terminator.

Declare a buffer large enough for the longest text you will write, including '\0'. Example: HH:MM:SS needs 8 characters plus '\0', so char line[9]; is the minimum - course sketches often use a little spare room (16 or 24) for labels.

Char cells holding 09:05 then a null terminator with unused cells after
Text lives in the array; '\0' marks the end. Leave space for it.

Buffer habits

sizeofPass the size

Always pass sizeof(line) to snprintf so the library knows the limit.

StringArduino String

String objects are convenient but fragment scarce SRAM on Uno. Prefer char buffers for fixed LCD/Serial lines in this course.

ClearOptional start

You do not usually need to clear the buffer first; snprintf overwrites from the start and writes a new '\0'.

Tip: If text looks truncated, the buffer is too small or you passed the wrong size. Count characters + 1 for '\0'.
api

snprintf and format specs

char line[24];
snprintf(line, sizeof(line), "%02d:%02d:%02d", hour, minute, second);
Serial.println(line);

snprintf writes formatted text into a char buffer. Unlike sprintf, it takes the buffer size and will not write past the end (it still needs a correct size argument from you).

Arguments after the format string fill each % placeholder in order. The format code must match the type you pass.

Four parts of snprintf: buffer, size, format string, and matching values
Buffer, size, format, values. Keep the % codes and arguments in lockstep.
SpecifierPass this typeExample result
%d or %iint (byte promotes)42
%uunsigned int400
%ldlong-100000
%luunsigned longmillis value
%02dint, width 2, zero-pad09 from 9
%04dint, width 4, zero-pad2026
%ccharA
%sconst char* / char[]OK
%%(none)literal %
%ffloat (often unavailable on AVR)Prefer dtostrf

Match % codes to datatypes

This is the rule students must memorise: every % code consumes one argument of the matching type. Mismatch is undefined behaviour - Serial may show nonsense without a compile error.

Grid of format specs %d %lu %02d %s %ld %u %c %% and AVR float warning
Pick the code from the type you already chose for the variable.
You storedFormat withAvoid
int hour%d or %02d%lu, %f
unsigned long t = millis()%lu%d (truncates / wrong)
long distance%ld%d on large values
char cmd%c%s
char msg[] = "OK"%s%c
float voltsdtostrf + %s%d, bare %f on Uno
bool ok%d (0/1) or if/else labels%s with bool
Watch out: Compilers may not catch format/type mismatches in snprintf. Wrong pairs are a silent runtime bug.

Width and zero-padding

%02d means: print an integer in at least 2 columns, pad with zeros on the left. That keeps clock and date columns stable on a 16x2 LCD when hours go from 9 to 10.

%04d is the usual year width. A plain %d is fine when width does not matter.

Unpadded 9:5:3 versus zero-padded 09:05:03
Padding stops the display from jumping as digits change.
Tip: Course RTC pattern: snprintf(line, sizeof(line), "%02d/%02d/%04d %02d:%02d:%02d", ...).

Return value and safety

snprintf returns the number of characters that would have been written (excluding '\0'), or a negative value on encoding error. If the return value is greater than or equal to the buffer size, the output was truncated.

Always pass sizeof(buffer) for array buffers. Never pass a guessed number that is larger than the array. Prefer snprintf to sprintf so a size mistake is less likely to smash neighbouring memory.

Watch out: sprintf has no size limit. One oversized line can corrupt variables and crash the board. Use snprintf.
api

Floats with dtostrf (AVR Uno)

char num[8];
char line[20];
float tempC = 23.5;
dtostrf(tempC, 0, 1, num);   // "23.5"
snprintf(line, sizeof(line), "T=%s C", num);
Serial.println(line);

On the classic Uno (AVR), printf-family float support (%f) is often disabled to save flash. Serial.print(23.5) still works for a lone float, but composing a full line with snprintf needs another path.

dtostrf(value, width, decimals, buffer) writes the decimal text into a char buffer. Then insert it with %s. width 0 means no forced character count; decimals is digits after the point.

Flow from float through dtostrf into a num buffer then snprintf with percent s
Float -> dtostrf -> char[] -> snprintf with %s.

dtostrf arguments

valueThe float

The number to convert.

widthMinimum width

Use 0 unless you want space/zero padding in the numeric field itself.

decimalsFraction digits

1 gives tenths (23.5); 2 gives hundredths (23.50).

bufferDestination

Must be large enough for sign, digits, point, decimals and '\0'.

Tip: For a quick debug of one float, Serial.println(tempC); is fine. Use dtostrf when the float must sit inside a fixed LCD or log line.
syntax

Worked patterns used in this course

// Clock / RTC line
snprintf(line, sizeof(line), "%02d:%02d:%02d", h, m, s);

// Date and time stamp
snprintf(stamp, sizeof(stamp), "%04d-%02d-%02d %02d:%02d:%02d",
         year, month, day, h, m, s);

// Labelled integer
snprintf(line, sizeof(line), "raw=%d", raw);

// millis uptime
snprintf(line, sizeof(line), "t=%lu ms", millis());

Copy these shapes into LCD, Serial and SD log sketches. Change only the buffer size, labels and variable names.

NeedPattern
Stable clock%02d:%02d:%02d
Calendar date%02d/%02d/%04d or %04d-%02d-%02d
ADC rawraw=%d
millist=%lu ms
Float in a linedtostrf then T=%s C
Status word%s with a const char* label
Tip: After snprintf, print once: Serial.println(line); or lcd.print(line); - do not rebuild the string with many separate print calls if you need fixed columns.

4. Operators and control flow

Calculate, compare and choose which statements run - the same toolkit as Lesson 05. One wrong operator (= vs ==) is a classic bug. Use the figures and truth table here as a desk reference while you build.

syntax

Arithmetic operators

a + b    // add
a - b    // subtract
a * b    // multiply
a / b    // divide
a % b    // remainder (modulo)

x = x + 1;
x += 1;   // same idea, shorter
i++;      // add 1 to i
i--;      // subtract 1 from i

Use + - * / and % to calculate with numbers in variables or literals. % (modulo) is the remainder after division - handy for wrapping counters and simple blink patterns such as millis() / 250 % 2.

Integer trap: when both operands are integers, / discards the fraction. 5 / 2 is 2. Write 5.0 / 2 (or cast) when you need 2.5.

Cards for plus minus multiply divide and modulo with integer division trap
Five arithmetic operators. Integer division drops the remainder unless you use a float.

Basic operators

+Addition

Adds two numbers. Example: 3 + 2 = 5.

-Subtraction

Subtracts the right number from the left. Example: 10 - 4 = 6.

*Multiplication

Multiplies two numbers. Example: 3 * 4 = 12.

/Division

Divides the left number by the right. With whole numbers (int), the fraction is dropped: 7 / 2 = 3, not 3.5. Use a decimal such as 7.0 / 2 if you need 3.5.

%Modulo (remainder)

Answers: after dividing, what is left over? Example: 10 % 3 = 1, because 3 fits into 10 three times (9) with 1 left. Odd/even checks: 7 % 2 = 1 (odd), 8 % 2 = 0 (even). Also useful for wrapping counters and alternating patterns.

Compound and shortcut operators

+= -= *= /=Compound assignment

Update a variable using its current value. x += 1 means take x, add 1, and store the result back in x (same as x = x + 1). Likewise x -= 2, x *= 2, or x /= 2.

++ --Increment / decrement

Shortcuts to add or subtract 1. i++ adds 1 to i; i-- subtracts 1. Common in for loops when counting one step at a time.

OperatorMeaningExample
+Add3 + 2 -> 5
-Subtract3 - 2 -> 1
*Multiply3 * 2 -> 6
/Divide5 / 2 -> 2 (ints)
%Remainder5 % 2 -> 1
Tip: If you need a decimal result from division, make at least one value floating-point (for example total / 4.0), otherwise int maths drops the fraction.
syntax

Comparison operators

a == b   // equal to
a != b   // not equal to
a <  b   // less than
a <= b   // less than or equal
a >  b   // greater than
a >= b   // greater than or equal

Comparison operators ask a yes/no question about two values. The answer is true or false, and is usually used inside if or a loop condition.

= stores a value. == asks whether two values are equal. Writing if (x = 1) by mistake assigns 1 to x and often looks always true - a classic bug.

One equals for assignment versus two equals for comparison
= stores. == compares. Memorise this before hunting wiring faults.

Comparisons

==Equal to

True only when both sides are the same. Example: sensorValue == 0 is true only when sensorValue is exactly 0.

!=Not equal to

True when the sides differ. Example: buttonState != HIGH is true when the button is not HIGH.

<Less than

True when the left value is smaller than the right.

>Greater than

True when the left value is larger than the right.

<=Less than or equal

True when the left value is smaller than or the same as the right.

>=Greater than or equal

True when the left value is larger than or the same as the right. Example: reading >= 512 is true in the upper half of the 0-1023 analogue range.

Assignment vs comparison

=Assign

Stores a value into a variable. state = HIGH puts HIGH into state.

==Compare

Checks whether two values are already equal, without changing them. state == HIGH asks: is state already HIGH?

Watch out: if (state = HIGH) assigns HIGH and is almost always a mistake. The condition becomes true because the assigned value is HIGH. Use == for comparisons.
syntax

Logical operators

A && B   // AND - both must be true
A || B   // OR  - either may be true
!A       // NOT - reverse true/false

&& (AND) is true only when both sides are true. || (OR) is true when either side is true (or both). ! (NOT) flips true to false and false to true.

Parentheses keep complex tests readable: if ((a && b) || alarm). Prefer && and || over bitwise & and | in this course.

Three cards for AND OR and NOT with course examples
AND both, OR either, NOT reverse.

Combining conditions

&&AND

True only when both sides are true. Example: (buttonA == LOW) && (buttonB == LOW) is true only when both buttons are pressed (active-low wiring).

||OR

True when either side is true (or both). Example: (alarm) || (override) is true if the alarm is on, or override is on, or both.

!NOT

Flips true to false and false to true. Example: if (!ready) means "if ready is false". Also useful as ledOn = !ledOn; to toggle a Boolean each time.

Reading complex tests

( )Parentheses

Group parts of a condition so the meaning is obvious. Prefer if ((a == LOW) && (b == HIGH)) over a long chain without brackets.

Tip: Evaluate one condition at a time on paper if a compound if feels confusing: write true/false under each part, then apply && or ||.

Truth table for two conditions

Name pressed flags clearly (aPressed, bPressed). Then both / either / neither match everyday English - as in Lesson 05.

Truth table of A and B for AND and OR results
Both true only for AND. OR is true when at least one side is true.
ABA && BA || B
falsefalsefalsefalse
falsetruefalsetrue
truefalsefalsetrue
truetruetruetrue
api

Constants: true, false, HIGH, LOW

true / false
HIGH / LOW

true and false are Boolean values for flags and conditions. HIGH and LOW are digital logic levels used with digitalWrite and returned by digitalRead.

On a 5 V Uno, HIGH is about 5 V and LOW is about 0 V. A bool can be written with digitalWrite (true acts like HIGH).

NameMeaningTypical use
true / falseBoolean yes/noFlags, if conditions
HIGHLogic 1 (~5 V on Uno)digitalWrite on; digitalRead open pull-up
LOWLogic 0 (~0 V)digitalWrite off; pressed INPUT_PULLUP button
Tip: With INPUT_PULLUP buttons to GND, pressed is LOW - compare with == LOW, not == HIGH.
api

Constants: INPUT, OUTPUT, INPUT_PULLUP

pinMode(pin, INPUT);
pinMode(pin, OUTPUT);
pinMode(pin, INPUT_PULLUP);

pinMode uses these modes. INPUT is high-impedance (can float if nothing drives the pin). OUTPUT drives the pin as a low-impedance output. INPUT_PULLUP enables the chip's internal pull-up - the usual course pattern for a button to GND.

Open button reads HIGH, pressed button reads LOW with INPUT_PULLUP
Course buttons: pinMode INPUT_PULLUP, switch to GND, pressed == LOW.
ModePin behaviourCourse habit
INPUTHigh-Z inputNeeds external pull-up or pull-down
OUTPUTDriven outputLEDs, logic lines (within current limits)
INPUT_PULLUPInput + internal pull-upButtons / switches to GND
syntax

if / else if / else

if (aPressed && bPressed) {
  // both - steady
} else if (aPressed || bPressed) {
  // either - flash
} else {
  // neither - off
}

An if chain tests conditions in order. Only the first true branch runs; the rest are skipped. else covers whatever is left when no earlier test matched.

Separate if statements are different: each is tested on its own, so more than one body can run. Use a chain when outcomes must be mutually exclusive (steady vs flash vs off).

Stacked if, else if and else branches for two-button LED behaviour
One chain, one winner. Order matters: test both before either.

Chain vs separate ifs

chainif / else if / else

One winner. Use when outcomes must not overlap.

separateif; if; if;

Each test runs. Several bodies can execute.

Order tip (Lesson 05)

both firstSpecific before general

Test aPressed && bPressed before aPressed || bPressed, or both-pressed will flash instead of staying steady.

Watch out: if (state = HIGH) assigns HIGH and is almost always a mistake. Use == for comparisons.

Decision flow pattern

Read inputs into clear bool names, then choose one output action. This is the backbone of Lesson 05 and most later control sketches.

Flow from reading buttons A and B through both and either checks to selecting the output
Read -> test both -> test either -> select steady / flash / off.
Tip: millis() / 250 % 2 flashes without delay() so loop stays responsive while a button is held.
syntax

Worked pattern: two buttons, one LED

bool aPressed = digitalRead(buttonA) == LOW;
bool bPressed = digitalRead(buttonB) == LOW;

if (aPressed && bPressed) {
  digitalWrite(ledPin, HIGH);
} else if (aPressed || bPressed) {
  digitalWrite(ledPin, millis() / 250 % 2);
} else {
  digitalWrite(ledPin, LOW);
}

This is the Lesson 05 control pattern in reference form. INPUT_PULLUP buttons to GND make pressed == LOW. Both pressed: steady on. Exactly one: flash with millis modulo. Neither: off.

Copy the shape - clear bool names, specific test first, one output choice - into later projects.

Read A and B, test both, test either, select output
Same decision flow as the Lesson 05 worked sketch.
InputsLED behaviour
Neither pressedOff
Exactly one pressedFlashing
Both pressedSteady on
Tip: Build and mark this in Lesson 05. Return here when a later if-chain feels messy.
syntax

for loops

for (int i = 0; i < 10; i++) {
  // body
}

A for header has three parts separated by semicolons: initialisation, condition and update.

Initialisation runs once. The condition is tested before each body pass - if false, the loop ends. After the body, the update runs (often i++), then the condition is tested again. Use for when the count is known: walk every LED, repeat N times, scan an array.

for header broken into init, condition and update cards
Init once, test the condition, run the body, update, repeat until the test fails.

Parts of for (...)

initInitialisation

Runs once - usually byte i = 0.

conditionTest

Must eventually become false - usually i < count.

updateChange

After each body - usually i++.

Watch out: Use i < ledCount, not i <= ledCount, or you walk one past the last array index.
syntax

while and do/while

while (condition) { /* … */ }

do {
  /* … */
} while (condition);

while (condition) repeats as long as the condition stays true. It suits waiting until a button releases, draining Serial bytes, or looping until a flag clears.

Danger: if nothing inside the body can make the condition false, the rest of loop() never runs. do/while runs the body at least once, then tests at the end.

Side by side cards comparing for for known counts and while for waiting until something changes
for = known count. while = until something changes.
Watch out: A while that never updates its condition blocks buttons, Serial and every other task.

5. Digital I/O

Configure pins, read switches, write logic levels. Respect current limits on every OUTPUT.

note

Power and safety rules

Keep these rules in mind whenever you wire outputs or share supplies with motors and modules.

Lab rules

GNDCommon ground

Every circuit that talks to the Uno must share GND with the Uno. Different grounds mean broken signals and odd faults.

20 mAPin current design limit

Design for about 20 mA or less per I/O pin. 40 mA is an absolute maximum, not a normal target.

LoadDrivers for heavy loads

Never power a motor, relay coil, or solenoid from an I/O pin. Use a transistor/MOSFET/driver and a suitable external supply.

DiodeFlyback protection

Inductive loads (relays, motors, solenoids) need a flyback diode across the coil so switching spikes do not damage the switch or the Uno.

ELVExtra-low voltage only

This course uses safe DC levels only. Never connect mains electricity to Arduino projects.

Watch out: Switch power off before moving wires. Confirm supply voltage and common GND before the first power-up.
api

pinMode()

pinMode(pin, mode);

Configure a pin as INPUT, OUTPUT or INPUT_PULLUP in setup. Set modes explicitly even though pins often default toward inputs. Design for about 20 mA or less per I/O on the Uno; 40 mA is an absolute maximum, not a normal target.

Watch out: Short circuits or overloads on OUTPUT pins can damage the microcontroller.

Active-low buttons and pull-ups

Most course button examples use INPUT_PULLUP and a switch to GND. That is active-low: pressed connects the pin to 0 V. Always write if (digitalRead(pin) == LOW) when you mean “pressed” in that wiring style.

Button with INPUT_PULLUP: open reads HIGH; pressed connects the input to GND
Open reads HIGH; pressed connects the input to GND.
SwitchPin readsMeaning (INPUT_PULLUP to GND)
Open (not pressed)HIGH (~5 V)Released - pull-up holds the pin high
Closed (pressed)LOW (~0 V)Pressed - pin shorted to GND
Tip: Active-high is the opposite idea: a button that pulls the pin up to 5 V when pressed, usually with an external pull-down. Know which style you built before you write the if test.
api

digitalRead()

int state = digitalRead(pin);  // HIGH or LOW

Returns HIGH or LOW from a digital pin. With INPUT_PULLUP and a button to GND: open reads HIGH, pressed reads LOW (active-low). Always document which polarity your wiring uses before writing the if test.

Open button reads HIGH, pressed button reads LOW with INPUT_PULLUP
Active-low press: compare to LOW when using INPUT_PULLUP.
SwitchdigitalReadMeaning (INPUT_PULLUP to GND)
OpenHIGHNot pressed
ClosedLOWPressed
api

digitalWrite()

digitalWrite(pin, HIGH);
digitalWrite(pin, LOW);

Drive an OUTPUT pin high or low after pinMode(pin, OUTPUT). On a 5 V Uno, HIGH is about 5 V and LOW about 0 V. Use for LEDs (with series resistor), logic inputs, and similar loads - not motors or relays without a driver.

Two cards showing LOW near 0 V and HIGH near 5 V
OUTPUT pins switch between roughly 0 V and 5 V on a Uno.
Watch out: Always set pinMode OUTPUT before digitalWrite. Stay within about 20 mA per pin.

6. Analogue I/O

Read voltages with the ADC; fake analogue drive with pulse-width modulation on ~ pins.

api

analogRead()

int reading = analogRead(A0);  // 0 … 1023

10-bit ADC on A0-A5. With the default 5 V reference, 0 is about 0 V and 1023 is about 5 V (about 4.9 mV per count). Convert with V = reading * 5.0 / 1023.0 using a float operand. Pins used only as ADC inputs do not need pinMode(OUTPUT).

Flow from analogue voltage through the ADC to a digital code 0-1023
analogRead returns a code, not volts. Convert when you need voltage.
ItemTypical Uno R3 default
ReferenceAbout 0-5 V
Resolution10-bit (0-1023)
Step sizeAbout 4.9 mV per count
FunctionanalogRead(pin)
api

analogWrite() and PWM

analogWrite(pin, value);  // value 0 … 255

Hardware PWM on pins marked ~ (Uno: 3, 5, 6, 9, 10, 11). value 0 is fully off, 255 fully on; values between set duty cycle. PWM is a fast digital pulse train - not a true DC analogue voltage - but many loads respond to average power.

PWM waveforms with different duty cycles
Duty cycle changes average power. Use a ~ pin with analogWrite.

7. Timing, helpers and Serial

Schedule work without freezing the sketch, print and receive over USB Serial, and compose fixed lines with snprintf when labels and padding matter.

api

delay()

delay(ms);

Pauses the sketch for ms milliseconds. Simple for first demos, but nothing else runs during delay - buttons, Serial and other LEDs wait. Prefer millis-based timing when several tasks must stay responsive.

delay blocking the whole loop versus millis scheduling that leaves loop free
delay freezes everything. millis lets other work continue between events.
Tip: If a button feels dead while an LED blinks with delay(), switch that blink to millis().
api

millis()

unsigned long now = millis();
if (now - last >= interval) {
  last = now;
  // do work
}

Milliseconds since start, as unsigned long. Save a start time; when millis() - start >= interval, run the action and update start. Overflow after many days is fine when both values are unsigned long. Give each independent task its own timestamp.

Several tasks scheduled with separate millis timestamps
Each task keeps its own last-run time. loop stays free between events.
Tip: Store timestamps in unsigned long. Format with snprintf using %lu when you print them.

Interval pattern

Copy this shape for every independent timed task.

millis interval pattern with last timestamp and interval check
if (millis() - last >= interval) { last = millis(); /* work */ }
api

min(), max(), random()

min(a, b);
max(a, b);
randomSeed(seed);
random(min, max);

min(a, b) and max(a, b) return the smaller or larger value - useful for clamping. randomSeed sets the pseudo-random sequence (often millis() or analogRead on a floating pin). random(min, max) returns a value in the requested range for variation in demos.

CallRole
min(a, b)Smaller of two values
max(a, b)Larger of two values
randomSeed(seed)Start the sequence
random(min, max)Value in range
api

Serial.begin() and Serial.println()

Serial.begin(9600);
Serial.println(value);

Opens USB serial (9600 is common for learning) and prints a line to the Serial Monitor. On the Uno, D0 (RX) and D1 (TX) are reserved for that link. Match Monitor baud to begin; use a USB data cable.

println adds a line ending; print does not. For multi-field padded lines, build with snprintf then println the buffer.

Path from sketch Serial.print through USB to Serial Monitor
Sketch <-> USB serial <-> Serial Monitor. Match baud on both ends.

Baud rate must match

Serial.begin(9600) and the Monitor dropdown must show the same number or you get garbage text.

Sketch Serial.begin 9600, USB link, Monitor also 9600
Mismatch looks like nonsense characters.
api

Serial.print, print labels, write

Serial.print("raw=");
Serial.println(raw);
Serial.write(byteValue);  // one raw byte

print continues on the same line; println adds a line ending. Label values so the Monitor is readable. write sends a raw byte - useful for binary protocols, not everyday student debugging.

For a composed line with several fields and padding, prefer snprintf into a buffer, then Serial.println(buffer).

Labelled print plus println versus bare numbers
Labels turn a stream of digits into something you can trust at a glance.

Debug print habits

Print key inputs, decisions and outputs at a readable rate - not every loop pass.

Three cards: what to print, how to pace, what to avoid
Aim the flashlight. Print on change or at a calm rate.
api

Serial.available() and Serial.read()

if (Serial.available() > 0) {
  char c = Serial.read();
  // act on c
}

available() reports how many bytes wait in the receive buffer. read() takes one byte. Compare characters with quotes ('1'), not bare numbers. Ignore '\n' and '\r' when you only care about command keys. parseInt() can wait for digits or a timeout - use carefully inside loop().

available, read, decide, act flow for Serial commands
available -> read -> decide -> act. Confirm with a println.
CallRole
Serial.available()Bytes waiting
Serial.read()Take one byte / char
'1' vs 1Character versus integer
parseInt()Read digits (can wait / block)

8. Circuit theory notes

Starter circuit ideas used across the course - LED, button, drivers, PWM, dividers and servo. Each note pairs a figure with wiring rules; full builds live in the linked practical lessons.

circuit

Digital output (LED)

Drive an LED from a digital OUTPUT through a series resistor to GND (anode toward the pin via resistor, cathode to GND for the usual course wiring). This is the hello-world hardware path for Lessons 01, 03 and 07.

Never omit the resistor unless the board already limits current on that path (for example the onboard LED behind LED_BUILTIN).

Breadboard LED with series resistor from an Arduino digital pin to GND
Pin -> resistor -> LED anode; cathode -> GND.
ItemCourse habit
ResistorOften 330 ohm for a 5 V red LED ~10 mA
pinModeOUTPUT before digitalWrite
PolarityLong LED leg is usually the anode
Onboard LEDLED_BUILTIN / pin 13 already has a resistor
Watch out: A bare LED across 5 V can overcurrent a pin. Always calculate or use a safe standard value such as 330 ohm.
circuit

Digital input (button)

Two-state input. Course style: pinMode INPUT_PULLUP and a switch between the pin and GND. Open reads HIGH; pressed reads LOW (active-low). Mechanical contacts bounce for a few milliseconds - debounce in software for reliable counts (Lesson 08).

Button with INPUT_PULLUP wiring
Button from pin to GND; internal pull-up holds open inputs HIGH.

Contact bounce

One physical press can look like many edges if you react to every raw change.

Ideal clean edge versus bouncing button waveform
Ideal: one clean edge. Real button: chatter, then stable LOW.

Software debounce idea

Accept a new state only after the raw reading stays unchanged for about 20-50 ms.

Debounce timer restarting on bounce then accepting stable state
Restart the timer on every raw change. Only a full stable window updates stableState.
circuit

High-current output

Uno pins cannot safely drive motors, relays, solenoids or heaters directly. Use a transistor or MOSFET as a low-side switch (or a driver module), an external supply sized for the load, common GND with the Uno, and a flyback diode across inductive coils.

Transistor or MOSFET low-side switch with relay and flyback diode
Logic pin controls a switch. The load uses its own supply and a flyback diode.
NeedWhy
Driver / transistorPin current and voltage are limited
External supplyMotors need amps the Uno USB rail cannot give
Common GNDShared reference between logic and load supply
Flyback diodeInductive kick when switching coils off
Watch out: Never power a motor from an I/O pin. Shared GND is mandatory between Arduino and the load supply.
circuit

PWM output

Vary duty cycle with analogWrite on a PWM-capable (~) pin to dim an LED or set motor-enable speed on a driver. PWM is a fast digital pulse train, not a smooth DC voltage - many loads still respond to average power.

PWM waveforms at different duty cycles
Higher duty cycle means more average power.
ItemUno habit
PinsD3, D5, D6, D9, D10, D11 (~)
Range0 (off) to 255 (full on)
LEDStill use a series resistor
MotorPWM into a driver enable / input - not the motor coil alone

PWM versus true analogue

analogWrite does not create a laboratory DC voltage. It pulses. Filtering or the load's inertia often makes that feel smooth.

PWM pulse train compared with a true smooth analogue voltage
PWM averages; true analogue holds a steady voltage.
circuit

Potentiometer input

A potentiometer is an adjustable voltage divider: one outer leg to 5 V, the other to GND, wiper to an analogue pin. analogRead returns 0-1023 for position or setpoint control. Measure the 5 V rail if you need accurate volts rather than relative position.

Arduino Uno 5V, A0 and GND wired to a potentiometer outer legs and centre wiper
Outer legs to 5 V and GND. Centre wiper to A0. Turn the shaft to change analogRead.
ConnectionRole
Outer leg5 V
Other outer legGND
Wiper (centre)A0 (or other analogue pin)
CodeanalogRead -> map / scale as needed
circuit

Variable-resistor sensors

LDRs, thermistors and similar change resistance with light or temperature. Place the sensor in a divider with a fixed resistor; read the junction with analogRead. This course usually uses pull-up Rfixed to 5 V and the sensor to GND. Convert counts with calibration or a known model (Beta / Steinhart for NTC) - not guesswork.

Resistive sensor in a voltage divider with a fixed resistor
Fixed resistor and sensor share the mid-point read by the ADC.
Tip: Same analogue ideas as the potentiometer lesson - the resistance just comes from the environment.
circuit

Servo output

Hobby servos need periodic control pulses (about 1-2 ms width inside a ~20 ms frame) and adequate 5-6 V motor power with common GND to the Uno. Prefer the Servo library for angle control unless a lesson asks for hand-timed pulses.

If the board resets when the servo moves, the servo is starving the USB 5 V rail - use a separate 5 V supply.

Servo control pulse width versus angle
Pulse width sets angle. Power the motor side properly.
Wire / needHabit
Signal (usually yellow/orange)Digital pin + Servo library
5 V / VIN motor powerCapable supply; not a weak USB-only setup if the servo stalls
GNDCommon with the Uno
Brown-out / reset on moveSeparate 5 V for the servo
Watch out: Do not power a stalling servo from the Uno 5 V pin alone on USB.

9. Quick reference

Pin map, conversions, common faults and glossary for oral checks and lab sheets. Keep this chapter open during assessments.

table

Uno pin map

Classic Arduino Uno R3 roles used in this course (header layout matches R4 Minima for typical shields). Memorise PWM, I2C and UART pins - they appear in many labs.

Arduino Uno pin map highlighting digital, analogue, power and bus pins
Find power, digital, analogue and bus pins before you wire.
GroupPins / notes
Digital I/OD0-D13
Analogue inA0-A5
PWM (~)D3, D5, D6, D9, D10, D11
UARTD0 RX, D1 TX
I2CA4 SDA, A5 SCL
SPID10-D13 (SS/MOSI/MISO/SCK)
External interruptsD2, D3
Logic / current5 V logic; R3 design ≤ 20 mA/I/O; R4 Minima ~8 mA/I/O
table

Units and conversions

Formulas students use constantly in labs. Keep units consistent (volts, ohms, amperes, milliseconds). Use a float operand when a division must keep a fraction.

NeedFormula / rule
ADC count to volts (default 5 V ref)V = reading * 5.0 / 1023.0
Volts to ADC count (approx.)reading ~= V * 1023.0 / 5.0
LED series resistorR = (Vsupply - Vled) / Iled e.g. (5 - 2) / 0.010 = 300 ohm -> use 330 ohm
Ohms lawV = I * R
PWM duty (0-255)About value/255 of the time HIGH; 0 = off, 255 = full on
millis intervalif (millis() - last >= intervalMs) { last = millis(); /* work */ }
Seconds to milliseconds1 s = 1000 ms; delay(500) is half a second
snprintf clock%02d:%02d:%02d with sizeof(buf)
table

Common sketch and wiring faults

Match the symptom to the usual cause before random rewiring. Many 'hardware' faults are = vs ==, floating inputs, baud mismatch or a buffer format error.

SymptomLikely causeWhat to check
Compile error near a later lineMissing ; on the previous lineAdd the semicolon; read the first error, not only the last
Weird if behaviour / always trueUsed = instead of ==Comparisons need ==; = assigns
Pin never changes / floating inputForgot pinMode or pull-uppinMode in setup; buttons usually INPUT_PULLUP
Serial garbage or nothingBaud mismatch or charge-only cableMatch Serial.begin and Monitor; use a data USB cable
LED dim/wrong or pin hotNo series resistor / overloadAdd resistor; stay within ~20 mA per pin
Button flickers many timesMechanical bounceDebounce in software (Lesson 08)
Motor resets the boardMotor powered from Uno pin/USB aloneExternal motor supply + common GND + driver
Wrong average / truncated mathsInteger divisionUse a float operand, e.g. total / 4.0
Crash or nonsense after many loopsint overflow or bad array indexUse long/unsigned long; stay inside array bounds
Garbage text from snprintfFormat code does not match argument typeUse %lu for unsigned long, %ld for long, dtostrf+%s for float
Truncated LCD / Serial linechar buffer too smallCount chars + 1 for '\0'; pass sizeof(buf)
Crash after sprintfWrote past end of bufferReplace with snprintf(buf, sizeof(buf), ...)
glossary

Glossary

Terms you should be able to explain in assessment or oral checks. If a word appears in a lesson outcome, you should have a one-sentence definition ready.

ADC
Analogue-to-digital converter - turns a voltage into a count.
PWM
Pulse-width modulation - varies duty cycle of a digital pulse train.
UART
Asynchronous serial link (USB Serial on the Uno uses D0/D1).
GPIO
General-purpose input/output pin.
Pull-up
Resistor that holds an open input at a defined HIGH level.
Active-low
The active condition is logic LOW (common for INPUT_PULLUP buttons to GND).
Duty cycle
Fraction of time a PWM signal is HIGH.
Debounce
Ignoring extra edges from a bouncing mechanical switch so one press counts once.
Flyback diode
Diode across an inductive coil that safely absorbs turn-off voltage spikes.
C string
A char array ending in '\0' that print functions treat as text.
snprintf
Formats values into a char buffer with a size limit (safer than sprintf).
Format specifier
A % code in a format string (%d, %lu, %02d, %s) that consumes a matching argument.
dtostrf
Converts a float to a decimal char buffer - used on AVR when %f is unavailable.
Null terminator
The zero byte '\0' that marks the end of a C string.

ESP32 guide (Units G-I)

Lookup notes for the ESP32 track. Practical wiring and full sketches stay in lessons 34-50. Classic ESP32 DevKit / ESP32 Dev Module is the default lab board unless a lesson says otherwise.

note

ESP32 3.3 V I/O safety

ESP32-family GPIO is 3.3 V class. Do not drive an ESP32 input from a 5 V Uno output or a 5 V sensor signal without level shifting. Many modules accept 5 V on USB or VIN while the chip I/O remains 3.3 V.

Design LED pin current around 12 mA or less. NeoPixel / WS2812 strips need an external 5 V supply and common GND for more than a few LEDs - do not power a long string from the ESP32 3.3 V pin.

3.3 V ESP32 I/O versus 5 V Uno I/O
Same IDE idea, different voltage class.
table

Strapping pins and boot

Some GPIOs are sampled at reset to select boot mode. Avoid using them for buttons that might be held during reset, or learn the safe idle level for your board. Exact sets vary by chip; classic ESP32 commonly cares about GPIO 0, 2, 12 and 15.

Strapping pins to treat carefully at boot
Check your module pinout before committing a wire.
TopicCourse habit
GPIO 0Often BOOT - leave free or know pull state
GPIO 2Often onboard LED - OK for blink; watch boot
GPIO 12 / 15Strapping on classic ESP32 - prefer other pins for critical inputs
ADC1 pinsPrefer ADC1 (e.g. GPIO 34-39 on classic) when Wi-Fi is on
note

ESP32 ADC notes

Arduino-ESP32 often exposes 12-bit analogRead (0-4095). That is not the same scale as Uno 0-1023. The ADC is useful for pots and relative sensing; it is not a precision lab meter. Attenuation settings change the usable voltage range. Nonlinearity is worse near the rails.

Classic ESP32 DAC exists on GPIO 25 and 26. Many C-series chips have no DAC - check the datasheet.

10-bit Uno versus 12-bit ESP32 ADC counts
More counts, still not a calibrated instrument.
note

WiFi Station and Access Point

Station (STA): the ESP32 joins your router. After WL_CONNECTED, WiFi.localIP() is the LAN address (usually from DHCP); gatewayIP() is typically the router; RSSI() is signal strength in dBm (more negative is weaker).

Access Point (AP / SoftAP): the ESP32 creates a hotspot for phones to join (handy for first-time setup). softAPIP() is often 192.168.4.1 - a different address from the STA LAN IP.

Classic ESP32 is 2.4 GHz only. Use Serial.begin(115200). Keep WiFi credentials as placeholders - never commit real passwords to shared repos.

Station mode versus Access Point mode
STA joins a router. AP is its own hotspot.
table

HTTP request and response

HTTP is the text protocol behind browsers and many APIs. A request has a method and path; a response has a status code and often a body. Lesson 42 is HTTP server; lesson 43 is HTTP client.

PieceCourse habit
GETRead a page or API document
POSTSend a body (form fields or JSON) to create/update
200OK - body is usable
303 + LocationRedirect after an action so refresh is safe
-1 (HTTPClient)Transport fail - DNS, WiFi, timeout, blocked HTTP
Content-Typetext/html for pages; application/json for APIs
note

JSON for IoT

{
  "room": "Lab room",
  "level": 2048,
  "alert": true,
  "sensor": {
    "temp_c": 21.5,
    "humid": 48
  },
  "tags": ["lab", "bench"]
}

JSON (JavaScript Object Notation) is structured text used by almost every web API and many MQTT payloads. If you can read the braces and quotes, you can invent your own documents for a room node, weather reply or heater command.

ArduinoJson by Benoit Blanchon (JsonDocument in v7) turns that text into values your sketch can test. Workflow: HTTP status 200 (or a known MQTT string) -> deserializeJson -> check DeserializationError -> read fields with optional | defaults -> then act. Print the raw body when a path looks wrong.

The six JSON value types

"text"string

Always double quotes. Keys are strings too. Single quotes are invalid JSON.

21.5number

Integer or decimal. No units in the value - put units in the key name (temp_c).

true / falseboolean

Lowercase only. Not True or TRUE.

nullnull

Means no value. Different from 0 or "".

{ ... }object

Unordered set of "key": value pairs, comma-separated. Nested objects are normal.

[ ... ]array

Ordered list of values (any type). Index from 0.

Rules that break parsers

,Trailing comma

No comma after the last item in an object or array.

'Quotes

Keys and strings must use ". Comments (//) are not allowed in strict JSON.

NaNSpecial numbers

Avoid NaN / Infinity - many parsers reject them. Send null or omit the field.

UTF-8Encoding

Stick to plain UTF-8 text. Escape newlines inside strings as \n.

Path in the exampleArduinoJson readResult type
roomdoc["room"]string
leveldoc["level"] | 0number (with default)
alertdoc["alert"] | falseboolean
sensor.temp_cdoc["sensor"]["temp_c"]number (nested)
tags[0]doc["tags"][0]first array string
Tip: Course labs stay on plain HTTP JSON where possible so certificates do not hide the structure lesson.

Nesting and arrays

// Nested object
float t = doc["sensor"]["temp_c"] | -999.0;

// Array of objects
// { "slides": [ { "title": "Wake up" }, { "title": "..." } ] }
const char* title = doc["slides"][0]["title"] | "?";

Objects hold named fields. Arrays hold ordered items. Mix them freely: an object can contain an array of objects (weather forecasts, slide decks, RFID events).

Zero-based indexes: the first element is [0]. Missing keys or out-of-range indexes yield null / empty - use | defaults so your sketch does not act on garbage.

Write your own JSON

// Hand-built (small)
String body = "{\"temp\":";
body += String(t, 1);
body += ",\"heater\":";
body += heaterOn ? "true" : "false";
body += "}";
// -> {"temp":21.5,"heater":false}

// ArduinoJson build (safer for growth)
JsonDocument out;
out["temp"] = t;
out["heater"] = heaterOn;
serializeJson(out, Serial);

Design the document before the sketch. Pick short stable key names, one clear meaning each, and nest only when a group of fields belongs together (sensor, wifi, alarm).

Building text by hand is fine for small payloads: start with {, add "key": value pairs with commas, close with }. For larger documents, prefer ArduinoJson serializeJson so commas and quotes stay valid.

Tip: Match names exactly on publisher and subscriber - temp and Temp are different keys.

Parse checklist

1) Confirm the body is JSON (not an HTML error page). 2) deserializeJson into a JsonDocument. 3) If error, print the error and the raw body. 4) Read only the fields you need with | defaults. 5) Then drive GPIO / Serial / MQTT.

MistakeFix
Parse before HTTP 200Check status first
Wrong nesting pathPrint body; walk keys one level at a time
Expecting a number, got a stringAPI may send "21.5" - read as string or use as<float>() carefully
Heap blow-upKeep documents small; avoid copying huge String bodies forever
note

Local web server habit

WebServer on port 80 maps URL paths to handler functions (server.on). loop must call server.handleClient() often - a long delay blocks the page.

A 303 redirect after an action (for example /led/on) sends the browser back to / so the page refreshes. Raw string literals R"=====( ... )=====" make HTML easier to embed without escaping every quote.

Browser GET request and ESP32 HTML response
Request path selects the handler; response can be HTML or a redirect.
table

MQTT, BLE and deep sleep

Quick map of Units H-I topics. Prefer the dedicated entries below for BLE, deep sleep, FreeRTOS and OTA. MQTT topics are exact string matches; payloads are often plain text or JSON.

TopicOne-line idea
MQTTPublish/subscribe through a broker - live updates without HTTP polling
QoS / retainDelivery effort and last-message memory on the broker (overview in lesson 44)
BLEPhone talks to a GATT service/characteristic - prefer BLE over Classic SPP
Deep sleepTimer or GPIO wake; RTC memory can keep a small counter
OTAArduinoOTA updates firmware over Wi-Fi after first USB upload
note

BLE GATT essentials

BLE peripheral advertises; phone (central) connects and uses GATT. A service groups characteristics; each characteristic is a readable/writable value identified by a UUID. Properties include READ, WRITE and NOTIFY. Labs often use custom 128-bit UUIDs.

Prefer BLE for short-range phone control without a router. Prefer Wi-Fi for LAN browsers, MQTT and cloud HTTP. Classic Bluetooth is legacy for new ESP32 labs. ESP32-P4 needs a companion radio for BLE.

Phone writing a BLE characteristic on an ESP32 GATT server
Advertise, connect, write a characteristic, drive GPIO.
TermRole
Peripheral / serverESP32 advertising GATT
Central / clientPhone app
ServiceGroup of characteristics
CharacteristicThe value you read or write
NOTIFYServer pushes updates to the phone
note

Deep sleep and wake

Deep sleep powers down most of the chip; normal SRAM is lost and wake typically restarts into setup(). Arm a timer with esp_sleep_enable_timer_wakeup(us) then call esp_deep_sleep_start(). Other wake sources include EXT0/EXT1 GPIOs and touch on supported pins.

RTC_DATA_ATTR keeps a variable across deep sleep but not across power loss - use Preferences/NVS for durable settings. DevKit USB-UART chips and LEDs can hide true sleep current on a USB meter.

Wake, work, deep sleep, timer wake cycle
Duty-cycle: wake, work, sleep.
note

FreeRTOS tasks on ESP32

Arduino-ESP32 runs on FreeRTOS. setup()/loop() usually run on core 1; WiFi/BT work often uses core 0. Create workers with xTaskCreatePinnedToCore; wait with vTaskDelay(pdMS_TO_TICKS(ms)). Start stack sizes around 2048 and raise if the board reboots under load.

Shared globals without protection cause race conditions - use queues or mutexes when tasks share data. Do not add a task for every blink; millis in one loop is often enough.

Dual-core FreeRTOS tasks on ESP32
Pin workers thoughtfully; keep networking responsive.
note

OTA updates

ArduinoOTA: after STA connect, call ArduinoOTA.begin() and ArduinoOTA.handle() often in loop. First flash is USB; later uploads can use Tools → Port → network hostname. Set a unique hostname and an OTA password outside trusted lab LANs. Keep USB recovery available.

HTTP OTA (device pulls an image URL) is the usual field-fleet pattern - different from IDE ArduinoOTA.

IDE OTA upload over WiFi to ESP32
USB once, then network uploads with handle() kept alive.
note

NeoPixel / WS2812 power

Addressable LEDs chain data (DIN to DOUT). Full white can draw about 60 mA per LED - budget the 5 V supply. Share GND with the ESP32. Put a series resistor on the data line (~330-470 ohm) and a bulk capacitor on 5 V near the strip. Call show() after you change pixel colours. Library for this course: Adafruit NeoPixel.

ESP32 data line and external 5 V NeoPixel supply with common GND
External 5 V for the strip; common GND; data from a GPIO.

Revision check

Download .ino sketch

Predict the Serial output before you upload. Then confirm in the Serial Monitor.

// Predict the Serial output before uploading.
char line[24];
int values[] = {2, 4, 6, 8};

void setup() {
  Serial.begin(9600);

  int total = 0;
  for (byte i = 0; i < 4; i++) {
    total += values[i];
  }
  Serial.println(total);          // 20
  Serial.println(total / 4.0);    // 5.00

  int hour = 9;
  int minute = 5;
  snprintf(line, sizeof(line), "%02d:%02d", hour, minute);
  Serial.println(line);           // 09:05

  unsigned long t = 1234UL;
  snprintf(line, sizeof(line), "t=%lu ms", t);
  Serial.println(line);           // t=1234 ms
}

void loop() {}

Expected reasoning

  1. The integer total is 20; 4.0 forces a floating-point average of 5.00.
  2. %02d zero-pads hour and minute so 9:5 becomes 09:05.
  3. %lu matches unsigned long - do not use %d for millis-scale values.
  4. sizeof(line) tells snprintf the buffer limit so it cannot overrun.

Check your understanding

Q1. What runs once after reset, and what runs repeatedly?

Show answer

setup() runs once; loop() repeats.

Q2. What is the difference between = and ==?

Show answer

= assigns; == compares equality.

Q3. Why pass sizeof(buf) to snprintf?

Show answer

So the function knows the buffer limit and will not write past the end.

Q4. Which snprintf code should format unsigned long millis()?

Show answer

%lu

Q5. Why avoid %f with snprintf on a classic Uno?

Show answer

AVR float printf support is often disabled; use dtostrf into a char buffer then %s.

Q6. Why prefer millis over long delay calls in multi-task sketches?

Show answer

delay blocks loop; millis schedules work without stopping other tasks.

Q7. List the Uno PWM pins used with analogWrite.

Show answer

D3, D5, D6, D9, D10 and D11.

Q8. What is the recommended design current per Uno I/O pin?

Show answer

About 20 mA or less; 40 mA is an absolute maximum.

Extension challenge: Hand-write a one-page cheat sheet: Uno types with snprintf codes, INPUT_PULLUP truth, and the millis interval pattern. Answer oral questions without looking.

Reference: Brian Evans Arduino Notebook (CC BY-NC-SA 3.0)