【STM32单片机】#6 定时器比较输出
主要参考学习资料:
B站@江协科技
STM32入门教程-2023版 细致讲解 中文字幕
开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb
单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协
实验:
- PWM驱动LED呼吸灯
- PWM驱动舵机
- PWM驱动直流电机
新函数:
- 定时器库函数(输出比较部分)
目录
- 输出比较简介
- PWM简介
- 输出比较通道
- 通用定时器
- 输出比较模式
- PWM基本结构
- 参数计算
- 高级定时器
- 外部设备
- 舵机简介
- 硬件电路
- 直流电机及驱动简介
- 硬件电路
- 函数详解
- TIM_OCxInit函数(x)
- TIM_OCInitTypeDef结构体
- TIM_OCStructInit函数
- TIM_ForcedOCxConfig函数(x)(不常用)
- TIM_OCxPreloadConfig函数(x)(不常用)
- TIM_OCxFastConfig函数(x)(不常用)
- TIM_ClearOCxRef函数(x)(不常用)
- TIM_OCxPolarityConfig函数(x)
- TIM_OCxNPolarityConfig函数(x)
- TIM_CCxCmd函数(x)
- TIM_CCxNCmd函数(x)
- TIM_SelectOCxM函数(x)
- TIM_SetComparex函数(x)
- TIM_CtrlPWMOutputs函数
- 实验10 PWM驱动LED呼吸灯
- 接线图
- PWM驱动
- 主程序
- 引脚重映射
- 实验11 PWM驱动舵机
- 接线图
- PWM驱动
- 舵机驱动
- 主程序
- 实验11 PWM驱动直流电机
- 接线图
- PWM驱动
- 电机驱动
- 主程序
输出比较简介
- OC(Output Compare)输出比较
- 输出比较可以通过比较CNT计数器与CCR捕获/比较寄存器值的关系,对输出电平进行置一、置零或翻转的操作,用于输出一定频率和占空比的PWM波形。
- 每个高级定时器和通用定时器都拥有四个输出比较通道。
- 高级定时器的前三个通道额外拥有死区生成和互补输出的功能。
PWM简介
- PWM(Pulse Width Modulation)脉冲宽度调制
- 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域,即用数字信号等效模拟信号。
- 惯性系统:LED由于余晖和视觉暂留现象不会立马熄灭;电机断电时转动会因为惯性过一会才停。
- PWM参数:
- 频率 = 1 / T S =1/T_S =1/TS:频率越快,等效的模拟信号就越平稳,性能开销也越大,一般在几千到几十千赫兹。
- 占空比 = T O N / T S =T_{ON}/T_S =TON/TS:高电平时间相对于整个周期时间的比例,决定了等效模拟电压的大小。
- 分辨率=占空比变化步距:占空比变化的精细程度。
输出比较通道
通用定时器
左侧来自捕获/比较寄存器的比较结果传入输出模式控制器,随后输出模式控制器改变输出OC1REF(reference缩写,参考信号)的高低电平。ETRF输入是定时器一个小功能,一般不用,无需了解。REF信号一方面可以映射到主模式的TRGO输出,但主要还是前往OC1。CC1P寄存器选择极性,写0时信号电平不翻转(高电平有效),写1时信号电平通过非门翻转(低电平有效)。输出使能电路选择要不要输出。最终信号到达OC1引脚,具体引脚参见引脚定义表TIMx_CHx功能。
输出比较模式
输出模式控制器的执行逻辑包含八种输出比较模式:
其中有效电平为高电平,无效电平为低电平。本次主要使用的模式为PWM模式1的向上计数情况,模式2是模式1的取反。
PWM基本结构
图中波形为ARR=99、CCR=30的情况,上方波形为CNT计数过程,下方波形为REF输出结果。
参数计算
- PWM频率:Freq=CK_PSC/(PSC+1)/(ARR+1)(等于计数器更新频率)
- PWM占空比:Duty=CCR/(ARR+1)
- PWM分辨率:Reso=1/(ARR+1)(ARR越大,CCR的取值范围越大,分辨率越小)
高级定时器
仅需大致了解上期介绍的输出比较电路升级的内容即可。
外部设备
舵机简介
- 舵机是一种根据输入PWM信号占空比来控制输出角度的装置。
- 输入PWM信号要求:周期20ms,高电平宽度0.5ms~2.5ms
舵机的大致执行逻辑为PWM信号输入到控制板,给控制板一个指定的目标角度,电位器检测输出轴的当前角度,若大于目标角度电机反转,若小于目标角度电机正转,最终使输出轴固定在指定角度。此时PWM主要是一种通信协议,与等效模拟信号关系不大。
硬件电路
舵机需要5V供电,若外接电机电源,则电源一端接GND,一端接舵机5V口。套件中可使用STLINK的5V输出脚实现USB供电。
直流电机及驱动简介
- 直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时电机正转,当电极反接时电机反转。
- 直流电机属于大功率器件,GPIO无法直接驱动,需要配合电机驱动电路来操作。
- TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向。
硬件电路
如图左侧为TB6612芯片引脚图,右上为引脚定义表,右下为控制电平逻辑表。
驱动模块每路电机的输入由一个PWM信号输入端和两个控制模式输入端组成,输出由两个驱动输出端组成。输出端均为低电平时电机制动(刹车),一高一低时电机根据正/反接情况正/反转。输入中PWM信号决定电机是否旋转,控制模式输入端决定电机旋转方向,两者共同决定输出电平。
函数详解
以下函数名称后标注(x)的,函数名称中x可取1~4,代表对应的输出比较通道。
TIM_OCxInit函数(x)
简介:配置输出比较模块。
参数一:定时器名称
参数二:指向初始化信息TIM_OCInitTypeDef结构体的指针
TIM_OCInitTypeDef结构体
成员TIM_OCMode:输出比较模式
TIM_OCMode_Timing(冻结)
TIM_OCMode_Active(匹配置有效电平)
TIM_OCMode_Inactive(匹配置无效电平)
TIM_OCMode_Toggle(匹配电平翻转)
TIM_OCMode_PWM1(PWM模式1)
TIM_OCMode_PWM2(PWM模式2)
TIM_ForcedAction_Active(强制为有效电平)
TIM_ForcedAction_InActive(强制为无效电平)
强制模式无法在初始化时配置,但可通过后续其他配置函数配置。
成员TIM_OutputState:输出使能/失能(非互补输出通道)
TIM_OutputState_Enable, TIM_OutputState_Disable
成员TIM_OutputNState:输出使能/失能(互补输出通道)
TIM_OutputNState_Enable, TIM_OutputNState_Disable
成员TIM_Pulse:CCR寄存器值
成员TIM_OCPolarity:极性(非互补输出通道)
TIM_OCPolarity_High, TIM_OCPolarity_Low
成员TIM_OCNPolarity:极性(互补输出通道)
TIM_OCNPolarity_High, TIM_OCNPolarity_Low
成员TIM_OCIdleState:空闲状态输出电平(非互补输出通道)
TIM_OCIdleState_Set, TIM_OCIdleState_Reset
成员TIM_OCNIdleState:空闲状态输出电平(非互补输出通道)
TIM_OCNIdleState_Set, TIM_OCNIdleState_Reset
TIM_OCStructInit函数
简介:给TIM_OCInitTypeDef结构体赋默认值。
参数:指向初始化信息TIM_OCInitTypeDef结构体的指针
TIM_ForcedOCxConfig函数(x)(不常用)
简介:配置强制输出模式。
参数一:定时器名称
参数二:输出比较模式
TIM_ForcedAction_Active(强制为有效电平)
TIM_ForcedAction_InActive(强制为无效电平)
TIM_OCxPreloadConfig函数(x)(不常用)
简介:配置CCR寄存器的预装功能。
参数一:定时器名称
参数二:是否预装
TIM_OCPreload_Enable, TIM_OCPreload_Disable
TIM_OCxFastConfig函数(x)(不常用)
简介:配置快速使能(匹配条件发生时立即改变输出状态)。
参数一:定时器名称
参数二:是否快速使能
TIM_OCFast_Enable, TIM_OCFast_Disable
TIM_ClearOCxRef函数(x)(不常用)
简介:外部事件清除REF信号。
参数一:定时器名称
参数二:是否启用外部事件清除
TIM_OCClear_Enable, TIM_OCClear_Disable
TIM_OCxPolarityConfig函数(x)
简介:单独配置输出比较通道的极性(非互补输出通道)。
参数一:定时器名称
参数二:极性
TIM_OCxNPolarityConfig函数(x)
简介:单独配置输出比较通道的极性(互补输出通道,无OC4)。
参数一:定时器名称
参数二:极性
TIM_CCxCmd函数(x)
简介:单独配置输出使能参数(非互补输出通道)。
参数一:定时器名称
参数二:输出通道
TIM_Channel_1, ..., TIM_Channel_4
参数三:使能/失能
TIM_CCx_Enable, TIM_CCx_Disnable
TIM_CCxNCmd函数(x)
简介:单独配置输出使能参数(互补输出通道)。
参数一:定时器名称
参数二:输出通道
参数三:使能/失能
TIM_CCxN_Enable, TIM_CCxN_Disnable
TIM_SelectOCxM函数(x)
简介:选择输出比较模式。
参数一:定时器名称
参数二:输出通道
参数三:输出比较模式
TIM_SetComparex函数(x)
简介:单独配置CCR寄存器值。
参数一:定时器名称
参数二:CCR值
TIM_CtrlPWMOutputs函数
简介:使用高级定时器输出PWM时需要调用该函数使能主输出。
参数一:定时器名称
参数二:使能/失能
实验10 PWM驱动LED呼吸灯
接线图
PWM驱动
PWM.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
PWM.c
#include "stm32f10x.h"
void PWM_Init(void)
{
//初始化PA0
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(GPIOA, &GPIO_InitStructure);
//时基单元初始化参照实验8
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;
//设置PWM波形频率1KHz,分辨率1%
//ARR=100-1, PSC=720-1,此时占空比=CCR/100*100%
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
//高级定时器部分的成员用不到,但为了结构体完整性,使用默认值初始化所有成员,再更改需要的成员
TIM_OCStructInit(&TIM_OCInitStructure);
//PWM1模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
//高电平有效
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
//PA0口对应OC1通道
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
//修改CCR
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
主程序
#include "stm32f10x.h"
#include "Delay.h"
#include "PWM.h"
//循环变量
uint8_t i;
int main(void)
{
PWM_Init();
while(1)
{
//占空比递增,LED变亮
for(i = 0;i <= 100;i++)
{
PWM_SetCompare1(i);
Delay_ms(10);
}
//占空比递减,LED变暗
for(i = 0;i <= 100;i++)
{
PWM_SetCompare1(100 - i);
Delay_ms(10);
}
}
}
引脚重映射
回顾外部中断一节介绍的引脚重映射函数:
GPIO_PinRemapConfig函数
简介:引脚重映射。
参数一:重映射方式(参数繁多,需见手册)
参数二:使能/失能
向PWMInit函数添加以下代码,将PA0端口重映射到PA15端口,以此在PA15实现TIM2_OC1的输出比较功能。
//引脚重映射
//启用AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//将PA0映射到PA15
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
//PA15为调试端口,需解除原有的JTAG复用功能
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
原PA0初始化函数改为PA15:
//初始化PA15
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
LED接线需改到PA15。
实验11 PWM驱动舵机
实现功能:每按一次按键舵机旋转30°,旋转到180°重置。
接线图
PWM驱动
在实验10基础上更改。
PWM.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
//修改函数编号为通道2
void PWM_SetCompare2(uint16_t Compare);
#endif
PWM.c
#include "stm32f10x.h"
void PWM_Init(void)
{
//初始化PA1
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;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
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;
//舵机要求PWM频率20Hz
//ARR=20K-1, PSC=72-1, 此时高电平时间=(CCR/1000)ms
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 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_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
//PA1口对应OC2通道
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
//修改函数编号为通道2
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
舵机驱动
Servo.h
#ifndef __SERVO_H
#define __SERVO_H
void Servo_Init(void);
void Servo_SetAngle(float Angle);
#endif
Servo.c
#include "stm32f10x.h"
#include "PWM.h"
void Servo_Init(void)
{
//初始化底层PWM
PWM_Init();
}
//设置舵机角度
void Servo_SetAngle(float Angle)
{
//角度换算到高电平持续时间
//0.5ms对应0°,2.5ms对应180°,CCR=1000对应1ms
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
主程序
#include "stm32f10x.h"
#include "Delay.h"
#include "Servo.h"
#include "OLED.h"
#include "Key.h"
uint8_t KeyNum;
float Angle;
int main(void)
{
OLED_Init();
Servo_Init();
Key_Init();
OLED_ShowString(1, 1, "Angle");
while(1)
{
//获取键值
KeyNum = Key_GetNum();
//按下按键舵机转30度
if(KeyNum == 1)
{
Angle += 30;
//转到头则重置角度
if(Angle > 180)
{
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowNum(1, 7, Angle, 3);
}
}
实验11 PWM驱动直流电机
实现功能:每按一次按键电机速度+20,速度到100反转。
接线图
建议在接线图基础上将驱动芯片的GND接地,否则电机可能不转。
PWM驱动
在实验10基础上更改。
PWM.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
//修改函数编号为通道3
void PWM_SetCompare3(uint16_t Compare);
#endif
PWM.c
#include "stm32f10x.h"
void PWM_Init(void)
{
//初始化PA2
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;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
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 = 100 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 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_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
//PA2口对应OC3通道
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
//修改函数编号为通道3
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare);
}
电机驱动
Motor.h
#ifndef __MOTOR_H
#define __MOTOR_H
void Motor_Init(void);
void Motor_SetSpeed(int8_t Speed);
#endif
Motor.c
#include "stm32f10x.h"
#include "PWM.h"
void Motor_Init(void)
{
PWM_Init();
//初始化PA4和PA5
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);
}
void Motor_SetSpeed(int8_t Speed)
{
//根据速度正负控制正反转,方向自定
if(Speed >= 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_4);
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
PWM_SetCompare2(Speed);
}
else
{
GPIO_SetBits(GPIOA, GPIO_Pin_5);
GPIO_ResetBits(GPIOA, GPIO_Pin_4);
//CCR传入正值
PWM_SetCompare2(-Speed);
}
}
主程序
#include "stm32f10x.h"
#include "Delay.h"
#include "Motor.h"
#include "OLED.h"
#include "Key.h"
uint8_t KeyNum;
int8_t Speed;
int main(void)
{
OLED_Init();
Motor_Init();
Key_Init();
Motor_SetSpeed(0);
OLED_ShowString(1, 1, "Speed:");
while(1)
{
KeyNum = Key_GetNum();
//按下按键电机速度加20
if(KeyNum == 1)
{
Speed += 20;
//超过100则反转
if(Speed > 100)
{
Speed = -100;
}
}
Motor_SetSpeed(Speed);
OLED_ShowSignedNum(1, 7, Speed, 3);
}
}