关于PWM产生原理,珠玉在前,这里就不再赘述,本文主要是整理和补充死区计算示例,源码链接在文末
使用:STM32F103ZET6
参考:
PWM原理 PWM频率与占空比详解
STM32定时器—-通用定时器输出带死区互补PWM
死区知识:
什么是死区时间? PWM是脉冲宽度调制,在电力电子中,最常用的就是整流和逆变。这就需要用到整流桥和逆变桥。
对三相电来说,就需要三个桥臂。以两电平为例,每个桥臂上有两个电力电子器件,比如IGBT
这两个IGBT不能同时导通,否则就会出现短路的情况,从而对系统造成损害。
这样就不会同时导通,从而避免功率元件烧毁;死区时间控制在通常的单片机所配备的PWM中都有这样的功能,下面会进一步介绍。
相对于PWM来说,死区时间是在PWM输出的这个时间,上下管都不会有输出,当然会使波形输出中断,死区时间一般只占百分之几的周期。但是当PWM波本身占空比小时,空出的部分要比死区还大,所以死区会影响输出的纹波,但应该不是起到决定性作用的。另外如果死区设置过小,但是仍然出现上下管同时导通,因为导通时间较短,电流较小,不足以烧毁,此时会导致开关元器件发热严重,所以选择合适的死区时间尤为重要;
————————————————
来自:
STM32 TIM高级定时器死区时间的计算
一、CubeMX的基础配置
1、时钟树
配置系统时钟72MHz
2、TIM1
ARR->频率
CCRx+死区->占空比
在PWM输出模式下,除了CNT(计数器当前值)、ARR(自动重装载值)之外,还多了一个值CCRx(捕获/比较寄存器值)。
当CNT小于CCRx时,TIMx_CHx通道输出低电平;
当CNT等于或大于CCRx时,TIMx_CHx通道输出高电平。 PWM的一个周期定时器从0开始向上计数 当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平 t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平 当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数...循环此过程 至此一个PWM周期完成
TIMx_ARR寄存器确定PWM频率,
TIMx_CCRx寄存器确定占空比
————————————————
来自:
【STM32】HAL库 STM32CubeMX教程七—PWM输出(呼吸灯)
对照上图设置
关于使能问题查看下面这篇文章
参考:
什么是自动重装载和预装载寄存器?
二、死区计算讲解
1、基础计算说明
参考:
STM32F103高级定时器死区时间的计算
PS.文章里的表有个小错,情况1的步长值应为0
例:设置频率200KHz,占空比为10%的一对互补PWM。
为了保证两路信号对称,需要将占空比设置为50%,死区设为10%,最终的占空比即为40%
1、设置PWM波周期为200kHz,需要设置预分频值PRESCLAER和预装载值ARR,TIM1在APB2上
注意:
定时器的时钟不是直接来自APB1或APB2,而是来自于输入为APB1或APB2的一个倍频器,当APB1的预分频系数为1时,这个倍频器不起作用,定时器的时钟频率等于APB1的频率;当
APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍。
例如AHP 72M,APB12分频36M,那么TIMER就是APB1的2倍频,即
72M
。
————————————————
来自:
理解通用定时器
- 时钟设置中APB2 为72MHz
- TIM1CLK = PCLK1
- 希望TIM1预分频后的计数时钟counter clock 为4MHz:
- 因此预分频值 PRESCLAER = 72 / 4 – 1 = 18 – 1
- TIM1输出时钟output clock为200KHz:
-
因此预装载值 PERIOD_TIM1(ARR) = (TIM1 counter clock / TIM1 output clock) – 1 = 4000/200 – 1=20 – 1
2、先设置PWM占空比为50%(保证互补波形相同),最终占空比还要扣除死区 -
因此待比较值 PULSE1_VALUE = 20 * 0.5
3、设置死区 - 希望最终波形的占空比为0.1,则死区长度为0.5-0.1=0.4,实际为0.4 / 200k=2us
-
根据PWM死区计算表,在系统时钟为72MHz情况下,2us为情况2,最终计算为0x88
2、代码
tim.h中添加
/* USER CODE BEGIN Private defines */
#define PRESCALER (uint16_t)(18 - 1)
#define PERIOD_TIM1 (uint16_t)(20 - 1)
#define PULSE1_VALUE (uint16_t)(20 * 0.5)
#define DEADTIME (uint8_t)0x88
/* USER CODE END Private defines */
死区自动计算代码(根据ST官方参考手册变化了一下,供参考,轻喷)
/* 死区计算函数 */
//根据死区计算表,返回死区设置值
//对于步长全为系统时钟的周期T_DTS来说,乘数范围
//情况1:0~127 || DT / T_TDS
//情况2:128~254 || DT / (2 * T_TDS) + 64
//情况3:256~504 || DT / (8 * T_TDS) + 160
//情况4:512~1008 || DT / (16 * T_TDS) + 192
//传入参数: pwm波频率(pwmfre_KHz,KHz),系统时钟(myCLK_MHz,MHz),占空比(mydutyc,%)
//如deadtime(200,72,10),PWM波频率200KHz,系统时钟72MHz,占空比10%
uint8_t deadtime(uint8_t pwmfre_KHz,uint8_t myCLK_MHz, uint8_t mydutyc)
{
float T_TDS;
T_TDS = (float)1000 / myCLK_MHz;
printf("T_TDS=%f ns, ", T_TDS);
float DT;//死区时间
DT = (50 - mydutyc) *10000 / pwmfre_KHz;
printf("DT=%f ns\r\n", DT);
uint8_t dt;
if ( DT >= 0 && DT <= T_TDS * 127)
{
dt = (uint8_t)(DT / T_TDS);
printf("situation 1: dt = %f ns, ",DT / T_TDS);
}
else if ( DT >= T_TDS * 128 && DT <= T_TDS * 254)
{
dt = (uint8_t)(DT / (2 * T_TDS) + 64 );
printf("situation 2: dt = %f ns, ",DT / T_TDS);
}
else if ( DT >= T_TDS * 256 && DT <= T_TDS * 504)
{
dt = (uint8_t)(DT / (8 * T_TDS) + 160 );
printf("situation 3: dt = %f ns, ",DT / T_TDS);
}
else
{
dt = (uint8_t)(DT / (16 * T_TDS) + 192 );
printf("situation 4: dt = %f ns, ",DT / T_TDS);
}
return dt;
}
结果:
main函数中添加:
/* USER CODE BEGIN 2 */
pwm_test();// pwm波发送测试函数
/* USER CODE END 2 */
对应测试函数
void pwm_test(void)// pwm波发送测试函数
{
if (HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1) != HAL_OK)//开启TIM1的CH1通道产生PWM
{
Error_Handler();
}
if (HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1) != HAL_OK)//开启TIM1的CH1N通道产生PWM
{
Error_Handler();
}
}
三、结果
无示波器可以使用keil的逻辑分析仪观察PWM结果,参考:
Keil 逻辑分析仪观测pwm波
PS.如果在添加观察信号步骤出现Unknown Signal警告,修改为以下配置
示波器:
占空比 500ns/5us=10%
四、开始产生PWM后如何改变频率
不太精确的方式:修改预分频值
void pwm_changefre()
{
uint16_t pre;//预分频值
//设为200KHz
pre= 18 - 1;
__HAL_TIM_SET_PRESCALER(&htim1, pre);//修改频率改预分频的值,如果改period的值占空比也会跟着变,
HAL_Delay(200);//或者修改period的值和pulse的值
//设为100KHz
pre= 36 - 1;
__HAL_TIM_SET_PRESCALER(&htim1, pre);
HAL_Delay(200);
}
精确一点的:
参考:
练习STM32动态更改PWM波频率和占空比
参考:
基于stm32的2FSK调制解调器设计
参考:
STM32 HAL库学习系列第4篇 定时器TIM—– 开始定时器与PWM输出配置
附:
源码链接