输出比较简介

OC(Output Compare)输出比较
输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
每个高级定时器和通用定时器都拥有4个输出比较通道。基本定时器是没有的。
高级定时器的前3个通道额外拥有死区生成和互补输出的功能。

PWM简介

PWM(Pulse Width Modulation)脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
PWM参数:
频率 = 1 / TS 占空比 = TON / TS 分辨率 = 占空比变化步距
在这里插入图片描述

输出比较通道

在这里插入图片描述
红色框图圈出的部分就是输出比较电路。捕获比较寄存器就是CCR(CC是捕获/比较的意思,R是Register)捕获/比较寄存器是输入捕获和输出比较共用的,当使用输入捕获时,他就是捕获寄存器,当时用输出比较时,它就是比较寄存器。输入捕获就是左边输入捕获,输出比较是与上面的比较。

输出比较这里这块电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的一个值,当CNT大于CCR、小于CCR、等于CCR时,输出就会输出对应的置1或置0。这样就可以输出一定频率的占空比的PWM波形

通用定时器输出比较通道

在这里插入图片描述
在这里插入图片描述
图示就是通用定时器输出比较的结构。就是通用定时器框图框出来的那部分,左边就是CNT与CCR比较的结果。右边是输出比较电路,最后通过TIM_CH1输出到GPIO引脚上。上图只是显示了通道1的结构。最左边是CNT计数器和CCR1第一路的捕获/比较寄存器。当CNT>CCR1或者CNT = CCR1的时候就会向输出模式传一个信号。然后输出模式控制器就会改变它输出OC1REF的高低电平(REF时reference的缩写,意思是参考信号),OC1REF就是框图中输出控制左边的信号。然后上面还有个ETRF输入,这是定时器的一个小功能。接着REF信号可以前往主模式控制器,你可以把这个REF映射到主模式的TRGO输出上去,但主要还是下面那一路(极性选择部分),给这个寄存器(CC1P)写0,信号会往上走,信号电平不翻,写1的话,信号就会往下走,信号通过一个非门取反,输出信号就是输入信号高低电平反转的信号。再往后就是输出使能电路就是选择要不要输出,最后就是OC1引脚,这个引脚就是CH1的通道的引脚。在引脚定义表中可以知道具体是哪个GPIO。

输出模式控制器工作原理

输出模式控制器输入时CNT和CCR的大小关系,输出时REF的高低电平,控制器可以选择多种模式来更加灵活地控制REF输出。模式可以通过它下方的寄存器(OC1M)来配置。
输出模式控制器的执行逻辑如下:
在这里插入图片描述
1:第一个模式是冻结,描述的实CNT=CCR时,REF保持原态,比如在输出PWM波,突然想暂停一会儿输出,就可以设置成这个模式,高低电平维持在暂停时刻。因为都是条件满足时才会执行相应的逻辑操作,不满足时电平又不变。所以可以理解为CNT与CCR无效。
2:匹配时值置有效电平(可以简单的将有效电平理解为高电平,将无效电平理解为低电平),匹配时值置无效电平,匹配时值电平翻转,就是CNT=CCR时,REF分别置高电平,低电平,电平翻转,在匹配时值电平翻转模式下,可以产生一个占空比为50%的方波。但是有效和无效的那两个操作都是一次性的,操作完后就没变化了。
3:强制为无效电平和强制为有效电平两个模式和冻结模式相似,如果想暂停波形输出,并且在暂停期间保持低电平或者高电平,就可以设置这两个模式。
4:PWM2就是PWM1的取反。最常用的就是PWM1模式。

PWM基本结构

PWM模式是怎样输出的呢?
在这里插入图片描述

首先左上角这里,是时基单元和运行控制的部分,再左边是时钟源选择,这里省略了,配置好时基单元,这里的CNT就可以开始不断地自增运行。因为是PWM输出,所以就不需要配置中断了。
通用定时器输出比较单元电路总共有四路。最开始,是CCR捕获/比较寄存器,CCR是我们自己设置的,CNT不断自增运行同时他俩还在不断的比较,后面就是输出模式控制器,以PWM1模式为例,在右上角的图中,在CNT还未计数到CCR时,置高电平,大于CCR时,置低电平,当计数到ARR然后归零时,又置高电平,往复循环,且占空比随CCR改变,如果CCR设置的高,那么输出的占空比就大,CCR设置的低一些,占空比就小一些,最后经过极性选择和输出使能就可以输出了。

