无库Arm Cortex-M0中如何将Systick_Handler()关联至SysTick异常
问题分析与解决方案
你的核心问题在于Cortex-M0的异常触发依赖硬件中断向量表机制,STM32等MCU的示例之所以不用手动调用Systick_Handler,是因为它们的启动文件已经帮你完成了向量表的配置——这不是库的“魔法”,而是Arm Cortex-M架构的硬件规定。下面是具体的解决步骤和代码修正:
1. 核心原理:中断向量表的作用
Cortex-M0硬件在触发异常(比如SysTick计数归零)时,会自动从中断向量表中找到对应异常的处理函数地址,然后跳转执行。向量表必须放在系统内存的起始地址(默认0x00000000),其中第15项(从0开始计数)就是SysTick异常的入口地址:
- 索引0:栈顶地址
- 索引1:复位向量
- ...
- 索引15:SysTick Handler
没有内置库的情况下,你需要手动定义向量表并配置链接脚本,让硬件能找到Systick_Handler的地址。
2. 代码修正与补充
2.1 修正现有代码的错误
- 把
main里的局部变量time改为全局volatile变量(中断服务函数无法访问局部变量,volatile防止编译器优化) - 修复
systick_disable中的位操作错误(!是逻辑非,应该用~按位取反) - 移除手动挂起SysTick异常的代码(
ICSR[0] =1<<26;是手动触发异常,正常场景不需要)
修正后的基础代码:
#include <stdint.h> // 定义SysTick基地址(根据你的MCU手册确认,通常是0xE000E010) #define SYSTICK_BASE ((volatile uint32_t*)0xE000E010) // 全局volatile变量,用于中断计数 volatile int time = 0; void systick_enable(void){ SYSTICK_BASE[0] = 0x0003; // bit0=ENABLE, bit1=TICKINT(开启中断触发) } void systick_disable(void){ SYSTICK_BASE[0] &= ~0x0003; // 关闭SysTick和中断 } void systick_clear(void){ SYSTICK_BASE[1] = 0x0000; // 清除当前计数器值 } void systick_init(uint32_t reload_val) { systick_clear(); SYSTICK_BASE[2] = reload_val; // 设置重载值 systick_enable(); } void Systick_Handler(void){ time++; }
2.2 添加中断向量表与启动代码
需要手动定义向量表,并实现复位处理函数来初始化系统并调用main:
// 栈顶地址:根据你的MCU RAM大小设置,示例为16KB RAM(0x20000000 ~ 0x20004000) #define STACK_TOP 0x20004000 // 声明异常处理函数 void Reset_Handler(void); void NMI_Handler(void) __attribute__((weak)); void HardFault_Handler(void) __attribute__((weak)); void Systick_Handler(void); // 中断向量表:放到专门的段,保证链接到Flash起始地址 __attribute__((section(".isr_vector"))) void (* const g_pfnVectors[])(void) = { (void (*)(void))STACK_TOP, // 栈顶地址 Reset_Handler, // 复位处理函数 NMI_Handler, // NMI异常(弱定义,可重写) HardFault_Handler, // 硬 fault(弱定义,可重写) 0, 0, 0, 0, 0, 0, 0, 0, // 保留向量 0, 0, // 保留向量 Systick_Handler, // SysTick异常(索引15) // 其他外设中断可根据你的MCU手册添加 }; // 默认异常的空实现 void NMI_Handler(void) {} void HardFault_Handler(void) { while(1); } // 硬fault后死循环 // 复位处理函数:初始化数据段、bss段,调用main void Reset_Handler(void) { // 初始化.data段:从Flash复制到RAM extern uint32_t __data_start__; extern uint32_t __data_end__; extern uint32_t __data_load__; uint32_t *pSrc = &__data_load__; uint32_t *pDest = &__data_start__; while(pDest < &__data_end__) { *pDest++ = *pSrc++; } // 初始化.bss段:清零 extern uint32_t __bss_start__; extern uint32_t __bss_end__; pDest = &__bss_start__; while(pDest < &__bss_end__) { *pDest++ = 0; } // 调用main函数 main(); // main返回后死循环 while(1); }
2.3 配置链接脚本
需要编写链接脚本,把向量表段.isr_vector放到Flash的起始地址,示例链接脚本(适用于GCC工具链):
MEMORY { FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 64K // 根据你的MCU Flash大小调整 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K // 根据你的MCU RAM大小调整 } SECTIONS { .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) // 强制保留向量表,防止被优化 . = ALIGN(4); } > FLASH .text : { . = ALIGN(4); *(.text) *(.text*) . = ALIGN(4); } > FLASH .data : { . = ALIGN(4); __data_start__ = .; *(.data) *(.data*) __data_end__ = .; . = ALIGN(4); } > RAM AT > FLASH __data_load__ = LOADADDR(.data); .bss : { . = ALIGN(4); __bss_start__ = .; *(.bss) *(.bss*) *(COMMON) __bss_end__ = .; . = ALIGN(4); } > RAM }
3. 编译与运行
用GCC工具链编译时,需要指定链接脚本:
arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -c your_code.c -o your_code.o arm-none-eabi-ld -T your_linker_script.ld your_code.o -o your_program.elf arm-none-eabi-objcopy -O binary your_program.elf your_program.bin
烧录后,SysTick计数归零就会自动触发Systick_Handler,无需在main中调用。
内容的提问来源于stack exchange,提问作者Jingxuan Cai
相关产品推荐
相关产品推荐

