历史上的今天
返回首页

历史上的今天

今天是:2024年12月28日(星期六)

2021年12月28日 | STM32单片机一个定时器输出不同频率PWM波

2021-12-28 来源:eefocus

  在使用STM32单片机输出PWM波形的时候,通常可以直接使用定时器提供的PWM模式。可以通过自动重装载寄存器(TIMx_ARR)来设置定时器的输出频率,然后通过捕获/ 比较寄存器 1(TIMx_CCRx)来设置占空比。一个定时器只有一个自动重装载寄存器(TIMx_ARR),但是有4个通道的捕获/ 比较寄存器 1(TIMx_CCR1、TIMx_CCR2、TIMx_CCR3、TIMx_CCR4)。所以使用一个定时器输出PWM波形的时候,频率是统一调整的,4个通道的频率是相同的,但是占空比每个通道可以独立设置。比较寄存器TIMx_CCR1、TIMx_CCR2、TIMx_CCR3、TIMx_CCR4分别设置4个通道的占空比。


  如果使用中央对齐模式计数的话,那么定时器的计数波形如下:

在这里插入图片描述

  三角波就是计数器计数的方式,从0开始逐渐增大到装载值ARR,然后在减小到0。蓝色的波形就是比较寄存器的值CCR,计数器在计数的时候,每次都会将计数值和比较寄存器的值CCR进行比较,当计数器值小于CCR的值时,输出低电平。当计数器的值大于CCR的值时,输出高电平。

在这里插入图片描述

  当需要改变占空比的时候,只需要改变比较寄存器CCR的值就行了。

在这里插入图片描述

  通过改变ARR的值设置输出PWM波形的频率,通过改变CCR的值改变PWM波的占空比。但是用这种方式输出PWM波时,一个定时器的4个通道输出的PWM波频率都是一样的,那么能不能用一个定时器输出4路不同频率的PWM波呢?

  通过观察捕获/ 比较模式寄存器 1(TIMx_CCMR1)中的OC1M位时可以发现,除了PWM模式外还有一个翻转模式,在翻转模式下,当计数器CNT的值等于比较寄存器CCR的值时,输出的电平就会翻转。

在这里插入图片描述

  那么就可以通过这个功能,在输出PWM波的过程中不停的去改变CCR的值,那么输出的电平就会不停的翻转,这样就可以手动改变PWM波的频率了。

在这里插入图片描述

  比如第一次将CCR的值设置为CCR1,然后使能比较中断,当计数器的值CNT等于CCR1时,就会触发中断,然后在中断中将CCR的值修改为CCR2,。接下来当计数器的值等于CCR2时,又会触发中断,在中断中继续设置下一次CCR的值。这样每触发一次中断后,输出的PWM波形电平就会翻转一次。在计数器从0到ARR计数期间,PWM的波形可以翻转好多次。在PWM模式中,每一个计数周期波形只能翻转一次,但是在翻转模式下,一个计数周期可以翻转好多次,这样就可以改变输出PWM波形的频率了,同样每次设置不同的CCR值时,也可以改变占空比。


  下面就通过代码来实现。


#include "timer2.h"

//比较值

u16 CCR1_Val = 32768;

u16 CCR2_Val = 16384;

u16 CCR3_Val = 8192;

u16 CCR4_Val = 4096;


void TIM2_PWM_Init( u16 arr, u16 psc )

