四部分讲解内容,本文是第二部分
输出比较主要用于PWM波形的控制电机(驱动电机的必要条件)
1、定时器基本定时,定一个时间,然后让定时器每隔一段时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,比如要做一个时钟、秒表或者使用一些程序算法
2、定时器输出比较的功能,输出比较这个模块最常见的用途是产生PWM波形,用于驱动电机等设备,使用stm32的PWM波形来驱动舵机和直流电机的例子
3、定时器输入捕获的功能,学习使用输入捕获这个模块来测量方波频率的例子
4、定时器的编码器接口,使用这个编码器接口,能够更加方便地读取正交编码器的输出波形,在编码电机测速中,应用广泛。
TIM输出比较
CNT:时基单元里面的计数器
CCR: 捕获/比较寄存器(输入捕获和输出比较公用)
输出比较的基本功能:当CNT大于CCR、小于CCR或者等于CCR时,输出就会对应的置1、置0。这样就可以输出一个电平不断跳变的PWM波形
PWM(Pulse Width Modulation 脉冲宽度调制)的秘诀时:天下武功,为快不破——惯性系统才可用
高低电平跳变的数字信号可以等效为中间虚线所表示的模拟量,当上面电平时间长一点,下面电平时间短一点的时候,那么等效的模拟量就偏向于上面,当下面电平时间长一点,上面电平时间短一点的时候,等效的模拟量就偏向于下面。
当我们调控这个点亮和熄灭的时间比例时,就能让LED呈现出不同的亮度级别。
只有具有惯性的系统,才能使用PWM
占空比,它决定了PWM等效出来的模拟电压的大小。占空比越大,那等效的模拟电压就越趋近于高电平,占空比越小,那等效的模拟电压就越趋近于低电平。(线性关系)
分辨率:占空比变化的精细程度
使用PWM波形,就可以在数字系统等效输出模拟量,就能实现LED控制亮度 ,电机控速等功能。
输出比较通道(通用定时器)
模式控制的输入是:CNT和CCR的大小关系,输出是REF的高低电平。
冻结:比如你正在输出PWM波,突然想暂停一会输出,就可以设置成这个模式,一旦切换为冻结模式,输出就暂停,并且高低电平也维持为暂停时刻的状态,保持不变。
PWM模式1和PWM模式2可以用于输出频率和占空比都可调的PWM波形。而PWM模式2实际上就是PWM模式1输出的取反。改变PWM模式1和PWM模式2就只是改变了REF电平的极性。
输出模式可设置极性,最终输出之前也可以设置极性灵活
重点-PWM基本结构
左上角是时基单元和运行控制,最左边是时钟源选择,这里省略了,这里更新中断申请不再需要,输出PWM暂时不需要中断。
配置好时基单元,CNT就可以开始不断自增运行。然后下面就是输出比较单元,总共4路。输出比较单元的最开始是CCR捕获寄存器。CCR是我们设定的,CNT不断自增运行,同时它俩还在不断比较,后面就是输出模式控制器。
上图是PWM1模式1的执行逻辑。
蓝色线: CNT的值
黄色线:ARR的值
红色线:CCR
蓝色线从0开始自增,一直增到ARR,也就是99,之后清0继续自增,在这个过程中,再设置一条红色线,这条红色线就是CCR,比如我们设置CCR为30,之后再执行以下这个逻辑。
绿色线就是输出:
CNT<CCR,置高电平
CNT>=CCR,置低电平
以上占空比是受到CCR值的调控的。
若CCR设置高,则输出的占空比就变大。若CCR设置低一些,输出的占空比就变小。
REF:一个频率可调,占空比也可调的PWM波形
最终经过极性选择,输出使能,最终通向GPIO,完成PWM波形的输出
PWM参数计算
CNT从0一直加到ARR(99),总共计算了100个数,再看高电平时间,从0加到CCR(30),在等于30的瞬间就已经跳变为低电平,所以CNT从0到29是高电平,总共是30个数。
占空比=30/(99(ARR)+1)=30/100=30%
CCR的值取决于ARR的范围。ARR越大,CCR的范围就越大,对应的分辨率就越大。
舵机,直流电机,硬件电路
执行逻辑:PWM输入到控制板,给控制板一个指定的目标角度,然后电位器检测输出轴的当前角度,如果大于目标角度,电机就会反转,如果小于目标角度,电机就会正转最终使输出轴固定在指定角度。
PWM驱动LED呼吸灯代码
第一步:RCC开启时钟,把我们要用的TIM外设和GPIO外设的始终打开
第二步:配置时基单元,包括前面的时钟源选择
第三步:配置输出比较单元,里面包括这个CCR的值、输出比较模式、极性选择、输出使能这些参数;
第四步:配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出的配置,PWM和GPIO的对应关系是怎样的(参考引脚定义表)
第五步:运行控制,启动计数器,就能输出PWM
PWM代码总体思路
初始化TIM2的通道1,产生一个PWM波形,输出引脚是PA0,然后通过SetCompare1函数,可以调节CCR1寄存器的值,从而控制PWM的占空比。但是PWM的频率在初始化写定,运行时调节不方便,所以在最后加一个函数来便捷调节PWM频率
通用公式:
PSC和ARR都可以调节频率,但是占空比=CCR/(ARR+1),如果通过调节ARR调节频率,同时会影响到占空比。通过PSC调节频率,不会影响占空比。因此固定ARR,通过调节PSC改变PWM频率,另外ARR为100-1,CCR的数值直接就是占空比。
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();
//在主循环里不断调用PWM_SetCompare1函数
//更改CCR的值,这样就能完成LED呼吸灯的效果
while (1)
{//从0变到100
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(i);
Delay_ms(10); //延时,否则太快
}
//从100变到0
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);
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //引脚重映射配置函数,部分重映射,可将PA0换到PA15
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //关闭调试端口的复用
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
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;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
//初始化输出比较单元(即初始化通道)
TIM_OCInitTypeDef TIM_OCInitStructure;
//给结构体赋初始值,如果不想把所有成员都列一遍赋值,可以先用structInit赋值,再更改想要修改的值
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置输出比较的极性,高极性,就是极性不翻转,REF波形直接输出
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR
//在TIM2的OC1通道上就可以输出PWM波形了,但最终这个波形需要借用GPIO口才能输出
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
//该函数单独设置通道1的CCR值。
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
pwm.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void); //PWM初始化
void PWM_SetCompare1(uint16_t Compare); //设置CCR,改变占空比
#endif
PWM舵机控制代码
驱动舵机的关键,输出如下图的PWM波形:
main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"
uint8_t Angle;
uint8_t KeyNum;
int main(void)
{
OLED_Init();
Servo_Init();
Servo_SetAngle(90);
OLED_ShowString(1,1,"Angle:");
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Angle += 90;
if(Angle > 180)
{
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowNum(1,7,Angle,3); //1行7列显示Angle,长度为3
}
}
pwm.c:
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//PA1 口的通道2
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 = 20000 - 1; //ARR周期
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC预分频器
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
//初始化输出比较单元(即初始化通道)
TIM_OCInitTypeDef TIM_OCInitStructure;
//给结构体赋初始值,如果不想把所有成员都列一遍赋值,可以先用structInit赋值,再更改想要修改的值
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置输出比较的极性,高极性,就是极性不翻转,REF波形直接输出
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR (500~2500对应0.5ms~2.5ms)
//在TIM2的OC1通道上就可以输出PWM波形了,但最终这个波形需要借用GPIO口才能输出
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //通道2
TIM_Cmd(TIM2, ENABLE);
}
//该函数在运行过程中单独设置通道2的CCR值
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
pwm.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);
#endif
servo.c
#include "stm32f10x.h"
//舵机模块需要继承PWM文件的功能
#include "PWM.h"
//舵机初始化函数
void Servo_Init(void)
{
PWM_Init();
}
/*
0 500
180 2500
*/
//线性映射
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
servo.h
#ifndef __SERVO_H
#define __SERVO_H
void Servo_Init(void);
void Servo_SetAngle(float Angle);
#endif
PWM驱动直流电机
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"
//通过按键控制电机转速(全局变量)
uint8_t KeyNum;
int8_t Speed;
int main(void)
{
OLED_Init();
Motor_Init();
Key_Init();
Motor_SetSpeed(2);
OLED_ShowString(1,1,"Speed:");
while (1)
{
//实现按键控制速度
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Speed += 20;
if(Speed > 100)
{
Speed = -100;
}
}
if(KeyNum == 2)
{
Speed -= 10;
if(Speed < -100)
{
Speed = 100;
}
}
Motor_SetSpeed(Speed);
OLED_ShowNum(1,7,Speed,3);
}
}
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_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PWM电平输出的GPIO口,控制速度
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 = 36 - 1; //PSC预分频器
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
//初始化输出比较单元(即初始化通道)
TIM_OCInitTypeDef TIM_OCInitStructure;
//给结构体赋初始值,如果不想把所有成员都列一遍赋值,可以先用structInit赋值,再更改想要修改的值
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,PWM模式1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置输出比较的极性,高极性,就是极性不翻转,REF波形直接输出
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //设置输出使能
TIM_OCInitStructure.TIM_Pulse = 0; //设置CCR
//在TIM2的OC3通道上就可以输出PWM波形了,但最终这个波形需要借用GPIO口才能输出
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
//该函数单独设置通道1的CCR值。
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare);
}
pwm.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void); //PWM初始化
void PWM_SetCompare3(uint16_t Compare); //设置CCR,改变占空比
#endif
motor.c
#include "stm32f10x.h"
//继承PWM
#include "PWM.h"
//初始化电机
void Motor_Init(void)
{
//电机模块里多了控制方向的两个引脚
//额外初始化方向控制的两个引脚
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5; //初始化电机控制方向的两个GPIO口
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
PWM_Init();
}
//通过PWM_Setcompare来设置占空比来设置转速
//设置电机速度的函数,参数需要给带符号的速度变量,负数用来表示反转
void Motor_SetSpeed(int8_t Speed)
{
if(Speed >= 0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_4); //PA4=1
GPIO_ResetBits(GPIOA,GPIO_Pin_5); //PA5 = 0
PWM_SetCompare3(Speed);
}
if(Speed < 0)
{
GPIO_SetBits(GPIOA,GPIO_Pin_5); //PA5 = 1
GPIO_ResetBits(GPIOA,GPIO_Pin_4); //PA4 = 0
PWM_SetCompare3(-Speed); //配置CCR的值
}
}
motor.h
#ifndef __MOTOR_H
#define __MOTOR_H
void Motor_Init(void);
void Motor_SetSpeed(int8_t Speed);
#endif