单片机学习小组
直播中

王银喜

7年用户 2377经验值
私信 关注

有哪几种方式去实现PWM脉宽调制呢

什么是死区呢?
有哪几种方式去实现PWM脉宽调制呢?

回帖(2)

甘晓茵

2022-1-25 10:39:51
一个概念:
每个桥的上半桥和下半桥是是绝对不能同时导通的,但高速的PWM驱动信号在到达功率元件的控制极时,往往会由于各种各样的原因产生延迟的效果,造成某个半桥元件在应该关断时没有关断,造成功率元件烧毁。死区就是在上半桥关断后,延迟一段时间再打开下半桥或在下半桥关断后,延迟一段时间再打开上半桥,从而避免功率元件烧毁。这段延迟时间就是死区。
PWM:脉宽调制
可以有三种方式实现PWM,1采用ETMIER实现,2采用专门的模块,3GPIO模拟
1.采用ETMIER实现

例:采用ETIMER控制直流电机
LK32T的每个定时器有四个通道,在每个通道的捕获比较寄存器(CCRx)中放一个值,计数器从0开始计数,该通道的PWM输出为0,当计数器与这个寄存器的数相同之后,此PWM输出1(电平发生反转)。当计数器值(CNT)值到达ARR的值时,PWM开始输出下一个PWM波。
//主函数
int main( void )
{


Device_Init();
TIM0_Init_PWM(1000,196);//TIM0 - CH2通道输出PWM
TIM6_T0_Init();//定时器T6-T0初始化
TIM6_T1_Init();




//外部中断配置
    //按键SB1--用于紧急停车
PA_INT_ENABLE(0);//开启PA0中断
PA_INT_EDGE(0);//配置为边沿中断
PA_INT_BE_DISABLE(0);//配置为单边沿触发
PA_INT_POL_LOW(0);//配置为下降沿触发
PA_INT_FLAG_CLR(0);//清除中断标志


//按键SB3--实际上是用于测转速,没有用于按键功能
PA_INT_ENABLE(10);//开启PA10中断
PA_INT_EDGE(10);//配置为边沿中断
PA_INT_BE_DISABLE(10);//配置为单边沿触发
PA_INT_POL_LOW(10);//配置为下降沿触发
PA_INT_FLAG_CLR(10);//清除中断标志


//按键SB4--切换液晶或是矩阵键盘功能
PA_INT_ENABLE(11);//开启PA11中断
PA_INT_EDGE(11);//配置为边沿中断
PA_INT_BE_DISABLE(11);//配置为单边沿触发
PA_INT_POL_LOW(11);//配置为下降沿触发
PA_INT_FLAG_CLR(11);//清除中断标志


PB -> OUTEN |= 0xf0fe;//PB输出使能,PB0作为PWM输出口PWM0A
PB -> OUT = 0xffff;


PA_OUT_ENABLE(12);//蜂鸣器端口输出使能,上电复位提示


SEG_GPIO_Init();//数码管显示端口初始化
delay_ms(500);
BUZZER_OFF;//上电提示结束
IRQ_Enable();//开总中断


//LCD12864 初始化============================
LCD_GPIO_Init();//LCD12864液晶 GPIO口初始化
PSB_0;
CS_1;
    lcd_init();//LCD12864初始化


    delay_ms(100);
lcd_clear();//清屏
delay_ms(100);


    lcd_wstr(1, 1, "恒温控制系统");//字符显示
    lcd_wstr(2, 0, "当前温度");//字符显示
lcd_wstr(3, 0, "当前状态");//字符显示
lcd_wstr(4, 2, "@LUNTEK");//字符显示


write_figer(2,5,0);
lcd_wstr(2,6,"℃");
lcd_wstr(3, 4, "NO  CMD");//字符显示
//LCD12864 初始化============================


while(1)
{
if(KeyTrg & 0x02)//SC32主板 独立按键S3
{
SB1_Pressed_Flg ^= 0x01;//按键翻转标志位
if(SB1_Pressed_Flg)
{
//蜂鸣器按键提示
PA_OUT_ENABLE(12);
PA_OUT_LOW(12);
delay_ms(100);
PA_OUT_HIGH(12);
PA_OUT_DISABLE(12);


TIMER0 -> TIM_ARR = 500;// 自动重装载寄存器的值
printf("按键2启动 MOTOR RUNNINGn");
GPIO_AF_SEL(DIGITAL, PA, 7, 3);// T0CH2
TIMER0 -> TIM_CR1_b.CEN = 1;


ST_delay_ms(5000);
if(!Motor_Estop_flg)
{
TIMER0 -> TIM_CR1_b.CEN = 0;
GPIO_AF_SEL(DIGITAL, PA, 7, 0);// T0CH2
PA_OUT_DISABLE(7);
Motor_RPM = Motor_R * (60 / 5)  / 4;//r/min
printf("5S时间到  MOTOR SPEED %3.1f RPMnn",Motor_RPM);
}
else
{
Motor_Estop_flg = 0;
printf("n警告:紧急停车!!!nn");
}
Motor_R = 0;//电机旋转圈数清零
Motor_RPM = 0;
}
else
{
Motor_R = 0;
printf("按键2关闭 MOTOR STOPn");
TIMER0 -> TIM_CR1_b.CEN = 0;
GPIO_AF_SEL(DIGITAL, PA, 7, 0);// T0CH2
PA_OUT_DISABLE(7);
}
}
PA_INT_ENABLE(10);//开启PA10中断
TIM6 -> CTC0_b.COUNT0INT_EN = 1;
//矩阵键盘应用函数===========================
if(SB4_Pressed_Flg)
{
i = Key_Value;//读取键盘扫描函数返回值
if(1 == i) //按键1触发
{
LED3_ON;
lcd_wstr(3, 4, "LED3 ON");//字符显示
}
else
{
LED3_OFF;
}


if(5 == i) //按键5触发
{
LED4_ON;
lcd_wstr(3, 4, "LED4 ON");//字符显示
}
else
{
LED4_OFF;
}


if(9 == i ) //按键9触发
{
LED5_ON;
lcd_wstr(3, 4, "LED5 ON");//字符显示
}
else
{
LED5_OFF;
}


if(13 == i ) //按键D触发
{
PA_OUT_ENABLE(12);
BUZZER_ON;
lcd_wstr(3, 4, "BUZ ON");//字符显示
}
else
{
BUZZER_OFF;
}
if(20 == i)
lcd_wstr(3, 4, "NO CMD ");//字符显示
}
//矩阵键盘应用函数============================


//工作状态指示/已经被tim0 PWM-CH3占用
// LED1_ON;
// ST_delay_ms(500);
// LED1_OFF;
// ST_delay_ms(500);


//DS18B20 液晶显示============================
//DS18B20_Temp = DS18B20_Get_Temp();//读取 DS18B20 温度采集数据 已在中断中实现
write_figer(2,4,DS18B20_Temp);


// printf( "当前温度:%F℃rn", DS18B20_Temp );
// printf( "----------TestFinished----------rn" );  
//DS18B20 液晶显示============================






//DS18B20 数码管显示==========================
// SEG_Display[0] = (uint32_t)DS18B20_Temp % 10;
//    SEG_Display[1] = (uint32_t)DS18B20_Temp % 100 / 10;
//    SEG_Display[2] = (uint32_t)DS18B20_Temp % 1000 / 100;
//    SEG_Display[3] = (uint32_t)DS18B20_Temp % 10000 / 1000;
//
//
// for( i = 0; i < 4; ++i)
// {
// Dispaly_Number(SEG_Display, i+1);
//
// ST_delay_ms(1);
// PB -> OUT |= 0xe0;
// Dispaly_Number(10, 2);
// ST_delay_ms(1);
// PB -> OUT |= 0xe0;
//
// }
//DS18B20 数码管显示==========================
//暂做保留====================================
// UartSendByte(65);
// UartSendString("hello");
// UartSentUint32ToASCII(64);
// ReadUartBuf();
// SendResponse();
// UartSendStart();
//暂做保留====================================
}


}
这里写了很多代码,实际上跟PWM有关的关键代码只有寥寥数行,为便于理解在此详细解释
第6行:



TIM0_Init_PWM(1000,196);//TIM0 - CH2通道输出PWM
     
从这行中我们可以看到这是定时器初始化,其主要设定了预分频1000,死区196,详细的下面再分析。系统的时钟频率为72Mhz,计数脉冲为1000,即计时脉冲为72Khz
第77-80行:

     TIMER0 -> TIM_ARR = 500;// 自动重装载寄存器的值
printf("按键2启动 MOTOR RUNNINGn");
GPIO_AF_SEL(DIGITAL, PA, 7, 3);// T0CH2
TIMER0 -> TIM_CR1_b.CEN = 1;
   
77(1)行:将TIM_ARR设定为500,在6行调用Tim0_Init_PWM(1000,196)中已经设置CCR2位250,所以我们可以计算出PWM的占空比为CCR2/ARR=50%,PWM的频率为72K/500=144Hz(?)。
79(3)行:设置PA07位PWM的输出口(T0CH2)
80(4)行:定时器启动,实际上就是开始输出PWM波,即电机开始转动!
85-89行:

TIMER0 -> TIM_CR1_b.CEN = 0;
GPIO_AF_SEL(DIGITAL, PA, 7, 0);// T0CH2
PA_OUT_DISABLE(7);
Motor_RPM = Motor_R * (60 / 5)  / 4;//r/min
printf("5S时间到  MOTOR SPEED %3.1f RPMnn",Motor_RPM);


