5.Embedded programming¶
Summary¶

What is Embedded Programming?¶
One kind of programming language used to create system code based on a microcontroller or microprocessor is called embedded programming. There are certain hardware requirements for this programming language. A common form of Internet of Things and electronic consumer applications are embedded systems, which are utilized inside industrial machinery, bicycle charging systems, and residential appliances.
Where is Embedded Programming Used?¶
Embedded programming is widely used across various industries. Industrial Automation: Used for production monitoring and machine control. Consumer Electronics: Found in devices like digital cameras, washing machines, and smart TVs. Internet of Things (IoT): Powers smart home appliances and connected devices. Automotive Systems: Plays a crucial role in airbags, ABS brakes, and engine control. Medical Equipment: Used in devices such as ventilators, insulin pumps, and heart rate monitors.
Group assignment¶
About Microcontroller¶
A circuit called a microcontroller is made to regulate particular operations in an embedded system. On a single chip, it combines a CPU, memory (RAM, ROM, and Flash), and input/output peripherals. Because of their low power consumption and real-time processing capabilities, microcontrollers are widely utilized in consumer electronics, automation, robotics, and the Internet of Things.
Microcontroller Comparison¶

-
AVR Microcontrollers AVR microcontrollers are commonly used in small embedded systems and are based on the RISC (Reduced Instruction Set Computer) architecture. They are widely popular among beginners and hobbyists, often found in Arduino boards. Known for their low power consumption, simplicity, and ease of programming, AVR chips are a preferred choice for basic embedded applications.
-
ARM Microcontrollers ARM microcontrollers are used in a wide range of high-performance applications. They are known for their scalability and power efficiency, making them ideal for industrial automation, automotive control, and mobile devices. The ARM Cortex-M family is widely utilized in various embedded systems.
-
ESP Microcontrollers The ESP8266 and ESP32 are popular microcontrollers designed for Internet of Things (IoT) applications. These microcontrollers are ideal for cloud-based applications, wireless communications, and smart home automation due to their built-in Wi-Fi and Bluetooth capabilities. Among IoT projects, the ESP32 is widely used for its advanced features and versatility.
-
STM32 Microcontroller STMicroelectronics' STM32 microcontrollers are based on the ARM Cortex-M core. They are widely used in automotive, medical, and industrial automation applications. These microcontrollers offer real-time processing, multiple peripherals, and high computational power, making them suitable for a variety of advanced embedded systems.
Reference by Search Engine
Toolchains and Development Workflows¶
The key instruments and procedures required for embedded system development are the subject of research on toolchains and development workflows. A compiler, assembler, linker, debugger, and integrated development environment (IDE) comprise a toolchain that facilitates the efficient writing, compilation, and debugging of code. Different toolchains are used by different microcontrollers, including AVR, ARM, ESP32, and STM32, to guarantee the seamless creation and operation of embedded applications. A clear workflow increases productivity, lowers errors, and boosts system performance as a whole.
Toolchain Components¶
Compiler: Transforms high-level code (C, C++) into machine code. Examples include GCC, Clang, IAR, and Keil. Assembler: Converts assembly language into binary instructions. Common assemblers include GNU Assembler and NASM. Linker: Combines compiled code and libraries to construct an executable. Examples include GNU LD and Keil Linker. Debugger: Enables step-by-step execution for troubleshooting. Popular debuggers include GDB, OpenOCD, and J-Link Debugger. Integrated Development Environment (IDE): Provides a complete development environment. Examples include VS Code, Eclipse, Keil, STM32CubeIDE, and Arduino IDE.
Development Workflow¶
Requirement Analysis: Select the appropriate hardware and define system objectives. Code Development: Write embedded code using C, C++, or Assembly. Compilation & Linking: Utilize the toolchain to convert source code into machine code. Flashing to Target: Use a programmer or debugger to upload the compiled application onto the microcontroller. Testing & Debugging: Employ tools like Serial Monitor, JTAG, or SWD for real-time debugging. Optimization: Enhance power efficiency, minimize memory usage, and improve overall performance. Maintenance & Deployment: Finalize and install the firmware on operational devices.
Example Toolchain for Microcontrollers¶

Which Tools Were Easier to Use?¶

ESP32¶