PWM参数计算

在这里插入图片描述

高级定时器输出比较通道

在这里插入图片描述

图示就是高级定时器前三个通道的输出比较部分电路。将红色框中的去掉就和通用定时器一样了。多加一部分电路主要是为了驱动三相无刷电机。右边是OC1和OC1N,两个引脚去控制外面的电路。外面接推挽电路,推挽电路的上下管的导通和断开,如果有两个推挽电路就可以构成H桥电路,可以控制直流电机的正反转。如果有三个推挽电路就可以控制三相无刷电机。如果用单片机控制电路,就必须让两个引脚输出相反,所以OC1和OC1N是互补输出,让MOS管一个导通,另一个断开。但是这个操作是瞬间的,如果器件不好,会存在同时导通的情况,此时就会短路,为了避免这个情况就有了死区生成电路,会在一个管关闭的时候延迟一小段时间,再导通另一个管。

pwm配置代码

配置PWM只需要将PWM配置模块打通即可
在这里插入图片描述
打开库函数
在这里插入图片描述
OC就是output compare的意思,就是输出比较,就是配置图中输出比较模块的。输出比较总共4个,4个函数,一个函数配置一个单元。参数TIMx选择定时器,第二个结构体,就是输出比较的那些参数。

void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

这个函数用来给输出比较结构体赋一个默认值的。

上面几个函数其实就已经配置好输出比较的功能了。

void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);

如果在运行中想要暂停输出波形并且强制输出高或者低电平。但是强制输出高低电平和输出占空比是100和0是一样的。所以用的不多

void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

这四个函数是用来配置CCR寄存器的预装功能的,这个预装功能就是影子寄存器。

void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);

上面这四个函数是用来配置快速使能的,介绍在手册中单脉冲模式那一节。

void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);

上面四个函数在手册中外部事件时清除REF信号有介绍。

void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);

上面这些函数是用来单独设置输出比较的极性的,带N的就是高级定时器里互补通道的配置,OC4没有互补通道,所以没有OC4N函数。这些函数可以设置极性,但是在结构体初始化的那个函数也可以设置极性。这两个地方设置极性的作用是一样的,只不过用结构体是一起初始化的。

void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);

上面两个函数用来单独修改输出使能参数的。

void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);

选择输出比较模式,用来单独输出比较模式的函数

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);

这四个单独用来更改CCR寄存器值的函数。在运行的时候更改占空比就使用这几个函数。

void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

这个函数是用来使能高级定时器输出PWM的,仅限高级定时器使用,需要调用这个函数使能主输出,否则PWM将不能正常输出。

代码

首先配置时基单元部分。但是不需要配置中断,所以也不需要打开NVIC了。

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	TIM_InternalClockConfig(TIM2);//上电后默认就是内部时钟,不写这个函数也行
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//ARR重装寄存器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,这是高级定时器的,我们配置TIM2,所以直接给0
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

然后配置输出比较通道,需要初始化哪个通道就调用哪个函数,不同的通道对应的GPIO口也是不一样的。所以要按照GPIO口的需求。我们使用PA0口,对应的第一个输出比较通道,所以调用TIM_OC1Init()
参数第一个是TIMx,我们使用的是TIM2,所以给TIM2.第二个参数是一个结构体的地址。但是结构体的参数比较多,我们只需要写出我们需要的参数即可。

但是不给结构体赋初始值,后面代码如果直接使用了高级定时器,初始化函数直接修改TIM2为TIM1,但是高级定时器的结构体成员没有赋初始值,此时就会出错。所以我们要给结构体赋初始值,但是又不一一列举出来。所以此时就使用了TIM_OCStructInit()。赋完初始值后直接修改我们需要的参数。

