Week 4: Embedded Programming
Just like the previous weeks, we had to do a group assignment along with our individual assignments.
Group Assignment
- Demonstrate and compare the toolchains and development workflows for available embedded architectures.
Individual Assignment
- Browse through the datasheet for a microcontroller.
- Write and test a program for an embedded system using a microcontroller to interact (with input &/or output devices) and communicate (with wired or wireless connections).
- Extra credit: Assemble the system.
- Extra credit: Try different languages &/or development environments.
Just like the recent week, I’ve started my week by planning out my schedule:
This week was more about me trying to comprehend all the new words that were thrown at me, so this page might be longer than usual because of all attempts to trying to explain things. First up, let’s understand what an embedded system really is.
Group Assignment
For this week’s group assignment, we compared the toolchains and development workflows of the embedded architectures available in our lab. We evaluated 3 different boards, which included the XIAO ESP32 C3, Arduino Uno, and an ATtiny 44 baord. To understand the differences in setup and programming, we uploaded simple blink sketches to each board. We divided the tasks among our group so that each of us could test a specific board. I worked with the Arduino Uno, which we selected because it is one of the most widely used microcontroller boards. You can find the detailed documentation on our group's work here:
Week 4 Group Assignment - Embedded ProgrammingEmbedded System
A computer (has a processor, memory, and runs code) built into a device to perform one specific task, running a single program permanently stored in memory. Unlike a laptop, it has no keyboard or screen; it just runs. Examples include the microcontroller inside a washing machine or the chip in a traffic light.
Key distinctions:
- Internal vs. External: Internal lives inside (microwave); External is a separate unit (Raspberry Pi).
- Special vs. General Purpose: Special does one job (smoke detector); General can run many programs (laptop).
Binary
Binary is the numbering system that makes digital signals useful. Computers represent all information using only 1s and 0s. A single 1 or 0 is called a bit. Eight bits grouped together is called a byte.
| Bit Position | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | = Value |
|---|---|---|---|---|---|---|---|---|---|
| Power of 2 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | - |
| All 1s | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 255 |
| Number 10 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10 |
Digital vs Analog Signals
Digital Signal
A signal that can only be one of two values: high (1) or low (0), voltage present or no voltage. No "in between", no gradual change. It is either on or off. This is the language computers and microcontrollers naturally speak, everything inside a chip is binary. A button press, an LED state, a logic output — these are all digital signals.
Analog Signal
A continuous signal that can be any value within a range. It flows and varies smoothly with no steps or jumps. The physical world is entirely analog: temperature doesn't jump instantly from 20°C to 21°C, it rises gradually through every value in between. A microphone output, a temperature sensor voltage, a light sensor — all produce analog signals. On a microcontroller, analog signals are read through the ADC pins.
This is how I think about it, a digital signal can only ever be 0 or 1. Nothing in between.
An analog signal can be 0, 0.1, 0.5, 0.75, 1.3, 2.7 —
This image right here shows that analog signals vary continuously and smoothly across a range of values, like a wave that can rise and fall to any point. Digital signals exist only as discrete states — either 0 or 1, with nothing in between.
Browsing through the Datasheet
ESP32
The microcontroller I plan on using for my final project is the Xaio ESP32 C3.
Here is the link to the datasheet of ESP32 C3.
Here is the link to the datasheet of ESP32.
This is the pinout for the Xiao-ESP32-C3
At first, it was really hard for me to comprehend how to do all the connections to make a basic circuit since I was a beginner. I learned the basics by first learning what pins were and the different types.
Pins
If you look at a microcontroller chip, you'll see metal legs sticking out of it. Those are called pins. Each pin is a physical point where the chip connects to the outside world (to buttons, LEDs, sensors, other chips, whatever your circuit has).
Each pin can do two things:
- Input: detect whether voltage is present on that pin or not. This is how the chip reads a button press or a sensor signal.
- Output: send voltage out through that pin or not. This is how the chip turns an LED on or off.
In both cases the chip is working with a simple binary signal, voltage present = 1, no voltage = 0.
Pins are not all the same.
Not every pin on a microcontroller does the same job. Different pins have different roles. Common types are:
- Power pins: one pin takes in the supply voltage (VCC) to power the chip, another connects to ground (GND) to complete the circuit. Every chip needs these.
- I/O pins: the pins you actually program and interact with. These are the ones you connect to LEDs, buttons, sensors etc.
- Reset pin: pulling this pin low restarts the chip, as if you cut and restored power. The reset pin is a special pin that the chip watches constantly. As long as it stays high (normal voltage which is typically 3.3V or 5V) the chip runs normally executing your program in a loop. The moment that pin is pulled low (voltage drops to 0) the chip interprets that as a signal to restart and it stops whatever it was doing, clears its working state, and starts your program from the very beginning, exactly as if you had cut the power and turned it back on.
- Programming pins: dedicated pins used specifically to upload code to the chip (UPDI on).
Most I/O pins are multi-function.
A single physical pin can often serve multiple purposes depending on how you configure it in code. For example one pin might be usable as a basic digital I/O pin, or as part of the UART serial communication peripheral, or as a PWM output, or as an analog input but only one at a time. You choose what role each pin plays when you write your program.
One more thing to Note: Pin count matters
The number of pins a chip has determines how many things it can connect to and control at once. A small chip like the ATtiny412 has 8 pins total, subtract power and ground and you have very few left for actual I/O. A larger chip like the ATtiny3216 has 20 pins, giving you more to work with.
Ports
A port is just a group of those pins bundled together. Instead of the processor dealing with each pin individually, pins are organized into groups called ports, typically 8 pins per port on AVR chips. The processor controls an entire port through one register (a register I talked about earlier which is a storage location inside the chip). Writing a value to that register sets the state of all 8 pins at once.
ESP32
ESP32 Architecture and Processor Family
Architecture: Architecture refers to the design and structure of a processor — essentially the rules and blueprint that define how it processes instructions, handles memory, and communicates with other components.
Processor Family: A processor family is simply a group of processors that share the same core design and architecture but may differ in speed, number of cores, or features. For example, the Xtensa LX6 is the processor family used in the ESP32, while the RISC-V is the processor family used in the ESP32-C3.
The ESP32 is based on the Xtensa® LX6 processor architecture, developed by Cadence. It is a 32-bit dual-core processor, meaning it processes data in 32-bit chunks and has two cores that can run tasks simultaneously. Each core can run at up to 240 MHz, making it fast enough for demanding tasks like audio processing, wireless communication, and real-time sensor data.
The image below shows the ESP32 microcontroller's functional block diagram, taken directly from the official ESP32 datasheet.
ESP32 Functional Block Diagram
- 2 (or 1) × Xtensa® 32-bit LX6 Microprocessors: This is the actual CPU, the thinking part of the chip. The ESP32 can have two cores (processors) running simultaneously (dual core), meaning it can do two tasks at the same time. Each core is a 32 bit processor, which means it handles data in 32 bit chunks fast and capable.
- ROM: Read Only Memory permanently burned into the chip at the factory. It holds the bootloader which is the program that starts everything up on power on and cannot be modified.
- SRAM: Static RAM is the chip's working memory where variables and runtime data live while the program is running. It is fast but temporary and is lost when power is off.
- Bluetooth and Wifi: This is the ESP32's superpower, it has both Wi-Fi and Bluetooth built directly into the chip
- Bluetooth link controller + Bluetooth baseband: These two blocks work together to handle Bluetooth communication. The baseband handles the low level radio signal processing (turning bits into radio waves and back). The link controller manages the connection protocol (pairing, data packets, timing.)
- Wi-Fi MAC + Wi-Fi baseband: Allows the ESP32 to connect to wireless networks without any external module.
- RF Receive / RF Transmit: RF stands for Radio Frequency. These are the actual analog radio circuits, the hardware that physically sends and receives electromagnetic signals through the antenna.
- Clock Generator: Radio communication requires extremely precise timing. This generates the high-frequency clock signal that keeps all the wireless circuitry synchronized.
- Switch: This hardware switch toggles between transmit and receive modes. The antenna can't do both simultaneously, so it flips between them very rapidly.
- Peripherals: A rich set of communication and I/O interfaces; SPI, I2C, I2S, UART, ADC, DAC, PWM, Touch sensing, Ethernet, RMT, and SDIO, in general and brief, it allows the ESP32 to interface with virtually any external sensor, display, or component. In other words, these are all just different languages the ESP32 uses to talk to other components. Different sensors, displays, and modules were each designed to communicate in a specific way, so the ESP32 supports all of them.
- RTC and Power Management: The RTC (Real-Time Clock) keeps track of the current time even while the chip is asleep. It is also the low-power domain that stays alive during sleep mode, which is essential for battery-powered devices. A PMU (Power Management Unit) controls which parts of the chip stay powered during sleep, a ULP coprocessor handles simple background tasks while the main CPU is off, and Recovery Memory retains critical data so nothing is lost when the chip wakes back up.
Important Findings from the Datasheet
I used Claude AI to review the datasheet, asking it to summarize the key points and explain them in simple terms.
This was the prompt:
ESP32 Series — ESP32 Series Datasheet v5.2
- Chip: ESP32-D0WD-V3: The ESP32 is a dual-core Wi-Fi and Bluetooth chip built with low-power 40 nm technology, designed for a wide range of IoT applications.
- Clock Frequency: 80 MHz to 240 MHz: Clock frequency refers to the speed of the processor. The ESP32's clock is adjustable, allowing it to run slower to save power or faster when needed.
- Wireless: 2.4 GHz Wi-Fi + Bluetooth 4.2: The ESP32 has built-in Wi-Fi (802.11 b/g/n) and Bluetooth 4.2 (Classic + BLE), with a maximum Wi-Fi data rate of 150 Mbps — no external wireless module needed.
- Output Power: up to 20.5 dBm: This refers to the strength of the Wi-Fi signal transmitted from the antenna, allowing stable wireless connections even over longer distances.
- Memory: 448 KB ROM + 520 KB SRAM: ROM stores the factory bootloader permanently. SRAM is the working memory used while the program runs, and is lost when power is off.
- Peripherals: 34 GPIO, SPI, I2C, UART, ADC, DAC, PWM, Touch and more: Peripherals are the built-in hardware features of a microcontroller. The ESP32 supports a wide range of communication protocols and interfaces, allowing it to connect to virtually any sensor or component.
- Sleep Current: as low as 10 µA: Sleep current refers to power consumption in the chip's lowest power state. At just 10 µA in deep-sleep, the ESP32 is well suited for battery-powered applications.
- Power Supply: 3.3V: The ESP32 runs on 3.3V, which is standard for modern low-power microcontrollers.
- Security: AES, SHA, RSA, RNG: The ESP32 has dedicated hardware for encryption and security, enabling fast secure communication without slowing down the CPU.
- Operating System: FreeRTOS: FreeRTOS is a lightweight operating system that runs in the background, automatically managing tasks and system resources.
- Programming: Arduino IDE, MicroPython, ESP-IDF, PlatformIO: The ESP32 supports multiple development environments, making it accessible for both beginners and advanced users.
XIAO ESP32-C3
The image below shows the ESP32 C3 microcontroller's functional block diagram:
The ESP32-C3 is built around a RISC-V 32-bit single-core processor supported by Cache, SRAM, and ROM. Its built-in RF circuits handle 2.4 GHz Wi-Fi and Bluetooth LE wirelessly. The peripherals section covers all common interfaces including GPIO, SPI, I2C, UART, ADC, PWM, and timers. The security section includes hardware-level AES, SHA, RSA, Secure Boot, and Flash Encryption. Finally, the RTC section manages power with a PMU and a Brownout Detector that resets the chip if voltage drops too low.
Important Findings from the Datasheet:
XIAO ESP32C3 — Seeed Studio XIAO ESP32C3 Datasheet
- Chip: ESP32-C3 SoC: The XIAO ESP32C3 is powered by an ESP32-C3 SoC — a single-core 32-bit RISC-V processor. Unlike the original ESP32, the C3 uses the open-source RISC-V architecture, making it more modern and power-efficient.
- Clock Frequency: up to 160 MHz: Clock frequency refers to the speed of the processor. The XIAO ESP32C3 runs at up to 160 MHz, meaning it can execute instructions very quickly.
- Wireless: 2.4GHz Wi-Fi + Bluetooth 5 (BLE): The board has built-in Wi-Fi (802.11 b/g/n) and Bluetooth 5 (BLE), with an external antenna for stronger signal range in wireless applications.
- Peripherals: 11 Digital I/O, 4 Analog I/O: Peripherals are the built-in hardware features of a microcontroller. The XIAO ESP32C3 has 11 digital I/O pins (usable as PWM), 4 analog input pins (ADC), and supports UART, I2C, and SPI communication interfaces.
- Sleep Current: as low as 44 µA: Sleep current refers to power consumption in deep sleep mode. At just 44 µA, the XIAO ESP32C3 is well suited for battery-powered and wearable applications.
- Onboard Battery Charging: The board includes a built-in battery charging circuit, making it convenient for portable and wearable projects without needing external charging hardware.
- Power Supply: 3.3V: The XIAO ESP32C3 runs on 3.3V, which is standard for modern low-power microcontrollers.
- Programming Languages: Arduino IDE, MicroPython, ESP-IDF, PlatformIO: The board supports multiple popular development environments, making it accessible for both beginners and advanced users.
- Operating System: FreeRTOS: FreeRTOS is a lightweight real-time operating system that runs in the background, automatically managing tasks and system resources.
This is the pin layout, it is a top down view of the bare ESP32-C3 chip showing all 33 pins around its edges:
Programming a microcontroller
I will be using Wokwi to program an Arduino for this project. I chose it because it is free, browser-based, requires no installation, and allows me to build and test circuits virtually without needing any physical hardware.
First, I went to the software's official website and started a new project by selecting Arduino Uno under 'Start from Scratch'.
This opened a blank workspace set up specifically for the Arduino Uno, showing all its pins and default settings.
I could now begin adding components to the circuit. I decided to create a basic Arduino circuit with an LED and a push button.
First, I started by setting up the LED. I connected the shorter leg (the cathode) directly to one of the GND pins on the Arduino. Then, I took a resistor and set it to 0.22 K ohms and connected it from the longer leg of the LED to Digital Pin 13.
Next, I moved on to the push button. I placed it on the breadboard and connected one of its legs to Digital Pin 2. For the other side of the button, I ran a wire straight to a GND pin.
And that was it, the hardware part was done. Then for the code, I asked gemini to generate a code to make the LED blink and wait for one second before turning off when the button was pressed. And I prompted gemini to give explanations for the code as well.
This was the prompt I used:
This was the code it generated:
/* --- VARIABLE DEFINITION BLOCK ---
This section gives names to the physical pins so we don't
have to remember numbers throughout the code. */
const int ledPin = 13; // Set 'ledPin' to pin 13 (where the LED is connected)
const int buttonPin = 2; // Set 'buttonPin' to pin 2 (where the button is connected)
/* --- SETUP BLOCK ---
This runs exactly ONCE when the Arduino powers on or is reset.
It configures the pins for their specific jobs. */
void setup() {
// Configures pin 13 to send power OUT to the LED
pinMode(ledPin, OUTPUT);
// Configures pin 2 to listen for signals.
// INPUT_PULLUP activates an internal resistor that holds the pin
// at HIGH (5V) until the button connects it to Ground.
pinMode(buttonPin, INPUT_PULLUP);
}
/* --- MAIN LOOP BLOCK ---
This block runs over and over again (thousands of times per second)
as long as the Arduino has power. */
void loop() {
// digitalRead checks if Pin 2 is currently HIGH or LOW.
// We store that "state" in a temporary variable called 'buttonState'.
int buttonState = digitalRead(buttonPin);
/* --- LOGIC BLOCK: CHECK FOR PRESS ---
Because we used INPUT_PULLUP, a "LOW" reading means the button
is physically completing the circuit to Ground. */
if (buttonState == LOW) {
// Step 1: Send 5V to Pin 13 to light the LED
digitalWrite(ledPin, HIGH);
// Step 2: Pause the entire program for 1000 milliseconds (1 second)
// The LED stays ON during this pause.
delay(1000);
// Step 3: Stop sending power to Pin 13 to turn the LED OFF
digitalWrite(ledPin, LOW);
/* --- HOLDING BLOCK ---
This 'while' loop tells the Arduino: "Do nothing as long as the
button is still being held down." This prevents the LED from
blinking again until you let go and press it a second time. */
while (digitalRead(buttonPin) == LOW) {
delay(10); // A tiny 10ms delay to keep the processor stable
}
}
}
Explanation for the code:
The code is divided into four main parts. The Variable Definition Block assigns names to the physical pin numbers — ledPin for pin 13 (the LED) and buttonPin for pin 2 (the button) — so the code is easier to read. The Setup Block runs once on startup and configures each pin's role: pin 13 is set as an OUTPUT to power the LED, and pin 2 is set as INPUT_PULLUP, which uses the Arduino's internal resistor to keep the pin at HIGH by default until the button is pressed. The Main Loop Block runs continuously and checks the button's state — because of INPUT_PULLUP, a LOW reading means the button is pressed, which then triggers the LED to turn ON for 1 second before turning OFF. Finally, the Holding Block pauses the loop using a while statement for as long as the button is still held down, preventing the LED from blinking repeatedly until the button is fully released and pressed again.
This is the results:
The LED lit up when I pressed the button.
After building and testing the circuit in simulation, I decided to recreate it using real hardware. I opened the Arduino IDE and pasted the same code I had used in my simulation.
After that, I selected the correct board from the board menu. For my hardware setup, I used the Arduino Metro board, so I made sure it was chosen before uploading the program. While selecting the board in Arduino IDE, I chose Arduino Uno. After connecting my Metro board via USB, the device name appeared in the list, confirming that the board was successfully detected. Then, I uploaded everything by clicking on the arrow button.
Then, I pressed the push button to check if everything was working. It did! The LED turned on, stayed lit for a second, and then turned off.
Yay to my first circuit!╰(*°▽°*)╯
Project Development
For this week's project development, I wanted to apply what I learned about embedded programming to my final project. To do this, I created a simple demonstration of the menu structure I designed earlier. I started by building a basic simulation in Wokwi based on the draft menu layout I made last week.
In Wokwi, I set up the required components, including an ESP32-C3 microcontroller, LEDs, resistors, a breadboard, and an OLED display. The simulation is intentionally simple and cycles through the different menu screens to give a rough idea of how the user interface might look. This version is mainly for visualization, and I plan to improve it later with proper input handling and navigation logic.
This is the first simulation I made, which only had a few components:
This is the second video, which has more components, and I updated the code to cycle through different menu structure for the demonstration:
After completing the simulation, I attempted to recreate the same setup using real hardware. I initially used a bare Seeed Studio XIAO ESP32-C3 mounted on a breadboard, since there wasn't enough time to design and fabricate a custom board. I wired the circuit to match the simulation, but it did not work as expected. For demonstration purposes, I then switched to an Arduino Metro board, which allowed me to continue testing and showcasing the concept.
Adding the Status LED
To indicate when the system is "active," I added an LED to the circuit. Following these steps:
- Step 1: Connected the short leg (Cathode) of the LED to the GND pin.
- Step 2: Connected the long leg (Anode) to a 220 ohm resistor.
- Step 3: Connected the other end of the resistor to Digital Pin 3 on the Metro board.
- Step 4: Connected the push button to Digital Pin 2 and GND.
I had a very hard time programming the oled display. It remained stuck on "Hello" instead of cycling through different menu structure. It turns out, the code was too long for the oled display, so I used Claude AI to shorten the initial code and it worked!
Here is the video:
Reflection:
For my individual assignment, I focused on embedded programming and learned many key terms, including architecture and peripherals, especially related to the ESP32-C3 microcontroller, which I plan to use for my final project. Honestly, this week introduced a lot of new concepts and terminology, and it was overwhelming at first, but I gradually got a basic understanding of the most important terms. I explored ideas like analog and digital signals and their differences, reviewed the microcontroller's datasheet to understand its features, and also practiced using Wokwi to simulate circuits.