85(1)行:定时器停止计数--PWM停止--电机停止
86(2)行:改变PA7管脚功能为GPIO功能
87(3)行:PA7禁止输出
88行、89行:计算电机转速并显示
101~105行:
   

Motor_R = 0;
printf("按键2关闭 MOTOR STOPn");
TIMER0 -> TIM_CR1_b.CEN = 0;
GPIO_AF_SEL(DIGITAL, PA, 7, 0);// T0CH2
PA_OUT_DISABLE(7);
     与85~89行基本类似,电机停止转动并作出提示
PWM波:

void TIM0_Init_PWM(uint32_t PRD, uint32_t DB_CFG)
{




TIMER0 -> TIM_ARR = PRD;// 自动重装载寄存器的值
TIMER0 -> TIM_PSC = 7200;//预分频值
TIMER0 -> TIM_CR1_b.CMS = CMS_EDGE_ALIGN;// 边沿对其模式
TIMER0 -> TIM_CR1_b.DIR = 0;//计数器增计数




TIMER0 -> TIM_CCMR1 |= OC2M_PWM_MODE2;      // OC2M   CH2选择PWM模式2
TIMER0 -> TIM_CCMR1 |= OC2PE_PRELOAD_ENABLE;// OC2PE  开启 TIM_CCR1 寄存器的预装载功能
TIM_ARPE_ENABLE;    // 周期计数预装载允许位:TIM_ARR 寄存器有缓冲


     
TIMER0 -> TIM_CCER_b.CC2E = CC2E_ENABLE;   // 捕捉/比较1输出使能
TIMER0 -> TIM_CCER_b.CC2NE = CC2NE_ENABLE;  // 捕捉/比较 1 互补输出使能
TIMER0 -> TIM_CCER_b.CC2P = CC2P_OUTPUT_LOW;   // 捕捉/比较1输出极性:OC1 高电平有效;
TIMER0 -> TIM_CCER_b.CC2NP = CC2NP_OUTPUT_HIGH;  // 捕捉/比较1互补输出极性:OC1N 高电平有效;
TIMER0 -> TIM_CCR2 = PRD/4;      // 占空比设置




TIMER0 -> TIM_BDTR_b.MOE = MOE_ENABLE;// 主输出使能
TIMER0 -> TIM_BDTR_b.AOE = AOE_SW_SET;
TIMER0 -> TIM_BDTR_b.OSSR = 0;   // 运行模式下“关闭状态”选择
TIMER0 -> TIM_BDTR_b.DTG = DB_CFG;  // 196 死区设置 OC1上升沿后延时2us,具体设置参考寄存器说明
TIMER0 -> TIM_DTG1 = DB_CFG;  // 196 死区设置 OC1N下降沿后延时2us,具体设置参考寄存器说明




//EGR_CNT_UPDATE;  // TIMER0->TIM_EGR_b.UG = 1; 重新初始化计数器,并产生一个 ( 寄存器 ) 更新事件
//CNT_ENABLE;    // TIMER0->TIM_CR1_b.CEN=1;使能计数器




/*************************************************************************
刹车输入
************************************************************************/






// TIMER0 -> TIM_BDTR_b.BKE = BKE_BRAKE_ENABLE;    // 刹车使能
// TIMER0 -> TIM_BDTR_b.BKP = BKP_BRAKE_POL_LOW;  // 刹车极性为低电平有效
// TIMER0 -> TIM_BDTR_b.OSSI = 0;   // 运行模式下“空闲状态”选择
// TIMER0 -> TIM_CR2_b.OIS1 = OIS1_OC1_AFTER_DT_LOW;    // 输出空闲状态 1(OC1 输出 )
// TIMER0 -> TIM_CR2_b.OIS1N = OIS1N_OC1N_AFTER_DT_LOW;   // 输出空闲状态 1(OC1N 输出 )


}


