TIM输出比较

第一个程序:PWM实现LED呼吸灯,GPIO口不是高电平就是低电平,那是如何实现控制LED亮度的呢?

第二个程序:实现通过按键控制舵机的角度,并在OLED上显示。

第三个程序:PWM驱动直流电机,通过按键控制风扇转速和方向。

简介

OC Output Compare )输出比较
输出比较可以通过比较 CNT CCR 寄存器值的关系,来对输出电平进行置 1 、置 0 或翻转的操作,用于输出一定频率和占空比的 PWM 波形
每个高级定时器和通用定时器都拥有 4 个输出比较通道
高级定时器的前 3 个通道额外拥有死区生成和互补输出的功能

 如果想做一些有电机的项目,比如智能车,机器人等项目,就就要学好这一项功能

PWM简介

PWM Pulse Width Modulation )脉冲宽度调制
具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
PWM 参数:

     频率 = 1 / TS            占空比 = TON / TS           分辨率 = 占空比变化步距

PWM的频率越快,他的等效模拟信号就越平稳,同时性能开销越大

占空比决定了PWM等效出来的模拟电压的大小,越大则等效的模拟电压越趋近于高电平 

分辨率取决于实际项目情况,一般要求不高1%即可

图1                                                                                    图2 

TIM输出比较_第1张图片TIM输出比较_第2张图片

如第一个程序所表示,LED本来只有高低电平两种情况,即本来只有完全亮和完全灭的两种情况,但是通过PWM就可以实现控制LED的亮度大小——让LED不断点亮、熄灭、点亮、熄灭······

当这个点亮和熄灭的频率足够大时,LED就不会闪烁了,而是呈现出一个中等亮度,当我们调控这个点亮和熄灭的时间比例时,就可以让LED呈现出不同的亮度级别,电机调速也是如此,通电和断电。

这其实是由于人眼的视觉暂留现象和余晖造成的,所以必须在具有惯性的系统中才能实现。

如图1所示,当高电平时间长点,低电平时间短点,等效的模拟量就更偏向于上面,反之亦然成立;

输出比较模块通道

TIM输出比较_第3张图片

下图在上图的红圈位置  

TIM输出比较_第4张图片

 通过捕获/比较寄存器输入数据进入输出模式控制器,只有当CNT>CCR1或者CNT>CCR1的情况下才可以进入输出模式控制器中,然后输出模式控制器通过设置好的输出比较模式模式输出一个参考值oc1ref,这个值可以到主模式控制器中,也可以到输出使能电路中;

TIM输出比较_第5张图片

参考值到这会经过处理才会被输出到OC1即CH1中,如果CC1P置0,则不处理参考值直接输出,如果置1,则取反再输出。

输出比较模式

TIM输出比较_第6张图片

 这里直接介绍特殊的模式,其他没有介绍的即是字面意思。

匹配时电平翻转:可以通过此模式输出一个频率可调,占空比始终为50%的PWM波形

比如这里设置CCR为0,则每次CNT更新清零时都会产生一次CNT=CCR的事件,使输出电平翻转一次,每更新两次,输出为一个周期,如下图所示,高电平和低电平的时间是始终相等的(占空比始终为50%),当我们改变定时器更新频率是,输出波形的频率也会随之改变(输出波形频率=更新频率/2)

TIM输出比较_第7张图片

PWM1和PWM2:可以用于输出频率和占空比都可调的PWM波形

一般只使用向上计数模式;PWM2即是PWM1的取反而已;改变PWM模式1和模式2,就只是改变了REF电平的极性,在比较输出通道中,还有一个极性控制装置CC1E,所以可以通过这个装置实现PWM模式1的正极性和PWM模式2的反极性最终的输出相同。

其实PWM模式1的向上计数就已经够了

PWM的基本结构

使用PWM也需要配置好时基单元

蓝:CNT的值;黄:ARR的值;红:CCR的值;绿:输出的值

可以看到输出的值在CNTCCR时,输出就变为低电平

这样就可以通过控制CCR的值来实现输出频率和占空比都可调的PWM波形REF了,再通过极性选择输出使能后,最终通向GPIO口。

TIM输出比较_第8张图片

TIM输出比较_第9张图片

