【嵌入式学习-STM32F103-TIM-输出比较】

输出比较

  • 四部分讲解内容,本文是第二部分
  • TIM输出比较
  • 输出比较通道(通用定时器)
  • 重点-PWM基本结构
    • PWM参数计算
  • 输出比较通道(高级定时器)
  • 推挽电路的工作流程
  • 舵机
    • 简介
    • 硬件电路
  • 直流电机
    • 简介
    • 硬件电路
  • PWM驱动LED呼吸灯代码
    • 硬件接线图
    • 步骤
    • 函数介绍
    • 引脚定义表
    • 引脚重映射
    • PWM代码总体思路
    • main.c
    • PWM.c
    • PWM.h
  • PWM舵机控制代码
    • 硬件接线图
    • 实验现象
    • main.c
    • PWM.c
    • PWM.h
    • Servo.c
    • Servo.h
  • PWM驱动直流电机
    • 硬件接线图
    • 实验现象
    • main.c
    • PWM.c
    • PWM.h
    • Motor.c
    • Motor.h

四部分讲解内容,本文是第二部分

1、定时器基本定时,定一个时间,然后让定时器每隔一段时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,比如要做一个时钟、秒表或者使用一些程序算法

2、定时器输出比较的功能,输出比较这个模块最常见的用途是产生PWM波形,用于驱动电机等设备,使用stm32的PWM波形来驱动舵机和直流电机的例子

3、定时器输入捕获的功能,学习使用输入捕获这个模块来测量方波频率的例子

4、定时器的编码器接口,使用这个编码器接口,能够更加方便地读取正交编码器的输出波形,在编码电机测速中,应用广泛

TIM输出比较

【嵌入式学习-STM32F103-TIM-输出比较】_第1张图片

CNT:时基单元里面的计数器

CCR: 捕获/比较寄存器(输入捕获和输出比较公用)

输出比较的基本功能:当CNT大于CCR、小于CCR或者等于CCR时,输出就会对应的置1、置0。这样就可以输出一个电平不断跳变的PWM波形

【嵌入式学习-STM32F103-TIM-输出比较】_第2张图片

【嵌入式学习-STM32F103-TIM-输出比较】_第3张图片

PWM(Pulse Width Modulation 脉冲宽度调制)的秘诀时:天下武功,为快不破——惯性系统才可用

高低电平跳变的数字信号可以等效为中间虚线所表示的模拟量,当上面电平时间长一点,下面电平时间短一点的时候,那么等效的模拟量就偏向于上面,当下面电平时间长一点,上面电平时间短一点的时候,等效的模拟量就偏向于下面。

【嵌入式学习-STM32F103-TIM-输出比较】_第4张图片
【嵌入式学习-STM32F103-TIM-输出比较】_第5张图片

当我们调控这个点亮和熄灭的时间比例时,就能让LED呈现出不同的亮度级别。

只有具有惯性的系统,才能使用PWM