PWM初始化的工作内容很多,暂时可以会用即可!
TIM6的两个定时器设置略,与定时器一章相似,用于按键及数码管的扫描。
举报

王春美

2022-1-25 10:39:55
三个按键中断:
void GPIO0_IRQHandler()
{
        if(!(PA -> PIN & (1 << 0)))//判断按键SB1是否按下
        {
                PA_INT_DISABLE(0);//关闭PA10中断
               
                Motor_Estop_flg = 1;//紧急停车标志位
                TIMER0 -> TIM_CR1_b.CEN = 0;
                GPIO_AF_SEL(DIGITAL, PA, 7, 0);// T0CH2
                PA_OUT_DISABLE(7);

                PA_INT_FLAG_CLR(0);//清除中断标志
                PA_INT_ENABLE(0);//开启PA10中断               
        }
       
        if(!(PA -> PIN & (1 << 10)))//判断按键SB3是否按下
        {
                PA_INT_DISABLE(10);//关闭PA10中断
                Motor_R++;

                PA_INT_FLAG_CLR(10);//清除中断标志
                PA_INT_ENABLE(10);//开启PA10中断               
        }
       
        if(!(PA -> PIN & (1 << 11)))//判断按键SB4是否按下
        {
                //蜂鸣器按键提示
                PA_OUT_ENABLE(12);
                PA_OUT_LOW(12);               
                delay_ms(200);       
                PA_OUT_HIGH(12);       
                PA_OUT_DISABLE(12);
       
                SB4_Pressed_Flg ^= 1;//切换液晶或是矩阵键盘功能
               
                if(SB4_Pressed_Flg)//模式选择位
                {
                        PA -> OUTEN &= ~0X70;//PA输出失能 数码管位选端
                        NVIC_DisableIRQ(TIM6_T1_IRQn);
                        NVIC_EnableIRQ(TIM6_T0_IRQn);//选择矩阵键盘功能                       
                }
                else
                {                       
                        PA -> OUTEN |= 0X70;//PA输出使能 数码管位选端
                        PB -> OUTEN |= 0XFF00;//PB输出使能        
                        PB -> OUT &= 0X00FF;//数据清零
                        PA -> OUT |= 0X70;//位选置位 关断                       
                        NVIC_DisableIRQ(TIM6_T0_IRQn);
                        NVIC_EnableIRQ(TIM6_T1_IRQn);//选择数码管显示功能
                }

                PA_INT_FLAG_CLR(11);//清除中断标志               
        }       
        NVIC_ClearPendingIRQ(PA_IRQn);//清除中断
}
上述的代码洋洋洒洒,但是实际关键的就是几步:
1.设定PWM的频率
2.设定PWM波的占空比
3.开始输出PWM波
4.停止输出PWM

