Introduction to STM32F103C8T6
This experimental environment is based on the STM32F103C8T6 minimum system board, which is a low-cost learning board equipped with an ARM Cortex-M3 core. Its main features are as follows:
-
ARM 32-bit Cortex-M3 core, with a maximum frequency of 72MHz
-
64KB or 128KB Flash
-
20KB SRAM
-
Multiple timers, ADC (12-bit, 16 channels), USART, I²C, SPI, CAN, USB 2.0 full-speed interface
-
Rich GPIO (up to 80 pins)
This board is suitable for beginners to quickly get started and is also suitable for Bootloader-related experiments.
STM32F103 Boot Modes
The startup behavior of STM32 is determined by the level combinations of the BOOT0 and BOOT1 pins:
-
Boot from main Flash (normal mode, BOOT0=0): After reset, user code is executed from address 0x08000000.
-
Boot from system memory (built-in Bootloader mode, BOOT0=1, BOOT1=0): Executes the vendor-provided ROM Bootloader, which can download programs via UART, USB, etc.
-
Boot from SRAM (debug mode, BOOT0=1, BOOT1=1): The CPU executes code from internal SRAM, commonly used for debugging or temporary experiments.
Note: If you are programming with ST-LINK, the BOOT pins generally remain at 0. If you want to download via UART, you need to set BOOT0=1.
Bootloader Implementation Approach
The Bootloader functionality we want to implement is very simple:
-
Bootloader runs → Light up LED, print debug information;
-
Complete initialization → Jump to application entry;
-
Application starts running → LED blinks at 1-second intervals.
To achieve this goal, we will create two independent projects:
-
Bootloader project: Code located at Flash starting address
<span>0x08000000</span>(size 16KB); -
Application project: Code placed at
<span>0x08004400</span>(size about 47KB);
Writing the Bootloader Project
1. Project Configuration
-
Enable GPIO PC13 (on-board LED) and USART1 (PA9, PA10);
-
Redirect
<span>printf</span>to UART1 for convenient serial printing of debug information;
Example code (redirecting <span>printf</span>):
#ifdef __GNUC__
int __io_putchar(int ch)
#else
int fputc(int ch, FILE *f)
#endif
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
2. Bootloader Main Logic
#define MAJOR 0
#define MINOR 1
uint8_t BL_Version[2] = { MAJOR, MINOR };
static void goto_application(void) {
printf("Jumping to Application...\n");
// Get the application reset handler address
uint32_t app_reset_addr = *(volatile uint32_t*)(0x08004400 + 4U);
void (*app_reset_handler)(void) = (void*)app_reset_addr;
// Jump to execute the application
app_reset_handler();
}
3. Linker Script Modification
Limit the Flash area occupied by the Bootloader in <span>STM32F103C8TX_FLASH.ld</span>:
MEMORY {
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 16K
}
This way, the Bootloader will be compiled to the address range 0x08000000 to 0x08003FFF.
Writing the Application Project
1. Project Configuration
-
Also enable GPIO PC13 and USART1;
-
Add
<span>printf</span>redirection code;
2. Application Logic
#define MAJOR 0
#define MINOR 1
uint8_t APP_Version[2] = {MAJOR, MINOR};
printf("Application %d.%d Started!\n", APP_Version[0], APP_Version[1]);
while (1) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(1000); // LED blinks once per second
}
3. Linker Script Modification
The application is stored starting from <span>0x08004400</span>:
MEMORY {
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
FLASH (rx) : ORIGIN = 0x8004400, LENGTH = 47K
}
4. Vector Table Offset Modification
In <span>system_stm32f1xx.c</span>, enable <span>USER_VECT_TAB_ADDRESS</span> and set the offset to <span>0x4400</span>:
#define USER_VECT_TAB_ADDRESS
#define VECT_TAB_OFFSET 0x00004400U
Programming and Running
-
First, program the Bootloader to
<span>0x08000000</span>; -
Then program the application to
<span>0x08004400</span>; -
Open the serial debugging assistant, and you will see:
-
Bootloader running → printing version number, LED blinking rapidly;
-
Then jumping to the application → printing application version, LED blinking at 1s intervals.