目录
驱动原理分析
L293D功能分析
代码分析
驱动原理分析
小车采用两片L293D芯片控制四个车轮,原理图如下:
因为L293D可分别控制两路电机,为了方便理解L293D芯片的工作原理,拿L293D的一侧功能引脚举例说明,图1中U1芯片的左半侧是一路电机控制引脚,EN1是电机T1的使能端,IN1和IN2是电机的输入,OUT1和OUT2是电机的输出。如果将EN1置于高电平,电机就完全受IN1和IN2这两个引脚控制,即如果IN1为高,IN2为低电机全速正转,反之IN1为低,IN2为高电机全速反转。那么当引入EN1这个使能引脚之后,其逻辑关系如下:不管电机正转还是反转,EN1为高电平时,电机转速不受影响;但EN1为低电平时,输出处于高阻态,即不能输出控制电压,通过这个引脚可以控制小车的转速。
本实验原理图就U1为例,L293D芯片的EN1与EN2使是使能端,将EN1与EN2连接是为了使一路PWM同时控制两个电机(显然电机方向相同)。
为了方便更容易理解L293D这款驱动芯片,引用了L293D-DC-MOTOR这篇博文,具体内容如下:
L293D功能分析
L293D是一款双通道H桥电机驱动器,能够驱动一对DC电机或一个步进电机,这意味着它可以单独驱动最多两个电机,因此非常适合构建两轮机器人平台,芯片功能引脚分布如下,并对该芯片各模块功能进行分析:
电源
L293D电机驱动器IC实际上有两个电源输入引脚,即“Vcc1”和“Vcc2”。Vcc1用于驱动应为5V的内部逻辑电路,Vcc2是驱动H桥的电源,该电源可以为4.5V至36V,他们都共用一个地线。
输出引脚
L293D电动机驱动器的电动机A和B的输出通道引出到引脚 OUT1,OUT2 和 OUT3,OUT4。IC上的每个通道均可向直流电动机提供高达600mA的电流。
对于L293D的每个通道,有两种类型的控制引脚,这些引脚使我们可以同时控制直流电动机的速度和旋转方向。
方向控制
我们可以控制电动机是向前还是向后旋转,这些引脚实际上控制L293D的IC内部的H桥电路的开关,该IC的每个通道都有两个方向控制引脚。IN1,IN2引脚控制马达A的旋转方向,同时IN3,IN4控制马达B。
可以通过在这些引脚上施加逻辑高电平(5V)或逻辑低电平(0V/接地)来控制电机的旋转方向。下图说明了如何完成此操作。
速度控制
速度控制引脚即控制ENA和ENB的打开和关闭来控制电动机A和B的速度。将这些引脚拉至高电平将使电动机旋转,将其拉至低电平将使其停止。借助脉冲宽度调制(PWM),我们实际上可以控制马达的速度。
那么如何用程序实现呢,写程序之前首先要明白小车的四个电机的运行方式,根据上述原理图U1控制左边的前后电机;U2控制右面的前后电机。我们可以命名为左前电机为L_F_motor;左后电机为L_B_motor;右前电机为R_F_motor;右后电机为R_B_motor。根据上述接线方式,右前和右后,左前和左后的控制信号是一样的,我们可以得到在做方向运动时的电机转向如下图所示。
代码分析
我们根据上述规律编写代码:
首先建立一个全局变量的头文件:globalvariable.h
#ifndef __GLOBALVARIABLE_H //防止重复定义
#define __GLOBALVARIABLE_H
/**************头文件申明******************/
#include "stm32f10x.h"
/*********电机驱动GPIO相关宏定义**********/
/*********左侧双电机正转+反转GPIO相关宏定义**********/
#define L_MOTOR_F_GPIO GPIOG //left motor foreward
#define L_MOTOR_F_PIN GPIO_Pin_11
#define L_MOTOR_F_SET GPIO_SetBits(L_MOTOR_F_GPIO , L_MOTOR_F_PIN)
#define L_MOTOR_F_RESET GPIO_ResetBits(L_MOTOR_F_GPIO , L_MOTOR_F_PIN)
#define L_MOTOR_R_GPIO GPIOD //left motor reversal
#define L_MOTOR_R_PIN GPIO_Pin_6
#define L_MOTOR_R_SET GPIO_SetBits(L_MOTOR_R_GPIO , L_MOTOR_R_PIN)
#define L_MOTOR_R_RESET GPIO_ResetBits(L_MOTOR_R_GPIO , L_MOTOR_R_PIN)
/*********右侧双电机正转+反转GPIO相关宏定义**********/
#define R_MOTOR_F_GPIO GPIOG //right motor foreward
#define R_MOTOR_F_PIN GPIO_Pin_9
#define R_MOTOR_F_SET GPIO_SetBits(R_MOTOR_F_GPIO , R_MOTOR_F_PIN)
#define R_MOTOR_F_RESET GPIO_ResetBits(R_MOTOR_F_GPIO , R_MOTOR_F_PIN)
#define R_MOTOR_R_GPIO GPIOD //right motor reversal
#define R_MOTOR_R_PIN GPIO_Pin_0
#define R_MOTOR_R_SET GPIO_SetBits(R_MOTOR_R_GPIO , R_MOTOR_R_PIN)
#define R_MOTOR_R_RESET GPIO_ResetBits(R_MOTOR_R_GPIO , R_MOTOR_R_PIN)
/*********左侧PWM使能CPIO初始化**********/
#define L_MOTOR_PWM_GPIO GPIOD //right motor reversal
#define L_MOTOR_PWM_PIN GPIO_Pin_4
#define L_MOTOR_PWM_SET GPIO_SetBits(R_MOTOR_R_GPIO , R_MOTOR_R_PIN)
#define L_MOTOR_PWM_RESET GPIO_ResetBits(R_MOTOR_R_GPIO , R_MOTOR_R_PIN)
/*********右侧PWM使能CPIO初始化**********/
#define R_MOTOR_PWM_GPIO GPIOD //right motor reversal
#define R_MOTOR_PWM_PIN GPIO_Pin_2
#define R_MOTOR_PWM_SET GPIO_SetBits(R_MOTOR_PWM_GPIO , R_MOTOR_PWM_PIN)
#define R_MOTOR_PWM_RESET GPIO_ResetBits(R_MOTOR_PWM_GPIO , R_MOTOR_PWM_PIN)
/*********电机运动函数**********/
/*********左侧电机前进运动函数**********/
#define L_MOTOR_GO L_MOTOR_F_SET; L_MOTOR_R_RESET
#define L_MOTOR_BACK L_MOTOR_F_RESET; L_MOTOR_R_SET
#define L_MOTOR_STOP L_MOTOR_F_RESET; L_MOTOR_R_RESET
/*********右侧电机前进运动函数**********/
#define R_MOTOR_GO R_MOTOR_F_SET; R_MOTOR_R_RESET
#define R_MOTOR_BACK R_MOTOR_F_RESET; R_MOTOR_R_SET
#define R_MOTOR_STOP R_MOTOR_F_RESET; R_MOTOR_R_RESET
/*********PWM周期时间为50ms,最小分辨率为1ms,设置默认占空比为40。**********/
#define SPEED_DUTY 40/
extern unsigned char time_5ms;//5ms计数器,作为主函数的基本周期
extern unsigned char time_1ms;//1ms计数器,作为电机的基本计数器
extern unsigned int speed_count;//占空比计数器 50次一周期
根据上述头文件中定义的引脚,我们需要建立一个sys.c文件夹对上述底层驱动进行初始化。
其sys.h很简单,主要有:
#ifndef __SYS_H
#define __SYS_H
#include "globalvariable.h"
void initsysblock(void)
void delay_init(void);
void delay_ms(unsigned int time);
void delay_us(unsigned int time);
#endif
其sys.c如下
#include "sys.h"
/*********************************************************
名 称:void initsysblock(void)
功 能:外设底层驱动初始化函数
参 数:None
返回值:None
**********************************************************/
void initsysblock(void)
{
/**************功能外设宏定义******************/
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/**************程序上电先将GPIO设置为默认值******************/
GPIO_DeInit(GPIOD);//恢复GPIO的相应寄存器为默认值
GPIO_DeInit(GPIOG);
/**************程序上电先将定时器设置为默认值******************/
TIM_DeInit(TIM2);
/**************定时器时钟开启******************/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/**************GPIO时钟开启******************/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG , ENABLE);
/**************四轮控制引脚GPIO的配置******************/
GPIO_InitStructure.GPIO_Pin = L_MOTOR_F_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(L_MOTOR_F_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = R_MOTOR_F_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(L_MOTOR_F_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = L_MOTOR_R_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(L_MOTOR_R_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = R_MOTOR_R_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(R_MOTOR_R_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = R_MOTOR_PWM_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(R_MOTOR_PWM _GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = R_MOTOR_PWM_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(R_MOTOR_PWM _GPIO, &GPIO_InitStructure);
/**************中断的配置*******************/
NVIC_InitStructure.NVIC_IRQChannel =TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/********************定时器配置************************/
//这个就是自动装载的计数值,由于计数是从0开始的,周期为100us
TIM_TimeBaseStructure.TIM_Period = (100-1);//10kHz
// 这个就是预分频系数,当由于为0时表示不分频所以要减1
TIM_TimeBaseStructure.TIM_Prescaler =(72-1);//1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //写寄存器
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE); //计数器使能,开始工作
/********************相关延时函数************************/
//主要利用系统滴答定时器,不占用定时器和CPU资源//
void delay_init(void)
{
SysTick->CTRL&=0xfffffffb;//控制寄存器,选择外部时钟即系统时钟的八分之一(HCLK/8;72M/8=9M)
}
/********************1us延时函数************************/
void delay_us(u32 Nus)
{
SysTick->LOAD=Nus*9; //时间加载 72M主频
SysTick->CTRL|=0x01; //开始倒数
while(!(SysTick->CTRL&(1<<16))); //等待时间到达
SysTick->CTRL=0X00000000; //关闭计数器
SysTick->VAL=0X00000000; //清空计数器
}
/********************1ms延时函数************************/
void delay_ms(u32 Nms)
{
while(Nms--)
{
delay_us(1000);
}
}
其次建立一个电机驱动头文件:motor.h
#ifndef __MOTOR_H_
#define __MOTOR_H_
extern unsigned int speed_count;//占空比计数器 50次一周期
extern char left_speed_duty;
extern char right_speed_duty;
void CarMove(void);
void CarGo(void);
void CarBack(void);
void CarLeft(void);
void CarRight(void);
void CarStop(void);
void MotorInit(void);
#endif
我们首先在头文件中声明几个变量用于后面电机驱动函数的编写
unsigned int speed_count=0;//占空比计数器50次一周期
char left_speed_duty=SPEED_DUTY;
char right_speed_duty=SPEED_DUTY;
unsigned char counter_5ms = 0;//5ms计数器,作为主函数的基本周期
unsigned char counter_1ms = 0;//1ms计数器,作为电机的基本计数器
char ctrl_comm = COMM_STOP;//控制指令
unsigned char continue_time=0;
并在globalvariable.h中对变量SPEED_DUTY赋值。
#define SPEED_DUTY 40//默认占空比 按1ms最小分辨率 周期50ms计算。
那么speed_count是如何计数的,主要是利用定时器TIM2进行50毫秒的周期计数。
那么问题又来了,我们所定义的counter_1ms在计数一次的时候是如何做到1毫秒计时的,我们在配置定时器时关于STM32定时器,TIMx(1-8),在库设置默认的情况下,都是72M的时钟;名为TIMx的有八个,其中TIM1和TIM8挂在APB2总线上,而TIM2-TIM7则挂在APB1总线上。其中TIM1&TIM8称为高级控制定时器(advanced control timer)。APB2可以工作在72MHz下,而APB1最大是36MHz。我们此前在sys.c文件中对TIM2进行了定时器中断的函数配置,终端周期是100us,计算公式为Tout=((arr+1)*(psc+1))/Tclk。也就是定时时间是重装载值和分频系数的乘积除以定时器的输入时钟频率。也就是100*72/72000000=100us。那么我们利用如下配置对TIM2中断处理函数TIM2_IRQHandler的进行编写,该函数主要实现50毫秒的计数。
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除中断标志位
counter_1ms++;
if(counter_1ms>=10)//100usz计数一次,计数10次为1ms
{
counter_1ms = 0;
speed_count++;
counter_5ms++;
if(speed_count >= 50)
{
speed_count = 0;
}
CarMove();
}
}
}
那么我们如何利用PWM来控制小车的运动呢?看下图:
通过这种方式我们就实了通过PWM控制L293D使能端对电机进行调速控制。
下面是电机驱动motor.c文件
#include "motor.h"
#include " globalvariable.h "
#include "stm32f10x.h"
/********************根据占空比驱动电机转动************************/
void CarMove(void)
{
L_MOTOR_PWM_SET;
R_MOTOR_PWM_SET;
if(right_speed_duty > 0)//向前
{
if(speed_count < right_speed_duty)//speed_count在累加与设定值40进行比较,如果小于40,电机执行正转,否则停止。这里就是PWM的控制思想。
{
R_MOTOR_GO;
}else //停止
{
R_MOTOR_STOP;
}
}
else if (right_speed_duty < 0)//向后
{
if(speed_count < (-1)right_speed_duty)
{
R_MOTOR_BACK;
}else //停止
{
R_MOTOR_STOP;
}
}
else //停止
{
L_MOTOR_STOP;
}
if(left_speed_duty > 0)//向前
{
if(speed_count < left_speed_duty)
{
L_MOTOR_GO;
} else //停止
{
L_MOTOR_STOP;
}
}
else if(left_speed_duty < 0)//向后
{
if(speed_count < (-1)* left_speed_duty)
{
L_MOTOR_BACK;
} else //停止
{
L_MOTOR_STOP;
}
}
else //停止
{
R_MOTOR_STOP;
}
上述函数就是确定left_speed_duty和right_speed_duty的值与利用定时器产生的speed_count的进行比较来产生PWM波形控制小车四个电机的运行方式。speed_count是TIM2计时器每毫秒计数一次,50毫秒为一个周期循环计数,left_speed_duty和right_speed_duty的值则是初始赋予40,也就是占空比的设定。
下面根据上述运动方式对小车的运动函数进行定义,即前进左右四个轮子都正转,向后左右两个轮子都反转,左转左面轮子后转、右面轮子向前转,右转左面轮子向前、右面轮子向后转。
#define SPEED_DUTY 40//默认占空比 按1ms最小分辨率 周期50ms计算
/********************向前运动函数************************/
void CarGo(void)
{
left_speed_duty=SPEED_DUTY;
right_speed_duty=SPEED_DUTY;
}
/********************向后运动函数************************/
void CarBack(void)
{
left_speed_duty=-SPEED_DUTY;
right_speed_duty=-SPEED_DUTY;
}
/********************向左运动函数************************/
void CarLeft(void)
{
left_speed_duty=-20;
right_speed_duty=SPEED_DUTY;
}
/********************向右运动函数************************/
void CarRight(void)
{
left_speed_duty=SPEED_DUTY;
right_speed_duty=-20;
}
/********************停止运动函数************************/
void CarStop(void)
{
left_speed_duty=0;
right_speed_duty=0;
}
/********************电机初始化************************/
void MotorInit(void)
{
MotorGPIO_Configuration();
CarStop();
}
那么控制小车运动程序我们就分析完了,下节我们对小车的红外控制模块进行分析。