声明:学习笔记根据b站江科大自化协stm32入门教程编辑,仅供学习交流使用!
定时器输出比较功能比较重要,主要用来输出PWM波形,PWM波形又是驱动电机的必要条件,智能车、机器人的电机都可能用到!!
本次学习有三个实操,分别是PWM驱动LED呼吸灯、PWM驱动舵机、PWM驱动直流电机。
1、OC(Output Compare)输出比较,IC(Input Capture)为输入捕获,CC(Capture/Compare)一般表示输入捕获和输出比较的单元!
2、输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。(可参考上节通用定时器或高级定时器图如上图)CNT为时基单元里的计数器,CCR即捕获/比较寄存器(输入捕获和输出比较共用的)。输出比较时,电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的一个值,当CNT>CCR、
PWM(Pulse Width Modulation)脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域。也就是用来等效地实现一个模拟信号的输出(左图虚线余弦),也就是最开始提出的问题:数字输出端口控制LED,按理说只有完全亮和完全灭两种状态,但通过PWM就可实现呼吸灯,当不断点亮熄灭点亮熄灭…的频率足够大时就不会时闪烁而是呈现中等亮度!!
但是PWM波的应用场景必须是一个惯性系统,就是LED在熄灭时由于余晖和人眼视觉暂留现象,LED不会立马熄灭而是具有一定惯性,电机也是同样。
PWM参数:
频率 = 1 / TS 。一般在几十——几十千
占空比 = TON / TS
分辨率 = 占空比变化步距。分辨率是指输出电压梯度,而频率是指输出变化的次数,例如LED的分辨率为1%即说明LED的亮度时1%亮、2%亮、3%…一直到100%亮。
基本结构:
时基单元部分配置与上节相似,只不过更新事件的中断申请不再需要了,输出PWM暂时不需要中断,配置完了时基单元,CNT就可开始不断自增运行。
下面为输出比较单元,总共有4路,CNT不断自增的同时与CCR不断进行比较,此图为PWM模式1的执行逻辑(REF怎么置),右上角蓝线为CNT的值,黄线为ARR的值,蓝线从0开始自增一直到黄线ARR即99,之后清零继续自增,再设置一条红线CCR(此处为30),之后根据执行逻辑1得到绿色线输出,图中的REF就是一个频率可调,占空比也可调的PWM波形,再经过极性选择输出使能通向GPIO口完成PWM波的输出!!
参数计算:
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
普通定时器没有这些通道,通用和高级的有!!
CNT计数器与CRR1第一路的捕获/比较寄存器进行比较后,当>或=时会给输出模式控制器传一个信号,然后该控制器会改变输出oc1ref的高低电平(ref为reference的缩写,意思为参考信号),ETRF输入为定时器的一个小功能,一般不用!ref信号可前往主模式控制器,可以把REF映射到主模式的TRGO的输出上,但主要去向为下面的极性选择,给CC1P寄存器写0信号会往上走即信号电平不反转,写1经过非门反转后到输出使能电路,输出使能电路选择要不要输出,最后OC1引脚是CH1通道的引脚,在引脚定义表里可知道具体是哪个GPIO口了!
输出比较八种模式:
注:可把有效电平当作高电平,无效电平当作低电平;输出波形频率=更新频率/2,因为更新两次输出才为1个周期。
这个先了解下就行!!与通用定时器不同的只有红圈内的电路。以前在介绍高级定时器时也有相关介绍
舵机是一种根据输入PWM信号占空比来控制输出角度的装置
输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms
第二个图为拆解图,舵机并不是一个单独的直流电机,舵机内部电板是一个控制电路板,PWM信号输入到控制板,给控制板一个指定的目标角度,然后电位器检测输出轴的当前角度,如果大于目标角度电机反转,小于的话电机正转,最终使输出轴固定在指定角度!!
周期20ms指第一个上升沿到下一个上升沿的时间,0.5ms、1ms等指高电平的时间,这里的PWM波形被当作一个通信协议使用,与之前说的等效一个模拟输出关系不大!!
机器人关节,小车小船方向!!
一般推荐单独供电,供电的负极要与stm32共地,5v供电可从STLINK的5V输出引脚引一根线,如果没有单独供电条件要看电源功率是否达标,不达标驱动会遇到问题!
直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转。
直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作。
TB6612是一款双路H桥型的直流电机驱动芯片(如下图两个推挽电路组合),可以驱动两个直流电机并且控制其转速和方向,舵机就不需要了内部有驱动电路。其他还有DRV8833、L9110、L298N等等。还有一些用分离元件MOS管搭建的电路,这种的功率可以做得更大!!
硬件电路,VM一般与电机额定电压保持一致,VCC与stm32保持一致3.3v;3个GND一样随便选一个;AO1、AO2、BO2、BO1为两路电机的输出,PWMA、AIN2、AIN1三个引脚控制A路电机,PWMA要接输出PWM波的GPIO口,B路电机同理;最后STBY(stand by)为待机控制脚,如果接GND芯片就不工作处于待机状态,若接VCC则正常工作:
右下角这个表说明了怎么控制正反转等!!
更多详细内容可多参考芯片手册!!
LED接线采用正极性驱动,即输出高电平点亮,现象更加直观,即占空比越大LED越亮反之越暗:
//PWM.c
#include "stm32f10x.h" // Device header
//初始化,也是根据PWM基本结构图把每个环节打通:
//1、RCC开启时钟,打开要用的TIM外设和GPIO外设的时钟
//2、配置时基单元,包括图中未画出的时钟源选择
//3、配置输出比较单元,包括CCR的值、输出比较模式、极性选择、输出使能这些参数(库函数也是用结构体统一配置)
//4、配置GPIO,把PWM对应的GPIO口初始化为复用推挽输出的配置,对应关系可参考引脚定义表
//5、运行控制,启动计数器就可以输出PWM波了
void PWM_Init(void){//上半部分可借鉴定时器中断
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE );//使用APB1的开启时钟函数,因为TIM2位APB1总线的外设
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 ;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;//Timing冻结模式、Active相等时置有效电平、Inactive相等置无效电平、Togg相等时电平翻转、PWM1、PWM2
TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse= 0;//CCR寄存器的值,与上ARR和PSC值共同决定PWM的周期和占空比,此处为1KHz、占空比待定
TIM_OC1Init(TIM2,&TIM_OCInitStructure);
//GPIO配置,需要参照引脚定义表,IO口默认复用功能、重映射位置,哪个对应哪个是定死的
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//可放在最上面,这里为了便于分块理解就放这里
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(GPIOB, &GPIO_InitStructure);
TIM_Cmd(TIM2,ENABLE);//启动定时器,PWM通过PA0输出
}
void PWM_SetCompare1(uint16_t Compare){//为了不断更改占空比,达到呼吸灯目的的函数
TIM_SetCompare1(TIM2,Compare);//该函数可单独修改CCR的值,不断改变占空比 ,参数在main.c中利用for循环赋予
}
//PWM.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
//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);//不断更改CCR,以改变占空比(占空比由CCR和ARR共同决定,ARR为100一直不变),逐渐变亮
Delay_ms(10);//防止太快
}
for (i=0;i<=100;i++){
PWM_SetCompare1(100-i);//不断更改CCR以改变占空比,逐渐变暗
Delay_ms(10);//防止太快
}
}
}
补充:试验下刚才所说的引脚重映射,例如引脚定义表上TIM2的CH1可以从PA0挪到PA15引脚上,怎么操作?需要用到AFIO
如果想让PA15、PB3、PB4三个引脚当作GPIO使用:
1、开启AFIO的时钟(第一句)
2、再用AFIO将JTAG复用解除掉(第三句)
如果想重映射定时器或者其他外设的复用引脚:
1、打开AFIO时钟(第一句)
2、再用AFIO重映射外设复用的引脚(第二句)
如果重映射的引脚正好是调试端口,三句代码都要!!
1、打开AFIO时钟
2、重映射引脚
3、解除调试端口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2 ,ENABLE);//引脚重映射函数,手册第8章8.3复用功能I/O和调试配置(AFIO)
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//解除PA15调试端口的复用
//PWM.c
//与驱动呼吸灯大同小异
#include "stm32f10x.h"
void PWM_Init(void){
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= 20000-1;//ARR不同
TIM_TimeBaseInitStructure.TIM_Prescaler= 72-1;//PSC不同
TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;//CCR(500-2500,对应0.5-2.5ms,对应0-180°
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse= 0;
TIM_OC2Init(TIM2,&TIM_OCInitStructure);//不同点,OC2通道2配置
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;//不同点,用的是PA1口的通道2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare2(uint16_t Compare){//不同点,改为compare2
TIM_SetCompare2(TIM2,Compare);//不同点,改为compare2
}
//Servo.c
#include "stm32f10x.h"
#include "PWM.h"
//为了使PWM输出与舵机角度的对应关系更加直观,特封装此.c文件
void Servo_Init(){
PWM_Init();
}
//0° 500
//180° 2500
void Servo_SetAngle(float Angle){
PWM_SetCompare2(Angle/180*2000 + 500);//角度到CCR值的映射
}
//main.c
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "key.h"
uint8_t KeyNum;
float Angle;//默认为0
int main(void){
OLED_Init();
Servo_Init();
Key_Init();
OLED_ShowString(1,1,"Angle:");
while(1){
KeyNum = Key_GetNum();
if (KeyNum == 1){
Angle +=30;//每按一下按键加30°
if(Angle > 180){
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowNum(1,7,Angle,3);//Angle:字符占了6列
}
}
//PWM.c
//同样与呼吸灯大同小异
#include "stm32f10x.h"
void PWM_Init(void){
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;
TIM_TimeBaseInitStructure.TIM_Prescaler= 7200-1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse= 0;
TIM_OC3Init(TIM2,&TIM_OCInitStructure);//不同点,通道3
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;//不同点,电机接在通道3
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare3(uint16_t Compare){//不同点,通道3
TIM_SetCompare3(TIM2,Compare);
}
//Motor.c
#include "stm32f10x.h"
#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_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_SetBits(GPIOA,GPIO_Pin_5);
GPIO_ResetBits(GPIOA,GPIO_Pin_4);
//速度设置
PWM_SetCompare3(-Speed);//该函数参数为无符号数,Speed为负,-Speed为正
}
}
//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();
OLED_ShowString(1,1,"Speed:");
while(1){
KeyNum = Key_GetNum();//按键控制速度-100~100
if (KeyNum == 1){
Speed +=20;
if (Speed >100){
Speed = -100;
}
}
Motor_SetSpeed(Speed);
//电机旋转时会发出蜂鸣器的声音,这是因为PWM频率在人耳范围导致的,可通过改变预分频PSC完善
OLED_ShowNum(1,7,Speed,3);
}
}
电机正反转的更改可以改变接线,也可以改变代码!在实操时注意与上面的理论相结合,三个实操之间也可对比学习,有助于加深理解!!
遇到挫折,要有勇往直前的信念,马上行动,坚持到底,决不放弃,成功者决不放弃,放弃者绝不会成功。成功的道路上,肯定会有失败;对于失败,我们要正确地看待和对待,不怕失败者,则必成功;怕失败者,则一无是处,会更失败。
往期精彩:
STM32定时中断
STM32外部中断
STM32GPIO精讲
…