占空比的变化越细腻越好

如果要输入一个频率为1kHz,占空比可任意调节,且分辨率为1%的PWM波形

——ARR=99,

舵机简介

舵机是一种根据输入 PWM 信号占空比来控制输出角度的装置 
输入 PWM 信号要求:周期为 20ms ,高电平宽度为 0.5ms~2.5ms

 TIM输出比较_第10张图片TIM输出比较_第11张图片TIM输出比较_第12张图片

 工作流程:PWM输入一个PWM信号,电位器读取当前舵机输出轴转角,如果大于输入的信号,则反转,如果小于输入的信号,则正转。

这里是把PWM当做一个通信协议。

图1:棕色为电源负,红色为电源正,橙色为信号线。

TIM输出比较_第13张图片

TIM输出比较_第14张图片

 如果单独供电的话,供电的负极就要和STM32共地,正极接在5V供电引脚上。我们这里可以直接使用ST-Link来供电

直流电机

直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
TB6612 是一款双路 H 桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

TIM输出比较_第15张图片TIM输出比较_第16张图片TIM输出比较_第17张图片

TB6612硬件驱动电路的硬件电路

TIM输出比较_第18张图片TIM输出比较_第19张图片

PWMA、AIN1和AIN2控制A01和A02的输出;

PWMA引脚要接到PWM信号输出端,AIN2和AIN1任意接两个GPIO口,这三个引脚给个低功率的控制信号,驱动电路就会从VM汲取电流,来输出到电机,从而实现低功率控制信号控制大功率设备。

如果不需要待机功能,则STBY直接接供电口,如果需要待机功能,可以接在GPIO口来控制高低电平。


代码实操:

PWM驱动LED呼吸灯

注意LED的接线,正极接在PA0,负极接在正极供电口,这样是高电平点亮,低电平熄灭。

即正极性驱动的方法,这样观察更直观(即根据PWM模式1的特点,CNT

TIM输出比较_第20张图片

进入代码编写过程,建立好模块化编程文件

根据下图编写初始化函数

TIM输出比较_第21张图片

接下来介绍一下TIM中有关比较输出的函数

现在TIM的头文件中查看有什么函数我们能用上的

这些就是配置我们输出比较单元的函数,因为有四个输出比较单元所以有四个函数相对应

(很重要,需要掌握)

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

这个是给输出比较结构体赋默认值的函数

void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);

 这些是用来单独设置输出比较极性的函数,带N的就是高级定时器里的互补通道的配置

这些函数就是用来单独更改输出极性的

void TIM_OC1PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC1NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC2PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC2NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC3PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
void TIM_OC3NPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCNPolarity);
void TIM_OC4PolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);

用来单独修改输出使能参数的

void TIM_CCxCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCx);
void TIM_CCxNCmd(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_CCxN);

用来单独修改输出比较模式的函数 

void TIM_SelectOCxM(TIM_TypeDef* TIMx, uint16_t TIM_Channel, uint16_t TIM_OCMode);

这四个是用来单独更改CCR寄存器值的函数 (我们在运行时,更改占空比就需要用到这些函数)

(很重要,需要掌握)

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

 仅高级定时器使用,在使用高级定时器输出PWM时,就需要调用这个函数,使能主输出,否则PWM将不能正常输出

void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalState NewState);

 以下是用来配置强制输出模式的(如果在运行过程中想要暂停输出波形并且强制输出为高或者低电平,使用该函数)其实可以通过设置占空比为100%或者0%来代替强制输出模式(不需要掌握)

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

 配置CCR寄存器的预装功能(在写入值时不会立即生效,而是更新事件后才会生效)(不需要掌握)

void TIM_OC1PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC3PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
void TIM_OC4PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

配置快速使能(不需要掌握)

void TIM_OC1FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC2FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC3FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);
void TIM_OC4FastConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCFast);

外部事件是清除REF信号(不需要掌握)

void TIM_ClearOC1Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC2Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC3Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);
void TIM_ClearOC4Ref(TIM_TypeDef* TIMx, uint16_t TIM_OCClear);

1、RCC开启时钟,即打开TIM和GPIO外设的时钟打开;

2、配置时基单元和时钟源选择; 

