首页 / 资讯中心 / 文章详情

FreeRTOS临界段与Cortex-M中断屏蔽机制详解

FreeRTOS临界段与Cortex-M中断屏蔽机制详解 ★ FEATURED ARTICLE
我最早被临界段坑是在一个电机控制的底板项目上。代码跑着跑着偶尔出现一次转速跳变抓波形又抓不到。后来才发现是两个任务同时操作了一个全局变量A任务读到一半B任务把值改了A任务接着用错的数据去算PWM。从那时起我认真把FreeRTOS的中断配置和临界段机制翻了个底朝天。这篇文章就把那套东西整理出来重点讲Cortex-M内核上中断如何被屏蔽、BASEPRI寄存器怎么用、任务级和中断级临界段分别保护什么、以及那些配置宏到底每个都要设多少。不管你是刚把FreeRTOS跑起来、还是已经写过几个ISR、想把中断里的资源保护做扎实这篇文章都可以当一份能直接对照的参考。1. 临界段究竟在防什么从Cortex-M的中断模型说起1.1 一个变量被两个执行流同时改的现实场景先讲一个最典型的丢数据场景我拿它当临界段问题的引子。假设有一个全局变量g_motor_speed任务A每秒更新一次这个值任务B在另一个任务里读这个值去驱动显示。看起来没什么问题但如果任务B正在读这个32位变量的过程中任务A被调度器切进来改了这个变量就会出现读到一半的数据。这还不是最可怕的更麻烦的是Cortex-M允许中断随时打断任务代码如果在中断服务函数里也去写这个变量数据错乱的概率就更高。FreeRTOS解决这类问题的思路不是给每个变量加锁而是从禁止调度和屏蔽中断两个层面下手。前者保证任务间不会出现抢占后者保证中断不会半路插进来。这两种手段在源码里分别对应了taskENTER_CRITICAL()和taskENTER_CRITICAL_FROM_ISR()两个宏。它们背后的原理本质上都是Cortex-M内核提供的几个特殊寄存器。1.2 Cortex-M的优先级数字陷阱数字越小越紧急很多被Cortex-M中断优先级搞晕的人都是被反向的数值关系坑了。这里必须先纠正一个直觉ARM Cortex-M的NVIC里中断优先级寄存器的位数有限这些位拼成的数值越小优先级反而越高。比如0x00是最高的可编程优先级0xF0是最低的可编程优先级。这和大多数人习惯的数值大代表重要性高完全相反也是FreeRTOS所有中断配置宏里最容易出错的地方。更迷惑的是Cortex-M还可以设置中断优先级分组。在STM32这类芯片上优先级寄存器通常被拆成抢占优先级和子优先级两部分。FreeRTOS其实根本不区分这两部分它只看整个优先级数值的大小。也就是说不管你怎么配置分组FreeRTOS把优先级寄存器当成一个整体来处理只关心这个值能不能被临界段屏蔽。1.3 FreeRTOS把执行环境切成两个世界FreeRTOS内部把整个系统的运行环境分成两层。第一层是任务层任务之间通过调度器切换任务级临界段只需要把调度器暂停防止上下文切换发生。第二层是中断层中断一旦触发就完全不受调度器控制必须通过内核的寄存器才能把中断屏蔽掉。这两个世界对应了完全不同的保护方式在任务里你一般只需要保证不被其他任务抢占不需要连中断一起屏蔽但在中断服务函数里你面对的不是任务调度而是NVIC的中断嵌套机制。FreeRTOS针对这两种场景给出了两套独立的宏定义。后面所有麻烦的配置其实都是在回答一个问题你到底想屏蔽掉哪些优先级的中断2. 中断配置宏先搞懂这几个数值再写代码2.1 configPRIO_BITS与configMAX_SYSCALL_INTERRUPT_PRIORITY的作用打开FreeRTOSConfig.h你会看到两个最核心的中断配置宏configPRIO_BITS和configMAX_SYSCALL_INTERRUPT_PRIORITY。前者表示芯片优先级寄存器实际使用的位数。比如STM32用4位那就写configPRIO_BITS 4。这个值决定了FreeRTOS在运行时做优先级数值移位和比较的精度。如果你的芯片其实只实现了3位却写了4内核在处理中断屏蔽值时就会把不该屏蔽的优先级也屏蔽掉或者反过来把本该屏蔽的漏掉。configMAX_SYSCALL_INTERRUPT_PRIORITY则是生死攸关的选项。代码里它常被写作configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的升级版表示允许调用FreeRTOS API的最高中断优先级。注意这里最高指的是数值最小例如5。为什么不允许所有中断都调用API呢因为FreeRTOS内部很多API会操作临界段和链表如果中断优先级比内核临界段屏蔽的阈值还高它就能打断临界段执行导致内核数据结构损坏。所以安全边界就是所有调用API的中断优先级数值必须小于等于这个配置值也就是优先级本身不能高于这个阈值。从实际项目出发我建议你把这两个宏放到配置文件的同一个区域加上注释写明芯片型号和优先级位数来源。我见过不少项目是因为换了芯片后忘记改configPRIO_BITS临界段开关行为完全错乱最后只能靠仿真器单步追汇编才发现。2.2 优先级分组与临界段阈值的换算关系这个换算关系值得我说三遍。假设configMAX_SYSCALL_INTERRUPT_PRIORITY写成5对应4位优先级寄存器的场景。Cortex-M的优先级寄存器数值需要左移4位才是实际写入NVIC的值因为寄存器的高4位有效低4位通常是保留的。所以内核运行时实际使用的阈值是(5 (8 - configPRIO_BITS))也就是5 4等于0x50。此时BASEPRI寄存器被设置为0x50意味着所有优先级数值小于0x50的中断都会被屏蔽即0x00到0x40的这些更高优先级中断都进不来。而优先级数值大于等于0x50的中断比如0x60、0x70则照常触发。这个阈值设计保证了低优先级中断被临界段屏蔽而高优先级中断依旧能够响应适合那些对实时性要求极高的场景。如果你搞反了比如把configMAX_SYSCALL_INTERRUPT_PRIORITY配置成了比实际需求更高的优先级数值那就等于把更多中断排除在屏蔽范围外表面看系统更实时实际上临界段保护范围变小了。更糟糕的是某些高优先级ISR如果能调用API直接导致内核链表损坏这种问题非常隐蔽。2.3 配置错误案例数值贴反之后发生了什么分享一个我自己排过的故障。某个双电机控制板用了FreeRTOS中断里有一个编码器采集ISR优先级配置得挺高。系统偶尔死机死机前没有任何规律。我一开始怀疑是硬件干扰后来把端口中断屏蔽后现象消失。最后检查配置发现configMAX_SYSCALL_INTERRUPT_PRIORITY被写成了11。问题在哪编码器ISR的优先级数值是11正好小于等于阈值可以调用API。可编码器ISR的执行频率很高它在临界段操作共享变量时偶尔又触发了比自己优先级更高的另一个中断那个中断里也调用了API。两个API同时操作系统内部链表自然就崩了。解决办法是让所有会调用FreeRTOS API的中断优先级数值都大于配置阈值。注意是数值大于因为数值越大优先级越低这样这些中断才能被临界段屏蔽住。同时也绝不能让一个ISR在运行中被另一个也会动内核数据的ISR打断。这里留一个实操建议在项目启动早期把configASSERT打开同时把configMAX_SYSCALL_INTERRUPT_PRIORITY当一个安全边界来审视凡是ISR里用了queue、semaphore、taskNOTIFY这些API中断优先级就必须比这个配置值低。等所有ISR都梳理完再关掉configASSERT也不迟。3. 任务级临界段BASEPRI寄存器比PRIMASK高明在哪里3.1 BASEPRI为什么是任务级临界段的首选Cortex-M内核屏蔽中断的常用寄存器有两个PRIMASK和BASEPRI。PRIMASK就是个总开关把它置1系统里所有可屏蔽中断全被关掉。BASEPRI则灵活得多它只屏蔽优先级数值小于某个阈值的中断更高优先级的中断不影响。FreeRTOS任务级临界段默认用的是BASEPRI原因很简单如果临界段里有对时间要求极高的中断比如高频PWM控制或编码器计数用PRIMASK把它们全部关掉会导致极其严重的实时性损失而BASEPRI能保留一部分足够重要的中断。在官方移植包里底层的底子是这么写的static portFORCE_INLINE void vPortRaiseBASEPRI( void ) { uint32_t ulNewBASEPRI configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm volatile( msr basepri, %0 :: r ( ulNewBASEPRI ) ); }这段代码把configMAX_SYSCALL_INTERRUPT_PRIORITY对应的实际数值写进了BASEPRI。它成功把不高于这个优先级的中断全部屏蔽。为什么是“不高于”因为BASEPRI只会屏蔽优先级数值小于等于BASEPRI值的中断优先级数值大于这个值的中断不会被屏蔽。也就是说数字小的中断被关掉数字大的中断得以保留。3.2 底层汇编宏的拆解如果打开portmacro.h你会看到这组宏#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )portENABLE_INTERRUPTS()把BASEPRI写为0相当于不屏蔽任何中断。任务级临界段的进入与退出就是这两个宏的来回切换。这里有一个细节很多人没注意到在Cortex-M上BASEPRI寄存器写入后对中断屏蔽的生效有一点点延迟吗答案是几乎没有它是同步生效的。但从汇编指令到C代码之间会有编译器的优化排序问题所以官方在这个宏外层还加了内存屏障相关的处理。参考实现里会使用带优化屏障的复杂宏展开核心目的就是保证先关中断、再访问共享资源的顺序不会因为编译器优化而被颠倒。如果你自己在裸机环境下写BASEPRI操作也一定要用__asm volatile并加上memory约束。3.3 嵌套计数uxCriticalNesting的守护逻辑进临界段不是只能进一次。如果临界段里又调用了另一个函数那个函数内部又进了临界段会出现进入两次需要退出两次的情况。FreeRTOS用了一个全局变量uxCriticalNesting来记录嵌套层数。每次taskENTER_CRITICAL()都会让这个变量加1同时关中断每次taskEXIT_CRITICAL()都会减1只有减到0时才真正恢复中断。这个设计在裸机开发中很容易被忽略因为裸机里进临界段通常就是关中断一步、开中断一步嵌套用不好就会出现第二次退出就把中断打开而第一层临界段还没结束的问题。FreeRTOS这种计数式嵌套的好处是把进入几次、退出几次彻底封装好。缺点是如果你在临界段里错误地调用了taskEXIT_CRITICAL()比如多调用了一次嵌套数提前归零中断被打开临界段保护就提前失效了。我通常会在临界段代码里尽量减少函数调用把临界段做得又短又清晰。这既是为了避免嵌套管理出问题也是为了缩短对高优先级中断的屏蔽时间。3.4 任务级临界段里到底能不能打印、能不能延时每个新手都会问taskENTER_CRITICAL()里面能用printf吗能用vTaskDelay()吗我的回答是原则上不要。printf本身耗时不定串口没有缓冲时可能要等外设完成而你在临界段里关掉了低优先级中断长时间屏蔽中断会破坏系统的实时性。如果串口打印所使用的驱动里恰好也有关中断操作那么中断嵌套计数会异常叠加后面再退出时就可能出现明明退出了一次中断还是关着或者没等嵌套清零中断就开了这类混乱。vTaskDelay()更不能出现在临界段里因为它依赖调度器切换任务而任务级临界段内调度器被禁止或中断被屏蔽调用延时函数会导致系统卡死。把临界段的代码范围控制在一两个变量修改或者一个链表节点的插入删除是工程上最稳妥的用法。临界段不是悲观锁协议它是非常短时间的互斥保护。4. 中断级临界段在ISR里该怎么保护共享数据4.1 为什么互斥锁在ISR里会出事任务之间保护共享数据常用互斥量xSemaphoreCreateMutex但ISR里不能直接获取互斥量。为什么因为互斥量在获取时可能会让调用者阻塞等待。如果ISR在等互斥量它既不能睡眠又不能切换任务整个系统就死锁了。在FreeRTOS里ISR能用的同步方式是带FromISR后缀的API比如xSemaphoreGiveFromISR、xQueueSendFromISR。这些API不会阻塞调用者只负责把事件记录下来并判断是否需要唤醒一个更高优先级的任务。那么ISR里想保护一段共享数据的修改应该用什么答案就是中断级临界段。它调用的宏是taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()。和任务级临界段的区别在于前者需要保存退出前的中断屏蔽状态因为你现在处于中断上下文你不知道进入这段ISR之前系统是否已经在临界段里了。4.2 taskENTER_CRITICAL_FROM_ISR的正确用法官方推荐的代码模式是UBaseType_t uxSavedInterruptStatus; uxSavedInterruptStatus taskENTER_CRITICAL_FROM_ISR(); // 访问共享资源的地方 shared_var; taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );这个模式最关键的一步是保存uxSavedInterruptStatus。taskENTER_CRITICAL_FROM_ISR()会把当前的BASEPRI值或中断屏蔽状态保存下来然后执行屏蔽操作。退出时把保存的值恢复而不是直接把中断全部打开。如果ISR进来之前系统就在临界段里那么退出时应该让系统保持之前的临界段状态而不是把中断全部打开。如果你用任务级的taskEXIT_CRITICAL()去退出中断被完全打开外层临界段保护就失效了。从实现上说taskENTER_CRITICAL_FROM_ISR()会调用portSET_INTERRUPT_MASK_FROM_ISR并把返回值存下来。在部分移植版本里这个宏会被定义成#define portSET_INTERRUPT_MASK_FROM_ISR() vPortRaiseBASEPRI()这个返回值在Cortex-M上通常是保存旧BASEPRI值的。退出时vPortSetBASEPRI( uxSavedInterruptStatus )就会恢复到之前的状态。如果你丢掉了那个返回值退出时只能选择“打开所有中断”或者“使用错误的屏蔽值”两种情况都容易出问题。4.3 中断嵌套场景下的状态恢复陷阱假如系统支持中断嵌套ISR执行过程中被更高优先级的中断打断新ISR里又进入了一次中断级临界段。嵌套回到原来的ISR后原来的uxSavedInterruptStatus里面保存的应该是第一次进入时的BASEPRI值。因此退出时要多次层级匹配地恢复。FreeRTOS的嵌套计数机制在中断里也起作用通过uxCriticalNesting在调度器层追踪嵌套数量。这里最考验人的地方在于中断切换时BASEPRI寄存器的行为。Cortex-M的BASEPRI不是进入ISR时自动保存的寄存器之一它属于特殊寄存器进入中断不一定会被硬件压栈。这也就是为什么FreeRTOS在中断入口处还会有一段汇编代码来维护中断嵌套状态。在ST的移植包中xPortStartScheduler之后的中断入口往往会有判断当前是否已经在中断里的逻辑用来决定是不是要保存和恢复嵌套计数。如果你自己写裸机中断想在ISR里用BASEPRI就必须额外小心不是所有编译器都会帮你保留BASEPRI。我一直建议的方法很简单如果ISR里需要保护的数据很短就直接用中断级临界段。如果不需要保护数据只是想给任务发一个通知那么直接用带FromISR的API就好不必每次都进临界段。这样既能减少嵌套计数维护的复杂度又能减少中断延迟。5. Port层的特殊寄存器处理别被官方移植代码绕晕5.1 从portmacro.h到port.c的调用链不少读者第一次打开FreeRTOS的port层源码会被一堆portXXX宏绕晕。拿ARM_CM4F移植为例任务级临界段实际上调用的是一组宏展开链taskENTER_CRITICAL()定义在task.h中实际展开是portENTER_CRITICAL()而在portmacro.h里#define portENTER_CRITICAL() vPortEnterCritical() #define portEXIT_CRITICAL() vPortExitCritical()vPortEnterCritical()就是port层实现的任务级进入函数。它的标准实现是void vPortEnterCritical( void ) { portDISABLE_INTERRUPTS(); uxCriticalNesting; }退出函数void vPortExitCritical( void ) { configASSERT( uxCriticalNesting ); uxCriticalNesting--; if( uxCriticalNesting 0 ) { portENABLE_INTERRUPTS(); } }从这里可以看出任务级临界段是先在入口关闭所有低于阈值的可屏蔽中断同时递增嵌套计数。退出时递减计数只有归零才重新打开中断。这比裸机里的简单关中断多了一层防止嵌套提前退出的保护。5.2 为什么FreeRTOS不直接用PRIMASK来保护所有临界段在较早的移植版本或某些特定移植上任务级临界段确实用的是PRIMASK。比如ARM7和ARM9的移植会把整个中断全部屏蔽。而Cortex-M系列普遍采用BASEPRI。原因是Cortex-M的可屏蔽中断数量多如果全部关掉那些对实时性要求很高的外设中断会被拖慢。比如一个定时器捕获中断频率是10kHz如果任务里每50us就进出一次临界段而且把定时器中断也关掉捕获事件就会丢失。BASEPRI的好处是我们可以把定时器中断的优先级设置得足够低让BASEPRI不打它或者把定时器中断优先级设置得高于阈值依然被BASEPRI屏蔽。具体怎么做取决于对时序要求更高的是临界段代码本身还是中断响应。就我自己的经验一般把最紧急的中断优先级设为很小的数值例如2或3让它在临界段期间也能响应把普通的外设中断设为较大的数值让它们被临界段屏蔽也无妨。如果设计的系统里某些中断在临界段内触发而且ISR必须读取一个正在被修改的共享结构体那么你就不能让BASEPRI屏蔽这个中断必须把它设置为更高的优先级数值更小。可是别忘了高优先级ISR里不能调用FreeRTOS API。这种情况下ISR只能通过硬件标志位通知或者用一个简单的原子读获取数据。在设计阶段就要把这些中断按优先级和使用场景分好类。5.3 中断开关宏究竟在哪一层生效一句话portDISABLE_INTERRUPTS()改变的是BASEPRI或PRIMASK真正让中断生效的是NVIC。关闭中断并不是把外设的中断使能位清零而是让NVIC在检查到中断请求后不触发处理器异常。中断标志位在外设状态寄存器里照样会置位不清除的话即使打开了BASEPRI中断也会立刻进。这也是很多新手困惑的为什么屏蔽中断后清完标志中断又触发一次的原因。在临界段里如果你关闭了中断外设产生的中断请求被NVIC记住但CPU不响应。等退出临界段打开中断后NVIC立刻把该中断提交给处理器。这不是异常而是正常的中断挂起行为。因此如果临界段里访问的外设会产生中断标志退出临界段前一定要检查并清除相关标志否则会重复进入同一个ISR。我还记得一个ADC采集的场景用BASEPRI把中断关了采集完成中断被挂起退出临界段后ISR立刻执行读取的结果其实是一个已经过时的数据。后来我把读取转换结果放在临界段里并且在使用前先清一次中断标志这个问题才解决。6. 实测心得临界段设计中的常见坑与我的处理习惯6.1 优先级分组被改动后的一切连锁反应Cortex-M允许程序在运行时修改中断优先级分组。NVIC_SetPriorityGrouping函数可以设置优先级分组方式比如GROUP_4表示4位全是抢占优先级没有子优先级。FreeRTOS的官方手册推荐使用NVIC_PriorityGroup_4也就是全部分配给抢占优先级。原因是FreeRTOS不考虑子优先级如果系统里既有抢占优先级又有子优先级两个中断的抢占优先级相同、子优先级不同时高子优先级的中断不能抢占低子优先级的中断这会造成不可预测的响应顺序。我在某个老项目里就吃过亏项目把分组设置成了GROUP_2高4位里有2位是抢占、2位是子优先级。编码器中断和串口接收中断的抢占优先级一样子优先级不同结果串口接收永远无法打断编码器中断即使串口数据的实时性要求更高。后来把分组统一改成4位抢占优先级一切才正常。修改分组之后FreeRTOS的临界段阈值计算并不会变它还是按照configPRIO_BITS来移位但实际生效的优先级数值含义却变了。所以项目启动初始化时需要明确调用一次NVIC_SetPriorityGrouping并保证所有中断的优先级完全按抢占优先级分配。6.2 临界段内调用带FromISR后缀的API是否安全这个问题很多人都问过。比如在任务级临界段里调用xQueueSendFromISR逻辑上不合理因为任务级临界段并没有中断支持但从API角度它也能编译。能不能用答案是能用但别用。FromISR后缀的API是为了让ISR能操作队列和信号量它的内部实现确实也会避免阻塞但它在任务中执行会把内核的上下文从一个状态切到另一个状态使用不当会导致调度器状态不一致。更合适的做法是在任务临界段里只操作两个任务都访问的核心共享变量例如环形缓冲区的读写指针、状态机的状态标志。如果需要把数据从一个任务送到另一个任务应该用队列并且配合中断级临界段或API本身的内部保护机制。FreeRTOS的队列读取和发送内部是有临界段保护的所以直接用队列API即可不需要手动在临界段里再包一层队列操作。如果确实需要在临界段里操作队列请一定使用xQueueSendFromISR对应的任务版xQueueSend它会正确处理等待超时。但还要注意任务级临界段关闭了调度器xQueueSend内部如果因为队列满而等待就会永远等下去。所以不要给临界段里的队列发送设置阻塞时间最好用非阻塞方式。6.3 关中断时间与系统实时性的平衡临界段优化的核心指标只有一个那就是“关中断时长”。我习惯用逻辑分析仪抓一个GPIO翻转来测量临界段耗时。在进入和退出临界段前后各翻转一次IO抓出的高电平宽度就是临界段总屏蔽时长。对一般工业控制应用这个时长最好控制在1到2微秒以内不要动辄几十微秒。哪些操作会让临界段变长最常见的是在临界段里调用库函数比如memcpy别人写的驱动、浮点数格式化打印这些都有可能把几百个周期甚至上千周期拖进屏蔽范围。另一个常见原因是中断服务函数里调用了一个耗时的API而你又在这些ISR中使用了高优先级它虽然不被临界段屏蔽但那段时间CPU被ISR占用任务级临界段虽然没在等但ISR执行时间本身就破坏了系统整体时延。我自己总结了一个检查清单会定期对照打开configASSERT所有ISR内调用的API是否带FromISR后缀所有在ISR中保护的数据是否使用了中断级临界段是否有任务在临界段内调用延时或阻塞函数是否有中断优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY却调用了API是否有外设中断在临界段期间被挂起后重复进入ISR6.4 一个能直接用的中断与临界段设计样例拿一个最常见的传感器数据采集任务举例设计思路可以复用。传感器模块在SPI总线上由DMA传输完成中断触发。项目里用了一个环形缓冲区存放原始采样数据一个二值信号量通知任务做处理。代码结构大致如下void SPI_DMA_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken pdFALSE; uint32_t *pData getDMABufferPointer(); UBaseType_t uxSaved taskENTER_CRITICAL_FROM_ISR(); ringBufferWrite(g_ring, pData, DMA_BUFFER_LEN); taskEXIT_CRITICAL_FROM_ISR(uxSaved); xSemaphoreGiveFromISR(g_dataSemaphore, xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }这里把DMA缓冲区的内容写入环形缓冲区用中断级临界段保护信号量的给出交给FreeRTOS的ISR API完成。注意taskENTER_CRITICAL_FROM_ISR保存了进入前的状态防止破坏外层临界段。如果中断里写缓冲区耗时太长也可以把数据写入改成轮询加双缓冲策略尽量缩短临界段长度。对应任务端读取时不需要自己在外部包临界段调用环形缓冲区的读取函数如果读取函数内部也操作同一个环形缓冲区那么它同样需要进入临界段。我建议把环形缓冲区的读写函数设计成成对使用临界段宏而不是依赖调用者自觉。这样哪怕未来有人忘了在外部保护数据结构内部也能保证基本安全。我在实际使用中发现代码里最容易出问题的往往不是临界段机制本身而是那些“看起来不需要保护”的全局状态。例如一个任务里通过指针直接修改了结构体字段而另一个任务在临界段外读它这种未定义行为很难复现却不代表不存在。真正靠谱的做法是写代码时就要明确每个全局变量的访问规则哪些是ISR专用、哪些是任务专用、哪些是跨层共享。跨层共享的一律用临界段保护ISR专用的只在ISR里访问。把规则写清晰系统才能长久稳定运行。最后再分享一个小技巧如果你在调试时发现临界段内中断被调用的频率异常高别急着优化临界段代码先检查是否有一个本该被关闭的周期性中断在里面反复触发。把逻辑分析仪挂在中断引脚和临界段翻转引脚上对比一下一眼就能看出是谁在频繁打断临界段。这个排查方法帮我省了很多无谓的“优化”时间。
阅读完成 · 觉得有帮助?
咨询建站