zhcj66 发表于 2022-10-29 13:17:37

<分享>STM32输出可控数量的pwm波形

在做步进电机驱动程序,想让电机按一定速度运行一定角度,于是采用了

一个主定时器产生pwm,另外一个从定时器负责发送脉冲数量的计数

STM32F103VE 时钟72MHz

实现:输出可控数量与频率的脉冲(pwm)

使用一个(主)定时器作为另一个(从)定时器的预分频器(外部时钟触发源)

本例子通过<df_MAIN_TIM_1_OR_8>修改可以实现TIM1和TIM8主的切换产生PWM信号,TIM2作为从定时器,用于对主定时器的计数,计数到达设定数量,停止pwm输出

TIM8已验证功能-ok;TIM1仿真运行正常,输出未验证


#define df_MAIN_TIM_1                        0                                        //主定时器1
#define df_MAIN_TIM_8                        1                                        //主定时器8
#define df_MAIN_TIM_1_OR_8                df_MAIN_TIM_8                //定时器选择

void TIM1_MasterPwmOutInit(void);
void TIM8_MasterPwmOutInit(void);

//TIM1_主pwm输出初始化
void TIM1_MasterPwmOutInit(void){
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
TIM_OCInitTypeDefTIM_OCInitStructure;
//pwm频率=72000000/psc/arr这里为了测试设置了1Hz用于led观测
unsigned short arr=10000;//周期
unsigned short psc=7200;//分频
       
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_TIM1 , ENABLE); //时钟使能

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;                   //TIM1_CH4 PA11
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;             //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_TimeBaseStructure.TIM_Period = arr-1;               //使用Cycle来控制频率(f=72/(71+1)/Cycle)当Cycle为100时脉冲频率为10KHZ                           
    TIM_TimeBaseStructure.TIM_Prescaler = psc-1;                  //设置用来作为TIMx时钟频率除数的预分频值                                                   
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;   //设置时钟分割:TDTS= Tck_tim            
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;            //重复计数,一定要=0!!!(高级定时器特有)
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);                                       

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                          //选择定时器模式:TIM脉冲宽度调制模式1      
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;         //比较输出使能
    TIM_OCInitStructure.TIM_Pulse = psc/2-1;                          //设置待装入捕获寄存器的脉冲值(占空比:默认50%,这可也可以调节如果需要的话将它作为一个参数传入即可)                                 
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;              //输出极性      

    TIM_OC4Init(TIM1, &TIM_OCInitStructure);                                      //使能通道4                                                

    TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);        //设置为主从模式
    TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);                        //选择定时器1的触发方式(使用更新事件作为触发输出)
   

    TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);               //使能通道4预装载寄存器   
       
    TIM_ARRPreloadConfig(TIM1, ENABLE);                           //使能TIM1在ARR上的预装载寄存器
       
    //TIM_CtrlPWMOutputs(TIM1, ENABLE);                                                           //高级定时器一定要加上,主输出使能   
}

//TIM8_pwm输出初始化
void TIM8_MasterPwmOutInit(void){
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
TIM_OCInitTypeDefTIM_OCInitStructure;
//pwm频率=72000000/psc/arr这里为了测试设置了1Hz用于led观测
unsigned short arr=10000;//周期
unsigned short psc=7200;//分频
       
{
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE);//使能GPIO外设和AFIO复用功能模块时钟使能
       
        //PC6-CH1
        //PC7-CH2
        //PC8-CH3 cut_pwm
        //PC9-CH4 out_pwm
        //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;//初始化GPIO
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;//初始化GPIO
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                                                //复用推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOC, &GPIO_InitStructure);
               
        TIM_TimeBaseStructure.TIM_Period = arr;                                                                 //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
        TIM_TimeBaseStructure.TIM_Prescaler =psc;                                                                 //设置用来作为TIMx时钟频率除数的预分频值72MHz/(psc+1)/(arr+1)=频率
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;                                 //设置时钟分割:TDTS = Tck_tim
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;                        //TIM向上计数模式
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;                                    //重复计数,一定要=0!!!(高级定时器特有)<<<这里必须设置否则从计数器不正常
        TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);                                                 //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
       
