L298N驱动两路电机
目录
首先是题外话,我在高中就接触了STM32,可是在学校的课程确是从最基础的C51学起。
至此,已经快到了结课的时间了,所以改写一个比较喜欢的电机模块。
话不多说,首先是
main.c
#include "main.h"
#include "motor.h"
u8 status = 0;
void main(){
L298_Init();
Motor_Stop();
Delay_ms(50);
while(1)
{
Motor_Forward(100);
}
}
因为只是驱动并没有写完这些功能,所以这只有延时一些时间然后前进。
main.h
其实是为了将u8,u16这些写在这,让main.c的代码量看起来少一些。
我在这使用的是typedef,当然也可以使用#define
注意以下,两者使用的方法:
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
#define u8 unsigned char
#define u16 unsigned int
#define u32 unsigned long
#ifndef _MAIN_H
#define _MAIN_H
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
/* Includes */
#include "regx52.h"
/* Private includes User */
#include "delay.h"
/* Exported functions prototypes */
#endif
delay.c
我阅读过大多数在CSDN上的一些51单片机开发的例程并不是一定需要delay.c和delay.h的,因为他们的delay延时函数通常是放在自己那个模块的顶部,或者是一个main.c的头部。这种事情看个人,毕竟每个人的编程风格不一样。
#include "delay.h"
#include "intrins.h"
void Delay_ms(unsigned int time)
{
unsigned char data i, j;
for(time;time>0;time--){
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void Delay_us(unsigned int time) //@11.0592MHz
{
unsigned char data i;
for(time;time>0;time--){
_nop_();
i = 25;
while (--i);
}
}
我的延时函数使用stc-isp进行生成并进行修改,但是我建议用到多长的延时就生成多少的延时,毕竟51单片机不像stm32,有时候你觉得没bug或者是语法并没有错误,但是它就是给你报错。
delay.h
#ifndef _DELAY_H
#define _DELAY_H
void Delay_ms(unsigned int time);
void Delay_us(unsigned int time);
#endif // _DELAY_H
pwm.c
然后就是我使用了普中提供的PWM函数,因为我需要操控两个电机,也就是ENA和ENB两个使能端,所以我就多加了一个IO口分配给ENB,定义为PWM1。当然,毕竟是单片机,IO口大多是自己设置的,设置什么都可行,建议不要占用专用的IO口,如:
IO口 | 特殊功能 | 解析 |
P3.0 | RXD | 串口数据接收 |
P3.1 | TXD | 串口数据发送 |
P3.2 | INT0 | 外部中断0 |
P3.3 | INT1 | 外部中断1 |
P3.4 | T0 | 定时器0或计数器0输入 |
P3.5 | T1 | 定时器1或计数器1输入 |
P3.6 | WR | 外部RAM写 |
P3.7 | RD | 外部RAM读 |
#include "pwm.h"
//全局变量定义
u8 gtim_h=0;//保存定时器初值高8位
u8 gtim_l=0;//保存定时器初值低8位
u8 gduty=0;//保存PWM占空比
u8 gtim_scale=0;//保存PWM周期=定时器初值*tim_scale
/*******************************************************************************
* 函 数 名 : pwm_init
* 函数功能 : PWM初始化函数
* 输 入 : tim_h:定时器高8位
tim_l:定时器低8位
tim_scale:PWM周期倍数:定时器初值*tim_scale
duty:PWM占空比(要小于等于tim_scale)
* 输 出 : 无
*******************************************************************************/
void pwm_init(u8 tim_h,u8 tim_l,u16 tim_scale,u8 duty)
{
gtim_h=tim_h;//将传入的初值保存在全局变量中,方便中断函数继续调用
gtim_l=tim_l;
gduty=duty;
gtim_scale=tim_scale;
TMOD|=0X01; //选择为定时器0模式,工作方式1
TH0 = gtim_h; //定时初值设置
TL0 = gtim_l;
ET0=1;//打开定时器0中断允许
EA=1;//打开总中断
TR0=1;//打开定时器
}
/*******************************************************************************
* 函 数 名 : pwm_set_duty_cycle
* 函数功能 : PWM设置占空比
* 输 入 : duty:PWM占空比(要小于等于tim_scale)
* 输 出 : 无
*******************************************************************************/
void pwm_set_duty_cycle(u8 duty)
{
gduty=duty;
}
void pwm(void) interrupt 1 //定时器0中断函数
{
static u16 time=0;
TH0 = gtim_h; //定时初值设置
TL0 = gtim_l;
time++;
if(time>=gtim_scale)//PWM周期=定时器初值*gtim_scale,重新开始计数
time=0;
if(time<=gduty)//占空比
{
PWM=1;
PWM1=1;
}
else
{
PWM=0;
PWM1=0;
}
}
像我这样直接把另外一个PWM加进来,会出现一些小情况。我更倾向于将两个PWM分别控制,因为可以一定程度上缓解驱动模块的两路输出不一致的问题,但是却无法完全根治。
pwm.h
#ifndef _PWM_H
#define _PWM_H
#include "main.h"
//管脚定义
sbit PWM=P2^2;
sbit PWM1=P2^5;
//变量声明
extern u8 gtim_scale;
//函数声明
void pwm_init(u8 tim_h,u8 tim_l,u16 tim_scale,u8 duty);
void pwm_set_duty_cycle(u8 duty);
#endif
最后是重头戏
motor.c
按键检测其实可以放其他地方的,因为是调试代码,所以可以删掉。亦可以转为红外或者蓝牙、WiFi等方式控制。
通过了解L298N的H桥逻辑,可以得出以下内容:
#include "motor.h"
#include "pwm.h"
sbit L298_IN1 = P2^0;
sbit L298_IN2 = P2^1;
sbit L298_ENA = P2^2;
sbit L298_IN3 = P2^3;
sbit L298_IN4 = P2^4;
sbit L298_ENB = P2^5;
sbit KEY1 = P3^1; //正反转切换
sbit KEY2 = P3^0; //加速
sbit KEY3 = P3^2; //减速
sbit KEY4 = P3^3; //停止
void L298_Init()
{
pwm_init(0xFF,0xF6,100,0);
}
void Motor_Forward(char Speed)
{
L298_IN1 = 1;
L298_IN2 = 0;
L298_IN3 = 1;
L298_IN4 = 0;
pwm_set_duty_cycle(Speed);
}
void Motor_Retreat(char Speed)
{
L298_IN1 = 0;
L298_IN2 = 1;
L298_IN3 = 0;
L298_IN4 = 1;
pwm_set_duty_cycle(Speed);
}
void Motor_TurnLeft(char Speed)
{
L298_IN1 = 1;
L298_IN2 = 0;
L298_IN3 = 0;
L298_IN4 = 0;
pwm_set_duty_cycle(Speed);
}
void Motor_TurnRight(char Speed)
{
L298_IN1 = 0;
L298_IN2 = 0;
L298_IN3 = 1;
L298_IN4 = 0;
pwm_set_duty_cycle(Speed);
}
void Motor_Stop()
{
L298_IN1 = 0;
L298_IN2 = 0;
L298_IN3 = 0;
L298_IN4 = 0;
pwm_set_duty_cycle(0);
}
//返回0表示没有按键按下
//返回1表示按键KEY1被按下
//返回2表示按键KEY2被按下
//返回3表示按键KEY3被按下
u8 Key_Sacn()
{
static unsigned char key_press_state = 0;//表示按键按下状态 0 表示按键没有按下 1 表示按键被按下
//如果没有按键被按下
if(key_press_state == 0)
{
//如果KEY1-KEY4任何一个被按下
if(KEY1 == 0 || KEY2 == 0 ||KEY3 == 0 || KEY4 == 0)
{
//延时10ms
Delay_ms(10);
//逐个判断,确定具体是哪一个被按下
if(KEY1 == 0)
{
key_press_state = 1;
return 1;
}
if(KEY2 == 0)
{
key_press_state = 1;
return 2;
}
if(KEY3 == 0)
{
key_press_state = 1;
return 3;
}
if(KEY4 == 0)
{
key_press_state = 1;
return 4;
}
}
}
//KEY1-KEY4全部松开,才能恢复状态
if(KEY1 == 1 && KEY2==1 && KEY3 ==1 && KEY4 ==1)
{
key_press_state = 0;
}
return 0;
}
motor.h
#ifndef _MOTOR_H
#define _MOTOR_H
/* Includes */
#include "main.h"
/* Private includes User */
#include "delay.h"
#include "regx52.h"
/* Exported functions prototypes */
void L298_Init();
void Motor_Forward(char Speed); //Speed(0-100)
void Motor_TurnRight(char Speed);
void Motor_TurnLeft(char Speed);
void Motor_Retreat(char Speed);
void Motor_Stop();
u8 Key_Scan();
#endif
总的来说,还是很简单的一个项目。可以多多锻炼,练多了就可以慢慢的读懂别人的代码。我认为还是得从基础的事物学起,因为可以知道比较底层的一些东西。
另外,本人有些厌恶二次开发,导致做一些项目的时候进度比别人慢的很多,因为需要搞懂工作原理,以及进行相对应的代码调试,直到能用的地步。不过我的性格是不能止步于能用,还得好用。
PWM使用的是普中提供的例程,并进行了小修改。
#一课一得#