占空比,它决定了PWM等效出来的模拟电压的大小。占空比越大,那等效的模拟电压就越趋近于高电平,占空比越小,那等效的模拟电压就越趋近于低电平。(线性关系

分辨率:占空比变化的精细程度

使用PWM波形,就可以在数字系统等效输出模拟量,就能实现LED控制亮度 ,电机控速等功能。

输出比较通道(通用定时器)

ref:参考信号

【嵌入式学习-STM32F103-TIM-输出比较】_第6张图片
上下两图对应
【嵌入式学习-STM32F103-TIM-输出比较】_第7张图片
【嵌入式学习-STM32F103-TIM-输出比较】_第8张图片

模式控制的输入是:CNT和CCR的大小关系,输出是REF的高低电平。

冻结:比如你正在输出PWM波,突然想暂停一会输出,就可以设置成这个模式,一旦切换为冻结模式,输出就暂停,并且高低电平也维持为暂停时刻的状态,保持不变。

PWM模式1和PWM模式2可以用于输出频率和占空比都可调的PWM波形。而PWM模式2实际上就是PWM模式1输出的取反。改变PWM模式1和PWM模式2就只是改变了REF电平的极性。

【嵌入式学习-STM32F103-TIM-输出比较】_第9张图片
输出模式可设置极性,最终输出之前也可以设置极性灵活

【嵌入式学习-STM32F103-TIM-输出比较】_第10张图片

重点-PWM基本结构

【嵌入式学习-STM32F103-TIM-输出比较】_第11张图片
以上时PWM1的例子。

首先左上角是时基单元和运行控制,最左边是时钟源选择,这里省略了,这里更新中断申请不再需要,输出PWM暂时不需要中断。

【嵌入式学习-STM32F103-TIM-输出比较】_第12张图片

配置好时基单元,CNT就可以开始不断自增运行。然后下面就是输出比较单元,总共4路。输出比较单元的最开始是CCR捕获寄存器。CCR是我们设定的,CNT不断自增运行,同时它俩还在不断比较,后面就是输出模式控制器。

上图是PWM1模式1的执行逻辑。

蓝色线: CNT的值

黄色线:ARR的值

红色线:CCR

蓝色线从0开始自增,一直增到ARR,也就是99,之后清0继续自增,在这个过程中,再设置一条红色线,这条红色线就是CCR,比如我们设置CCR为30,之后再执行以下这个逻辑。

【嵌入式学习-STM32F103-TIM-输出比较】_第13张图片
绿色线就是输出:

CNT

CNT>=CCR,置低电平

以上占空比是受到CCR值的调控的

若CCR设置高,则输出的占空比就变大。若CCR设置低一些,输出的占空比就变小。

REF:一个频率可调,占空比也可调的PWM波形

最终经过极性选择,输出使能,最终通向GPIO,完成PWM波形的输出

PWM参数计算

【嵌入式学习-STM32F103-TIM-输出比较】_第14张图片
【嵌入式学习-STM32F103-TIM-输出比较】_第15张图片
CNT从0一直加到ARR(99),总共计算了100个数,再看高电平时间,从0加到CCR(30),在等于30的瞬间就已经跳变为低电平,所以CNT从0到29是高电平,总共是30个数。

占空比=30/(99(ARR)+1)=30/100=30%

CCR的值取决于ARR的范围。ARR越大,CCR的范围就越大,对应的分辨率就越大。

输出比较通道(高级定时器)

【嵌入式学习-STM32F103-TIM-输出比较】_第16张图片

推挽电路的工作流程

MOS管左边是控制极,比如说给高电平右边两根线就导通,低电平就断开。中间是输出。
【嵌入式学习-STM32F103-TIM-输出比较】_第17张图片
如果上管导通,下管断开,那么输出就是高电平。

如果下官导通,上管断开,那么输出就是低电平。

如果上下管都导通,那就是电源短路,不允许。

如果上下管都断开,那输出就是高阻态。

如果有两个这样的推挽电路,就构成了H桥电路,可控制直流电机正反转。

如果有三个这样的推挽电路,就可以驱动三相无刷电机。

如果直接用单片机来控制的话,那就需要两个控制极,并且这两个控制极电平相反(互补)。因为上管导通,下管就必须断开。

在切换上下管导通状态时,如果在上管关断瞬间,下管就立刻打开,那可能会因为器件不理想,上管还没有完全关断,下管就已经导通了,出现了短暂的上下管同时导通的现象,这会导致功率损耗,引起器件发热,为了避免这个问题,因此有死区生成电路,它会在上管关闭的时候,延迟一小段时间,再导通下管,下管关闭的时候,延迟一小段时间,再导通上管。避免上下管同时导通的现象。

舵机

简介

执行逻辑:PWM输入到控制板,给控制板一个指定的目标角度,然后电位器检测输出轴的当前角度,如果大于目标角度,电机就会反转,如果小于目标角度,电机就会正转最终使输出轴固定在指定角度。

【嵌入式学习-STM32F103-TIM-输出比较】_第18张图片

硬件电路

【嵌入式学习-STM32F103-TIM-输出比较】_第19张图片

直流电机

简介

【嵌入式学习-STM32F103-TIM-输出比较】_第20张图片

硬件电路

【嵌入式学习-STM32F103-TIM-输出比较】_第21张图片
IN1、IN2和PWM怎么控制电机正反转和速度呢?

输入IN1、IN2、PWM、STBY为输入,STBY低电平就待机,高电平就正常工作。右边是输出,O1、O2和模式状态。如果IN1、和IN2都为低电平,两个输出就都为低电平,这样两个输出没有电压差,电机是不会转的。如果IN1和IN2全都接低电平,输出直接关闭,电机也是不会转的。IN1和IN2全高和全低电机都不会转。

==反转:==如果IN1低电平,IN2高电平,电机处于反转状态,转还是不转取决于PWM。如果PWM给高电平,输出就一低一高,有电压差,电机可以转,此时定义为反转。如果PWM给低电平,那输出两个低电平,电机不转。

如果IN1低,IN2高,那么PWM是一个不断翻转的电平信号,那电机就是快速地反转、停止、反转、停止。如果PWM频率足够快,电机就可以连续稳定地反转。速度取决于PWM的占空比。

同理正转

PWM驱动LED呼吸灯代码

硬件接线图

【嵌入式学习-STM32F103-TIM-输出比较】_第22张图片

步骤

【嵌入式学习-STM32F103-TIM-输出比较】_第23张图片
第一步:RCC开启时钟,把我们要用的TIM外设和GPIO外设的始终打开

第二步:配置时基单元,包括前面的时钟源选择

【嵌入式学习-STM32F103-TIM-输出比较】_第24张图片
第三步:配置输出比较单元,里面包括这个CCR的值、输出比较模式、极性选择、输出使能这些参数

【嵌入式学习-STM32F103-TIM-输出比较】_第25张图片
第四步:配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出的配置,PWM和GPIO的对应关系是怎样的(参考引脚定义表)

第五步:运行控制,启动计数器,就能输出PWM

【嵌入式学习-STM32F103-TIM-输出比较】_第26张图片

函数介绍

四个初始化函数,对应4个输出比较单元(输出比较通道),你需要初始化哪个通道就调用哪个函数。不同通道对应的GPIO口是不一样的。
【嵌入式学习-STM32F103-TIM-输出比较】_第27张图片
配置强制输出模式,如果你在运行中想要暂停输出波形并且强制输出高或低电平

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);

