目录

前言

一、硬件元器件介绍

1.舵机

2.直流电机驱动

二、C语言编程步骤

 1.开启时钟

2.配置输出的GPIO口

3.配置时基单元

 4.初始化输出比较通道

5.开启定时器

实践项目

1.PWM驱动LED呼吸灯

2.PWM驱动舵机

3.PWM驱动直流电机


前言

        本期我们就开始去进行TIM定时器的输出比较功能的实操了,如果有什么疑惑的可以去看一下上一期理论的知识点(上一期链接:stm32入门-----TIM定时器(PWM输出比较——上)-CSDN博客)这里就分为三个部分的项目,分别是PWM驱动LED呼吸灯,PWM驱动直流电机转动和PWM驱动舵机。(视频:[6-4] PWM驱动LED呼吸灯&PWM驱动舵机&PWM驱动直流电机_哔哩哔哩_bilibili

一、硬件元器件介绍

1.舵机

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

 舵机里面本身就是有一个驱动电路板的,所以我们只需要给这个舵机通上电以及输入PWM波形就可以控制这个舵机了,内部电路结构就不需要多去了解。根据输入信号的高电平占比不同,舵机就会转动不同的角度(舵机不是一直转动的,当且仅当输入信号PWM发生改变的时候才会转动,转动就之转一定角度,转玩了就保持静止状态)。

电路图:

2.直流电机驱动

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

由于直流电机不像舵机那样有驱动电路,所以我们需要外接一个驱动电路的芯片这里,我们就选择TB6612来驱动直流电机,驱动芯片不知这一种,还是有挺多的,比如L298这个还是挺常见的。

TB6612驱动是双路的,分为AO和BO口,下面电路图展示了这个驱动电路的接线方式,VM是用来接外接电源的,这个可以给到高电压的电源,这个驱动电路可以实现低电压PWM信号驱动高电压电机,AIN2,AIN1是表示转动的方向,PWMA是表示输入的PWM信号。

硬件电路:

二、C语言编程步骤

看到下图的流程,我们只需要把这些通路给打开就行了,这样就可以实现定时器的输出比较功能。

 1.开启时钟

时钟包括定时器的时钟和GPIO口的输出时钟

 //1.开启定时器时钟,TIM2总线是为APB1的
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    TIM_InternalClockConfig(TIM2);  //给TIM2选择时钟 为内部时钟,定时器默认是使用内部的时钟,不写这一行也行的

2.配置输出的GPIO口

 //2.配置GPIO口, PA0 为输出口
	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);

3.配置时基单元

  • PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1)
  • PWM占空比:  Duty = CCR / (ARR + 1)
  • PWM分辨率:  Reso = 1 / (ARR + 1)

根据上面这些公式我们来去计算出想要的波形效果,再去进行配置。 

 //3.配置时基单元
    TIM_TimeBaseInitTypeDef TIM_timebasestruct;
    //下面两个是运行控制操作值
    TIM_timebasestruct.TIM_ClockDivision=TIM_CKD_DIV1;//对输入信号进行初步分频,内部时钟72Mhz信号
    TIM_timebasestruct.TIM_CounterMode=TIM_CounterMode_Up;//计数方式选择向上计数
    //以下三个是时基单元里面的实际参数值
    /* 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
					       = CK_PSC / (PSC + 1) / (ARR + 1) */
    TIM_timebasestruct.TIM_Period=100-1;  //计数器的重装值,目标值  ARR
    TIM_timebasestruct.TIM_Prescaler=720-1; //预分频器的值          PSC
    TIM_timebasestruct.TIM_RepetitionCounter=0;//重复计数功能,这个是高级计数器才有的,当前选择的是通用计数器,设置0即可
	TIM_TimeBaseInit(TIM2,&TIM_timebasestruct);

 4.初始化输出比较通道

//4.初始化输出比较通道,这里以OC1通道为示例
    TIM_OCInitTypeDef TIM_ocinitstruct;//配置输出比较通道结构体
    TIM_OCStructInit(&TIM_ocinitstruct);//给这个结构体设置默认初始值,避免改为使用高级定时器的时候出错
    //下面是使用通用定时器的部分,就拉出来单独修改,其余是高级定时器的东西就不去改,保持默认值就行了
    TIM_ocinitstruct.TIM_OCMode=TIM_OCMode_PWM1;//PWM选择输出比较工作模式,八选一
    TIM_ocinitstruct.TIM_OCPolarity=TIM_OCPolarity_High;//选择输出极性,当ref为高电平的时候就输出保持不变,还有其他两种一个是低电平,另一个是翻转
    TIM_ocinitstruct.TIM_OutputState=TIM_OutputState_Enable;//输出使能,开启输出比较通道使能
    TIM_ocinitstruct.TIM_Pulse=0;  //CCR的初始值,我们要去进行比较的数
    TIM_OC1Init(TIM2,&TIM_ocinitstruct);