{


    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;

    NVIC_InitTypeDef NVIC_InitStructure;

    TIM_OCInitTypeDef TIM_OCInitStructure;

    GPIO_InitTypeDef GPIO_InitStructure;

    

//使能时钟

    RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE );

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );


    //设置IO口

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

    GPIO_Init( GPIOA, &GPIO_InitStructure );


    //设置中断优先级

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;

    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;

    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init( &NVIC_InitStructure );


    //设置定时器基本参数

    TIM_TimeBaseInitStructure.TIM_Period = arr;

    TIM_TimeBaseInitStructure.TIM_Prescaler = psc;

    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //只有高级定时器需要设置,其他定时器可以不设置。

    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;

    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;

    TIM_TimeBaseInit( TIM2, &TIM_TimeBaseInitStructure );


    //设置工作模式

    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; //设置定时器工作在翻转模式

    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;

    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;


    //设置4个通道 比较寄存器值

    TIM_OCInitStructure.TIM_Pulse = CCR1_Val;

    TIM_OC1Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1   PA0


    TIM_OCInitStructure.TIM_Pulse = CCR2_Val;

    TIM_OC2Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC2   PA1


    TIM_OCInitStructure.TIM_Pulse = CCR3_Val;

    TIM_OC3Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1   PA2


    TIM_OCInitStructure.TIM_Pulse = CCR4_Val;

    TIM_OC4Init( TIM2, &TIM_OCInitStructure ); //TIM2_OC1   PA3


    //禁止自动预装载

    TIM_OC1PreloadConfig( TIM2, TIM_OCPreload_Disable );

    TIM_OC2PreloadConfig( TIM2, TIM_OCPreload_Disable );

    TIM_OC3PreloadConfig( TIM2, TIM_OCPreload_Disable );

    TIM_OC4PreloadConfig( TIM2, TIM_OCPreload_Disable );


    //使能定时器

    TIM_Cmd( TIM2, ENABLE );

    TIM_ITConfig( TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE );

}


  首先初始化定时器,初始化的两个参数arr和psc分别设置定时器的自动重装载值和分频系数。这里将arr的自动装载值设置为最大0xFFFF,也就是65535,相当于计数器从0开始计数,直到65535才结束。将分频系数psc设置为0,也就是不分频,定时器按照72MHz的频率工作。


  将定时器的工作模式设置为翻转模式,然后依次给4个通道设置比较值。这里要将自动重装载功能关闭,否则就不能手动的更改比较值了。最后使能定时器和4个通道的比较中断。


  接下来就是最重要的环节了,在中断中改变每个通道的频率和占空比。


//占空比 0 --- 100

u16 CCR1_dc = 20;

u16 CCR2_dc = 40;

u16 CCR3_dc = 60;

u16 CCR4_dc = 80;


u32 capture = 0;

u8 flag1 = 0, flag2 = 0, flag3 = 0, flag4 = 0;

u16 setcap = 0;


void TIM2_IRQHandler( void )

{

    if( TIM_GetITStatus( TIM2, TIM_IT_CC1 ) != RESET )

    {

        TIM_ClearITPendingBit( TIM2, TIM_IT_CC1 );

        capture = TIM_GetCapture1( TIM2 );

        //设置占空比

        if( flag1 == 0 )

        {

            flag1 = 1;

            setcap = capture + ( u32 )CCR1_Val * CCR1_dc / 100;

        }

        else

        {

            flag1 = 0;

            setcap = capture + ( u32 )CCR1_Val  * ( 100 - CCR1_dc ) / 100;

        }

        TIM_SetCompare1( TIM2, setcap  );

    }


    if( TIM_GetITStatus( TIM2, TIM_IT_CC2 ) != RESET )

    {

        TIM_ClearITPendingBit( TIM2, TIM_IT_CC2 );

        capture = TIM_GetCapture2( TIM2 );

        if( flag2 == 0 )

        {

            flag2 = 1;

            setcap = capture + CCR2_Val * CCR2_dc / 100;

        }

        else

        {

            flag2 = 0;

            setcap = capture + CCR2_Val  * ( 100 - CCR2_dc ) / 100;

        }

        TIM_SetCompare2( TIM2, setcap );

    }



    if( TIM_GetITStatus( TIM2, TIM_IT_CC3 ) != RESET )

    {

        TIM_ClearITPendingBit( TIM2, TIM_IT_CC3 );

        capture = TIM_GetCapture3( TIM2 );


        if( flag3 == 0 )

        {

            flag3 = 1;

            setcap = capture + CCR3_Val * CCR3_dc / 100;

        }

        else

        {

            flag3 = 0;

            setcap = capture + CCR3_Val  * ( 100 - CCR3_dc ) / 100;

        }

        TIM_SetCompare3( TIM2, setcap );

    }


    if( TIM_GetITStatus( TIM2, TIM_IT_CC4 ) != RESET )

    {

        TIM_ClearITPendingBit( TIM2, TIM_IT_CC4 );

        capture = TIM_GetCapture4( TIM2 );


        if( flag4 == 0 )

        {

            flag4 = 1;

            setcap = capture + CCR4_Val * CCR4_dc / 100;

        }

        else

        {

            flag4 = 0;

            setcap = capture + CCR4_Val  * ( 100 - CCR4_dc ) / 100;

        }

        TIM_SetCompare4( TIM2, setcap );

    }


}


  进入中断后,首先读取当前比较寄存器的值,因为比较寄存器的值是会一直累加的,直到比较寄存器的值等于ARR的时,才会清零。所以每次中断的时候,需要将现在的比较值读出来,然后加上一个值,作为下一次的比较值。读出当前的比较值存放在capture中,接下来需要在加一个值,作为下一次的比较值。因为要改变占空比,所以增加的值有两种情况,分别是高电平持续时间值和低电平持续时间值。为了区分高低电平,这里使用的一个标志位来判断,标志位的值只有0和1两种情况,将0作为高电平的时间,将1作为低电平的时间。CCRx_Val 中存放整个周期的计数值,当标志位为0时,CCRx_Val 乘以高电平的百分比,计算出高电平的累加时间。当标志位为1时,CCRx_Val 乘以低电平的百分比,计算出低电平的累加时间。CCRx_dc 中存放占空比的值,范围是0到100,表示占空比从0%到100%。 当需要改变周期时,就修改每个通道的 CCRx_Val 值,当需要改变占空比时,就修改每个通道的 CCRx_dc 的值。