【嵌入式学习-STM32F103-TIM-输出比较】_第28张图片
单独更改CCR寄存器值的函数,在运行的时候,更改占空比,需要用到这四个函数。

【嵌入式学习-STM32F103-TIM-输出比较】_第29张图片
如果你不想把所有成员都列一遍赋值,就可以调用该函数,可防止结构体某些参数未被初始化

【嵌入式学习-STM32F103-TIM-输出比较】_第30张图片
我们主要使用PWM1模式

【嵌入式学习-STM32F103-TIM-输出比较】_第31张图片

引脚定义表

默认复用功能就是片上外设的端口和GPIO的连接关系,TIM2的引脚复用在了PA0引脚上。
TIM2的ETR引脚和通道1的引脚都是借用了PA0这个引脚的位置—>即TIM2的引脚复用在了PA0引脚上,如果我们要使用TIM2的OC1通道,输出PWM波形,那它就只能在PA0的引脚上输出,而不能任意选择引脚输出。其他外设同理。比如我们需要使用SPI1的MISO引脚,那就是PA6,如果要使用I2C2的SCL引脚,那就是PB10。

【嵌入式学习-STM32F103-TIM-输出比较】_第32张图片
重定义功能:比如你既要用USART2_TX引脚,又要用TIM2的CH3通道,它俩冲突了,没办法同时用,就可以在这个重映射列表里找一个。那将TIM2的CH3引脚PA2换到PB10引脚,避免两个外设引脚的冲突。如果重映射的列表找不到,那外设复用的GPIO就不能挪位置。配置重映射用AFIO来完成。

【嵌入式学习-STM32F103-TIM-输出比较】_第33张图片
为什么 选择复用推挽输出?
在这里插入图片描述
【嵌入式学习-STM32F103-TIM-输出比较】_第34张图片

对于普通的开漏/推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想让定时器来控制引脚,那就需要使用复用开漏/推挽输出的模式。在这里(上图),输出数据寄存器将被断开,输出控制权将转移给片上外设。而这里片上外设引脚连接的就是TIM2的CH1通道,因此,只有把GPIO设置成复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出

【嵌入式学习-STM32F103-TIM-输出比较】_第35张图片
如果要产生一个频率为1KHZ,占空比为50%,分辨率为1%的PWM波形。计算过程如下图所示。

