The board
UNO form factor, 5 V logic, two processors. The RA4M1 owns every header pin; the ESP32-S3 is a peripheral that speaks AT-style commands over an internal UART and doubles as the USB-serial bridge and the SWD programmer.
Schematic top view, USB-C at the left. Header pin order and spacing are to scale (2.54 mm pitch); component outlines are indicative. Board outline 68.85 × 53.34 mm, four M3 mounting holes, ~25 g.
Silicon U1 · M1
WiFiS3 and ArduinoBLE concurrently.Reference designators silkscreen
Wire1Dedicated connectors
Wire1Wire1Compatible with SparkFun Qwiic, Adafruit STEMMA QT and Arduino Modulino nodes.
Pin map
Every alternate function the core can actually mux onto a pin, read from variants/UNOWIFIR4/variant.cpp and pinmux.inc. Header positions come from the ABX00087 netlist. Bold = the role the board is wired for.
| Pin | idx | RA4M1 | Header | ADC | PWM out | IRQ | Bus function | Notes |
|---|---|---|---|---|---|---|---|---|
| D0 | 0 | P301 | DIG-1 | — | GTIOC4B | IRQ6 | SCI2 RXD | Serial1 RX |
| D1 | 1 | P302 | DIG-2 | — | GTIOC4A | IRQ5 | SCI2 TXD | Serial1 TX |
| D2 | 2 | P104 | DIG-3 | — | GTIOC1B | IRQ1 | SCI0 RX | classic INT1 slot |
| D3 | 3 | P105 | DIG-4 | — | GTIOC1A | IRQ0 | — | PWM at boot · classic INT0 slot |
| D4 | 4 | P106 | DIG-5 | — | GTIOC0B | — | — | plain GPIO |
| D5 | 5 | P107 | DIG-6 | — | GTIOC0A | — | — | PWM at boot |
| D6 | 6 | P111 | DIG-7 | — | GTIOC3A | IRQ4 | SCI2/SCI9 SCK · SPI1 SCK | PWM at boot |
| D7 | 7 | P112 | DIG-8 | — | GTIOC3B | — | SCI1 SCK · SCI2 TX | plain GPIO |
| D8 | 8 | P304 | DIG-9 | — | GTIOC7A | IRQ9 | — | plain GPIO |
| D9 | 9 | P303 | DIG-10 | — | GTIOC7B | — | — | PWM at boot |
| D10 | 10 | P103 | DIG-11 | AN19 | GTIOC2A | — | SPI0 SSL · CAN0 TX · SCI0 CTS | SPI CS · PWM at boot |
| D11 | 11 | P411 | DIG-12 | — | GTIOC6A | IRQ4 | SPI0 MOSI · SCI0 TX | SPI COPI · PWM at boot |
| D12 | 12 | P410 | DIG-13 | — | GTIOC6B | IRQ5 | SPI0 MISO · SCI0 RX | SPI CIPO |
| D13 | 13 | P102 | DIG-14 | AN20 | GTIOC2B | — | SPI0 RSPCK · CAN0 RX | SPI SCK · LED_BUILTIN |
| A0 | 14 | P014 | ANA-9 | AN9 | — | — | DAC0 out | true analog out, 8/12-bit |
| A1 | 15 | P000 | ANA-10 | AN0 | — | IRQ6 | OPAMP0 + | op-amp non-inverting in |
| A2 | 16 | P001 | ANA-11 | AN1 | — | IRQ7 | OPAMP0 − | op-amp inverting in |
| A3 | 17 | P002 | ANA-12 | AN2 | — | IRQ2 | OPAMP0 OUT | op-amp output |
| A4 / SDA | 18 | P101 | ANA-13 = DIG-17 | AN21 | GTIOC5A | IRQ1 | IIC1 SDA · SCI0 TX | Wire — same net as the SDA header pin |
| A5 / SCL | 19 | P100 | ANA-14 = DIG-18 | AN22 | GTIOC5B | IRQ2 | IIC1 SCL · SCI0 RX | Wire — same net as the SCL header pin |
Off-header pins idx 20–38
The core maps 39 pins. Indices 20–38 are not on any header but are addressable with digitalWrite(21, HIGH) and friends.
| idx | port | ADC | PWM | IRQ | Role |
|---|---|---|---|---|---|
| 20 | P500 | AN16 | GTIOC2A | — | supply-voltage sense, ×8.33 divider |
| 21 | P408 | — | GTIOC5B | IRQ7 | USB mux select — HIGH hands USB to the RA4M1 |
| 22 | P109 | — | GTIOC1A | — | Serial TX → level translator → ESP32 USB bridge |
| 23 | P110 | — | GTIOC1B | IRQ3 | Serial RX ← ESP32 USB bridge |
| 24 | P501 | AN17 | GTIOC2B | IRQ11 | Serial2 TX → ESP32 Wi-Fi modem |
| 25 | P502 | AN18 | GTIOC3B | IRQ12 | Serial2 RX ← ESP32 Wi-Fi modem |
| 26 | P400 | — | GTIOC6A | IRQ0 | Wire1 SCL — Qwiic |
| 27 | P401 | — | GTIOC6B | IRQ5 | Wire1 SDA — Qwiic |
| 28–38 | P003, P004, P011, P012, P013, P015, P204, P205, P206, P212, P213 | AN3,4,6,7,8,10 | several | several | the 11 charlieplex drive lines of the 12×8 LED matrix |
Shared IRQ channels collision list
The RA4M1 has 16 external-interrupt channels and several header pins land on the same one.
Two pins that share a channel cannot both hold an attachInterrupt().
No IRQ at all: D4, D5, D7, D9, D10, D13, A0.
Pin electrical limits
INPUT, INPUT_PULLUP, OUTPUT, OUTPUT_OPENDRAIN; no internal pull-downanalogReference(AR_EXTERNAL)Handy pin aliases
PINCOUNT_fn())// the classic AVR port macros work on the RA4M1 volatile uint32_t *out = portOutputRegister(digitalPinToPort(13)); uint32_t mask = digitalPinToBitMask(13); *out |= mask; // D13 high, no function call // or straight at the peripheral R_PORT1->PODR |= (1 << 2); // P102 = D13
Basics
The five things every sketch does — drive a pin, read a pin, read a voltage, print a line, and keep time without stopping. Plain Arduino that works on any board; where the R4 differs from the classic UNO, it says so.
Digital output turning a pin on and off
setup() — every pin is an input until you say otherwisebool passes straight throughvoid setup() { pinMode(LED_BUILTIN, OUTPUT); // D13 } void loop() { digitalWrite(LED_BUILTIN, HIGH); // on delay(500); // milliseconds digitalWrite(LED_BUILTIN, LOW); // off delay(500); }
digitalWrite(13, !digitalRead(13)); // flip it, no state variable // the same blink, leaving the rest of loop() free to run static uint32_t last = 0; if (millis() - last >= 500) { last += 500; digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); }
Digital input buttons and switches
const int BUTTON = 2; void setup() { pinMode(BUTTON, INPUT_PULLUP); pinMode(LED_BUILTIN, OUTPUT); } void loop() { bool pressed = !digitalRead(BUTTON); // LOW == pressed digitalWrite(LED_BUILTIN, pressed); }
static bool prev = HIGH; static uint32_t at = 0; bool now = digitalRead(BUTTON); // 25 ms of quiet before believing it if (now != prev && millis() - at > 25) { at = millis(); prev = now; if (now == LOW) { /* pressed, once */ } }
A bare mechanical contact bounces for a few milliseconds. Without the timer you get five presses out of one push.
Analog in and out
~: D3, D5, D6, D9, D10, D11map() happily returns out-of-range valuesvoid loop() { int pot = analogRead(A0); // 0 … 1023 int duty = map(pot, 0, 1023, 0, 255); analogWrite(9, duty); // dim an LED on D9 float volts = pot * (5.0f / 1023.0f); }
for (int v = 0; v <= 255; v++) { // up analogWrite(9, v); delay(4); } for (int v = 255; v >= 0; v--) { // and back down analogWrite(9, v); delay(4); }
PWM is a 490 Hz square wave, not a voltage. It dims an LED and drives a motor; it will not feed an analog input without a filter. The Analog section covers depth, frequency and the real DAC.
Serial talking to the computer
setup(); the Serial Monitor must be set to the same baudDEC HEX OCT BIN, or a digit count for floatssetTimeout()void setup() { Serial.begin(115200); Serial.println("ready"); } void loop() { while (Serial.available()) { char c = Serial.read(); Serial.print("got: "); Serial.println(c); } }
Serial.println(3.14159, 2); // 3.14 Serial.println(0xC0FFEE, HEX); // C0FFEE Serial.printf("up %lu ms\n", millis()); // available on this core Serial.print(analogRead(A0)); // tab-separated columns feed Serial.print('\t'); // the IDE's Serial Plotter Serial.println(analogRead(A1));
while (!Serial) needed here. On the UNO R4 WiFi Serial is a
UART bridged by the ESP32, not native USB, so it is ready the moment you call begin() and it
survives a reset without re-enumerating. The wait loop is harmless but does nothing.Timing without stopping the sketch
unsigned long since reset; wraps at ~49.7 daysloop() runs, though interrupts still fire// two jobs at two rates, neither blocking the other uint32_t tLed = 0, tLog = 0; void loop() { uint32_t now = millis(); if (now - tLed >= 250) { tLed = now; digitalWrite(13, !digitalRead(13)); } if (now - tLog >= 1000) { tLog = now; Serial.println(analogRead(A0)); } }
now - last >= interval stays correct
across the rollover; now >= last + interval does not, and will hang your sketch for 49 days
once every 49 days.Servo install from Library Manager
~ needed#include <Servo.h> Servo arm; void setup() { arm.attach(9); } void loop() { arm.write(0); delay(600); arm.write(180); delay(600); arm.writeMicroseconds(1500); // centre }
Sound tone()
tone() on another pin replaces the firstconst int SPK = 8; int notes[] = {262, 294, 330, 349}; // C4 D4 E4 F4 for (int n : notes) { tone(SPK, n, 200); delay(250); // note + a gap } noTone(SPK);
A piezo disc works straight off a pin. A loudspeaker does not — it needs a series resistor at the very least, and really wants an amplifier.
Power
Two inlets, one buck converter, one LDO. The OFF pin gates the buck; VRTC keeps only the clock alive.
Recommended operating conditions
| Symbol | Source | Min | Typ | Max |
|---|---|---|---|---|
| VIN | VIN pad / DC jack | 6 V | 7.0 V | 24 V |
| VUSB | USB-C | 4.8 V | 5.0 V | 5.5 V |
| TOP | ambient | −40 °C | 25 °C | 85 °C |
Power tree
DC jack (6–24 V) ─┐
VIN pin ├─▶ D1 ─┐
USB-C VBUS (5 V) ─┴─▶ D2 ─┴─▶ ISL854102 buck (U3) ─▶ 5 V rail
▲ │
OFF pin ┘ (to GND = off) │
▼
SGM2205 LDO (U5) ─▶ 3.3 V rail
├─▶ ESP32-S3 (M1)
├─▶ Qwiic (J2)
└─▶ TXB0108 A-side (U4)
5 V rail ─▶ RA4M1 (U1) ─▶ every header pin, IOREF, AREF domain
VRTC pin (1.6–3.3 V) ─▶ RTC domain only, survives loss of the main supply
OFF and VRTC JOFF header
Two pins UNO boards never had before, on the small header beside the barrel jack.
#include "RTC.h" RTC.begin(); RTCTime fallback(6, Month::NOVEMBER, 2023, 18, 12, 0, DayOfWeek::MONDAY, SaveLight::SAVING_TIME_ACTIVE); RTCTime saved; RTC.getTime(saved); // woke up "as new"? if (!RTC.isRunning()) RTC.setTime(saved.getYear() == 2000 ? fallback : saved);
Buses & connectivity
Three UARTs, one SPI, two I2C, one CAN controller, one native-USB device controller — and a mux that decides which processor the USB-C port is talking to.
Serial ports the part that surprises people
The core builds this variant with -DNO_USB, so the Serial
object is not native USB — it is a hardware UART wired to the ESP32, which bridges it to the USB-C port.
| Object | SCI | Pins | Goes to |
|---|---|---|---|
| Serial | SCI9 | P109/P110 | level translator → ESP32 → USB-C serial monitor |
| Serial1 | SCI2 | D1 TX / D0 RX | the header pins — your external UART device |
| Serial2 | SCI1 | P501/P502 | ESP32 AT modem — owned by WiFiS3, leave alone |
| SerialUSB | USBFS | USB D+/D− | RA4M1 native USB CDC — needs the mux flipped |
Serial1.begin(9600) or begin(baud, SERIAL_8N1)SERIAL_5N1…SERIAL_8O2 — data bits 5–8, parity N/E/O, 1 or 2 stop bitsSerial.available() insteadSerial1.begin(115200); while (Serial1.available()) { char c = Serial1.read(); Serial.write(c); // forward to the USB monitor }
USB, the mux, and HID
USB D+/D− pass through two NLASB3157 muxes. By default they point at the ESP32, which acts as the serial bridge and resets the RA4M1 for uploads.
HIGH = RA4M1 owns USB, LOW = ESP32 (default)Serial to SerialUSB and starts native USB, which flips the mux for you#include <Keyboard.h> // pulls in HID.h Keyboard.begin(); Keyboard.press('W'); Keyboard.releaseAll(); Mouse.move(x, y); Mouse.click(MOUSE_LEFT); // manual switch, without HID pinMode(21, OUTPUT); digitalWrite(21, HIGH);
SPI SPI0 · D10–D13 + ICSP
SPI); the ICSP header carries the same bus#include <SPI.h> const int CS = 10; pinMode(CS, OUTPUT); digitalWrite(CS, HIGH); SPI.begin(); SPI.beginTransaction( SPISettings(8000000, MSBFIRST, SPI_MODE0)); digitalWrite(CS, LOW); uint8_t in = SPI.transfer(0x9F); SPI.transfer(buf, len); // in-place block transfer digitalWrite(CS, HIGH); SPI.endTransaction();
I2C two independent buses
Wire; the Qwiic side is pulled up on the module you plug in#include <Wire.h> Wire.begin(); // controller on A4/A5 Wire1.begin(); // controller on Qwiic Wire.setClock(400000); // 100k / 400k Wire.beginTransmission(0x3C); Wire.write(0x00); Wire.write(value); uint8_t err = Wire.endTransmission(); // 0 = ok Wire.requestFrom(0x3C, 2); while (Wire.available()) uint8_t b = Wire.read(); // peripheral mode Wire.begin(0x42); Wire.onReceive(handler); Wire.onRequest(handler);
analogRead() them while the bus is running.CAN CAN0 · 2.0A / 2.0B
BR_125k BR_250k BR_500k BR_1000k, or a raw value#include <Arduino_CAN.h> CAN.begin(CanBitRate::BR_250k); uint8_t data[] = {0xCA,0xFE,0,0,0,0,0,0}; CanMsg msg(CanStandardId(0x20), sizeof(data), data); CAN.write(msg); if (CAN.available()) { CanMsg rx = CAN.read(); Serial.println(rx.id, HEX); } CAN.setFilterMask_Standard(0x7FF); CAN.setFilterId_Standard(0, 0x20); CAN.enableInternalLoopback(); // bench test, no bus
Analog
A 14-bit ADC, a real 12-bit DAC on A0, and an on-chip operational amplifier wired to A1–A3. This is where the R4 leaves the AVR UNO furthest behind.
ADC analogRead
analogReadResolution() scales the returned valuevoid setup() { analogReadResolution(14); // 0 – 16383 analogReference(AR_DEFAULT); } int raw = analogRead(A0); float volts = raw * (5.0f / 16383.0f);
analogAddPinToGroup(A0); analogAddPinToGroup(A1); attachScanEndIrq(onScanDone); // void f(uint8_t unit) // DMA-fed, no blocking reads analogStartScan();
DAC A0 · 12-bit
A0 is a genuine analog output, not PWM — no filtering needed, no carrier ripple.
analogWriteResolution(12)analogWriteResolution() is global — it changes PWM depth on the other pins tooanalogWriteResolution(12);
analogWrite(A0, 2048); // ≈ mid-rail
#include "analogWave.h" analogWave wave(DAC); // DAC == A0 wave.sine(440); // also square() and saw() wave.amplitude(0.5); // 0.0 – 1.0 wave.freq(880); wave.offset(12); wave.stop(); wave.start(); // or your own sample table, cycled out by DTC uint16_t table[256]; analogWave custom(DAC, table, 256, 0); custom.begin(1000); // whole table per period
OPAMP on-chip, channel 0
#include <OPAMP.h> // or OPAMP_SPEED_LOWSPEED OPAMP.begin(OPAMP_SPEED_HIGHSPEED); if (OPAMP.isRunning(0)) { /* … */ } OPAMP.end();
Once started, the three pins belong to the amplifier — build your gain network externally between A3 and A2 exactly as you would with a discrete op-amp. The DAC on A0 makes a convenient signal source.
Timers, PWM & interrupts
Two AGT timers and eight GPT channels. The core spends one AGT on millis() and claims
six GPT outputs for PWM at boot; everything left is yours through FspTimer.
Timer inventory
millis(), micros(), delay()#include "FspTimer.h" FspTimer t; void tick(timer_callback_args_t *a) { /* ISR */ } uint8_t type; int8_t ch = FspTimer::get_available_timer(type); if (ch < 0) { // nothing spare? FspTimer::force_use_of_pwm_reserved_timer(); ch = FspTimer::get_available_timer(type, true); } t.begin(TIMER_MODE_PERIODIC, type, ch, 1000.0f, 0.0f, tick, nullptr); t.setup_overflow_irq(); t.open(); t.start(); // .stop() .close() .set_frequency()
Timer type constants are GPT_TIMER and AGT_TIMER.
PWM
~analogWriteResolution(12) for 0–4095analogWrite(9, 128); // 50 % at 490 Hz
analogWriteResolution(12);
analogWrite(9, 2048);
#include "pwm.h" PwmOut pwm(9); // 25 kHz at 50 % — silent fan drive pwm.begin(25000.0f, 50.0f); pwm.pulse_perc(30.0f); pwm.period_us(40); pwm.pulseWidth_us(12); pwm.suspend(); pwm.resume(); pwm.end();
External interrupts
digitalPinToInterrupt(p) returns pRISING, FALLING, CHANGE, LOWvolatile bool pressed = false; void isr() { pressed = true; } pinMode(2, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(2), isr, FALLING);
Watchdog WDT
#include <WDT.h> // timeout in ms, rounded to a legal prescaler WDT.begin(2000); void loop() { WDT.refresh(); // kick it, or the board resets } uint32_t t = WDT.getTimeout(); uint32_t c = WDT.getCounter();
Prescalers 1–8192 and reload values 128–16384 constrain the achievable timeouts; the library picks
the closest pair and getTimeout() tells you what you actually got.
Real-time clock
#include "RTC.h" RTC.begin(); RTCTime t(30, Month::JUNE, 2023, 13, 37, 0, DayOfWeek::WEDNESDAY, SaveLight::SAVING_TIME_ACTIVE); RTC.setTime(t); RTC.setTimeIfNotRunning(t); RTCTime now; RTC.getTime(now); now.getDayOfMonth(); now.getMonth(); now.getYear(); now.getHour(); now.getMinutes(); now.getSeconds(); now.getDayOfWeek(); now.getUnixTime(); Month2int(now.getMonth()); DayOfWeek2int(dow, true);
RTC.setPeriodicCallback(tick, Period::ONCE_EVERY_1_SEC); AlarmMatch m; m.addMatchHour(); m.addMatchMinute(); m.addMatchSecond(); RTC.setAlarmCallback(ring, alarmTime, m); RTC.isRunning();
ONCE_EVERY_2_SEC, ONCE_EVERY_1_SEC, N2_…N128_TIMES_EVERY_SECWiFi.getTime() returns a Unix epoch you can feed to RTCTimeTime functions
unsigned long ms since boot; wraps after ≈49.7 daysdelay(); override it to keep background work alive// non-blocking cadence — reach for this by default static uint32_t last = 0; if (millis() - last >= 500) { last += 500; /* … */ }
LED matrix
96 red LEDs in 12 columns × 8 rows, charlieplexed across 11 RA4M1 pins. A timer ISR lights exactly one LED at a time at ~10 kHz, so the whole display costs you eleven pins you never see and one spare GPT channel.
Getting a picture up
#include "Arduino_LED_Matrix.h" ArduinoLEDMatrix matrix; void setup() { matrix.begin(); }
// 1. readable — a byte per pixel, edit at runtime uint8_t frame[8][12] = { {0,0,1,1,0,0,0,1,1,0,0,0}, {0,1,0,0,1,0,1,0,0,1,0,0}, {0,1,0,0,0,1,0,0,0,1,0,0}, {0,0,1,0,0,0,0,0,1,0,0,0}, {0,0,0,1,0,0,0,1,0,0,0,0}, {0,0,0,0,1,0,1,0,0,0,0,0}, {0,0,0,0,0,1,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0}}; frame[2][1] = 1; matrix.renderBitmap(frame, 8, 12); // 2. compact — 96 bits packed into three uint32, row-major const uint32_t heart[] = {0x3184a444, 0x44042081, 0x100a0040}; matrix.loadFrame(heart);
Read the packed form by writing all three words out in binary, concatenating them and slicing into groups of twelve — each group is one row, MSB on the left.
Animation API
true repeats foreverconst uint32_t blink[][4] = { {0x0, 0x0, 0xc00c0000, 150}, // pixels ×3, then ms {0x0, 0x1e01, 0x201201e0, 150}}; matrix.loadSequence(blink); matrix.play(true);
Text with ArduinoGraphics
Install the ArduinoGraphics library and include it before the matrix header — the matrix class inherits from it only if it is already visible.
#include "ArduinoGraphics.h" // must come first #include "Arduino_LED_Matrix.h" ArduinoLEDMatrix matrix; matrix.beginDraw(); matrix.stroke(0xFFFFFFFF); matrix.textScrollSpeed(50); matrix.textFont(Font_5x7); // or Font_4x6 matrix.beginText(0, 1, 0xFFFFFF); matrix.println(" Hello World! "); // SCROLL_RIGHT, or omit it for static text matrix.endText(SCROLL_LEFT); matrix.endDraw();
The colour argument exists because ArduinoGraphics also drives RGB hardware; any non-zero value lights the LED. Twelve columns fits about two 5×7 characters, so scroll anything longer.
Built-in gallery
matrix.loadFrame(NAME)LEDMATRIX_BLUETOOTH · LEDMATRIX_BOOTLOADER_ON · LEDMATRIX_CHIP · LEDMATRIX_CLOUD_WIFI · LEDMATRIX_DANGER · LEDMATRIX_EMOJI_BASIC · LEDMATRIX_EMOJI_HAPPY · LEDMATRIX_EMOJI_SAD · LEDMATRIX_HEART_BIG · LEDMATRIX_HEART_SMALL · LEDMATRIX_LIKE · LEDMATRIX_MUSIC_NOTE · LEDMATRIX_RESISTOR · LEDMATRIX_UNO
matrix.loadSequence(NAME)LEDMATRIX_ANIMATION_ ⟨ ARROWS_COMPASS · ATMEGA · AUDIO_WAVEFORM · BATTERY · BOUNCING_BALL · BUG · CHECK · CLOUD · DOWNLOAD · DVD · HEARTBEAT · HEARTBEAT_LINE · HOURGLASS · INFINITY_LOOP_LOADER · LED_BLINK_HORIZONTAL · LED_BLINK_VERTICAL · LOAD · LOAD_CLOCK · LOCK · NOTIFICATION · OPENSOURCE · SPINNING_COIN · STARTUP · TETRIS · TETRIS_INTRO · WIFI_SEARCH ⟩
The browser-based editor at ledmatrix-editor.arduino.cc exports both frame formats. Its
live-preview mode wants a sketch that reads 12 bytes at a time off Serial and calls
loadFrame() — File ▸ Examples ▸ LED_Matrix ▸ LivePreview.
What it costs you
matrix.begin()None of the eleven pins reach a header, so the matrix never conflicts with your wiring — only with your timer budget.
Wi-Fi & Bluetooth
The ESP32-S3 ships with Arduino's own firmware and behaves as an AT-style modem on Serial2.
You talk to it through WiFiS3, which mimics the classic WiFiNINA API.
Connecting
#include "WiFiS3.h" if (WiFi.status() == WL_NO_MODULE) { /* radio not answering */ } Serial.println(WiFi.firmwareVersion()); while (WiFi.begin(ssid, pass) != WL_CONNECTED) delay(5000); Serial.println(WiFi.localIP()); Serial.println(WiFi.RSSI()); Serial.println(WiFi.SSID());
begin()WL_IDLE_STATUS · WL_NO_SSID_AVAIL · WL_SCAN_COMPLETED · WL_CONNECTED · WL_CONNECT_FAILED · WL_CONNECTION_LOST · WL_DISCONNECTED · WL_AP_LISTENING · WL_AP_CONNECTED · WL_AP_FAILED · WL_NO_MODULE
Sockets
connect(), print(), available(), read(), stop()begin(), available()beginPacket(), write(), endPacket(), parsePacket()WiFiServer server(80); server.begin(); WiFiClient c = server.available(); if (c) { while (c.connected()) { if (c.available()) { char ch = c.read(); /* … */ } } c.stop(); }
#include <OTAUpdate.h> OTAUpdate ota; ota.setCACert(root_ca); ota.begin(); // or startDownload + downloadProgress ota.download(url); ota.verify(); ota.update(); // reboots into the new sketch
Bluetooth LE
#include <ArduinoBLE.h> if (!BLE.begin()) while (1); BLE.scan(); BLEDevice p = BLE.available(); if (p && p.localName() == "Sensor") { BLE.stopScan(); p.connect(); p.discoverAttributes(); BLECharacteristic c = p.characteristic(0); if (c.canRead()) c.read(); }
Living with the ESP32
Serial2 at 115200 by defaultESP_DOWNLOAD (J6 pin 4) to GND while resettingWiFiS3 all stop working until you restore Arduino's firmware with
espflash. Keep the stock binary before you experiment.Serial.println(WiFi.firmwareVersion()); // mismatch with the core's expectation → update via // IDE ▸ Tools ▸ Firmware Updater, or arduino-cli
The language
An Arduino sketch is C++17 compiled by arm-none-eabi-g++ with a generated main() that calls setup() once and loop() forever. On this 32-bit target several familiar type sizes change.
Sketch skeleton
// everything above setup() is global scope #include <Wire.h> const int BUTTON = 2; volatile bool flag = false; void setup() { // runs once after reset Serial.begin(115200); pinMode(BUTTON, INPUT_PULLUP); } void loop() { // runs forever }
The core supplies main(): it calls initVariant(), then setup(), then loops on loop() — no while(1) of your own needed. Sketch files (.ino) are concatenated alphabetically after the primary tab, and function prototypes are auto-generated, which is why you can call a function defined further down the file.
Serial is a UART, not USB CDCTypes and their sizes here 32-bit ARM
The single biggest source of ported-sketch bugs: int is four bytes, and double is a real double.
true / falsemillis() and micros()setup() and loop()int a[5] = {1,2,3,4,5}; — no bounds checking, ever#define for typed constantsconst in headersOperators
= in an if is the classic silent bug*(a + i)sizeof(arr)/sizeof(arr[0]) is the element countControl flow
breakint average(int a, int b) { return (a + b) / 2; } // default arguments and overloads are fine — this is C++ void blink(int pin, int times = 1); // pass an array by pointer plus a length; arrays decay void sum(const int *data, size_t n); // lambdas work, and convert to a plain function pointer attachInterrupt(2, []() { flag = true; }, FALLING);
Structs, enums and classes
A sketch is C++17, so the aggregate types are all available and cost nothing at run time. Grouping related state beats a row of parallel arrays.
struct Reading { uint32_t at; float v; }; Reading r = { millis(), 3.3f }; r.v = 5.0f; // dot; arrow via a pointer enum class Mode : uint8_t { Idle, Arming, Run }; Mode m = Mode::Idle; // scoped — no name collisions enum { LEFT, RIGHT, BOTH }; // plain, converts to int
class Blinker { int pin; uint32_t last = 0; bool on = false; public: Blinker(int p) : pin(p) {} void begin() { pinMode(pin, OUTPUT); } void tick(uint32_t ms) { if (millis() - last < ms) return; last = millis(); on = !on; digitalWrite(pin, on); } }; Blinker led(LED_BUILTIN); // a global object // hardware gets touched here, never in the constructor void setup() { led.begin(); }
pinMode(), Serial or millis() from
the constructor of a global object. Static constructors run before the core has initialised the
clocks and the pin tables. Give the class a begin() and call it from setup() —
which is exactly why every Arduino library works that way.Preprocessor
&& and ||#define LED_PIN 13 // untyped, invisible to the debugger const int ledPin = 13; // prefer this — typed and scoped #if defined(ARDUINO_UNOR4_WIFI) #include "WiFiS3.h" #elif defined(ARDUINO_AVR_UNO) #error "no radio on this board" #endif
The board macros worth branching on are listed under Sketch skeleton. Reach for
const or constexpr for values and leave the
preprocessor for conditional compilation.
Interrupt-safe code
The RA4M1 has a nested vectored interrupt controller and the core is happy to hand you ISRs. The usual rules bite harder here because the CPU is fast enough to make races common.
loop()delay(), no Serial.print(), no String, no mallocloop() can tear; guard with noInterrupts()void f() for attachInterrupt; void f(timer_callback_args_t*) for FspTimervolatile uint32_t edges = 0; void isr() { edges++; } void loop() { noInterrupts(); uint32_t snapshot = edges; interrupts(); Serial.println(snapshot); }
Memory 32 kB SRAM
new, malloc and String all use it — fragmentation is real in a long-running loopchar buffers with snprintf() in code that runs for weeksFunction index
The whole callable surface: core API, Stream, String, and the bundled libraries. Use the filter box in the header bar to narrow every card at once.
Digital I/O core
INPUT, OUTPUT, INPUT_PULLUP, OUTPUT_OPENDRAINHIGH or LOWAnalog I/O core
AR_DEFAULT, AR_INTERNAL, AR_INTERNAL_1_5V, AR_EXTERNALAdvanced I/O core
MSBFIRST or LSBFIRSTTime core
unsigned long ms since reset, wraps at ~49.7 daysdelay(); weak, override it freelyMath core
atan2 keeps the quadrantRandom core
analogRead() or micros()Bits and bytes core
Interrupts core
RISING, FALLING, CHANGE, LOWSerial & Stream HardwareSerial
DEC, HEX, OCT, BIN, or decimal places for floatsString class Arduino
Every String operation touches the heap. In a sketch meant to run for months, build output with snprintf() into a fixed buffer instead.
EEPROM 8 kB data flash
EERefData flash is rated for a finite number of erase cycles. Never call write() unconditionally inside loop().
Bundled libraries
These ship inside the board package — no Library Manager needed. Everything else installs as usual.
| Library | Include | What it gives you |
|---|---|---|
| Arduino_LED_Matrix | "Arduino_LED_Matrix.h" | the 12×8 display, frames, sequences, gallery |
| WiFiS3 | "WiFiS3.h" | Wi-Fi station/AP, TCP, TLS, UDP, NTP time |
| Arduino_CAN | <Arduino_CAN.h> | CAN 2.0A/B controller on D10/D13 |
| RTC | "RTC.h" | real-time clock, alarms, periodic callbacks |
| EEPROM | <EEPROM.h> | 8 kB emulated EEPROM, AVR-compatible API |
| Preferences | <Preferences.h> | typed key/value store over the same flash |
| WDT | <WDT.h> | watchdog timer |
| OPAMP | <OPAMP.h> | on-chip operational amplifier on A1–A3 |
| AnalogWave | "analogWave.h" | DMA-fed waveform generator on the DAC |
| SPI | <SPI.h> | SPI0 on D10–D13 |
| Wire | <Wire.h> | both I2C buses, controller and peripheral |
| HID | <HID.h> | native-USB HID plumbing; remaps Serial to SerialUSB |
| SoftwareSerial | <SoftwareSerial.h> | bit-banged extra serial port |
| I2S | <I2S.h> | digital audio interface |
| OTAUpdate | <OTAUpdate.h> | download and apply a sketch over Wi-Fi |
| SSLClient | <SSLClient.h> | TLS layer over any Client |
| Ethernet · lwIpWrapper | <Ethernet.h> | wired networking stack (shield / Portenta family) |
| Storage · BlockDevices | <Storage.h> | block devices behind the filesystems |
| FATFilesystem · LittleFilesystem | <FATFileSystem.h> | FAT and LittleFS |
| UsbMsd · UsbHostMsd | <UsbMsd.h> | expose or mount USB mass storage |
| SDU · SFU | <SDU.h> | sketch update from SD card / flash |
| KVStore | <KVStore.h> | key/value backend used by Preferences |
| Arduino_FreeRTOS | <Arduino_FreeRTOS.h> | preemptive multitasking kernel |
| ESPhost | — | low-level ESP32 host protocol used by WiFiS3 |
Build & flash
The IDE hides all of this, but knowing the identifiers makes CI, scripting and rescue work straightforward.
Identifiers
arduino:renesas_uno:unor4wifiUNOWIFIR4arduino (ArduinoCore-renesas), FSP from Renesasarm-none-eabi-g++, -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hardbossac, SAM-BA protocol, 1200 bps touch to resetR7FA4M1AB.cfg, SVD R7FA4M1AB.svd — needs an external SWD probearduino-cli core install arduino:renesas_uno arduino-cli compile -b arduino:renesas_uno:unor4wifi sketch/ arduino-cli upload -b arduino:renesas_uno:unor4wifi -p /dev/cu.usbmodem1101 sketch/ arduino-cli monitor -p /dev/cu.usbmodem1101 -c baudrate=115200
Reset, bootloader, recovery
espflash per Arduino's help-centre articleESP_DOWNLOAD to GND, then resetComing from the UNO R3
sizeof and printed widths all moveavailable() insteadFspTimer or PwmOutattachInterrupt() and FSP callbacks<avr/pgmspace.h> is shimmed awayTraps
The things that cost an evening. Every one of these is documented somewhere — just not where you looked.
Contradictions in the official docs
Wire1Wiring traps
Software traps
SerialUSB, and including <HID.h> silently redefines Serial to itFspTimer later reports nothing free, this is usually whySerial.print(), no delay()Arduino_LED_Matrix.h or text methods vanishupdate(), or write on a state change onlynow - last >= n), never with > on absolute values