//        {//CH1
//                TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                                                 //选择定时器模式:TIM脉冲宽度调制模式1
//                TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;                        //使能/失能OCx管脚
//                TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;                 //使能/失能OCxN管脚(使用OCxN必须使能TIM_OutputState)
//                TIM_OCInitStructure.TIM_Pulse = arr+1;                                                                         //设置待装入捕获比较寄存器的脉冲值
//                TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                                 //输出极性:TIM输出比较极性高
//                TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;                                //输出极性
//                TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;                                //BDTR寄存器的MOE为0时对应管脚状态
//                TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;                        //BDTR寄存器的MOE为0时对应管脚状态
//                TIM_OC1Init(TIM8, &TIM_OCInitStructure);                                                                //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//               
//                TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable);                                                //使能TIMx在CCR2上的预装载寄存器
//        }
//        {//CH2
//                TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                                                 //选择定时器模式:TIM脉冲宽度调制模式1
//                TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;                        //使能/失能OCx管脚
//                TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;                 //使能/失能OCxN管脚(使用OCxN必须使能TIM_OutputState)
//                TIM_OCInitStructure.TIM_Pulse = arr+1;                                                                         //设置待装入捕获比较寄存器的脉冲值
//                TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                                 //输出极性:TIM输出比较极性高
//                TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;                                //输出极性
//                TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;                                //BDTR寄存器的MOE为0时对应管脚状态
//                TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;                        //BDTR寄存器的MOE为0时对应管脚状态
//                TIM_OC2Init(TIM8, &TIM_OCInitStructure);                                                                //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
//               
//                TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable);                                                //使能TIMx在CCR2上的预装载寄存器
//        }
        {//CH3
                TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                                                 //选择定时器模式:TIM脉冲宽度调制模式1
                TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;                        //使能/失能OCx管脚
                TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;                 //使能/失能OCxN管脚(使用OCxN必须使能TIM_OutputState)
                TIM_OCInitStructure.TIM_Pulse = arr/2-1;                                                                 //设置待装入捕获比较寄存器的脉冲值 占空比
                TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                                 //输出极性:TIM输出比较极性高
                TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;                                //输出极性
                TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;                                //BDTR寄存器的MOE为0时对应管脚状态
                TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;                        //BDTR寄存器的MOE为0时对应管脚状态
                TIM_OC3Init(TIM8, &TIM_OCInitStructure);                                                                //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
               
                TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable);                                                //使能TIMx在CCR2上的预装载寄存器
        }
        {//CH4
                TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                                                 //选择定时器模式:TIM脉冲宽度调制模式1
                TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;                        //使能/失能OCx管脚
                TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;                 //使能/失能OCxN管脚(使用OCxN必须使能TIM_OutputState)
                TIM_OCInitStructure.TIM_Pulse = arr/2-1;                                                                 //设置待装入捕获比较寄存器的脉冲值 占空比
                TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                                 //输出极性:TIM输出比较极性高
                TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_Low;                                //输出极性
                TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;                        //BDTR寄存器的MOE为0时对应管脚状态
                TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;                        //BDTR寄存器的MOE为0时对应管脚状态
                TIM_OC4Init(TIM8, &TIM_OCInitStructure);                                                                //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
               
                TIM_OC4PreloadConfig(TIM8, TIM_OCPreload_Enable);                                                //使能TIMx在CCR2上的预装载寄存器
        }
       
        TIM_ARRPreloadConfig(TIM8, ENABLE);                                                                                 //使能TIMx在ARR上的预装载寄存器
       
       
//        TIM_CtrlPWMOutputs(TIM8, ENABLE);                                                                                //TIM8->BDTR|=1<<15; 主输出MOE
       
//        TIM_Cmd(TIM8, ENABLE);                                                                                                //使能TIMx外设
        TIM_SetCompare3(TIM8,arr/2-1);                                                                                        //TIM8->CCR2 = x;更改占空比(设置范围0~arr)
        TIM_SetCompare3(TIM8,arr/2-1);                                                                                        //TIM8->CCR2 = x;更改占空比(设置范围0~arr)
       
}               
        {//设置为主从模式
                TIM_SelectMasterSlaveMode(TIM8, TIM_MasterSlaveMode_Enable);        //设置为主从模式
                TIM_SelectOutputTrigger(TIM8, TIM_TRGOSource_Update);                        //选择定时器1的触发方式(使用更新事件作为触发输出)      
        }       
                     
}

//TIM2从计数定时器初始化
//设定脉冲数量后面可以通过TIM_SetAutoreload(TIM2,X);修改,这里预设了一个数值
void TIM2_SlaveCountInit(void){
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
unsigned short PulseNum = 65535;                                                        //预设脉冲数
       
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);                //使能定时器2的时钟
       
    TIM_TimeBaseStructure.TIM_Period = PulseNum-1;                           //脉冲数
    TIM_TimeBaseStructure.TIM_Prescaler =0;   
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;   
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

#if (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_1)
    TIM_SelectInputTrigger(TIM2, TIM_TS_ITR0);                                        //选择定时器2的输入触发源(内部触发(TIM1))
#elif (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_8)
        TIM_SelectInputTrigger(TIM2, TIM_TS_ITR1);                                        //选择定时器2的输入触发源(内部触发(TIM8))
#endif //df_MAIN_TIM_1_OR_8
                                 
        TIM_SelectSlaveMode(TIM2,TIM_SlaveMode_External1);                   //TIM2->SMCR|=0x07;设置从模式寄存器(SMS:111 外部时钟模式1)

    TIM_ITConfig(TIM2,TIM_IT_Update,DISABLE);                                        //更新中断失能

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;      
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;   
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);                                                                //定时器2中断初始化
}

//定时器_主从_pwm计数_初始化
void TIM_MasterSlave_PWMCount_Init(void){       
    TIM2_SlaveCountInit();                                                //初始化 (从定时器)
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);        //清除中断标志位
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);        //使能更新中断
       
#if (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_1)       
    TIM1_MasterPwmOutInit();                                                //使能定时器1(主定时器)   
