Week 4 : Embedded programming
Embedded programming is the process of writing software that runs on embedded system to control specific hardware functions.
Week 4 assignment could be categorized as follows:
-
Group assignment
- Comparison of development software and programming languages (e.g. available tools pre-deployment)
-
Individual assignment
- Analysis of a microcontroller(s)
- Programming and simulation of the microcontroller
Glossary of terms
General
- Analog : Continuous. Character of most real-world signals.
- Bit : Short for binary digit, smallest unit of data in computing and digital communication. 8 bits make 1 byte, capable of representing 256 values (from 00000000 to 11111111 in binary).
- Bootloader : A small program that initializes the load or rewriting of code from, for example, a serial port like USB, etc. Reset, on the other hand, turns off and turns on the microcontroller, and reruns the program that is already installed in it.
- Digital : Discrete. Character of microcontroller signals.
-
Integrated circuit (IC) : Also known as microchip or simply chip. A compact assembly of various electronic components and their interconnections.
-
Operating system (OS) : Software acting as an interface between users and computer hardware, managing resources like CPU and memory.

-
Operating System diagram (sourced from GeeksforGeeks — Operating System Tutorial).
-
Peripherals :
- Analog-to-digital converter (ADC) : Converts analog signals into digital values that can be processed by the microcontroller. Used for reading sensor data, etc.
- Digital-to-analog converter (DAC) : Converts digital values into analog signals. Used for generating audio signals, controlling analog devices, etc.
- General-purpose input/output (GPIO) : Pins that can be programmed to function as either input or output. Used for interfacing with sensors, actuators, etc. GPIO is the physical interface for all the functions in these sections.
- Pulse-width modulation (PWM) : A technique used to control the amount of power delivered to a device by varying the width of the pulses in a signal. Used for controlling brightness of LEDs, speed of motors, etc.
- Serial communication interfaces : Protocols for communication between microcontroller and other devices. Examples include UART, SPI, I2C, etc.
Classifications
-
Classifications of memory: Storage of data and instructions:

-
Classification of memory (sourced from GeeksforGeeks — Introduction of Microprocessor)
-
Primary memory : Directly accessible by CPU, fast but low memory capacity.
- Random access memory (RAM) : Computer’s main memory used for temporary storage of active programs and data. Data is not retained when power is turned off. Examples include static RAM (SRAM) and dynamic RAM (DRAM).
- Read-only memory (ROM) : Stores instructions permanently. Data is retained when power is turned off.
- Mask ROM (MROM) : Programmed at manufacture and so cannot be changed.
- Programmable ROM (PROM) : Can be programmed once by the user after manufacture.
- Erasable Programmable ROM (EPROM) : Can be erased and rewritten, but only by exposing the chip to ultraviolet (UV) light.
- Electrically Erasable ROM (EEPROM) : Like EPROM, but can be erased electrically. No UV light needed.
-
Secondary memory : External to the CPU. Slower but larger in capacity (e.g. HDD, SSD, USB drive, flash)
-
Classification based on memory architecture :
- Von Neumann : A single memory space for both data and instructions. Simpler design, but slower due to the need to fetch instructions and data sequentially.
- Harvard : Separate memory spaces for data and instructions. Faster as it allows simultaneous access, but more complex design.
-
Classification based on instruction set :
- Reduced Instruction Set Computer (RISC) : Simple instructions. Utilize more registers. A fixed number of clock cycles. The Fab Academy program focuses on this architecture. Examples include AVR, ARM, RISC-V, etc. More for microcontrollers, mobile devices, etc.
- Complex instruction Set Computer (CISC) : Complex instructions. Utilize fewer registers. Numerous clock cycles. Examples include Intel, AMD, etc. More for desktops and laptops.
Processing units (from small to big)
- Central processing unit (CPU) : Core component in a computing unit, responsible for processing and executing inputs.
-
Microprocessor unit (MPU) : Chip that contains CPU plus additional components. Performs arithmetic and logic operations