ESP32-WROOM-32U is a powerful Wi-Fi and Bluetooth module developed by Espressif Systems, based on the ESP32 microcontroller. The letter “U” means that this version includes a u.FL connector for an external antenna, which provides stronger and more stable wireless communication compared to the onboard antenna version.
Key Features:
Processor: Dual-core Xtensa LX6, up to 240 MHz
Memory: 520 KB SRAM
Wireless Connectivity: Wi-Fi 802.11 b/g/n and Bluetooth v4.2 (Classic + BLE)
Interfaces: GPIO, UART, SPI, I2C, PWM, ADC, DAC
Power Supply: 3.0–3.6 V
Antenna: External antenna via u.FL connector
In summary, the ESP32-WROOM-32U is an ideal module for IoT, robotics, and embedded projects that require high performance, wireless communication, and longer signal range using an external antenna. Datasheet click here.
Testing ESP32 Performance in Two Programming Environments¶
During this experiment, I wanted to compare the performance of the ESP32 microcontroller when programmed in two different environments — ESP-IDF (in VS Code) and Arduino IDE. The main goal was to find out which environment executes code faster and produces cleaner signals.
I wrote the same simple code in both environments — a digital output toggling on and off without any delay. By avoiding delays, I could measure the pure switching frequency of the microcontroller and determine which framework is more efficient at the low-level hardware level.
First, I uploaded the code using ESP-IDF through VS Code, then connected the ESP32 to an oscilloscope to observe the output signal. Next, I uploaded the exact same code through Arduino IDE and again checked the signal on the oscilloscope.
ESP-IDF¶

VS code witch ESP-IDF¶

This here is code¶
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define LED 25
void app_main(void)
{
gpio_config_t io_conf = {0};
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pull_up_en = 0;
io_conf.pull_down_en = 0;
io_conf.pin_bit_mask =
(1ULL << LED);
gpio_config(&io_conf);
while (1) {
gpio_set_level(LED , 1);
gpio_set_level(LED , 0);
}
}
LED Blinking Test on ESP32 (ESP-IDF)¶
In this test, I wrote a simple ESP-IDF program to control a single LED connected to GPIO 25. First, I configured the pin as a digital output using the gpio_config() function. Then, inside the app_main() loop, I attempted to toggle the LED ON and OFF.
At the beginning of the program, I called set_all(0) to ensure the LED started in the OFF state. Inside the infinite loop, the code tries to set the LED HIGH and LOW by calling gpio_set_level().
However, the code still contains placeholder names (LED1, LED3), which are not defined. These were part of a previous multi-LED experiment and should be replaced with the correct pin name (LED). The example also does not include a delay function, so the LED switching happens too fast to observe visually.
This example shows my first steps experimenting with ESP-IDF GPIO configuration and testing digital output control on the ESP32.
Arduino IDE¶

void setup() {
pinMode(25, OUTPUT);
}
void loop() {
digitalWrite(25, 1);
digitalWrite(25, 0);
}
For this simple test, I used the Arduino IDE to control an LED connected to GPIO 25 on the ESP32. In the setup() function, I configured the pin as an output using pinMode(). Inside the loop(), I repeatedly set the pin HIGH and then LOW using digitalWrite().
Because there is no delay between these operations, the LED switches too fast to be visible. This experiment demonstrates the basic workflow of uploading code to the ESP32 and controlling GPIO pins in Arduino IDE.
ESP32: Comparison of ESP-IDF vs Arduino IDE¶
| Parameter | ESP-IDF | Arduino IDE |
|---|---|---|
| Programming workflow | Professional toolchain; CMake + command-line; configuring via menuconfig | Very simple workflow; write → upload |
| Abstraction level | Low-level (close to hardware) | High-level (simplified APIs) |
| Code size | Smaller, more optimized | Larger binaries |
| Execution speed | Faster; minimal overhead | Slightly slower due to abstraction |
| Ease of use | Harder to learn; detailed configuration | Beginner-friendly |
| Control over peripherals | Full control (GPIO, timers, FreeRTOS tasks, drivers) | Limited flexibility; wrappers over ESP-IDF |
| FreeRTOS integration | Native and flexible | Hidden, limited access |
| Available libraries | Fewer ready libs; requires manual config | Many easy-to-use libraries |
| Debugging | Advanced (JTAG, GDB) | Basic serial monitor |
| Build system | CMake / IDF.py | Arduino build system |
| Hardware configuration | Full configuration via menuconfig | Mostly automatic |
| Project structure | Complex, modular | Simple sketch-based |
| IDE support | VS Code, CLion, etc. | Arduino IDE, PlatformIO |
| Best use case | Production, performance-critical embedded systems | Prototyping / quick development |
Summary¶
- ESP-IDF gives more control, faster execution, and smaller code size.
Best for professional embedded development. - Arduino IDE is easier and faster to start with.
Best for beginners and quick prototyping.
From the oscilloscope readings, I noticed a clear difference: the signal generated with ESP-IDF had a higher frequency and cleaner waveform, meaning it executed faster compared to the Arduino version. This shows that ESP-IDF provides better performance and more precise timing control because it works closer to the hardware and uses native APIs.
Although Arduino IDE is easier to use and perfect for quick prototyping, ESP-IDF is a more professional and optimized development framework, especially for large and performance-critical projects.
This test helped me understand the advantages of both environments — simplicity versus efficiency — and confirmed that for serious robotics or embedded system projects, ESP-IDF is the best choice.
Conclusion¶
Through this experiment, I learned that while Arduino IDE is simple and great for beginners, ESP-IDF offers much better performance and control over the ESP32 hardware. The oscilloscope clearly showed that ESP-IDF executes faster and produces a cleaner signal, which makes it the ideal choice for professional and high-performance embedded projects.
Individual assignment¶
Nucleo F103RB¶