已知是使用内部定时器,频率72MHz, 这里我们可以计算出输出波形的频率为:72MHz / 720*100=100Hz ,PWM占空比:0 / 100 =0(当前初始值为0的)

5.开启定时器

//5.开启定时器
    TIM_Cmd(TIM2,ENABLE);//开启定时器

实践项目

本次项目的代码都在百度网盘,可自行下载。

链接:https://pan.baidu.com/s/10fNUjkPm1WmvSoK0gkCiRA?pwd=0721 
提取码:0721

1.PWM驱动LED呼吸灯

先看现象:

LED呼吸灯

电路连接图:

项目文件:

PWM.c代码:

#include "stm32f10x.h"                  // Device header

void PWM_init(){
      //1.开启定时器时钟,TIM2总线是为APB1的
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	TIM_InternalClockConfig(TIM2);  //给TIM2选择时钟 为内部时钟,定时器默认是使用内部的时钟,不写这一行也行的

   // RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
   // GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//设置重映射
    //GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //解除像PA15,PB3,PB4 这些端口的调试功能,变为普通的GPIO

    //2.配置GPIO口, PA0 为输出口
	GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //使用复用推挽输出,因为这里不是输出寄存器控制的,是片上外设定时器操作的,所以要用到复用推挽输出
	GPIO_initstruct.GPIO_Pin=GPIO_Pin_0;  //重新映射到15口 GPIO_initstruct.GPIO_Pin=GPIO_Pin_15;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_initstruct);
 
    //3.配置时基单元
    TIM_TimeBaseInitTypeDef TIM_timebasestruct;
    //下面两个是运行控制操作值
    TIM_timebasestruct.TIM_ClockDivision=TIM_CKD_DIV1;//对输入信号进行初步分频,内部时钟72Mhz信号
    TIM_timebasestruct.TIM_CounterMode=TIM_CounterMode_Up;//计数方式选择向上计数
    //以下三个是时基单元里面的实际参数值
    /* 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
					       = CK_PSC / (PSC + 1) / (ARR + 1) */
    TIM_timebasestruct.TIM_Period=100-1;  //计数器的重装值,目标值  ARR
    TIM_timebasestruct.TIM_Prescaler=720-1; //预分频器的值          PSC
    TIM_timebasestruct.TIM_RepetitionCounter=0;//重复计数功能,这个是高级计数器才有的,当前选择的是通用计数器,设置0即可
	TIM_TimeBaseInit(TIM2,&TIM_timebasestruct);
 
    //4.初始化输出比较通道,这里以OC1通道为示例
    TIM_OCInitTypeDef TIM_ocinitstruct;//配置输出比较通道结构体
    TIM_OCStructInit(&TIM_ocinitstruct);//给这个结构体设置默认初始值,避免改为使用高级定时器的时候出错
    //下面是使用通用定时器的部分,就拉出来单独修改,其余是高级定时器的东西就不去改,保持默认值就行了
    TIM_ocinitstruct.TIM_OCMode=TIM_OCMode_PWM1;//PWM选择输出比较工作模式,八选一
    TIM_ocinitstruct.TIM_OCPolarity=TIM_OCPolarity_High;//选择输出极性,当ref为高电平的时候就输出保持不变,还有其他两种一个是低电平,另一个是翻转
    TIM_ocinitstruct.TIM_OutputState=TIM_OutputState_Enable;//输出使能,开启输出比较通道使能
    TIM_ocinitstruct.TIM_Pulse=0;  //CCR的值,我们要去进行比较的数
    TIM_OC1Init(TIM2,&TIM_ocinitstruct);

    //5.开启定时器
    TIM_Cmd(TIM2,ENABLE);//开启定时器
   
}

//寄存器设置CCR的值,我们要去进行比较的数
void PWM_Setcompare1(uint16_t Compare){
    TIM_SetCompare1(TIM2,Compare);


}

PWM.h代码:

#ifndef __PWM_H
#define __PWM_H

void PWM_init();
void PWM_Setcompare1(uint16_t Compare);
#endif // !1

main.c代码:

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

int main(void)
{	
	LED_init();
	OLED_Init();
	PWM_init();
	OLED_ShowString(1,1,"jjjjj:");
	uint8_t i;
	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);
		}
		 
	}
}


2.PWM驱动舵机

现象:

PWM驱动舵机

实际电路连接图:

项目文件:

其中Servo.c和Servo.h文件是用来封装PWM的。

