[应用相关] STM32定时器输出比较功能

[复制链接]
41|11
Zuocidian 发表于 2026-6-10 20:36 | 显示全部楼层 |阅读模式
1、输出比较简介:
OC(Output Compare)输出比较
输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形(CC(Capture/Compare)是捕获/比较的意思,R是Register,寄存器的意思。)所以,CCR是捕获/比较寄存器的意思,这个寄存器是输入捕获和输出比较共用的,当使用输入捕获时,他就是捕获寄存器,当使用输出比较时,他就是比较寄存器
每个高级定时器和通用定时器都拥有4个输出比较通道 高级定时器的前3个通道额外拥有死区生成和互补输出的功能
2、输出比较模式:

843036a279bd094ba8.png

539506a279bc9db318.png

3、PWM简介
PWM(Pluse Width Modulation) 脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
PWM参数:频率 = 1 / Ts            占空比 = Ton / Ts           分辨率 = 占空比变化步距

331776a279431331de.png

4、死区发生器:
规则:

当 OC1REF 从高→低跳变时:
OC1_DT(主通道):立刻跟着变低
OC1N_DT(互补通道):延迟一段时间再变高(这就是死区)
效果:两个信号在切换时,有一段「都为低」的空白期,不会同时为高。
当 OC1REF 从低→高跳变时:
OC1_DT(主通道):延迟一段时间再变高(死区)
OC1N_DT(互补通道):立刻跟着变低
效果:同样是「都为低」的空白期,避免同时导通
例图:

329166a2794288b036.png

5、定时器的结构:
通用:

940216a279423ed5e3.jpg

拥有四个通道,每个通道的结构如下图所示:

88866a27941ca3f4d.png

流程:
最左边的输入部分
当CNT < CCR1时是原本波形,不用做改变。
当CNT = CCR1时是波形改变的一个临界点。
当CNT > CCR1时,波形发生改变。
ETRF : 外部触发信号,可用于同步或触发输出模式控制器,让外部事件影响输出行为。
输出模式控制器就是配置TIMx_CCMR1来配置输出模式的。
他会输出oc1ref信号
去向1:至主模式控制器。
这部分功能叫 定时器的主模式触发(TRGO)。OC1REF 是定时器内部的比较参考信号,它的跳变(比如 CNT 和 CCR1 匹配时的电平翻转)不只是用来驱动输出引脚,还可以作为内部触发事件,用来:
触发其他定时器(从模式定时器)开始计数
触发 ADC/DAC 外设的转换
实现多定时器之间的同步、级联
去向2:进入输出极性控制电路。
它的功能主要是控制CC1P寄存器来控制信号要不要反转。
CC1P = 0:选择输入0的信号,oc1ref会直接输出(原信号)。
CC1P = 1:选择输入1的信号,也就是经过反相器的oc1ref,输出反相后的信号
最后的信号会经过输出使能电路,它的作用是通过配置CC1E寄存器来决定信号最终能不能达到OC1引脚
CC1E=0:通道输出被禁用,引脚不受定时器控制(由 GPIO 配置决定)
CC1E=1:通道输出使能,前面处理好的信号会送到OC1引脚输出
高级:

269526a279413ed596.jpg

也是拥有四个通道,每个通道的结构图如下:

456716a27940cda9e1.png