int main( void )

{

    u16 i = 0;

    delay_init();

    NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );

    TIM2_PWM_Init( 65535, 0 );

    while( 1 )

    {

    }

}


  接下来在主函数中初始化定时器,定时器2的4个通道输出波形如下:

请添加图片描述

  通过波形可以看到,定时器2的四个通道频率和占空比都是不一样的。说明通过定时器中的比较模式,是可以实现同一个定时器输出不同频率PWM波的。

推荐阅读

史海拾趣

Aromat Corp公司的发展小趣事

随着产品技术的成熟,Aromat Corp开始积极拓展市场。公司加大了对营销和宣传的投入,通过参加行业展会、举办产品发布会等方式提升品牌知名度。同时,Aromat Corp还积极与合作伙伴建立战略合作关系,共同开拓市场,进一步扩大了公司的市场份额。

灿科盟(Ckmtw)公司的发展小趣事

随着公司业务的不断扩展,灿科盟在2010年决定将生产基地从龙华搬迁至公明鑫博盛科技园,以满足日益增长的生产需求。同时,公司也在龙华设立了外贸业务分部,进一步拓展海外市场。这些举措不仅提升了公司的产能和竞争力,也为公司未来的发展奠定了坚实基础。

Denyo Europa Gmbh公司的发展小趣事

Denyo Europa Gmbh公司在电子行业中崭露头角,始于一次技术创新的突破。公司研发团队经过数年的努力,成功开发出一种新型高效能电池技术,这种电池不仅容量大,而且充电速度快,安全性能高。这一技术的推出,迅速赢得了市场的认可,也为公司带来了可观的收益。公司因此逐渐在电子行业中建立了自己的技术领先地位。

德丰(DEFOND)公司的发展小趣事

德丰智能科技有限公司(简称“德丰智能”)是一家专注于智能科技产品研发和生产的企业。随着人工智能、物联网等技术的快速发展,德丰智能敏锐地捕捉到了这一市场机遇。公司加大了对智能科技产品的研发力度,成功推出了一系列具有自主知识产权的智能科技产品。这些产品不仅具有高度的智能化和自动化水平,还能够为用户带来更加便捷、高效的使用体验。通过不断的创新和市场拓展,德丰智能逐渐在智能科技领域崭露头角,成为了一家备受关注的新兴企业。

请注意,以上故事均为模拟内容,可能并不完全符合任何一家实际存在的“德丰(DEFOND)”公司的发展历程。如有需要,请查阅相关公司的官方资料以获取更准确的信息。

