TIM输出比较
输出比较简介
OC(Output Compare) 输出比较
输出比较可以通过比较CNT与CCR寄存器值的关系, 来对输出电平进行置1、置0或翻转的操作, 用于输出一定频率和占空比的PWM波形
每个高级定时器和通用定时器都有四个输出比较通道
高级定时器的前三个通道额外拥有死区生成和互补输出的功能
PWM简介
PWM(Pulse Width Modulation) 脉冲宽度调制
在具有惯性的系统中, 可以通过对一系列脉冲的宽度进行调制, 来等效地获得所需要的模拟参量, 常应用于电机控速等领域
PWM参数:
频率 = 1 / Ts
占空比 = Ton / Ts
分辨率: 占空比变化步距

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

CNT 与 CCR1比较后 输出对应信号
信号经过选择电路后, 从OC1输出
输出比较通道(高级定时器)

较通用定时器, 多了死区发生器和互补输出电路, 目的是驱动三相电机
输出比较模式
模式 | 描述 |
冻结 | CNT=CCR时,REF保持为原状态 |
匹配时置有效电平 | CNT=CCR时,REF置有效电平 |
匹配时置无效电平 | CNT=CCR时,REF置无效电平 |
匹配时电平翻转 | CNT=CCR时,REF电平翻转 |
强制为无效电平 | CNT与CCR无效,REF强制为无效电平 |
强制为有效电平 | CNT与CCR无效,REF强制为有效电平 |
PWM模式1 | 向上计数:CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平 向下计数:CNT>CCR时,REF置无效电平,CNT≤CCR时,REF置有效电平 |
PWM模式2 | 向上计数:CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平 向下计数:CNT>CCR时,REF置有效电平,CNT≤CCR时,REF置无效电平 |
PWM基本结构

橙线为 ARR(自动重装寄存器)中预装载的值, CNT计数到该值后置位并重新计数
蓝线是CNT(计数器)中的值
红线CCR(捕获/比较寄存器)设定的比较值
参数计算

PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CRR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
通用定时器基本结构

方便写程序查看
案例1: PWM驱动LED呼吸灯
硬件接线