流程:
最左边的输入部分(跟通用定时器一样)
当CNT < CCR1时是原本波形,不用做改变。
当CNT = CCR1时是波形改变的一个临界点。
当CNT > CCR1时,波形发生改变。
ETRF : 外部触发信号,可用于同步或触发输出模式控制器,让外部事件影响输出行为。
输出模式控制器就是配置TIMx_CCMR1来配置输出模式的。(跟通用定时器一样)
输出oc1ref信号
去向1:直接进入3输入多路选择器。
去向2:进入死区发生器。输出两路互补信号,并且对两路互补信号的跳变沿做掩饰错开(避免半桥上下路同时导通),随后两路信号分别进入主通道多路选择器和互补通道多路选择器
3输入多路选择器选择完信号后进入极性控制电路。
随后输出使能电路控制是主信号输出还是互补信号输出
6、代码:
总代码:
void PWM_Init(void)
{
        //开启TIM2时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

        //开启GPIO外设时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
       
//        //初始化AFIO
//        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//        //引脚重映射
//        GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);   //把A0重映射到A15
//        GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);   //因为重映射的A15口是调试端口,需要把它变成GPIO口
       
        GPIO_InitTypeDef GPIOA_InitStructure;
        GPIOA_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIOA_InitStructure.GPIO_Pin = GPIO_Pin_0;
        GPIOA_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
       
        GPIO_Init(GPIOA, &GPIOA_InitStructure);
       
        //初始化TIM2
        TIM_InternalClockConfig(TIM2);
       
        TIM_TimeBaseInitTypeDef TIM_TimInitStructure;
        TIM_TimInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
        TIM_TimInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimInitStructure.TIM_Period = 100 - 1;
        TIM_TimInitStructure.TIM_Prescaler = 720 - 1;
        TIM_TimInitStructure.TIM_RepetitionCounter = 0;
       
        TIM_TimeBaseInit(TIM2, &TIM_TimInitStructure);
       
        TIM_ClearFlag(TIM2, TIM_IT_Update);
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);  //开启更新中断的使能位,只有配置了这一位,定时器才可以把中断请求发出来
       
        //初始化输出比较单元
        TIM_OCInitTypeDef TIM_OCInitStructure;
//        TIM_OCInitStructure.TIM_OCIdleState = ;  //高级定时器才使用
//        TIM_OCInitStructure.TIM_OCNIdleState = ;
//        TIM_OCInitStructure.TIM_OCNPolarity = ;
//        TIM_OCInitStructure.TIM_OutputNState = ;
        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);
}

void PWM_SetCompare1(uint16_t Compare)
{
        TIM_SetCompare1(TIM2, Compare);
}



详细解答:
首先是要打开TIM2和GPIO外设时钟并且初始化GPIO口(不过多赘述了)
        //开启TIM2时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

        //开启GPIO时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

        GPIO_InitTypeDef GPIOA_InitStructure;
        GPIOA_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIOA_InitStructure.GPIO_Pin = GPIO_Pin_0;
        GPIOA_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
       
        GPIO_Init(GPIOA, &GPIOA_InitStructure);



随后就是初始化TIM2了(上一篇讲了)
        //初始化TIM2
        TIM_InternalClockConfig(TIM2);  //给定时器2配置内部时钟
       
        TIM_TimeBaseInitTypeDef TIM_TimInitStructure;
        TIM_TimInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;  //定时器时钟分频系数
        TIM_TimInitStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式
        TIM_TimInitStructure.TIM_Period = 100 - 1;  //自动重装值ARR
        TIM_TimInitStructure.TIM_Prescaler = 720 - 1;  //预分频器PSC
        TIM_TimInitStructure.TIM_RepetitionCounter = 0;  //重复计数
       
        TIM_TimeBaseInit(TIM2, &TIM_TimInitStructure);
       
        TIM_ClearFlag(TIM2, TIM_IT_Update);
        TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);  //开启更新中断的使能位,只有配置了这一位,定时器才可以把中断请求发出来




时基单元的配置:
        //初始化输出比较单元
        TIM_OCInitTypeDef TIM_OCInitStructure;
//        TIM_OCInitStructure.TIM_OCIdleState = ;  //高级定时器才使用
//        TIM_OCInitStructure.TIM_OCNIdleState = ;
//        TIM_OCInitStructure.TIM_OCNPolarity = ;
//        TIM_OCInitStructure.TIM_OutputNState = ;
        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);  //OC1通道



TIM_OCInitTypeDef的成员:(只列举通用定时器所使用的)

TIM_OCMode:配置输出比较的模式(配置红框圈出来的)

800766a2793b772316.png

TIM_OCPolarity:设置输出比较的极性
先是他的可用的值
#define TIM_OCPolarity_High  //高电平有效
#define TIM_OCPolarity_Low  //低电平有效
对应图中的位置(红框圈出来的)

809966a2793b00a6b9.png

TIM_OutputState:设置使能输出,配置图中的使能输出电路。

354076a2793a9d9bb8.png

TIM_Pulse:设置CCR
7、补充:
        有的时候我们所选择的GPIO口会被占用,但是一个OC通道对应一个GPIO口。所以官方给了我们一种重映射的方法,就是将原来的GPIO口重映射到别的GPIO口上。

