如果控制马达的EA使能的stm32的GPIO引脚的电平一直都是高电平的话,马达会一直匀速以最高速度转动,
但是如果在一个周期内使这个引脚的电平不断交换持续高电平和低电平的话(即方波),这样该引脚电平的平均电压就会减少,
这样马达的速度也会降速,stm32就是通过引脚输出PWM波来进行马达调速的。
下面做一个小实验:
先手工模拟一个方波信号,用于控制马达的转速。
以占空比为50%正转600ms,以占空比5%反转600ms,以占空比5%正转600ms,以占空比50%反转600ms。
正确实验现象是:马达快速正转600ms,慢速反转600ms,慢速正转600ms,快速反转600ms
接线图:
即:
工程结构:
代码:
bsp_moto.h
#ifndef __BSP_MOTO_H__
#define __BSP_MOTO_H__
#include "stm32f10x.h"
#define HIGH 1
#define LOW 0
#define GPIO_MOTO_CLK RCC_APB2Periph_GPIOA
#define GPIO_MOTO_PORT GPIOA
#define GPIO_MOTO_Pin_IN1 GPIO_Pin_2
#define GPIO_MOTO_Pin_IN2 GPIO_Pin_3
#define GPIO_MOTO_Pin_PWM1 GPIO_Pin_8 // PWM引脚
#define IN1(a)\
if(a) GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN1);\
else GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN1);
#define IN2(a)\
if(a) GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN2);\
else GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN2);
/*GPIO端口初始化*/
void GPIO_Moto_Init(void);
/*Moto1正转*/
void Moto1_Forward(void);
/*Moto1反转*/
void Moto1_Reverse(void);
/*Moto1停止*/
void Moto1_Pause(void);
#endif /*__BSP_MOTO_H__*/
bsp_moto.c
#include "./moto/bsp_moto.h"
/*GPIO端口初始化*/
void GPIO_Moto_Init(void)
{
// 结构体
GPIO_InitTypeDef GPIO_InitStruct;
// 开时钟
RCC_APB2PeriphClockCmd(GPIO_MOTO_CLK, ENABLE);
// 实例化
GPIO_InitStruct.GPIO_Pin = GPIO_MOTO_Pin_IN1|GPIO_MOTO_Pin_IN2|GPIO_MOTO_Pin_PWM1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化
GPIO_Init(GPIO_MOTO_PORT, &GPIO_InitStruct);
GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN1|GPIO_MOTO_Pin_IN2|GPIO_MOTO_Pin_PWM1);
}
/*Moto1正转*/
void Moto1_Forward(void)
{
IN1(HIGH);
IN2(LOW);
}
/*Moto1反转*/
void Moto1_Reverse(void)
{
IN1(LOW);
IN2(HIGH);
}
/*Moto1停止*/
void Moto1_Pause(void)
{
IN1(LOW);
IN2(LOW);
}
main.c
#include "stm32f10x.h"
#include "./moto/bsp_moto.h"
// 延迟time毫秒
void Delay_ms(uint16_t time)
{
u16 i=0;
while(time--)
{
i=10000; //自己定义
while(i--) ;
}
}
int main(void)
{
uint32_t time;
GPIO_Moto_Init(); // 马达引脚初始化
while(1)
{
// 正转,占空比:50%
Moto1_Forward();
for(time=0; time<30; time++)
{
GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(10);
GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(10);
}
Delay_ms(500);
// 反转,喊空比:5%
time=0;
Moto1_Reverse();
for(time=0; time<30; time++)
{
GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(1);
GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(19);
}
Delay_ms(500);
// 正转,占空比:5%
time=0;
Moto1_Forward();
for(time=0; time<30; time++)
{
GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(1);
GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(19);
}
Delay_ms(500);
// 反转,占空比:50%
time=0;
Moto1_Reverse();
for(time=0; time<30; time++)
{
GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(10);
GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_PWM1);
Delay_ms(10);
}
Delay_ms(500);
}
}
将程序编译烧录到单片机中运行,实验现象和预测的一致,说明手动实验产生的PA8引脚上的PWM脉冲脉宽调制信号对
L298N的使能端引脚ENA起作用了,而且可以通过设置不同的占空比来控制直流马达的转速,从而实现了调速的效果。
但是在实际情况中,不会这么去写代码,可以使用STM32的Tim通用定时器(Tim2-Tim5)或高级定时器(Tim1和Tim8)来
控制PA8引脚(或者其他的引脚,因为不同的Tim定时器会作用到不同的输出引脚上,这个和单片机的设计说明有关,请参考说明文档来设计),
但是不能使用基本定时器Tim6或者Tim7,因为基本定时器没有输出比较信号的功能,所以只能使用通用定时器或者高级定时器来实现。
=============================== 使用Tim定时器来产生PWM信号控制马达的转速=============================
实验:1. 占空比为80%正转2秒;2.停止2秒;3.占空比为40%反转2秒;4.停止2秒
一、按图接好线
二、这里选择Tim3的输出比较通道1,查stm32资料,知道该通道的GPIO引脚为:PA6,所以L298N的ENA的外侧引脚接PA6引脚,
当向该引脚输出PWM信号的时候就能控制该引脚的占空比,就能控制该引脚的平均电压,就能控制直流马达的转速。
三、工程结构
代码:
bsp_pwm.h
#ifndef BSP_ADVANCETIME_H
#define BSP_ADVANCETIME_H
#include "stm32f10x.h"
/************通用定时器TIM参数定义,只限TIM2、3、4、5************/
// 当使用不同的定时器的时候,对应的GPIO是不一样的,这点要注意
// 我们这里默认使用TIM3
#define GENERAL_TIM TIM3
#define GENERAL_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd
#define GENERAL_TIM_CLK RCC_APB1Periph_TIM3
#define GENERAL_TIM_Period 9
#define GENERAL_TIM_Prescaler 71
// TIM3 输出比较通道1
#define GENERAL_TIM_CH1_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH1_PORT GPIOA
#define GENERAL_TIM_CH1_PIN GPIO_Pin_6
/* 234通道用不上,如果需要就打开这里即可
// TIM3 输出比较通道2
#define GENERAL_TIM_CH2_GPIO_CLK RCC_APB2Periph_GPIOA
#define GENERAL_TIM_CH2_PORT GPIOA
#define GENERAL_TIM_CH2_PIN GPIO_Pin_7
// TIM3 输出比较通道3
#define GENERAL_TIM_CH3_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH3_PORT GPIOB
#define GENERAL_TIM_CH3_PIN GPIO_Pin_0
// TIM3 输出比较通道4
#define GENERAL_TIM_CH4_GPIO_CLK RCC_APB2Periph_GPIOB
#define GENERAL_TIM_CH4_PORT GPIOB
#define GENERAL_TIM_CH4_PIN GPIO_Pin_1
*/
/**************************函数声明********************************/
void GENERAL_TIM_Init(uint16_t CCR1_Val);
#endif /* BSP_ADVANCETIME_H */
bsp_pwm.c
#include "./pwm/bsp_pwm.h"
static void GENERAL_TIM_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 输出比较通道1 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH1_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH1_PORT, &GPIO_InitStructure);
/*
// 输出比较通道2 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH2_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH2_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH2_PORT, &GPIO_InitStructure);
// 输出比较通道3 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH3_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH3_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
// 输出比较通道4 GPIO 初始化
RCC_APB2PeriphClockCmd(GENERAL_TIM_CH4_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = GENERAL_TIM_CH3_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStructure);
*/
}
///*
// * 注意:TIM_TimeBaseInitTypeDef结构体里面有5个成员,TIM6和TIM7的寄存器里面只有
// * TIM_Prescaler和TIM_Period,所以使用TIM6和TIM7的时候只需初始化这两个成员即可,
// * 另外三个成员是通用定时器和高级定时器才有.
// *-----------------------------------------------------------------------------
// *typedef struct
// *{ TIM_Prescaler 都有
// * TIM_CounterMode TIMx,x[6,7]没有,其他都有
// * TIM_Period 都有
// * TIM_ClockDivision TIMx,x[6,7]没有,其他都有
// * TIM_RepetitionCounter TIMx,x[1,8,15,16,17]才有
// *}TIM_TimeBaseInitTypeDef;
// *-----------------------------------------------------------------------------
// */
/* ---------------- PWM信号 周期和占空比的计算--------------- */
// ARR :自动重装载寄存器的值
// CLK_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM 信号的周期 T = ARR * (1/CLK_cnt) = ARR*(PSC+1) / 72M
// CCR1_Val:占空比配置,占空比P=CCR/(ARR+1)
void GENERAL_TIM_Mode_Config(uint16_t CCR1_Val)
{
// 开启定时器时钟,即内部时钟CK_INT=72M
GENERAL_TIM_APBxClock_FUN(GENERAL_TIM_CLK,ENABLE);
/*--------------------时基结构体初始化-------------------------*/
// 配置周期,这里配置为100K
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断
TIM_TimeBaseStructure.TIM_Period=GENERAL_TIM_Period;
// 驱动CNT计数器的时钟 = Fck_int/(psc+1)
TIM_TimeBaseStructure.TIM_Prescaler= GENERAL_TIM_Prescaler;
// 时钟分频因子 ,配置死区时间时需要用到
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
// 计数器计数模式,设置为向上计数
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
// 重复计数器的值,没用到不用管
TIM_TimeBaseStructure.TIM_RepetitionCounter=0;
// 初始化定时器
TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);
/*--------------------输出比较结构体初始化-------------------*/
// 占空比配置
// uint16_t CCR1_Val = 5;
// uint16_t CCR2_Val = 4;
// uint16_t CCR3_Val = 3;
// uint16_t CCR4_Val = 2;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 配置为PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
// 输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// 输出通道电平极性配置
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 输出比较通道 1
TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
TIM_OC1Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
/*
// 输出比较通道 2
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
TIM_OC2Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 3
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
// 输出比较通道 4
TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(GENERAL_TIM, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(GENERAL_TIM, TIM_OCPreload_Enable);
*/
// 使能计数器
TIM_Cmd(GENERAL_TIM, ENABLE);
}
void GENERAL_TIM_Init(uint16_t CCR1_Val)
{
GENERAL_TIM_GPIO_Config();
GENERAL_TIM_Mode_Config(CCR1_Val);
}
bsp_moto.h
#ifndef __BSP_MOTO_H__
#define __BSP_MOTO_H__
#include "stm32f10x.h"
// 引脚高低电平选择
#define HIGH 1
#define LOW 0
// 马达用到的引脚
#define GPIO_MOTO_CLK RCC_APB2Periph_GPIOA
#define GPIO_MOTO_PORT GPIOA
#define GPIO_MOTO_Pin_IN1 GPIO_Pin_2 // PA2接L298N上的IN1
#define GPIO_MOTO_Pin_IN2 GPIO_Pin_3 // PA3接L298N上的IN2
// 控制L298N的IN1和IN2的引脚的高低电平
#define IN1(a)\
if(a) GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN1);\
else GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN1);
#define IN2(a)\
if(a) GPIO_SetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN2);\
else GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN2);
/*GPIO端口初始化*/
void GPIO_Moto_Init(void);
/*Moto1正转*/
void Moto1_Forward(void);
/*Moto1反转*/
void Moto1_Reverse(void);
/*Moto1停止*/
void Moto1_Pause(void);
#endif /*__BSP_MOTO_H__*/
bsp_moto.c
#include "./moto/bsp_moto.h"
/*GPIO端口初始化*/
void GPIO_Moto_Init(void)
{
// 结构体
GPIO_InitTypeDef GPIO_InitStruct;
// 开时钟
RCC_APB2PeriphClockCmd(GPIO_MOTO_CLK, ENABLE);
// 实例化
GPIO_InitStruct.GPIO_Pin = GPIO_MOTO_Pin_IN1|GPIO_MOTO_Pin_IN2;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化
GPIO_Init(GPIO_MOTO_PORT, &GPIO_InitStruct);
// 拉低电平复位引脚
GPIO_ResetBits(GPIO_MOTO_PORT, GPIO_MOTO_Pin_IN1|GPIO_MOTO_Pin_IN2);
}
/*Moto1正转*/
void Moto1_Forward(void)
{
IN1(HIGH);
IN2(LOW);
}
/*Moto1反转*/
void Moto1_Reverse(void)
{
IN1(LOW);
IN2(HIGH);
}
/*Moto1停止*/
void Moto1_Pause(void)
{
IN1(LOW);
IN2(LOW);
}
main.c
#include "stm32f10x.h"
#include "./pwm/bsp_pwm.h"
#include "./moto/bsp_moto.h"
// 粗略延迟ms毫秒
void Delay_ms(uint16_t ms)
{
u16 i=0;
while(ms--)
{
i=10000; // 自己定义
while(i--) ;
}
}
int main(void)
{
// 马达引脚初始化
GPIO_Moto_Init();
// 马达转速由L298N驱动模块的ENA使能端和stm32输出pwm信号的GPIO输出比较引脚的pwm信号占空比来控制
while(1)
{
// 占空比为80%正转2秒
GENERAL_TIM_Init(8);
Moto1_Forward();
Delay_ms(2000);
// 停止2秒
Moto1_Pause();
Delay_ms(2000);
// 占空比为40%反转2秒【低于40%的占空比不能驱动马达,因为这个马达的驱动电压是3V-6V】
GENERAL_TIM_Init(4);
Moto1_Reverse();
Delay_ms(2000);
// 停止2秒
Moto1_Pause();
Delay_ms(2000);
}
}
实验现象:快速正转,停止,慢速反转。和预期效果一直。
================= 另外:通过Keil软件模拟该引脚的输出PWM信号图如下:
一、点击魔术棒
二、点击Start/Stop Debug Session图标:
三、点击Analysis Windows图标:
四、点击Setup...
五、点全速运行:
注意,如果没有PWM图,就点下面的图标:可能是Keil软件的Bug,没有来得及更新。
六、分析占空比
高电平的时间:5.36us,总时间:6.86us,则占空比=5.36/6.86=78%,有点误差,可能是对线的时候没有完全对齐,但是基本等于程序中的Tim3的通道1的占空比80%
所以通过在一个周期里面,设置不同的占空比就可以控制马达的转速。
扩展实验:
1.可以通过蓝牙通信,使用手机APP来控制马达的在转速;
2.也可以通过USART串口通信来实现PC机控制马达的转速;
3.还可以使用Java和stm32的通信通过Java程序来控制马达的转速。
======================================== 完 ==========================================