1. 项目概述:从基础PWM到互补输出的跨越
玩过STM8或者STM32的朋友,对PWM(脉冲宽度调制)肯定不陌生。点个灯、驱动个舵机、让电机转起来,PWM都是最基础也是最核心的技能。但当你需要驱动一个半桥或者全桥电路,比如控制直流有刷电机的正反转,或者驱动一个简单的逆变器时,单路的PWM就力不从心了。这时候,你需要的是两路在逻辑上完全相反、并且中间最好还能插入一段“安全空白区”的波形——这就是互补PWM输出。
我最近在折腾一个基于STM8S105的小型电机驱动板,核心需求就是用单片机直接产生带死区的互补PWM来驱动一个半桥MOS管。听起来好像高级定时器的标配功能?但STM8的资源比STM32要紧张得多,玩法也更“原始”一些,需要更仔细地配置定时器的每一个寄存器。网上资料虽多,但要么是STM32的,要么只讲基础PWM,关于STM8互补输出,特别是如何精确配置死区时间,讲得透彻的并不多。踩过几个坑后,我把整个配置流程、原理和调试心得梳理出来,如果你也在STM8上搞电机控制、电源转换或者任何需要互补信号的应用,这篇笔记应该能帮你省下不少时间。
简单说,这个项目就是利用STM8S系列单片机(以105为例)的高级控制定时器1(TIM1),配置其两个通道(如CH1和CH1N)输出一对中心对齐或边沿对齐的互补PWM波,并加入可编程的死区时间,确保功率开关管不会因为同时导通而短路烧毁。整个过程不依赖CubeMX(STM8的CubeMX生态远不如STM32),直接操作寄存器,理解更深刻。
2. 核心需求与硬件资源解析
2.1 为什么需要互补PWM与死区时间?
想象一下控制一个半桥电路,上管和下管是两个MOSFET。理想的开关逻辑是:上管开时下管关,下管开时上管关。如果用两个普通的GPIO输出反相的逻辑,似乎也能实现。但在实际硬件中,MOS管的开启和关闭都需要时间(即上升时间和下降时间)。如果“关断”命令和“开启”命令同时(或几乎同时)发出,可能会出现一个管子还没完全关断,另一个管子已经开始导通的情况,这会在电源和地之间形成一条低阻通路,产生巨大的瞬间电流,也就是致命的“直通”或“穿通”现象,结果往往是MOS管和驱动芯片一起炸掉。
为了解决这个问题,我们需要在两路互补的PWM信号之间,插入一段两者都为低电平(对于高电平有效的驱动而言)的“空白区”,这就是死区时间。在这段时间里,确保一个管子已经完全关断后,另一个管子才被允许开启。死区时间是硬件安全的关键,通常需要根据你所使用的MOS管或IGBT的开关特性来微调,一般在几十纳秒到几微秒之间。
2.2 STM8S105的定时器家底
STM8S105的资源对于8位机来说算是丰厚的,但与STM32相比就需要精打细算。它有几个定时器:
- TIM1:16位高级控制定时器。这是实现互补PWM输出的唯一选择。它功能最强,支持:
- 向上、向下、中心对齐三种计数模式。
- 多达4个独立的通道,每个通道都可以配置为PWM输出(OCx)。
- 每个通道都对应一个互补输出(OCxN)。
- 内置可编程的死区时间发生器。
- 刹车(Break)功能,用于紧急关断PWM输出。
- TIM2/TIM3:16位通用定时器。它们有PWM输出功能,但没有互补输出和死区发生器。适合做普通的单路PWM。
- TIM4:8位基本定时器。主要用于时基、延时。
所以,我们的目标很明确:攻克TIM1。重点吃透它的计数模式、输出比较模式、互补输出使能和死区时间配置这几个关键部分。
2.3 引脚分配与硬件连接
以TIM1的通道1(CH1)和它的互补通道1(CH1N)为例,在STM8S105K4T6这款常见的LQFP32封装芯片上:
- TIM1_CH1对应PC1。
- TIM1_CH1N对应PC2。
在你的原理图和PCB布局时,务必把这两个引脚连接到你的半桥/全桥驱动芯片的输入脚上(例如IR2104、IR2110的HIN和LIN)。驱动芯片的输出再连接MOS管的栅极。切记:单片机引脚不能直接驱动功率MOS管,必须经过驱动芯片。同时,驱动芯片的电源、自举电容等外围电路要严格按照数据手册设计,这是硬件稳定的基础。
3. 定时器1(TIM1)互补PWM配置详解
这里我们以生成一个频率为10kHz,死区时间为1us的互补PWM为例。假设系统主频fMASTER为16MHz(HSI)。
3.1 定时器时钟与预分频设置
首先,TIM1的时钟来源于系统主频。我们需要计算计数频率。
- 确定PWM频率(fPWM):10kHz = 10000 Hz。
- 选择计数模式:对于电机控制,中心对齐模式(中央对齐PWM)更常用,因为它能减少谐波,但理解原理可以从边沿对齐模式开始。这里我们先讲边沿对齐(向上计数)。
- 计算自动重装载值(ARR):
- 在边沿对齐模式下,PWM频率
fPWM = fCK_PSC / (ARR + 1)。其中fCK_PSC是经过预分频器后的定时器时钟。 - 我们先设定预分频器
PSC = 0,即不分频,fCK_PSC = fMASTER = 16MHz。 - 那么
ARR = fCK_PSC / fPWM - 1 = 16,000,000 / 10,000 - 1 = 1600 - 1 = 1599。 - ARR必须是0-65535之间的整数。1599完全合理。
- 在边沿对齐模式下,PWM频率
- 计算死区时间对应的计数值:
- 死区时间
DT = 1us = 0.000001 s。 - 定时器计数周期
T_cnt = 1 / fCK_PSC = 1/16MHz = 0.0625us。 - 死区时间需要的计数个数
DT_CNT = DT / T_cnt = 1us / 0.0625us = 16。 - STM8 TIM1的死区时间发生器(DTG)有一个特定的编码格式,我们需要将
DT_CNT=16转换成寄存器值。根据数据手册,公式为:当0 <= DT_CNT <= 127时,DTG[7:0] = DT_CNT。所以这里DTG = 16。
- 死区时间
注意:这里为了计算方便,假设了
PSC=0。在实际项目中,如果fMASTER很高,计算出的ARR可能很小(比如小于100),导致PWM分辨率(即占空比调节精度)很低。这时可以适当增大PSC值,提高ARR,从而获得更高的分辨率。但要注意,PSC增大会降低计数频率,可能影响死区时间的最小步进。需要权衡。
3.2 关键寄存器配置步骤(代码级解析)
下面用类似C的伪代码展示核心配置流程,我会穿插在IAR for STM8环境下的实操要点。
#include "stm8s.h" void TIM1_PWM_Complementary_Init(void) { // 1. 使能TIM1的外设时钟(在STM8中,默认是开启的,但好的习惯是确认一下) CLK->PCKENR1 |= CLK_PCKENR1_TIM1; // 使能TIM1时钟 // 2. 配置GPIO引脚为推挽输出(高速模式,以驱动外部电路) GPIOC->DDR |= GPIO_PIN_1 | GPIO_PIN_2; // PC1, PC2 设置为输出模式 GPIOC->CR1 |= GPIO_PIN_1 | GPIO_PIN_2; // 推挽输出模式 GPIOC->CR2 |= GPIO_PIN_1 | GPIO_PIN_2; // 输出速度高达10MHz // 3. 配置TIM1时基单元 TIM1->PSCRH = 0; // 预分频器高位 (PSC=0) TIM1->PSCRL = 0; // 预分频器低位 TIM1->ARRH = (1599 >> 8) & 0xFF; // 自动重装载值高位 TIM1->ARRL = 1599 & 0xFF; // 自动重装载值低位 // 4. 配置通道1为PWM模式1(向上计数时,CNT<CCR1为有效电平) // TIM1_CCMR1: OC1M[2:0] = 110 (PWM模式1) TIM1->CCMR1 = (TIM1->CCMR1 & ~TIM1_CCMR_OCM) | (0x6 << 4); // 设置输出极性:CH1高电平有效,CH1N低电平有效(这是互补的常见设置) // TIM1_CCER1: CC1P=0 (高有效), CC1NP=1 (低有效) TIM1->CCER1 = (TIM1->CCER1 & ~(TIM1_CCER1_CC1P | TIM1_CCER1_CC1NP)) | TIM1_CCER1_CC1NP; // 使能通道1主输出和互补输出 TIM1->CCER1 |= TIM1_CCER1_CC1E | TIM1_CCER1_CC1NE; // 5. 配置死区时间 // 根据前面计算,DTG = 16。直接写入死区时间寄存器。 // TIM1_DTR: DTG[7:0] = 16 TIM1->DTR = 16; // 1us死区 // 6. 配置刹车和死区寄存器(BDTR),这是使能互补输出的关键! // MOE: 主输出使能,必须置1才能输出PWM。 // OSSI: 输出空闲状态控制,我们设为0(关闭状态下输出无效电平)。 // OSSR: 运行模式下输出空闲状态控制,根据需求设置。 // 这里我们先简单使能MOE。 TIM1->BKR = TIM1_BKR_MOE; // 7. 设置初始占空比(通过捕获/比较寄存器CCR1) // 假设初始占空比为50%,则 CCR1 = ARR * 50% = 1599 * 0.5 = 799.5 ≈ 800 TIM1->CCR1H = (800 >> 8) & 0xFF; TIM1->CCR1L = 800 & 0xFF; // 8. 启动定时器(向上计数模式) TIM1->CR1 |= TIM1_CR1_CEN; // 使能计数器 }关键点解析与避坑指南:
- GPIO配置:互补PWM输出引脚必须配置为推挽输出(
CR1置1),并且为了快速响应,建议开启高速模式(CR2置1)。如果配置为开漏输出,波形幅度会不对。 - 输出极性:
CC1P和CC1NP位控制极性。CC1P=0表示CH1高电平有效(即占空比期间输出高)。CC1NP=1表示CH1N低电平有效。这样配置后,在死区时间以外,CH1和CH1N的信号永远是反相的。这个组合可以根据你的驱动芯片逻辑需求调整。 - 主输出使能(MOE):这是最大的一个坑!
TIM1_BKR寄存器中的MOE位不置1,即使前面配置全对,引脚上也没有任何波形输出。它相当于互补PWM输出的总开关。 - 死区时间寄存器(DTR):直接写入计算好的值即可。STM8的死区时间发生器是硬件实现的,插入的死区时间非常精确,不受软件干扰。
- 中心对齐模式:如果需要中心对齐PWM,只需修改
TIM1_CR1寄存器的CMS位。例如,设置为0x2(中央对齐模式1,先向下后向上计数)。在中心对齐模式下,PWM频率计算公式变为fPWM = fCK_PSC / (2 * ARR),计算ARR时需要注意。
4. 调试与波形观测实战
代码写好了,下载到芯片里,怎么知道对不对呢?示波器是必不可少的工具。
4.1 连接与测量
将示波器的两个探头,分别连接到PC1(CH1)和PC2(CH1N),共地端接板子的地。触发模式可以设为边沿触发,触发源选CH1。
预期看到的波形:
- 两个频率同为10kHz的方波。
- 两个波形相位相反。当CH1为高电平时,CH1N为低电平,反之亦然。
- 在CH1的下降沿到CH1N的上升沿之间,以及CH1N的下降沿到CH1的上升沿之间,会有一段两个通道都为低电平(假设你设置CH1N低有效)的区域,这就是死区时间。用示波器的光标功能测量这段间隔,应该接近你设置的1us(由于时钟精度和测量误差,可能在0.9us-1.1us之间)。
4.2 常见问题与排查技巧
完全没有波形输出
- 检查MOE位:十有八九是
TIM1_BKR寄存器中的MOE位没有置1。用调试器单步执行,或者直接在初始化后添加一句TIM1->BKR |= TIM1_BKR_MOE;。 - 检查GPIO配置:确认
DDR、CR1、CR2寄存器是否正确配置为输出模式。可以用一个简单的GPIO翻转程序先测试一下这两个引脚是否能正常输出高低电平。 - 检查定时器使能:确认
TIM1_CR1的CEN位已置1。 - 检查引脚复用:STM8的引脚复用功能相对简单,但也要确认没有其他外设(比如别的定时器或串口)占用了PC1和PC2。
- 检查MOE位:十有八九是
只有一路有波形,另一路没有或常高/常低
- 检查互补输出使能位:确认
TIM1_CCER1寄存器中的CC1NE位(对于CH1N)是否置1。 - 检查输出极性位:检查
CC1P和CC1NP的设置是否符合预期。如果CC1NP设成了0(高有效),那么CH1N的波形就会和CH1同相,这显然不对。可以尝试在运行时修改这两个位,观察波形变化。 - 检查死区时间是否过大:如果你不小心把死区时间设置得比PWM的有效高电平时间还长,那么互补通道可能永远没有机会输出有效电平,看起来就像常低。检查
DTR寄存器的值,重新计算。
- 检查互补输出使能位:确认
死区时间测量值与设置值不符
- 时钟源确认:确认你的系统主频
fMASTER是否真的是16MHz。如果用了外部晶振(HSE)或者分频了内部时钟(HSI),fCK_PSC会变,需要重新计算。 - DTG计算公式:死区时间
DT = (DTG[7:0]) * T_dtg。其中T_dtg可能是T_ck(定时器时钟周期)或其倍数,具体取决于DTG[7:5]的值。我前面举例是DTG<128的情况,公式最简单。如果死区时间较长,需要参考数据手册中DTG[7:5]对应的乘数表。务必仔细阅读参考手册中“TIM1 deadtime generation”这一节。
- 时钟源确认:确认你的系统主频
PWM频率不对
- ARR和PSC计算错误:这是最常见的原因。再核对一遍公式:
fPWM = fMASTER / ((PSC+1) * (ARR+1))(边沿对齐向上计数)。 - 中心对齐模式公式用错:中心对齐模式下,
fPWM = fMASTER / ((PSC+1) * 2 * ARR)。 - 系统时钟树配置错误:在main函数初始化时,是否配置了正确的时钟源和分频?用示波器测量一个已知的定时器中断翻转的引脚,可以反推出实际的系统时钟频率。
- ARR和PSC计算错误:这是最常见的原因。再核对一遍公式:
5. 高级应用与优化思路
基础功能调通后,可以玩一些更高级的。
5.1 动态调整占空比与死区
在实际应用中,比如电机调速,需要动态改变PWM占空比。这很简单,只需在运行中修改TIM1_CCR1寄存器的值即可。为了防止修改过程中产生毛刺,对于STM8,通常可以直接写,因为16位寄存器写入是原子操作(高8位和低8位分开写入,但硬件有缓冲)。更稳妥的做法是在PWM周期的一个特定点(如下一个更新事件)进行更新,这可以通过使能预装载寄存器(TIM1_CR1的ARPE位)和设置TIM1_CCMR1中的OC1PE位来实现。
动态调整死区时间比较少见,但也可以实现,直接修改TIM1_DTR寄存器即可。注意,死区时间的改变可能会在下一个PWM周期生效,具体时机需查手册。
5.2 刹车(Break)功能的使用
TIM1的刹车功能是一个重要的安全特性。你可以将一个外部信号(比如过流检测电路的输出)连接到特定的刹车输入引脚(如TIM1_BKIN)。当刹车信号有效时,硬件会自动将PWM输出强制置为一个预设的安全状态(通常都是无效电平,比如CH1拉低,CH1N拉高),并且产生中断。这为系统提供了硬件级的保护,响应速度远快于软件检测。
配置刹车功能涉及TIM1_BKR(刹车寄存器)和TIM1_IER(中断使能寄存器)。需要设置刹车输入极性、刹车后输出状态(通过OSSR和OSSI位控制)以及使能刹车中断。这是一个值得深入研究的保护机制。
5.3 从边沿对齐切换到中心对齐
对于电机驱动和某些电源应用,中心对齐PWM可以减少电流纹波和开关损耗。切换起来并不复杂:
- 停止定时器(
CEN=0)。 - 修改
TIM1_CR1寄存器的CMS[1:0]位。例如,设置为0x2(中央对齐模式1,计数器先向下计数到0,再向上计数到ARR,如此反复)。 - 重新计算ARR值。因为频率公式变了。
- 可能需要调整
CR1中的DIR位(方向位),硬件在中心对齐模式下会自动管理方向,通常不用管。 - 重新使能定时器(
CEN=1)。
用示波器观察,中心对齐PWM的波形特点是,每个PWM脉冲关于计数周期的中心点对称。
折腾STM8的互补PWM输出,是一个从“会用定时器”到“理解定时器内核”的很好跳板。它迫使你去读那些平时可能跳过的寄存器描述,去计算时钟和计数关系,去思考硬件安全机制。当你在示波器上第一次清晰地看到那两段完美的、中间带着一小段安全间隔的互补方波时,那种成就感是调通一个LED呼吸灯无法比拟的。这份笔记里的代码和参数都是经过实际板子验证的,你可以直接拿去作为骨架修改。最重要的是理解每个配置步骤背后的意图,这样无论换到STM8的哪个型号,或者未来升级到STM32的复杂定时器,你都能快速抓住核心。