- Typical microprocessor structure (sourced from GeeksforGeeks — Introduction of Microprocessor)
- Arithmetic logic unit (ALU) : Performs all arithmetic operations
- Control unit (CU) : Directs the flow of data between the microprocessor's components and external devices
- Registers : Small, high-speed storage locations that hold data. Provides fast access to frequently used data and instructions
- Bus system : Provides communication pathway between components. Usually has data, address, and control bus
- Additional info
- Clock speed: Measured in Hertz. Refers to the speed of microprocessor's capability to execute instructions. Reminder: frequency is the inverse of time period (s)
- Word length : The number of bits the data bus could process at one time
- Cache : Something like register, but slower
- Typical microprocessor structure (sourced from GeeksforGeeks — Introduction of Microprocessor)
-
Microcontroller (MCU): MPU, memory (ROMs and RAMs), and some peripherals including input and output interfaces (I/O), A/D or D/A converters, etc. No OS, which means the user needs to code and manage hardware resources on their own. Examples include ESP32.

-
Structure of microcontroller (sourced from GeeksforGeeks — Microcontroller and its Types)
-
Development boards : A physical platform built around the MCU. For example, addition of USB connector, voltage regulators, etc.
- Single board computers (SBC) : MCU + integrated standard computer interfaces such as USB, HDMI, Ethernet. Has OS. Examples include Raspberry Pi and NVIDIA Jetson.
Embedded language
- Low-level language (languages that are close to the hardware)
- Assembly
- High-level language (languages that are closer to human language)
- C/C++
- Rust
- Python
- Architecture: The overall structure and components of a software environment, including how code, libraries, etc are organized
- Compiler : A compiler is a program that translates source code written in a high-level programming language into machine code that a computer or microcontroller can execute. A compiler can only translate programs written in the specific language it is designed for. Therefore, each high-level programming language requires its own dedicated compiler for proper conversion.
- Interpreter : program that executes code written in a language and translates it into instructions that CPU can understand. Python needs this. Same goal as compiler, but it executes the program line by line, which could make debugging and error detection easier, although the program may run more slowly.
- Toolchains: The set of tools (compiler, debugger, uploader, etc.) used to transform source code into a running program.
- Workflow: The sequence of steps a developer follows to write, build, test, and deploy code.
References : Seeed studio, GeeksforGeeks, Hackernoon
Comparison of development software and programming languages
The following two common languages and it's respective development softwares were compared for this test :
-
Python:
- Thony IDE
-
C/C++ (Arduino) :
- Arduino IDE
- VSCode IDE + PlatformIO
Connection between Micropython and Python
MicroPython is a lightweight version of Python designed for microcontrollers and embedded systems. Unlike standard Python, MicroPython includes only essential libraries, making it efficient for devices with limited memory and processing power, such as the ESP32, Raspberry Pi Pico, or Arduino boards.
Source: ChatGPT by OpenAI, February 2026
Although the user has some experience with embedded programming, some terms—such as toolchain, workflow and architecture—may be unfamiliar. This table was prepared using ChatGPT as an initial reference. From here, details could then be further explored.
| IDE | Language | Toolchain (process of building & upload) | Pre-setup |
|---|---|---|---|
| Thonny | Python / MicroPython | Sends user's code directly to the MCU over USB. No compile step—run the script (interpreter rather than compiler) and it runs on the MCU. The MCU must already have MicroPython firmware on it. | User must flash (copy) the correct MicroPython firmware onto each MCU type once. Different MCUs need different firmware files. |
| Arduino IDE | C++ | Turns user's code into a program that a machine could read (compiler), then sends that program to the MCU over USB. Uses a built-in compiler; user picks the MCU/board. | Install support/libraries for MCU type once via Board Manager (e.g. “ESP32 board”). After that, that board type works in any sketch. |
| VS Code + PlatformIO | C++ (MicroPython not officially supported) | Same idea as Arduino IDE: compile on PC, then upload via USB. Downloads the right compiler and tools for the MCU on first build. Each project has its own settings and libraries (no shared “global” libraries). | Create a project, choose the MCU in the project config file; the rest downloads automatically on first build. |
Source: ChatGPT by OpenAI, February 2026
!!! info For micropython to work, user need to flash the right firmware onto each type of microcontroller first; after that, most MCUs can operate with it.
The XIAO ESP32C3 is used as the base for the test (more details on this MCU and it's capability could be seen further below at the "analysis of microcontroller" section). All development softwares would be installed first, then the features of the softwares and the respective languages will be compared.
The installation steps for the combination of micropython x Thonny IDE x XIAO ESP32C3 could be found in SEEED Studio's wiki. While perhaps Thonny could be linked to the miniconda/anaconda environment (see week 1) because it's python based, the standard manual instructions are followed simply for the test.
esptool not detected on Windows?
This means esptool is not installed as a global command or is not added to the user's system PATH.
Instead of running:
esptool --chip esp32c3 --port COM4 write_flash -z 0x0 firmware.bin
Use:
python -m esptool --chip esp32c3 --port COM4 write_flash -z 0x0 firmware.bin
This executes esptool via Python directly and works even if it is not globally available.
Important:
COM4 was the port used during the test. Change if necessary.
The firmware file shown in this documentation (e.g. ESP32_GENERIC_C3-20251209-v1.27.0.bin)
was the latest version available at the time of writing.
Always replace the .bin filename with the firmware version that would be used.
The next is the Arduino IDE x XIAO ESP32C3 combination and installation steps could be found also in SEEED Studio's wiki. In general, the process is straightforward, and not much needs to be done beyond installing the board support package, selecting the correct board in the IDE, and connecting the XIAO ESP32C3 via USB.
The last combination is VS Code + PlatformIO x XIAO ESP32C3 and thee installation step could be found at the Random nerd tutorial. The XIAO ESP32C3 is also available as a board.

And so the table here summarizes the test purely based on the software perspective :
| IDE | Architecture | Compilation / Upload Speed | Pre-setup (workflow) |
|---|---|---|---|
| VSCode + PlatformIO | Modern, highly configurable | Fast | Install PlatformIO extension; board support handled automatically |
| Thonny | Simple, beginner-friendly | Fast | Manual MicroPython .bin download and flashing required once |
| Arduino IDE | Simple, beginner-friendly | Slower | Install board package via Board Manager |
From a pure software perspective and perhaps could also be seen from the videos above, VSCode + PlatformIO provides a strong overall experience, at least in terms of the architecture, compilation/upload speed and pre-setup compared to the other options. Furthermore, since this software is also used by the user for documentation, it provides an all-in-one solution that streamlines the general workflow.
Now shifting to the language - the code based on micropython and C/C++ could be seen at the admonition for interest. However
Micropython
from machine import Pin, ADC, PWM
import time
# ----- Pins (change these if you use different pins) -----
servo_pin = 10 # Servo signal wire (XIAO D10)
ldr_north = 2 # North LDR (XIAO A0)
ldr_south = 3 # South LDR (XIAO A1)
# ----- Settings to tweak -----
servo_pos = 90 # Start in the middle (0 = left, 180 = right)
tolerance = 150 # Ignore small differences (stops jitter)
pause_ms = 50 # How often we check the sensors
step_min = 1 # Smallest move (degrees) when difference is just above tolerance
step_max = 5 # Biggest move (degrees) when difference is large
# ----- Setup: connect the light sensors -----
#
# Where to find this: MicroPython docs → machine.ADC
# https://docs.micropython.org/en/latest/library/machine.ADC.html
# (ESP32 section lists atten/width and valid constants.)
#
sensor_north = ADC(Pin(ldr_north))
sensor_south = ADC(Pin(ldr_south))
sensor_north.atten(ADC.ATTN_11DB)
sensor_north.width(ADC.WIDTH_12BIT)
sensor_south.atten(ADC.ATTN_11DB)
sensor_south.width(ADC.WIDTH_12BIT)
# ----- Setup: connect the servo -----
#
# NOTE — Why freq=50?
# -------------------
# Standard hobby servos (SG90, MG90S, etc.) expect a PWM signal at 50 Hz:
# that means one pulse every 1/50 second = 20 ms period. The position is
# set by the pulse width: about 1 ms = 0°, 1.5 ms = 90°, 2 ms = 180°.
#
# How to know:
# • Servo datasheet or product page: look for "PWM period" or "20 ms" or "50 Hz".
# • Arduino Servo library uses 50 Hz by default (you can check the source).
# • Most tutorials say "50 Hz" or "20 ms period" for standard servos.
# • If you use a different servo, check its datasheet (some use 333 Hz / 3 ms).
#
servo = PWM(Pin(servo_pin), freq=50)
# ----- Simple function to move servo -----
def move_servo(angle):
"""Move servo to angle (0–180 degrees)."""
if angle < 0:
angle = 0
if angle > 180:
angle = 180
# Servos need 1 ms pulse (0°) to 2 ms pulse (180°)
pulse_ns = 1000000 + int(angle * 1000000 / 180)
servo.duty_ns(pulse_ns)
# ----- Start: put servo in the middle -----
move_servo(servo_pos)
time.sleep(1)
# ----- Main loop: read sensors and move servo -----
while True:
north = sensor_north.read() # 0–4095 (darker–brighter)
south = sensor_south.read()
diff = north - south
print("North:", north, " South:", south, " Diff:", diff)
# Only move if the difference is big enough
if abs(diff) > tolerance:
# Step size: 1 to 5 degrees. Bigger light difference → bigger step.
#
# When is step 1, 2, 3, 4, or 5? (tolerance=150, max diff=4095)
# step=1: diff from 150 to 1136 (small move)
# step=2: diff from 1137 to 2121
# step=3: diff from 2122 to 3107
# step=4: diff from 3108 to 4094
# step=5: diff 4095 or more (big move)
#
above_tolerance = abs(diff) - tolerance # how much we're over the threshold
max_above = 4095 - tolerance # max value of above_tolerance
step_range = step_max - step_min # 4 → we map to 1..5
step = step_min + (above_tolerance * step_range // max_above)
if step > step_max:
step = step_max
if step < step_min:
step = step_min
if diff > 0:
servo_pos = servo_pos + step # North brighter → go right
else:
servo_pos = servo_pos - step # South brighter → go left
# Keep angle between 0 and 180
if servo_pos < 0:
servo_pos = 0
if servo_pos > 180:
servo_pos = 180
move_servo(servo_pos)
time.sleep_ms(pause_ms)
#*Source: ChatGPT by OpenAI, February 2026*
??? C/C++ (Arduino) at Arduino and VSCode code for XIAO ESP32C3 x Servomotor x Photoresistors
#include <ESP32Servo.h> // Include library to control servos on ESP32
Servo servo; // Create a Servo object
// Servo settings
int servoPos = 90; // Start position (middle)
const int servoLimitHigh = 180; // Max servo angle
const int servoLimitLow = 0; // Min servo angle
const int servoPin = 10; // PWM pin connected to servo
// LDR (photoresistor) pins
const int ldrNorth = A0; // North sensor
const int ldrSouth = A1; // South sensor
// Servo movement tolerance
const int tolerance = 150; // Minimum difference between sensors to move servo
void setup() {
Serial.begin(115200); // Initialize serial monitor for debugging
servo.attach(servoPin); // Attach servo to the PWM pin
servo.write(servoPos); // Move servo to initial position
delay(1000); // Wait 1 second for servo to reach position
}
void loop() {
// Read analog values from LDRs (0–4095 on ESP32)
int northValue = analogRead(ldrNorth);
int southValue = analogRead(ldrSouth);
// Calculate difference between sensors
int difference = northValue - southValue;
// Print readings for debugging
Serial.print("North ADC: "); Serial.print(northValue);
Serial.print(" South ADC: "); Serial.print(southValue);
Serial.print(" Difference: "); Serial.println(difference);
// Only move servo if the difference is larger than tolerance
if (abs(difference) > tolerance) {
// Map the difference to a step size (1–5 degrees)
// Bigger difference → bigger movement
int stepSize = map(abs(difference), tolerance, 4095, 1, 5);
// Move servo left or right depending on which LDR is brighter
if (difference > 0) servoPos += stepSize; // North is brighter → move right
else servoPos -= stepSize; // South is brighter → move left
// Keep servo angle within limits
servoPos = constrain(servoPos, servoLimitLow, servoLimitHigh);
// Update servo position
servo.write(servoPos);
}
delay(50); // Small delay to reduce jitter and avoid moving too fast
}
If the actual code lines above are to be compared, the micropython version has about 60 lines of executable code, while the Arduino/VSCode version has around 28 lines to reach the same objective (see programming and simulation of the microcontroller section below for more context). This is due to the latter having already built-in functions and libraries that removes extra manual implementation codes that could be seen at the former.
Of course, having more explicit code like the micropython version allows users to better understand the processes behind the prebuilt libraries. Furthermore, since python is a more common and widely used language than C/C++, it is particularly suitable for quickly testing ideas and prototyping before moving to more performance-oriented implementations.
There is another framework called ESPIDF and is well-known to provide more precise control - however as the current goal is to get the system running first, this optimization will be explored further in the future.
Analysis of a microcontroller(s)
The XIAO ESP32C3 and XIAO RP2040 are compared to provide a clearer understanding of performance differences. These two boards were selected for the analysis primarily because they are readily available here in Shenzhen, China.
For detailed information of these MCUs, please refer to the XIAO-RP2040 and XIAO ESP32C3 SEEED studio wiki respectively - a short summary of it could be seen below :
Architecture
| Parameter | ESP32C3 | RP2040 |
|---|---|---|
| Processor | Single-core 32-bit RISC-V | Dual-core ARM Cortex M0+ |
| Clock Speed | Up to 160 MHz | Up to 133 MHz |
| Instruction Set | RISC-V with extensions (open source) | ARM Thumb-2 (proprieatry) |
| Wireless Connectivity | WiFi 802.11 b/g/n + Bluetooth 5.0 LE | None |
| Physical Size | 21 x 17.8 mm | 21 x 17.8 mm |
Wireless connectivity : ESP32 wins Processing power : ESP32C3 faster (160 MHz vs 133 MHz), but RP2040 can do parallel task execution.
Memory
| Parameter | ESP32C3 | RP2040 |
|---|---|---|
| Flash memory | 4 MB | 2 MB |
| SRAM | 400 KB | 264 KB |
Memory overall : ESP32 wins
Peripherals
| Parameter | ESP32C3 | RP2040 |
|---|---|---|
| GPIO pins | 11x GPIO | 11x GPIO |
| ADC inputs | 4× | 4x |
| PWM | 11x | 11x |
| Serial interfaces | 1x UART, 1x I2C, 1x SPI | 1x UART, 1x I2C 1x SPI |
| Power input | USB-C 5V or Battery 3.7V | USB‑C 5V or Battery 5V |
All I/O operate at 3.3V
At this stage, it is not yet possible to draw a definitive conclusion as to whether a greater number and variety of pins, as well as higher or lower power input specifications provide significant advantage.
Supported Programming Software
| Parameter | ESP32C3 | RP2040 |
|---|---|---|
| Python-based | MicroPython, CircuitPython (?) | MicroPython, CircuitPython |
| C/C++ Frameworks | Arduino IDE, VS Code (PlatformIO), ESP-IDF | Arduino IDE, VS Code (PlatformIO) |
Programming and simulation of the microcontroller
A safety check on the planned circuit could be done in Wokwi (a simulation program for embedded programming) before moving to the physical hardware. The XIAO ESP32C3 is readily available as a component there, but not the XIAO RP2040 - and so the test focuses on the former and assumes that the essential features/logics could also be applied to the latter.
The final project incorporates solar panel with dual-axis tracker - however only the single-axis case is simulated for test purpose and below is the corresponding setup :
-
Components (see Wokwi's website for more details):
- 1 x XIAO ESP32C3
- 2 x photoresistors
- 1 x servo motor
-
Logic overview :
-
Each photoresistor records different light intensity
-
This information is then to be sent to the XIAO ESP32C3
- The MCU converts the information from analog to digital
- Calculates the difference between the the "north" and "south" photoresistor
- The servo motor will rotate toward the brighter side only if the difference is above a certain threshold. This is to prevent constant changes
- The step size of the servo movement is dictated by the light difference. The bigger the difference, the bigger or "the faster" the movement would be
-
The servo motors follows the instructions from the MCU
Photoresistor's resistance decreases nonlinearly as light intensity (lux) increases, making the ADC reading a nonlinear reflection of lux
Details on the code and the result of it could be seen directly at the simulation :
Arduino/C++ functions for the XIAO ESP32+photoresistor+servo example
This note summarizes all the Arduino-specific and C++ functions used in the example code for the photoresistor servo tracker.
analogRead(pin)
Reads the voltage at an analog pin and converts it to a digital number.
On the ESP32, returns 0–4095.
Example: int value = analogRead(A0);
servo.attach(pin)
Connects a Servo object to a PWM pin. Must be called before servo.write().
Example: servo.attach(10);
servo.write(angle)
Moves the servo to a specific angle (0–180°).
Example: servo.write(90); moves the servo to the middle position.
delay(ms)
Pauses program execution for ms milliseconds. Useful for giving the servo time to move or slowing loops.
Example: delay(1000); pauses for 1 second.
Serial.begin(baud)
Initializes serial communication at the specified baud rate. Common for ESP32.
Example: Serial.begin(115200);
Serial.print() / Serial.println()
Sends data to the Serial Monitor. println adds a newline, print does not.
Example: Serial.println("Hello");
map(value, in_min, in_max, out_min, out_max)
Converts a number from one range to another linearly.
Example: map(sensorValue, 0, 4095, 0, 180);
constrain(value, min, max)
Ensures a number stays within a range.
Example: constrain(servoPos, 0, 180);
abs(value)
Returns the absolute value of a number. Useful for differences where only the magnitude matters.
Example: abs(-50) // returns 50
Source: ChatGPT by OpenAI, February 2026
The next step is to upload the code to the XIAO ESPC2C3 and assemble the circuit with real hardware components - some electrical and mechanical details of the components used could be seen below :
-
Power supply
- Adaptor :
- Input : 100V-240V 50/60Hz
- Output : 9V (DC), 1A (1000mA) max
-
Breadboard power supply :
- Input : 6.5-9V (DC) or 9V Battery
- Output : 3.3V/5V (DC),700mA(Max)
- Adaptor :
-
Servo motor ([for more details]:(https://towerpro.com.tw/product/mg996r/)):
- Input: 4.8~6.6V (DC), 100 - 1400 mA
- Output (stall/maximum torque): 9.4kg/cm (4.8V); 11kg/cm (6.0 V)
-
Photoresistor:
- Input : 3.3 V
The researched features of the servo motor and photoresistor is documented further in the final project log.
Compared to the simulation environment, it was decided to power the servo motor using an external power supply rather than the 5 V output pin from the XIAO EP32C3. This decision is based on the uncertainty and perhaps limitation of the available current from the MCU. The arranging the circuit process and the result is shown in the next videos.
microcontroller processor - not uploaded yet
It is interesting to observe that although the same code is applied, the servo motor appears to position itself correctly between 0° and 180° at the simulation but not at the real implementation. Despite this, the other main control logic seems to be working and so the reasons for this discrepancy will be investigated in the next coming weeks.