Detailed content
🧰 Arduino IDE 2.x and the esp32 3.x package
A Arduino IDE 2.x is the program where you write code, compile it, and send it to the board. By itself, it only supports Arduino boards; for ESP32, you install the Espressif "esp32" package, which is now in the 3.xgeneration. This version changed several functions, which is why many 2022 or 2023 tutorials produce compilation errors: they were written for API 2.x.
🌱 New here?
IDE (integrated development environment) is the editor with buttons to compile and upload. Compiling means translating your C++ text into the chip's machine language. A board package (core) is the set of tools and libraries that teaches the IDE how to communicate with a family of chips. API is the list of functions you can call, such as digitalWrite(); when it changes, old code may stop compiling.
Step-by-step installation
Download IDE 2.x
At arduino.cc/en/software, choose your system (Windows, macOS, or Linux AppImage). Install and open it.
Open Boards Manager
Click the board icon in the left sidebar (or Tools → Board → Boards Manager). Type esp32.
Install "esp32 by Espressif Systems" 3.x
Choose the latest version 3 and click Install. If the package doesn't appear, go to File → Preferences and paste this into "Additional URLs": https://espressif.github.io/arduino-esp32/package_esp32_index.json
Check the version
In Boards Manager, the package should show "INSTALLED" with a number starting with 3. This is the number to include when you ask for help on a forum.
How to read this map: On the left, the old path (dashed gray) went through a numbered channel that you created and then connected to the pin. On the right, the current path: one call sets up the pin, and the write uses the pin itself. If a tutorial asks you to choose "channel 0" for PWM, it's using API 2.x; translate it into the two green functions.
✓ Signs of a current tutorial
- ✓Uses ledcAttach(pin, freq, bits) e ledcWrite(pin, value).
- ✓Mentions "arduino-esp32 3.x" or ESP-IDF 5.
- ✓Uses a servo with the ESP32Servo library, not the Uno's Servo.h.
✗ Signs of an old tutorial
- ✗PWM functions with a channel number separate from the pin.
- ✗Tells you to "go back to package version 2.0.x" to compile.
- ✗Claims that the ESP32 doesn't have analogWrite (the current package does).
Why learn this
Knowing the package version saves you from the worst early frustration: copying code "that worked" and seeing ten red errors. With the rule "separate channel = old API," you can adapt any tutorial in a minute.
Key concepts
🔌 Board, port, and first upload
To upload a program, the IDE needs two pieces of information: which board (so it compiles for the right chip) and which port (where to send it). For a standard DevKit (WROOM-32), select ESP32 Dev Module; for boards with ESP32-S3, select ESP32S3 Dev Module or your board’s exact name. The port appears when you plug in the USB cable.
🌱 New here?
A port is the cable’s “address” on the computer: COM3, COM4 on Windows; /dev/ttyUSB0 or /dev/ttyACM0 on Linux; /dev/cu.usbserial… on macOS. Many boards have a USB-to-serial converter chip (CP2102 or CH340) that may need a driver on Windows. The BOOT button puts the ESP32 in program-receiving mode; the EN (or RST) restarts the board.
How to read this map: The green row shows the program’s path from left to right. Under each step is the failure that most often occurs there. When an upload fails, read the IDE’s last message and find the box you’re in: that tells you what to try.
Goal: upload to the real board the same LED blink you ran in Wokwi in module 1.1 (the external LED on GPIO 2 with 220 Ω, or just the board’s blue LED, if it has one).
// First upload to the real board (ESP32, esp32 3.x package)
const int PINO_LED = 2;
void setup() {
Serial.begin(115200);
pinMode(PINO_LED, OUTPUT);
Serial.println("Upload OK: o ESP32 esta rodando seu programa");
}
void loop() {
digitalWrite(PINO_LED, HIGH);
delay(500);
digitalWrite(PINO_LED, LOW);
delay(500);
}
How to check
- ✓The IDE finishes with "Hard resetting via RTS pin..." and the LED starts blinking.
- ✓The Serial Monitor (115200 baud) shows "Upload OK..."; if it doesn’t, press EN to restart.
- ✗On Linux, if you get "Permission denied" on the port, run sudo usermod -aG dialout $USER, then log out and back into your session.
✓ When it gets stuck, try these in order
- ✓Switch to a known data cable.
- ✓Check whether the port disappears and reappears when you unplug the cable.
- ✓Hold BOOT when "Connecting..." appears, and release it when you see the percentage.
✗ What won’t help
- ✗Reinstalling the entire IDE before testing the cable.
- ✗Leaving another program’s Serial Monitor using the same port.
- ✗Connecting something to GPIO 0 or 12 that forces a level at startup, then blaming the IDE.
Why learn this
The first upload is where most people give up, almost always because of the cable or port. With a map of the four steps, the error becomes predictable and the fix takes minutes.
Key concepts
🧱 setup, loop, variables, and functions
Every sketch has two required functions. setup() runs once when the board starts: this is where you configure pins and Serial. loop() runs again as soon as it finishes, forever: it’s the “thinking” part of the sense-think-act cycle. Inside them, you use variables to store values, if to make decisions, for to repeat, and your own functions to give each part of the behavior a name.
How to read this map: From left to right is the order in time. The dashed arrow shows that the loop() never ends. On the right, the cyan boxes are functions you write and loop calls: loop stays short, and each function does just one thing, with a name that explains it.
| Type | Stores | Robot example |
|---|---|---|
| int | whole number | pin, ADC reading |
| float | decimal number | distance in cm, voltage |
| bool | true or false | obstacle ahead? |
| unsigned long | large, unsigned integer | time in ms with millis() |
| const | value that doesn’t change | pin number, limits |
Goal: a one-LED traffic light, written with functions and a for, so you can see how readable loop can be. Run it in Wokwi with an LED on GPIO 2.
// Variables, if, for, and functions (ESP32, esp32 3.x package)
const int PINO_LED = 2;
int ciclo = 0; // global variable: available throughout the program
void piscar(int vezes, int intervaloMs) {
for (int i = 0; i < vezes; i++) {
digitalWrite(PINO_LED, HIGH); delay(intervaloMs);
digitalWrite(PINO_LED, LOW); delay(intervaloMs);
}
}
bool cicloPar(int n) { // function that returns a value
return (n % 2 == 0);
}
void setup() {
Serial.begin(115200);
pinMode(PINO_LED, OUTPUT);
}
void loop() {
ciclo++;
if (cicloPar(ciclo)) {
Serial.println("ciclo par: 2 piscadas lentas");
piscar(2, 400);
} else {
Serial.println("ciclo impar: 4 piscadas rapidas");
piscar(4, 120);
}
delay(1000);
}
How to check: The LED alternates between 4 fast blinks and 2 slow blinks, with a 1 s pause between groups; the Serial Monitor alternates between displaying "ciclo impar..." and "ciclo par...".
Why learn this
The rover in module 1.6 will have a loop with just a few lines calling lerDistancia(), decidir() e mover(). Organizing code this way lets you adjust one part without breaking the others.
Key concepts
🔍 Serial Monitor for debugging
A robot has no screen. The Serial Monitor is your window into it: the program writes using Serial.print() and you read it on the computer. The Serial Plotter, in the same Tools menu, displays numbers as a live graph, making noise and trends visible at a glance. Debugging is exactly that: printing what the program is thinking until you find where it’s thinking wrong.
🌱 New here?
Baud rate is the speed of serial communication, in bits per second. The number in Serial.begin(115200) must match the one selected in the Serial Monitor’s corner; if they differ, you’ll see meaningless symbols. print writes and continues on the same line; println writes and moves to the next line.
Goal: generate two curves (a wave and a ramp) in the format the Serial Plotter understands, name:value separated by commas, and a status message in the Serial Monitor. It works in Wokwi without connecting anything.
// Serial Monitor and Serial Plotter (ESP32, esp32 3.x package)
unsigned long amostra = 0;
void setup() {
Serial.begin(115200); // same value as in the Monitor/Plotter corner
delay(500);
Serial.println("Depuracao iniciada");
}
void loop() {
float onda = 50.0 * sin(amostra * 0.1); // moves up and down between -50 and 50
int rampa = amostra % 100; // counts up from 0 to 99 and starts over
// Plotter format: name:value,name:value and newline
Serial.print("onda:"); Serial.print(onda);
Serial.print(",rampa:"); Serial.println(rampa);
amostra++;
delay(50); // 20 points per second
}
How to check
- ✓In Serial Monitor: "Debugging started" followed by lines like "wave:29.55,ramp:6".
- ✓Close the Monitor and open Tools → Serial Plotter: a sine wave and a sawtooth, with the legends "wave" and "ramp".
- ✗Strange symbols? The Monitor baud rate is not set to 115200.
💡 Debugging that helps
Print the name and value ("dist=23.4", not just "23.4") and a message in each if branch so you know which path the code took. Avoid thousands of lines per second without a delay (the window will freeze), and remember that the Monitor and Plotter cannot use the port at the same time.
Why learn this
When the robot does something unexpected, the serial output is how you’ll find out whether the sensor read incorrectly, the decision was wrong, or the motor didn’t respond. In Track 2, this same habit becomes Wi-Fi telemetry.
Key concepts
🔘 Inputs: button and analog reading
So far, the board has only "spoken." Now it listens. A button with INPUT_PULLUP (covered in module 1.2) is read with digitalRead(); a potentiometer (a resistor with a knob) is read with analogRead(), which returns a value from 0 to 4095. Mechanical buttons have one catch: the contact "bounces" for a few milliseconds, and the program may count one press several times.
🌱 New here?
Debouncing means ignoring changes that last less than a minimum amount of time (about 30 ms), so only real presses are counted. millis() returns how many milliseconds have passed since the board started; comparing two values of millis() measures intervals without stopping the program. The potentiometer has three legs: the outer ones connect to 3.3 V and GND, and the middle one provides the variable voltage.
How to read this map: On the left, the button connects GPIO 4 to GND when pressed (no external resistor: the pull-up is inside the chip). On the right, the potentiometer gets 3.3 V (red) and GND (cyan) on its outer legs; the arrow is the middle wiper, which connects to GPIO 34. Below, the LED on GPIO 2 shows the result. Never connect the potentiometer to 5 V: GPIO 34 would receive more than 3.3 V at the end of its range.
Goal: Count button presses with debouncing (each press toggles the LED) and show the potentiometer position as a percentage. Build it like the figure in Wokwi ("Pushbutton", "Potentiometer", "LED" and "Resistor").
// Button with debouncing + potentiometer (ESP32, esp32 3.x package)
const int PINO_BOTAO = 4; // button to GND, internal pull-up
const int PINO_POT = 34; // ADC1, input only
const int PINO_LED = 2;
const unsigned long DEBOUNCE_MS = 30;
int estadoEstavel = HIGH; // released
int ultimaLeitura = HIGH;
unsigned long mudouEm = 0;
int apertos = 0;
bool ledLigado = false;
unsigned long ultimoPrint = 0;
void setup() {
Serial.begin(115200);
pinMode(PINO_BOTAO, INPUT_PULLUP);
pinMode(PINO_LED, OUTPUT);
Serial.println("Press the button and turn the potentiometer");
}
void loop() {
int leitura = digitalRead(PINO_BOTAO);
if (leitura != ultimaLeitura) { mudouEm = millis(); ultimaLeitura = leitura; }
if (millis() - mudouEm > DEBOUNCE_MS && leitura != estadoEstavel) {
estadoEstavel = leitura;
if (estadoEstavel == LOW) { // just pressed
apertos++;
ledLigado = !ledLigado;
digitalWrite(PINO_LED, ledLigado ? HIGH : LOW);
Serial.print("Press number "); Serial.println(apertos);
}
}
if (millis() - ultimoPrint >= 500) { // potentiometer every half second, without delay
ultimoPrint = millis();
int bruto = analogRead(PINO_POT); // 0 a 4095
int pct = map(bruto, 0, 4095, 0, 100);
Serial.print("pot="); Serial.print(bruto);
Serial.print(" ("); Serial.print(pct); Serial.println("%)");
}
}
How to check
- ✓Each press toggles the LED and adds 1 to "Press number N"—never 2 or 3 at once.
- ✓Turning the potentiometer from end to end makes "pot=" go from close to 0 (0%) to 4095 (100%).
- ✗Counting presses by itself? Check that the button is in different breadboard columns and that the pin is 4.
Why learn this
This is the same reading you’ll use with real sensors in module 1.4: digital for the obstacle IR sensor, analog for the line sensor. And the trick of using millis() instead of delay() returns in a big way in the last sensor topic.
Key concepts
🎚️ PWM in API 3.x: brightness and sound
A digital pin can only output 0 or 3.3 V. To make an LED glow at half brightness, the trick is to switch it on and off very quickly: if it stays on half the time, your eye sees half the brightness. This is PWM (pulse-width modulation). On the ESP32, the LEDC peripheral generates PWM, and in API 3.x you only need two functions: ledcAttach(pin, frequency, bits) e ledcWrite(pin, value). For sound, ledcWriteTone(pin, frequency) changes the frequency and keeps the pulse at half duty.
🌱 New here?
Duty cycle is the percentage of time the signal stays on. Frequency is how many times per second the cycle repeats (Hz). Resolution is how many steps you divide the cycle into: with 8 bits, from 0 to 255; with 10 bits, from 0 to 1023. A passive buzzer only makes a sound when it receives a frequency; an active buzzer beeps by itself when switched on.
How to read this map: All three lines have the same rhythm (frequency); only the amount of time each pulse stays high changes. On the right, the brightness your eye perceives and the value of ledcWrite in 8 bits (255 = 100%). In module 1.5, the same number will represent motor speed.
Goal: The potentiometer from topic 5 controls the brightness of the LED on GPIO 2, and the button plays a short scale on a passive buzzer connected to GPIO 25. In Wokwi, use the "Buzzer" part (it is passive).
// PWM in API 3.x: potentiometer-controlled brightness and sound on the buzzer (ESP32)
const int PINO_LED = 2;
const int PINO_BUZZER = 25;
const int PINO_POT = 34;
const int PINO_BOTAO = 4;
const int NOTAS[] = {262, 294, 330, 349, 392}; // C, D, E, F, G (Hz)
void tocarEscala() {
for (int i = 0; i < 5; i++) {
ledcWriteTone(PINO_BUZZER, NOTAS[i]); // changes the frequency, 50% duty cycle
delay(180);
}
ledcWrite(PINO_BUZZER, 0); // silence
}
void setup() {
Serial.begin(115200);
pinMode(PINO_BOTAO, INPUT_PULLUP);
ledcAttach(PINO_LED, 5000, 8); // LED: 5 kHz, 8 bits (0 to 255)
// channels 0-1, 2-3, 4-5, 6-7 share a timer, and ledcWriteTone changes the
// frequency of the entire timer: the buzzer goes on channel 2, away from the LED
ledcAttachChannel(PINO_BUZZER, 2000, 10, 2);
ledcWrite(PINO_BUZZER, 0);
Serial.println("PWM ready: turn the potentiometer, press the button");
}
void loop() {
int bruto = analogRead(PINO_POT); // 0 a 4095
int brilho = map(bruto, 0, 4095, 0, 255); // 0 a 255
ledcWrite(PINO_LED, brilho); // receives the PIN
if (digitalRead(PINO_BOTAO) == LOW) {
Serial.println("Playing scale");
tocarEscala();
while (digitalRead(PINO_BOTAO) == LOW) delay(10); // wait for release
}
Serial.print("brilho:"); Serial.println(brilho);
delay(50);
}
How to check
- ✓Turning the potentiometer smoothly changes the brightness without flickering; the Serial Plotter shows "brightness" from 0 to 255.
- ✓Pressing the button plays five ascending notes, and the Monitor displays "Playing scale".
- ✗Compilation error in ledcAttach? The installed package is 2.x: update to 3.x (topic 1).
💡 The right frequency for each use
LED: above 1 kHz (5 kHz is a good default) to avoid flickering on camera. DC motor: between 1 and 20 kHz, depending on the driver. Servo: 50 Hz, but use the ESP32Servo library in module 1.5. The higher the frequency, the lower the maximum resolution available; 8 to 10 bits is plenty for robots.
Why learn this
PWM is how the ESP32 controls motor speed through the H-bridge in module 1.5 and servo angles. The rover’s horn, in the next steps of module 1.6, uses the same ledcWriteTone from this example.
Key concepts
✅ Module summary
To go further
- Install Arduino-ESP32 (Espressif) — Board Manager and official URL
- LEDC (PWM) in API 3.x — ledcAttach, ledcWrite, ledcWriteTone
- Migration guide 2.x → 3.0 — what changed and how to translate
- IDE 2 Serial Plotter — variable format and labels
- Arduino Language Reference — if, for, millis, map
Next module
1.4 · Sensors: the robot perceives. Measure distance with the HC-SR04, detect lines and obstacles with infrared, filter noise, use I2C, and read multiple sensors without blocking.