STM32单片机入门学习笔记——深入理解定时器TIM的工作原理

笔记整理自B站UP主江科大自化协教程《STM32入门教程-2023持续更新中》,所用单片机也为教程推荐单片机。

大致内容

第一部分:定时器基本定时的功能,定时器每隔这个时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,比如要做一个时钟、秒表或者使用一些程序算法的时候都需要用到定时中断这个功能

第二部分:定时器输出比较的功能,最常见的用途就是产生PWM波形,用于驱动电机等设备

第三部分:定时器输入捕获的功能,使用输入buhuo这个模块来实现测量方波频率的例子

第四部分:定时器的编码器接口,使用编码器接口能够更加方便地读取正交编码器的输出波形,在编码电机测速中,应用广泛

使用定时器的外部时钟,可以提供一个更加精准的时钟来计时或者也可以把外部时钟当做一个计数器,用来统计引脚上电平翻转的次数。

输出比较简介

OC(Output Compare)输出比较;IC(Input Compare)输入捕获;CC(Capture/Compare)输入捕获和输出比较的单元

输出比较可以通过比较CNT(计数器)与CCR捕获/比较寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形

每个高级定时器和通用定时器都拥有4个输出比较通道,高级定时器的前3个通道额外拥有死区生成和互补输出的功能

PWM简介

PWM的频率越快,那它等效模拟的信号就越平稳,不过同时性能开销就越大,一般来说PWM的频率都在几K~几十KHz

输出比较通道(通用定时器)

输出比较模式(输出模式控制器执行逻辑)

冻结模式:比如正在输出PWM波,想暂停一会儿输出,就可以设置成这个模式,一旦切换为冻结模式后,输出就暂停了,并且高低电平也维持为暂停时刻的状态,保持不变。

PWM基本结构

参数计算

PWM频率=计数器的更新频率CK_PSC / (PSC + 1) / (ARR + 1)第一部分提到过

PWM频率:Freq = CK_PSC / (PSC + 1) / (ARR + 1)

PWM占空比:Duty = CCR / (ARR + 1)

PWM分辨率:Reso = 1 / (ARR + 1)——占空比最小的变化步距,值越小越好

ARR越大,CCR的范围就越大,对应的分辨率就越大

输出比较通道(高级定时器)

死区生成和互补输出

右边红色部分是一个普通的推挽电路,如果有两个这样的推挽电路就构成了H桥电路,可以控制直流电机正反转;如果有三个这样的推挽电路就可以用于驱动三相无刷电机。

外围电路就是类似红色部分这样的,输出一高一低(互补)

OC1和OC1N两个互补的输出端口,分别控制上管和下管的导通和关闭

在切换上下管导通状态时,如果在上管关断的瞬间,下管立刻就打开,那可能会因为器件的不理想,上管还没有完全关断,下管就已经导通了,出现了短暂的上下管同时导通的现象,这会导致功率损耗,引起器件发热。所以为了避免这个问题,就有了死区生成电路,它会在上管关闭的时候延迟一小段时间再导通下管,这样就可以避免上下管同时导通的现象了。

舵机简介

大概执行逻辑:PWM信号输入到控制板,给控制板一个指定的目标角度,然后电位器检测输出轴的当前角度,如果大于目标角度电机就会反转,如果小于目标角度电机就会正转,最终使输出轴固定在指定角度。

这里其实把PWM当成了一种通信协议

信号线也有其他颜色,要注意区分

直流电机及驱动简介

硬件电路

问题:模式状态的制动和停止有什么区别?

PWM驱动LED呼吸灯代码讲解(选择PA0TIM2通道1)

第一步:RCC开启时钟

第二步:配置时基单元(时钟源选择)

第三步:配置输出比较单元

第四步:配置GPIO(复用推挽输出)

第五步:运行控制,启动计数器

