1. 案例1
任务目标
编写基于ARM Cortex-M3/M4处理器的完整启动代码,实现:向量表定义、堆栈指针初始化、系统时钟配置(外部晶振→PLL→系统时钟)、以及跳转至C语言main()函数。本案例以STM32F407(Cortex-M4)为硬件参考,代码可直接用于实际工程。
环境与依赖
- 硬件:STM32F407VET6开发板(25MHz外部晶振HSE)
- 工具链:arm-none-eabi-gcc + Makefile(或任意支持ARM的GCC交叉编译器)
- 链接脚本:需提供
stm32f407.ld(定义FLASH起始0x08000000,RAM起始0x20000000)
关键完整代码
/* startup_stm32f407.c - ARM Cortex-M4启动代码 */
#include <stdint.h>
/* 外设寄存器地址定义(仅列出本案例所需) */
#define RCC_BASE 0x40023800U
#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x00))
#define RCC_CFGR (*(volatile uint32_t *)(RCC_BASE + 0x08))
#define RCC_PLLCFGR (*(volatile uint32_t *)(RCC_BASE + 0x04))
#define FLASH_ACR (*(volatile uint32_t *)(0x40023C00U + 0x00))
/* 系统主频:168MHz(HSE=25MHz,PLL_M=25,PLL_N=336,PLL_P=2) */
#define PLL_M 25U
#define PLL_N 336U
#define PLL_P 0U /* PLLP=0 → /2 */
#define PLL_Q 7U
/* 栈顶地址(由链接脚本提供) */
extern uint32_t _estack;
/* C运行时需要的符号 */
extern uint32_t _sdata, _edata, _sidata;
extern uint32_t _sbss, _ebss;
/* 函数声明 */
void Reset_Handler(void);
void Default_Handler(void);
void SystemClock_Init(void);
extern int main(void);
/* 向量表:必须位于0x08000000 */
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
(void *)&_estack, /* 初始SP */
Reset_Handler, /* Reset */
Default_Handler, /* NMI */
Default_Handler, /* HardFault */
Default_Handler, /* MemManage */
Default_Handler, /* BusFault */
Default_Handler, /* UsageFault */
0, 0, 0, 0, /* 保留 */
Default_Handler, /* SVCall */
0, 0, /* 调试保留 */
0, /* PendSV */
Default_Handler /* SysTick */
};
/* 默认中断处理:死循环 */
void Default_Handler(void) {
while (1) { ; }
}
/* 系统时钟初始化:HSE → PLL → 168MHz */
void SystemClock_Init(void) {
/* 1. 打开HSE并等待就绪 */
RCC_CR |= (1U << 16); /* HSEON */
while (!(RCC_CR & (1U << 17))); /* 等待HSERDY */
/* 2. 配置FLASH预取与等待周期(168MHz需5个等待周期) */
FLASH_ACR = (5U << 0) | (1U << 8) | (1U << 9); /* LATENCY=5, PRFTEN, ICEN */
/* 3. 配置PLL:HSE作为输入,分频/倍频 */
RCC_PLLCFGR = (PLL_M << 0) | (PLL_N << 6) |
(PLL_P << 16) | (PLL_Q << 24) |
(1U << 22); /* PLLSRC=HSE */
/* 4. 打开PLL并等待就绪 */
RCC_CR |= (1U << 24); /* PLLON */
while (!(RCC_CR & (1U << 25))); /* 等待PLLRDY */
/* 5. 切换系统时钟到PLL */
RCC_CFGR |= (2U << 0); /* SW=PLL */
while ((RCC_CFGR & 0x0CU) != (2U << 2)); /* 等待SWS=PLL */
}
/* 复位处理:C运行时初始化 + 跳转main */
void Reset_Handler(void) {
uint32_t *pSrc, *pDest;
/* 拷贝.data段(从FLASH到RAM) */
pSrc = &_sidata;
pDest = &_sdata;
while (pDest < &_edata) {
*pDest++ = *pSrc++;
}
/* 清零.bss段 */
pDest = &_sbss;
while (pDest < &_ebss) {
*pDest++ = 0;
}
/* 配置系统时钟 */
SystemClock_Init();
/* 跳转至C主函数 */
main();
/* main不应返回,若返回则死循环 */
while (1) { ; }
}
链接脚本关键段定义
/* stm32f407.ld 关键段(完整脚本需包含MEMORY与SECTIONS命令) */
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS {
.isr_vector : {
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH
.text : { *(.text*) } > FLASH
.rodata : { *(.rodata*) } > FLASH
_sidata = LOADADDR(.data);
.data : {
_sdata = .;
*(.data*)
_edata = .;
} > RAM AT> FLASH
.bss : {
_sbss = .;
*(.bss*)
_ebss = .;
} > RAM
_estack = ORIGIN(RAM) + LENGTH(RAM);
}
编译与烧录
# 编译命令(假设startup.c与main.c在同一目录)
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -nostartfiles \
-T stm32f407.ld -o firmware.elf startup_stm32f407.c main.c
# 生成二进制并烧录(使用ST-Link)
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
st-flash write firmware.bin 0x08000000
验证方法与判定标准
- 编译验证:上述编译命令无错误、无警告,生成
firmware.elf。 - 向量表验证:执行
arm-none-eabi-nm firmware.elf | grep g_pfnVectors,确认向量表地址为0x08000000;arm-none-eabi-objdump -h firmware.elf中.isr_vector段起始地址为0x08000000。 - 运行验证:烧录后连接调试器,在
main()入口设断点。全速运行后若停止在断点处,说明启动代码正确完成:SP初始化→向量表跳转→时钟配置→C运行时初始化→进入main。 - 时钟验证:在调试器中读取
RCC_CFGR寄存器,其SWS位([3:2])应为0b10(PLL作为系统时钟);读取RCC_CR的PLLRDY位([25])应为1。
常见问题
- 程序跑飞:检查
_estack是否指向RAM末尾(0x20020000),向量表首项是否为该地址。 - HSE就绪超时:确认外部晶振焊接正确,或改用内部HSI(将PLLSRC置0并调整分频系数)。
- 进入HardFault:检查FLASH_ACR等待周期是否与系统时钟匹配(168MHz需5个等待周期)。