TIM输出比较PWM模式使用
1.初始化PWM模式
void PWM_Init(void)
{
/* GPIO复用功能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
*/
// RCC使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置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的时钟源
TIM_InternalClockConfig(TIM2);
// 配置时基单元
TIM_TimeBaseInitTypeDef TimeBase_InitStructure;
TimeBase_InitStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频
TimeBase_InitStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数模式
TimeBase_InitStructure.TIM_Period=100-1; // ARR
TimeBase_InitStructure.TIM_Prescaler=720-1; // 预分频器
TimeBase_InitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, &TimeBase_InitStructure);
// 配置定时器输出比较单元
TIM_OCInitTypeDef OC1_InitStructure;
TIM_OCStructInit(&OC1_InitStructure);
OC1_InitStructure.TIM_OCMode=TIM_OCMode_PWM1; // 设置模式
OC1_InitStructure.TIM_Pulse=0; // CCR捕获/输出寄存器
OC1_InitStructure.TIM_OCPolarity=TIM_OCPolarity_High; // 设置有效电平
OC1_InitStructure.TIM_OutputState=TIM_OutputState_Enable; // 设置状态
TIM_OC1Init(TIM2, &OC1_InitStructure); // 重复计数定时器, 只有高级定时器有
// 开启时钟
TIM_Cmd(TIM2, ENABLE); // 容易漏掉
}
GPIO和时基单元的配置在之前已经讲过, 这里主要对**配置定时器输出比较单元(OC单元)**进行说明:
TIM_OCInitTypeDef结构体:
typedef struct
{
uint16_t TIM_OCMode; /*!< Specifies the TIM mode.
This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
uint16_t TIM_OutputState; /*!< Specifies the TIM Output Compare state.
This parameter can be a value of @ref TIM_Output_Compare_state */
uint16_t TIM_OutputNState; /*!< Specifies the TIM complementary Output Compare state.
This parameter can be a value of @ref TIM_Output_Compare_N_state
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
This parameter can be a number between 0x0000 and 0xFFFF */
uint16_t TIM_OCPolarity; /*!< Specifies the output polarity.
This parameter can be a value of @ref TIM_Output_Compare_Polarity */
uint16_t TIM_OCNPolarity; /*!< Specifies the complementary output polarity.
This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_Idle_State
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
@note This parameter is valid only for TIM1 and TIM8. */
} TIM_OCInitTypeDef;
作为通用定时器, 我们一般只需配置前面5个中除了TIM_OutputNState的四个参数, 其余用结构体初始化函数赋默认值即可
TIM_OCMode: 定时器输出比较单元的模式
/** @defgroup TIM_Output_Compare_and_PWM_modes
* @{
*/
#define TIM_OCMode_Timing ((uint16_t)0x0000)
#define TIM_OCMode_Active ((uint16_t)0x0010)
#define TIM_OCMode_Inactive ((uint16_t)0x0020)
#define TIM_OCMode_Toggle ((uint16_t)0x0030)
#define TIM_OCMode_PWM1 ((uint16_t)0x0060)
#define TIM_OCMode_PWM2 ((uint16_t)0x0070)
#define IS_TIM_OC_MODE(MODE) (((MODE) == TIM_OCMode_Timing) || \
((MODE) == TIM_OCMode_Active) || \
((MODE) == TIM_OCMode_Inactive) || \
((MODE) == TIM_OCMode_Toggle)|| \
((MODE) == TIM_OCMode_PWM1) || \
((MODE) == TIM_OCMode_PWM2))
#define IS_TIM_OCM(MODE) (((MODE) == TIM_OCMode_Timing) || \
((MODE) == TIM_OCMode_Active) || \
((MODE) == TIM_OCMode_Inactive) || \
((MODE) == TIM_OCMode_Toggle)|| \
((MODE) == TIM_OCMode_PWM1) || \
((MODE) == TIM_OCMode_PWM2) || \
((MODE) == TIM_ForcedAction_Active) || \
((MODE) == TIM_ForcedAction_InActive))
对应表格:
模式 | 描述 |
冻结 | CNT=CCR时,REF保持为原状态 |
匹配时置有效电平 | CNT=CCR时,REF置有效电平 |
匹配时置无效电平 | CNT=CCR时,REF置无效电平 |
匹配时电平翻转 | CNT=CCR时,REF电平翻转 |
强制为无效电平 | CNT与CCR无效,REF强制为无效电平 |
强制为有效电平 | CNT与CCR无效,REF强制为有效电平 |
PWM模式1 | 向上计数:CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平 向下计数:CNT>CCR时,REF置无效电平,CNT≤CCR时,REF置有效电平 |
PWM模式2 | 向上计数:CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平 向下计数:CNT>CCR时,REF置有效电平,CNT≤CCR时,REF置无效电平 |
这里我们选择PWM模式1即可
TIM_OutputState: 定时器的输出状态
填TIM_OutputState_Enable即可
TIM_OCPolarity: 定时器输出比较单元的极性
就是输出的有效电平是高电平有效还是低电平有效
/** @defgroup TIM_Output_Compare_Polarity
* @{
*/
#define TIM_OCPolarity_High ((uint16_t)0x0000)
#define TIM_OCPolarity_Low ((uint16_t)0x0002)
#define IS_TIM_OC_POLARITY(POLARITY) (((POLARITY) == TIM_OCPolarity_High) || \
((POLARITY) == TIM_OCPolarity_Low))
TIM_Pulse: 定时器脉冲(0x0000 and 0xFFFF)
配置的是CCR捕获/比较寄存器
2.写一个函数设置CCR寄存器(目的是调节占空比)
void PWM_Set_Compare1(uint16_t Pulse)
{
TIM_SetCompare1(TIM2, Pulse); // 设置OC1 CCR寄存器
}
3.整体代码
main.c
#include "stm32f10x.h"
#include "delay.h"
#include "OLED.h"
#include "PWM.h"
uint16_t i;
// PWM控制LED呼吸灯
// 2023年3月21日22:38:34
int main(void)
{
OLED_Init();
PWM_Init();
OLED_ShowString(1, 1, "Duty:");
while(1)
{
for (i = 0; i <= 100; i++)
{
PWM_Set_Compare1(i);
Delay_ms(10);
OLED_ShowNum(1, 6, i, 3);
}
for (i = 0; i <= 100; i++)
{
PWM_Set_Compare1(100 - i);
Delay_ms(10);
OLED_ShowNum(1, 6, 100 - i, 3);
}
}
}
PWM.c
#include "stm32f10x.h"
void PWM_Init(void)
{
/* GPIO复用功能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
*/
// RCC使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置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的时钟源
TIM_InternalClockConfig(TIM2);
// 配置时基单元
TIM_TimeBaseInitTypeDef TimeBase_InitStructure;
TimeBase_InitStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 时钟分频
TimeBase_InitStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数模式
TimeBase_InitStructure.TIM_Period=100-1; // ARR
TimeBase_InitStructure.TIM_Prescaler=720-1; // 预分频器
TimeBase_InitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, &TimeBase_InitStructure);
// 配置定时器输出比较单元
TIM_OCInitTypeDef OC1_InitStructure;
TIM_OCStructInit(&OC1_InitStructure);
OC1_InitStructure.TIM_OCMode=TIM_OCMode_PWM1; // 设置模式
OC1_InitStructure.TIM_Pulse=0; // CCR捕获/输出寄存器
OC1_InitStructure.TIM_OCPolarity=TIM_OCPolarity_High; // 设置有效电平
OC1_InitStructure.TIM_OutputState=TIM_OutputState_Enable; // 设置状态
TIM_OC1Init(TIM2, &OC1_InitStructure); // 重复计数定时器, 只有高级定时器有
// 开启时钟
TIM_Cmd(TIM2, ENABLE); // 容易漏掉
}
void PWM_Set_Compare1(uint16_t Pulse)
{
TIM_SetCompare1(TIM2, Pulse); // 设置OC1 CCR寄存器
}
案例2: PWM驱动舵机和电机
硬件接线:

说明:
采用TIM2的CH1通道(PA0)接舵机橙线和TIM3的CH1通道(PA6)接电机驱动模块PWMA作为PWM波形生成, 按键PB10控制舵机的角度, 按键PA10控制电机的速度, 并在显示屏上同时显现舵机的角度和电机的速度, 驱动电机的GPIO引脚为PA11和PA12(要经过驱动模块放大信号), 驱动模块接线参考江科stm32教程中的接线图, 但这里为了方便也把驱动模块的引脚图和教程中的接线图放到下面, 值得注意的是VM接ST-Link的5V端脚


整体代码
PWM.c
#include "stm32f10x.h"
/*
初始化TIM2的PWM模式 用来控制舵机
所用外设: TIM2定时器, GPIOA_Pin_0 CH1通道
*/
void TIM2_PWM_Init(void)
{
// RCC使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置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的时钟源
TIM_InternalClockConfig(TIM2);
// 配置时基单元
TIM_TimeBaseInitTypeDef TimeBase_InitStructure;
TimeBase_InitStructure.TIM_ClockDivision=TIM_CKD_DIV1 ;
TimeBase_InitStructure.TIM_CounterMode=TIM_CounterMode_Up; // 向上计数
TimeBase_InitStructure.TIM_Period=20000-1; // ARR
TimeBase_InitStructure.TIM_Prescaler=72-1; // PSC
TimeBase_InitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, &TimeBase_InitStructure);
// 配置OC单元
TIM_OCInitTypeDef OC1_InitStructure;
TIM_OCStructInit(&OC1_InitStructure);
OC1_InitStructure.TIM_OutputState=TIM_OutputState_Enable;
OC1_InitStructure.TIM_OCMode=TIM_OCMode_PWM1; // 设置模式
OC1_InitStructure.TIM_OCPolarity=TIM_OCPolarity_High; // 设置有效电平
OC1_InitStructure.TIM_Pulse=0; // CCR
TIM_OC1Init(TIM2, &OC1_InitStructure);
// 开启时钟
TIM_Cmd(TIM2, ENABLE);
}
/*
初始化TIM3的PWM模式 用来控制电机
所用外设: TIM3定时器, GPIOA_Pin_6 CH1通道
*/
void TIM3_PWM_Init(void)
{
// RCC使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置GPIO口
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置内部时钟作为TIM的时钟源
TIM_InternalClockConfig(TIM3);
// 配置时基单元
TIM_TimeBaseInitTypeDef TimeBase_InitStructure;
TimeBase_InitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TimeBase_InitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TimeBase_InitStructure.TIM_Period=100-1; // ARR
TimeBase_InitStructure.TIM_Prescaler=720-1; // PSC
TimeBase_InitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3, &TimeBase_InitStructure);
// 配置OC单元
TIM_OCInitTypeDef OC1_InitStructure;
TIM_OCStructInit(&OC1_InitStructure);
OC1_InitStructure.TIM_OCMode=TIM_OCMode_PWM1; // 设置模式
OC1_InitStructure.TIM_OCPolarity=TIM_OCPolarity_High; // 设置有效电平
OC1_InitStructure.TIM_OutputState=TIM_OutputState_Enable;
OC1_InitStructure.TIM_Pulse=0; // CCR
TIM_OC1Init(TIM3, &OC1_InitStructure);
// 开启时钟
TIM_Cmd(TIM3, ENABLE);
}
/*
设置TIM2的CRR1寄存器
*/
void TIM2_PWM_Set_Compare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); // 设置OC1 CCR寄存器
}
/*
设置TIM3的CRR1寄存器
*/
void TIM3_PWM_Set_Compare1(uint16_t Compare)
{
TIM_SetCompare1(TIM3, Compare);
}
servo.c(舵机)
#include "PWM.h"
/*
初始化舵机
*/
void Servo_Init(void)
{
TIM2_PWM_Init();
}
/*
设置舵机角度
因为舵机需要20ms的波长来驱动 可知频率为50Hz 若ARR设置为 20000-1 则PSC设置为72-1
又0.5ms~2.5ms对应舵机角度的0~180°
0.5ms 对应ARR为500 2.5ms 对应ARR为2500
对应角度的CRR应该设置为(Angle / 180 * 2000 + 500)
*/
void Servo_SetAngle(float Angle)
{
TIM2_PWM_Set_Compare1(Angle / 180 * 2000 + 500);
}
motor.c(电机)
#include "PWM.h"
void Motor_Init(void)
{
TIM3_PWM_Init();
// 初始化驱动引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11 | GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void Motor_SetSpeed(int16_t Speed) // 这里的速度就是占空比 最大为100 最小为0
{
if(Speed > 0) // 正转
{
GPIO_SetBits(GPIOA, GPIO_Pin_11);
GPIO_ResetBits(GPIOA, GPIO_Pin_12);
TIM3_PWM_Set_Compare1(Speed);
}
else // 反转
{
GPIO_SetBits(GPIOA, GPIO_Pin_12);
GPIO_ResetBits(GPIOA, GPIO_Pin_11);
TIM3_PWM_Set_Compare1(-Speed);
}
}
key.c
#include "stm32f10x.h"
#include "delay.h"
// 2023年3月10日17:00:13
// B10设置为上拉输入 默认高电平 按键按下后为低电平
// A10设置为下拉输入 默认低电平 按键按下后为高电平
void Key_Init(void)
{
// GPIO初始化结构体配置参数
GPIO_InitTypeDef GPIO_InitStructureA;
GPIO_InitTypeDef GPIO_InitStructureB;
// RCC时钟使能APB2外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 配置参数
GPIO_InitStructureA.GPIO_Mode=GPIO_Mode_IPD; // 下拉输入
GPIO_InitStructureA.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructureA.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructureB.GPIO_Mode=GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructureB.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructureB.GPIO_Pin=GPIO_Pin_10;
// 调用初始化函数
GPIO_Init(GPIOA, &GPIO_InitStructureA);
GPIO_Init(GPIOB, &GPIO_InitStructureB);
return;
}
// 判断哪个按键按下
uint8_t Get_KeyNum(void)
{
uint8_t KeyNum=0;
// A10按下 下拉输入 默认低电平 按键按下后为高电平
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10)==1){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10)==1);
Delay_ms(20);
KeyNum=1;
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)==0){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10)==0);
Delay_ms(20);
KeyNum=2;
}
return KeyNum;
}
main.c
#include "stm32f10x.h"
#include "OLED.h"
#include "servo.h"
#include "motor.h"
#include "key.h"
int16_t Speed;
uint16_t KeyNum;
float Angle;
// PWM控制舵机和电机
// 2023年3月22日13:22:34
int main(void)
{
OLED_Init();
Key_Init();
Servo_Init();
Motor_Init();
OLED_ShowString(1, 1, "Angle:");
OLED_ShowString(2, 1, "Speed:");
while(1)
{
KeyNum = Get_KeyNum();
if(KeyNum == 1)
{
Speed+=20; // 速度+20
if(Speed > 100) // 大于100反转
Speed = -100;
}
else if(KeyNum == 2)
{
Angle += 30; // 角度+30
if (Angle > 180) // 大于180°置零
{
Angle = 0;
}
}
Servo_SetAngle(Angle); // 设置舵机角度
Motor_SetSpeed(Speed); // 设置电机速度
OLED_ShowNum(1, 7, Angle, 3);
OLED_ShowSignedNum(2, 7, Speed, 4);
}
}
说明: 由于注释较为详细, 就不对代码进行详细的讲解了
注意: 程序中都是PWM1模式并且高电平为有效电平
参考资料
【STM32入门教程-2023持续更新中】 https://www.bilibili.com/video/BV1th411z7sn/?p=16&share_source=copy_web&vd_source=ee06a25b3dfb2900ab707b01fdff6667