TIMER0 -> TIM_ARR = 500;// 自动重装载寄存器的值
printf("按键2启动 MOTOR RUNNINGn");
GPIO_AF_SEL(DIGITAL, PA, 7, 3);// T0CH2
TIMER0 -> TIM_CR1_b.CEN = 1;//计数器开始计数,当然PWM就可以输出了


我们简单粗暴一点:
1.设定PWM的频率
Tim0_Init_PWM(1000,196)不要动了,另外设定ARR,可以设定PWM频率,72Mhz/1000/ARR=....

        TIMER0 -> TIM_ARR = 500;

.设定PWM波的占空比
设定CCRx可设定占空比,CCRx/(ARR+1)=......
TIMER0 -> TIM_CCR2 = PRD/4;             // 占空比设置

3.开始输出PWM波
启动定时器可以开始输出PWM波

GPIO_AF_SEL(DIGITAL, PA, 7, 3);// T0CH2
TIMER0 -> TIM_CR1_b.CEN = 1;
4.停止输出PWM

TIMER0 -> TIM_CR1_b.CEN = 0;//计数器不可以计数了,当然就停止输出PWM波了
GPIO_AF_SEL(DIGITAL, PA, 7, 0);// PA7管脚不再是T0CH2的输出了
PA_OUT_DISABLE(7);
2采用专门的模块

LK32T102采用专用的 16 位时间基准计数器,有三个通道PWM波输出PWM0,PWM1和PWM2,每个通道为一个模块,每个通道又分为A,B两路。
  每个  PWM  模块由  6 个子模块组成,不同的模块间结构完全相同,既可以独立运行,又可以通过时钟同步信号,组成 一个系统运行。

1. 时基子模块:
它是一个专用的 16 位计数器,以 PWM 时钟(TBCLK)频 率运行,该时钟是由系统时钟(SYSCLKOUT)(通过 TBCTL[CLKDIV])分频得到的。因此,通过设置时基计数器 的计数周期[TBPRD]即可确定 PWM 的周期和频率。
2. 计数比较子模块:
计数器比较子模块中有两个比较值寄存器 CMPA 和 CMPB,通过持续的与时间基准比较器的值进行比较来产 生比较事件。例如:TBCTR=CMPA,TBCTR=CMPB
   3. 动作限定子模块:
  动作限定子模块的主要功能是决定响应何种比较事件,做出何种动作,从而得到所需的 PWM 波形。
  4. 死区生成子模块
  为防止 PWM 某一通道的上下桥臂同时导通导致短路,需要在一个开关管彻底关断和同通道另一个开关管导通 之间加入“死区”。死区生成子模块的作用就在于此。
     5. 故障保护子模块
   故障保护子模块通过对故障信号的检测,判断是否发生了故障,从而控制 PWM 输出做出相应的动作。
   6. 事件触发子模块
   事件触发子模块通过管理时间基准子模块(TB)和计数比较子模块(CC)所产生的事件,产生一个中断到
   CPU 或者一个 SOC 信号到 ADC。
   实例:
   产生一个频率为10KHz,负占空比为 10%的PWM波
   1)管脚功能选择
  

void GPIO_Init(){
       
                  GPIO_AF_SEL(DIGITAL,PA,8,2);             // PWM1A
//                GPIO_AF_SEL(DIGITAL,PA,9,2);                         // PWM2B
//                GPIO_AF_SEL(DIGITAL,PA,10,2);                     // PWM2A
//            GPIO_AF_SEL(DIGITAL,PB,13,2);                         // PWM0B
//                GPIO_AF_SEL(DIGITAL,PB,14,2);                         // PWM0A
//                GPIO_AF_SEL(DIGITAL,PB,15,2);                         // PWM1B
//       
....
}