【嵌入式学习-STM32F103-TIM-输出比较】_第36张图片
【嵌入式学习-STM32F103-TIM-输出比较】_第37张图片

想让LED呈现呼吸灯的效果,那就是不断更改CCR的值。在运行过程更改CCR,从而不断改变占空比。

TIM_SetCompare1() //函数单独更改通道1的CCR值。

【嵌入式学习-STM32F103-TIM-输出比较】_第38张图片

引脚重映射

首先要用到AFIO,就要开启AFIO的时钟

//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
//	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);    //引脚重映射配置函数,部分重映射,可将PA0换到PA15
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);  //关闭调试端口的复用

//  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;		//GPIO_Pin_15;

1、如果你想让PB15 PB3 PB4 这三个引脚当作GPIO来使用,那么就加上第一句和第三句,先打开AFIO时钟,再用AFIO将JTAG复用解除掉。

2、如果你想重映射定时器或者其他外设的复用引脚,那就加一下第一句和第二句,先打开AFIO时钟,再用AFIO重映射外设复用的引脚

3、如果你重映射的引脚正好是调试端口,那三句都加上,打开AFIO,重映射引脚,解除调试端口。

【嵌入式学习-STM32F103-TIM-输出比较】_第39张图片

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接PA1通道2,在PB1接一个按键,用来控制舵机
【嵌入式学习-STM32F103-TIM-输出比较】_第40张图片

驱动舵机的关键,输出如下图的PWM波形
【嵌入式学习-STM32F103-TIM-输出比较】_第41张图片
同一个定时器不同通道输出PWM的特点

如果通道1和通道2都想要用的话,那就把通道1和通道2都初始化,这样就能同时使用两个通道来输出两个PWM。

那对于同一个定时器的不同通道输出的PWM,它们的频率因为不同通道是共用一个计数器的,所以它们的频率必须是一样的。它们的占空比由各自的CCR决定,所以占空比可以各自设定。还有它们的相位,由于计数器更新,所有PWM同时跳变,所以它们的相位是同时跳变的。

如果驱动多个舵机或者直流电机,那使用同一个定时器不同通道的PWM就完全可以。

【嵌入式学习-STM32F103-TIM-输出比较】_第42张图片
设置CCR、PSC、ARR

舵机要求的周期是20ms,那频率就是1/20ms = 1/0.02s = 50HZ

【嵌入式学习-STM32F103-TIM-输出比较】_第43张图片
最终功能
首先给舵机建一个模块,设置一个函数,舵机设置角度,参数是0到180度,调用一下就能变为对应的角度。

实验现象

0.5ms 的高电平,对应0度

2.5ms的高电平,对应180度
【嵌入式学习-STM32F103-TIM-输出比较】_第44张图片
自己总结:通过通用定时器2模块和输出比较模块,利用CCR、ARR和PSC的关系,输出PWM有效波形,通过按键,每按一下自增角度,再映射到CCR上,从而增加占空比,扩大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驱动直流电机

硬件接线图

红色是TB6612电机驱动模块,它的第一个引脚VM,电机电源接在STLINK的5V引脚。第二个VCC,逻辑电源接在面包板的3.3V正极第三个GND接在面包板负极,AO1和AO2,电机输出端,接电机的两根线,不分正反,若对调这两根线,那么电机旋转的方向就会反过来。STBY,待机控制脚,不需要待机,直接接逻辑电源正3.3V,剩下的三个是控制引脚,AIN1和AIN2是方向控制,任意接两个GPIO,PWMA是速度控制,需要接PWM的输出脚,此处换引脚PA2,对应的是TIM2的通道3,到时候初始化通道3.

【嵌入式学习-STM32F103-TIM-输出比较】_第45张图片

实验现象

【嵌入式学习-STM32F103-TIM-输出比较】_第46张图片

人耳听到的声音的频率范围是20HZ到20KHZ

因此为了避免蜂鸣器的声音,可以加大频率,通过减小预分频器来完成,这样不会影响占空比

电机接在了定时器2的通道3上
【嵌入式学习-STM32F103-TIM-输出比较】_第47张图片

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

你可能感兴趣的:(嵌入式学习-STM32,stm32,学习,单片机)