STM8 Timer1互补PWM配置详解:从死区时间到电机驱动实战
1. 从需求到方案:为什么需要互补PWM?
在电机驱动、逆变电源这些强电应用里,直接用一个引脚输出的PWM波去控制一个桥臂的开关管,是行不通的。想象一下,一个H桥的上管和下管,如果控制信号完全同步,上管导通(PWM高电平)时下管也导通,那结果就是电源正负极直接短路,瞬间产生巨大的“直通”电流,轻则烧毁MOS管,重则炸机。为了避免这种灾难性的情况,我们引入了“互补PWM”的概念。
互补PWM的核心思想很简单:控制同一个桥臂的两个开关管的信号,必须是反相的。当上管导通时,下管必须关断;反之亦然。这样就能保证电流总是流经负载(电机或电感),而不是在电源内部短路。但是,仅仅反相就够了吗?还不够。半导体开关管(如MOSFET、IGBT)从导通到关断,或从关断到导通,都需要一个短暂的过渡时间。如果上一个管子还没完全关断,下一个管子就开始导通了,在这段重叠的时间里,依然会形成瞬间的直通短路。为了解决这个问题,我们必须在两路互补的PWM信号之间,插入一段两者都为低电平的“死区时间”(Dead Time)。在这段时间里,上下两个管子都处于关断状态,为开关状态的切换提供了安全缓冲。
所以,一个完整的电机驱动PWM方案,通常需要:两路互补的、带可调死区时间的PWM信号。对于STM8这类微控制器,这个任务通常由其高级控制定时器(在STM8中通常是Timer1)来承担。它内置了专门的互补输出通道和死区时间发生器,硬件自动生成带死区的互补波形,极大地减轻了CPU的负担并保证了时序的精确性。接下来,我们就以STM8S207系列为例,拆解如何配置Timer1来实现这个功能。
2. STM8定时器体系与Timer1的定位
STM8的定时器资源根据型号有所不同,常见的有TIM1、TIM2、TIM3、TIM4等。其中,TIM1是一个16位的高级控制定时器,是唯一一个支持互补PWM输出和死区插入的定时器,这也是我们本次实验的核心。其他的定时器(如TIM2/TIM3)通常用作通用定时器,支持基本的PWM输出,但没有互补和死区功能。
理解Timer1的通道结构是关键。以最常见的三相互补输出为例,Timer1拥有3个互补通道对:CH1/CH1N, CH2/CH2N, CH3/CH3N。这里的CHx是主输出通道,CHxN是其对应的互补输出通道。例如,CH1和CH1N就是一对,它们的波形是互补的(考虑死区后)。此外,TIM1还有一个独立的CH4通道,但它没有互补输出端。
这些通道的输出引脚是复用在GPIO上的,具体映射关系需要查阅对应型号的数据手册。例如,在STM8S207C8T6上:
- TIM1_CH1 对应 PC1
- TIM1_CH1N 对应 PC2
- TIM1_CH2 对应 PC3
- TIM1_CH2N 对应 PC4
- TIM1_CH3 对应 PC5
- TIM1_CH3N 对应 PC6
配置的第一步,就是将这些需要用到的GPIO引脚初始化为推挽输出模式,并且设置为高速输出,以提供足够的驱动能力和边沿速度。
// 以配置CH1和CH1N为例 void GPIO_Config(void) { // 使能GPIOC时钟 CLK->PCKENR2 |= CLK_PCKENR2_TIM1; // 配置PC1 (TIM1_CH1) 为推挽输出,高速模式 PC_DDR |= (1 << 1); // 输出模式 PC_CR1 |= (1 << 1); // 推挽输出 PC_CR2 |= (1 << 1); // 高速输出 // 配置PC2 (TIM1_CH1N) 为推挽输出,高速模式 PC_DDR |= (1 << 2); PC_CR1 |= (1 << 2); PC_CR2 |= (1 << 2); }注意:STM8的GPIO配置寄存器是分端口(如PC)的,每个端口有DDR(数据方向)、ODR(输出数据)、IDR(输入数据)、CR1和CR2(控制寄存器)。CR1和CR2的组合决定了具体模式(悬浮输入、上拉输入、开漏输出、推挽输出)以及输出速度。
3. Timer1核心寄存器配置详解
配置Timer1输出互补PWM,主要围绕几个核心寄存器展开:时基单元、捕获/比较模式寄存器、刹车与死区寄存器以及控制寄存器。我们一步步来。
3.1 时基单元配置:ARR与PSC
时基单元决定了PWM波的频率。它由自动重装载寄存器(ARR)和预分频器(PSC)共同控制。
- PSC (TIM1_PSCRH/L):对系统时钟(fMASTER)进行分频,得到计数器时钟CK_CNT。公式:CK_CNT = fMASTER / (PSC + 1)。PSC是一个16位寄存器。
- ARR (TIM1_ARRH/L):决定了计数器的周期。计数器从0计数到ARR,然后归零(或向下计数),形成一个周期。
PWM频率的计算公式为:Fpwm = CK_CNT / (ARR + 1)。假设主频fMASTER=16MHz,我们想要一个16kHz的PWM频率,可以设置PSC=0(不分频),那么CK_CNT=16MHz。ARR = 16MHz / 16kHz - 1 = 1000 - 1 = 999。
// 配置PWM频率为16kHz (假设主频16MHz) #define PWM_FREQ 16000 #define F_MASTER 16000000 #define PWM_PERIOD (F_MASTER / PWM_FREQ - 1) // 计算ARR值 void TIM1_BaseConfig(void) { // 设置预分频器为0 (不分频) TIM1->PSCRH = 0; TIM1->PSCRL = 0; // 设置自动重装载值,决定PWM周期 TIM1->ARRH = (uint8_t)((PWM_PERIOD) >> 8); TIM1->ARRL = (uint8_t)(PWM_PERIOD); }3.2 捕获/比较模式配置:CCMR与CCER
这部分配置决定了通道的工作模式(此处为PWM模式)和输出极性。
对于每个通道x(1, 2, 3),都有一个对应的捕获/比较模式寄存器(TIM1_CCMRx)和捕获/比较使能寄存器(TIM1_CCER)。
配置PWM模式 (TIM1_CCMRx):
- 我们需要将通道配置为PWM模式1或模式2。这两种模式的区别在于输出电平与比较结果的关系。
- 通常使用PWM模式1:当计数器值小于比较寄存器(CCRx)值时,输出有效电平(由极性决定);大于或等于时,输出无效电平。
- 需要设置
OCxM位域为110(PWM模式1)。
配置输出使能与极性 (TIM1_CCER):
CCxE位:使能主输出通道(CHx)。CCxNE位:使能互补输出通道(CHxN)。CCxP位:设置主输出通道的极性。0为高电平有效,1为低电平有效。CCxNP位:设置互补输出通道的极性。
极性配置需要根据你驱动的硬件电路来决定。常见的用法是设置主输出高电平有效,互补输出低电平有效(或者都设置为高有效,但靠死区错开)。这里我们设置主输出高有效,互补输出低有效。
void TIM1_OC_Config(void) { // 以通道1为例 // 1. 配置CCMR1寄存器,设置CH1为PWM模式1 TIM1->CCMR1 = (0x6 << 4); // OC1M = 110 (PWM mode 1) // 同时,CCMR1中的OC1PE位需要置1,开启预装载,防止修改CCR时产生毛刺 TIM1->CCMR1 |= (1 << 3); // OC1PE = 1 // 2. 配置CCER寄存器,使能输出并设置极性 // CC1E = 1 (使能CH1输出) // CC1NE = 1 (使能CH1N输出) // CC1P = 0 (CH1高电平有效) // CC1NP = 1 (CH1N低电平有效) TIM1->CCER1 |= (1 << 0) | (1 << 2); // 使能CC1E和CC1NE TIM1->CCER1 &= ~(1 << 1); // CC1P=0 TIM1->CCER1 |= (1 << 3); // CC1NP=1 // 3. 设置比较寄存器CCR1,决定占空比 // 占空比 = CCR1 / (ARR + 1) // 例如设置50%占空比 uint16_t duty = PWM_PERIOD / 2; TIM1->CCR1H = (uint8_t)(duty >> 8); TIM1->CCR1L = (uint8_t)(duty); }3.3 死区时间配置:BDTR寄存器
这是互补PWM安全性的核心。死区时间由TIM1_BKR(刹车和死区寄存器)中的DTG[7:0]位域控制。STM8的死区时间计算公式相对固定,取决于DTG位的设置:
- 如果
DTG[7:5] = 0xx: 死区时间 =DTG[6:0] * T_dts。其中T_dts是定时器时钟周期。 - 如果
DTG[7:5] = 10x: 死区时间 =(64 + DTG[5:0]) * T_dts。 - 如果
DTG[7:5] = 110: 死区时间 =(32 + DTG[4:0]) * 2 * T_dts。 - 如果
DTG[7:5] = 111: 死区时间 =(32 + DTG[4:0]) * 8 * T_dts。
T_dts通常是系统时钟周期(如果TIM1_CKD[1:0]=00)。假设系统时钟16MHz,T_dts = 1/16M = 62.5ns。
我们需要根据驱动MOS管的开关速度来计算所需的死区时间。例如,常用的IRF540 MOSFET,其导通延迟+上升时间约几十ns,关断延迟+下降时间也类似。为了安全,通常设置死区时间为200ns到1us。我们以500ns为例进行计算:
所需死区周期数 = 500ns / 62.5ns = 8个时钟周期。 查看公式,DTG[7:5]=000时,死区时间 =DTG[6:0] * T_dts。所以设置DTG[6:0] = 8即可,即DTG = 0x08。
void TIM1_DeadTime_Config(void) { // 配置死区时间,DTG = 0x08,对应约500ns死区 (16MHz主频) TIM1->BKR |= (0x08 << 0); // 设置DTG位域 // 同时,必须使能刹车功能(即使不用刹车,也要置位MOE位来使能输出) TIM1->BKR |= TIM1_BKR_MOE; // 主输出使能 }重要提示:
MOE(主输出使能)位必须置1,否则所有TIM1的输出通道(包括互补通道)都会被强制禁止。这在调试时如果忘了设置,会导致完全没有波形输出,是一个常见的坑。
3.4 控制寄存器与启动
最后,我们需要配置计数模式并启动定时器。
- TIM1_CR1:配置计数模式。中心对齐模式(用于电机控制减少谐波)或边沿对齐模式。我们使用边沿对齐模式(默认),向上计数。
- TIM1_CR2:输出空闲状态控制等,暂时用默认值。
- TIM1_EGR:通过置位
UG位来产生更新事件,将预装载寄存器(ARR, CCR)的值更新到影子寄存器。 - TIM1_CR1:置位
CEN位,启动计数器。
void TIM1_Start(void) { // 产生一个更新事件,更新所有预装载寄存器 TIM1->EGR |= TIM1_EGR_UG; // 开启自动重装载预装载使能(ARPE),确保修改ARR时平滑过渡 TIM1->CR1 |= TIM1_CR1_ARPE; // 启动计数器 TIM1->CR1 |= TIM1_CR1_CEN; }4. 完整代码整合与实测波形分析
将上述所有配置函数整合,并在main函数中调用,一个完整的互补PWM输出程序就完成了。
#include "stm8s.h" #define PWM_FREQ 16000 #define F_MASTER 16000000 #define PWM_PERIOD (F_MASTER / PWM_FREQ - 1) void GPIO_Config(void); void TIM1_BaseConfig(void); void TIM1_OC_Config(void); void TIM1_DeadTime_Config(void); void TIM1_Start(void); void main(void) { // 初始化系统时钟,假设使用内部16MHz HSI CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); // 1分频,16MHz GPIO_Config(); TIM1_BaseConfig(); TIM1_OC_Config(); TIM1_DeadTime_Config(); TIM1_Start(); while (1) { // 主循环中可以动态修改CCR1来改变占空比 // TIM1->CCR1H = ...; // TIM1->CCR1L = ...; } } // ... 上述各个配置函数的实现 ...将程序编译下载到STM8S207C8T6核心板,用示波器同时测量PC1(CH1)和PC2(CH1N)的波形,你应该能看到类似下图的波形:
CH1 (高有效): |¯¯¯|___|¯¯¯|___|¯¯¯|___| <--高电平--> <--死区--> <--低电平--> CH1N(低有效): |___|¯¯¯|___|¯¯¯|___|¯¯¯| <--低电平--> <--死区--> <--高电平--> 时间轴 ------------------------------------>波形解读:
- 互补关系:CH1为高电平时,CH1N为低电平;CH1为低电平时,CH1N为高电平。两者相位相反。
- 死区插入:在电平跳变的边缘,有一段两个信号都为低电平(无效电平)的区域。对于高有效的CH1,在从高变低后,会插入一段低电平死区,然后CH1N才从低变高。反之亦然。这确保了在任何切换瞬间,上下管都有一小段共同关断的时间。
- 占空比控制:通过修改
TIM1_CCR1寄存器的值,可以改变CH1高电平的时间(即占空比),CH1N的波形会自动互补调整。
5. 进阶话题与避坑指南
在实际项目中,仅仅输出波形只是第一步。下面分享几个从实际调试中总结出来的经验和容易踩的坑。
5.1 死区时间计算与实测验证
理论计算死区时间是一回事,实际测量又是另一回事。强烈建议用示波器测量死区时间是否与设定值相符。方法是用示波器的两个通道分别测量CH1和CH1N,使用边沿触发和光标功能,测量从CH1下降沿到CH1N上升沿之间的时间,以及从CH1N下降沿到CH1上升沿之间的时间。这两个时间应该基本相等,且接近你计算的值。
如果实测死区时间偏差很大,请检查:
- 系统时钟频率:你代码中
F_MASTER的定义是否准确?STM8启动后默认是2分频的HSI(8MHz),如果你没有重新配置时钟,那么实际主频是8MHz,计算就会出错。 - 定时器时钟使能:是否通过
CLK->PCKENR2寄存器使能了TIM1的时钟?如果没有使能,定时器根本不工作。 - GPIO速度:如果GPIO输出模式没有设置为高速(
CR2相应位置1),输出边沿会有延迟,这可能会被误认为是死区时间的一部分,或者影响死区时间的精度。
5.2 动态调整占空比与同步更新
在电机控制中,我们需要实时调整PWM占空比。直接写入CCR1寄存器是立即生效的,这可能会在PWM周期中间产生一个“断裂”的脉冲,导致电流畸变。为了解决这个问题,我们之前配置中开启了OC1PE(输出比较预装载使能)。
正确做法是使用预装载功能:
- 确保
TIM1_CCMR1中的OC1PE=1。 - 在修改占空比时,写入的是预装载寄存器
TIM1_CCR1。 - 这个新值会在下一个更新事件(UEV)发生时,从预装载寄存器传输到影子寄存器(真正起作用的寄存器),从而实现所有通道占空比的同步更新。
你可以通过软件产生更新事件(TIM1->EGR |= TIM1_EGR_UG;),但更常见的做法是等待定时器自然溢出产生更新事件。对于多通道同步(例如三相电机),这至关重要。
5.3 刹车功能与故障安全
高级定时器的“刹车”功能,用于在硬件检测到故障(如过流、过压)时,立即强制将所有输出通道置于一个安全状态(通常是无输出的状态)。这是硬件级别的保护,响应速度极快。
刹车功能通常通过一个特定的引脚(BKIN)来触发。当BKIN引脚检测到有效电平(可配置高或低有效),刹车电路会动作:
- 立即将输出置为无效状态或空闲状态(由
MOE、OSSI等位控制)。 - 可以产生刹车中断,通知CPU处理故障。
即使你不用刹车输入引脚,MOE位也必须置1才能使能输出,这是很多新手容易忽略的地方。如果发现配置完全正确但没有波形输出,第一个要检查的就是TIM1_BKR寄存器中的MOE位。
5.4 中心对齐模式与移相PWM
我们上面的例子是边沿对齐模式(向上计数)。在电机控制中,为了减小电流谐波和开关损耗,经常使用中心对齐模式(又称对称PWM)。
在中心对齐模式下,计数器先向上计数到ARR,再向下计数到0。PWM波形关于中心对称。这种模式下,PWM频率的计算公式变为:Fpwm = CK_CNT / (2 * ARR)。
配置方法是在TIM1_CR1寄存器中设置CMS[1:0]位为非00的值(例如01为中心对齐模式1),并设置DIR位为0(向上计数起始)。中心对齐模式的互补PWM波形更“干净”,对EMI更友好。
关于网络热词中提到的“STM32 3路PWM移相”,这在STM8的Timer1上也能实现。移相的本质是让多个通道的PWM脉冲起点(相位)不同。通过设置不同通道的捕获/比较寄存器(CCRx)的初始值,就可以实现固定的相位差。例如,设置CCR1=0, CCR2=ARR/3, CCR3=2*ARR/3,那么三个通道的PWM波就彼此相差120度,非常适合三相无刷电机的驱动。
5.5 调试技巧:当没有波形输出时
- 检查时钟树:确认
CLK_CKDIVR(时钟分频寄存器)和CLK_PCKENR2(外设时钟使能寄存器2)的TIM1位是否配置正确。没有时钟,一切免谈。 - 检查GPIO复用:STM8的引脚功能是复用的。除了配置GPIO模式,还需要确保定时器功能映射到了这个引脚。对于TIM1_CH1,它默认就是映射到PC1的,通常不需要额外的重映射配置(部分型号支持重映射,需查手册)。
- 检查MOE位:这是最高频的原因。确认
TIM1_BKR寄存器的MOE位已经置1。 - 检查输出使能位:确认
TIM1_CCER1寄存器中的CC1E和CC1NE位已经置1。 - 检查计数器是否运行:读取
TIM1_CR1的CEN位,或者直接读取TIM1_CNTRH/L(计数器值),看它是否在变化。 - 使用调试器:如果有ST-Link和调试环境(如IAR for STM8),可以单步运行,查看上述关键寄存器的值,是最直接的排查手段。
配置互补PWM输出是STM8应用于电机控制类项目的基石。理解每个寄存器位背后的硬件意义,而不仅仅是拷贝代码,才能在各种复杂场景下游刃有余。从示波器上看到第一组干净、带死区的互补波形时,那种成就感,就是嵌入式开发的乐趣所在。希望这篇笔记能帮你绕过我当年踩过的那些坑。