Nucleo F103RB is a development board by STMicroelectronics based on the STM32F103RB microcontroller (ARM Cortex-M3). It includes:
USB interface for programming and power,
Arduino-compatible pin headers,
ST-Link debugger built in.
It’s used for learning, prototyping, and developing embedded systems using STM32CubeIDE or Arduino IDE.
STM32 CudeMX¶

STM32CubeMX is an official software tool from STMicroelectronics that helps developers configure STM32 microcontrollers before programming them.
In short:
It is a graphical interface (GUI) where you can select a microcontroller, configure its pins, clock settings, and peripheral interfaces such as UART, SPI, and I2C.
It automatically generates C code for various IDEs, including STM32CubeIDE, Keil, and IAR.
It simplifies the configuration process and saves time during project setup.
In simple terms, STM32CubeMX is a configuration tool that allows you to easily set up an STM32 microcontroller and generate the necessary code for development.
CLion¶

CLion is a professional Integrated Development Environment (IDE) created by JetBrains for programming in C and C++. It offers powerful features such as:
intelligent code completion,
advanced debugging tools,
cross-platform project building (using CMake, Makefile, or Gradle),
code analysis and refactoring,
integration with Git, Docker, and other development tools.
CLion makes C/C++ development more convenient and efficient, especially when working on large-scale projects or microcontroller-based systems.
STM32 CudeProgrammer¶

STM32CubeProgrammer is an official software tool developed by STMicroelectronics for programming and managing the memory of STM32 microcontrollers.
With STM32CubeProgrammer, you can:
Flash firmware to STM32 devices via USB, UART, SWD, JTAG, or DFU;
Read and erase memory;
Modify Option Bytes and security settings;
Work with both internal and external flash memory;
Update firmware using either a graphical interface (GUI) or a command-line interface (CLI).
In simple terms, STM32CubeProgrammer is a universal tool for flashing, configuring, and maintaining STM32 microcontrollers.
These are the software tools I use when working with microcontrollers from STMicroelectronics.¶
How my MCU communicates (Nucleo-F103RB)¶
Overview¶
I use wired serial (UART) to communicate between the STM32F103RB on the Nucleo board and my computer.
The Nucleo’s on-board ST-LINK converts the MCU’s UART (USART2) to a USB Virtual COM Port (VCP), so I just connect a single USB cable to the Nucleo and open a serial terminal on the PC.
- MCU peripheral: USART2
- MCU pins: PA2 = TX, PA3 = RX
- Bridge: ST-LINK V2-1 (on the Nucleo board)
- PC side: USB (Virtual COM Port)
- Serial settings: 115200 baud, 8 data bits, no parity, 1 stop bit (115200-8-N-1)
This setup lets me send data from the MCU to the PC (logs, sensor values) and receive commands from the PC (text commands, bytes).
Method for Sending Data (TX)¶
I transmit bytes/strings using the HAL UART driver in polling mode:
const char *msg = "Hello from Nucleo!\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
I created a running light effect using LEDs on the Nucleo-F103RB board.¶
I first created a new project in STM32CubeMX, configured the pins in output mode to connect the LEDs, and selected CMake as the toolchain.