先了解一下TIM外设对应的库函数,第一部分也提到过一些

  • 【重要】TIM_OC1Init、TIM_OC2Init、TIM_OC3Init、TIM_OC4Init——结构体配置输出比较模块

  • OC:Output Compare输出比较

    TIM_ForcedOC1Config、TIM_ForcedOC2Config、TIM_ForcedOC3Config、TIM_ForcedOC4Config——配置强制输出模式

    TIM_OC2PreloadConfig、TIM_OC2PreloadConfig、TIM_OC3PreloadConfig、TIM_OC4PreloadConfig——配置CCR寄存器的预装功能(影子寄存器第一部分也介绍过:写入的值不会立即生效,而是在更新事件才会生效)

    TIM_OC1FastConfig、TIM_OC2FastConfig、TIM_OC3FastConfig、TIM_OC4FastConfig——配置快速使能的

    TIM_ClearOC1Ref、TIM_ClearOC2Ref、TIM_ClearOC3Ref、TIM_ClearOC4Ref——外部事件时清除REF信号

    单独设置输出比较的极性

    带N的就是高级定时器互补通道的配置,OC4没有互补通道

    单独修改输出使能参数

    TIM_SelectOCxM——选择输出比较模式,单独更改输出比较模式的函数

  • 【重要】

  • 单独更改CCR寄存器值的函数,更改占空比

    TIM_CtrlPWMOutputs——仅高级定时器使用,在使用高级定时器输出PWM时需要调用这个函数使能主输出,否则PWM将不能正常输出

    第一步——RCC开启时钟

    TIM2_CH1是复用在PA0引脚上

    主体代码
    //第一步开启RCC时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    第二步——配置GPIO

    对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚就需要使用复用开漏/推挽输出的模式,输出数据寄存器将被断开,输出控制权将转移给片上外设。

    这里片上外设连接的就是TIM2的CH1通道

    主体代码
    //配置GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    第三步——时钟源选择及配置时基单元

    时钟源选择:选择内部时钟

    主体代码
    //选择内部时钟
    TIM_InternalClockConfig(TIM2);

    初始化时基单元

  • 关于ARR/PSC/CCR这三个参数应该如何配置?

  • 假设现在要产生一个频率为1KHz,占空比为50%,分辨率为1%的PWM波形

    代入公式Freq = CK_PSC / (PSC + 1) / (ARR + 1)得

    PWM频率 = 计数器的更新频率 = 72MHz / (PSC + 1) / (ARR + 1) = 1KHz

    占空比 = CCR / (ARR + 1) = 0.5 = 50%

    分辨率 = 1 / (ARR + 1) = 1%

    解得

    ARR + 1 = 100 -> ARR = 100 – 1

    CCR = 50-> CCR = 50

    PSC + 1 = 720-> PSC = 720 – 1

    主体代码
    //初始化时基单元
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;   //ARR自动重装器的值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;   //PSC预分频器的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;   //重复计数器的值
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

    第四步——配置输出比较单元

    主体代码

    这里注意我们是先给结构体赋初始值,然后再更改其中我们需要的参数。

    CCR初始给0即可,后面会变化,产生呼吸灯的效果

    //初始化输出比较单元
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);  //给结构体赋初始值,再更改需要改的值
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;     //CCR
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);

    第五步——开启定时器

    主体代码
    //开启定时器
    TIM_Cmd(TIM2, ENABLE);

    第六步——设置CCR的值

    TIM_SetCompare1——用来单独更改通道1的CCR的值

    主体代码
    void PWM_SetCompare1(uint16_t Compare)
    {
        TIM_SetCompare1(TIM2, Compare);
    }

    源代码

    #include "stm32f10x.h"                  // Device header
    
    void PWM_Init(void)
    {
        //第一步开启RCC时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        
        //配置GPIO
        GPIO_InitTypeDef GPIO_InitStructure;
        //对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚
        //就需要使用复用开漏/推挽输出的模式,输出数据寄存器将被断开,输出控制权将转移给片上外设
        //这里片上外设连接的就是TIM2的CH1通道,
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        
        //选择内部时钟
        TIM_InternalClockConfig(TIM2);
        
        //初始化时基单元
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        //对72M进行7200分频,得到10K的计数频率,计10000个数,1s的时间
        TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;   //ARR自动重装器的值
        TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;   //PSC预分频器的值
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;   //重复计数器的值
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
        
        //初始化输出比较单元
        TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCStructInit(&TIM_OCInitStructure);  //给结构体赋初始值,再更改需要改的值
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = 0;     //CCR
        TIM_OC1Init(TIM2, &TIM_OCInitStructure);
        
        //开启定时器
        TIM_Cmd(TIM2, ENABLE);
    }
    
    /*
    关于ARR/PSC/CCR这三个参数应该如何配置?
    现在要产生一个频率为1KHz,占空比为50%,分辨率为1%的PWM波形
    代入公式Freq = CK_PSC / (PSC + 1) / (ARR + 1)得
    PWM频率 = 计数器的更新频率 = 72MHz / (PSC + 1) / (ARR + 1) = 1KHz
    占空比 = CCR / (ARR + 1) = 0.5 = 50%
    1 / (ARR + 1) = 1%
    解得
    ARR + 1 = 100   ->   ARR = 100 - 1
    CCR = 50        ->     CCR = 50
    PSC + 1 = 720    ->     PSC = 720 - 1
    */
    
    /*
    TIM_SetCompare1——用来单独更改通道1的CCR的值
    */
    
    void PWM_SetCompare1(uint16_t Compare)
    {
        TIM_SetCompare1(TIM2, Compare);
    }
    

    下面我们再讲一下IO口重定义的功能

    通过上图可知TIM2_CH1重定义在PA15引脚上

    GPIO_PinRemapConfig——引脚重映射配置

    如果想把PA0改到PA15,可以选择部分重映像1或者完全重映像

    GPIO_PartialRemap1_TIM2——部分重映像1

    GPIO_PartialRemap2_TIM2——部分重映像2

    GPIO_FullRemap_TIM2——完全重映像

    还有一个问题要注意PA15上电后已经默认复用为调试端口JTDI,如果想让它作为普通的GPIO或者复用定时器的通道,需要先关闭调试端口的复用

    SWJ:SWD和JTAG这两种调试方式

    GPIO_Remap_SWJ_NoJTRST(PB4)——解除JTRST引脚的复用

    GPIO_Remap_SWJ_JTAGDisable——解除JTAG调试端口的复用

    配置之后这三个端口PA15、PB3、PB4变为普通GPIO

    GPIO_Remap_SWJ_Disable——把SWD和JTAG的调试端口全部解除

    配置之后这五个端口变为GPIO,这个不要乱用,我们就是用STLINK下载程序的

    这张表也详细说明了上述几种情况

    总结

    如果想要PA15、PB3、PB4这三个引脚当做GPIO来使用

    主体代码
    //开启AFIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    //解除JTAG调试端口的复用功能
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

    如果想要重映射定时器或者其他外设的复用引脚

    主体代码
    //开启AFIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    //引脚重映射配置
    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);

    如果想要重映射的引脚正好是调试端口

    主体代码
    //开启AFIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    //引脚重映射配置
    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
    //解除JTAG调试端口的复用功能
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

    源代码

    #include "stm32f10x.h"                  // Device header
    
    如果需要重映射到PA15引脚,该如何配置?代码如下
    void PWM_Init(void)
    {
        //第一步开启RCC时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
        
        //引脚重映射配置
        GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
        //因为PA15正好是JTAG调试端口,所以需要解除JTAG调试端口的复用功能
        GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
        
        //配置GPIO
        GPIO_InitTypeDef GPIO_InitStructure;
        //对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚
        //就需要使用复用开漏/推挽输出的模式,输出数据寄存器将被断开,输出控制权将转移给片上外设
        //这里片上外设连接的就是TIM2的CH1通道,
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        
        //选择内部时钟
        TIM_InternalClockConfig(TIM2);
        
        //初始化时基单元
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        //对72M进行7200分频,得到10K的计数频率,计10000个数,1s的时间
        TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;   //ARR自动重装器的值
        TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;   //PSC预分频器的值
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;   //重复计数器的值
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
        
        //初始化输出比较单元
        TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCStructInit(&TIM_OCInitStructure);  //给结构体赋初始值,再更改需要改的值
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = 0;     //CCR
        TIM_OC1Init(TIM2, &TIM_OCInitStructure);
        
        //开启定时器
        TIM_Cmd(TIM2, ENABLE);
    }
    
    /*
    关于ARR/PSC/CCR这三个参数应该如何配置?
    现在要产生一个频率为1KHz,占空比为50%,分辨率为1%的PWM波形
    代入公式Freq = CK_PSC / (PSC + 1) / (ARR + 1)得
    PWM频率 = 计数器的更新频率 = 72MHz / (PSC + 1) / (ARR + 1) = 1KHz
    占空比 = CCR / (ARR + 1) = 0.5 = 50%
    1 / (ARR + 1) = 1%
    解得
    ARR + 1 = 100   ->   ARR = 100 - 1
    CCR = 50        ->     CCR = 50
    PSC + 1 = 720    ->     PSC = 720 - 1
    */
    
    /*
    TIM_SetCompare1——用来单独更改通道1的CCR的值
    */
    
    void PWM_SetCompare1(uint16_t Compare)
    {
        TIM_SetCompare1(TIM2, Compare);
    }
    

    PWM驱动舵机代码讲解(选择PA1TIM2通道2)

    对于同一个定时器的不同通道输出的PWM,它们的频率因为不同通道是共用一个计数器的,所以它们的频率必须是一样的,占空比由各自的CCR决定,所以占空比可以各自设定,由于计数器更新,所有PWM同时跳变,所以它们的相位是同步的。

  • 关于ARR/PSC/CCR这三个参数应该如何配置?

  • 舵机要求的周期是20ms,频率为1/20ms=50Hz

    PWM频率:Freq = CK_PSC / (PSC + 1) / (ARR + 1)

    72MHz / (PSC + 1) / (ARR + 1) = 50Hz

    可以设置PSC + 1 = 72

    ARR + 1 = 20K

    PWM占空比:Duty = CCR / (ARR + 1)

    CCR = 500对应0.5ms

    CCR = 2500对应2.5ms

    第一步——开启RCC时钟

    选择的引脚是PA1

    主体代码
    //第一步开启RCC时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    第二步——配置GPIO

    主体代码
    //配置GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    //对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚
    //就需要使用复用开漏/推挽输出的模式,输出数据寄存器将被断开,输出控制权将转移给片上外设
    //这里片上外设连接的就是TIM2的CH2通道,
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    第三步——时钟源选择及配置时基单元

    主体代码
    //选择内部时钟
    TIM_InternalClockConfig(TIM2);
    
    //初始化时基单元
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    //对72M进行7200分频,得到10K的计数频率,计10000个数,1s的时间
    TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;   //ARR自动重装器的值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;   //PSC预分频器的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;   //重复计数器的值
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

    第四步——配置输出比较单元

    这里通道选择的是CH2,所以最后函数选择TIM_OC2Init

    主体代码
    //初始化输出比较单元
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);  //给结构体赋初始值,再更改需要改的值
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    //CCR取值范围500~2500对应0.5ms~2.5ms
    TIM_OCInitStructure.TIM_Pulse = 0;     //CCR
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);

    第五步——开启定时器

    主体代码
    //开启定时器
    TIM_Cmd(TIM2, ENABLE);

    第六步——设置CCR的值

    主体代码
    void PWM_SetCompare2(uint16_t Compare)
    {
        TIM_SetCompare2(TIM2, Compare);
    }

    源代码

    #include "stm32f10x.h"                  // Device header
    
    void PWM_Init(void)
    {
        //第一步开启RCC时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        
        //配置GPIO
        GPIO_InitTypeDef GPIO_InitStructure;
        //对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚
        //就需要使用复用开漏/推挽输出的模式,输出数据寄存器将被断开,输出控制权将转移给片上外设
        //这里片上外设连接的就是TIM2的CH2通道,
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        
        //选择内部时钟
        TIM_InternalClockConfig(TIM2);
        
        //初始化时基单元
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        //对72M进行7200分频,得到10K的计数频率,计10000个数,1s的时间
        TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;   //ARR自动重装器的值
        TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;   //PSC预分频器的值
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;   //重复计数器的值
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
        
        //初始化输出比较单元
        TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCStructInit(&TIM_OCInitStructure);  //给结构体赋初始值,再更改需要改的值
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        //CCR取值范围500~2500对应0.5ms~2.5ms
        TIM_OCInitStructure.TIM_Pulse = 0;     //CCR
        TIM_OC2Init(TIM2, &TIM_OCInitStructure);
        
        //开启定时器
        TIM_Cmd(TIM2, ENABLE);
    }
    
    void PWM_SetCompare2(uint16_t Compare)
    {
        TIM_SetCompare2(TIM2, Compare);
    }
    

    可对比之前PWM驱动LED呼吸灯的代码,可见只有PSC/ARR/CCR、引脚、通道相关的地方改了一下,大部分都没变。

    PWM驱动直流电机代码讲解(选择PA2TIM2通道3)

    PWM和驱动LED部分代码类似,只是通道和引脚不同,这里我们直接给出源代码。

    #include "stm32f10x.h"                  // Device header
    
    void PWM_Init(void)
    {
        //第一步开启RCC时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        
        //配置GPIO
        GPIO_InitTypeDef GPIO_InitStructure;
        //对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚
        //就需要使用复用开漏/推挽输出的模式,输出数据寄存器将被断开,输出控制权将转移给片上外设
        //这里片上外设连接的就是TIM2的CH3通道,
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        
        //选择内部时钟
        TIM_InternalClockConfig(TIM2);
        
        //初始化时基单元
        TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
        TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        //对72M进行7200分频,得到10K的计数频率,计10000个数,1s的时间
        TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;   //ARR自动重装器的值
        TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1;   //PSC预分频器的值
        TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;   //重复计数器的值
        TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
        
        //初始化输出比较单元
        TIM_OCInitTypeDef TIM_OCInitStructure;
        TIM_OCStructInit(&TIM_OCInitStructure);  //给结构体赋初始值,再更改需要改的值
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = 0;     //CCR
        TIM_OC3Init(TIM2, &TIM_OCInitStructure);
        
        //开启定时器
        TIM_Cmd(TIM2, ENABLE);
    }
    
    void PWM_SetCompare3(uint16_t Compare)
    {
        TIM_SetCompare3(TIM2, Compare);
    }
    

    不同的地方如下(头文件不要忘记改):

    感谢抽出宝贵时间阅读的各位小读者们,创作不易,如果感觉有帮助的话,帮忙点个赞再走吧!你的支持是我创作的动力,希望能带给大家更多优质的文章。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32单片机入门学习笔记——深入理解定时器TIM的工作原理

    发表回复