Learning Goals 5 min
Your old UNO sketches mostly work on newer boards — until they don't. A board with 3.3 V GPIO, a different ADC width, no tone() support, or different PWM frequencies will fail in surprising ways. By the end of this lesson you will be able to:
- List the seven most common breakages when moving an UNO sketch to a 32-bit / 3.3 V board.
- Read compiler errors that hint at board-specific incompatibilities (missing libraries, wrong pin numbers, undefined symbols).
- Apply
#ifdefguards to make a single sketch run on multiple board families.
Warm-Up 10 min
Pick a Level 2 or Level 3 sketch that runs cleanly on your UNO. Anything will do — preferably one with: PWM, analog read, Serial, maybe Servo or tone().
Predict before you upload
Look at the sketch and ask: which lines will work on a Nano 33 BLE? Which will fail or behave differently?
Read on for the seven canonical breakages.
New Concept · Seven canonical breakages 25 min
1. 5 V → 3.3 V GPIO
UNO outputs 5 V on HIGH. Nano 33 BLE outputs 3.3 V. A 5 V external device (HC-05, certain sensors) expecting a 5 V signal may not register the 3.3 V as HIGH. Conversely, a 5 V external sensor feeding the Nano 33 BLE can damage its inputs.
Fix: level shifter (TXS0108E, MOSFET-based) on any wire crossing voltage domains.
2. ADC reference + bit width
UNO: 10-bit (0..1023), referenced to 5 V (or AREF). Nano 33 BLE: defaults to 10-bit but supports 12-bit (0..4095), referenced to 3.3 V (or AREF). ESP32: 12-bit, 3.3 V. Same code returns different numbers.
// On UNO, pot at midpoint -> raw ~512
// On ESP32, pot at midpoint -> raw ~2048 (12-bit default)
int raw = analogRead(A0);Fix: always read the actual range. Call analogReadResolution(10) at startup on 32-bit boards if you want the old 0..1023 behaviour.
3. PWM frequency + resolution
UNO: analogWrite uses 8-bit (0..255), ~490 Hz on most pins. Nano 33 BLE: 8-bit by default, but the PWM frequency differs per pin. ESP32: analogWrite doesn't exist in older cores; you use ledcAttach() instead.
Fix: use analogWriteResolution() / analogWriteFreq() where supported. For ESP32, check the core version — newer ones do provide an analogWrite shim.
4. tone() support
UNO: tone() works on any digital pin. Nano 33 BLE: works but uses one of the MCU's timers. ESP32 in older cores: tone() doesn't exist; you use ledcWriteTone().
Fix: wrap in a small helper function with #ifdef guards (see §4).
5. EEPROM library
UNO has real EEPROM (1 KB). ESP8266 / ESP32 emulate EEPROM in flash via a library that requires EEPROM.begin(size) at startup and EEPROM.commit() after writes. The API is slightly different.
Fix: on ESP boards, prefer the more native Preferences (ESP32) or LittleFS for anything beyond a few bytes.
6. Hardware UART count
UNO: 1 hardware UART (D0/D1, shared with USB). Mega: 4. ESP32: 3. Nano 33 BLE: 1. Code that uses Serial1, Serial2 compiles on Mega / ESP32 but fails on UNO with "Serial1 was not declared".
Fix: use SoftwareSerial as a fallback (works on UNO but not on ESP32 — see #7).
7. SoftwareSerial support
UNO / Mega / Nano: SoftwareSerial works. Nano 33 BLE: Adafruit's SoftwareSerial library or none — the official one doesn't support it. ESP32: SoftwareSerial works but the recommended path is to use one of the 3 hardware UARTs instead.
Fix: use a hardware UART when possible. Reach for SoftwareSerial only on UNO / Nano.
Bonus: 5 V devices on the same I²C bus
I²C is open-drain, so it usually survives mixed voltages provided the pull-ups go to the LOWER voltage (3.3 V). But a 5 V chip might not recognise 3.3 V as HIGH. Real-world: most modern I²C peripherals (SSD1306, BME280, DS3231) work fine at 3.3 V; older ones (some 16×2 LCD I²C backpacks) need 5 V on VCC and a level-shifter on SDA/SCL.
How to read the compiler error
| Error | Likely cause |
|---|---|
'Serial1' was not declared | Sketch uses Serial1 on a board without it (UNO). |
analogWrite not declared (ESP32 older core) | Use ledcAttach() + ledcWrite(). |
multiple definition of `__vector_*` | Two libraries claim the same interrupt vector. Common when mixing Servo + SoftwareSerial on UNO. |
region `data` overflowed | Static RAM use exceeded the chip's capacity. Move strings to flash with F() / PROGMEM, or upgrade board. |
EEPROM not declared | ESP boards need #include <EEPROM.h> + EEPROM.begin(size) in setup. |
Worked Example · Make one sketch run on three boards 25 min
Target sketch
A blinker + buzzer + pot reader that runs identically on UNO, ESP32, Nano 33 BLE.
// L04-03 · Cross-board sketch
#if defined(ARDUINO_ARCH_AVR)
// UNO / Mega / Nano
const int ADC_MAX = 1023;
const int BUZZER = 8;
void tonePlay(int pin, int freq, int ms) { tone(pin, freq, ms); }
#elif defined(ARDUINO_ARCH_ESP32)
const int ADC_MAX = 4095;
const int BUZZER = 16;
void tonePlay(int pin, int freq, int ms) {
// ESP32 older cores need ledc; newer have tone() shim
tone(pin, freq, ms);
}
#elif defined(ARDUINO_ARCH_NRF52840) || defined(ARDUINO_NANO33BLE)
const int ADC_MAX = 1023;
const int BUZZER = 2;
void tonePlay(int pin, int freq, int ms) { tone(pin, freq, ms); }
#else
#warning "Unknown board, defaults assumed."
const int ADC_MAX = 1023;
const int BUZZER = 8;
void tonePlay(int pin, int freq, int ms) { tone(pin, freq, ms); }
#endif
const int LED_PIN = LED_BUILTIN;
const int POT_PIN = A0;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER, OUTPUT);
#if defined(ARDUINO_ARCH_NRF52840) || defined(ARDUINO_ARCH_ESP32)
analogReadResolution(10); // force 0..1023 on 12-bit boards
#endif
}
void loop() {
int raw = analogRead(POT_PIN); // always 0..1023 thanks to resolution call
int pct = map(raw, 0, 1023, 0, 100);
digitalWrite(LED_PIN, (millis() / 500) % 2);
if (pct > 80) tonePlay(BUZZER, 880, 50);
Serial.print("pct="); Serial.println(pct);
delay(100);
}Walk-through
#if defined(ARDUINO_ARCH_AVR): the compiler defines this macro automatically based on the selected board family.analogReadResolution(10)on 32-bit boards forces the same 0..1023 range as the UNO so the maths still works.- The
tonePlaywrapper is a single-line shim so future ESP32 cores that droptone()can be patched in one place. - The
#warningbranch lets you compile on unknown boards but reminds you to update the table.
Test on each board
If you have multiple boards, upload to each. The behaviour should be identical: LED blinks once a second, pot drives the percentage, buzzer beeps above 80%.
If you don't have multiple boards
Compile-only for each board family (File → Verify after switching the board). Confirm zero errors. That's 80% of the migration success.
Try It Yourself 15 min
Goal: Take your L01-22 Reaction Timer sketch. Compile-only for UNO + ESP32 + Nano 33 BLE. Fix any errors with #ifdef.
Goal: Take L02-15 Photoresistor Light Meter. Migrate to ESP8266. The 0..1023 ADC range, the A0-only ADC pin, and the 3.3 V reference all change the meaning of the numbers. Recalibrate the threshold constants.
Goal: Migrate the L02-26 Smart Bin Lid (UNO + HC-SR04 + servo) to a Nano 33 BLE. Address: SoftwareSerial replacement (not needed here — no module), servo library compatibility, voltage difference for the HC-SR04 (3.3 V logic OK but ECHO pulse height is now 3.3 V — UNO sketches that assume 5 V echo work fine since pulseIn reads pulse length, not voltage).
Mini-Challenge · Cross-board project starter 10 min
Save the §4 sketch as portable.ino in your personal library folder. Use it as the starting template for any new project — fewer surprises later when you switch boards.
Add to the top a comment block listing the boards you've tested it on + the date + observed behaviour. Future-you (or a teammate) will appreciate.
Recap 5 min
Seven canonical breakages when moving UNO sketches to other boards: voltage, ADC width, PWM, tone, EEPROM, hardware UARTs, SoftwareSerial. #ifdef with ARDUINO_ARCH_* macros lets one sketch handle multiple boards. The cleanest migrations start with a portable template; the painful ones start with a UNO-specific sketch that hardcodes assumptions. Tomorrow we drill into pin maps — which pin does what on which board.
- ARDUINO_ARCH_* macros
- Compiler-defined macros that identify the board family:
ARDUINO_ARCH_AVR,ARDUINO_ARCH_ESP32,ARDUINO_ARCH_NRF52840, etc. - Shim / wrapper function
- A small function that hides board-specific implementation behind a uniform interface. Lets the rest of the sketch stay portable.
- Conditional compilation
#ifdef/#if defined()/#elif: the C preprocessor selects code paths based on macros. Used heavily for cross-board sketches.F()macro- Wraps a string literal so it's stored in flash instead of RAM. Critical on AVR where RAM is tight.
Serial.println(F("Hello")); - Region overflow
- Compiler error meaning your code's static data exceeds the chip's RAM. Fix with
F()/PROGMEMor upgrade board. - Native USB
- Modern boards present USB to the host directly without a separate USB-serial chip. Different reset semantics from UNO; check the board's docs.
Homework 5 min
- Pick one of your favourite L1–L3 sketches. Make it compile on a non-UNO board. Bring the diff.
- Tape the §3 "seven canonical breakages" into your notebook for quick reference.
- Read ahead to ARD-L04-04 (Pin Mapping Limitations).