6步PWM输出是对 F103 的 TIM1 进行配置成PWM输出模式,带刹车和死区功能。按照模块化进行初始化配置。勾选keil中的C99标准(支持任意地方定义变量)。
GPIO初始化
打开相应功能模块时钟,将TIM1 的TIx引脚配置为复用推挽输出模式,BKIN(刹车)引脚配置为浮空输入模式。
通过在头文件进行宏定义配置,在硬件改变的时候方便修改
#define BLDC_TIMx TIM1#define BLDC_TIM_APBxClock_FUN RCC_APB2PeriphClockCmd #define BLDC_TIM_CLK RCC_APB2Periph_TIM1 #define BLDC_TIM_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd #define BLDC_TIM_GPIO_CLK (RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB) #define BLDC_TIM_CH1_PORT GPIOA #define BLDC_TIM_CH1_PIN GPIO_Pin_8 //通道1 #define BLDC_TIM_CH2_PORT GPIOA #define BLDC_TIM_CH2_PIN GPIO_Pin_9 //通道2 #define BLDC_TIM_CH3_PORT GPIOA #define BLDC_TIM_CH3_PIN GPIO_Pin_10 //通道3 #define BLDC_TIM_CH1N_PORT GPIOB #define BLDC_TIM_CH1N_PIN GPIO_Pin_13 //互补通道1 #define BLDC_TIM_CH2N_PORT GPIOB #define BLDC_TIM_CH2N_PIN GPIO_Pin_14 //互补通道2 #define BLDC_TIM_CH3N_PORT GPIOB #define BLDC_TIM_CH3N_PIN GPIO_Pin_15 //互补通道3 #define BLDC_TIM_BKIN_PORT GPIOB #define BLDC_TIM_BKIN_PIN GPIO_Pin_12 //刹车输入
复制代码static void BLDC_TIMx_GPIO_Config(void) { //GPIO初始化结构体 GPIO_InitTypeDef GPIO_InitStruct; //打开GPIOA和GPIOB的时钟和复用功能时钟 BLDC_TIM_GPIO_APBxClock_FUN(BLDC_TIM_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE); //通道1引脚配置 GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Pin=BLDC_TIM_CH1_PIN; GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(BLDC_TIM_CH1_PORT,&GPIO_InitStruct); //通道2引脚配置 GPIO_InitStruct.GPIO_Pin=BLDC_TIM_CH2_PIN; GPIO_Init(BLDC_TIM_CH2_PORT,&GPIO_InitStruct); //通道3引脚配置 GPIO_InitStruct.GPIO_Pin=BLDC_TIM_CH3_PIN; GPIO_Init(BLDC_TIM_CH3_PORT,&GPIO_InitStruct); //互补通道1引脚配置 GPIO_InitStruct.GPIO_Pin=BLDC_TIM_CH1N_PIN; GPIO_Init(BLDC_TIM_CH1N_PORT,&GPIO_InitStruct); //互补通道2引脚配置 GPIO_InitStruct.GPIO_Pin=BLDC_TIM_CH2N_PIN; GPIO_Init(BLDC_TIM_CH2N_PORT ,&GPIO_InitStruct); //互补通道3引脚配置 GPIO_InitStruct.GPIO_Pin=BLDC_TIM_CH3N_PIN;; GPIO_Init(BLDC_TIM_CH3N_PORT,&GPIO_InitStruct); //BKIN pin 引脚配置 GPIO_InitStruct.GPIO_Pin=BLDC_TIM_BKIN_PIN; GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_Init(BLDC_TIM_BKIN_PORT ,&GPIO_InitStruct); }
复制代码时基初始化

时基配置
APB2时钟为72Mhz,预分频系数PSC为0,(PSC决定记一次的时间),方便计算
将PWM频率为 f=20khz,故定时器计数周期ARR=72M/(PSC+1)/ f,因为我们预分频系数为0,故 ARR=72M/(0+1)/20k=3600。
通过在h文件宏定义配置这些参数:
//PWM频率#define BLDC_TIM_PWM_FREQ 20000 // 定时器预分频系数 #define BLDC_TIM_PRESCALER 0 //定时器计数周期 #define BLDC_TIM_PERIOD (uint16_t)(SystemCoreClock/(BLDC_TIM_PRESCALER+1)/BLDC_TIM_PWM_FREQ) //定时器重复寄存器数值 #define BLDC_TIM_REPETITIONCOUNTER 0
复制代码这里需要说明的是 TIM_TimeBaseInitStruct.TIM_ClockDivision ,时钟分割系数,她实际上配置的是定时器控制寄存器1的 CKD [1:0]。

她的作用主要在两个方面:
1.死区时间配置,死区时间发生器需要一个死区时钟来计数,她通过内部时钟 CK_INT 分频得来,后面详细讲计算。这里我将CKD配置为00,即不分频,还是72Mhz。
2.当使用外部时钟模式(ETR/TIx)/输入捕获功能时(TIx),如果频率太高需要降频,或者滤波时,需要一个时钟对这些信号进行采样,采样时钟 \(F_{DTS}=CK_{INT}/CKD/N\),N是数字滤波器滤波长度
3.当CKD[1:0]=00,不对CK_INT分频,当CKD[1:0]=01,对CK_INT进行2分频,当CKD[1:0]=10,对CK_INT进行4分频。
static void BLDC_TIMx_TimeBaseInit(void){ //打开TIM1时钟 BLDC_TIM_APBxClock_FUN(BLDC_TIM_CLK,ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //时基初始化结构体 /*时基初始化*/ TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分割为1 TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上计数 TIM_TimeBaseInitStruct.TIM_Period=BLDC_TIM_PERIOD; //计数周期 TIM_TimeBaseInitStruct.TIM_Prescaler=BLDC_TIM_PRESCALER; //预分频 TIM_TimeBaseInitStruct.TIM_RepetitionCounter=BLDC_TIM_REPETITIONCOUNTER; //重复计数器值为0,不重复 TIM_TimeBaseInit(BLDC_TIMx,&TIM_TimeBaseInitStruct); //定时器使能预装载功能 TIM_ARRPreloadConfig(BLDC_TIMx,ENABLE); //ARR预装载 }
复制代码输出比较模式初始化
输出比较模块的功能框图:

将定时器配置为输出比较模式——PWM1模式。将定时器配置为向上计数模式,当计数器数值小于输出比较寄存器的值时,即TIMx_CNT<TIMx_CCR1,输出有效电平。有效电平的选择通过TIM_OCInitStruct.TIM_OCPolarity这个成员配置,我将其配置为高电平有效。

需要注意的是,TIM_OCInitStruct.TIM_OCIdleState和TIM_OCInitStruct.TIM_OCNIdleState这两个成员是配置引脚空闲状态的,就是关闭定时器时的输出电平,所以在刹车功能有效时(关闭定时器输出),我们不能将同一桥的两个输入都配置为高电平,否则就烧mos了。这里我将两个空闲状态都配置为低电平。
占空比=CCR/ARR
static void BLDC_TIMx_OCInit(void){ TIM_OCInitTypeDef TIM_OCInitStruct; //输出比较初始化结构体 //输出比较通道1模式配置 TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1; //TIMx_CNT<TIMx_CCR1,输出有效电平 TIM_OCInitStruct.TIM_OCIdleState=TIM_OCIdleState_Reset; //关闭定时器时空闲状态为高电平 TIM_OCInitStruct.TIM_OCNIdleState=TIM_OCIdleState_Reset; TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High; //输出有效电平为高电平 TIM_OCInitStruct.TIM_OCNPolarity=TIM_OCNPolarity_High; TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable; //输出使能 TIM_OCInitStruct.TIM_OutputNState=TIM_OutputNState_Enable; //互补通道输出使能 TIM_OCInitStruct.TIM_Pulse=0; //设置占空比,即CCR值,这里不使用,后面用库函数配置 TIM_OC1Init(BLDC_TIMx ,&TIM_OCInitStruct); //输出比较通道2模式配置 TIM_OCInitStruct.TIM_Pulse=0; TIM_OC2Init(BLDC_TIMx, &TIM_OCInitStruct); //输出比较通道3模式配置 TIM_OCInitStruct.TIM_Pulse=0; TIM_OC3Init(BLDC_TIMx, &TIM_OCInitStruct); //输出比较使能预装载功能 TIM_OC1PreloadConfig(BLDC_TIMx,TIM_OCPreload_Enable); //CCR1预装载 TIM_OC2PreloadConfig(BLDC_TIMx,TIM_OCPreload_Enable); //CCR2预装载 TIM_OC3PreloadConfig(BLDC_TIMx,TIM_OCPreload_Enable); //CCR3预装载 }
复制代码死区和刹车功能初始化
static void BLDC_TIMx_BDTRInit(void){ TIM_BDTRInitTypeDef TIM_BDTRInitStructure; //刹车与死区初始化结构体 //刹车功能初始化,配置断路时通道输出状态,以及死区时间 /* Automatic Output enable, Break, dead time and lock configuration*/ TIM_BDTRInitStructure.TIM_OSSRState = TIM_OSSRState_Enable;//运行模式下“关闭状态”选择 TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable;//空闲模式下“关闭状态”选择 TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1; //锁定设置,防止软件出错,提供写保护 TIM_BDTRInitStructure.TIM_DeadTime = 10; //死区时间 TIM_BDTRInitStructure.TIM_Break = TIM_Break_Enable; //使能刹车功能 TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High; //刹车输入极性,高电平有效 TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Disable; TIM_BDTRConfig(BLDC_TIMx, &TIM_BDTRInitStructure); }
复制代码死区时间的计算
关于死区时间的计算,她是在刹车和死区寄存器(TIMx_BDTR)中的UTG[7:0]: 死区发生器设置 (Dead-time generator setup)中进行配置。

举例说明,假设将成员配置为TIM_BDTRInitStructure.TIM_DeadTime = 10;这个成员实际上配置的就是UTG[7:0]的值。
- 十进制10的二进制表示为00001010,可得DTG[7:5]=000,故死区持续时间DT=DTG[7:0]*Tdtg=10*Tdtg=10*Tdts
- Tdts由控制寄存器CR1中的CKD决定,前面我们已经分析过,我们将CKD[1:0]=00(即不分频),故Tdts=1/72M
- 综上,我们可以算出,死区持续时间DT=10/72M≈138.9ns
static void BLDC_TIMx_PWM_Init(void){ //GPIO初始化 BLDC_TIMx_GPIO_Config(); //时基初始化 BLDC_TIMx_TimeBaseInit(); //输出比较模式初始化 BLDC_TIMx_OCInit(); //死区和刹车功能初始化 BLDC_TIMx_BDTRInit(); //定时器使能 TIM_Cmd(BLDC_TIMx, ENABLE); //PWM输出使能,配置的是BDTR寄存器的MOE位,高级定时器独有。 TIM_CtrlPWMOutputs(BLDC_TIMx, ENABLE); //关闭定时器输出比较 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable); }
复制代码这样就初始化完毕了。
换相函数
换相函数是要根据霍尔换相时序表编写,按照顺序对给定MOS管PWM信号,这里采用的是H-PWM-L-ON驱动方式,所以上桥CCR按照占空比给定,下桥CCR给定ARR值,让她一直高电平
在头文件中宏定义占空比
//PWM占空比#define speed_duty 15 //占空比为15/100,注意这里没有除以100,只是定义数值
复制代码void BLDC_PHASE_CHANGE(uint8_t uwstep){ switch(uwstep) { case 6: //B+ C- //输出比较通道1配置 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable); //输出比较通道2配置 TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Enable); //输出比较通道3配置 TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Enable); break; case 2: //B+ A- //输出比较通道3配置 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable); //输出比较通道1配置 TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Enable); //输出比较通道2配置 TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Enable); break; case 3: //C+ A- //输出比较通道2配置 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable); //输出比较通道1配置 TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Enable); //输出比较通道3配置 TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Enable); break; case 1: //C+ B- //输出比较通道1配置 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable); //输出比较通道2配置 TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Enable); //输出比较通道3配置 TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Enable); break; case 5: //A+ B- //输出比较通道3配置 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable); //输出比较通道1配置 TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Enable); //输出比较通道2配置 TIM_SetCompare2(BLDC_TIMx,BLDC_TIM_PERIOD); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Enable); break; case 4: //A+ C- //输出比较通道2配置 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable); //输出比较通道1配置 TIM_SetCompare1(BLDC_TIMx,BLDC_TIM_PERIOD*speed_duty/100); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Enable); //输出比较通道3配置 TIM_SetCompare3(BLDC_TIMx,BLDC_TIM_PERIOD); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Enable); break; default: //关闭输出 TIM_CCxCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_1,TIM_CCxN_Disable); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_2,TIM_CCxN_Disable); TIM_CCxCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCx_Disable); TIM_CCxNCmd(BLDC_TIMx,TIM_Channel_3,TIM_CCxN_Disable); break; } }
复制代码通过逻辑分析仪看MCU输出的六路PWM信号,判断输出逻辑正不正确。我用的是Saleae logic 16.


在下桥高电平期间,上桥是占空比为20%的矩形波。

其中发现下桥高电平期间会出现低电平问题,是因为逻辑分析仪采样问题,在设置为500KS/S时候,低电平时间正好是2us。
在在设置为1MS/S时候,低电平时间正好是1us.
而用示波器(只有两路)看的波形则没有这种问题,在此留个小坑,待深入了解一下逻辑分析仪为什么会出现这种问题。
转载自:BLDC开发笔记2.六步PWM输出 - 懒懒阳光下的午睡 - 博客园 (cnblogs.com)

在在设置为1MS/S时候,低电平时间正好是1us.

而用示波器(只有两路)看的波形则没有这种问题,在此留个小坑,待深入了解一下逻辑分析仪为什么会出现这种问题。

转载自:BLDC开发笔记2.六步PWM输出 - 懒懒阳光下的午睡 - 博客园 (cnblogs.com)