Funny thing · 4 天前

灵动Mini-F5375-OB开发板评测(二)MindPWM使用(上)

  1. MDPWM介绍
            MindPWM 是灵动微电子自主研发的高精度脉宽调制器模块,该模块拥有最多 4 个子模块,每个子模块都配有一个单独的 16 位定时器,可以分别产生 2 个独立的 PWM 输出或者 1 对互补的 PWM 输出,整个模块的 8 个输出,全部支持硬件死区插入,支持故障刹车,支持最高 208ps 的高精度。 MindPWM 可以产生多种多样的开关模式,包括非常复杂的波形。它可以用来控制所有已知的电机类型,用于控制开关电源也非常理想。
  2. MDPWM输出(中央对齐向上计数模式)
    MDPWM输出完全由SMxINIT和SMxVAL0-SMxVAL5还有SMxFRCR这几个寄存器决定 title=
    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);
  1. 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 的设定值来分别调整输出的上升沿和下降沿延时。 title=
    在调用PWM_SetupPwm()函数时会自动开启HRPWM并计算VALX与FRCR的值,因此我们只需要配置结构体就能得到高精度PWM。在使用寄存器配置时,需要先配置以下寄存器以开启HRPWM

    MindPWM->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得到
    image.png
    计算环节:周期=主频/预分频系数/输出pwm频率 = 150M/8/2000 = 9375,因此理论上INIT->VAL1的大小应该为9375,脉冲宽度=周期x占空比 = 9375*63.23% = 5927.8125,因此VAL2->VAL3理论上大小为5927;打开keil仿真页面查看各个寄存器的值:image.png这里发现周期比计算值少1,查看官方库函数发现确实在这里减去1,我暂时不清楚为什么这样做,不过问题不大,与实际计算值基本一致image.png

推荐阅读
关注数
0
文章数
3
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息