打开TIM的时钟我们之前编写过,直接复制

    //开启TIM2的时钟
	//TIM2是APB1总线的外设,所以
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	//选择时基单元的时钟,选择为内部时钟
	//如果不写选择时钟源的代码,默认为内部时钟
	TIM_InternalClockConfig(TIM2);
	
	//时基单元初始化
	TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
	//指定时钟分频(与本次操作没太大关系)
	TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式
	TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	
	//下面三个就是时基单元里每个关键寄存器的参数
	//按照公式CK_CNT_OV = CK_PSC / (PSC + 1) / (ARR + 1)
	//定时频率 = 72M/(PSC+1)/(ARR+1)
	//定时1s,即定时频率为1Hz,则Period给10000,Prescaler给7200
	//预分频是对72M进行7200分频,即10k的计数频率,在10k的频率下计10000个数,即1s
	//因为两个参数都有一个数的偏差,所以都需要-1
	//ARR自动重装载器的值
	TimeBaseInitStructure.TIM_Period = 10000 - 1;
	//PSC预分频器的值
	TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
	//重复计数器的值(高级计数器特有的,我们没有直接赋0)
	TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TimeBaseInitStructure);
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);

	//启动定时器
	TIM_Cmd(TIM2, ENABLE);
	

3、配置输出比较单元(CCR和CCR的模式和极性选择和输出使能); 

  打开输出比较通道(不同的通道对应的GPIO口也不一样,这里我们是PA0口,对应的就是输出通道1)

	//打开比较输出通道
	//这个结构体成员很多,包括了高级定时器才会用到的
	//我们这里仅仅配置我们需要用到的结构体成员即可
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	//注意,当我们想把高级定时器当做通用定时器来使用时
	//因为没有配置高级定时器需要用到的结构体成员,这会导致出错
	//所以我们需要调用一下结构体初始化函数来把每个结构体成员初始化后
	//再对特定的结构体成员重新赋值即可
	TIM_OCStructInit(&TIM_OCInitStructure);
	//比较输出的模式(PWM模式1)
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	//比较输出的极性(高极性,REF不翻转) 
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	//设置输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	//设置CCR捕获比较寄存器
	//与PSC、ARR共同决定输出PWM的周期和占空比
	TIM_OCInitStructure.TIM_Pulse = ;
	
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	

4、配置GPIO,把PWM对应的GPIO口,初始化为复用推挽输出的配置

配置好后,我们就可以在TIM2的OC1通道上输出PWM,但是他需要通过GPIO口来输出PWM波形,参考引脚对照表可知TIM2的OC1(CH1)通道在GPIO的PA0输出PWM,不能任意选择引脚输出。

我们可以通过重定义(重映射)来更改引脚使用位置

比如我们既想使用USART2_TX,又想用TIM2_CH3引脚时,就可以通过AFIO来配置重映射来使用PB10输出TIM2_CH3,这样就可以避免引脚冲突,但如果重映射没有标明,则该引脚不可以重映射。

TIM输出比较_第22张图片

 配置GPIO

	//配置TIM2的CH1对应的GPIO口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;

这里选择复用推挽输出的原因是:如下图所示,当选择为复用推挽输出后,输出数据寄存器就会与输出驱动器断开,反而把输出控制权转移给片上外设,这里片上外设引脚连接的就是TIM2的CH1通道,只有把GPIO设置为复用推挽输出,引脚的控制权才能交给片上外设,PWM波形才能通过引脚输出。

TIM输出比较_第23张图片

5、运行控制(启动计数器)

	//启动定时器
	TIM_Cmd(TIM2, ENABLE);

调整参数(PSC,ARR,CCR)

要产生一个频率为1KHz,占空比为50%,分辨率为1%的PWM波形

依据以下公式(CK_PSC=72M)

TIM输出比较_第24张图片

 计算可得:ARR=99,CCR=50,PSC=720

配置完成后再在主函数中调用,接上STM32可以发现LED亮了,调节CCR的值,可以发现LED亮度有所变化,这就代表调节占空比可以调节其亮度。(可以使用示波器,把波形显示出来)

接下来就需要实现循环更改CCR的值来调节占空比(ARR不变的情况下),从而控制LED的亮度实现呼吸灯。

