ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

深入解析STM32F429系统架构:从总线矩阵到DMA与中断协同设计

深入解析STM32F429系统架构:从总线矩阵到DMA与中断协同设计 1. 从“单片机”到“微控制器”STM32F429的定位转变很多工程师朋友一提到STM32脑子里蹦出来的第一个词可能还是“单片机”。这个称呼没错但用在STM32F429这类芯片上就显得有点“委屈”它了。传统的8051、AVR单片机核心就是一个CPU加上一些定时器、串口等外设处理能力有限内存也小跑个裸机程序或者简单的RTOS就到头了。但当你拿到一块STM32F429的开发板看到它那动辄180MHz的主频、256KB的SRAM、2MB的Flash还能驱动RGB接口的LCD、跑起像emWin这样的图形库、甚至移植Linux虽然比较吃力时你就会意识到这玩意儿已经不是一个简单的“单片机”了。我更愿意称它为“微控制器系统”或者更时髦一点一个“嵌入式片上系统SoC”。它的内部结构远比传统单片机复杂更像是一个精心规划的小型城市。这个“城市”里有高速的主干道总线矩阵有不同职能的行政区内核、存储器、外设还有负责交通调度的警察总线仲裁器。理解这个“城市”的规划蓝图——也就是它的系统架构对于写出高效、稳定、能榨干芯片性能的代码至关重要。否则你可能会遇到一些匪夷所思的问题比如明明主频很高但刷屏就是卡顿DMA搬运数据时CPU像被“冻住”了一样或者某个外设的时钟配置总是出问题。STM32F429基于ARM Cortex-M4内核但它的精髓远不止这个内核。它的系统架构设计特别是其先进的高性能总线AHB和高级外设总线APB组成的多层矩阵是支撑其强大性能的骨架。今天我们就抛开简单的点灯、串口深入到STM32F429的“城市”内部看看它的道路总线是如何规划的各个“功能区块”是如何布局和通信的以及我们作为“城市规划师”嵌入式软件工程师如何根据这份蓝图来设计我们的“应用程序”。2. 核心与总线STM32F429的“城市”骨架与“交通网络”要理解STM32F429首先得抓住两个核心处理一切指令和数据的“大脑”——Cortex-M4内核以及连接大脑与身体各个部分的“神经网络”——多层总线矩阵。这两者共同构成了整个系统运行的基石。2.1 Cortex-M4内核不止是计算单元STM32F429搭载的Cortex-M4内核大家可能都知道它支持DSP指令和单精度浮点单元FPU这对于做数字信号处理、电机控制、简单算法非常有用。但内核在系统架构中的角色远不止一个计算器。首先内核通过几条关键的接口线与外部世界连接I-Code总线专门用于从Flash存储器位于0x0800 0000起始的地址取指令。这是一条32位宽的总线优化了取指效率。当CPU需要执行下一条指令时就通过这条“专用高速通道”去Flash里拿。D-Code总线专门用于从Flash存储器加载数据常量、查找表等。将指令和数据取指分开可以有效避免总线冲突提升性能。你可以想象成去图书馆一个人走“借阅通道”拿书指令另一个人走“查阅通道”复印资料数据互不干扰。System总线用于访问SRAM、外设和剩下的Flash区域比如用于数据存储的Flash。当你的程序需要读写变量在SRAM中或者配置一个定时器的寄存器在外设地址空间就走这条“综合通道”。私有外设总线PPB这是一条专门用于访问内核内部私有外设的总线比如嵌套向量中断控制器NVIC、系统定时器SysTick、浮点单元状态控制寄存器等。这些是紧耦合在内核上的访问速度极快且与外部总线隔离。这里有一个关键点内核的这多条总线是同时工作的。这意味着当CPU正在通过System总线从SRAM读取一个变量时它可以同时通过I-Code总线预取后面的几条指令。这种并行性极大地提升了流水线的效率避免了“等数据来”的停滞。很多初学朋友写的代码性能不高有时就是因为没有利用好这个特性比如把大量需要频繁访问的数据错误地放在了Flash中导致D-Code总线繁忙间接影响了I-Code总线的取指。2.2 多层总线矩阵精密的“交通枢纽”如果说内核是城市中心那么总线矩阵就是环绕中心、连接所有功能区的立交桥系统。STM32F429使用的是基于ARM AMBA高级微控制器总线架构规范的多层AHB总线矩阵这是它高性能的关键。为什么需要“多层”想象一下如果整个城市只有一条双向两车道的路单一共享总线那么CPU要去SRAM、DMA1要去Flash、以太网MAC要往SRAM写数据全都得在这条路上排队堵车是必然的。多层总线矩阵相当于建设了一个多车道、多入口/出口的立体交通枢纽允许多个主设备Master同时访问不同的从设备Slave只要它们的路径不冲突。在STM32F429中主要的主设备包括Cortex-M4内核通过I/D/System总线DMA1控制器DMA2控制器以太网MACUSB OTG HS如果使用内置PHY它通过一个专用DMALCD-TFT控制器LTDC主要的从设备内存/外设区域包括Flash接口FLITF内部SRAM1112KB内部SRAM216KBAHB1外设如GPIO、CRC、DMA等AHB2外设如USB OTG FS、摄像头接口等APB1和APB2外设桥后面会详细讲总线矩阵内部有一个仲裁器。当两个主设备比如CPU和DMA2同时想访问同一个从设备比如SRAM1时仲裁器会根据预设的优先级来决定谁先通行。通常像DMA、以太网这种为数据吞吐而生的主设备会被赋予较高的优先级以确保数据流不中断。而CPU的访问可能会被短暂延迟这也就是为什么在大量DMA传输时你可能会感觉CPU“变慢”了的原因——它需要等待总线空闲。理解这个矩阵模型对于调试至关重要。例如当你发现LCD刷新异常而同时又在进行大量的SD卡数据读取通过DMA时就要考虑是不是LCD控制器LTDC和SDIO的DMA在竞争访问SRAM或总线带宽导致LTDC获取显存数据不及时从而出现撕裂或闪烁。解决方案可能是调整两者的优先级或者使用不同的SRAM块作为缓冲区如果芯片支持。3. 存储器架构分门别类的“仓库”与“地图”知道了道路总线和中心内核我们来看看这个城市里的“仓库”——存储器。STM32F429的存储器不是铁板一块而是根据速度、用途和总线连接被精心组织在不同的地址区域。这张“内存地图”是我们编程时必须时刻参考的导航图。3.1 内存映射一切皆有地址在STM32F429中无论是代码、数据、寄存器甚至是一些外设的FIFO都被映射到一个统一的4GB线性地址空间中。这带来了极大的便利性你可以像操作内存变量一样通过指针直接读写一个外设的寄存器。例如点亮一个LED本质上就是向某个GPIO端口的“输出数据寄存器”ODR对应的内存地址写入一个值。整个4GB空间被大致划分为几个主要区域代码区0x0000 0000 - 0x1FFF FFFF通常映射到内部Flash。我们的程序就固化在这里。上电后CPU从0x0800 0000Flash起始地址开始取指执行。这个区域也可以通过“别名”在0x0000 0000访问由BOOT引脚决定。SRAM区0x2000 0000 - 0x3FFF FFFF映射到芯片的内部RAM。变量、堆栈、堆都生活在这里。STM32F429有多个SRAM块地址可能不连续例如SRAM1在0x2000 0000CCM RAM在0x1000 0000。它们的速度和总线连接可能不同。外设区0x4000 0000 - 0x5FFF FFFF这是APB和AHB外设寄存器的家。所有我们配置的GPIO、USART、SPI、定时器的控制寄存器都像一个个小房间排列在这个巨大的地址大厦里。通过芯片参考手册的“内存映射”章节可以查到每个外设的准确“门牌号”基地址。其他区域还包括用于外部存储器的FSMC/FMC区域、用于内核内部私有外设的区域等。3.2 Flash与SRAM的细节与陷阱Flash闪存 STM32F429的Flash通常有1MB或2MB。它不仅仅是存储代码的“硬盘”。为了加速执行STM32F429内置了Flash预取缓冲器和指令缓存。预取缓冲器会在CPU执行当前指令时偷偷地把后面几条指令从Flash读到缓冲区里。因为Flash的读取速度几十MHz远低于CPU核心速度180MHz如果没有这个缓冲CPU大部分时间都在“等指令”。指令缓存则更进一步缓存最近访问过的指令区域。在开启缓存的情况下对Flash的访问性能会有巨大提升。在系统初始化时SystemInit函数里通常会看到开启指令缓存和预取缓冲的代码这是发挥芯片性能的第一步务必确保它们被启用。SRAM静态随机存储器 STM32F429的SRAM通常包括主SRAM112KB 16KB位于0x2000 0000通过系统总线访问所有主设备CPU, DMA等都能访问。CCM RAM64KB这是一个关键特色CCM内核耦合存储器直接挂在D-Code总线上只能被CPU通过D-Code总线访问DMA无法访问。这意味着什么如果你把需要被DMA搬运的数据比如ADC采样缓冲区、串口收发缓冲区放到了CCM RAM里DMA会访问失败程序会进入硬件错误中断HardFault反之CCM RAM是CPU的“私有高速车库”访问延迟极低。最适合存放对性能要求极高的代码或数据比如中断服务程序中的关键变量、实时控制算法的核心数组、或者需要极快访问的查找表。在链接脚本.ld文件中你需要专门划分一个段section来将特定变量或函数放到CCM RAM中。这里分享一个我踩过的坑在一个电机控制项目中我把PID计算中的中间变量数组放到了CCM RAM以求最快速度同时使用DMA搬运ADC的采样值。我错误地将DMA的目标地址也设到了CCM RAM结果电机一启动就HardFault。排查了很久才发现是DMA试图写入“禁行区域”。最后将DMA缓冲区改到主SRAM问题解决。教训是使用CCM RAM前必须清楚它的访问限制。外部存储器 对于需要大容量存储的场合如图片、字库、音频数据STM32F429提供了灵活的静态存储器控制器FMC可以连接SRAM、PSRAM、NOR/NAND Flash以及液晶显示控制器LTDC接口直接驱动RGB接口的LCD。FMC也有自己的地址空间访问它们就像访问内部内存一样但速度较慢。在设计UI应用时通常会把显存Frame Buffer放在外部SRAM或SDRAM中由LTDC控制器自动读取并刷屏从而解放CPU。4. 时钟树驱动整个系统的“脉搏”与“能量分配”如果说总线是道路那么时钟就是道路上所有车辆和红绿灯的同步节拍器。STM32F429的时钟树可能是初学者最头疼的部分但它又是系统稳定和功耗控制的命脉。一个配置不当的时钟树轻则外设无法工作重则系统锁死。4.1 时钟源能量的来源STM32F429有多个时钟源就像城市有不同的发电厂HSI高速内部RC振荡器频率16MHz精度一般±1%但上电即用无需等待。适合作为系统初始化的临时时钟或看门狗的时钟源。HSE高速外部晶体振荡器通常接8MHz晶体精度高。它是生成高系统主频如180MHz的基石。大部分对时序要求高的应用如USB、以太网都依赖HSE。LSI低速内部RC约32kHz用于独立看门狗IWDG和RTC在Vbat域。LSE低速外部晶体通常接32.768kHz晶体为RTC提供高精度时钟源。一个关键实践在main函数一开始甚至在初始化堆栈之前芯片是靠HSI运行的。你的时钟配置代码通常用HAL库的HAL_RCC_OscConfig和HAL_RCC_ClockConfig的任务就是“切换电厂”——锁相环PLL以HSE为输入倍频到目标频率然后切换系统时钟源SYSCLK到这个PLL输出。这个过程必须严格按照参考手册的序列操作特别是等待时钟就绪标志位。4.2 PLL与分频器变频与分配这是时钟树的核心。STM32F429有多个PLL主PLL、PLLI2S、PLLSAI。我们以配置180MHz系统时钟为例使能HSE等待就绪。配置主PLL假设HSE8MHz。主PLL的输入需要先经过一个M分频器PLL_M。例如设PLL_M 8则输入到PLL的VCO压控振荡器的时钟是1MHz8MHz / 8。这里有个坑VCO的输入频率必须在1-2MHz之间必须通过PLL_M分频到这个范围。VCO倍频然后通过N倍频器PLL_N放大。要得到180MHz系统时钟我们需要VCO输出360MHz因为后面还要分频。所以PLL_N 3601MHz * 360。VCO的输出频率必须在192-432MHz之间。输出分频VCO输出的360MHz通过P分频器PLL_P得到系统主时钟SYSCLK。设PLL_P 2则SYSCLK 360MHz / 2 180MHz。PLL_P只能是2、4、6、8。还可以通过PLL_Q分频得到用于USB OTG、SDIO等的48MHz时钟PLL_Q通常设为7360/7≈51.4接近48实际USB有专门的时钟容差范围。配置好PLL后将系统时钟源切换到PLL整个芯片的“心跳”就加速到了180MHz。4.3 总线时钟不同区域的“限速”SYSCLK并不是直接驱动所有外设。为了平衡性能和功耗不同的总线域有不同的时钟AHB总线时钟HCLK由SYSCLK经过AHB预分频器得到。它驱动内核、内存Flash, SRAM和大部分高速外设如DMA、GPIO。通常我们把它设为和SYSCLK一样分频系数为1即180MHz以获得最高性能。APB1总线时钟PCLK1由HCLK经过APB1预分频器得到。APB1是低速外设总线挂载了如I2C、UART、SPI部分、定时器TIM2-TIM5, TIM12-TIM14等。它的最大频率是45MHz。如果你把HCLK设为180MHz那么APB1预分频器必须至少设为4180/445。如果设小了比如设为2得到90MHz就会超出外设规格导致工作异常。APB2总线时钟PCLK2由HCLK经过APB2预分频器得到。APB2是高速外设总线挂载了高级定时器TIM1, TIM8、ADC、SPI1等。它的最大频率是90MHz。定时器时钟这是另一个易错点。挂在APB1/APB2上的定时器其实际时钟源可能不是PCLKx。如果对应的APBx预分频系数为1则定时器时钟等于PCLKx如果预分频系数不为1比如2/4/8/16则定时器时钟会是PCLKx的2倍这是硬件自动完成的。例如HCLK180MHzAPB1预分频器4则PCLK145MHz。由于预分频系数不为1那么挂在APB1上的通用定时器如TIM2的实际时钟是PCLK1 * 2 90MHz在计算定时器溢出频率、PWM频率时必须用这个“实际时钟”而不是PCLK1否则你的定时会快一倍。配置时钟时我习惯用ST的CubeMX工具生成初始化代码但一定会仔细检查生成的SystemClock_Config()函数核对每一个分频系数是否符合我的外设需求特别是USB的48MHz、以太网的25/50MHz、SDIO的48MHz等并手动计算一下关键外设的时钟是否正确。盲目使用默认配置是项目后期出现各种诡异时序问题的根源之一。5. 外设总线与通信矩阵低速与高速的“交通管制”理解了系统总线和时钟我们再把目光聚焦到具体的外设上。STM32F429的外设并非都挂在同一级总线上而是根据速度需求分层次地连接在AHB和APB总线上并通过“桥”进行连接和时钟控制。5.1 AHB与APB高速干道与区域道路AHB先进高性能总线这是系统内的“高速公路”时钟频率高等于HCLK可达180MHz数据位宽32位支持突发传输、分块传输等高级特性。直接挂在AHB总线上的都是对带宽或实时性要求极高的模块例如Cortex-M4内核通过总线矩阵接口DMA1/DMA2控制器Flash内存接口FLITFSRAM控制器CRC计算单元以太网MAC这是典型的高速数据流设备必须放在AHB上才能满足带宽。USB OTG HS如果使用内置PHY其DMA也挂在AHB上。LCD-TFT控制器LTDC刷屏需要持续不断地从内存读取像素数据数据量巨大AHB是唯一选择。APB先进外设总线这是“区域道路”或“辅路”时钟频率较低PCLK1最大45MHzPCLK2最大90MHz协议简单功耗更低。大部分通用外设都挂在这里分为两条APB1连接低速外设如I2C、UART、CAN、SPI2/3、普通定时器、看门狗、电源接口等。APB2连接相对高速的外设如ADC、高级定时器TIM1/TIM8、SPI1、系统配置控制器等。AHB和APB之间通过桥APB Bridge连接。桥的作用不仅是协议转换AHB到APB更重要的是时钟隔离。当你通过外设库的__HAL_RCC_GPIOA_CLK_ENABLE()函数使能某个外设时钟时你实际上是在操作桥上的时钟控制门。如果这个外设的时钟被禁用那么访问它的寄存器将会导致总线错误HardFault。因此在使用任何外设前必须先使能其对应的总线时钟这是STM32编程的铁律。5.2 外设地址映射与位带操作每个外设都有一组寄存器它们被映射到APB或AHB的地址空间。例如GPIOA的寄存器基地址是0x4002 0000在APB2总线上。其ODR寄存器偏移量是0x14所以GPIOA-ODR的地址就是0x4002 0014。对于简单的位操作比如快速置位或清零某个GPIO引脚直接读写整个ODR寄存器32位效率不高因为需要“读-改-写”三步。STM32的Cortex-M内核支持一种称为位带Bit-Banding的特性。它将SRAM和外设区的一小部分地址空间称为位带区的每一个位都映射到另一个地址空间位带别名区的一个完整32位字上。对别名区字的写操作会直接映射到位带区对应位的写操作读别名区字则返回该位的值0或1。例如要快速将PA0引脚置1假设ODR寄存器的位0对应PA0。传统方法是GPIOA-ODR | 0x0001; // 读ODR或操作写回ODR使用位带操作可以先计算出PA0在ODR寄存器中第0位对应的别名地址然后直接向该地址写1// 这是一个示例计算实际中HAL库或CMSIS提供了宏 *(volatile uint32_t*)(位带别名区地址) 0x01;这样操作是原子的且只需要一次写操作速度极快。在需要极高实时性的控制场合如数字电源的PWM保护引脚快速关断位带操作非常有用。不过在标准外设库或HAL库中通常用更易读的GPIO_PIN_SET等宏封装了这些操作底层可能已经优化。了解位带原理有助于你理解这些库函数背后的性能考量。6. 直接存储器访问DMA解放CPU的“专职搬运工”在复杂的嵌入式系统中CPU的时间非常宝贵。如果让CPU去搬运大量数据比如从ADC读取采样值存到数组或者从串口接收缓冲区搬数据到处理区它就会被这些简单的重复劳动占用无法执行更复杂的算法或逻辑。DMA就是为了解放CPU而生的“专职搬运工”。6.1 DMA控制器架构与数据流STM32F429有两个DMA控制器DMA1和DMA2。每个控制器有多个数据流StreamDMA1有8个DMA2有8个每个数据流有多个通道Channel。你可以把数据流理解为一条独立的“搬运流水线”而通道则决定了这条流水线服务于哪个外设的请求。数据流是DMA调度的核心单元。每个数据流可以配置源地址数据从哪里来可以是外设数据寄存器地址如ADC1-DR也可以是内存地址。目标地址数据到哪里去同理可以是内存或外设。传输数据量一次要搬多少个数据项每个数据项可以是字节、半字或字。传输模式外设到内存例如ADC连续采样到数组。内存到外设例如将一幅图片数据通过SPI发送到屏幕。内存到内存DMA2的某些流支持可以在内部SRAM之间或与外部存储器之间快速拷贝数据无需CPU介入。循环模式传输完成后地址和计数器自动重置重新开始传输。这对于连续数据流如音频播放、ADC持续采样至关重要。优先级当多个数据流同时请求时仲裁器根据优先级决定谁先使用DMA总线。中断传输完成、半传输完成、传输错误都可以产生中断通知CPU进行后续处理如处理半缓冲的数据。一个关键概念FIFO。每个DMA数据流都有一个4字的FIFO。它的作用是在源和目标总线带宽或突发大小不匹配时进行缓冲。例如外设如ADC可能每次产生一个16位的数据半字而DMA配置为以字32位为单位写入内存。DMA会先在FIFO中累积两个半字凑成一个字后再一次性写入内存这能提高总线利用效率。在大多数情况下使能FIFO并选择合适的阈值如半满是个好习惯。6.2 DMA与CPU的协同与竞争DMA和CPU都是总线矩阵的主设备它们都可能访问Flash、SRAM等资源。这就产生了竞争。总线仲裁器会处理这些竞争但作为程序员我们需要意识到这种竞争带来的影响并妥善处理。竞争场景与解决方案DMA与CPU访问同一块SRAM这是最常见的情况。例如DMA正在将ADC数据写入数组adc_buffer而CPU正在读取adc_buffer中的数据进行处理。如果没有任何同步措施CPU可能会读到“半新半旧”的数据当DMA写操作未完成时。解决方案是使用“双缓冲区”或“乒乓缓冲区”。分配两个缓冲区bufA和bufB。DMA先写bufA写满后产生中断CPU开始处理bufA同时DMA自动切换到写bufB。如此循环实现生产和消费的解耦。STM32的DMA支持“双缓冲模式”可以自动切换目标地址非常方便。DMA访问与CPU指令取指竞争如果DMA频繁地通过系统总线访问Flash例如从Flash搬运数据到SRAM可能会阻塞CPU通过I-Code总线取指导致CPU性能下降。解决方案是尽量将DMA的源或目标地址设在SRAM避免DMA直接访问Flash。如果必须从Flash搬运可以考虑将关键代码或中断服务程序放到CCM RAM或SRAM中执行以避免取指被阻塞。多个DMA数据流之间的竞争如果多个高优先级的数据流如以太网DMA和SDIO DMA同时频繁操作可能会使低优先级的流或CPU长期得不到总线使用权。需要根据数据流的实时性要求合理分配优先级。对于不能容忍延迟的流如音频DAC的DMA给予最高优先级。配置DMA时我通常会遵循以下步骤明确传输方向、数据宽度、地址是否递增。根据外设选择正确的DMA控制器和数据流通道查数据手册的DMA请求映射表。配置优先级对于连续流使能循环模式。使能传输完成中断或半传输中断用于双缓冲。在启动DMA传输前确保目标内存区域是可访问的并且已经初始化如果需要。一个常见的错误是声明了一个指针或数组没初始化就作为DMA目标地址导致DMA向未知区域写数据可能破坏栈或堆。在中断服务函数中及时清除中断标志并处理数据/切换缓冲区。7. 中断系统NVIC处理紧急事件的“110中心”嵌入式系统必须能及时响应外部事件比如按键按下、串口收到数据、定时器时间到。轮询不断检查状态的方式低效且实时性差。中断机制就是让外设在事件发生时“打断”CPU当前的工作让CPU先去处理这个紧急事件处理完再回来继续原来的工作。管理所有这些“打断请求”的就是嵌套向量中断控制器NVIC。7.1 NVIC优先级与嵌套规则STM32F429的Cortex-M4 NVIC支持多达240个外部中断IRQ每个中断都可以独立设置优先级。优先级数值越小优先级越高。优先级寄存器通常用4位或8位来表示在STM32中通常配置为4位即有16个优先级级别。关键规则抢占优先级高抢占优先级的中断可以打断低抢占优先级的中断正在执行的服务函数。这就是“嵌套”。子优先级当两个中断的抢占优先级相同时比较子优先级子优先级高的先执行。如果连子优先级也相同则比较它们的硬件中断编号IRQn编号小的优先。中断屏蔽CPU可以在特殊寄存器PRIMASK中全局关闭中断或者在NVIC中单独禁用某个中断。配置建议 对于实时性要求极高的中断如电机驱动的PWM保护、紧急故障信号应设置为高抢占优先级、低中断编号。对于处理大量数据但可以稍作延迟的中断如DMA传输完成、串口接收可以设置为较低的抢占优先级。要避免将太多中断设为高抢占优先级否则会导致系统频繁被嵌套打断影响整体吞吐量。通常SysTick定时器中断用于操作系统时基会设为一个中等优先级以保证操作系统的“心跳”不被普通任务中断完全阻塞又能被更高优先级的硬件中断打断。7.2 中断服务函数与临界区保护当中断发生时硬件会自动将当前CPU的若干寄存器压栈保护现场然后跳转到对应中断向量表指向的地址开始执行代码。这个地址就是中断服务函数ISR。在STM32的工程中中断服务函数有固定的名字例如USART1_IRQHandler()。你只需要在stm32f4xx_it.c文件中重写这个函数即可。编写ISR的黄金法则快进快出。快进尽快判断中断源检查哪个标志位触发了中断并清除中断挂起标志。快出ISR里只做最必要、最紧急的处理比如从寄存器读取数据放到缓冲区、设置一个事件标志、通知某个任务。复杂的处理如数据解析、算法计算应该放到主循环或RTOS任务中由ISR通过信号量、消息队列等机制去触发。绝对避免在ISR中进行长时间循环、延时、或可能阻塞的操作如等待某个慢速外设。临界区保护 由于中断可以随时打断主程序如果主程序和ISR访问了同一个全局变量或硬件资源就可能发生竞态条件。例如主程序正在读取一个32位变量g_sensor_value需要多次加载指令读到一半时被中断打断ISR修改了这个变量然后主程序继续读取剩下的部分得到的就是一个错误的值半旧半新。 保护方法有两种关闭全局中断在访问共享资源前用__disable_irq()访问后用__enable_irq()。这是最粗暴但有效的方法但会影响整个系统的实时性。使用原子操作或硬件原语对于简单的标志位可以使用C11的原子操作或编译器提供的特殊指令。在STM32中对于位操作使用位带别名区访问是原子的。对于更复杂的同步需要借助RTOS提供的互斥锁、信号量等机制。一个实用的经验对于在ISR和主程序间共享的缓冲区我通常会定义一个结构体包含缓冲区本身、读写索引以及一个使用volatile关键字修饰的标志变量。volatile告诉编译器这个变量可能被意外改变比如被ISR禁止对它进行优化如缓存到寄存器确保每次访问都从内存读取。在ISR中写数据并更新索引后我会设置一个标志在主循环中我检查这个标志然后处理数据并清除标志。
返回列表