【应用笔记】TAE32G5800 PWM 刷新方案
在使用 HRPWM 模块的时候,用户对该模块的应用场景是 PWM 的 Period 和 CMP 值会不停地刷新。本篇方案将详细介绍如何通过控制 HRPWM 主定时器中断来满足 PWM 不断刷新的需求。
PWM 刷新的方式有很多种,使用 HRPWM 主定时中断的方式实现 PWM 是为了规避了 HRPWM 模块中常见的问题:“背靠背”,背靠背的意思是指 PWM 在更新 Period 和 Cmp 寄存器的时候,定时器的更新事件在 Period 值和 Cmp 值还没完全把值更新到影子寄存器的时候到来,导致出了 PWM 波形的未完全更新的情况,这种情况会导致当前的 PWM 波形发生错乱。
为了避免出现上述情况,我们的 PWM 需要在一个周期内 UPD 事情来临之前就执行完刷新这个动作,在这个基础上我们可以选择在 PWM 计数在一个周期内某个固定的时间点做刷新即可。这个固定的时间点可以是周期初,可以是周期中,可以是周期末。
有一个点需要注意的是,如果用户选择了 PWM 刷新在周期末执行的话就需要在周期末留有足够的时间裕量。因为 TAE32G5800 在做 PWM 刷新的时候会有禁止更新使能和更新使能这两个动作,所以如果 PWM 在周期末刷新的时候没有保留足够的时间裕量的话就会出现:通过读取 PWM 的寄存器我们可以看见 PWM 的 Period 和 CMP 寄存器的值都有更新,但是 PWM 频率没有变化的情况。出现这种现象的原因是更新 Period 和 CMP 寄存器之前关闭了更新使能,也就是在 PWM 的 UPD 事件在更新使能这个动作之前就已经产生,所以该周期的 UPD 事件会因为还没有更新使能而失效。
由图可知,PWM 如果在周期末执行刷新的话需要给周期留有时间裕量,这个裕量和刷新函数的执行时间还有刷新点有关。只要能够保证解锁这个动作能够在 UPD 事件来临之前执行,那么 PWM 的频率都可以正常更新。
TAE32G5800 可以通过使能 HRPWM 主定时器中断,在主定时器中断里面执行刷新。主定时器中断一共有 9 个,我们只需要取其中一个中断事件,在中断里面做 PWM 刷新。
3.1 方案描述
本篇方案以 HRPWM 主定时器 CMPA 中断为例,讲述如何通过中断的方式实现 PWM 的刷新。
根据上述提供的方案,我们在 TAE32G5800 开发板上对该方案进行测试验证。如图 3.1 所示,选取 MST_CMPA 作为一个周期内的固定刷新点开启相关中断,在 ADC 转换完成中断内使能 MST_CMPA 中断,如果 MST_PWM 当前周期的计数值超过了 MST_CMPA,那么就会到下一个周期的计数值到达 MST_CMPA 的时候进入中断执行 PWM 刷新,在 PWM 刷新完成后关闭 MST_CMPA 中断。如果 MST_PWM 当前计数值没有到达 MST_CMPA,那么在这个周期内等待计数值到达 MST_CMPA 然后进入中断,执行 PWM 刷新。
本篇方案会将刷新中断方式的代码罗列出来提供参考,根据参考代码配置能够实现方案所提到的开中断的实现刷新,以下是代码配置。
首先我们要配置的是 HRPWM 模块相关的时钟、中断和功能引脚等相关基础的外设配置。
void LL_HRPWM_MspInit(HRPWM_TypeDef *Instance)
{
GPIO_InitTypeDef HRPWM_GPIO_Init;
//Assert param
assert_param(IS_HRPWM_ALL_INSTANCE(Instance));
memset((void *)&HRPWM_GPIO_Init, 0x00, sizeof(HRPWM_GPIO_Init));
//HRPWM GPIO Common Config
HRPWM_GPIO_Init.IntMode = GPIO_INT_MODE_CLOSE;
HRPWM_GPIO_Init.OType = GPIO_OTYPE_PP;
HRPWM_GPIO_Init.Pull = GPIO_NOPULL;
HRPWM_GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
HRPWM_GPIO_Init.Alternate = GPIO_AF14_HRPWM;
//HRPWM Slave 0 Output Pinmux Config: PA8->OUT0A PA9->OUT0B PA10->OUT1A PA11->OUT1B
HRPWM_GPIO_Init.Pin = GPIO_PIN_8;
LL_GPIO_Init(GPIOA, &HRPWM_GPIO_Init);
//HRPWM Slave 0 Output Pinmux Config: PA9->OUT0B
HRPWM_GPIO_Init.Pin = GPIO_PIN_9;
LL_GPIO_Init(GPIOA, &HRPWM_GPIO_Init);
//HRPWM Slave 0 Output Pinmux Config: PA10->OUT1A
HRPWM_GPIO_Init.Pin = GPIO_PIN_10;
LL_GPIO_Init(GPIOA, &HRPWM_GPIO_Init);
//HRPWM Slave 0 Output Pinmux Config: PA11->OUT1B
HRPWM_GPIO_Init.Pin = GPIO_PIN_11;
LL_GPIO_Init(GPIOA, &HRPWM_GPIO_Init);
//HRPWM Bus Clock Disable and Soft Reset Assert
LL_RCU_HRPWM_ClkEnRstRelease();
//NVIC HRPAM Interrupt Enable
LL_NVIC_EnableIRQ(HRPWM_MST_IRQn);
LL_NVIC_EnableIRQ(HRPWM_SLV0_IRQn);
LL_NVIC_EnableIRQ(HRPWM_SLV1_IRQn);
}
在配置 HRPWM 模块的主定时器和子定时器之前,我们首先要配置的是 HRPWM 模块的时钟使能,并使能该模块的相关中断,以及相关功能引脚的复用,这些操作我们都在 LL_HRPWM_MspInit 函数内完成。
在配置完外设时钟和引脚后,我们就需要先对 HRPWM 主定时器的功能进行配置,而首要配置的就是主定时器时基部分的内容。
LL_HRPWM_Init(HRPWM);
//User HRPWM Timer Base Config
user_hrpwm_tmr_base_cfg.rep_prd = 0;
user_hrpwm_tmr_base_cfg.cntr_prd = 7200 - 1; //100K
user_hrpwm_tmr_base_cfg.int_en_msk = HRPWM_MST_INT_NONE;
user_hrpwm_tmr_base_cfg.clk_prescl = HRPWM_CLK_PRESCL_MUL_4; //4倍频
user_hrpwm_tmr_base_cfg.work_mode = HRPWM_WORK_MODE_CONTINUE; //连续模式
user_hrpwm_tmr_base_cfg.sync_rst_en = false;
user_hrpwm_tmr_base_cfg.sync_start_en = false;
user_hrpwm_tmr_base_cfg.single_retrig_en = false;
user_hrpwm_tmr_base_cfg.resync_mode = HRPWM_SLV_RESYNC_NEXT_RST_ROLLOVER;
user_hrpwm_tmr_base_cfg.half_mode_en = false;
user_hrpwm_tmr_base_cfg.intlvd_mode = HRPWM_INTLVD_MODE_CLOSE;
user_hrpwm_tmr_base_cfg.push_pull_en = false;
user_hrpwm_tmr_base_cfg.trig_half_en = false;
user_hrpwm_tmr_base_cfg.cmpA_greatr_than_en = false;
user_hrpwm_tmr_base_cfg.cmpC_greatr_than_en = false;
user_hrpwm_tmr_base_cfg.upd_gate = HRPWM_SLV_UPD_GATE_BST_DMA_INDEPEND;
user_hrpwm_tmr_base_cfg.cmpB_auto_dly_mode = HRPWM_SLV_CMPB_AUTO_DLY_ALWAYS;
user_hrpwm_tmr_base_cfg.cmpD_auto_dly_mode = HRPWM_SLV_CMPD_AUTO_DLY_ALWAYS; //Master PWM Timer Base Config
LL_HRPWM_TmrBaseCfg(HRPWM, HRPWM_MST_PWM, &user_hrpwm_tmr_base_cfg);
在主定时器时基配置完成后需要对主定时器的 CMP 值进行配置。
代码清单 3.3 主定时器比较值配置
//User HRPWM Timer Compare Config
user_hrpwm_tmr_cmp_cfg.pre_load_en = true; //使能预加载
user_hrpwm_tmr_cmp_cfg.cmp_a_val = 64;
user_hrpwm_tmr_cmp_cfg.cmp_b_val = 400;
user_hrpwm_tmr_cmp_cfg.cmp_c_val = 300;
user_hrpwm_tmr_cmp_cfg.cmp_d_val = 320;
//Master PWM Timer Compare Config
LL_HRPWM_TmrCmpCfg(HRPWM, HRPWM_MST_PWM, &user_hrpwm_tmr_cmp_cfg);
使能主定时器复位更新,使能 MST_CMPA 中断,对主定时器进行一次软件更新。
HRPWM->Master.MCR0_b.MRSTU = 1;
__LL_HRPWM_Mst_CmpA_INT_En(HRPWM);
__LL_HRPWM_Comm_MstPWMSwUpdReg_Set(HRPWM);
LL_HRPWM_Comm_DLL_Start(HRPWM);
在配置完主定时器并使能 MST_CMPA 中断后,就可以配置这一步了,首先要先获取主定时器的中断服务函数接口,在获取了中断服务函数接口的基础上需要对相对应开启的中断进行一个判断,本篇方案用的是 CMPA 的计数值作为中断源,因此我们需要在中断服务函数内先判断 CMPA 中断是否使能,CMPA 标志位是否置位如果都符合的话就执行里面的内容,在 MST_CMPA 中断里需要执行的动作一共有三个,首先是清除中断标志位,然后是进行 PWM 刷新,在 PWM 刷新完成后就关闭 MST_CMPA 中断。
代码清单 3.5 主定时器中断服务函数
void HRPWM_MST_IRQHandler(void)
{
uint32_t int_en, int_pending;
//HRPWM Master All Interrupt Enalbe and Pending Get
int_en = __LL_HRPWM_Mst_AllIntEn_Get(HRPWM);
int_pending = __LL_HRPWM_Mst_AllIntPnd_Get(HRPWM);
//Compare A Interrupt Handler
if ((int_en & HRPWM_MST_MDIER_MCMPAIE_Msk) && (int_pending & HRPWM_MST_MISR_MCMPA_Msk)) {
//Clear Interrupt Pending
__LL_HRPWM_Mst_CmpAIntPnd_Clr(HRPWM);
hrpwm_update();
HRPWM_MST->MDIER_b.MCMPAIE = 0;
}
}
在配置完主定时器基本配置以及主定时器中断后,还需要继续配置子定时器,主定时器的作用是作为子定时器的同步信号源以及开启相关的中断。所以子定时器的时基配置和主定时器的时基配置在复位源和工作模式上面是有明显区别的这里要注意,主定时器的工作模式是连续模式,而子定时器的工作模式是单次可重触发模式。因为子定时器的复位源都是主定时器的 Period 值,所以在这里要注意的是要让主定时器、子定时器的 Period 值保持一致。
//User HRPWM Timer Base Config
user_hrpwm_tmr_base_cfg.rep_prd = 0;
user_hrpwm_tmr_base_cfg.cntr_prd = 7200 - 1; //100K
user_hrpwm_tmr_base_cfg.int_en_msk = HRPWM_SLV_INT_NONE;
user_hrpwm_tmr_base_cfg.clk_prescl = HRPWM_CLK_PRESCL_MUL_4; //4 倍频
user_hrpwm_tmr_base_cfg.work_mode = HRPWM_WORK_MODE_SINGLE; //单次模式
user_hrpwm_tmr_base_cfg.sync_rst_en = false;
user_hrpwm_tmr_base_cfg.sync_start_en = false;
user_hrpwm_tmr_base_cfg.cntr_rst_evt = HRPWM_SLV0_CNTR_RST_EVT_MST_PWM_PRD; //复位源:maseter period
user_hrpwm_tmr_base_cfg.single_retrig_en = true; //单次可重触发模式
user_hrpwm_tmr_base_cfg.resync_mode = HRPWM_SLV_RESYNC_NEXT_RST_ROLLOVER;
user_hrpwm_tmr_base_cfg.half_mode_en = false;
user_hrpwm_tmr_base_cfg.intlvd_mode = HRPWM_INTLVD_MODE_CLOSE;
user_hrpwm_tmr_base_cfg.push_pull_en = false;
user_hrpwm_tmr_base_cfg.trig_half_en = false;
user_hrpwm_tmr_base_cfg.cmpA_greatr_than_en = false;
user_hrpwm_tmr_base_cfg.cmpC_greatr_than_en = false;
user_hrpwm_tmr_base_cfg.upd_gate = HRPWM_SLV_UPD_GATE_BST_DMA_INDEPEND;
user_hrpwm_tmr_base_cfg.cmpB_auto_dly_mode = HRPWM_SLV_CMPB_AUTO_DLY_ALWAYS;
user_hrpwm_tmr_base_cfg.cmpD_auto_dly_mode = HRPWM_SLV_CMPD_AUTO_DLY_ALWAYS;
//Slave PWMx Timer Base Config
LL_HRPWM_TmrBaseCfg(HRPWM, HRPWM_SLV_PWM_0, &user_hrpwm_tmr_base_cfg);
user_hrpwm_tmr_base_cfg.cntr_rst_evt = HRPWM_SLV1_CNTR_RST_EVT_MST_PWM_PRD; //复位源:maseter period
LL_HRPWM_TmrBaseCfg(HRPWM, HRPWM_SLV_PWM_1, &user_hrpwm_tmr_base_cfg);
在配置完子定时器的时基之后,进行子定时器的 CMP 值的配置。
//User HRPWM Timer Compare Config
user_hrpwm_tmr_cmp_cfg.pre_load_en = true; //使能预加载
user_hrpwm_tmr_cmp_cfg.cmp_a_val = 500;
user_hrpwm_tmr_cmp_cfg.cmp_b_val = 400;
user_hrpwm_tmr_cmp_cfg.cmp_c_val = 300;
user_hrpwm_tmr_cmp_cfg.cmp_d_val = 320;
//Slave PWMx Timer Compare Config
LL_HRPWM_TmrCmpCfg(HRPWM, HRPWM_SLV_PWM_0, &user_hrpwm_tmr_cmp_cfg);
LL_HRPWM_TmrCmpCfg(HRPWM, HRPWM_SLV_PWM_1, &user_hrpwm_tmr_cmp_cfg);
在配置完子定时器的比较值之后,就要配置子定时器相关的输出配置,输出配置这里可以选择电平的极性,以及 PWM 输出波形的 Set 事件和 Clr 事件。
//User HRPWM Slave PWMx Output Config
user_hrpwm_slv_output_cfg.Aout_set_evt_msk = HRPWM_SLV_OUT_CTRL_EVT_PWMx_CMPA ;
user_hrpwm_slv_output_cfg.Aout_clr_evt_msk = HRPWM_SLV_OUT_CTRL_EVT_PWMx_CMPB ;
user_hrpwm_slv_output_cfg.Aout_pol = HRPWM_SLV_OUT_POL_ACT_HITH;
user_hrpwm_slv_output_cfg.Aout_idle_lvl = HRPWM_SLV_OUT_IDLE_LVL_INVLD;
user_hrpwm_slv_output_cfg.Aout_flt_lvl = HRPWM_SLV_OUT_FAULT_LVL_NO_ACTION;
user_hrpwm_slv_output_cfg.Bout_set_evt_msk = HRPWM_SLV_OUT_CTRL_EVT_PWMx_CMPC;
user_hrpwm_slv_output_cfg.Bout_clr_evt_msk = HRPWM_SLV_OUT_CTRL_EVT_PWMx_CMPD ;
user_hrpwm_slv_output_cfg.Bout_pol = HRPWM_SLV_OUT_POL_ACT_HITH;
user_hrpwm_slv_output_cfg.Bout_idle_lvl = HRPWM_SLV_OUT_IDLE_LVL_INVLD;
user_hrpwm_slv_output_cfg.Bout_flt_lvl = HRPWM_SLV_OUT_FAULT_LVL_NO_ACTION;
user_hrpwm_slv_output_cfg.swap_en = false;
user_hrpwm_slv_output_cfg.dly_prot_en = false;
user_hrpwm_slv_output_cfg.bal_idle_auto_rcvr_en = false;
user_hrpwm_slv_output_cfg.dly_prot_mode = HRPWM_SLV_DLY_PROT_MECH_OUTA_EVTy_IDLE;
LL_HRPWM_Slv_OutputCfg(HRPWM, HRPWM_SLV_PWM_0, &user_hrpwm_slv_output_cfg);
LL_HRPWM_Slv_OutputCfg(HRPWM, HRPWM_SLV_PWM_1, &user_hrpwm_slv_output_cfg);
使能子定时器复位更新,对子定时器进行一次软件更新。
HRPWM->Master.MCR0_b.MRSTU = 1;
HRPWM->PWM[0].REG.PWMCR0_b.UPDRST |= 1;
HRPWM->PWM[1].REG.PWMCR0_b.UPDRST |= 1;
//Preload Enable, software need to generate a update event to update the shadow register to the working register
//If Preload Disable, don't need this action.
LL_HRPWM_Comm_DLL_Start(HRPWM);
__LL_HRPWM_Comm_MstPWMSwUpdReg_Set(HRPWM);
__LL_HRPWM_Comm_PWMxSwUpdReg_Set(HRPWM, HRPWM_SLV_PWM_0);
__LL_HRPWM_Comm_PWMxSwUpdReg_Set(HRPWM, HRPWM_SLV_PWM_1);
将上述主定时器和子定时器功能配置完成之后就可以使能主定时器和子定时器,然后让子定时器输出波形。
void hrpwm_config_app(void)
{
HRPWM->Master.MCR1 |= (HRPWM_MST_MCR1_MCEN_Msk | HRPWM_MST_MCR1_CEN0_Msk |HRPWM_MST_MCR1_CEN1_Msk | HRPWM_MST_MCR1_CEN2_Msk);
HRPWM->Common.OENR |= (HRPWM_COM_OENR_OEN0A_Msk | HRPWM_COM_OENR_OEN0B_Msk | HRPWM_COM_OENR_OEN1A_Msk | HRPWM_COM_OENR_OEN1B_Msk);
}
本篇方案是以 ADC0 中断为例,讲述了如何控制主定时器中断的开关,以及在开关中断前的注意事项。
代码清单 3.11 ADC 中断服务函数
void ADC0_NORM_IRQHandler(void)
{
uint32_t int_en, int_pending;
//Assert param
assert_param(IS_ADC_ALL_INSTANCE(ADC0));
//ADC Normal All Interrupt Enalbe and Pending Get
int_en = __LL_ADC_NormAllIntEn_Get(ADC0);
int_pending = __LL_ADC_NormAllIntPending_Get(ADC0);
//Regular Sequence End Interrupt Handler
if ((int_en & ADC0_IER_EOSIE_Msk) && (int_pending & ADC0_ISR_EOS_Msk))
{
//Clear Interrupt Pending
__LL_ADC_REG_SeqEndIntPnd_Clr(ADC0);
HRPWM_MST->MISR_b.MCMPA = 1;
HRPWM_MST->MDIER_b.MCMPAIE = 1;
}
}
在使能 MST_CMPA 中断前要先将 MST_CMPA 中断标志位清除,如果不先清除中断标志位的话,在使能了 MST_CMPA 中断后,就会出现不需要等待计数值到达 MST_CMPA 就会马上进入到 MST_CMPA 的情况。
如图 3.13 所示,通道 2 是 PWM,通道 0 是进行刷新中断的执行时间,通道 1 是 ADC0 中断执行时间,可以看到 PWM 刷新每次都会在一个周期内的固定点进行刷新,上图中出现刷新事件丢失的情况是因为会存在 PWM 的计数值已经到了 MST_CMPA 但是 ADC0 还没有执行到使能 MST_CMPA 中断的那一步,所以导致这个周期的刷新中断会被错过。因为波形符合预期,所以该刷新方案可以有效规避 PWM 的 UPD 事件在更新使能之前来临。