这就需要用到这个单独调节CCR的函数,我们可以写个函数封装一下

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void PWM_CCR(uint16_t CCR)
{
	//单独更改CCR的值
	TIM_SetCompare1(TIM2, CCR);
}

在主函数中调用 

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

uint8_t compare;

int main(void)
{
	OLED_Init();
	PWM_Init();
	while(1)
	{
		for(compare=0; compare<=100; compare++)
		{
			PWM_CCR(compare);
			//一定要加延时函数,否则变化太快,肉眼看不出来
			Delay_ms(10);
		}
		for(compare=0; compare<=100; compare++)
		{
			PWM_CCR(100-compare);
			Delay_ms(10);
		}
	}
}

重定义引脚功能(慎用)

江科大P16_30min

PWM驱动舵机

如图所示接好线

TIM输出比较_第25张图片注意这里我们舵机接收输出的端口为PA1,所以初始化GPIO时要注意,且PA1时通道2的所以

在配置TIM2的比较输出时,要注意使用OC2Init函数

	TIM_OC2Init(TIM2, &TIM_OCInitStructure);

同时修改CCR的值的函数也需要修改为SetCompare2

void PWM_CCR(uint16_t CCR)
{
	//单独更改CCR的值
	TIM_SetCompare2(TIM2, CCR);
}

再来设定CCR,ARR,CNT的值

根据公式和舵机的信号要求,计算得出比较适用的值

TIM输出比较_第26张图片

TIM输出比较_第27张图片

 这里设置PSC=71,ARR=20k,然后当CCR的值为500时,占空比就是0.5ms,CCR为2500,占空比为2.5ms,通过调节CCR的函数来实现

自己写的代码:

实现按一下转45°

void KeyfPWM_Init(void)
{
	//初始化时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure1;
	GPIO_InitStructure1.GPIO_Mode = GPIO_Mode_IPU;
	//上拉输入
	GPIO_InitStructure1.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStructure1.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure1);
}

uint8_t KeyfPWM_Num(void)
{
	static 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++;
		if(KeyNum>=6)
		{
			KeyNum=1;
		}
	}
	return KeyNum;
}

主函数中

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"


uint8_t compare;
uint8_t KeyNum;
int main(void) 
{
	OLED_Init();
	PWM_Init();
	KeyfPWM_Init();
	
	while(1)
	{
		KeyNum=KeyfPWM_Num();
		switch(KeyNum)
		{
			case 1:PWM_CCR(500);break;
			case 2:PWM_CCR(1000);break;
			case 3:PWM_CCR(1500);break;
			case 4:PWM_CCR(2000);break;
			case 5:PWM_CCR(2500);break;
		}
	}
}

还有优化解,实现按按键控制转的角度

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Servo_Init(void)
{
	PWM_Init();
	
}

void Servo_SetAngle(float Angle)
{
	//舵机旋转角度为0~180,想要输入多少就转多少,就需要进行缩放
	//且0°对应CCR=500,180对应CCR=2500,则最大最小差值为2000
	//Angle/180*2000即可得到目标比例,再加一个偏移500
	//这样就完成了0~180到500~2500的映射
	//映射是线性的,所以所得值是一一对应的
	PWM_CCR(Angle / 180 * 2000 + 500);
}

这样就完成了对控制电机角度的封装,再使用舵机时调用这两个函数会更直观。

再在主函数中调用一下

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "Servo.h"
#include "Key.h"



uint8_t compare,KeyNum,Angle;

int main(void)
{
	OLED_Init();
	Servo_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);
	}
}

就可以得到没按一下按键转30°的舵机了

总体:

PWM.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

