1. 前言
本篇博文将着眼于 i.MX RT1010 内部的 eFlexPWM,介绍其各个功能模块,以及 PWM 产生的原理。
2. 功能模块组成
以下是 RT1010 内部 PWM 的一个 Submoudle 的组成框图,从框图中我们可以看到:
- 自左向右依次有 Prescaler 对时钟进行预分频,分频之后的时钟作为 16 bit counter 计数器的时钟,驱动其计数,计数的初始值由 Counter preload mux 根据不同的同步方式进行选择重载;
- 接下来将由 3 组 6 个比较器对 0-5 Campare value 寄存器的数值与 16 bit counter 的计数值进行比较:
- 对由 Compare 0 value 寄存器和 Compare 1 value 寄存器为一组的数值与当前的计数值进行比较,输出的比较值连接到 Reload Logic 进行 Master Reload 控制;另外通过 Compare 0 value 寄存器与当前计数值的值进行比较,通过 Pin Mux 可将四个 GPIO 端口复用为 PWMX 进行 PWM 的输出。
- 对由 Compare 2 value 寄存器和 Compare 3 value 寄存器为一组的数值与当前的计数值进行比较,分别控制一个 SR 触发的 S 端和 R 端,当 S 为 1 时 PWM 输出高电平,R 为 1 时,PWM 输出低电平,这样通过 Compare 2 value 寄存器和 Compare 3 value 寄存器即可输出一路 PWM23;
- 类似的可以通过 Compare 4 value 寄存器和 Compare 5 value 寄存器输出另一路 PWM45;
- PWM23 和 PWM45 经过 Comp. vs Indep. 模块的处理用于控制输出独立的或互补的 PWM 的波形,通过 Deed Time Cenerator 的处理用以插入死区时间,通过 Mux Select Logic 用以选择 GPIO 的引脚复用;最后一级的模块将对这两路 PWM 由 Fault protection 和 Output override control 进行出错保护。
图 1. PWM 子模块框图
通过 MCUXpresso Config Tools 可以看到芯片内部只有一个 PWM1,PWM1 下面有 4 个 Submoudles,每个 Submodle 可以控制 A 和 B 两个可互补的通道和一个 X 通道,可以针对有相应复用功能的引脚进行 PWM 功能复用。
参考 SDK 的 PWM 例程,在枚举 _pwm_init_source 中对 counter 的重载方式进行了定义,可选的方式有本地同步(LocalSync)、主模块重载同步(MasterReload)、主模块同步(MasterSync)、外部同步(ExtSync)。不同的同步方式将影响到模块 4 个 Submodle 的计数器同步方式。例如对于选用主模块同步 MasterSync 这种方式将导致四个模块的计数器重载与 Submoulde 0 的重载时机一样,而采用本地同步(LocalSync)则不会受到主模块同步的影响。
/*! @brief PWM counter initialization options */
typedef enum _pwm_init_source
{
kPWM_Initialize_LocalSync = 0U, /*!< Local sync causes initialization */
kPWM_Initialize_MasterReload, /*!< Master reload from submodule 0 causes initialization */
kPWM_Initialize_MasterSync, /*!< Master sync from submodule 0 causes initialization */
kPWM_Initialize_ExtSync /*!< EXT_SYNC causes initialization */
} pwm_init_source_t;
PWM 模式,在枚举 _pwm_mode 中定义,kPWM_SignedCenterAligned 和 kPWM_CenterAligned 是中央对其方式,kPWM_SignedEdgeAligned 和 kPWM_EdgeAligned 是边缘对齐方式,Signed 和 Unsigned 指的是 INIT 计数器的初始值是有符号数和无符号数。
由于 SDK 中没有 PWMX PWM 生成的代码例程,本篇博文将添加一种 kPWM_NonAligned 方式进行测试。
/*! @brief PWM operation mode */
typedef enum _pwm_mode
{
kPWM_SignedCenterAligned = 0U, /*!< Signed center-aligned */
kPWM_CenterAligned, /*!< Unsigned cente-aligned */
kPWM_SignedEdgeAligned, /*!< Signed edge-aligned */
kPWM_EdgeAligned, /*!< Unsigned edge-aligned */
kPWM_NonAligned //added by joey wang 2023.2.8
} pwm_mode_t;
中央对齐模式:
在此模式下,INIT 将向上计数,由 VAL2 通过比较器与计数值进行比较打开 PWMA 的上升边沿,VAL3 打开 PWMA 的下降边沿; 由 VAL4 打开 PWMB 的上升边沿,VAL5 打开 PWMB 的下降边沿,不难看出 Singed 模式下数值对称,有利于计算。
边沿对齐模式:
在边沿对齐模式下,INIT 的取值和 VAL2 和 VAL4 的取值一样,即 PWMA 和 PWMB 上升沿 PWM 打开的时机一样,即达到了边沿对齐的目的,只需要指定 VAL3 和 VAL5 的数值,即可改变 PWMA 或 PWMB 的占空比。
实验现象:
在 PWM_SetupPwm 中增加 kPWM_NonAligned 的 case 判断:
status_t PWM_SetupPwm(PWM_Type *base,
pwm_submodule_t subModule,
const pwm_signal_param_t *chnlParams,
uint8_t numOfChnls,
pwm_mode_t mode,
uint32_t pwmFreq_Hz,
uint32_t srcClock_Hz)
{
assert(chnlParams);
assert(pwmFreq_Hz);
assert(numOfChnls);
assert(srcClock_Hz);
uint32_t pwmClock;
uint16_t pulseCnt = 0, pwmHighPulse = 0;
uint16_t modulo = 0;
uint8_t i, polarityShift = 0, outputEnableShift = 0;
if (numOfChnls > 3U)
{
/* Each submodule has 3 signals; PWM A & PWM B & PWM X*/
return kStatus_Fail;
}
/* Divide the clock by the prescale value */
pwmClock = (srcClock_Hz / (1UL << ((base->SM[subModule].CTRL & PWM_CTRL_PRSC_MASK) >> PWM_CTRL_PRSC_SHIFT)));
pulseCnt = (uint16_t)(pwmClock / pwmFreq_Hz);
/* Setup each PWM channel */
for (i = 0; i < numOfChnls; i++)
{
/* Calculate pulse width */
pwmHighPulse = (pulseCnt * chnlParams->dutyCyclePercent) / 100U;
/* Setup the different match registers to generate the PWM signal */
switch (mode)
{
case kPWM_SignedCenterAligned:
/* Setup the PWM period for a signed center aligned signal */
if (i == 0U)
{
modulo = (pulseCnt >> 1U);
/* Indicates the start of the PWM period */
base->SM[subModule].INIT = PWM_GetComplementU16(modulo);
/* Indicates the center value */
base->SM[subModule].VAL0 = 0;
/* Indicates the end of the PWM period */
/* The change during the end to start of the PWM period requires a count time */
base->SM[subModule].VAL1 = modulo - 1U;
}
/* Setup the PWM dutycycle */
if (chnlParams->pwmChannel == kPWM_PwmA)
{
base->SM[subModule].VAL2 = PWM_GetComplementU16(pwmHighPulse / 2U);
base->SM[subModule].VAL3 = (pwmHighPulse / 2U);
}
else if(chnlParams->pwmChannel == kPWM_PwmB)
{
base->SM[subModule].VAL4 = PWM_GetComplementU16(pwmHighPulse / 2U);
base->SM[subModule].VAL5 = (pwmHighPulse / 2U);
}
break;
case kPWM_CenterAligned:
/* Setup the PWM period for an unsigned center aligned signal */
/* Indicates the start of the PWM period */
if (i == 0U)
{
base->SM[subModule].INIT = 0;
/* Indicates the center value */
base->SM[subModule].VAL0 = (pulseCnt / 2U);
/* Indicates the end of the PWM period */
/* The change during the end to start of the PWM period requires a count time */
base->SM[subModule].VAL1 = pulseCnt - 1U;
}
/* Setup the PWM dutycycle */
if (chnlParams->pwmChannel == kPWM_PwmA)
{
base->SM[subModule].VAL2 = ((pulseCnt - pwmHighPulse) / 2U);
base->SM[subModule].VAL3 = ((pulseCnt + pwmHighPulse) / 2U);
}
else
{
base->SM[subModule].VAL4 = ((pulseCnt - pwmHighPulse) / 2U);
base->SM[subModule].VAL5 = ((pulseCnt + pwmHighPulse) / 2U);
}
break;
case kPWM_SignedEdgeAligned:
/* Setup the PWM period for a signed edge aligned signal */
if (i == 0U)
{
modulo = (pulseCnt >> 1U);
/* Indicates the start of the PWM period */
base->SM[subModule].INIT = PWM_GetComplementU16(modulo);
/* Indicates the center value */
base->SM[subModule].VAL0 = 0;
/* Indicates the end of the PWM period */
/* The change during the end to start of the PWM period requires a count time */
base->SM[subModule].VAL1 = modulo - 1U;
}
/* Setup the PWM dutycycle */
if (chnlParams->pwmChannel == kPWM_PwmA)
{
base->SM[subModule].VAL2 = PWM_GetComplementU16(modulo);
base->SM[subModule].VAL3 = PWM_GetComplementU16(modulo) + pwmHighPulse;
}
else
{
base->SM[subModule].VAL4 = PWM_GetComplementU16(modulo);
base->SM[subModule].VAL5 = PWM_GetComplementU16(modulo) + pwmHighPulse;
}
break;
case kPWM_EdgeAligned:
/* Setup the PWM period for a unsigned edge aligned signal */
/* Indicates the start of the PWM period */
if (i == 0U)
{
base->SM[subModule].INIT = 0;
/* Indicates the center value */
base->SM[subModule].VAL0 = (pulseCnt / 2U);
/* Indicates the end of the PWM period */
/* The change during the end to start of the PWM period requires a count time */
base->SM[subModule].VAL1 = pulseCnt - 1U;
}
/* Setup the PWM dutycycle */
if (chnlParams->pwmChannel == kPWM_PwmA)
{
base->SM[subModule].VAL2 = 0;
base->SM[subModule].VAL3 = pwmHighPulse;
}
else
{
base->SM[subModule].VAL4 = 0;
base->SM[subModule].VAL5 = pwmHighPulse;
}
break;
/*added by joey wang at 2023.2.14*/
case kPWM_NonAligned:
/* Setup the PWM period for a signed center aligned signal */
if (i == 0U)
{
/* Setup the PWM period for a PWM_X non-aligned signal */
pulseCnt = base->SM[subModule].VAL1;
/* Indicates the start of the PWM period */
base->SM[subModule].INIT = 0;
/* Indicates the center value */
base->SM[subModule].VAL0 = (pulseCnt / 2);
/* Indicates the end of the PWM period */
base->SM[subModule].VAL1 = pulseCnt;
}
/* Setup the PWM dutycycle */
if (chnlParams->pwmChannel == kPWM_PwmA)
{
base->SM[subModule].VAL2 = PWM_GetComplementU16(pwmHighPulse / 2U);
base->SM[subModule].VAL3 = (pwmHighPulse / 2U);
}
else if(chnlParams->pwmChannel == kPWM_PwmB)
{
base->SM[subModule].VAL4 = PWM_GetComplementU16(pwmHighPulse / 2U);
base->SM[subModule].VAL5 = (pwmHighPulse / 2U);
}
/*initialize duty circle, VAL0/VAL1 is the duty circle*/
else if(chnlParams->pwmChannel == kPWM_PwmX)
{
PRINTF("kPWM_PwmX duty is set\t\n");
base->SM[subModule].VAL0 = pwmHighPulse;
base->SM[subModule].VAL1 = pwmHighPulse*2;
PRINTF("VAL0 = %d\r\n",base->SM[subModule].VAL0);
PRINTF("VAL1 = %d\r\n",base->SM[subModule].VAL1);
}
break;
default:
assert(false);
break;
}
/* Setup register shift values based on the channel being configured.
* Also setup the deadtime value
*/
if (chnlParams->pwmChannel == kPWM_PwmA)
{
polarityShift = PWM_OCTRL_POLA_SHIFT;
outputEnableShift = PWM_OUTEN_PWMA_EN_SHIFT;
base->SM[subModule].DTCNT0 = PWM_DTCNT0_DTCNT0(chnlParams->deadtimeValue);
}
else if(chnlParams->pwmChannel == kPWM_PwmB)
{
polarityShift = PWM_OCTRL_POLB_SHIFT;
outputEnableShift = PWM_OUTEN_PWMB_EN_SHIFT;
base->SM[subModule].DTCNT1 = PWM_DTCNT1_DTCNT1(chnlParams->deadtimeValue);
}
else
{
polarityShift = PWM_OCTRL_POLX_SHIFT;
outputEnableShift = PWM_OUTEN_PWMX_EN_SHIFT;
base->SM[subModule].DTCNT0 = PWM_DTCNT1_DTCNT1(chnlParams->deadtimeValue);
}
/* Set PWM output fault status */
switch (chnlParams->pwmChannel)
{
case kPWM_PwmA:
base->SM[subModule].OCTRL &= ~((uint16_t)PWM_OCTRL_PWMAFS_MASK);
base->SM[subModule].OCTRL |= (((uint16_t)(chnlParams->faultState) << (uint16_t)PWM_OCTRL_PWMAFS_SHIFT) &
(uint16_t)PWM_OCTRL_PWMAFS_MASK);
break;
case kPWM_PwmB:
base->SM[subModule].OCTRL &= ~((uint16_t)PWM_OCTRL_PWMBFS_MASK);
base->SM[subModule].OCTRL |= (((uint16_t)(chnlParams->faultState) << (uint16_t)PWM_OCTRL_PWMBFS_SHIFT) &
(uint16_t)PWM_OCTRL_PWMBFS_MASK);
break;
case kPWM_PwmX:
base->SM[subModule].OCTRL &= ~((uint16_t)PWM_OCTRL_PWMXFS_MASK);
base->SM[subModule].OCTRL |= (((uint16_t)(chnlParams->faultState) << (uint16_t)PWM_OCTRL_PWMXFS_SHIFT) &
(uint16_t)PWM_OCTRL_PWMXFS_MASK);
break;
default:
assert(false);
break;
}
/* Setup signal active level */
if ((bool)chnlParams->level == kPWM_HighTrue)
{
base->SM[subModule].OCTRL &= ~((uint16_t)1U << (uint16_t)polarityShift);
}
else
{
base->SM[subModule].OCTRL |= ((uint16_t)1U << (uint16_t)polarityShift);
}
/* Enable PWM output */
base->OUTEN |= ((uint16_t)1U << ((uint16_t)outputEnableShift + (uint16_t)subModule));
/* Get the next channel parameters */
chnlParams++;
}
return kStatus_Success;
}
再更新 PWM_UpdatePwmDutycycleHighAccuracy 函数
void PWM_UpdatePwmDutycycleHighAccuracy(
PWM_Type *base, pwm_submodule_t subModule, pwm_channels_t pwmSignal, pwm_mode_t currPwmMode, uint16_t dutyCycle)
{
assert((uint16_t)pwmSignal < 3U);
uint16_t pulseCnt = 0, pwmHighPulse = 0;
uint16_t modulo = 0;
switch (currPwmMode)
{
case kPWM_SignedCenterAligned:
modulo = base->SM[subModule].VAL1 + 1U;
pulseCnt = modulo * 2U;
/* Calculate pulse width */
pwmHighPulse = (pulseCnt * dutyCycle) / 65535U;
/* Setup the PWM dutycycle */
if (pwmSignal == kPWM_PwmA)
{
base->SM[subModule].VAL2 = PWM_GetComplementU16(pwmHighPulse / 2U);
base->SM[subModule].VAL3 = (pwmHighPulse / 2U);
}
else
{
base->SM[subModule].VAL4 = PWM_GetComplementU16(pwmHighPulse / 2U);
base->SM[subModule].VAL5 = (pwmHighPulse / 2U);
}
break;
case kPWM_CenterAligned:
pulseCnt = base->SM[subModule].VAL1 + 1U;
/* Calculate pulse width */
pwmHighPulse = (pulseCnt * dutyCycle) / 65535U;
/* Setup the PWM dutycycle */
if (pwmSignal == kPWM_PwmA)
{
base->SM[subModule].VAL2 = ((pulseCnt - pwmHighPulse) / 2U);
base->SM[subModule].VAL3 = ((pulseCnt + pwmHighPulse) / 2U);
}
else
{
base->SM[subModule].VAL4 = ((pulseCnt - pwmHighPulse) / 2U);
base->SM[subModule].VAL5 = ((pulseCnt + pwmHighPulse) / 2U);
}
break;
case kPWM_SignedEdgeAligned:
modulo = base->SM[subModule].VAL1 + 1U;
pulseCnt = modulo * 2U;
/* Calculate pulse width */
pwmHighPulse = (pulseCnt * dutyCycle) / 65535U;
/* Setup the PWM dutycycle */
if (pwmSignal == kPWM_PwmA)
{
base->SM[subModule].VAL2 = PWM_GetComplementU16(modulo);
base->SM[subModule].VAL3 = PWM_GetComplementU16(modulo) + pwmHighPulse;
}
else
{
base->SM[subModule].VAL4 = PWM_GetComplementU16(modulo);
base->SM[subModule].VAL5 = PWM_GetComplementU16(modulo) + pwmHighPulse;
}
break;
case kPWM_EdgeAligned:
pulseCnt = base->SM[subModule].VAL1 + 1U;
/* Calculate pulse width */
pwmHighPulse = (pulseCnt * dutyCycle) / 65535U;
/* Setup the PWM dutycycle */
if (pwmSignal == kPWM_PwmA)
{
base->SM[subModule].VAL2 = 0;
base->SM[subModule].VAL3 = pwmHighPulse;
}
else
{
base->SM[subModule].VAL4 = 0;
base->SM[subModule].VAL5 = pwmHighPulse;
}
break;
case kPWM_NonAligned:
pulseCnt = base->SM[subModule].VAL1 + 1U;
/* Calculate pulse width */
pwmHighPulse = (pulseCnt * dutyCycle) / 65535U;
/* Setup the PWM dutycycle */
if (pwmSignal == kPWM_PwmA)
{
base->SM[subModule].VAL2 = 0;
base->SM[subModule].VAL3 = pwmHighPulse;
}
else if (pwmSignal == kPWM_PwmB)
{
base->SM[subModule].VAL4 = 0;
base->SM[subModule].VAL5 = pwmHighPulse;
}
//add PWMX duty update here
else
{
base->SM[subModule].VAL0 = pwmHighPulse;
//base->SM[subModule].VAL1 = 0;
PRINTF("pwmHighPulse = %d\r\n",pwmHighPulse);
PRINTF("VAL1 = %d\r\n",base->SM[subModule].VAL1);
}
break;
default:
assert(false);
break;
}
}
通过添加以上对 kPWM_NonAligned 输出 PWMX 的代码,再通过 PWM configure 函数,对 PWMX 的输出方式进行配置,代码如下,即可输出指定占空比的 PWMX 的 PWM 波形。
//PWM
void pwm_config(pwm_no_t pwm_no, uint32 frq ,gpio_no_t gpio_no) //Configure PWM initial settings, frq: pwm frquency, 1 per 1Hz, 6 PWM channels share the same SCT, the frq value should be the same.
{
/* Structure of initialize PWM */
pwm_config_t pwmConfig;
pwm_fault_param_t faultConfig;
CLOCK_SetDiv(kCLOCK_IpgDiv, 0x3); /* Set IPG PODF to 3, divede by 4 */
/* Set the PWM Fault inputs to a low value */
XBARA_Init(XBARA);
XBARA_SetSignalsConnection(XBARA, kXBARA1_InputLogicHigh, kXBARA1_OutputFlexpwm1Fault0);
XBARA_SetSignalsConnection(XBARA, kXBARA1_InputLogicHigh, kXBARA1_OutputFlexpwm1Fault1);
XBARA_SetSignalsConnection(XBARA, kXBARA1_InputLogicHigh, kXBARA1_OutputFlexpwm1Fault2);
XBARA_SetSignalsConnection(XBARA, kXBARA1_InputLogicHigh, kXBARA1_OutputFlexpwm1Fault3);
PWM_GetDefaultConfig(&pwmConfig);
#ifdef DEMO_PWM_CLOCK_DEVIDER
pwmConfig.prescale = DEMO_PWM_CLOCK_DEVIDER;
#endif
/* Use full cycle reload */
pwmConfig.reloadLogic = kPWM_ReloadPwmFullCycle;
/* PWM A & PWM B form a complementary PWM pair */
pwmConfig.pairOperation = kPWM_Independent;
pwmConfig.prescale = kPWM_Prescale_Divide_4;
pwmConfig.enableDebugMode = true;
/* Initialize submodule 0 */
if (PWM_Init(PWM1, kPWM_Module_0, &pwmConfig) == kStatus_Fail)
{
PRINTF("PWM initialization failed\r\n");
}
pwmConfig.clockSource = kPWM_BusClock;
pwmConfig.prescale = kPWM_Prescale_Divide_4;
pwmConfig.initializationControl = kPWM_Initialize_LocalSync; //initializationControl
if (PWM_Init(PWM1, kPWM_Module_1, &pwmConfig) == kStatus_Fail)
{
PRINTF("PWM initialization failed\r\n");
}
/* Initialize submodule 2 the same way as submodule 1 */
if (PWM_Init(PWM1, kPWM_Module_2, &pwmConfig) == kStatus_Fail)
{
PRINTF("PWM initialization failed\r\n");
}
/* Initialize submodule 3 the same way as submodule 1 */
if (PWM_Init(PWM1, kPWM_Module_3, &pwmConfig) == kStatus_Fail)
{
PRINTF("PWM initialization failed\r\n");
}
/*
* config->faultClearingMode = kPWM_Automatic;
* config->faultLevel = false;
* config->enableCombinationalPath = true;
* config->recoverMode = kPWM_NoRecovery;
*/
PWM_FaultDefaultConfig(&faultConfig);
#ifdef DEMO_PWM_FAULT_LEVEL
faultConfig.faultLevel = DEMO_PWM_FAULT_LEVEL;
#endif
/* Sets up the PWM fault protection */
PWM_SetupFaults(PWM1, kPWM_Fault_0, &faultConfig);
PWM_SetupFaults(PWM1, kPWM_Fault_1, &faultConfig);
PWM_SetupFaults(PWM1, kPWM_Fault_2, &faultConfig);
PWM_SetupFaults(PWM1, kPWM_Fault_3, &faultConfig);
/* Set PWM fault disable mapping for submodule 0/1/2/3 */
PWM_SetupFaultDisableMap(PWM1, kPWM_Module_0, kPWM_PwmA, kPWM_faultchannel_0,
kPWM_FaultDisable_0 | kPWM_FaultDisable_1 | kPWM_FaultDisable_2 | kPWM_FaultDisable_3);
PWM_SetupFaultDisableMap(PWM1, kPWM_Module_1, kPWM_PwmA, kPWM_faultchannel_0,
kPWM_FaultDisable_0 | kPWM_FaultDisable_1 | kPWM_FaultDisable_2 | kPWM_FaultDisable_3);
PWM_SetupFaultDisableMap(PWM1, kPWM_Module_2, kPWM_PwmA, kPWM_faultchannel_0,
kPWM_FaultDisable_0 | kPWM_FaultDisable_1 | kPWM_FaultDisable_2 | kPWM_FaultDisable_3);
/* Call the init function with demo configuration */
pwm_set_param(pwm_no, frq, gpio_no, pwm_mode);
/* Set the load okay bit for all submodules to load registers from their buffer */
PWM_SetPwmLdok(PWM1, kPWM_Control_Module_0 | kPWM_Control_Module_1 | kPWM_Control_Module_2 | kPWM_Control_Module_3, true);
/* Start the PWM generation from Submodules 0, 1 and 2 */
PWM_StartTimer(PWM1, kPWM_Control_Module_0 | kPWM_Control_Module_1 | kPWM_Control_Module_2 | kPWM_Control_Module_3);
}
static PWM_Type *const s_pwmBases[] = PWM_BASE_PTRS;
static uint32_t PWM_GetInstance(PWM_Type *base)
{
uint32_t instance;
/* Find the instance index from base address mappings. */
for (instance = 0; instance < ARRAY_SIZE(s_pwmBases); instance++)
{
if (s_pwmBases[instance] == base)
{
break;
}
}
assert(instance < ARRAY_SIZE(s_pwmBases));
return instance;
}