输出比较(Output Compare)
- 通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作。
用于输出一定频率和占空比的PWM波形。 - 每个高级定时器和通用定时器都拥有4个输出比较通道。
- 高级定时器的前3个通道额外拥有死区生成和互补输出的功能。
PWM波形
例:以一个很快的频率让电机通、断、通、断电,那么电机就能都呈现出一个中等速度。
- 占空比决定了PWM等效出的模拟电压的大小,占空比越高,模拟电压越接近高电压输出。
- 分辨率就是占空比变化的精细程度
输出比较通道
当CNT计数器和CCR1第一路的捕获/比较寄存器进行比较,当CNT>/=CCR1时,输出模式控制器收到信号,然后改变输出oc1ref信号的高低电平。
然后这个信号一路到主模式控制器去(可以映射到TRGO输出上)
一路通过极性选择电路经过输出使能电路,最后输出到OC1引脚。
可以通过TIMx_CCER寄存器的值来决定是否将oc1ref输出的信号翻转。
输出比较模式
1. 冻结可以用于要暂停一段时间的输出情况
2.匹配时置有/无效电平:可以理解为高/低电平,用于一次性输出,不适合连续波形。
3.输出占空比50%的PWM波形
4.强制:可以用于暂停期间需要保持高/低电平
5.PWM模式1/2主要是极性的区别。可以就选模式1。
参数计算
外接设备(舵机和电机)
给个PWM,输出轴就会固定在一个角度。
上面红色三个引脚控制下面AO的电机,PWMA接PWM输出,而IN引脚可以接GPIO输出,给一个低功率控制信号就能控制电机转动。
输入与输出状态对应关系如右下角所示。
如当IN1置低,IN2置高,PWM为高电平则电机翻转,低电平则电机不转。
代码
1. PWM驱动呼吸灯
OLED部分与之前一样接,灯正极接PA0,负极接GND。
首先初始化PWM,即把输出比较模块打通。
//配置输出比较单元的函数
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
//给结构体赋默认值
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);
//单独数值输出比歼的极性(单独修改输出极性)
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);
//单独修改输出使能
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);
//单独更改CCR寄存器的值(运行时改变占空比)
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_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);
//配置快速使能
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);
//外部事件时清除REF
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);
//在使用高级定时器时,需调用使能主输出,否则PWM不能正常输出
void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);
1. RCC开启时钟,把要用的TIM外设和GPIO口的时钟打开
2. 配置时基单元,包括时钟源选择。
3. 配置输出比较单元,包括输出比较模式、极性选择、输出使能
4. 配置GPIO,把PWM输出对应的GPIO口初始化为复用推挽输出
查看引脚定义,TIM2的通道1和ETR复用在了PA0端口上。
5. 运行控制,启动计数器输出PWM
main.c
#include "stm32f10x.h"// Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint8_t i;
int main(void)
{
OLED_Init();
PWM_Init();
while(1)
{
for(i = 0; i <= 100; i++)
{
PWM_SetCompare1(i);
Delay_ms(10);
}
for(i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i);
Delay_ms(10);
}
}
}
PWM.c
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
//打开时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽,将引脚控制权交与片上外设
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//配置TIM,时基单元
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 100-1; //ARR的值
TIM_TimeBaseInitStruct.TIM_Prescaler = 720-1; //PSC的值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//初始化输出比较
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;//输出模式PWM1
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;//高极性
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;//
TIM_OCInitStruct.TIM_Pulse = 0; //CCR的值
TIM_OC1Init(TIM2,&TIM_OCInitStruct);
TIM_Cmd(TIM2,ENABLE);//启动定时器
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
2. PWM驱动舵机
根据舵机所需高电平时间及相应公式计算出各个寄存器的值。
TIM_TimeBaseInitStruct.TIM_Period = 2000-1; //ARR的值
TIM_TimeBaseInitStruct.TIM_Prescaler = 72-1; //PSC的值
TIM_OCInitStruct.TIM_Pulse = 0; //CCR的值
CCR:500->0°,2500->180°,根据线性关系可计算。
//main.c
#include "stm32f10x.h"// Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
uint8_t i;
float Angle;
int main(void)
{
OLED_Init();
Servo_Init();
Servo_SetAngle(90);
OLED_ShowString(1,1,"Angle:");
while(1)
{
Angle += 30;
if(Angle > 180)
Angle = 0;
Delay_s(5);
Servo_SetAngle(Angle);
OLED_ShowNum(2,1,Angle,7);
}
}
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
//打开时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽,将引脚控制权交与片上外设
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//配置TIM,时基单元
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 2000-1; //ARR的值
TIM_TimeBaseInitStruct.TIM_Prescaler = 72-1; //PSC的值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//初始化输出比较
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;//输出模式PWM1
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;//高极性
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;//
TIM_OCInitStruct.TIM_Pulse = 0; //CCR的值
TIM_OC2Init(TIM2,&TIM_OCInitStruct);
TIM_Cmd(TIM2,ENABLE);//启动定时器
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2,Compare);
}
3. PWM驱动电机
TB6612硬件电路如下,根据引脚定义接线。
Motor.c
#include "stm32f10x.h" // Device header
#include "PWM.h"
void Motor_Init(void)
{
//初始化控制方向的引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
PWM_Init();
}
//设置速度函数
void Motor_SetSpeed(int8_t Speed) //带符号的,负号表示反转
{
if(Speed > 0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4);
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
PWM_SetCompare3(Speed);
}
else
{
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
GPIO_SetBits(GPIOA,GPIO_Pin_5);
PWM_SetCompare3(-Speed);
}
}
main.c
#include "stm32f10x.h"// Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
uint8_t i;
float Angle;
int main(void)
{
OLED_Init();
Motor_Init();
Motor_SetSpeed(-50);
while(1)
{
}
}