管脚PA8的数字功能2(PWM1A)功能,具体设置哪一个管脚及哪一个通道,哪一路可查LK32T102编程手册。
2)设置PWM的频率
v

void PWM_Init(void){
        PWM_CFG(3600, 0);    // 72MHz下,10kHzPWM,0us死区
        //PWM0 -> CMPA = 500;   //PB14
        PWM1 -> CMPA = 360;  //PA8
        //PWM2 -> CMPA = 1500;  //PA10
        PWM_START;
}
上述中1行调用了PWM_CFG(3600,0)对PWM进行配置,而4行设置了占空比。
因为采用增减计数的方式,计数脉冲也没有分频,所以,或者说PWM波的周期为0.1ms。
负占空比=360/3600=10%(?)或低电平时间为360*2/72=10us(?)
3)void PWM_CFG(uint32_t Period,uint32_t DB)关键代码分析

/************************************************************************
                                初始化PWM0-2,设置参数为:PWM半周期计数值、死区计数值
************************************************************************/
void PWM_CFG(uint32_t Period,uint32_t DB)
{       
       
......
/******************************************************************************
                                                        PWM1
*******************************************************************************/       
        PWM1->TBPRD = Period; // 设置PWM1的半周期
        PWM1->TBPHS = 0; // Set Phase register to zero

        PWM1->TBCTL = 0;
        PWM1_CTRMODE_UPDOWN;                         // Symmetrical mode CTRMODE
        PWM1_PHASE_ENABLE;                         // Slave module PHSEN
        PWM1_PRD_SHADOWON;                                 // TB_SHADOW
        PWM1->TBCTL_b.SYNCOSEL = SYNCOSEL_SYNC; // sync flow-through  SYNCOSEL
       
        PWM1->CMPCTL_b.SHDWAMODE = SHADOWMODE_ON;        // CC_SHADOW
        PWM1->CMPCTL_b.SHDWBMODE = SHADOWMODE_ON;        // CC_SHADOW
        PWM1->CMPCTL_b.LOADAMODE = CMP_LOAD_ZERO; // load on CTR=Zero
        PWM1->CMPCTL_b.LOADBMODE = CMP_LOAD_ZERO; // load on CTR=Zero
       
        PWM1->AQCTLA_b.CAU = AQ_CAU_CLR;                                          // 当计数器等于主CMPA寄存器并且计数递增时动作:清除:使PWMxA输出低
        PWM1->AQCTLA_b.CAD = AQ_CAD_SET;                                                 // 当计数器等于主CMPA寄存器并且计数递减时动作:置位:使PWMxA输出高
       
        PWM1->AQCTLB_b.CAU = AQ_CAU_SET;                                          // 当计数器等于主CMPB寄存器并且计数递增时动作:置位:使PWMxB输出高
        PWM1->AQCTLB_b.CAD = AQ_CAD_CLR;                                                 // 当计数器等于主CMPB寄存器并且计数递减时动作:清除:使PWMxB输出低
       
        PWM1->DBCTL_b.IN_MODE = DB_INMODE_PWMB_BOTH_EDGE;   // PWMxBIN作为上升沿和下降沿延迟的输入源
        PWM1->DBCTL_b.OUT_MODE = DB_OUTMODE_BOTH_ENABLE;           // 死区对于PWMXA输入的上升沿延迟和PWMxB输出的下降沿延迟完全使能。
        PWM1->DBCTL_b.POLSEL = DB_POLSEL_AHC;                                                   // 高有效互补(AHC)模式。PWMxB反相
       
        PWM1->DBFED = DB; // 下降沿死区
        PWM1->DBRED = DB; // 上升沿死区
....
       
}
需要注意的是这段代码 中还有很多的配置,如PWM触发中断等,如果不需要的话最好将其注释掉,因为频繁的中断发生有可能使得其他部分的代码没有机会运行。
举报

更多回帖

×
20
完善资料,
赚取积分