#elif (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_8)
        TIM8_MasterPwmOutInit();                                                //使能定时器1(主定时器)
#endif //df_MAIN_TIM_1_OR_8
}

//定时器_主从_pwm计数_开始
//PulseNum :用于设定输出脉冲的数量
void TIM_MasterSlave_PWMCount_Start(unsigned short PulseNum){
        PulseNum = (PulseNum>1?PulseNum-1:PulseNum); //这里的数值不能为0,为0后TIM2将失效; 设置数-1为需要的脉冲数
    TIM_Cmd(TIM2, ENABLE);                                                //使能TIM2 (从定时器)
        TIM_SetAutoreload(TIM2,PulseNum);                        //设置脉冲数量
        TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);        //使能更新中断
       
#if (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_1)          
    TIM_Cmd(TIM1, ENABLE);                                                //使能定时器1
        TIM_CtrlPWMOutputs(TIM1, ENABLE);                   //高级定时器一定要加上,主输出使能
#elif (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_8)
    TIM_Cmd(TIM8, ENABLE);                                                //使能定时器8
        TIM_CtrlPWMOutputs(TIM8, ENABLE);                   //高级定时器一定要加上,主输出使能
#endif //df_MAIN_TIM_1_OR_8
}

//当TIM的CNT寄存器的值到达设定的Update值会触发更新中断,此时设定的脉冲数已输出完毕,关闭TIM1和TIM2
void TIM2_IRQHandler(void){
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){         //TIM_IT_Update
   
      TIM_ClearITPendingBit(TIM2, TIM_IT_Update);         // 清除中断标志位
#if (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_1)       
      TIM_CtrlPWMOutputs(TIM1, DISABLE);                        //主输出失能
      TIM_Cmd(TIM1, DISABLE);                                                 //关闭定时器
#elif (df_MAIN_TIM_1_OR_8 == df_MAIN_TIM_8)               
      TIM_CtrlPWMOutputs(TIM8, DISABLE);                        //主输出失能
      TIM_Cmd(TIM8, DISABLE);                                                 //关闭定时器
#endif //df_MAIN_TIM_1_OR_8
      TIM_Cmd(TIM2, DISABLE);                                                 //关闭定时器
      TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);         //关闭TIM2更新中断
    }
}


u8 ksw=0;
void tim_test_co(void){
        TIM_MasterSlave_PWMCount_Init();//初始化
        while(1)
        {
                if(ksw){
                        ksw = 0;
                        TIM_MasterSlave_PWMCount_Start(3);//启动3个pwm
                }
        }
}

powermeter 发表于 2022-10-29 14:12:55

感谢分享。。。

lnso 发表于 2022-10-29 21:05:57

感谢分享。。

whatcanitbe 发表于 2022-10-30 13:11:17

18年VIP++,牛

zhcj66 发表于 2022-10-31 11:16:20

whatcanitbe 发表于 2022-10-30 13:11
18年VIP++,牛
(引用自4楼)

一直到我退休了{:lol:}

kyq_linux 发表于 2022-10-31 15:25:26

zhcj66 发表于 2022-10-31 11:16
一直到我退休了
(引用自5楼)

可能会多几个脉冲,你可以控控多种数量的,比如从几个到几千,印象中有这毛病。

磊磊映画 发表于 2022-11-3 16:37:53

这个控制方法以前用,后来参考别人的帖子,改成DMA 方式了看看 我的帖子占用资源少,一路可控只用一个定时器就能实现了,而且用DMA 方式,不占CPU

zhcj66 发表于 2022-11-4 08:13:18

kyq_linux 发表于 2022-10-31 15:25
可能会多几个脉冲,你可以控控多种数量的,比如从几个到几千,印象中有这毛病。

...
(引用自6楼)

启用了CCR1和CCR2,在CCR1=0CCR2 = 50(方波 占空比50%) ,启动输出,CCR1管脚会出现几个脉冲就消失了,不知道你遇到过吗?

zhcj66 发表于 2022-11-4 08:18:30

磊磊映画 发表于 2022-11-3 16:37
这个控制方法以前用,后来参考别人的帖子,改成DMA 方式了看看 我的帖子占用资源少,一路可控只用一个 ...
(引用自7楼)

https://www.amobbs.com/thread-5758539-1-1.html

<STM32驱动步进电机精确可控输出脉冲个数方案及对比>
7.定时器CHx独立通道dma,改变dma传输长度控制个数(上一个用的溢出dma一个定时器只能产生一路有点浪费,改成用通道dma)
                                                            一个定时器可以产生3路或4路,dma限制不能是4,例如定时器5、8可以4路
                                                               优点:能读取剩余脉冲个数
                                                               可变占空比,周期,用于可以调速等         缺点:好像没哈缺点

这个DMA确实更好

磊磊映画 发表于 2022-11-4 17:37:23

zhcj66 发表于 2022-11-4 08:18
https://www.amobbs.com/thread-5758539-1-1.html


(引用自9楼)

嗯,做了两个项目实战得来的
页: [1]
查看完整版本: <分享>STM32输出可控数量的pwm波形