PWM.c代码:

#include "stm32f10x.h"                  // Device header

void PWM_init(){
      //1.开启定时器时钟,TIM2总线是为APB1的
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    //配置GPIO口 PA0
	GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //使用复用推挽输出,因为这里不是输出寄存器控制的,是片上外设定时器操作的,所以要用到复用推挽输出
	GPIO_initstruct.GPIO_Pin=GPIO_Pin_1; //重新映射到15口 GPIO_initstruct.GPIO_Pin=GPIO_Pin_15;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_initstruct);

    TIM_InternalClockConfig(TIM2);  //给TIM2选择时钟 为内部时钟,定时器默认是使用内部的时钟,不写这一行也行的
	
    //2.配置时基单元
    TIM_TimeBaseInitTypeDef TIM_timebasestruct;
    //下面两个是运行控制操作值
    TIM_timebasestruct.TIM_ClockDivision=TIM_CKD_DIV1;//对输入信号进行初步分频,内部时钟72Mhz信号
    TIM_timebasestruct.TIM_CounterMode=TIM_CounterMode_Up;//计数方式选择向上计数
    //以下三个是时基单元里面的实际参数值
    /* 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
					       = CK_PSC / (PSC + 1) / (ARR + 1) */
    TIM_timebasestruct.TIM_Period=20000-1;  //计数器的重装值,目标值 //ARR
    TIM_timebasestruct.TIM_Prescaler=72-1; //预分频器的值         //PSC
    TIM_timebasestruct.TIM_RepetitionCounter=0;//重复计数功能,这个是高级计数器才有的,当前选择的是通用计数器,设置0即可
	TIM_TimeBaseInit(TIM2,&TIM_timebasestruct);

    //初始化输出比较通道,这里以OC1通道为示例
    TIM_OCInitTypeDef TIM_ocinitstruct;//配置输出比较通道结构体
    TIM_OCStructInit(&TIM_ocinitstruct);//给这个结构体设置默认初始值,避免改为使用高级定时器的时候出错
    //下面是使用通用定时器的部分,就拉出来单独修改,其余是高级定时器的东西就不去改,保持默认值就行了
    TIM_ocinitstruct.TIM_OCMode=TIM_OCMode_PWM1;//PWM选择输出比较工作模式,八选一
    TIM_ocinitstruct.TIM_OCPolarity=TIM_OCPolarity_High;//选择输出极性,当ref为高电平的时候就输出保持不变,还有其他两种一个是低电平,另一个是翻转
    TIM_ocinitstruct.TIM_OutputState=TIM_OutputState_Enable;//输出使能,开启输出比较通道使能
    TIM_ocinitstruct.TIM_Pulse=0;  //CCR的值,我们要去进行比较的数
    //如果想使用多个通道输出同样的波形话,把下面这个初始化复制粘贴改变通道名称就行了
    /*同一个定时器输出多个PWM通道,是满足 相位一致性,CCR是可以各自设置的,波形的占空比可自定义
    */
    TIM_OC2Init(TIM2,&TIM_ocinitstruct);

    //5.开启定时器
    TIM_Cmd(TIM2,ENABLE);//开启定时器
   
}

//寄存器设置CCR的值,我们要去进行比较的数
void PWM_Setcompare2(uint16_t Compare){
    TIM_SetCompare2(TIM2,Compare);


}

PWM.h代码:

#ifndef __PWM_H
#define __PWM_H
void PWM_init();
void PWM_Setcompare2(uint16_t Compare);
#endif // !1

 Servo.c代码:

#include "PWM.h"

void Servo_init(){
    PWM_init();

}

void Servo_setangle(float angle){
    PWM_Setcompare2(angle/180*2000+500);
    
}

Servo.h代码:

#ifndef __SERVO_H
#define __SERVO_H
void Servo_init();
void Servo_setangle(float angle);
#endif // !__SERVO_H

main.c代码:

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

uint8_t keynum;
float angle;
int main(void)
{	
	
	OLED_Init();
	Servo_init();
	Key_init();
	
	OLED_ShowString(1,1,"angle:");
	
	Servo_setangle(90);
	while(1){
		keynum=Keynum();
		if(keynum==1)
		 {
			angle+=30;
			if(angle>180)
				angle=0;
		 }
		 Servo_setangle(angle);
		 OLED_ShowNum(1,7,angle,3);
	}
	
}


3.PWM驱动直流电机

现象:

直流电机​​​​​

电路连接图:

项目主要文件:

同样的Motor.c 和 Motor.h 文件是用来封装的。

PWM.c代码:

#include "stm32f10x.h"                  // Device header