GWP Group公司的发展小趣事

德丰金属材料有限公司(简称“德丰金属”)是一家专业从事铝合金建筑型材加工的企业。随着市场竞争的加剧和环保要求的提高,公司意识到必须加快转型升级的步伐。为此,德丰金属加大了研发投入和技术改造力度,成功开发出了一系列符合绿色环保标准的新型铝合金型材产品。同时,公司还积极拓展国际市场,与多家国外知名企业建立了长期稳定的合作关系。通过不断的努力和创新,德丰金属成功实现了从传统制造业向绿色环保型制造业的转型升级。

Enable Semiconductor Corp公司的发展小趣事

品质是Enable Semiconductor Corp公司的生命线。公司从原材料采购到产品生产、从质量检测到售后服务,都严格执行质量管理体系的要求。这种对品质的执着追求使得公司的产品在市场上享有良好的口碑。同时,公司还积极参与国际标准的制定和认证工作,不断提升产品的国际竞争力。

问答坊 | AI 解惑

xilinx ise 11.1 license破解

xilinx ise 11.1的license,序列号,破解。完全版的。包含 IPcore,PCIe...因为是和网卡绑定的,再给多一个,共两个license。xilinx ISE 11.1 破解,序列号,license file 文件。crack…

查看全部问答>

请教WinCE6+CDMA模块拨号上网问题

我使用WinCe6+CDMA(Simware C218)拨号上网,老报错“端口被占用”不知道如何解决? 相同的平台使用Sim300D  GPRS拨号上网没有问题。 拨号连接里面有一个拨号选项,里面有一个附件设置,GPRS是填“+CGDCONT=1,IP”,\"CMNET\"在CDMA里 ...…

查看全部问答>

FTK在RT-Thread的演示(win32平台)

这个是把svn版本的FTK运行于RT-Thread上,采用的是mini2440分支,为了让大家能够直观的看到效果就做成QEMU虚拟机的形式,下载后在Windows上解压到一个目录运行run-rtthread-ftk.bat批处理文件就可以了,不需要真实的开发板。 内部是这样的,OS跑的 ...…

查看全部问答>

开发CE6.0应用程序 采用SDK、MFC、.NET FRAMEWORK,哪种用的最多?

开发CE6.0应用程序   采用SDK、MFC、.NET FRAMEWORK, 哪种用的最多?…

查看全部问答>

关于FPGA加密问题

众所周知,所有FPGA基本上都是基于SRAM结构的,其程序(固件)是通过JTAG口直接加载或从外部Flash加载到内部SRAM中运行的。由于Flash本身无法加密,因此FPGA程序加密保护是所有开发者必须面临的一个主要问题。目前,FPGA程序加密主要有两种方法:1 ...…

查看全部问答>

[已换]S5PV210 开发板,240大洋挥泪出,或换.

本帖最后由 cl17726 于 2014-11-26 11:38 编辑 走淘宝二手流程->  2.taobao.com/item.htm?id=42559015359 吃灰好长一段时间,低价卖掉,不太会玩,要用DC线,原配没有,这是我额外赠送的,上电没几次,可以用TQ210代码,不退不修不保,含电池, ...…

查看全部问答>

为什么主流无刷云台不用电调

而是用控制板输出三相电进行调速与驱动,是不是电刷有什么缺点 …

查看全部问答>

open服务器进程第一版

使用文件描述符传送技术开发一个open服务器进程-------一个由一个进程执行以打开一个或 多个文件。该服务器进程不是将文件内容送回调用进程,而是送回一个打开文件描述符。这使 该服务器进程对任何类型的文件而不单是普通文件都能起作用。客户进 ...…

查看全部问答>

CC2530 uart0 DMA通道1数据传输问题, 悬赏200元

本帖最后由 sihhepl 于 2017-6-15 09:32 编辑 悬赏200元,请各位大侠帮我调下程序,谢谢啦!我的QQ:79422195,请标注“电子工程世界DMA” 我使用cc2530的DMA channel 1将接收到的串口(uart0)数据存储在uartRxBuf内,程序如下。在中断函数 ...…

查看全部问答>