ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32中央对齐模式3双中断机制:触发时序、方向判断与工程实践

STM32中央对齐模式3双中断机制:触发时序、方向判断与工程实践 1. 先弄清“双中断”到底指什么做电机驱动或者数字电源的朋友应该都遇到过这种场面把PWM从边沿对齐改成中央对齐想改善波形对称性结果一开中断就发现什么都在翻倍——ADC采样的频率不对、控制周期不对、甚至换相的时序全乱了。问题几乎总是出在同一个地方中央对齐模式下中断触发点不是你以为的那个数。尤其模式3“双中断”三个字背后其实是一个PWM周期里两次比较匹配加两次更新事件的完整时间线。这篇文章就把STM32定时器中央对齐模式3的双中断机制从头拆一遍顺便把方向判断、预装载时机、中断优先级这些容易踩坑的点一起聊透。先说清楚这篇文章适合谁。如果你在用STM32做无刷电机控制、数字电源、逆变器、步进驱动或者任何需要“PWM波形对称 精确采样窗口”的场合那中央对齐模式3几乎绕不开。模式3的双中断本质上给了你一个把PWM周期拆成两个相位分别处理的手段用好了控制性能可以明显上一个台阶用不好就是无穷无尽的中断风暴。下面直接从计数器的行为开始讲。1.1 中央对齐模式的计数时间线三角波而不是锯齿波STM32定时器最常用的是向上计数模式计数器从0一路加到ARR然后归零重新加波形是锯齿波。中央对齐模式完全不同计数器从0加到ARR然后掉头往下减到0再掉头往上加如此反复。也就是说计数器输出的是一个三角波PWM波形关于周期中心对称。这个“掉头”是关键。向上计数到ARR的那一刻叫“上溢”向下计数到0的那一刻叫“下溢”。每次掉头都会产生一个更新事件。这就导致了一个很多人第一次接触中央对齐模式时没反应过来的事实同样的定时器时钟和ARR值中央对齐模式的PWM频率约为边沿对齐模式的一半但更新中断频率却是PWM频率的两倍。有个容易混淆的点中央对齐模式下TIMx_CR1里的DIR位不再由软件控制而是硬件自动翻转。计数器向上时DIR0向下时DIR1。这个位在中断服务函数里特别有用后面详细说。1.2 模式1、模式2、模式3的比较中断差异中央对齐模式在TIMx_CR1的CMS位里又细分了三种子模式很多资料把这三种模式的区别写得云里雾里其实拿中断触发点一对比就非常清楚了CMS设置子模式向上计数时CCxIF置位向下计数时CCxIF置位特点01中央对齐模式1不置位置位只在下坡产生比较中断10中央对齐模式2置位不置位只在上坡产生比较中断11中央对齐模式3置位置位上坡和下坡都产生比较中断需要提醒一下这个定义我见过不少资料写反的以芯片参考手册“中央对齐模式1/2/3”那一小节为准。模式1是“只在向下计数时置位”模式2是“只在向上计数时置位”模式3才是上下都置位。模式1和模式2本质上是“半边对称”的中断方案适合只需要半个PWM周期做处理的场景。模式3则把两次比较匹配全部打开这就是所谓“双中断”最核心的含义同一个CCR比较值在一个完整的PWM周期里会触发两次比较中断一次在上升段一次在下降段。1.3 模式3下一个PWM周期的四次中断事件如果同时使能了比较中断和更新中断模式3下一个完整的PWM周期里实际会发生四次中断按时间顺序排列如下计数器向上计数到CCR触发一次比较中断此时DIR0这是“上坡匹配”。计数器继续向上到ARR发生上溢更新事件触发更新中断此刻方向已经翻转DIR1。计数器向下计数到CCR再次触发比较中断此时DIR1这是“下坡匹配”。计数器继续向下到0发生下溢更新事件再次触发更新中断方向重新翻转为DIR0。很多人在更新中断里做PWM周期计数发现频率莫名其妙是预期的两倍原因就在这里——中央对齐模式下更新事件一个PWM周期就有两次。理解了这条时间线后面的方向判断、采样窗口设计才有基础。2. 两次比较匹配的精确触发点与方向判断2.1 同一CCR值上半周期和下半周期各匹配一次中央对齐模式3下CCR参数决定一个PWM周期里PWM引脚的两个翻转点一个位于三角波的上升段一个位于下降段。这两个翻转点对应的计数器值完全相同都是CCR唯一区别是计数方向相反。举个例子假设ARR1000CCR300。计数器从0向上数数到300时第一次匹配PWM电平翻转产生第一次比较中断计数器继续向上到1000后掉头向下从1000一路减减到300时第二次匹配PWM电平再次翻转产生第二次比较中断。这个过程中CNT在两次匹配时都等于300但方向从递增变成了递减。这就引出一个非常实用的结论模式3的双中断并不是在两个不同CCR值上触发而是在同一个CCR值、两个不同方向各触发一次。如果你想让两次中断做不同的事情唯一的依据就是DIR位没有其他更可靠的硬件信号。2.2 在CC中断里读DIR上坡和下坡的判别逻辑在比较中断服务函数里判断当前触发方向的标准写法是读TIMx_CR1的DIR位void TIM1_CC_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_CC1); if ((TIM1-CR1 TIM_CR1_DIR) 0) { // 上坡匹配计数器正在向上计数CNT刚到达CCR // 这里适合做电流采样、ADC触发等 } else { // 下坡匹配计数器正在向下计数CNT刚到达CCR // 这里适合做占空比更新、控制算法等 } } }为什么在这里可以信任DIR位因为从硬件置位CCxIF到CPU真正进入中断服务函数中间只有中断响应的几个周期计数器最多往前走几步还远没到下一次方向翻转。所以DIR读出来的就是本次匹配时的真实方向这是模式3双中断能可靠区分“上半周期”和“下半周期”的硬件基础。有一个细节要特别提醒清除比较中断标志时不要用“读SR再读CCR”那种输入捕获的清除方式。在输出比较模式下CCxIF标志必须通过软件写0清除用标准外设库就是TIM_ClearITPendingBitHAL里则是__HAL_TIM_CLEAR_IT。否则标志位清不掉中断会没完没了地触发。2.3 CCR等于0或ARR时标志位叠加的边界情况如果CCR取值很极端双中断的时序会出现特殊行为。CCR0时比较匹配发生在CNT0处而下溢更新事件也发生在CNT0处两个事件完全重合。CCxIF和UIF会同时置位。如果比较中断和更新中断都开着这两个中断标志都会处于pending状态NVIC会按优先级决定先响应哪一个处理完一个之后另一个还挂在那里紧接着再进一次中断。看起来就像是某个周期里突然“多了一次中断”。ARR和CCR相等时上溢更新与匹配事件重合也是同样的现象。所以实际配置时如果CCR会靠近0或者ARR要么预先在逻辑上规避这些点要么在中断服务函数里进入后先同时把两个标志都清掉免得出现不可预测的嵌套触发。我见过有人在CCR0的情况下调试换相时序怎么调都对不齐最后才发现是标志位叠加造成的。3. 更新中断在上下溢的两次触发另一个“双中断”3.1 中央对齐模式下更新事件频率为何是周期的两倍很多人刚把中央对齐模式接进项目时最容易困惑的其实是更新中断。边沿对齐模式下一个PWM周期只有一次更新事件所以“更新中断 一个周期”这个直觉自动成立。但中央对齐模式下更新事件发生在两个地方CNT计数到ARR的上溢以及CNT计数到0的下溢。一个完整PWM周期包含一次上溢和一次下溢所以更新中断恰好触发两次。这意味着如果你用更新中断累计周期数做出来的时间基准天然就是PWM周期的两倍频率。正确换算关系如下参数边沿对齐模式中央对齐模式PWM频率fck / ((PSC1) * (ARR1))fck / ((PSC1) * 2 * ARR)更新中断频率等于PWM频率等于PWM频率的2倍比较中断频率模式3等于PWM频率单方向等于PWM频率的2倍不过要注意中央对齐模式更新事件虽然每个周期两次但URS位如果置1那么只有计数器上溢或下溢才能触发更新事件软件写入UG位、从模式触发这些都不会触发更新事件。做控制类应用建议置1避免调试时随手写个UG就多一次莫名其妙的中断。3.2 更新中断里读DIR判断上溢还是下溢比想象中绕更新中断里如果你想分辨刚发生的是上溢还是下溢第一个念头必然是读DIR位。但这里比比较中断要绕一步更新事件发生在方向翻转的瞬间事件发生之后DIR已经切换成反方向了。上溢更新后计数器从向上变成向下DIR1。下溢更新后计数器从向下变成向上DIR0。所以在更新中断里DIR1表示刚刚发生的是上溢DIR0表示刚刚发生的是下溢。这与比较中断里的判断逻辑刚好相反很多人在这一步栽了跟头把上溢和下溢完全弄反了后续采样相位全部错乱。更稳妥的做法是直接读CNT值。上溢更新时CNT从ARR附近开始往下走读出来接近ARR下溢更新时CNT从0附近开始往上走读出来接近0。配合DIR位双重验证基本万无一失。不过CNT在中断响应延迟期间会继续跑读出来的数和ARR或0会有偏差只能用于大方向判断。3.3 比较中断与更新中断的协作顺序实际项目里比较中断和更新中断经常一起用一个典型的分工是比较中断做高频、时间敏感的处理比如电流环采样、PWM占空比微调更新中断做低频、对时间不敏感的处理比如周期计数、速度环计算、通信状态机推进。这里要特别强调协作顺序。模式3下如果两个中断都开着一个PWM周期里事件顺序是固定的上坡CC匹配 → 上溢更新 → 下坡CC匹配 → 下溢更新。你可以依赖这个顺序设计“相位锁存”逻辑。比如在上坡CC中断里把ADC转换的结果搬运给控制算法在下坡CC中断里写新的CCR然后下溢更新事件把新CCR加载进影子寄存器下一个周期即生效。这样整个控制链路有严格的先后次序不会出现“数据用到一半被改写”的竞争问题。更新中断在中央对齐模式下频率翻倍这件事本身不是坏事反而给了你更多时间点去同步数据。关键是心里要时刻清楚一个PWM周期有两个更新点写周期相关的代码时不能默认“一次更新 一个周期”。4. 模式3双中断的应用场景采样窗口与控制相位拆解4.1 电机控制中的对称电流采样无刷电机和永磁同步电机的FOC控制普遍采用中心对齐PWM。为什么因为中心对齐PWM对应的电流纹波是对称的采样点放在三角波的顶点或者谷底时电流纹波正好处于平均位置采样值最能代表真实相电流。模式3的双中断在这里最大的价值是你可以把电流采样放在一个方向的匹配中断里把占空比更新放在另一个方向的匹配中断里两者互不干扰。比如上坡CC中断触发ADC注入采样下坡CC中断更新CCR。这样采样和控制各自拥有独立的相位槽不会互相挤占时间也不会因为ISR太长导致采样点漂移。如果用单电阻采样通常还需要根据当前开关状态判断采样窗口是否有效。这时DIR位同样有用根据DIR判断当前是PWM周期前半段还是后半段再结合三组PWM的比较值就能确定当前采样窗口对应的是哪一相电流。4.2 数字电源里避开开关噪声窗口数字电源的ADC采样有个很现实的麻烦开关管动作的瞬间PCB寄生电感和结电容会形成高频振荡这时采样出来的电压电流全是毛刺。边沿对齐模式下开关动作集中在周期边界附近采样点要么贴着开关动作要么离得太远导致控制周期变长。中央对齐模式3提供了一种很优雅的解法PWM引脚在CCR匹配时翻转翻转瞬间就是开关噪声最严重的时刻。而模式3把一个周期内的两次开关翻转分别放在了上坡匹配和下坡匹配你可以在上坡匹配中断里做“错峰处理”把ADC采样安排在开关翻转之后的固定延时处用定时器的TRGO或者DMA触发而不是直接在中断里读ADC。更关键的是桥臂的两次开关动作在中央对齐模式下天然关于周期中心对称这意味着你只需要处理一次相位逻辑另一次自动对称。对LLC、移相全桥这类需要精准死区和同步整流的拓扑这个特性可以省掉大量的软件补偿工作。4.3 把两次比较中断当两个控制相位用模式3双中断最“进阶”的用法是干脆把两次比较中断当成两个独立控制相位来用。很多控制算法实际上是“半周期对称”的比如两电平逆变器的PWM一个周期内上桥臂和下桥臂的导通时间计算是对称的又比如某些PFC的控制正半波和负半波需要分别处理。这种场景下边沿对齐模式只能在更新中断里一次性处理完整周期中断负载集中计算量大时很容易超时。模式3把半个周期的计算分散到两次中断里上坡匹配处理正半波下坡匹配处理负半波两个ISR各自尽量短CPU负载天然被削峰填谷。我实做过一个双向BUCK-BOOST数字电源控制频率100kHz用中央对齐模式3上坡CC中断做电感电流采样和峰值比较下坡CC中断做占空比计算和CCR更新整体效果比原来单点更新版动态响应明显更好。核心原因就是控制从“每个周期一次”变成了“每个半周期一次”数字控制延迟缩短了接近一个开关周期。5. 寄存器级配置与ISR参考实现5.1 时基、CCR、中断使能的配置顺序下面给一份可直接参考的初始化代码基于标准外设库以TIM1的通道1为例跑中央对齐模式3并开启比较中断和更新中断。寄存器级的思路在任何系列上都通用HAL下只是API封装不同。void TIM1_CenterAligned3_DualIRQ_Init(uint16_t arr, uint16_t psc, uint16_t ccr) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tb; TIM_OCInitTypeDef oc; NVIC_InitTypeDef nvic; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE); // PA8 复用为 TIM1_CH1 gpio.GPIO_Pin GPIO_Pin_8; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); // 时基配置关键点是计数器模式 tb.TIM_Period arr; tb.TIM_Prescaler psc; tb.TIM_ClockDivision TIM_CKD_DIV1; tb.TIM_CounterMode TIM_CounterMode_CenterAligned3; TIM_TimeBaseInit(TIM1, tb); // PWM模式1CCR为比较值 oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_Pulse ccr; oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, oc); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 比较中断 更新中断 TIM_ITConfig(TIM1, TIM_IT_CC1 | TIM_IT_Update, ENABLE); // 高级定时器必须开主输出否则PWM引脚无波形 TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); // 比较中断优先级高于更新中断 nvic.NVIC_IRQChannel TIM1_CC_IRQn; nvic.NVIC_IRQChannelPreemptionPriority 0; nvic.NVIC_IRQChannelSubPriority 0; nvic.NVIC_IRQChannelCmd ENABLE; NVIC_Init(nvic); nvic.NVIC_IRQChannel TIM1_UP_IRQn; nvic.NVIC_IRQChannelPreemptionPriority 1; nvic.NVIC_IRQChannelSubPriority 0; nvic.NVIC_IRQChannelCmd ENABLE; NVIC_Init(nvic); }有几个地方必须标重点。一是TIM_CounterMode_CenterAligned3这个宏对应CMS11也就是前面说的“上下都置位”的模式别选成CenterAligned1或2。二是高级定时器TIM1/TIM8必须调用TIM_CtrlPWMOutputs使能主输出否则引脚上就是没波形这是新手最容易卡住的地方。TIM_ARRPreloadConfig和TIM_OC1PreloadConfig是使能影子寄存器建议默认打开。中央对齐模式下影子寄存器的加载时机比边沿对齐复杂不打开预装载运行中修改ARR或CCR的值会在意想不到的时刻立刻生效波形容易瞬间乱掉。5.2 中断服务函数里如何分工对应上面的配置两个中断服务函数参考实现如下// TIM1 捕获/比较中断服务函数 void TIM1_CC_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_CC1); // 中央对齐模式下DIR位由硬件维护0向上计数1向下计数 if ((TIM1-CR1 TIM_CR1_DIR) 0) { // 上坡匹配采样、ADC触发等快速任务 g_up_match_flag 1; } else { // 下坡匹配占空比更新、控制算法 g_down_match_flag 1; } } } // TIM1 更新中断服务函数上溢/下溢各一次 void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); if ((TIM1-CR1 TIM_CR1_DIR) 0) { // 刚发生的是下溢更新计数器从0转向ARR g_underflow_flag 1; } else { // 刚发生的是上溢更新计数器从ARR转向0 g_overflow_flag 1; } } }中断服务函数里尽量不要做复杂计算更不要调用耗时函数。我的习惯是中断里只置标志位、启动DMA或者触发ADC真正的控制算法放在主循环或者较低优先级的中断里执行。否则中断里跑浮点计算一旦超过一个半周期采样点就会开始漂移控制性能会随之劣化。5.3 标准外设库与HAL的关键对照如果用HAL库思路完全一致只是入口变了。HAL_TIM_PWM_Start_IT开启PWM和比较中断HAL_TIM_Base_Start_IT开启更新中断回调函数需要在HAL_TIM_PWM_PulseFinishedCallback和HAL_TIM_PeriodElapsedCallback里实现。不同系列HAL回调名可能略有差异以CubeMX生成代码为准。方向判断的寄存器操作在HAL里同样可以直接读if ((htim-Instance-CR1 TIM_CR1_DIR) 0) { // 上坡方向 }需要注意HAL的PWM中断和OC中断有时共用同一个回调需要根据句柄和通道区分来源。实际排查时我建议先回退到寄存器直接读八成就知道问题在哪了。6. 实测踩坑预装载、初始化时序与中断优先级6.1 写CCR不是立刻生效加载时机和方向有关这是中央对齐模式下最容易出问题的地方。配置了TIM_OC1PreloadConfig之后你在代码里修改TIM_SetCompare1的值并不是立即生效而是等更新事件把预装载值拷到影子寄存器后才生效。边沿对齐模式下这个加载点只有周期边界好理解。中央对齐模式下更新事件有上溢和下溢两个问题就来了CCR的新值到底在哪个点加载参考手册里对中央对齐模式的补充说明很关键在中央对齐模式下如果在向上计数期间写入新的CCR预装载值它会在下一个下溢更新事件时加载如果在向下计数期间写入则在下一个上溢更新事件时加载。也就是说加载点取决于你写入数据的那一刻计数器处于哪个方向。这个特性实际用起来很别扭。如果你在下坡CC中断里更新CCR写入时计数器已经处于向下方向按照规则要等到下一个上溢更新事件才加载也就是下一个周期的起始点。如果你在上坡CC中断里更新则要等到下溢更新事件才加载也就是半个周期之后。想让每个周期都在同一点更新占空比就必须固定一个方向写并且明确知道生效点在哪。我的经验是只在同一个ISR相位里写CCR通常是下坡CC中断里写并且严格记录这是“下个周期生效”。这样即使预装载加载点和直觉不同控制时序也是确定且可复现的。6.2 开启定时器后的第一个周期方向是确定的但很多人没利用配置好定时器后调用TIM_Cmd(TIM1, ENABLE)计数器从0开始向上计数所以第一个匹配事件一定是上坡匹配第一个更新事件一定是上溢更新。这是一个可以用来做初始化同步的特性。比如你想让ADC采样在第一个PWM周期就落在正确相位上就可以在开启定时器时先触发一次软件启动或者等第一个上坡CC中断来临时再做采样通道的初始化。很多控制算法在启动瞬间的第一个周期里状态不对就是因为没有利用这个“首个方向确定”的特性默认了某个采样相位结果第一个周期就采错。如果想人为控制初始计数方向可以在初始化时向TIMx_CNT写入一个介于0和ARR之间的值。先把CNT预设到接近ARR的位置开启定时器后计数器会先向上走到ARR再掉头向下这样第一个匹配事件变成下坡匹配。但这个操作比较绕不推荐新手用保持默认的从0开始向上计数即可。6.3 中断频率翻倍的CPU负载问题与优先级分配模式3双中断的代价非常实在所有中断频率都是PWM频率的两倍。如果PWM跑20kHz比较中断加上更新中断全开CPU每秒要处理80k次中断。哪怕每次ISR只有几十个周期累加起来也是不小的开销主循环的实时性会被严重挤压。所以中断优先级分配要非常克制。我的做法是比较中断负责时间敏感任务给最高抢占优先级。更新中断负责周期统计和慢速任务给较低抢占优先级。中断服务函数里绝对不做printf、绝对不做浮点三角函数、绝对不调用延时函数。能放主循环的处理全部放主循环中断只负责“通知”和“触发”。如果控制算法本身只需要单点更新比如某些简单的温控PWM就别用模式3改用模式1或模式2把中断频率降回PWM频率省下来的CPU时间可以让主循环更从容。还有一个很实用的调试技巧在中断里翻转一个空闲GPIO用逻辑分析仪看这个引脚的波形。正常工作时它应该是一个严格的PWM倍频方波毛刺、丢波、相位抖动都能一眼看出来。量产固件里我通常保留这个调试引脚只占一个GPIO排查问题能省很多时间。6.4 高级定时器的死区与刹车注意互补通道的中断表现TIM1/TIM8这类高级定时器做电机控制时经常会开互补通道和死区生成。死区的插入是在PWM信号进入输出级之前完成的它会影响引脚的实际电平但不会改变计数器匹配的时间点。也就是说比较中断仍然在CNTCCR处触发死区只是把互补通道的电平翻转时间往后推了。这个差异在采样窗口设计时要考虑进去。如果采样点紧贴着比较中断触发点而实际开关动作因为死区被往后延迟了采样时可能刚好落在死区振荡窗口里。解决方案是把采样窗口相对比较中断点往后挪一个死区时间以上或者干脆把采样点放在另一个方向的匹配中断里避开本半周期的开关动作。还有一点如果使能了刹车功能BKIN引脚或软件刹车刹车发生后定时器输出被强制到安全电平计数器是否继续运行取决于配置。中断标志位在这种情况下也可能处于非预期状态。调试时如果发现模式3的双中断行为异常先把刹车和死区相关寄存器全部复位确认纯PWM模式下中断时序正常再逐步把高级功能加回来。
返回列表