void PWM_init(){
      //1.开启定时器时钟,TIM2总线是为APB1的
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

    //配置GPIO口 PA0
	GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_AF_PP; //使用复用推挽输出,因为这里不是输出寄存器控制的,是片上外设定时器操作的,所以要用到复用推挽输出
	GPIO_initstruct.GPIO_Pin=GPIO_Pin_2; //重新映射到15口 GPIO_initstruct.GPIO_Pin=GPIO_Pin_15;
	GPIO_initstruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_initstruct);

    TIM_InternalClockConfig(TIM2);  //给TIM2选择时钟 为内部时钟,定时器默认是使用内部的时钟,不写这一行也行的
	
    //2.配置时基单元
    TIM_TimeBaseInitTypeDef TIM_timebasestruct;
    //下面两个是运行控制操作值
    TIM_timebasestruct.TIM_ClockDivision=TIM_CKD_DIV1;//对输入信号进行初步分频,内部时钟72Mhz信号
    TIM_timebasestruct.TIM_CounterMode=TIM_CounterMode_Up;//计数方式选择向上计数
    //以下三个是时基单元里面的实际参数值
    /* 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
					       = CK_PSC / (PSC + 1) / (ARR + 1) */
    TIM_timebasestruct.TIM_Period=100-1;  //计数器的重装值,目标值 //ARR
    TIM_timebasestruct.TIM_Prescaler=36-1; //预分频器的值         //PSC
    TIM_timebasestruct.TIM_RepetitionCounter=0;//重复计数功能,这个是高级计数器才有的,当前选择的是通用计数器,设置0即可
	TIM_TimeBaseInit(TIM2,&TIM_timebasestruct);

    //初始化输出比较通道,这里以OC1通道为示例
    TIM_OCInitTypeDef TIM_ocinitstruct;//配置输出比较通道结构体
    TIM_OCStructInit(&TIM_ocinitstruct);//给这个结构体设置默认初始值,避免改为使用高级定时器的时候出错
    //下面是使用通用定时器的部分,就拉出来单独修改,其余是高级定时器的东西就不去改,保持默认值就行了
    TIM_ocinitstruct.TIM_OCMode=TIM_OCMode_PWM1;//PWM选择输出比较工作模式,八选一
    TIM_ocinitstruct.TIM_OCPolarity=TIM_OCPolarity_High;//选择输出极性,当ref为高电平的时候就输出保持不变,还有其他两种一个是低电平,另一个是翻转
    TIM_ocinitstruct.TIM_OutputState=TIM_OutputState_Enable;//输出使能,开启输出比较通道使能
    TIM_ocinitstruct.TIM_Pulse=0;  //CCR的值,我们要去进行比较的数
    TIM_OC3Init(TIM2,&TIM_ocinitstruct);

    //5.开启定时器
    TIM_Cmd(TIM2,ENABLE);//开启定时器
   
}

//寄存器设置CCR的值,我们要去进行比较的数
void PWM_Setcompare3(uint16_t Compare){
    TIM_SetCompare3(TIM2,Compare);


}

PWM.h代码:

#ifndef __PWM_H
#define __PWM_H

void PWM_init();
void PWM_Setcompare3(uint16_t Compare);
#endif // !1

Motor.c代码:

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

void Motor_init()
{
    //电机方向控制脚
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_initstruct; 
	GPIO_initstruct.GPIO_Mode=GPIO_Mode_Out_PP; 
	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_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);
    }
}

Motor.h代码:

#ifndef __MOTOR_H
#define __MOTOR_H
void Motor_init();
void Motor_setspeed(int8_t speed);
#endif // !__MOTOR_H

main.c代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "Motor.h"
#include "LED.h"

uint8_t keynum;
int speed;

int main(void)
{	
	Key_init();
	OLED_Init();
	Motor_init();
	LED_init();
	OLED_ShowString(1,1,"speed:");
	OLED_ShowString(2,1,"state:");
	while(1){
		keynum=Keynum();

		OLED_ShowSignedNum(1,8,speed,3);
		if(speed>0){
			LED1_ON();
			OLED_ShowString(2,7,"Posi");
		}
		else if(speed==0){
			LED1_OFF();
			OLED_ShowString(2,7,"Rest");
		}
		else{
			LED1_ON();
			OLED_ShowString(2,7,"Oppo");
		}
		
		if(keynum==1){ //调档,占空比分百分之 0 25 50 75 100 
			speed+=25;
			if(speed>100)
				speed=0;
		}
		if(keynum==2){ //调方向,按下就按照原来的速度反方向转
			speed=-speed;
		}
		
		Motor_setspeed(speed);
	}
}


以上就是本期的全部内容了,我们下次见!

每日壁纸:

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部