Introduction to ARM Bare-Metal Programming and Embedded Systems
Getting Started with ARM Bare-Metal Programming: From Zero to Embedded Development
Hello everyone, I’m Daodao. Today, let’s talk about ARM bare-metal development. Many beginners feel overwhelmed when they see ARM, thinking it’s particularly difficult. In fact, it’s not; once you grasp the essentials, writing a simple LED blinking program is easier than knitting (just kidding).
What Exactly is ARM?
Speaking of ARM, let me tell you an interesting story. In the 1980s, a British company called Acorn wanted to create a processor for their computers but found that the processors available on the market were either too expensive or too slow. So they decided to create their own, and that’s how ARM came to be. Nowadays, most processors in your smartphones are based on the ARM architecture.
The biggest feature of ARM processors is their Reduced Instruction Set Computing (RISC). To put it simply, if we compare processors to chefs, the X86 processor is like a chef who knows hundreds of dishes, while the ARM processor is a cook who specializes in just a few basic recipes. Although there are fewer recipes, each one can be completed quickly and accurately.
Basic Concepts of Bare-Metal Programming
Bare-metal programming, as the name suggests, involves directly interacting with hardware without an operating system. It’s like buying a piece of land and building a house yourself, where you have to manage every brick and tile from the foundation up.
The most basic components of a bare-metal program are:
-
Startup file (startup.s): equivalent to the foundation of the house
-
Linker script (*.ld): like the architectural blueprint of the house
-
Main program (main.c): which is the house itself
Setting Up the Development Environment
To do a good job, one must first sharpen their tools. Bare-metal development requires the following tools:
-
Cross-compilation toolchain: used to convert the C code we write into machine code that ARM can understand
-
Debugger: such as J-Link or ST-Link, used to find bugs in the program
-
Editor: recommended VSCode or Keil MDK
Note: Make sure to correctly configure the environment variables for the toolchain under Windows; otherwise, the commands will not be found during compilation. I’ve stumbled into this pitfall several times in the past.
Understanding the Hardware
Taking the STM32F103 series as an example, its core is an ARM processor with a CM3 core. The most basic peripherals on the board include:
- Clock system
- GPIO ports
- NVIC interrupt controller
- Various buses (AHB/APB)
Clock configuration is crucial, just like a human heartbeat; if configured incorrectly, the entire system will fail.
First Program: Blinking an LED
Let’s take a look at the most basic LED blinking program:
#define LED_PIN GPIO_PIN_13
#define LED_PORT GPIOC
int main(void)
{
// 1. Enable clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
// 2. Configure GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = LED_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LED_PORT, &GPIO_InitStruct);
while(1)
{
GPIO_SetBits(LED_PORT, LED_PIN); // LED on
delay_ms(500); // Delay 500ms
GPIO_ResetBits(LED_PORT, LED_PIN); // LED off
delay_ms(500); // Delay 500ms
}
}
Case Analysis:
Last year, I encountered an interesting problem where the customer’s LED wouldn’t light up. The code was fine, but I eventually discovered that the GPIO port clock wasn’t enabled. It’s like wanting to turn on the TV but forgetting to plug it in.
Interrupt Handling
Speaking of interrupts, I like to compare them to doorbells. When you’re watching TV and the doorbell rings (interrupt signal), you have to go answer the door (interrupt service routine).
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// Interrupt handling code
LED_Toggle(); // Toggle LED state
// Clear interrupt flag
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
Notes:
-
Keep the interrupt service routine as short and efficient as possible, just like a delivery person ringing the doorbell—you can’t make them wait too long.
-
Remember to clear the interrupt flag, otherwise it will trigger repeatedly.
-
Priority settings should be reasonable to avoid delaying critical interrupts.
Debugging Techniques
-
The most commonly used debugging methods:
-
Serial printing
-
LED status indication
-
Waveform observation
Common Problem Solutions:
-
Program runaway: usually due to stack overflow or wild pointers.
-
Infinite loop: check if the while condition is reasonable.
-
Peripheral not working: check clock and pin configuration.
Practical Suggestions
-
Prepare a development board, I recommend the STM32F103 series.
-
Start with the simplest GPIO control.
-
Use the HAL library or standard library to avoid direct register manipulation.
-
Use serial printing frequently during debugging for easier problem localization.
-
Establish good coding standards and add reasonable comments.
Memory Management Suggestions:
-
Avoid allocating memory in interrupts.
-
Be mindful of stack space size settings.
-
Use static variables judiciously.
Lastly, let me say that patience is the most important thing in embedded development. When the code doesn’t work, brew a cup of coffee, take a break, and come back with a fresh perspective. After all, Rome wasn’t built in a day, and neither was a chip debugged in a day.
That’s all for now; I hope this article helps you get started with ARM bare-metal development. Feel free to discuss any questions in the comments section.
Note: The code mentioned in this article is for reference only and should be adjusted according to specific project requirements. Remember to take precautions and avoid turning your development board into a brick.