This is the implementation of the running light effect based on the STM32 microcontroller — here is the result.
This here is code¶
#include "main.h"
int time = 20;
UART_HandleTypeDef huart2;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
while (1)
{
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, SET);
for (int i = 0; i < 4; i++) {
if (i == 1) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 0);
}
HAL_Delay(time);
if (i == 2) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 0);
}
HAL_Delay(time);
if (i == 3) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 0);
}
HAL_Delay(time);
}
for (int i = 0; i < 4; i++) {
if (i == 1) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 1);
}
HAL_Delay(time);
if (i == 2) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 0);
}
HAL_Delay(time);
if (i == 3) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 0);
}
HAL_Delay(time);
}
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
static void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LD2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
Code Description (STM32 HAL)¶
Overview¶
The program runs a “running light” (chaser) pattern across three LEDs on PB0, PB1, and PB2, first left-to-right and then right-to-left, continuously. The on-board LED LD2 is kept ON. A pushbutton (B1_Pin) is configured as an external interrupt input but is not used in the current logic.
Global Variables¶
-
int time = 20;
Step delay for the LED sequence in milliseconds.
Note: consider renaming tostep_msto avoid confusion with the standardtime()function. -
UART_HandleTypeDef huart2;
UART2 handle (UART is initialized but not used in this program).
Initialization Functions¶
HAL_Init();¶
Initializes the HAL library, SysTick, and resets peripherals.
SystemClock_Config();¶
Configures system clock: HSI → PLL (HSI/2 × 16) to reach approximately 64 MHz.
MX_GPIO_Init();¶
- Enables clocks for GPIOC, GPIOD, GPIOA, GPIOB.
- Sets
LD2as push-pull output, initially RESET. - Sets
PB0,PB1,PB2as push-pull outputs, initially RESET. - Configures
B1_Pinas external interrupt input on rising edge (GPIO_MODE_IT_RISING). - Enables
EXTI15_10_IRQnin NVIC.
MX_USART2_UART_Init();¶
Initializes UART2 at 115200-8-N-1. Not used in the current code.
Button-controlled LED (step-by-step)¶
Idea / wiring¶
One button to GPIO with internal pull-up (pressed = LOW).
Three LEDs on GPIOs with series resistors.
Each valid press advances the state: LED1 → LED2 → LED3 → ALL OFF → repeat.
Debounce (≈30–50 ms) to ignore switch noise.
Algorithm¶
Read button.
If pressed and was previously released for > debounce time → state = (state + 1) % 4.
Update LEDs according to state.
This here is code
#include "main.h"
int count = 0;
UART_HandleTypeDef huart2;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
while (1)
{
if (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == 0) {
count++;
HAL_Delay(300);
}
if (count == 1) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 0);
}
if (count == 2) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 0);
}
if (count == 3) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 1);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 1);
}
if (count == 4) {
count = 0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, 0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, 0);
}
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
static void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
/*Configure GPIO pin : B1_Pin */
GPIO_InitStruct.Pin = B1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : LD2_Pin */
GPIO_InitStruct.Pin = LD2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PB0 PB1 PB2 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
Code Description¶
This document explains what each part of the STM32 HAL code does.
Purpose of Each Function¶
-
HAL_Init();Initializes the HAL library: configures the SysTick timer, resets peripherals, and prepares the MCU for operation. -
SystemClock_Config();Configures the system clock. Uses HSI with PLL (HSI/2 × 16) to achieve approximately 64 MHz for the CPU and peripherals. -
MX_GPIO_Init();Enables GPIO port clocks and configures pins.
Configured pins: - PB0, PB1, PB2 as output pins (used to control LEDs) - B1_Pin as external interrupt input, rising edge (GPIO_MODE_IT_RISING)
Also enables the EXTI interrupt line EXTI15_10_IRQn in NVIC.
MX_USART2_UART_Init();Initializes UART2 with baud rate 115200, 8 data bits, 1 stop bit, no parity. UART is prepared for serial communication but is not used in this program.
Variables¶
int count = 0;Stores the number of button presses.
Main Loop¶
while (1) { ... }The program continuously checks the button state and controls LEDs according to the value ofcount.
Button Reading¶
HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) reads the button status.
If the pin is LOW (0), count is incremented. HAL_Delay(300) is used as a simple debounce.
LED Control Logic¶
LEDs on pins PB0, PB1, and PB2 are controlled based on count.
| count value | LED0 (PB0) | LED1 (PB1) | LED2 (PB2) |
|---|---|---|---|
| 1 | ON | OFF | OFF |
| 2 | ON | ON | OFF |
| 3 | ON | ON | ON |
| 4 | OFF | OFF | OFF (count resets to 0) |
After count == 4, the count resets and all LEDs turn off.
Functions Used¶
-
HAL_GPIO_ReadPin()Reads the digital level of a pin. -
HAL_GPIO_WritePin()Sets a GPIO pin to HIGH or LOW (turns LEDs on or off). -
HAL_Delay()Blocking delay used for simple button debounce.
Simulator Wokwi¶
I worked with the Wokwi simulator, and I really liked it. You can quickly build a simple circuit, write the code, and instantly see the result. When you get an idea, you can test it within minutes.
Previously, I knew about Wokwi and other simulators like Tinkercad. Let me share a small story: when I was in the 9th grade, I really wanted to learn Arduino. I watched video tutorials, but realized that Arduino is hardware — to truly learn it, you need the physical board.
I saved money by myself, ordered an Arduino from China, and waited two months for it to arrive — shipping back then was slow. While waiting, I used Tinkercad. I found it incredibly exciting to build virtual circuits and write code.
When I finally received my first board and lit my first LED, I realized that this was my life path. Since then, I’ve sincerely wanted to become one of the strongest engineers in the world in embedded systems.
And I never knew you could write code using HAL in Wokwi — that’s really amazing!

