Cortex-M4中断与内存管理:PRIMASK、BASEPRI、内存屏障与位带操作详解

发布时间:2026/7/27 19:56:11

Cortex-M4中断与内存管理:PRIMASK、BASEPRI、内存屏障与位带操作详解 1. 项目概述在嵌入式系统尤其是基于ARM Cortex-M系列内核的开发中中断和内存管理是决定系统实时性、稳定性和效率的两大基石。很多开发者尤其是从应用层转向底层开发的工程师往往对这两个概念的理解停留在“中断来了就跳转”、“内存就是变量存放的地方”的层面。然而当你开始设计一个需要精确时序控制的电机驱动、一个要求高可靠性的通信协议栈或者一个需要动态加载代码的复杂应用时你会发现如果不深入理解处理器如何“看待”和“管理”中断与内存调试过程将如同在黑暗中摸索。我遇到过不少项目代码在逻辑上完全正确但在高负载或特定时序下会出现偶发的数据错乱、中断丢失甚至死机。追根溯源问题往往不是出在算法而是出在对硬件机制的理解偏差上。比如一个关键任务在执行到一半时被低优先级中断打断导致时序错乱或者在多总线并发访问外设寄存器时因为访问顺序问题配置未能及时生效。这些问题的解决离不开对PRIMASK、FAULTMASK、BASEPRI这类异常屏蔽寄存器以及内存映射、内存类型、内存屏障等内存管理机制的透彻掌握。本文将以TI的TM4C129X微控制器基于Cortex-M4F内核为例抛开枯燥的寄存器手册描述从一个一线开发者的视角拆解这些核心机制的工作原理、设计考量并分享在实际项目中如何正确、高效地使用它们。我们会从“为什么需要它们”开始深入到“具体怎么用”最后总结“用的时候有哪些坑”。无论你是正在学习Cortex-M架构的新手还是希望优化现有系统性能的资深工程师相信都能从中获得一些直接的、可落地的启发。2. 核心寄存器深度解析异常管理的三道闸门在Cortex-M架构中异常包括中断的管理核心是嵌套向量中断控制器NVIC。但NVIC负责的是“派发”而决定一个异常能否被处理器“响应”或“激活”则依赖于几个关键的全局屏蔽寄存器。它们就像是系统异常流上的三道闸门给了开发者精细控制系统响应行为的权力。2.1 PRIMASK全局可屏蔽异常的开关PRIMASK寄存器是异常管理中最直接、最“粗暴”的一把锁。它是一个只有1位有效的寄存器Bit 0。2.1.1 工作原理与操作方式当PRIMASK置1时它会屏蔽所有优先级可配置的异常。注意这里强调的是“优先级可配置”。这意味着像非屏蔽中断NMI和硬故障HardFault这类具有固定最高优先级、用于处理最严重系统错误的异常是不受PRIMASK影响的。复位Reset自然也不受影响。它的操作必须在特权模式下进行。在C代码中我们通常使用CMSIS-Core标准接口来操作这比直接写汇编更安全、可读性更好// 方法一使用CMSIS-Core函数 #include “core_cm4.h” void EnterCriticalSection(void) { __disable_irq(); // 设置PRIMASK1 关总中断 // 或者使用 __set_PRIMASK(1); } void ExitCriticalSection(void) { __enable_irq(); // 清除PRIMASK0 开总中断 // 或者使用 __set_PRIMASK(0); } // 方法二内联汇编了解原理不推荐日常使用 __asm void Set_PRIMASK(void) { CPSID i // 设置PRIMASK1关中断 } __asm void Clear_PRIMASK(void) { CPSIE i // 清除PRIMASK0开中断 }2.1.2 设计意图与典型应用场景PRIMASK的设计意图非常明确为极短的关键代码段提供绝对不被中断打扰的执行环境。这种“关键”通常体现在时序的严格性上。场景一实时性要求极高的短任务例如在生成一个精确的PWM脉冲边沿、读取一个高速ADC采样值的瞬间或者操作一个非原子性的硬件寄存器序列时。任何中断的插入都可能破坏时序导致脉冲宽度错误或数据读取错误。场景二保护非重入的软件临界区虽然Cortex-M的中断处理机制本身支持嵌套但如果你有一段操作全局链表或复杂数据结构的代码它可能不是中断安全的。在操作前关闭中断是最简单的保护方法。实操心得PRIMASK的使用铁律能不用则不用关闭全局中断是影响系统实时性的“核武器”。应首先考虑通过优化中断服务程序ISR长度、使用BASEPRI进行优先级屏蔽等更精细的方法。用时尽可能短关中断的时间必须极短通常建议在几十个时钟周期内完成。长时间关中断会导致中断响应延迟急剧增加可能错过重要事件。避免嵌套与匹配确保__disable_irq()和__enable_irq()成对出现尤其是在有多个函数调用路径的复杂代码中。不匹配的开关操作是导致系统随机锁死的常见原因。可以考虑使用引用计数或RAII资源获取即初始化模式在C中管理。注意编译器优化编译器可能会对关中断区间内的代码进行重排序。如果临界区内有对多个易失性volatile变量的操作且顺序重要可能需要使用内存屏障后文详述。2.2 FAULTMASK故障处理时的终极屏障FAULTMASK可以看作是PRIMASK的“增强版”或“特殊模式版”。当FAULTMASK置1时它会屏蔽除NMI之外的所有异常。也就是说连硬故障HardFault都会被屏蔽。2.2.1 与PRIMASK的核心区别这个区别是根本性的。PRIMASK屏蔽的是“可配置优先级异常”把处理严重错误的能力HardFault留给了系统。而FAULTMASK则是在系统已经发生严重错误进入了HardFault等故障处理程序后为了防止故障处理程序本身被其他异常包括更低优先级的故障打断而设计的。它用于实现一种“故障处理的安全模式”。处理器有一个关键行为在退出除NMI处理程序外的任何异常处理程序时硬件会自动清除FAULTMASK位。这意味着你无法在普通的线程模式下设置FAULTMASK并保持它它的生命周期被严格限制在异常处理上下文内。2.2.2 实际应用与注意事项普通应用程序员几乎不会直接操作FAULTMASK。它主要用在操作系统的错误处理、或深度嵌入式系统自定义的致命错误恢复流程中。例如在FreeRTOS的vApplicationStackOverflowHook或configASSERT触发的错误处理中系统可能希望进入一个最简化的状态只响应最顶级的NMI比如看门狗复位此时可以设置FAULTMASK。// 示例在HardFault处理程序中可能使用FAULTMASK void HardFault_Handler(void) { __asm volatile ( “cpsid f” // 设置FAULTMASK1屏蔽所有低于NMI的异常 ); // 进行最简化的错误日志记录或系统状态保存 // ... while(1); // 等待看门狗或NMI复位 // 退出时硬件会自动清除FAULTMASK }重要提示在Cortex-M3/M4中FAULTMASK的优先级比PRIMASK高。一旦FAULTMASK置1即使清除PRIMASK中断也不会被响应。这进一步印证了它的“终极屏障”定位。2.3 BASEPRI基于优先级的精细化管理如果说PRIMASK是“一刀切”那么BASEPRI就是“精确制导”。BASEPRI寄存器允许你设置一个优先级阈值所有优先级号大于或等于此阈值的异常都会被屏蔽。这里需要理解Cortex-M的优先级规则优先级数字越小优先级越高。优先级0为最高通常保留给Reset、NMI和HardFault。可配置优先级通常从1开始。2.3.1 工作机理详解BASEPRI是一个8位寄存器在Cortex-M4上实际使用高3位[7:5]表示优先级0-7。当它被设置为一个非零值X时意味着“屏蔽所有优先级值 X的异常”。因为优先级数字越大实际优先级越低。例如__set_BASEPRI(0x40);// 二进制01000000优先级值4取高3位010效果屏蔽优先级为4,5,6,7的异常。优先级为0,1,2,3的异常仍可响应。__set_BASEPRI(0x00);或__set_BASEPRI(0);效果不屏蔽任何异常这是复位默认值。2.3.2 工程实践中的巨大优势BASEPRI是构建健壮实时系统的利器它实现了动态优先级天花板的概念。场景保护共享资源。假设有一个SPI总线驱动被多个任务或中断共享。SPI操作必须原子完成。我们可以将SPI驱动函数运行的“临界区”优先级提升到一个特定值。#define PROTECT_PRIORITY 0x60 // 优先级值6 (二进制01100000) void SPI_TransmitCritical(uint8_t* data, uint32_t len) { uint32_t old_basepri __get_BASEPRI(); // 保存当前BASEPRI __set_BASEPRI(PROTECT_PRIORITY); // 屏蔽优先级6的中断 // 执行SPI传输操作此时只有优先级0-5的中断能打断我们 SPI_SendData(data, len); __set_BASEPRI(old_basepri); // 恢复之前的BASEPRI }这样做的好处是高优先级、更紧急的中断如紧急停止信号优先级设为2仍然能够得到响应系统不会因为保护SPI而完全丧失实时性。这比直接用PRIMASK关总中断要优越得多。场景创建可嵌套的临界区。通过保存和恢复BASEPRI可以安全地实现临界区的嵌套调用而不会像开关中断那样容易出错。2.3.3 优先级分组与BASEPRICortex-M的NVIC支持优先级分组将8位优先级分为“抢占优先级”和“子优先级”。BASEPRI屏蔽的是“抢占优先级”部分。在配置NVIC优先级分组时需要同步考虑BASEPRI的设置值确保逻辑一致。例如如果使用优先级分组22位抢占6位子那么BASEPRI比较的将是高2位抢占优先级。3. 内存模型与访问机制理解处理器的“视角”寄存器控制着CPU的“行为”而内存模型则定义了CPU与外部世界的“交互规则”。很多玄学般的硬件问题根源在于对内存访问特性的误解。3.1 内存映射地址空间的战略划分Cortex-M4F处理器拥有4GB的线性地址空间这个空间被预先划分成多个具有不同属性的区域。以TM4C129X为例其内存映射是芯片厂商根据设计定义的。3.1.1 关键区域解析Code区域 (0x0000 0000 - 0x1FFF FFFF)通常映射到片内Flash。其内存类型为Normal且可执行非XN。处理器有独立的I-Code和D-Code总线可以同时从该区域取指和读取数据这是将代码放在此区域能获得最佳性能的原因。SRAM区域 (0x2000 0000 - 0x3FFF FFFF)通常映射到片内SRAM。类型也是Normal可执行。变量、堆栈通常位于此区域。前1MB (0x2000 0000 - 0x200F FFFF) 支持位带操作。Peripheral区域 (0x4000 0000 - 0x5FFF FFFF)映射到所有外设寄存器。类型为Device且标记为XN不可执行。这意味着CPU不能从这里取指令执行这是重要的安全特性。前1MB也支持位带操作。PPB区域 (0xE000 0000 - 0xE00F FFFF)私有外设总线包含NVIC、SysTick、MPU、FPU等核心外设。类型为Strongly Ordered。访问这里的寄存器有最严格的顺序保证。3.1.2 内存类型Memory Type的深刻影响这是理解内存访问行为的核心。手册中定义了三种类型NormalCPU和总线系统为了性能可以对访问进行重排序Reordering和预取Speculative Read。例如连续两次不相关的变量读操作后一条可能会被提前执行。这对程序正确性通常无害因为逻辑结果不变。Device访问必须保持相对于其他Device或Strongly Ordered访问的顺序。但总线可以缓冲Buffer写操作。例如你先写外设A的配置寄存器再写外设B的启动寄存器。系统保证A的写先于B的写到达总线但A的写操作本身可能在CPU指令流完成后才真正完成。Strongly Ordered最严格的类型。访问必须保持相对于所有其他访问的顺序且写操作通常不能被缓冲。对SysTick或NVIC寄存器的写操作会立即生效。3.1.3 一个常见的误解与实例假设我们要配置一个UARTUART0-CTL 0x0; // 1. 先禁用UART UART0-IBRD 104; // 2. 设置波特率除数 UART0-FBRD 11; // 3. UART0-LCRH 0x60; // 4. 设置数据格式 UART0-CTL 0x301; // 5. 重新使能UART由于UART外设在Peripheral区域Device类型CPU和总线必须保证这5条写指令的相对顺序1一定在2之前2在3之前...。但是并不能保证当CPU执行完第5条指令时第1条指令的写操作已经真正在UART硬件上生效了。Device类型允许写操作被缓冲。在绝大多数情况下这没有问题因为软件后续会等待。但在极精密的时序控制或前后有依赖的配置中就需要引入内存屏障。3.2 内存屏障指令强制排序的尚方宝剑内存屏障Memory Barrier指令用于在需要的时候强制CPU和内存系统遵守我们期望的访问顺序。Cortex-M4提供了三条DMB (Data Memory Barrier)确保在该指令之前的所有内存访问读/写都完成后才执行其之后的内存访问。它只约束内存访问之间的顺序。DSB (Data Synchronization Barrier)比DMB更严格。确保在该指令之前的所有内存访问都完成后才执行其之后的任何指令包括非内存访问指令。ISB (Instruction Synchronization Barrier)清空处理器的流水线确保在该指令之后执行的指令能够看到在其之前所有已完成的系统设置如修改CP15寄存器、切换地址空间的效果。3.2.1 必须使用内存屏障的经典场景自修改代码如果你在SRAM中动态生成或修改了指令然后跳转过去执行必须在修改代码后、跳转前使用ISB。// 在SRAM中写入指令代码 memcpy((void*)code_in_ram, machine_code, len); // 数据缓存清理如果有Cache... __DSB(); // 确保写操作对内存系统可见 __ISB(); // 清空流水线确保后续取指得到新指令 // 跳转到新代码执行 ((void(*)(void))code_in_ram)();修改向量表或中断优先级在运行时改变中断向量表地址或某个中断的优先级然后立即使能该中断。SCB-VTOR (uint32_t)my_new_vector_table; // 更改向量表地址 __DSB(); // 确保向量表地址写入完成 __ISB(); // 确保后续取指包括可能的中断取向量使用新地址 NVIC_EnableIRQ(USART0_IRQn);切换栈指针CONTROL寄存器ASP位在Thread模式下从MSP切换到PSP。__set_CONTROL(__get_CONTROL() | 0x02); // 设置ASP位使用PSP __ISB(); // 关键确保后续指令使用新的PSP // 之后的上下文如局部变量就会使用PSP了配置MPU内存保护单元在更改MPU区域设置后必须使用DSB和ISB使新配置立即生效。MPU-RNR region_number; MPU-RBAR base_address; MPU-RASR attributes; __DSB(); // 确保MPU配置写入完成 __ISB(); // 确保后续指令执行遵守新MPU规则避坑指南何时用DMB何时用DSB简单记法如果你关心的是内存操作之间的顺序用DMB如果你关心的是内存操作完成后才能做别的事用DSB。在修改完一段数据然后设置一个“数据就绪”标志供另一个线程或DMA查看时应该在设置标志前加DMB确保数据写入先于标志写入。在配置完一个外设如DMA控制器的源/目标地址后在启动DMA前加DSB确保所有配置写入都已完成DMA不会读到旧配置。3.3 位带操作实现原子位操作的硬件魔法位带Bit-Banding是Cortex-M系列一个非常实用的特性。它通过地址重映射将SRAM和外设区域特定地址的单个位“膨胀”到别名区域的一个完整32位字上。对这个字的读写直接映射为对原始位的原子操作。3.3.1 原理与地址计算公式手册已经给出。对于SRAM位带区0x20000000-0x200FFFFF的某个位其别名地址为别名地址 0x22000000 (字节偏移 * 32) (位序号 * 4)例如要原子地设置地址0x2000FF00字节的第2位bit 2字节偏移 0x2000FF00 - 0x20000000 0xFF00位序号 2别名地址 0x22000000 (0xFF00 * 32) (2 * 4) 0x22000000 0x1FE000 0x8 0x221FE008操作变得极其简单#define BITBAND_SRAM_REF(address, bit) ((volatile uint32_t *)(0x22000000 (((uint32_t)(address) - 0x20000000) * 32) ((bit) * 4))) volatile uint32_t *alias_word BITBAND_SRAM_REF(0x2000FF00, 2); *alias_word 0x00000001; // 原子地将该位置1 uint32_t bit_value *alias_word; // 原子地读取该位值非0即13.3.2 工程价值与替代方案位带的核心价值是原子性。在没有位带的情况下要置位一个位需要经历“读-改-写”三步REG | (1bit)这个操作在多线程或中断环境下不是原子的可能被打断导致数据竞争。然而位带也有其局限性地址范围固定只有SRAM和外设的前1MB支持。消耗地址空间1MB位带区需要32MB的别名空间。性能考量位带操作实际上是一次32位访问对于某些外设寄存器可能不如直接写整个寄存器高效。在现代编程中对于SRAM中的标志位更通用的替代方案是使用C11原子操作stdatomic.h或编译器提供的原子内置函数如GCC的__atomic_xxx。但对于外设寄存器的位操作位带仍然是Cortex-M上一个简洁有效的硬件解决方案。4. 系统控制寄存器与执行环境除了异常屏蔽CONTROL寄存器是另一个影响程序执行环境的核心寄存器它管理着栈指针和特权级别。4.1 CONTROL寄存器详解CONTROL寄存器主要控制两个关键方面ASP (Active Stack Pointer, bit 1)选择在Thread模式下使用主栈指针MSP还是进程栈指针PSP。0使用MSP复位默认值。1使用PSP。关键点在Handler模式即中断、异常处理中处理器强制使用MSP对此位的写操作被忽略。模式的切换通常通过异常进入/返回时加载的EXC_RETURN值自动完成。TMPL (Thread Mode Privilege Level, bit 0)控制Thread模式下的特权级别。0特权模式复位默认值。可以访问所有资源和指令。1非特权模式。访问受限例如不能操作NVIC、不能访问某些系统控制寄存器。关键点Handler模式总是特权模式。4.1.1 双栈机制的设计意义MSP和PSP的分离是RTOS实现任务隔离的基础。MSP用于操作系统内核和异常处理。所有异常共享同一个MSP保证了内核运行的稳定性。PSP每个用户任务可以拥有自己独立的PSP。当任务切换时只需保存/恢复当前任务的PSP即可实现了任务栈的物理隔离一个任务的栈溢出不会破坏内核或其他任务的栈。4.1.2 栈指针切换的实操步骤与陷阱从特权级线程模式使用MSP切换到非特权级线程模式使用PSP是一个RTOS启动第一个用户任务时的标准操作。步骤必须严格// 1. 准备任务的栈空间和初始上下文例如通过PendSV异常 // 假设 task_stack 是任务栈顶且已按异常帧格式初始化包含PC, LR, xPSR, R0-R12等 // 2. 将任务的初始栈指针PSP设置为准备好的栈顶 __set_PSP((uint32_t)task_stack); // 3. 切换到使用PSP并进入非特权级线程模式 // 注意必须先设置PSP再修改CONTROL uint32_t new_control __get_CONTROL(); new_control | (1 1); // 设置ASP位使用PSP new_control | (1 0); // 设置TMPL位进入非特权级 __set_CONTROL(new_control); // 4. 执行ISB指令这是最关键的一步确保后续指令使用新的栈指针(PSP) __ISB(); // 5. 此时CPU已处于非特权线程模式并使用PSP。 // 通常接下来会触发一个调度从PSP指向的栈中恢复任务上下文R0-R12, LR, PC, xPSR。致命陷阱忘记第4步的__ISB()。如果不加ISBCONTROL寄存器的更改可能不会立即生效随后几条指令可能包括函数调用、局部变量存取仍会错误地使用MSP导致栈数据错乱系统崩溃。这个错误非常隐蔽因为崩溃点可能远离切换代码。4.2 浮点上下文与FPCA位对于Cortex-M4F带浮点单元CONTROL寄存器的bit 2是FPCAFloating-Point Context Active。当该位为1时表示当前上下文中使用了浮点寄存器S0-S31和FPSCR硬件在异常入口时需要自动保存这些寄存器占用额外的栈空间。这由处理器自动管理但软件需要知晓其存在。在编写RTOS的任务切换代码时如果任务可能使用浮点运算就必须在保存/恢复上下文时检查FPCA位并决定是否保存浮点寄存器组否则会导致任务间浮点状态互相污染。5. 常见问题排查与调试技巧理解了原理但在实际调试中如何定位与这些机制相关的问题呢5.1 中断相关问题排查症状某个中断永不触发或触发一次后不再触发。检查PRIMASK/BASEPRI在调试器中查看PRIMASK或BASEPRI寄存器的值。可能是在某个临界区关闭中断后由于代码bug如提前返回、条件分支错误未能恢复。检查NVIC使能位使用NVIC_GetEnableIRQ()确认中断是否在NVIC中被使能。检查外设中断标志与使能确认外设本身的中断源是否已使能中断标志是否被清除。症状系统偶尔死锁尤其是在执行一段关中断的代码后。测量关中断时间使用一个高优先级定时器中断在进入和退出临界区时打时间戳。如果关中断时间过长例如超过100us需要重构代码。检查临界区嵌套确保__disable_irq()和__enable_irq()严格匹配。使用一个全局的“关中断深度”计数器来辅助调试。5.2 内存与位带相关问题排查症状访问某个外设寄存器位时相邻位被意外修改。确认是否使用位带如果你使用了位带别名地址进行操作这是正常且原子的。如果你希望进行“读-改-写”请检查操作是否被打断。更安全的方式是使用位带或者先关中断再操作。检查寄存器类型有些外设寄存器是“写1清除”或“读清零”类型直接位带写可能不符合其操作语义。症状配置了外设如DMA后立即启动但配置似乎未生效。插入内存屏障在启动命令前添加__DSB()指令确保所有配置写入已完成。DMAChannel-SRC_ADDR src_buf; DMAChannel-DST_ADDR dst_buf; DMAChannel-CONTROL ctrl_word; __DSB(); // 确保上面三条配置写入完成 DMAChannel-CONFIG CONFIG_ENABLE; // 最后使能症状修改了MPU配置或向量表后程序跑飞。检查屏障指令确认在MPU配置序列后、或向量表重定位后是否正确地使用了__DSB()和__ISB()。使用调试器查看在修改后立即通过调试器查看SCB-VTOR或MPU相关寄存器确认值是否已写入。5.3 栈与CONTROL相关问题排查症状任务一运行就产生UsageFault或MemManage Fault。检查PSP初始化确认在任务切换前PSP是否被正确初始化为该任务栈的有效地址通常是栈顶或栈顶向下某个对齐位置。检查CONTROL切换序列回顾切换代码是否遗漏了__ISB()。检查栈对齐Cortex-M4要求栈指针在异常入口时必须8字节对齐。确保任务栈初始化时栈指针是8字节对齐的。症状使用浮点的任务切换后浮点计算结果错误。检查上下文保存在RTOS的任务切换汇编代码中确认是否根据CONTROL.FPCA位自动保存和恢复了浮点寄存器组S0-S31, FPSCR。如果没有需要扩展你的上下文切换代码。6. 总结与最佳实践建议回顾Cortex-M4F的寄存器与内存管理其设计哲学是在提供强大硬件特性和确定性的同时给予软件最大的灵活度。要驾驭好它关键在于理解每项特性背后的“约束”和“保证”。优先使用BASEPRI慎用PRIMASK将BASEPRI作为管理临界区的首选工具。它允许高优先级中断保持响应提高了系统的实时性。PRIMASK只留给那些极短、且必须绝对独占的瞬间。理解内存类型善用内存屏障在头脑中建立起代码地址与内存类型的映射。当操作外设寄存器、修改关键系统配置VTOR, MPU或进行自修改代码时主动思考是否需要以及需要哪种内存屏障DMB/DSB/ISB。在不确定时多加一个DSB/ISB通常比少加更安全。利用位带简化原子操作对于SRAM或外设区中需要频繁、原子访问的标志位位带是高效的硬件解决方案。对于更复杂的同步转向C11原子操作或RTOS提供的信号量/互斥锁。双栈机制是RTOS的基石如果开发RTOS必须透彻理解MSP/PSP的切换机制和CONTROL寄存器的操作序列特别是__ISB()的不可或缺性。一个稳健的上下文切换函数是系统稳定的前提。调试时寄存器状态是第一线索遇到异常问题首先用调试器查看PRIMASK,BASEPRI,CONTROL,xPSR查看当前模式等核心寄存器的状态。它们往往能直接指出问题是中断被屏蔽、栈指针错误还是特权级不对。最后再分享一个我调试时常用的小技巧在工程中定义一个宏用于安全地进入/退出临界区并自动记录调用文件和行号在调试版本中。这样当系统因关中断过长而出问题时你能快速定位到是哪个函数、哪行代码导致的。#ifdef DEBUG #define ENTER_CRITICAL() \ do { \ uint32_t __primask __get_PRIMASK(); \ __disable_irq(); \ log_critical_entry(__FILE__, __LINE__, __primask); \ } while(0) #define EXIT_CRITICAL() \ do { \ log_critical_exit(__FILE__, __LINE__); \ __enable_irq(); \ } while(0) #else #define ENTER_CRITICAL() __disable_irq() #define EXIT_CRITICAL() __enable_irq() #endif硬件机制是冰冷的但理解它之后你就能写出与硬件和谐共舞的、稳定而高效的代码。希望这篇深入解析能帮你扫清在Cortex-M4F底层开发中的一些迷雾。

相关新闻