重映射方法:

打开AFIO的外设时钟(在APB2总线上)
使用重映射函数
代码如下:

        //打开AFIO时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
        //引脚重映射
        GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);   //把A0重映射到A15


注意:我们重映射时,是不能随便重映射的。官方给了我们一张芯片引脚定义图(我把它放在最后了),上面记录了引脚编号与位置,主功能与复用功能,特殊功能引脚等。我们需要注意的是,我们重映射的引脚是否为GPIO引脚

映射规则:

33106a27939d41934.png

GPIO_PinRemapConfig()参数1:

165746a27939881754.png

比如我们的代码中是TIM2的OC1通道,OC1通道对应的是A0口。A0口重映射到A15口。如下图所示

840196a27939539643.png

571086a279390ad3c3.png

但是PA15上电后默认是调试端口JTDI,所以要再关闭其调试端口的复用

871566a27938a45b28.png

图中的PA13,PA14,PA15,PB3,PB4都是调试端口,PA13和PA14是JTAG和SWD调试,PA15,PB3和PB4就只有JTAG调试。

我们解除调试要再一次调用GPIO_PinRemapConfig()函数。

其中的参数1:

730716a27938462480.png

注意:第三个参数GPIO_Remap_SWJ_Disable要慎用。后果:SWD 下载 / 调试永久失效,芯片只能通过串口 ISP 烧录程序。

以下是解除调试的代码:

        //打开AFIO时钟
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
        //引脚重映射
        GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);   //把A0重映射到A15
        GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);   //因为重映射的A15口是调试端口,需要把它变成GPIO口


芯片引脚图:

464736a2793761d4f3.jpg

————————————————
版权声明:本文为CSDN博主「沉淀的晴天」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2503_91468386/article/details/160970408

公羊子丹 发表于 2026-6-11 09:28 | 显示全部楼层
讲解得特别细致,从原理到代码一步步拆解,新手入门定时器输出比较太友好了。提醒下,复用引脚一定要记得开启AFIO时钟哦。
周半梅 发表于 2026-6-11 09:28 | 显示全部楼层
我之前踩过重映射的坑,PA15用作TIM2_CH1时,只关JTAG就行,千万别彻底关闭SWD,不然后续下载程序都成问题。
帛灿灿 发表于 2026-6-11 09:29 | 显示全部楼层
想问下楼主,PWM模式1和模式2切换的时候,除了修改配置寄存器,还需要改动极性相关的参数吗?
童雨竹 发表于 2026-6-11 09:30 | 显示全部楼层
死区时间这块讲得很明白,做电机驱动、半桥电路一定要用上,能彻底避免上下管直通烧毁器件,实用性拉满。
万图 发表于 2026-6-11 09:31 | 显示全部楼层
实测了你贴里的TIM2输出代码运行正常,建议调试时先用示波器抓波形,直观判断频率和占空比是否符合预期。
Wordsworth 发表于 2026-6-11 09:32 | 显示全部楼层
补充个小经验:通用定时器没有死区和互补输出,要是需要两路互补PWM,直接选高级定时器会省事很多。
Bblythe 发表于 2026-6-11 09:33 | 显示全部楼层
哈哈很多新手分不清CCR和ARR寄存器,简单记:ARR决定PWM频率,CCR专门用来调节占空比,很好区分。
Pulitzer 发表于 2026-6-11 09:35 | 显示全部楼层
我发现不少人初始化时漏掉TIM_OCStructInit,容易出现参数残留问题,建议每次配置输出比较都先调用这个函数。
Uriah 发表于 2026-6-11 09:35 | 显示全部楼层
除了软件修改CCR值调占空比,也可以结合中断动态调整,做呼吸灯、调速类项目用这个方法特别灵活。
Clyde011 发表于 2026-6-11 09:36 | 显示全部楼层
引脚重映射表格标注得很清楚,提醒下36脚的STM32F103封装是不支持TIM2重映射的,选型和接线时多留意这点。
MessageRing 发表于 2026-6-11 11:29 | 显示全部楼层
搞定时器输出比较,注意看CCR和ARR配置,频率和占空比直接相关,调试时示波器是好朋友。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

260

主题

665

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部
0