- MDPWM介绍
MindPWM 是灵动微电子自主研发的高精度脉宽调制器模块,该模块拥有最多 4 个子模块,每个子模块都配有一个单独的 16 位定时器,可以分别产生 2 个独立的 PWM 输出或者 1 对互补的 PWM 输出,整个模块的 8 个输出,全部支持硬件死区插入,支持故障刹车,支持最高 208ps 的高精度。 MindPWM 可以产生多种多样的开关模式,包括非常复杂的波形。它可以用来控制所有已知的电机类型,用于控制开关电源也非常理想。 - MDPWM输出(中央对齐向上计数模式)
MDPWM输出完全由SMxINIT和SMxVAL0-SMxVAL5还有SMxFRCR这几个寄存器决定
MDPWM可以分为4个子模块,每个模块都能输出PWMA、PWMB两组PWM信号。
SMxINIT:INIT的值表示初始值,每次计数器重装载之后都从这个值开始计数。
SMxVAL1:VAL1表示计数器的终点值,计数器达到这个值之后就会重装载回到INIT。
INTT->VAL1就表示一个周期的时间。计算方法周期 = 时钟频率/预分频系数/输出PWM的频率。
SMxVAL2:计数器经过VAL2时PWMA输出高电平。
SMxVAL3:计数器经过VAL3时PWMA输出低电平。
VAL2->VAL3就表示PWMA输出高电平的时间,即脉冲宽度,改变VAL3即可改变占空比,将VAL2与VAL3同时增加或减小一定值即可实现PWM相移;如果使用两个不同子模块产生相移 PWM 时,也可以通过不同的 INIT 值来达到这一效果。
SMxVAL4:计数器经过VAL4时PWMB输出高电平。
SMxVAL5:计数器经过VAL5时PWMB输出低电平。
SMxVAL0:0点,即中央对齐点,对齐的数值也可以是 0 之外的其他值,只是 0 作为对齐点可以简化运算并提供最大的范围。
中央对齐向上计数时一般使用int有符号数来配置,可以使用库函数PWM_GetComplementU16()函数计算16位无符号整数的二进制补码(取反加一)
计算得出有符号数之后可以通过以下函数更新SMxVALx的值。或者使用DMA快速更新寄存器的值:\`// 启用DMA写入
PWM_DMAWriteCmd(MindPWM, PWM_Module_0, ENABLE);
// 设置DMA传输目标为VAL3寄存器\`
// 更新周期寄存器
PWM_SetVALxValue(MindPWM, PWM_Module_0, PWM_ValueRegister_1, newPeriod);
// 更新匹配寄存器
PWM_SetVALxValue(MindPWM, PWM_Module_0, PWM_ValueRegister_3, newMatch);
// 触发更新
PWM_SetPwmLdok(MindPWM, PWM_Control_Module_0, true);
HRPWM(高精度PWM)
之前说MDPWM输出完全由SMxINIT和SMxVAL0-SMxVAL5还有SMxFRCR这几个寄存器决定,SMxVALx是决定PWM输出的整数部分,如果配置PWM为频率2000.35,占空比为60.32%,那应该如何实现?
这就要用到SMxFRCR了,SMxFRCR可以实现脉宽与周期的小数部分,每个子模块的 PWMx_A 和 PWMx_B 输出,可经过 DLL 的细调,改变上升沿、下降沿的输出宽度,实现高精度 PWM 的输出。
DLL 模块以 1/32 APBCLK 或 PLL1 的 VCO CLK 为单位扩展 PWM 的精度,可根据不同的使用场景和需求,选择合适的时钟源。PWMx_A 输出通道可通过FRACVAL2 和 FRACVAL3 的设定值来分别调整输出的上升沿和下降沿延时,PWMx_B 输出通道可通过 FRACVAL4 和 FRACVAL5 的设定值来分别调整输出的上升沿和下降沿延时。
在调用PWM_SetupPwm()函数时会自动开启HRPWM并计算VALX与FRCR的值,因此我们只需要配置结构体就能得到高精度PWM。在使用寄存器配置时,需要先配置以下寄存器以开启HRPWMMindPWM->GCR1 = MindPWM_GCR1_HRPWM_LDO_EN; MindPWM->SM[PWM_Module_0].FRCR0 = MindPWM_SMFRCR0_FRAC_PU; MindPWM->SM[PWM_Module_0].FRCR0 |= MindPWM_SMFRCR0_FRAC1_EN; MindPWM->SM[PWM_Module_0].FRCR0 |= MindPWM_SMFRCR0_FRACVAL1;
4.PWM触发ADC采样
在电机或电源控制中,通常需要在PWM的特定时间进行采样,比如在PWM输出高电平的中间进行ADC采样,使用MDPWM如何实现?这就不得不提到MindSwitch,这个外设可以连接MDPWM与ADC。
当子模块计数器匹配 VAL0-VAL5 时,产生出发输出的使能,其中 VAL0/VAL2/VAL4 用于产生 OUT_TRIG0,VAL1/VAL3/VAL5 用于产生OUT_TRIG1。而在中央对齐向上计数模式中VAL0的 OUT_TRIG0事件刚好就是在PWM输出高电平的中间,只需要配置VAL0触发OUT_TRIG0,MindSwitch配置触发条件为MindPWM_Trig10(表示MindPWM子模块1的OUT_TRIG0事件),触发外设为ADC1_EXT_TRIGGER(表示ADC1由外部事件触发采样),就可以实现在PWM输出高电平的中间进行ADC采样
5.实践
实现输出占空比为63.23%频率为2000的PWM波,并且在pwm输出高电平时adc进行采样,为了直观表现出ADC采样时间,我们可以配置ADC采样中断,在中断函数中反转IO口来得知ADC采样的时间位于PWM的哪个位置,下面是配置代码:void GPIO_Configure(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE); GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 ; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &GPIO_InitStruct); GPIO_WriteBit(GPIOC, GPIO_Pin_1 , Bit_RESET); } /*********************************************************************************************************************** * @brief * @note none * @param none * @retval none *********************************************************************************************************************/ void GPIO_IO_Toggle(GPIO_TypeDef *GPIOn, uint16_t PINn) { if (Bit_RESET == GPIO_ReadOutputDataBit(GPIOn, PINn)) { GPIO_SetBits(GPIOn, PINn); } else { GPIO_ResetBits(GPIOn, PINn); } } //ADC配置部分 void Adc_Configure() { GPIO_InitTypeDef GPIO_InitStruct; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); GPIO_StructInit(&GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_5; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStruct); ADC_InitTypeDef ADC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); ADC_CalibrationConfig(ADC1, 0x1FE); ADC_StructInit(&ADC_InitStruct); ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b; ADC_InitStruct.ADC_Prescaler = ADC_Prescaler_16; ADC_InitStruct.ADC_Mode = ADC_Mode_Scan; // 扫描每个通道进行一次转换 ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_Init(ADC1, &ADC_InitStruct); ADC_SampleTimeConfig(ADC1, ADC_Channel_1, ADC_SampleTime_240_5); ADC_SampleTimeConfig(ADC1, ADC_Channel_5, ADC_SampleTime_240_5); ADC_ChannelCmd(ADC1, ADC_Channel_1, ENABLE); ADC_ChannelCmd(ADC1, ADC_Channel_5, ENABLE); ADC_ExternalTrigSourceConfig(ADC1, ADC_ExtTrig_Edge_Up, ADC_ExtTrig_Shift_16); // 选择外部触发源 ADC_ExternalTrigConvCmd(ADC1, ENABLE); ADC_ClearFlag(ADC1, ADC_FLAG_EOS); ADC_ITConfig(ADC1, ADC_IT_EOS, ENABLE); NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = ADC1_2_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); ADC_Cmd(ADC1, ENABLE); } void ADC1_2_IRQHandler(void) //在中断中翻赚PC1 { if (SET == ADC_GetITStatus(ADC1, ADC_IT_EOS)) { float vol = (float)ADC_GetChannelConvertedValue(ADC1, ADC_Channel_2) * (float)3.3 / (float)4096.0; printf("PA1 Voltage :%0.2f\n ", vol); GPIO_IO_Toggle(GPIOC, GPIO_Pin_1); ADC_ClearITPendingBit(ADC1, ADC_IT_EOS); } } void MDS_Configure() { MDS_TriggerNotCLU_TypeDef MDS_TriggerNotCLU; RCC_APB2PeriphClockCmd(RCC_APB2Periph_MDS, ENABLE); MDS_TriggerNotCluStructInit(&MDS_TriggerNotCLU); MDS_TriggerNotCLU.MDS_Channel = MDS_TriggerOutput_ADC1_EXT_TRIGGER; MDS_TriggerNotCLU.MDS_InputEdge = MDS_InputEdge_Both; MDS_TriggerNotCLU.MDS_InputSource = MDS_TriggerSource_MindPWM_Trig10;//表示MindPWM子模块1的OUT_TRIG0事件 MDS_TriggerNotCluInit(&MDS_TriggerNotCLU); } /* Private macro ******************************************************************************************************/ #define APP_DEFAULT_PWM_FREQUENCE (2000.0f) /*!< PWM pulse width, value should be between 0 to 100 */ #define APP_PWM_DUTYCYCLE_PERCENT (63.23f) /* Private variables **************************************************************************************************/ /* Private functions **************************************************************************************************/ /*********************************************************************************************************************** * @brief * @note none * @param none * @retval none *********************************************************************************************************************/ static void PWM_Init3PhPWM(void) { RCC_ClocksTypeDef RCC_Clocks; uint16_t deadTimeVal = 0; pwm_signal_param_t pwmSignal[2]; uint32_t pwmSourceClockInHz; float pwmFrequencyInHz = APP_DEFAULT_PWM_FREQUENCE; RCC_GetClocksFreq(&RCC_Clocks); pwmSourceClockInHz = RCC_Clocks.PCLK2_Frequency; pwmSignal[0].pwmChannel = PWM_PwmA; //配置PWMA pwmSignal[0].level = PWM_HighTrue; //有效电平为高电平 pwmSignal[0].dutyCyclePercent = APP_PWM_DUTYCYCLE_PERCENT; //占空比 pwmSignal[0].deadtimeValue = deadTimeVal; //死区 pwmSignal[0].faultState = PWM_PwmFaultState0; //默认故障状态 pwmSignal[0].pwmchannelenable = true; //通道使能 pwmSignal[1].pwmChannel = PWM_PwmB; pwmSignal[1].level = PWM_HighTrue; /* Dutycycle field of PWM B does not matter as we are running in PWM A complementary mode */ pwmSignal[1].dutyCyclePercent = APP_PWM_DUTYCYCLE_PERCENT; pwmSignal[1].deadtimeValue = deadTimeVal; pwmSignal[1].faultState = PWM_PwmFaultState0; pwmSignal[1].pwmchannelenable = true; /*********** PWMA_SM0 - phase A, configuration, setup 2 channel as an example ************/ PWM_SetupPwm(MindPWM, PWM_Module_0, pwmSignal, 2, PWM_UpwardCenterAligned, pwmFrequencyInHz, pwmSourceClockInHz);//子模块0,2通道,中央向上计数对齐,频率,时钟 /*********** PWMA_SM1 - phase B configuration, setup PWM A channel only ************/ PWM_SetupPwm(MindPWM, PWM_Module_1, pwmSignal, 2, PWM_UpwardCenterAligned, pwmFrequencyInHz, pwmSourceClockInHz/8); /*********** PWMA_SM2 - phase C configuration, setup PWM A channel only ************/ PWM_SetupPwm(MindPWM, PWM_Module_2, pwmSignal, 2, PWM_UpwardCenterAligned, pwmFrequencyInHz, pwmSourceClockInHz/8); } /*********************************************************************************************************************** * @brief * @note none * @param none * @retval none *********************************************************************************************************************/ void MindPWM_Configure(void) { PWM_InitTypeDef PWM_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_MDPWM, ENABLE); GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_11); //MDPWM_CHA1 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_High; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_17); //MDPWM_CHB1 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_High; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_11); //MDPWM_CHA2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_High; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_11); //MDPWM_CHB2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_High; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_11); //MDPWM_CHA3 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_High; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_11); //MDPWM_CHB3 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_High; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); /* * PWM_InitStructure.enableDebugMode = false; * PWM_InitStructure.reloadSelect = PWM_LocalReload; * PWM_InitStructure.clockSource = PWM_BusClock; * PWM_InitStructure.prescale = PWM_Prescale_Divide_1; * PWM_InitStructure.initializationControl = PWM_Initialize_LocalSync; * PWM_InitStructure.forceTrigger = kPWM_Force_Local; * PWM_InitStructure.reloadFrequency = kPWM_LoadEveryOportunity; * PWM_InitStructure.reloadLogic = PWM_ReloadImmediate; * PWM_InitStructure.pairOperation = PWM_Independent; */ PWM_GetDefaultConfig(&PWM_InitStructure); PWM_InitStructure.prescale = PWM_Prescale_Divide_8;//PWM 时钟频率 = fclk/8 PWM_InitStructure.reloadLogic = PWM_ReloadPwmFullCycle;//寄存器在 PWM 满周期时加载 PWM_InitStructure.pairOperation = PWM_ComplementaryPwmA;//PWM A & PWM B是互补通道,PWM A产生信号 PWM_InitStructure.enableDebugMode = true; /* Initialize submodule 0 */ if (PWM_Init(MindPWM, PWM_Module_0, &PWM_InitStructure) == 1) { printf("PWM submodule 0 initialization failed\n"); } /* Initialize submodule 1, make it use same counter clock as submodule 0. */ PWM_InitStructure.clockSource = PWM_Submodule0Clock;//子模块 0 (AUX_CLK) 的 clock 用作源时钟 PWM_InitStructure.prescale = PWM_Prescale_Divide_1; PWM_InitStructure.initializationControl = PWM_Initialize_MasterSync; /*!< Master sync from submodule 0 causes initialization */ if (PWM_Init(MindPWM, PWM_Module_1, &PWM_InitStructure) == 1) { printf("PWM submodule 1 initialization failed\n"); } /* Initialize submodule 2 the same way as submodule 1 */ if (PWM_Init(MindPWM, PWM_Module_2, &PWM_InitStructure) == 1) { printf("PWM submodule 2 initialization failed\n"); } /* Call the init function with demo configuration */ PWM_Init3PhPWM(); /* Set the load okay bit for all submodules to load registers from their buffer */ PWM_SetPwmLdok(MindPWM, PWM_Control_Module_0 | PWM_Control_Module_1 | PWM_Control_Module_2, true); /* Start the PWM generation from Submodules 0, 1 and 2 */ PWM_StartTimer(MindPWM, PWM_Control_Module_0 | PWM_Control_Module_1 | PWM_Control_Module_2); } int main(void) { Adc_Configure(); MDS_Configure(); MindPWM_Configure(); GPIO_Configure(); PWM_ActivateOutputTrigger(MindPWM, PWM_Module_0, 1);//使能子模块0的VAL0触发 while (1U) { PLATFORM_DelayMS(50); } }
用逻辑分析仪测量PC6与PC1得到
计算环节:周期=主频/预分频系数/输出pwm频率 = 150M/8/2000 = 9375,因此理论上INIT->VAL1的大小应该为9375,脉冲宽度=周期x占空比 = 9375*63.23% = 5927.8125,因此VAL2->VAL3理论上大小为5927;打开keil仿真页面查看各个寄存器的值:这里发现周期比计算值少1,查看官方库函数发现确实在这里减去1,我暂时不清楚为什么这样做,不过问题不大,与实际计算值基本一致