5.4 Cortex-M4 使用说明
本章说明 N32G41x 中 Cortex-M4 内核相关的两个示例:M4_BitBand 用位带别名地址改写 SRAM 中变量的单个位,M4_Mode_Privilege 在线程模式中切换堆栈与特权访问级别。这两个示例不调用标准外设库驱动,接口来自 CMSIS 内核头文件;位带别名区与 SRAM 的映射请配合用户手册的“存储器和总线架构”章节阅读,内核异常模型可参考 Arm Cortex-M4 技术参考手册,芯片已知问题以勘误手册为准。
5.4.1 相关文件
| 相对路径 | 作用 |
|---|---|
firmware/CMSIS/core/core_cm4.h |
定义 Cortex-M4 内核寄存器访问接口 __set_CONTROL、__get_CONTROL、__set_PSP、__get_PSP 与 SCB、SysTick 等内核结构体,并按编译器包含对应的 CMSIS 适配头 |
firmware/CMSIS/device/n32g41x.h |
定义 N32G41x 的外设寄存器结构体与中断号,并包含 core_cm4.h 与 system_n32g41x.h |
projects/n32g41x_EVAL/examples/Cortex-M4/ |
Cortex-M4 示例根目录,含 M4_BitBand、M4_Mode_Privilege 2 个示例 |
projects/n32g41x_EVAL/examples/Cortex-M4/M4_Mode_Privilege/ |
该示例的工程目录组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt |
projects/n32g41x_EVAL/bsp/ |
两个示例共用的板级支持文件:串口日志初始化与打印函数 |
5.4.2 API 参考
本模块不使用标准外设库驱动,示例直接调用 CMSIS 内核接口。firmware/CMSIS/core/core_cm4.h 经 cmsis_compiler.h 按编译器包含适配头,提供 __set_CONTROL、__get_CONTROL、__set_PSP、__get_PSP 等内核寄存器访问函数:M4_Mode_Privilege 用它们读写 CONTROL 与 PSP,M4_BitBand 直接操作位带别名地址,不涉及库函数。
5.4.3 Demo 示例说明
本节说明 Cortex-M4 的 2 个示例:M4_BitBand 通过位带别名地址改写 SRAM 变量的单个位并从串口打印结果,M4_Mode_Privilege 在线程模式中切换进程堆栈与特权访问级别。
5.4.3.1 M4_BitBand 位带别名访问
工程目录: projects/n32g41x_EVAL/examples/Cortex-M4/M4_BitBand/
初始化串口日志→计算变量 Data 的位带别名地址→对第 0~7 位逐位置 1 并打印→同一位清 0 并打印
| 项目 | 源码解读 |
|---|---|
| 功能 | 用位带别名地址对全局变量 Data 的第 0~7 位逐位置 1、清 0,并把每次写入后的值从串口打印出来。 |
| 源码组成 | src/main.c:RAM_BASE、RAM_BB_BASE 与三个位带宏、全局变量 Data 与 main;inc/main.h:包含 n32g41x.h;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能与环境说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | RAM_BASE 为 0x20000000,别名区基地址 RAM_BB_BASE 为 0x22000000;别名地址由 RAM_BB_BASE、变量地址相对 RAM_BASE 的字节偏移左移 5 位、以及位号左移 2 位三者按位或组成;全局变量 Data 为 __IO uint32_t。系统时钟为 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 调用 log_init 并打印 Cortex-M4F BitBand;把 &Data 存入 DataAddr,在 for (i = 0; i < 8; i++) 循环里对第 i 位调用 Data_SetBit_BB 后打印 Data,再调用 Data_ResetBit_BB 后打印 Data;循环结束后进入空 while(1)。 |
| 主要 API | 本示例不调用库函数,直接写位带别名地址;printf 与 log_init 来自板级日志支持。 |
| 验证方法 | 复位运行后串口先打印 Cortex-M4F BitBand,随后 8 组两行信息依次显示置 1 后的值与清 0 后的 0x00。 |
| 使用提示 | 位带别名访问的目标地址须落在被映射的 SRAM 位带区内; 开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1,硬件为 N32G412/N32G415。 |
核心代码解读与移植
示例在运行时算出全局变量 Data 的位带别名地址,再通过该地址逐位置 1、清 0 并打印结果。
数据路径: main → &Data 存入 DataAddr → Data_SetBit_BB 与 Data_ResetBit_BB 按别名公式组成地址并整字写 1 或 0 → Data 的值经 printf 输出到串口
1. 用宏定义位带基地址与访问宏。 RAM_BASE 与 RAM_BB_BASE 给出 SRAM 位带区及其别名区的基地址,Data_SetBit_BB 与 Data_ResetBit_BB 把换算出的别名地址整字写 1 或 0。
/* RAM address */
#define RAM_BASE ((uint32_t)0x20000000)
/* RAM bitband address */
#define RAM_BB_BASE ((uint32_t)0x22000000)
__IO uint32_t Data = 0, DataAddr = 0, DataBitValue = 0;
#define Data_ResetBit_BB(DataAddr, BitNumber) \
(*(__IO uint32_t*)(RAM_BB_BASE | ((DataAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 0)
#define Data_SetBit_BB(DataAddr, BitNumber) \
(*(__IO uint32_t*)(RAM_BB_BASE | ((DataAddr - RAM_BASE) << 5) | ((BitNumber) << 2)) = 1)
#define Data_GetBit_BB(DataAddr, BitNumber) (Data >> BitNumber)
移植到其它带位带区的型号时,RAM_BASE、RAM_BB_BASE 要同步改为该型号 SRAM 位带区与别名区的基地址。
2. 取变量地址并逐位置 1。 main 把 &Data 存入 DataAddr,再在循环里用同一个别名地址对第 i 位调用置位宏。
/* Get the variable address */
DataAddr = (uint32_t)&Data;
/* Modify variable bit using bit-band access */
for (i = 0; i < 8; i++)
{
/* Modify Data variable bit i */
Data_SetBit_BB(DataAddr, i); /* Data = (0x00000001 << i) */
循环上界 8 决定逐位操作的位数;改用其它变量时只需把 &Data 换成目标变量地址。
3. 清 0 并打印每次写入结果。 每置 1 之后随即清 0 同一位,两次写入后的值都通过串口输出。
printf("Set bit %d of variable Data to 1 = 0x%02X \r\n", i, Data);
Data_ResetBit_BB(DataAddr, i); /* Data = 0x00000000 */
printf("Set bit %d of variable Data to 0 = 0x%02X \r\n", i, Data);
}
对应源码: src/main.c
5.4.3.2 M4_Mode_Privilege 线程模式特权与堆栈切换
工程目录: projects/n32g41x_EVAL/examples/Cortex-M4/M4_Mode_Privilege/
初始化串口日志→清零进程堆栈缓冲区并设置 PSP→切换线程模式使用进程堆栈→置 CONTROL 位进入非特权模式→经 SVC 异常恢复特权模式
| 项目 | 源码解读 |
|---|---|
| 功能 | 把线程模式从主堆栈切换到进程堆栈,再在非特权与特权访问级别之间切换,并用串口打印每次切换的结果。 |
| 源码组成 | src/main.c:四个模式位宏、__SVC 的编译器分支实现、进程堆栈数组 PSPMemAlloc 与 main;inc/main.h:包含 n32g41x.h;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数,含 SVC_Handler;readme.txt:功能与环境说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | SP_PROCESS_SIZE 为 0x200,进程堆栈由 __IO uint8_t PSPMemAlloc[0x200] 预留;SP_PROCESS 为 0x02、SP_MAIN 为 0x00、THREAD_MODE_PRIVILEGED 为 0x00、THREAD_MODE_UNPRIVILEGED 为 0x01;__SVC 在 __CC_ARM、__ICCARM__、__GNUC__ 下分别以汇编或内联汇编触发 svc 0x01。系统时钟为 HSI+PLL,N32G412 为 80 MHz,N32G415 为 96 MHz。 |
| 程序与数据路径 | main 调用 log_init 并打印首行;清零 PSPMemAlloc 后 __set_PSP 设置进程堆栈顶,__set_CONTROL 写入 SP_PROCESS 切到进程堆栈并读回 CurrentStack 与 PSPValue;再以 THREAD_MODE_UNPRIVILEGED 与 SP_PROCESS 的按位或写入 CONTROL 并打印结果;非特权下写 THREAD_MODE_PRIVILEGED 无法恢复特权,改为 __SVC 触发 SVC_Handler 执行 __set_CONTROL(0x00) 恢复特权并打印结果。 |
| 主要 API | __set_PSP、__set_CONTROL、__get_CONTROL、__get_PSP |
| 验证方法 | 复位运行后串口依次打印 Cortex-M4F Mode Privilege、Cortex-M4F enter Unprivileged Mode OK、Cortex-M4F enter Privileged Mode OK;线程模式的堆栈与访问级别可在编译器的 Cortex 寄存器窗口中查看。 |
| 使用提示 | 非特权线程模式下 MSR 等指令与 NVIC、SysTick 等系统控制空间寄存器的访问受限制;开发环境为 KEIL MDK-ARM V5.34.0.0 或 IAR EWARM 8.50.1,硬件为 N32G412/N32G415。 |
核心代码解读与移植
示例先切换到进程堆栈,再用 CONTROL 寄存器在线程模式中切换特权级别,最后经 SVC 异常回到特权模式。
数据路径: main → __set_PSP 写入进程堆栈顶 → __set_CONTROL 写入 SP_PROCESS 选择进程堆栈 → __set_CONTROL 写入 CONTROL[0] 切换特权级别 → __SVC 触发 SVC_Handler → __set_CONTROL(0x00) 恢复特权
1. 定义模式位宏、__SVC 与进程堆栈。 文件开头给出四个模式位宏,__SVC 按编译器分支触发 svc 0x01,进程堆栈由 PSPMemAlloc 预留。
#define SP_PROCESS_SIZE 0x200 /* Process stack size */
#define SP_PROCESS 0x02 /* Process stack */
#define SP_MAIN 0x00 /* Main stack */
#define THREAD_MODE_PRIVILEGED 0x00 /* Thread mode has privileged access */
#define THREAD_MODE_UNPRIVILEGED 0x01 /* Thread mode has unprivileged access */
/* clang-format off */
#if defined ( __CC_ARM )
__ASM void __SVC(void)
{
SVC 0x01
BX R14
}
#elif defined ( __ICCARM__ )
static __INLINE void __SVC() { __ASM ("svc 0x01");}
#elif defined ( __GNUC__ )
static __INLINE void __SVC() { __ASM volatile ("svc 0x01");}
#endif
/* clang-format on */
__IO uint8_t PSPMemAlloc[SP_PROCESS_SIZE];
__IO uint32_t Index = 0, PSPValue = 0, CurrentStack = 0, ThreadMode = 0;
移植到其它编译器时,需要在 __CC_ARM、__ICCARM__、__GNUC__ 分支之外补充对应的 __SVC 实现。
2. 切换到进程堆栈并读回当前堆栈。 main 先把进程堆栈顶写入 PSP,再选择进程堆栈并读回当前使用的堆栈与堆栈指针。
__set_PSP((uint32_t)PSPMemAlloc + SP_PROCESS_SIZE);
/* Select Process Stack as Thread mode Stack */
__set_CONTROL(SP_PROCESS);
/* Get the Thread mode stack used */
if ((__get_CONTROL() & 0x02) == SP_MAIN)
{
/* Main stack is used as the current stack */
CurrentStack = SP_MAIN;
}
else
{
/* Process stack is used as the current stack */
CurrentStack = SP_PROCESS;
/* Get process stack pointer value */
PSPValue = __get_PSP();
}
进程堆栈顶为 PSPMemAlloc 数组末尾,调整 SP_PROCESS_SIZE 时要保证数组大小与堆栈顶一致。
3. 进入非特权模式并请求恢复。 置 CONTROL[0] 进入非特权模式并打印结果;非特权下无法直接写回特权位,改为触发 SVC 异常。
/* Switch Thread mode from privileged to unprivileged */
/* Thread mode has unprivileged access */
__set_CONTROL(THREAD_MODE_UNPRIVILEGED | SP_PROCESS);
/* Unprivileged access mainly affect ability to:
- Use or not use certain instructions such as MSR fields
- Access System Control Space (SCS) registers such as NVIC and SysTick */
/* Check Thread mode privilege status */
if ((__get_CONTROL() & 0x01) == THREAD_MODE_PRIVILEGED)
{
/* Thread mode has privileged access */
ThreadMode = THREAD_MODE_PRIVILEGED;
printf("Cortex-M4F enter Unprivileged Mode Fail \r\n");
}
else
{
/* Thread mode has unprivileged access*/
ThreadMode = THREAD_MODE_UNPRIVILEGED;
printf("Cortex-M4F enter Unprivileged Mode OK \r\n");
}
/* Switch back Thread mode from unprivileged to privileged */
/* Try to switch back Thread mode to privileged (Not possible, this can be
done only in Handler mode) */
__set_CONTROL(THREAD_MODE_PRIVILEGED | SP_PROCESS);
/* Generate a system call exception, and in the INTSTS switch back Thread mode to privileged */
__SVC();
非特权线程模式下,MSR 等指令与 NVIC、SysTick 等系统控制空间寄存器的访问受限制。
4. 在 SVC_Handler 中恢复特权模式。 SVC 异常把处理器切到处理模式,处理器在此执行 __set_CONTROL(0x00),返回线程模式后即为特权访问。
void SVC_Handler(void)
{
/* Switch Thread mode from privileged to unprivileged */
__set_CONTROL(0x00);
}
对应源码: src/main.c、src/n32g41x_it.c
5.4.4 注意与排障
| 场景 | 检查与建议 | 来源 |
|---|---|---|
| 位带别名地址的换算 | 用户手册“存储器和总线架构”章的 Bit banding 小节给出换算公式 bitband_byte_addr = bitband_base + (byte_offset × 32) + (bit_number × 4),其中 byte_offset 为含目标位的字节在位段区的序号,bit_number 为目标位在位内的位置(0~7),SRAM 位带别名区基地址为 0x22000000。 |
用户手册 |
| 位带区覆盖的地址范围 | 同一章的 SRAM 小节写明 N32G412/N32G415 的 SRAM 容量最大 24 KB、地址范围为 0x20000000~0x20005FFF; SRAM 被映射到位段区,可通过位带别名完成原子读改写。 |
用户手册 |
N32G412、N32G415 系列勘误手册 V1.0.0(2026.6.30)的勘误列表只列出一行 A 版芯片条目,勘误链接列为空,未列出 Cortex-M4 相关条目。