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。

src/main.csrc/main.c
/* 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 位调用置位宏。

src/main.csrc/main.c · main
    /* 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 同一位,两次写入后的值都通过串口输出。

src/main.csrc/main.c · main
        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 预留。

src/main.csrc/main.c
#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,再选择进程堆栈并读回当前使用的堆栈与堆栈指针。

src/main.csrc/main.c · main
    __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 异常。

src/main.csrc/main.c · main
    /* 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),返回线程模式后即为特权访问。

src/n32g41x_it.csrc/n32g41x_it.c · SVC_Handler
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 相关条目。

国民技术开发套件文档中心

按芯片系列在线查阅国民技术 MCU 开发套件的芯片资料、SDK、驱动 API 与 Demo 示例说明。

© 2026 国民技术股份有限公司 版权所有粤ICP备11069485号本站使用不含 Cookie 的匿名访问统计