当配置好各种参数后,我们要知道PWM是借用IO口才能输出,但是TIM2的OC1通道借用了哪个GPIO呢?
在引脚定义表中可以看到
在这里插入图片描述
默认复用功能就是片上外设的端口和GPIO的连接关系。可以看到TIM2_CH1_ETR和PA0一起的,这说明TIM2的ETR引脚通道1的引脚都是借用了PA0引脚的位置。所以如果要使用TIM2的OC1也就是CH1通道输出PWM就需要使用PA0引脚,不能使用其他的引脚。同样的TIM2的CH2只能在PA1引脚输出。其他外设也同理。但是STM32有重定义或者叫重映射,假如我们要使用USART2的TX引脚,又要使用TIM2的CH3通道,就可以将TIM2_CH3换到PB10引脚。但是这样使用必须能在重映射中找到。配置重映射是用AFIO来完成的。

所以,输出比较模块的配置如下。

	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 = 50//CCR的值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);

此时还需要将GPIO也配置一下,因为GPIO我们需要配置输出模式,如果是输出的话,PWM就无法输出了。因为只有配置为复用推挽输出,引脚控制权才来自片上外设,所以此时要使用复用推挽输出。

所以最终要配置一个频率为1kHz,占空比为50的PWM波形,代码如下。

void PWM_Init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	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;
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//ARR重装寄存器的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//PSC预分频器的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,这是高级定时器的,我们配置TIM2,所以直接给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 = 50;//CCR的值
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	
	TIM_Cmd(TIM2, ENABLE);//启动定时器
}

后面再修改可以直接使用

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);

这样修改CCR的值来改变占空比。

引脚重映射

如果要使用引脚重映射,就需要开启引脚重映射的时钟,打开AFIO时钟。RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO引脚重映射也要打开
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
第一个参数,参数有很多,都是重映射方式。每个方式对应的重映射关系可以在手册中找到。找到TIM2复用功能重映像这个表,里面有四种重映射的方式,第一种,没有重映射,TIM2的四个通道对应的引脚不变。第二种,部分引脚重映射,就可以将前两个通道映射到PA15和PB3,剩下的一样的原理。
在这里插入图片描述
然后看函数的参数就有TIM2的重映射方式,如果都不使用就没有重映射。

在这里插入图片描述
我们可以使用重映射方式1,将PA0映射到PA15。

GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);

使用上述代码就能将PA0复用到PA15了。
但是PA15上电默认是调试端口JTDI,不是GPIO功能。如果需要这个端口是普通的GPIO或者复用功能(定时器通道),就需要关闭调试端口的复用。关闭它也是使用这个函数。
参数可以给
在这里插入图片描述
SWJ就是SWDJTAG两种调试方式,第一个NoJTRST就是解除JTRST引脚(PB4)的复用功能。第二个JTAGDisable,就是解除JTAG调试端口的复用,此时PA15PB3PB4这三个端口变回GPIO。第三个SWG_Disable,就是将SWDJTAG的调试端口全部解除。在引脚定义中,就是5个引脚全部变回普通的GPIO,此时没有调试功能,此时也就不能通过STLINK下载程序了。
所以此时为了解除复用就使用第二个参数JTAGDisable

所以当需要这几个IO口当普通GPIO来配置的话,就需要先打开AFIO,然后改变GPIO的映射。如果需要复用功能重映射就再加上对应的参数再执行一次就行。

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
	
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

什么时候该打开AFIO时钟呢?
只要配置了AFIO相关的寄存器都需要开启AFIO时钟。

AFIO 相关的寄存器有:

  1. 事件控制寄存器 (AFIO_EVCR)
  2. 复用重映射和调试 I/O 配置寄存器 (AFIO_MAPR)
  3. 外部中断配置寄存器 1 (AFIO_EXTICR1)
  4. 外部中断配置寄存器 2 (AFIO_EXTICR2)
  5. 外部中断配置寄存器 3 (AFIO_EXTICR3)
  6. 外部中断配置寄存器 4 (AFIO_EXTICR4)

上面我们配置的就对应了复用重映射调试 I/O 配置寄存器

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