目录
一:TIM输出比较功能
1: 简历
2:PWM波形
3:输出比较模式
4:参数计算
5:PWM基本结构
6:输出比较功能的实际应用
A:PWM驱动LED呼吸灯
1:连接图
2:步骤
3:函数介绍
4:代码
B:PWM驱动舵机
1:连接图
2:舵机介绍
3:步骤
4:代码
C:PWM驱动直流电机
1:连接图
2:电机与驱动电路
3:代码
OC(Output Compare)输出比较
输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
每个高级定时器和通用定时器都拥有4个输出比较通道
高级定时器的前3个通道额外拥有死区生成和互补输出的功能
PWM(Pulse Width Modulation)脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
PWM参数: 频率 = 1 / TS 占空比 = TON / TS 分辨率 = 占空比变化步距
配置比较单元时使用TIM_OCXInit()
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择PWM1的模式
1 :RCC开启时钟,(TIM外设---RCC_APB1PeriphClockCmd和GPIO外设----RCC_APB2PeriphClockCmd的时钟打开)
2: 配置GPIO----GPIO_Init (最后写第二步)
3: 时钟选择----TIM_InternalClockConfig(内部)
4: 配置时基单元-----TIM_TimeBaseInit()
5: 配置输出比较单元----TIM_OC1Init()
6:启动定时器-----TIM_Cmd
在stm32f10x tim.h文件中的函数-----配置输出比较单元函数
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);
TIM_OCXInit : 配置输出比较单元
在stm32f10x tim.h文件中的函数
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
TIM_OCStructInit : 给输出比较结构体赋一个默认的值
在stm32f10x tim.h文件中的函数-----改变占空比函数
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);
TIM_SetCompareX : 来单独更改CCR寄存器值的函数,可以更改占空比
每次调用修改它的占空比,以达到呼吸灯的作用
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
void PWM_init(){
//第一步,RCC开启时钟,把我们要用的TIM外设和GPIO外设的时钟打开
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//第二步,配置GPIO
GPIO_InitTypeDef GPIO_INITStruct;
GPIO_INITStruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_INITStruct.GPIO_Pin=GPIO_Pin_0 ;
GPIO_INITStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_INITStruct);
//第三步,时钟源选择(内部时钟)
TIM_InternalClockConfig(TIM2);
//第四步,配置时基单元
TIM_TimeBaseInitTypeDef TIM_INITStruct;
TIM_INITStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_INITStruct.TIM_CounterMode=TIM_CounterMode_Up; 向上计数
TIM_INITStruct.TIM_Period=100-1; ///自动重装载寄存器ARR
TIM_INITStruct.TIM_Prescaler=720-1; // //预分频器PSC
TIM_INITStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_INITStruct);
//第五步,配置输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给输出比较结构体赋一个默认的值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择PWM1的模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //有效电频为低电频
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
//第六步,启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
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);
}
}
}
为什么选择TIM_OC1Init
在这里可以看到,有TM2-CH1_ETR它是在这个PAO这一行的 , 这就说明,TIM2的ETR引脚和通道1的引脚都是借用了PAO这个引脚的位置的. 换句话说就是TM2的引脚复用在了PAO引脚上 , 所以说如果我们要使用TIM2的OC1也就是CH1通道,输出PWM, 那它就只能在PA0的引脚上输出一而不能任意选择引脚输出; PA0输出PWM波形
为什么选择TIM_SetCompare1
因为使用的是TIM_OC1Init CH1通道
TIM_SetCompareX : 来单独更改CCR寄存器值的函数,可以更改占空
引脚重定义
那首先,要使用AFIO,就要开启AFIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
引脚重映射
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//部分重映射1
将PA0改为PA15
因为PA15在它上电后已经默认复用为了调试端回TDIT,如果想让他作为普通的GPIO或者复用定时器的通道, 需要先关闭调试端口的复用(需要谨慎)
//PA15、PB3、PB4这三个引脚当做GPIO来使用的话 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//解除JTAG复用(解除重映射端口) //想重映射定时器或者其他外设的复用引脚 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//GPIO_PartialRemap1_TIM2部分重映射1 //如果重新映射的使调式端口使用 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//GPIO_PartialRemap1_TIM2部分重映射1 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);解除JTAG复用(解除重映射端口)
GPIO调式 选择复用推挽输出
GPIO_INITStruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
输出控制权将转移给片上外设,PWM波形才能通过引脚输出
计算
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
CK_PSC=72MHZ PSC=TIM_INITStruct.TIM_Prescaler=; // //预分频器PSC
ARR= TIM_INITStruct.TIM_Period=; ///自动重装载寄存器ARR
CRR= TIM_OCInitStructure.TIM_Pulse = ; //CCR
分辨率 = 占空比变化步距
频率为1KHz,占空比为50%,分辨率为1%的PWM波形
红色-----接USE5V(3v带不动)
橙色-----信号线 棕色---地线
舵机是一种根据输入PWM信号占空比来控制输出角度的装置
输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms
高电平宽度=某电频的持续时间
CCR=高电频
舵机的控制一般需要一个20ms的时基脉冲(周期),该脉冲的高电平部分一般为0.5ms~2.5ms范围内的角度控制脉冲部分。以180度角度舵机为例,那么对应的控制关系是这样的:
0.5ms--------------0度;
1.0ms------------45度;
1.5ms------------90度;
2.0ms-----------135度;
2.5ms-----------180度;
1 :RCC开启时钟,(TIM外设---RCC_APB1PeriphClockCmd和GPIO外设----RCC_APB2PeriphClockCmd的时钟打开)
2: 配置GPIO----GPIO_Init (最后写第二步)
3: 时钟选择----TIM_InternalClockConfig(内部)
4: 配置时基单元-----TIM_TimeBaseInit()
5: 配置输出比较单元----TIM_OC2Init()
6:启动定时器-----TIM_Cmd
这个使用的是CH2通道, 上面的LED使用的是CH2通道
使用: TIM_SetCompareX和TIM_OCXInit 函数都有换改为相对应的X值
X应为2.见下面的引脚定义表
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"
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_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;
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; //CCR
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare);
}
void Servo_Init(void)
{
PWM_Init();
}
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500);
}
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
Delay_ms(20);
KeyNum = 1;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
uint8_t KeyNum;
float Angle;
int main(void)
{
OLED_Init();
Servo_Init();
Key_Init();
OLED_ShowString(1, 1, "Angle:");
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
Angle += 30;
if (Angle > 180)
{
Angle = 0;
}
}
Servo_SetAngle(Angle);
OLED_ShowNum(1, 7, Angle, 3);
}
}
计算
TIM_SetCompareX : 来单独更改CCR寄存器值的函数,可以更改占空比
引脚定义表
舵机的信号线与PA1连接所以使用的是CH2通道2,使用X为2; PA1输出PWM波形
直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,
当电极反接时,电机反转直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向
电机驱动详情见: 51:电机(ULN2003D)这一篇
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "PWM.h"
#include "motor.h"
#include "Key.h"
void PWM_init(){
//第一步,RCC开启时钟,把我们要用的TIM外设和GPIO外设的时钟打开
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//第二步,配置GPIO
GPIO_InitTypeDef GPIO_INITStruct;
GPIO_INITStruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
GPIO_INITStruct.GPIO_Pin=GPIO_Pin_2 ;
GPIO_INITStruct.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_INITStruct);
//第三步,时钟源选择(内部时钟)
TIM_InternalClockConfig(TIM2);
//第四步,配置时基单元
TIM_TimeBaseInitTypeDef TIM_INITStruct;
TIM_INITStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_INITStruct.TIM_CounterMode=TIM_CounterMode_Up; 向上计数
TIM_INITStruct.TIM_Period=100-1;
TIM_INITStruct.TIM_Prescaler=720-1;
TIM_INITStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_INITStruct);
//第五步,配置输出比较单元
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给输出比较结构体赋一个默认的值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择PWM1的模式
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //有效电频为低电频
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
//第六步,启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare);
}
void motor_init(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_INITstruct;
GPIO_INITstruct.GPIO_Mode=GPIO_Mode_IPU;//GPIO_Mode_Out_PP //GPIO_Mode_IPU
GPIO_INITstruct.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5;
GPIO_INITstruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_INITstruct);
PWM_init();
}
void motor_serspeed(int8_t speed){
if(speed >=0){
GPIO_SetBits(GPIOA,GPIO_Pin_5); //高电频
GPIO_ResetBits(GPIOA,GPIO_Pin_4); //低电频
PWM_SetCompare3(speed);
}
else
{
GPIO_SetBits(GPIOA,GPIO_Pin_4); //高电频
GPIO_ResetBits(GPIOA,GPIO_Pin_5); //低电频
//传穿过来的speed为负数,要变为正,所以要加一个负号;
PWM_SetCompare3(-speed);
}
}
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
Delay_ms(20);
KeyNum = 1;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
uint8_t key_num;
int8_t speed; //速度必须是有符号的
int main(void)
{
Key_Init();
OLED_Init();
motor_init();
OLED_ShowString(1,1,"speed:");
while (1)
{
key_num=Key_GetNum();
if (key_num==1){
speed+=20;
if(speed>100){
speed=-100;
}
}
motor_serspeed(speed);
OLED_ShowSignedNum(1, 7, speed, 3);
}
}