I searched for “Wokwi” on Google. The first result shows the official website of Wokwi, an advanced STM32 simulator.

This is the component search panel in Wokwi. Here I can search and add components such as LEDs, pushbuttons, resistors, and OLED displays.

On the left is the code editor, and on the right is the STM32 circuit diagram. The highlighted buttons allow me to start and control the simulation.

After clicking the run button, Wokwi begins compiling the project. It shows compilation time and offers a paid upgrade to speed up building.

The simulation is running. The timer and performance indicator are shown at the top. LEDs connected to the STM32 board are blinking.
This here is code¶
const uint8_t LEDS[] = {2, 4, 7, 10};
const uint8_t N = sizeof(LEDS);
const uint16_t SPEED = 80;
void setup() {
for (uint8_t i = 0; i < N; i++) {
pinMode(LEDS[i], OUTPUT);
digitalWrite(LEDS[i], LOW);
}
}
void allOff() {
for (uint8_t i = 0; i < N; i++) digitalWrite(LEDS[i], LOW);
}
void showOne(uint8_t idx) {
allOff();
digitalWrite(LEDS[idx], HIGH);
}
void effectBounce() {
for (int i = 0; i < N; i++) {
showOne(i);
delay(SPEED);
}
for (int i = N - 2; i >= 1; i--) {
showOne(i);
delay(SPEED);
}
}
void effectCounter() {
for (uint8_t val = 0; val < (1 << N); val++) {
for (uint8_t i = 0; i < N; i++) {
bool on = (val >> i) & 0x01;
digitalWrite(LEDS[i], on ? HIGH : LOW);
}
delay(200);
}
}
void loop() {
for (int k = 0; k < 6; k++) effectBounce();
effectCounter();
}
Result video¶
Reflection¶
This week, I didn’t face any major challenges because I have over five years of experience working with microcontrollers. I first started learning about them back in school, and since then I’ve worked with several different types — from Arduino to more advanced platforms like STM32, where I programmed at the register level using professional development environments.
Thanks to this background, working with microcontrollers feels very natural to me. It allows me to understand both high-level frameworks like Arduino IDE and low-level embedded programming with ESP-IDF or STM32CubeIDE. This experience helped me complete all the weekly assignments efficiently and deepened my confidence in hardware programming and embedded systems development.
Conclusion¶
This week was smooth and productive since I already have over five years of experience working with microcontrollers. Starting from Arduino and progressing to STM32 and ESP32, I’ve learned to program both with high-level frameworks and directly through registers. This experience helped me complete all the tasks efficiently and deepened my understanding of embedded systems.