5.3 Cortex-M0 使用说明
本章说明 N32G033 的 Cortex-M0 内核相关用法,以及 ProcessStack、SysTick 2 个示例。内核不提供独立的标准外设驱动,相关操作由 CMSIS 层提供;系统主频、时钟树与内核异常请配合用户手册的系统章节阅读,芯片已知问题以勘误手册为准。
5.3.1 相关文件
| 相对路径 | 作用 |
|---|---|
firmware/CMSIS/core/core_cm0.h |
定义 Cortex-M0 内核的寄存器与 CMSIS 内联函数:__set_PSP、__set_CONTROL、__ISB、SysTick_Config 等,示例直接调用这些接口 |
firmware/CMSIS/device/system_n32g033.c |
芯片系统初始化与 SystemCoreClockUpdate、SystemCoreClock 的实现,示例用它取得系统主频 |
projects/n32g033_EVAL/examples/Cortex-M0/ |
Cortex-M0 示例根目录,含 ProcessStack、SysTick 2 个示例 |
projects/n32g033_EVAL/examples/Cortex-M0/SysTick/ |
该示例的工程目录组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt |
projects/n32g033_EVAL/bsp/ |
示例共用的板级支持文件:log.h 提供串口日志接口 |
5.3.2 API 参考
Cortex-M0 内核不提供独立的标准外设驱动库,本模块因此没有 :::devkit-api 条目与函数卡片。内核相关的操作都由 CMSIS 提供:firmware/CMSIS/core/core_cm0.h 定义控制寄存器读写与 SysTick_Config 等内联函数,firmware/CMSIS/device/system_n32g033.c 提供系统初始化与主频更新。两个示例直接调用这些接口完成线程模式堆栈切换与系统节拍配置,移植时把相应的内联函数与主频变量一起带走即可。
5.3.3 Demo 示例说明
本节说明 Cortex-M0 的 2 个示例:ProcessStack 演示线程模式堆栈从主栈切换到进程栈并在异常后自动切回,SysTick 演示如何配置 10 ms 系统节拍中断。
5.3.3.1 ProcessStack 线程模式堆栈切换
工程目录: projects/n32g033_EVAL/examples/Cortex-M0/ProcessStack/
声明栈大小与堆栈取值宏→清零进程堆栈区→设置进程栈指针并切换到进程栈→执行同步指令刷新流水线→触发软件异常验证中断上下文使用主栈→读回控制寄存器确认切换成功
| 项目 | 源码解读 |
|---|---|
| 功能 | 演示线程模式堆栈从主栈切换到进程栈并在异常进入后自动切回:切换后读控制寄存器确认使用进程栈,触发异常后中断使用主栈,返回后仍是进程栈。 |
| 源码组成 | src/main.c 中的 main 完成堆栈切换与验证,并定义软件异常调用的内联汇编;src/n32g033_it.c 中的 SVC_Handler 记录中断上下文使用的堆栈;inc/main.h、inc/n32g033_it.h 给出声明;readme.txt 给出测试步骤;工程文件在 MDK-ARM/ 与 EWARM/。 |
| 关键配置 | 进程栈区大小 0x200;控制寄存器取 0x02 选择进程栈,取 0x00 表示主栈;系统时钟 64 MHz,串口 115200,TX 为 PA9、RX 为 PA10。 |
| 程序与数据路径 | main → log_init → 清零进程堆栈区 → __set_PSP 设置进程栈顶 → __set_CONTROL 切换到进程栈 → __ISB 刷新流水线 → 读 CONTROL 校验 → 触发软件异常 → SVC_Handler 读回中断上下文堆栈 → 返回后再读 CONTROL 确认为进程栈。 |
| 主要 API | __set_PSP、__set_CONTROL、__ISB、__get_CONTROL、__get_PSP |
| 验证方法 | 硬件基于评估板 N32G033K8Q7-1_STB V1.0,串口波特率 115200; 编译下载复位运行后,串口打印 Cortex-M0 ProcessStack Test Success 表示切换与自动切回都正确。 |
| 使用提示 | 系统时钟 64 MHz; 也可用编译器的 Cortex 寄存器窗口观察线程模式下的堆栈与代码访问级别。 |
核心代码解读与移植
示例把线程模式堆栈从主栈切到进程栈,再用一次软件异常验证异常上下文使用主栈、返回后自动切回进程栈。
数据路径: 宏声明栈大小与堆栈取值 → 清零进程堆栈区 → __set_PSP 设置进程栈顶 → __set_CONTROL 切换到进程栈 → __ISB 刷新流水线 → 读回 CONTROL 与 PSP 校验 → 软件异常后 SVC_Handler 读回中断上下文堆栈
1. 栈大小与堆栈取值先用宏定好。 进程栈区大小与两种堆栈的取值都写成宏,切换与校验两侧共用同一组常量。
#define SP_PROCESS_SIZE 0x200 /* Process stack size */
#define SP_PROCESS 0x02 /* Process stack */
#define SP_MAIN 0x00 /* Main stack */
SP_PROCESS_SIZE 取 0x200 表示进程栈区 512 字节;两个取值直接对应控制寄存器的堆栈选择位。
2. 设置进程栈指针、切换堆栈并读回校验。 先把进程栈指针指向栈顶,再改控制寄存器选择进程栈,随后用同步指令刷新流水线并读回确认。
/* Set Process stack value */
__set_PSP((uint32_t)PSPMemAlloc + SP_PROCESS_SIZE);
/* Select Process Stack as Thread mode Stack */
__set_CONTROL(SP_PROCESS);
/* Execute ISB instruction to flush pipeline as recommended by Arm */
__ISB();
/* Get the Thread mode stack used */
CurrentStack = (__get_CONTROL() & 0x02);
/* Get process stack pointer value */
PSPValue = __get_PSP();
切换堆栈后必须执行同步指令,否则后续指令可能仍按旧堆栈取值执行。
3. 用软件异常验证异常上下文使用主栈。 触发一次软件异常后,中断服务读回控制寄存器,确认异常上下文不再使用进程栈。
void SVC_Handler(void)
{
IsrStack = (__get_CONTROL() & 0x02);
}
异常进入时硬件自动改用主栈,返回线程模式后再恢复原来的堆栈选择,移植时可直接沿用这段校验逻辑。
对应源码: src/main.c、src/n32g033_it.c、firmware/CMSIS/core/core_cm0.h
5.3.3.2 SysTick 系统节拍中断
工程目录: projects/n32g033_EVAL/examples/Cortex-M0/SysTick/
更新系统主频变量→按 10 ms 计算并设置 SysTick 重装值→使能 SysTick 中断→节拍中断触发后累加计数→计数为偶数时串口打印计数值
| 项目 | 源码解读 |
|---|---|
| 功能 | 配置 SysTick 每 10 ms 产生一次中断,中断服务里累加计数,计数为偶数时通过串口打印。 |
| 源码组成 | src/main.c 中的 main 更新主频并配置 SysTick;src/n32g033_it.c 中的 SysTick_Handler 累加计数并按需打印;inc/main.h、inc/n32g033_it.h 给出声明;readme.txt 给出测试步骤;工程文件在 MDK-ARM/ 与 EWARM/。 |
| 关键配置 | 系统时钟 64 MHz; 分频系数取 100,使节拍周期为 10 ms; 串口 115200,TX 为 PA9、RX 为 PA10。 |
| 程序与数据路径 | main → log_init → SystemCoreClockUpdate 更新主频 → SysTick_Config 按 10 ms 配置并启动节拍 → 主循环空转 → 每 10 ms 进入 SysTick_Handler 累加 Tick_num → 计数为偶数时打印计数值。 |
| 主要 API | SystemCoreClockUpdate、SystemCoreClock、SysTick_Config |
| 验证方法 | 硬件基于评估板 N32G033K8Q7-1_STB V1.0,串口波特率 115200; 编译下载复位运行后,串口约每 20 ms 打印一行计数值。 |
| 使用提示 | 系统时钟 64 MHz; 改节拍周期时同步调整分频系数,避免重装值超出计数器的位宽。 |
核心代码解读与移植
示例按系统主频算出一个 10 ms 的节拍周期并启动 SysTick,在中断里累加计数并按偶数条件打印。
数据路径: 宏声明分频系数并定义计数变量 → SystemCoreClockUpdate 更新主频 → SysTick_Config 按 10 ms 配置并启动节拍 → 主循环空转 → 每 10 ms 进入 SysTick_Handler 累加计数 → 偶数时打印
1. 分频系数与计数变量先定好。 分频系数决定节拍周期,计数变量用来反映中断触发次数,两者都在文件作用域声明。
#define SYSTICK_10MS ((uint32_t)100)
uint32_t Tick_num = 0;
SYSTICK_10MS 取 100 表示把主频等分 100 份,得到 10 ms 的节拍周期;改周期时改这个宏即可。
2. 更新主频并按 10 ms 配置 SysTick。 先按寄存器重算当前主频,再用主频除以分频系数设置重装值并启动节拍中断。
int main(void)
{
/* USART Init */
log_init();
log_info("Cortex-M0 SysTick \r\n");
/* Get SystemCoreClock */
SystemCoreClockUpdate();
/* Config 10ms SysTick */
SysTick_Config(SystemCoreClock/SYSTICK_10MS);
while (1)
{
}
}
主频必须先更新,否则重装值按旧主频计算,实际节拍周期会与预期不符。
3. 中断里累加计数并按条件打印。 每次节拍中断给计数变量加一,计数为偶数时打印一行,避免打印过于频繁。
void SysTick_Handler(void)
{
Tick_num++;
if((Tick_num%2) == 0)
{
log_info("Cortex-M0 SysTick IRQ %d\r\n",Tick_num);
}
}
中断里的打印会占用一定时间;节拍周期很短时,应把需要打印的信息先缓存再在主流程输出。
对应源码: src/main.c、src/n32g033_it.c、firmware/CMSIS/core/core_cm0.h
5.3.4 注意与排障
| 场景 | 检查与建议 | 来源 |
|---|---|---|
| 系统主频读取 | 用户手册系统章节写明主频由时钟配置决定,SystemCoreClockUpdate 按寄存器重算 SystemCoreClock;改时钟树后先更新主频再计算节拍重装值。 |
用户手册 |
| 线程模式访问级别 | 用户手册内核相关章节写明线程模式可运行在特权级或非特权级; 切到非特权级前先把需要访问的外设配置好。 |
用户手册 |
| 异常上下文堆栈 | 用户手册内核相关章节写明进入异常后使用主栈、返回线程模式后恢复原有堆栈选择; 切换堆栈后按此验证行为。 |
用户手册 |
| SysTick 重装值 | 用户手册内核相关章节写明 SysTick 为递减计数器且有位宽上限; 重装值不能超过计数器位宽。 |
用户手册 |
勘误手册 V1.0.0(2025.9.25)针对 A 版芯片,未列出 Cortex-M0 相关条目。