简介:围绕STM32F103微控制器实现3通道120°相位差方波输出的完整工程源码包,面向嵌入式系统开发者及三相电机驱动、电源管理等应用方向的工程师。资源基于标准库完成TIM定时器PWM模式配置,包括预分频器、自动重载寄存器、各通道比较值设置,以及通道间相位同步的实现方法,并给出实际调试中死区时间与示波器校验等注意事项。压缩包共73个文件,以C源码和头文件为主体(33个.c与34个.h),另含汇编启动文件、Keil工程文件(.uvprojx/.uvoptx)及编译好的HEX固件,整体仅279KB,轻量且结构清晰。工程内还封装了系统延时、串口通信等底层驱动,便于快速整合到自定义项目。已有588人学习下载,代码注释完整、层次分明,对深入理解STM32多通道相位控制、设计与调试相关硬件方案具有实用价值,也为步进或无刷电机驱动等场景提供了可复用的参考实现。
1. 三路120°相位差方波,STM32F103最稳的输出方式是定时器PWM
三路频率相同、占空比各为50%、相位依次错开120°的方波,在BLDC无感方波驱动、三相逆变器开环测试、步进电机三相激励和霍尔信号模拟里都是常见波形。很多人第一反应是用三个定时器中断去翻转GPIO,实际做下来会发现相位抖动大、改频率时要同时改三个计数器,调试成本很高。STM32F103的定时器PWM通道天然支持多路独立比较输出,只要把三个通道的占空比都配置成50%,再把比较值按周期三分之一错开,硬件就会持续输出严格同步的三路方波,CPU完全不用参与翻转。本文从原理、参数计算、验证方法到可动态调频的相位累加器,给出可以直接抄走的实现方案。
2. 用定时器PWM生成3通道120°相位差方波:原理、参数与最小代码
2.1 为什么选TIM1的三个PWM通道,而不是定时器中断翻转GPIO
三路方波要求相位关系精确,核心指标是通道间的时基同步性。定时器中断翻转GPIO的实现,每个通道单独占用中断,中断响应延迟受NVIC优先级、Flash等待周期和其他外设中断影响,通道之间的偏差是ns到us级随机量,频率越高相位误差越明显。而PWM输出靠定时器硬件比较单元产生,三个通道共享同一个计数器,CCR与计数器匹配的瞬间由硬件置位或复位引脚,通道间偏差只来自比较器本身的电气路径,通常在几十纳秒以内。
STM32F103上做三路PWM,首选TIM1。TIM1是高级定时器,CH1、CH2、CH3三路输出对应PA8、PA9、PA10,三路共用一个ARR和PSC,天然满足同频同源。每路设置占空比50%,输出比较值按周期长度错开三分之一,就得到120°相位差。TIM1还带互补输出和死区,后续要扩展成六路互补PWM驱动半桥时,不需要换定时器。
如果只是输出三路方波做逻辑信号,TIM2到TIM4的多通道PWM也一样能实现,区别在于TIM1的更新事件、触发输出和从模式功能更丰富,适合做PWM同步和后续扩展。普通定时器没有MOE主输出使能位,配置流程更简单,但功能边界也更明显。
2.2 ARR、PSC和相位差的计算关系
输出频率由PSC和ARR共同决定。STM32F103的TIM1挂在APB2上,时钟72MHz,设置PSC为71后计数频率为1MHz,ARR决定一个周期的计数值。输出频率f与ARR的关系为:
f = 72MHz / ((PSC + 1) * (ARR + 1))若PSC=71,则每个计数单位1us。设单个周期总计数为N = ARR + 1,120°相位差对应的计数值为N/3。占空比50%意味着比较值设为N/2。
| 输出频率 | PSC | ARR+1 (N) | 计数频率 | 120°对应计数值 | 单计数相位精度 |
|---|---|---|---|---|---|
| 100Hz | 71 | 10000 | 1MHz | 3333 | 0.036° |
| 1kHz | 71 | 1000 | 1MHz | 333 | 0.36° |
| 10kHz | 71 | 100 | 1MHz | 33 | 3.6° |
| 1kHz | 7199 | 10000 | 10kHz | 3333 | 0.036° |
表格里能看到一个关键问题:高频输出时,如果计数频率固定1MHz,ARR很小,N/3取整带来的舍入误差会被放大。10kHz下N=100,120°对应33.33个计数,取33后单周期相位偏差0.33个计数,即约1.2°,这不是抖动,而是固定的量子化误差。要降低这个误差,需要提高计数频率,比如PSC设为0,计数频率72MHz,但ARR会缩小72倍,反而更糟。正确的做法是预先算好(PSC+1)*(ARR+1)的乘积尽量接近72MHz,同时让N能被3整除。比如输出1kHz,直接设PSC=23999、ARR+1=3000,计数频率3kHz不合适;更实际的做法是PSC=71、ARR+1=1000,N不能被3整除。这时可以把PSC调成23,计数频率3MHz,N=3000,能被3整除,120°对应刚好1000个计数,误差为零。
实际项目中不必追求绝对整除,但要清楚误差量级是否满足下游电路要求。BLDC霍尔信号模拟允许1°以内偏差,10kHz下用1MHz计数频率就不够,把PSC降到7,计数频率9MHz,N=900,120°对应300个计数,精度回到0.4°以内。
2.3 最小实现代码与初始化参数说明
以下是基于标准外设库V3.5的TIM1三通道PWM初始化代码,输出频率100Hz,相位差120°,占空比50%。
// 输出100Hz、三路120°相位差的方波,PSC=71 => 计数频率1MHz,ARR+1=10000 void TIM1_3Phase_SquareWave_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // PA8=TIM1_CH1, PA9=TIM1_CH2, PA10=TIM1_CH3 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Prescaler = 71; // 72MHz/(71+1)=1MHz TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 9999; // ARR,周期10000个计数 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); uint16_t half = 5000; // 50%占空比 uint16_t third = 3333; // 120°对应计数,10000/3取整 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 计数小于CCR时输出高 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_Pulse = half; TIM_OC1Init(TIM1, &TIM_OCInitStructure); // CH1:0°起,高电平到5000 TIM_OCInitStructure.TIM_Pulse = (half + third) % 10000; TIM_OC2Init(TIM1, &TIM_OCInitStructure); // CH2:滞后120° TIM_OCInitStructure.TIM_Pulse = (half + 2 * third) % 10000; TIM_OC3Init(TIM1, &TIM_OCInitStructure); // CH3:滞后240° TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC3PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_CtrlPWMOutputs(TIM1, ENABLE); // 高级定时器必须使能MOE TIM_Cmd(TIM1, ENABLE); }每个通道的比较值由两部分合成:half决定占空比50%,third决定该通道相对CH1的滞后时间。CH1从0°开始输出高电平,CH2从120°开始,CH3从240°开始。(half + third) % 10000里的取模运算处理跨周期情况,比如CH3的相位是240°加半个周期共420°,折算回0°到10000的计数范围内就是2000。
TIM1是高级定时器,初始化完成后必须调用TIM_CtrlPWMOutputs(TIM1, ENABLE)使能主输出,否则三路引脚没有任何波形。这是新手最容易漏掉的一步,普通定时器不需要这条语句,移植代码时尤其要留意。
3. STM32F103引脚分配、工程搭建与频率/相位动态调整
3.1 引脚选择避开JTAG和下载器冲突
PA8、PA9、PA10是TIM1三路PWM的默认复用引脚,不受JTAG占用影响,可以直接使用。若换成PB13、PB14、PB15做TIM1的通道映射,就需要重映射GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE),因为PB3、PB4默认被JTAG占用。很多人在DAP下载失败后查半天boot0和驱动,实际是PB3被复用成了JTDO,此时先检查GPIO_Remap_SWJ_JTAGDisable是否执行。非调试状态下把JTAG完全关闭能释放PB3、PB4、PA15,但不建议为了三路PWM去动SWD,除非你只用SWDIO和SWCLK两根线调试。
引脚初始化时用GPIO_Mode_AF_PP,这是复用推挽输出。如果误配成GPIO_Mode_Out_PP普通推挽,PWM波形不会出现在引脚上。GPIO速度建议用50MHz,方波边沿会更陡,虽然会带来轻微EMI,但逻辑信号场景下边沿质量更重要。
若手头板子没有引出PA8到PA10,退路是定时器中断翻转普通GPIO。用TIM2的更新中断,在中断服务函数里翻转三路GPIO,同时维护一个相位计数值。这种方案频率不高时能用,但10kHz以上中断开销和抖动都很明显,不建议作为产品方案。
3.2 从启动文件到库版本:一个可复现的工程基线
基于标准外设库V3.5的工程,启动文件要根据芯片容量选择:startup_stm32f10x_ld.s对应低密度,md对应中密度,hd对应高密度。F103C8T6是64KB Flash,属于中密度,用startup_stm32f10x_md.s。选错启动文件的现象很隐蔽:编译不报错,下载后程序不运行或进HardFault,因为中断向量表偏移不对。
库版本不同会导致函数名差异。V3.5里TIM_CtrlPWMOutputs的消息是TIM_CtrlPWMOutputs,HAL库里对应HAL_TIM_PWM_Start。若用CubeMX生成HAL工程,TIM1初始化代码会自动带上__HAL_TIM_MOE_ENABLE,思路相同。用标准库时确保stm32f10x_tim.c和stm32f10x_gpio.c已加入工程,并在stm32f10x_conf.h里放开对应的头文件宏,否则编译直接报未定义。
工程建好后先做最小验证:只初始化CH1,用示波器看PA8是否有方波。有波形再逐个打开CH2、CH3。这样能快速隔离配置问题,避免三路同时异常时无从下手。
3.3 运行时改频率不破坏相位差的方法
PWM输出频率由ARR和PSC共同决定。修改频率要在更新事件发生前完成,否则当前周期会出现异常脉宽。标准做法是先改TIM1的ARR,再等一次更新事件后改PSC。原因是:PSC有影子寄存器,写入后不会立即生效,要到下一次更新事件才装载;而ARR写入后是否立即生效取决于TIM_ARRPreloadConfig的配置。
void Set_Square_Freq(uint16_t freq_hz) { uint16_t arr = 1000000 / freq_hz - 1; // 按PSC=71、计数1MHz计算 TIM1->ARR = arr; TIM1->EGR |= TIM_EGR_UG; // 产生更新事件,装载ARR和PSC TIM1->CCR1 = arr / 2; // 重新计算50%占空比 TIM1->CCR2 = (arr / 2 + arr / 3) % (arr + 1); TIM1->CCR3 = (arr / 2 + 2 * arr / 3) % (arr + 1); }这段代码把相位关系用arr重新推导,改频率后三路相位差依然保持120°。特别注意EGR的UG位会同时清计数器并触发更新事件,如果此时刚好有比较事件,可能出现一个极窄脉冲。对逻辑信号测试无影响,但驱动功率电路时要注意在停机状态下改频率。
arr / 2和arr / 3都是整数除法,舍入误差不可避免。低频时影响可忽略,10kHz以上建议用前面表格里的方法重新选PSC,让(ARR+1)能被3整除,或者直接用第6章的相位累加器方案。
4. 用逻辑分析仪和输入捕获验证相位差,误差预算怎么看
4.1 逻辑分析仪直接测量三路上升沿时差
验证三路方波的相位差,最简单可靠的工具是逻辑分析仪。采样率至少要高于信号频率的20倍,否则上升沿定位误差太大。100Hz方波用2.5MHz采样率测,上升沿分辨率0.4us,相位分辨率0.014°,足够。测10kHz方波时,采样率提到25MHz以上,否则相位读数会出现明显的量化跳变。
在逻辑分析仪软件里同时抓PA8、PA9、PA10三路,添加测量项:相邻通道上升沿时间差,换算公式:
相位差(度) = 上升沿时间差(us) * 频率(Hz) * 360 / 1000000比如1kHz信号,CH2比CH1滞后333us,相位差就是333 * 1000 * 360 / 1000000 = 119.88°。这个结果与理论值120°差0.12°,主要来自N/3取整舍入,而不是PWM硬件误差。若实测偏差明显大于预算,优先怀疑两个地方:一是示波器探头或逻辑分析仪通道间延迟不一致,二是PSC或ARR配置被其他代码改过。
4.2 用定时器输入捕获做相位差测量,不依赖外部仪器
没有逻辑分析仪时,可以用STM32F103自身定时器的输入捕获来测两个通道的相位差。思路是让TIM2的CH1捕获第一路方波的上升沿,并把该上升沿作为从模式复位源,每次CH1上升沿把TIM2计数器清零,然后TIM2的CH2捕获第二路方波的上升沿,捕获值就是两路上升沿的时间差。
// TIM2_CH1接被测A相,TIM2_CH2接被测B相,PSC=71,计数频率1MHz void TIM2_PhaseMeas_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; // PA0=CH1, PA1=CH2 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); TIM_InternalClockConfig(TIM2); TIM2->PSC = 71; // 1MHz计数 // CH1上升沿捕获,作为从模式复位触发源 TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0; TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1); // 内部触发源选TI1 TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset); // 从模式复位 // CH2上升沿捕获,直接读取相对时间 TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI; TIM_ICInit(TIM2, &TIM_ICInitStructure); TIM_Cmd(TIM2, ENABLE); } // 读取相位差,返回单位us,乘以0.36再乘频率(kHz)得到度数 uint16_t Read_Phase_Delta(void) { return TIM2->CCR2; }这段代码利用了定时器从模式复位功能:CH1上升沿到来时计数器清零,CH2的捕获值直接从CCR2读出,单位是计数周期。PSC=71时,计数频率1MHz,CCR2数值就是us。测100Hz方波时,理论读数3333us,换算成角度公式为:
角度 = CCR2 * 频率(Hz) * 360 / 1000000这个方法有两点限制。TIM2是16位计数器,最大计数值65535,若被测频率太低、周期超过65.535ms,计数器会溢出,此时需要把PSC调大或改用TIM2和TIM5的32位模式。另一个限制是CH1和CH2的捕获必须来自同一时钟域的方波信号,如果两路信号完全异步,从模式复位会让测量结果随机化,本场景下两路同源,不存在这个问题。
4.3 相位误差的3个来源与预算表
三路120°相位差方波的误差来源主要有三类:量子化误差、GPIO输出延迟差异、外部负载引起的边沿变形。
| 误差来源 | 典型量级 | 是否能消除 | 处理方式 |
|---|---|---|---|
| N/3取整量子化 | 0~1个计数周期 | 能 | 调整PSC使ARR+1能被3整除 |
| GPIO上升沿延迟差异 | 几ns到十几ns | 不能 | 选择同组GPIO,走线等长 |
| 负载电容导致边沿变缓 | 几十ns到us | 部分能 | 降低负载,提高GPIO速度等级 |
量子化误差是最主要的可控项。前面表格里已经算过,1MHz计数频率下输出10kHz方波,120°相位差对应33.33个计数,取整后偏差0.33us,换算成角度约1.2°,对于霍尔信号模拟已经偏大。解决办法是把计数频率提到与ARR匹配。设PSC=7,计数频率9MHz,ARR+1=900,120°对应300个计数,误差降到0.4°。再往上提计数频率,受限于72MHz主频和16位ARR,1kHz以下信号很容易做到零误差,高频信号则需要接受0.1°到0.5°的量化误差,或者采用相位累加器方案。
5. 无感方波BLDC与霍尔模拟场景:从三路方波到六步换向
5.1 用三路120°方波模拟霍尔传感器输出
无感方波BLDC控制里,霍尔传感器的输出正是三路占空比50%、相位差120°的方波。电机转子位置不同,三路霍尔信号组合出6种状态码,对应六步换向。用本方案生成的三路PWM方波可以直接接入控制板的霍尔接口,做开环测试:给定频率对应电机转速,相位关系固定120°。
使用时要确认霍尔接口的电气标准。多数霍尔传感器是开漏输出配上拉电阻,高电平是上拉到5V或3.3V。STM32的PA8、PA9、PA10是推挽输出,高电平3.3V,若控制板霍尔接口上拉到5V,两者电平兼容性要看具体输入阈值。保险做法是在三路输出后串联1k电阻,再接一个上拉到5V的2.2k电阻,形成开漏兼容的电平转换。
// 霍尔模拟模式,三路输出直接映射到霍尔接口 // 频率对应电机机械转速:f = 转速(rpm) * 极对数 / 60 void Hall_Simulator_Start(uint16_t rpm, uint8_t pole_pairs) { uint16_t freq = rpm * pole_pairs / 60; Set_Square_Freq(freq); }转速与频率的换算公式依赖极对数,6极对电机1200rpm对应输出120Hz。改频率时用上面第三章的Set_Square_Freq,三路相位关系保持不变。这种模拟方法可以验证控制板的换向逻辑是否正确,电机没接也能跑完整六步。
5.2 外部比较器法无感方波里的相位关系
无感方波控制反电动势过零检测常用比较器方案,三相端电压经过分压和滤波后进比较器,输出三路方波信号。理想情况下,反电动势过零时刻与换向时刻相差30°电角度,因此比较器输出方波与真正需要的换向信号存在固定相位偏移,软件里通常用延时或定时器补偿。
这个场景下,标题里的三路120°相位差方波可以作为比较器输出的模拟信号源,用来验证控制器的过零检测逻辑。此时三路方波的相位关系必须严格120°,因为比较器输出的三个方波本身就代表三相的反电动势极性,相位偏差会被软件当成转子位置误差。
实测中比较器输出方波的边沿质量比PWM输出差很多,因为端电压经过分压和RC滤波后,过零附近的变化是缓慢的。若把STM32的方波直接送入控制板,边沿过于干净,反而掩盖了滤波电路的真实特性。模拟比较器信号时,在三路输出后各加一个RC低通,时间常数按电机电频率的十分之一到五分之一选取,边沿变得圆润,更接近真实工况。
5.3 接半桥驱动时要注意的边界
如果三路方波要驱动半桥正负方波输出电路,只输出三路PWM是不够的。半桥需要上管和下管互补信号,以及防止上下管同时导通的死区时间。TIM1天然支持这个扩展:CH1、CH2、CH3作为上管信号,CH1N、CH2N、CH3N作为下管信号,相位关系不变,死区由TIM1的BDTR寄存器控制。
TIM_BDTRInitTypeDef TIM_BDTRInitStructure; TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable; TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Disable; TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_OFF; TIM_BDTRInitStructure.TIM_DeadTime = 0x50; // 死区时间,具体值由时钟分频决定 TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High; TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);死区时间的计算要结合TIM_ClockDivision和BDTR里的DTS分频。APB2时钟72MHz,TIM_ClockDivision为1时,DTS为72MHz,BDTR低7位每个单位约13.9ns,0x50对应约1.1us。死区过小,上下管直通烧毁;过大,换向效率下降。功率稍大的应用建议在PWM输出和栅极驱动芯片之间加隔离,IR2104这类半桥驱动芯片自带死区时间设置引脚,硬件设计上可以双保险。
6. 相位累加器:把3通道120°方波升级成任意相位差信号源
6.1 PWM比较法在高频或非整除频率下的精度局限
前面所有方案都是固定ARR、固定CCR的PWM比较法,优点是全程硬件输出、CPU零负担,缺点是相位精度受N/3取整限制。当输出频率需要动态变化,或者频率值不是计数时钟的整数分频时,CCR的量化误差会直接反映到相位关系上。比如输出137Hz,1MHz计数频率下N≈7299.27,取7299,实际频率偏了0.0037%,看似很小,但相位差3333.33取整后的0.33个计数量化误差在BLDC高速运转时会造成可测量的换向偏差。
PWM比较法另一个痛点是改频率时要同步改三个CCR,任何赋值顺序不当都可能产生一拍错误脉宽。相位累加器用单一累计值推导三路相位,天然规避了这个问题。
6.2 用32位相位累加器在中断里更新三路CCR
相位累加器的思路来自DDS直接数字频率合成。用一个32位变量phase_acc表示当前相位,满量程2^32对应360°。每个定时器更新周期累加一个步进值,自加溢出即相位回绕,天然连续。三路相位分别偏移0、2^32/3、2^32*2/3,映射到CCR前做一次乘法运算。
static uint32_t phase_acc = 0; static uint32_t phase_step = 0; // freq_hz为输出频率,更新中断频率等于freq_hz void PhaseAcc_SetFreq(uint32_t freq_hz) { // 72MHz / (PSC+1) 为更新中断频率,此处假设PSC=71即1MHz // 每次更新中断累加的相位步进 = freq / 1MHz * 2^32 phase_step = (uint32_t)(((uint64_t)freq_hz << 32) / 1000000ULL); TIM1->ARR = 1000000 / freq_hz - 1; // 保持更新中断频率接近1MHz } void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); phase_acc += phase_step; // 三路相位偏移:0°、120°、240° uint32_t p1 = phase_acc; uint32_t p2 = phase_acc + 1431655766u; // 2^32/3 uint32_t p3 = p2 + 1431655766u; // 再次加2^32/3 // 将32位相位映射到ARR范围 TIM1->CCR1 = (uint16_t)(((uint64_t)p1 * (TIM1->ARR + 1)) >> 32); TIM1->CCR2 = (uint16_t)(((uint64_t)p2 * (TIM1->ARR + 1)) >> 32); TIM1->CCR3 = (uint16_t)(((uint64_t)p3 * (TIM1->ARR + 1)) >> 32); } }((uint64_t)p * (TIM1->ARR + 1)) >> 32把32位相位映射到0到ARR的CCR区间。phase_acc溢出后自然回绕,p2、p3同理,不需要手动取模。映射得到的CCR仍是50%占空比,因为每路方波在相位超过半周期时由PWM硬件自动翻转极性,累加器本身不关心占空比。
相位累加器让频率分辨率达到1MHz / 2^32,约0.00023Hz,任意非整数频率都能精确合成。频率切换只需改phase_step一个变量,三路CCR在下一个更新中断自动重新计算,不存在中间态。
6.3 两种实现方式的适用边界
相位累加器适合频率需要持续调节、相位精度要求高、中断负载可接受的场合,输出频率在几十Hz到几十kHz范围内都很从容,100Hz以下时更新中断频率低,CPU占用可忽略。代价是每个周期都要进一次中断,且更新中断频率等于输出频率,输出10kHz时每秒1万次中断,STM32F103在72MHz下能轻松承受,但输出500kHz时每秒50万次中断就会挤压主循环,不如回到纯PWM比较方案。
PWM比较法适合固定频率、高频率输出的场景。1kHz固定输出,PSC和ARR算好后一条语句TIM_Cmd启动,CPU完全不管,输出频率能推到MHz级。两种方案不是替代关系,相位累加器解决的是动态频率和相位精度问题,固定频率的严苛场景,PWM比较法的稳定性和极低抖动仍然是首选。
如果输出频率还要从1MHz往上走,更新中断本身会成为瓶颈,下一步要考虑的是把相位累加逻辑下沉到定时器从模式或DMA,靠硬件更新CCR,那已经是另一个量级的工程设计。
本文还有配套的精品资源,点击获取