4. Embedded Programing¶
The Microcontroller — ESP32-S3 Datasheet¶
Before writing any code it’s worth spending time with the datasheet — the technical document that tells you exactly what the board can do and which pins do what. Think of it as the owner’s manual for the chip.
The ESP32-S3 has up to 45 GPIO pins (General Purpose Input/Output). Most of them can be reassigned to different functions through software, which makes the board very flexible. Here are the main input capabilities worth knowing about:
-
Analog Input (ADC) — GPIO 1 to 10 The ADC input channels have 12-bit resolution, meaning readings range from 0 to 4095, where 0 corresponds to 0V and 4095 to 3.3V. This is what you’d use to read analog sensors — light sensors, temperature sensors, potentiometers, anything that gives a variable voltage rather than a simple on/off signal.
-
Touch Sensing — GPIO 1 to 14 The ESP32-S3 has 14 internal capacitive touch GPIOs. These can sense variations in anything that holds an electrical charge, like the human skin — detecting changes induced when touching the GPIO with a finger.This can be used for my capacitive touch design later.
-
Digital Input — all GPIOs Any GPIO can be configured as a simple digital input — reading either HIGH or LOW. This is what I used for the button on GPIO 0.
-
Communication Inputs (I2C, UART, SPI) These are protocols for talking to external sensors and devices — temperature modules, displays, accelerometers etc.
-
A note on pins to avoid: GPIO 26–32 are usually used for SPI flash and PSRAM and not recommended for other uses. GPIO 19 and 20 are used by USB-JTAG by default.
Programming the Board — LED and Button¶
The Goal¶
I wanted to program the ESP32-S3 so that pressing the built-in boot button causes the built-in LED to start flashing, while simultaneously sending the word ‘awake’ to the serial monitor with every flash.
Setup¶
The first step is defining the basic elements. The Boot Button is assigned to pin 0 — this is just a nickname we give it in code using #define, so we don’t have to remember the number later. The LED doesn’t need a manual pin definition because Arduino already has a LED_BUILTIN command that cross-references the board’s information and finds the right pin automatically.
A few other things get defined in setup: * bool ledBlinkingActive — a true/false variable that tracks whether blinking is currently on or off unsigned long lastBlinkTime — stores a timestamp so the code can measure time between blinks in milliseconds * LED_BUILTIN is set as OUTPUT — the controller will send voltage out to the LED * BUTTON_PIN is set as INPUT_PULLUP — the pin reads incoming signal. The PULLUP part means the pin is internally held at HIGH (3.3V) by default. When the button is pressed it connects to ground, pulling the reading LOW. This is how the code detects a press — and it means no external resistor is needed. * Serial.begin(115200) activates the connection between the board and the serial monitor through USB, at a communication speed of 115200 baud.
The Logic¶
The code runs a constant loop checking two things: whether the button has been pressed, and whether it’s time for the next blink.
- Button detection: The code reads the voltage on pin 0. When the button is pressed, it pulls the pin LOW. But because buttons are mechanical and can give false readings (a brief bounce on contact), the code checks twice — reading the pin, waiting 50 milliseconds, then reading again. If both readings match, it’s confirmed as a real press. At that point the code toggles ledBlinkingActive — flipping it from false to true or vice versa. That single toggle is what turns blinking on or off.
- Blink timing: Rather than using a simple delay() (which would freeze the whole program while waiting), the code tracks time by comparing the current time millis() against lastBlinkTime. When the difference reaches 1500 milliseconds, it fires the LED, prints ‘awake’ to the serial monitor, and resets the timestamp. This way the board stays responsive to button presses even while blinking.
#define BUTTON_PIN 0
bool ledBlinkingActive = false;
unsigned long lastBlinkTime = 0;
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
Serial.println("System Ready - Press button to start/stop blinking");
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) {
delay(50);
if (digitalRead(BUTTON_PIN) == LOW) {
ledBlinkingActive = !ledBlinkingActive;
if (ledBlinkingActive) {
lastBlinkTime = millis();
Serial.println("Blinking started - will send 'awake' with each blink");
} else {
digitalWrite(LED_BUILTIN, LOW);
Serial.println("Blinking stopped");
}
while (digitalRead(BUTTON_PIN) == LOW) {
delay(10);
}
}
}
if (ledBlinkingActive) {
if (millis() - lastBlinkTime >= 1500) {
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("awake");
delay(100);
digitalWrite(LED_BUILTIN, LOW);
lastBlinkTime = millis();
}
}
}
Troubleshooting — Serial Monitor Issue¶
I tried to upgrade the code so that ‘awake’ would print to the serial monitor every time the LED flashed. The code uploaded correctly — I could verify this by the LED blinking as expected — but nothing appeared on the serial monitor.
Here’s how I diagnosed it:
Checked the baud rate — made sure the rate in the code (115200) matched the setting in the serial monitor. It did. Isolated the problem — ran other simple codes that uploaded and ran correctly on the board. This pointed to the serial monitor connection as the issue, not the board itself. Identified the likely cause — with help from AI, the issue appeared to be the serial monitor disconnecting before the code had time to run, putting the board in a repeated reset loop. The fix was adding a longer startup delay, giving the serial monitor time to establish its connection before the code begins sending data.
The group assignment of this week is here
This week’s group assignment walked me through the full workflow for programming the ATtiny — from installing the megaTinyCore board package in Arduino IDE to selecting the correct board, port, and programmer. The key practical lesson was that uploading to the ATtiny requires using Sketch > Upload Using Programmer, and that small configuration issues like a board manager timeout can be fixed with a simple edit to the Arduino CLI settings file.