void PWM_Init(void)
{
	//开启TIM2的时钟
	//TIM2是APB1总线的外设,所以1	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	//配置TIM2的CH1对应的GPIO口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_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	//选择时基单元的时钟,选择为内部时钟
	//如果不写选择时钟源的代码,默认为内部时钟
	TIM_InternalClockConfig(TIM2);
	
	//时基单元初始化
	TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
	//指定时钟分频(与本次操作没太大关系)
	TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式
	TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	
	//下面三个就是时基单元里每个关键寄存器的参数
	//按照公式CK_CNT_OV = CK_PSC / (PSC + 1) / (ARR + 1)
	//定时频率 = 72M/(PSC+1)/(ARR+1)
	//定时1s,即定时频率为1Hz,则Period给10000,Prescaler给7200
	//预分频是对72M进行7200分频,即10k的计数频率,在10k的频率下计10000个数,即1s
	//因为两个参数都有一个数的偏差,所以都需要-1
	//ARR自动重装载器的值
	TimeBaseInitStructure.TIM_Period = 20000 - 1;
	//PSC预分频器的值
	TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
	//重复计数器的值(高级计数器特有的,我们没有直接赋0)
	TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TimeBaseInitStructure);
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);

	//打开比较输出通道
	//这个结构体成员很多,包括了高级定时器才会用到的
	//我们这里仅仅配置我们需要用到的结构体成员即可
	TIM_OCInitTypeDef TIM_OCInitStructure;
	
	//注意,当我们想把高级定时器当做通用定时器来使用时
	//因为没有配置高级定时器需要用到的结构体成员,这会导致出错
	//所以我们需要调用一下结构体初始化函数来把每个结构体成员初始化后
	//再对特定的结构体成员重新赋值即可
	TIM_OCStructInit(&TIM_OCInitStructure);
	//比较输出的模式(PWM模式1)
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	//比较输出的极性(高极性,REF不翻转) 
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	//设置输出使能
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	//设置CCR捕获比较寄存器
	//与PSC、ARR共同决定输出PWM的周期和占空比
	TIM_OCInitStructure.TIM_Pulse = 0;
	
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);
	
	
	
	//启动定时器
	TIM_Cmd(TIM2, ENABLE);
	
}

void PWM_CCR(uint16_t CCR)
{
	//单独更改CCR的值
	TIM_SetCompare2(TIM2, CCR);
}

 Servo.c

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Servo_Init(void)
{
	PWM_Init();
	
}

void Servo_SetAngle(float Angle)
{
	//舵机旋转角度为0~180,想要输入多少就转多少,就需要进行缩放
	//且0°对应CCR=500,180对应CCR=2500,则最大最小差值为2000
	//Angle/180*2000即可得到目标比例,再加一个偏移500
	//这样就完成了0~180到500~2500的映射
	//映射是线性的,所以所得值是一一对应的
	PWM_CCR(Angle / 180 * 2000 + 500);
}

main.c

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Servo.h"
#include "Key.h"



uint8_t compare,KeyNum,Angle;

int main(void)
{
	OLED_Init();
	Servo_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);
	}
}

PWM驱动直流电机

如图所示

驱动模块电机电源VM接ST-Link5V供电口,VCC逻辑电源接3.3V,AO1和AO2接电机;

PWMA是速度控制,需要接PWM的输出脚,图中接在PA2,是通道3,需要初始化;AIN1和AIN2时是方向控制,接任意GPIO口,STBY待机直接接高电平。

TIM输出比较_第28张图片

 正反转与IN1和IN2的关系

TIM输出比较_第29张图片

代码主体

motor.c

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

//用宏定义方便修改端口
#define IN1 GPIO_Pin_4
#define IN2 GPIO_Pin_5

void Motor_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	//驱动模块的方向控制接在了PA5和PA4,需要初始化
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = IN1 | IN2;
	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, IN2);
		GPIO_ResetBits(GPIOA, IN1);
		PWM_CCR(Speed);
	}
	else
	{
		GPIO_ResetBits(GPIOA, IN2);
		GPIO_SetBits(GPIOA, IN1);
		//因为CCR的值不可以为负数
		//负数Speed仅仅是用来判断方向
		PWM_CCR(-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();
		if(KeyNum==1)
		{
			Speed+=25;
			if (Speed > 100)
			{
				Speed = -100;
			}
		}
		Motor_SetSpeed(Speed);
		OLED_ShowSignedNum(1,7,Speed,3);
	}
	
}

因为在运行的时候,会发出蜂鸣器的生意,可以通过提高频率来消除

减小预分频器值既可以提高频率,又不影响占空比,且人耳朵能听到频率最大为20kHz

设置预分频器值为36-1 

你可能感兴趣的:(单片机,嵌入式硬件)