一、输出比较简介:
只有高级定时器和通用寄存器才有输入捕获/输出比较电路,他们有四个CCR(捕获/比较寄存器),共用一个CNT(计数器),而输出比较功能是用来输出PWM波形的。
红圈部分就是输出比较电路,其中CCR(捕获/比较寄存器)是输入捕获和输出比较共用的,输入捕获和输出比较不能同时进行,当我们进行输出比较时,它就是比较寄存器,我们可以给这个寄存器设定一个值,然后CNT计数器就会不停和这个值进行比较,根据比较结果会输出不同的电平信号(通过输出比较控制器控制,下文讲解),由此可以产生PWM信号,如下图。
二、PWM简介:
PWM的本质就是一种方法(PWM波形是一种数字信号),通过输出一段变化的波形,这个信号是一个脉冲信号,因为它只有高电平(这是逻辑电平由控制器决定,32是3.3v而51是5v)和低电平(0),而高电平就是脉冲信号,其所占整个周期的比例就是脉冲宽度,通过调整脉冲宽度就能够得到不同的模拟信号,例如右图得到一个近似的正弦信号。而任何信号都可以看作是由一系列的正弦信号合成的。
PWM(Pulse Width Modulation) 是一种模拟信号的数字化处理方式,通过微处理器的数字输出来对模拟电路进行控制。它通过对一系列脉冲的宽度进行调制,来等效地获得所需要的波形(含形状和幅值),从而达到调整电压和频率的目的。PWM广泛应用于从测量、通信到功率控制与变换的许多领域中,特别在电机控制中表现出色。
-
频率
定义:PWM频率是指1秒钟内信号从高电平到低电平再回到高电平的次数,即一秒钟内PWM的周期数。频率越快,其模拟的信号越平稳。 -
占空比
定义:占空比是一个脉冲周期内,高电平的时间与整个周期时间的比例。占空比等效为PWM波形等效出来的模拟信号的电压的大小,占空比越大,模拟信号的越趋近于高电平,反之就是趋近于低电平,等效关系一般是线性的。例如高电平是5v,占空比是50%,那模拟信号的电压就近似为2.5v。
单位:%(0%-100%) -
分辨率
就是例如,占空比以1% 2% 3% 4%…99% 100%这样的情况跳变,那么占空比的变化步距就是1%,因为上述占空比每一次变化就是加1%,这种变化步距越小,说明变化的越细腻。
总结:
- 1.PWM是一种方法,采用这个方法输出PWM波形是为了使数字输出端口(只能输出高电平和低电平两种,用于控制电机就是只能让它转或者停止)输出模拟信号,这样就能够实现电机调速、控制舵机的转动角度等等。
- 2.输出比较电路可以说是实现PWM的工具:
(1)如果CRR捕获/比较寄存器的值(红线),ARR自动重装器的值(下图黄线),蓝线就是计数器值的变化,那么黄线和红线之间的差距越小,低电平所占时间就会越短,占空比就越大,通过跳帧占空比,如果是电机,占空比越大转速越快。
(2)占空比可以看作CRR的值比ARR的值。注意这里到30就已经是低电平了,所以高电平的范围是(0~29)30个数。
(3)我们如果让CCR的值每次操作加1,那么占空比每次就加1%,分辨率就是1%。
三、通用定时器的输出比较模块如何输出PWM波形:
上图对应的就是下图电路,红色部分输出比较结果OC1REF(reference参考信号),蓝色部分根据比较结果输出比较信号OC1,最后通过黄色部分TIMx_CH1通道输出到GPIO引脚上。
1.CNT和CRR两个寄存器的值进行比较之后,其比较结果进入输出模式控制器,然后根据结果输出模式控制器会输出相应的电平OC1_ref
2.信号OC1_ref分别可以进入两条支路,一路进入主模式控制器就能够作为触发输出TRGO输出给其他定时器或者DA/DC转换
3.另一路就进入极性选择器TIMx_CCER,极性选择器置0则信号走上一路不做变换直接输出到输出使能电路,如果置1则让它走下支路可以使信号OC1_ref经过一个非门电路将其反转,高变低,低变高
4.然后再控制输出使能让其通过OC1引脚输出到GPIO引脚上,至于是哪个GPIO可以看引脚定义图
(1)输出比较控制器执行逻辑:
通过配置下图红圈的寄存器可以选择不同的模式:
四、H桥电路:
这是我在抖音搜索的,而输出比较电路一般就接一个这样的电路,这个能够实现电机的正转和反转,中级的圆圈M就代表电机。
1. 当Q1和Q4导通,当Q2和Q3截止,那么电流从电机的正极流入负极流出,电机正转
2. 当Q2和Q3导通,当Q1和Q4截止,那么电流从电机的负极进入正极流出,电机反转
五、高级定时器的输出比较电路:
高级定时器的输出比较电路比通用定时器的输出比较电路多了一个死区生成电路,之后会讲解这个电路的作用。
首先,这个电路一般会接一个H桥电路
(1)死区生成电路的作用:
- 由于如果接H桥电路,那么对于H桥电路的半桥就是左半边或者由半边,一个MOS管导通另一个MOS管就必须截止,如果同时导通或者截止就会出现问题,那么死区生成电路就是产生一段时间的死区,死区生成电路中的“死区”通常指的是在输入信号进入某个特定范围(即死区)时,电路的输出电压为零;当输入信号脱离这个范围时,电路的输出电压会随输入信号的变化而变化。这个特定范围就是死区。 所以为了防止在半桥电路中,上面MOS管还没完全关断下面的MOS管就已经导通从而出现的两个MOS管同时导通的情况(会产生功率损耗,引起器件发热)就设置了一个死区生成电路,它会在上管关断之后延迟一段时间菜导通下管,保证半桥电路中保持一个MOS管导通另一个MOS管截止的状态。
六、舵机和电机:
(1)舵机:
在这里插入图片描述
(2)电机:
注意:
逻辑电平是由控制器决定,例如32单片机为3.3v,89c51单片机为5v
七、引脚定义:
- STM32F103C8T6芯片的引脚定义如下图,例如我们代码中要使用的TIM2_OC1就是被默认接在PA0口,但是如果我要用的两个输出通道都被定义在了同一个GPIO口上,就能够通过重映射的功能将其中一个换到另一个引脚上,但不是所有的输出通道都能更换,只有具有重定义功能也就是下图中最右边一列,例如红圈中的TIM2_CH3通道就可以输出到PA2引脚或者PB10引脚上,如果你要使用ADC12_IN2又要使用TIM2_CH3,那么就可以将TIM2_CH3重映射到PB10引脚。
八、复用开漏/推挽输出:
- 首先我们看一下普通的开漏/推挽输出,这个GPIO在这个模式下,其输出电平是根据输出数据寄存器的值决定的,也就是我们给这个输出数据寄存器写什么值,对应就会输出什么电平。
- 再来看复用开漏/推挽输出模式,这个模式下输出数据寄存器和GPIO的输出控制模块是断开的,输出控制模块与单片机的外设连接,在我们这个示例中就是与TIM2_CH1连接,这时GPIO输出的电平由TIM2_CH1通道传输过来的电平信号决定。
九、引脚重映射:
(1)方法:
这里要用到AFIO,关于AFIO的库函数被包含在GPIO的文件中。
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
/**
* @brief Changes the mapping of the specified pin.
* @param GPIO_Remap: selects the pin to remap.
* This parameter can be one of the following values:
* @arg GPIO_Remap_SPI1 : SPI1 Alternate Function mapping
* @arg GPIO_Remap_I2C1 : I2C1 Alternate Function mapping
* @arg GPIO_Remap_USART1 : USART1 Alternate Function mapping
* @arg GPIO_Remap_USART2 : USART2 Alternate Function mapping
* @arg GPIO_PartialRemap_USART3 : USART3 Partial Alternate Function mapping
* @arg GPIO_FullRemap_USART3 : USART3 Full Alternate Function mapping
* @arg GPIO_PartialRemap_TIM1 : TIM1 Partial Alternate Function mapping
* @arg GPIO_FullRemap_TIM1 : TIM1 Full Alternate Function mapping
* @arg GPIO_PartialRemap1_TIM2 : TIM2 Partial1 Alternate Function mapping
* @arg GPIO_PartialRemap2_TIM2 : TIM2 Partial2 Alternate Function mapping
* @arg GPIO_FullRemap_TIM2 : TIM2 Full Alternate Function mapping
* @arg GPIO_PartialRemap_TIM3 : TIM3 Partial Alternate Function mapping
* @arg GPIO_FullRemap_TIM3 : TIM3 Full Alternate Function mapping
* @arg GPIO_Remap_TIM4 : TIM4 Alternate Function mapping
* @arg GPIO_Remap1_CAN1 : CAN1 Alternate Function mapping
* @arg GPIO_Remap2_CAN1 : CAN1 Alternate Function mapping
* @arg GPIO_Remap_PD01 : PD01 Alternate Function mapping
* @arg GPIO_Remap_TIM5CH4_LSI : LSI connected to TIM5 Channel4 input capture for calibration
* @arg GPIO_Remap_ADC1_ETRGINJ : ADC1 External Trigger Injected Conversion remapping
* @arg GPIO_Remap_ADC1_ETRGREG : ADC1 External Trigger Regular Conversion remapping
* @arg GPIO_Remap_ADC2_ETRGINJ : ADC2 External Trigger Injected Conversion remapping
* @arg GPIO_Remap_ADC2_ETRGREG : ADC2 External Trigger Regular Conversion remapping
* @arg GPIO_Remap_ETH : Ethernet remapping (only for Connectivity line devices)
* @arg GPIO_Remap_CAN2 : CAN2 remapping (only for Connectivity line devices)
* @arg GPIO_Remap_SWJ_NoJTRST : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST
* @arg GPIO_Remap_SWJ_JTAGDisable : JTAG-DP Disabled and SW-DP Enabled
* @arg GPIO_Remap_SWJ_Disable : Full SWJ Disabled (JTAG-DP + SW-DP)
* @arg GPIO_Remap_SPI3 : SPI3/I2S3 Alternate Function mapping (only for Connectivity line devices)
* When the SPI3/I2S3 is remapped using this function, the SWJ is configured
* to Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST.
* @arg GPIO_Remap_TIM2ITR1_PTP_SOF : Ethernet PTP output or USB OTG SOF (Start of Frame) connected
* to TIM2 Internal Trigger 1 for calibration (only for Connectivity line devices)
* If the GPIO_Remap_TIM2ITR1_PTP_SOF is enabled the TIM2 ITR1 is connected to
* Ethernet PTP output. When Reset TIM2 ITR1 is connected to USB OTG SOF output.
* @arg GPIO_Remap_PTP_PPS : Ethernet MAC PPS_PTS output on PB05 (only for Connectivity line devices)
* @arg GPIO_Remap_TIM15 : TIM15 Alternate Function mapping (only for Value line devices)
* @arg GPIO_Remap_TIM16 : TIM16 Alternate Function mapping (only for Value line devices)
* @arg GPIO_Remap_TIM17 : TIM17 Alternate Function mapping (only for Value line devices)
* @arg GPIO_Remap_CEC : CEC Alternate Function mapping (only for Value line devices)
* @arg GPIO_Remap_TIM1_DMA : TIM1 DMA requests mapping (only for Value line devices)
* @arg GPIO_Remap_TIM9 : TIM9 Alternate Function mapping (only for XL-density devices)
* @arg GPIO_Remap_TIM10 : TIM10 Alternate Function mapping (only for XL-density devices)
* @arg GPIO_Remap_TIM11 : TIM11 Alternate Function mapping (only for XL-density devices)
* @arg GPIO_Remap_TIM13 : TIM13 Alternate Function mapping (only for High density Value line and XL-density devices)
* @arg GPIO_Remap_TIM14 : TIM14 Alternate Function mapping (only for High density Value line and XL-density devices)
* @arg GPIO_Remap_FSMC_NADV : FSMC_NADV Alternate Function mapping (only for High density Value line and XL-density devices)
* @arg GPIO_Remap_TIM67_DAC_DMA : TIM6/TIM7 and DAC DMA requests remapping (only for High density Value line devices)
* @arg GPIO_Remap_TIM12 : TIM12 Alternate Function mapping (only for High density Value line devices)
* @arg GPIO_Remap_MISC : Miscellaneous Remap (DMA2 Channel5 Position and DAC Trigger remapping,
* only for High density Value line devices)
* @param NewState: new state of the port pin remapping.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
(1)第一个参数是重映射的模式,这个可以参考手册
如果我们想将TIM2_CH1_ETR从PA0改成PA15就可以选择第一个部分重映像或者完全重映像
那么就对应:
GPIO_PartialRemap1_TIM2 //TIM2 Partial1 Alternate Function mapping
或者
GPIO_FullRemap_TIM2 // TIM2 Full Alternate Function mapping
但是注意,PA15的主功能是作为调试端口JTDI,也就是上电之后其默认为调试端口JTDI,还需要先关闭它调试端口的复用,才能够让它作为普通的GPIO口或复用定时器通道。
(2) 关闭引脚调试功能:
使用的还是GPIO_PinRemapConfig函数:
根据下面这几个参数能够实现
* @arg GPIO_Remap_SWJ_NoJTRST : Full SWJ Enabled (JTAG-DP + SW-DP) but without JTRST
* @arg GPIO_Remap_SWJ_JTAGDisable : JTAG-DP Disabled and SW-DP Enabled
* @arg GPIO_Remap_SWJ_Disable : Full SWJ Disabled (JTAG-DP + SW-DP)
这几个参数对应的情况可以查看手册:
因此我们选择:
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
如果你把所有调试功能的引脚都关闭你就下载不了程序了。不会的不要这么干
十、参数计算:
推荐参数:
ARR = 20K + 1
PSC = 72 + 1
CCR = 500 ~ 2500 对应0° ~ 180°
//ARR自动重装器的值
TIM_TIM2_InitStructure.TIM_Period = 20000 - 1;//ARR
//PSC预分频器的值
TIM_TIM2_InitStructure.TIM_Prescaler = 72 - 1;//PSC
七、采用PWM输出信号控制电机程序实现:
总代码:
主函数在第七步
示例中只让舵机转到0°位置,理解后可以通过TIM_SetCompare1函数结合按键或者其他外设更改CCR的值从而实现不同角度的转换。
#include "stm32f10x.h" // Device header
//初始化舵机
void Steering_EngineInit(void)
{
//RCC打开TIM2、GPIO、AFIO的时钟
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的调试功能,之后TIM2_CH1就由PA0->PA15
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
//初始化GPIOA15:
GPIO_InitTypeDef GPIO_PA15_InitStructure;
GPIO_PA15_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_PA15_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_PA15_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_PA15_InitStructure);
//PB1:
GPIO_InitTypeDef GPIO_PB1_InitStructure;
GPIO_PB1_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_PB1_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_PB1_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_PB1_InitStructure);
//选择时钟源
TIM_InternalClockConfig(TIM2);
//配置时基单元
//初始化定时器2时基单元的结构体
TIM_TimeBaseInitTypeDef TIM_TIM2_InitStructure;
/*
TIM_ClockDivision 是这个结构体中的一个字段,用于设置定时器的时钟分频。
具体来说,它决定了定时器时钟(TIMxCLK)的频率与内部时钟(CK_INT)之间的关系。
TIM_CKD_DIV1 表示不进行分频,即 CK_INT = TIMxCLK。
换句话说,定时器的内部时钟频率与输入的定时器时钟频率相同。
*/
TIM_TIM2_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//计数器模式:选择向上计数模式
TIM_TIM2_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//ARR自动重装器的值
TIM_TIM2_InitStructure.TIM_Period = 20000 - 1;//ARR
//PSC预分频器的值
TIM_TIM2_InitStructure.TIM_Prescaler = 72 - 1;//PSC
//重复计数器的值,高级计数器才有
TIM_TIM2_InitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TIM2_InitStructure);
//配置输出比较单元CCR:
TIM_OCInitTypeDef TIM_OCInitStructure;
//给TIM2_CH1的初始化结构体先进行一次初始化把用不到的关于高级定时器的成员也初始化
//然后再对要用到的成员变量进行更改
TIM_OCStructInit(&TIM_OCInitStructure);
//输出比较模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//输出比较极性
//高极性 = 极性不反转;低极性反之,这里选的高极性
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;
//输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//CCR比较寄存器的值
TIM_OCInitStructure.TIM_Pulse = 0;//CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare(uint16_t compare)
{
TIM_SetCompare1(TIM2,compare);
}
实现步骤:
第一步:使用RCC开启外设的时钟
这里涉及GPIO、AFIO、TIM2三个外设。
//RCC打开TIM2、GPIO、AFIO的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
第二步:配置好时基单元还有时钟源选择
//选择时钟源
TIM_InternalClockConfig(TIM2);
//配置时基单元
//初始化定时器2时基单元的结构体
TIM_TimeBaseInitTypeDef TIM_TIM2_InitStructure;
/*
TIM_ClockDivision 是这个结构体中的一个字段,用于设置定时器的时钟分频。
具体来说,它决定了定时器时钟(TIMxCLK)的频率与内部时钟(CK_INT)之间的关系。
TIM_CKD_DIV1 表示不进行分频,即 CK_INT = TIMxCLK。
换句话说,定时器的内部时钟频率与输入的定时器时钟频率相同。
*/
TIM_TIM2_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//计数器模式:选择向上计数模式
TIM_TIM2_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//ARR自动重装器的值
TIM_TIM2_InitStructure.TIM_Period = 20000 - 1;//ARR
//PSC预分频器的值
TIM_TIM2_InitStructure.TIM_Prescaler = 72 - 1;//PSC
//重复计数器的值,高级计数器才有
TIM_TIM2_InitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TIM2_InitStructure);
第三步:配置输出比较单元
注意:
这里提醒一下输出使能部分:TIM_OCInitStructure.TIM_OutputState
和
TIM_OCInitStructure.TIM_OutputNState
的区别只有一个N,但是加了N的是高级定时器的部分,所以这里错了舵机会不动。
//配置输出比较单元CCR:
TIM_OCInitTypeDef TIM_OCInitStructure;
//给TIM2_CH1的初始化结构体先进行一次初始化把用不到的关于高级定时器的成员也初始化
//然后再对要用到的成员变量进行更改
TIM_OCStructInit(&TIM_OCInitStructure);
//输出比较模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//输出比较极性
//高极性 = 极性不反转;低极性反之,这里选的高极性
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCPolarity_High;
//输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//CCR比较寄存器的值
TIM_OCInitStructure.TIM_Pulse = 0;//CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
第四步:配置GPIO
把PWM对应的GPIO口初始化为复用推挽输出的配置:
因为这里我们使用的是PA15,而TIM2_CH1M默认是接在PA0,我们需要对引脚进行重映射。
//引脚重映射,选择部分重映射1
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
//关闭PA15的调试功能,之后TIM2_CH1就由PA0->PA15
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
//初始化GPIOA15:
GPIO_InitTypeDef GPIO_PA15_InitStructure;
GPIO_PA15_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_PA15_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_PA15_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_PA15_InitStructure);
第五步:运行控制,启动计数器
//启动定时器
TIM_Cmd(TIM2,ENABLE);
第六步:更改CCR值的函数TIM_SetCompare1
通过传给compare的值可以更改CCR的值
void PWM_SetCompare(uint16_t compare)
{
TIM_SetCompare1(TIM2,compare);
}
第七步:在主函数中调用
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Steering_Engine.h"
int main()
{
Steering_EngineInit();
PWM_SetCompare(500); //对应的是0度
while(1)
{
}
}