前言

关于硬件部分可以看我上次写的帖子https://blog.csdn.net/ZER00000001/article/details/124378788
#新人报道我是普通本科大一新生,因为兴趣爱好加入学校机器人协会,计划本学期挑战电赛|ू・ω・` )没想到疫情突发,只能匆匆用了一个星期入门51,做阶段性小项目,基于51智能红外寻迹小车,虽然论坛教程很多,但还是发此贴记录分享撸车的时光,也顺便做个引子给刚入门的同好们…


一、准备工作

1.安装keil 5
2.有一定的c语言基础
3.明白各模块工作原理,才能写逻辑

二、使用步骤

1.模块化编程

【视频教程:b站江科大自化协】我跟着这位老师入门的51,他讲的很好,其中教会了我模块化编程(可以有效降低代码耦合性,并且写下来思路非常清晰!!!),所以在接下的说明中我会按照这种方式来讲解代码( ̄▽ ̄)~*
(代码不成熟,仅供参考)

2.电机模块

这是电机模块,也就是控制轮子怎么转的。(如果你的电机反向,就把1和0交换位置)

#include <REGX52.H>

sbit IN1 = P1^0;//左轮
sbit IN2 = P1^1;//左轮
sbit IN3 = P1^2;//右轮
sbit IN4 = P1^3;//右轮

void Left_Motor_Forward()//左轮前进
{
	IN1 = 1;
	IN2 = 0;
}

void Right_Motor_Forward()//右轮前进
{
	IN3 = 1;
	IN4 = 0;
}
//
void Left_Motor_Back()//左轮后退
{
	IN1 = 0;
	IN2 = 1;
}

void Right_Motor_Back()//右轮后退
{
	IN3 = 0;
	IN4 = 1;
}
//
void Left_Motor_Stop()//左轮停
{
	IN1 = 0;
	IN2 = 0;
}

void Right_Motor_Stop()//右轮停
{
	IN3 = 0;
	IN4 = 0;
}

这是函数封装

#ifndef __MOTORSET_H__
#define __MOTORSET_H__


void Left_Motor_Forward();
void Right_Motor_Forward();
void Left_Motor_Back();
void Right_Motor_Back();
void Left_Motor_Stop();
void Right_Motor_Stop();

#endif

3.小车动作模块

利用封装好的电机模块对小车整体控制

#include <REGX52.H>
#include "MotorSet.h"

void Car_Go()
{Left_Motor_Forward();Right_Motor_Forward();}

void Car_Left()
{Left_Motor_Stop();Right_Motor_Forward();}

void Car_Right()
{Right_Motor_Stop();Left_Motor_Forward();}

void Car_Stop()
{Left_Motor_Stop();Right_Motor_Stop();}

void Car_Back()
{Left_Motor_Back();Right_Motor_Back();}

封装

#ifndef __MOTORSET_H__
#define __MOTORSET_H__


void Left_Motor_Forward();
void Right_Motor_Forward();
void Left_Motor_Back();
void Right_Motor_Back();
void Left_Motor_Stop();
void Right_Motor_Stop();

#endif

4**.PWM**和定时器、中断系统

关于PWM

在这里插入图片描述
在这里插入图片描述

CSDN上也有足够详细的讲解:
pwm的频率:
是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);
也就是说一秒钟PWM有多少个周期
单位: Hz
表示方式: 50Hz 100Hz
pwm的周期:
T=1/f
周期=1/频率
50Hz = 20ms 一个周期
如果频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期
占空比:
是一个脉冲周期内,高电平的时间与整个周期时间的比例
单位: % (0%-100%)
表示方式:20%

以单片机为例,我们知道,单片机的IO口输出的是数字信号,IO口只能输出高电平和低电平
假设高电平为5V 低电平则为0V 那么我们要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号
我们知道,电压是以一种连接1或断开0的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压
比方说 占空比为50% 那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压 那么75%的占空比 得到的电压就是3.75V

在这里插入图片描述

图 14.1.1 就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当 CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候, IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1), 当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。

关于定时器的配置,这里我们使用的是计时器0模式1
在这里插入图片描述(图片为定时器和中断系统的原理图)

定时器0的初始化

#include <REGX52.H>

sbit ENA_1 = P3^0;
sbit ENA_2 = P3^1;
sbit ENB_1 = P3^2;
sbit ENB_2 = P3^3;

/**
  * @brief  定时器0初始化,100us@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0_Init(void)		//100微秒@11.0592MHz
{

	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = (65536-100)%256;		//设置定时初始值
	TH0 = (65536-100)/256;		//设置定时初始值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}

利用中断函数实现PWM


unsigned int pwml,pwmr,t;//左右占空比,比较值
void Timer0_Routine() interrupt 1
{
	TL0 = (65536-100)%256;		//设置定时初始值
	TH0 = (65536-100)/256;		//设置定时初始值
	t++;
	if(t<pwml)
	{
		ENA_1 = 1;
		ENA_2 = 1;
	}
	else
	{
		ENA_1 = 0;
		ENA_2 = 0;
	}
//左pwm
	if(t<pwmr)
	{
		ENB_1 = 1;
		ENB_2 = 1;
	}
	else
	{
		ENB_1 = 0;
		ENB_2 = 0;
	}
//右pwm
	if(t>=100){t = 0;}
}

定时器模块的封装

#ifndef __TIMER0_H__
#define __TIMER0_H__


extern pwml;
extern pwmr;
extern t; 
void Timer0_Init(void);

#endif

整个撸车过程中耗时最长的就属调车的参数了,需要根据现场情况,一点点找到合适的占空比,来实现两轮转速的相对平衡(高级多的可以用一些带编码器的电机或物理仿真来找到合适的参数);

5.寻迹逻辑

我们使用的是红外避障模块(和寻迹原理一样,详细见我另一篇文章【基于51】红外寻迹智能小车 - 硬件篇)
但模块接收回红外信号时,OUT端返回低电平;但在黑线上时,黑线吸收红外,模块没有信号返回,OUT端返回高电平。
根据以上原理,我们可以写出寻迹逻辑

#include <REGX52.H>
#include "CarGo.h"
#include "Timer0.h"
#include "Delay.h"

sbit OUT1 = P2^5;//左大调
sbit OUT2 = P2^4;//左微调
sbit OUT4 = P2^2;//右大调
sbit OUT3 = P2^1;//右微调

sbit IN1 = P1^0;//左轮
sbit IN2 = P1^1;//左轮
sbit IN3 = P1^2;//右轮
sbit IN4 = P1^3;//右轮

extern pwml;
extern pwmr;

void Chack()
{
	if(OUT1 == 1 && OUT2 == 1 && OUT3 == 1 && OUT4 == 1)//全黑线(或空中)停车
	{
		pwml = 0;
		pwmr = 0;
		Car_Stop();
	}

	if(OUT1 == 0 && OUT2 == 0 && OUT3 == 1 && OUT4 == 0)//偏左,右移
	{
		pwml = 20;
		pwmr = 16;
		Car_Go();
	}

	if(OUT1 == 0 && OUT2 == 1 && OUT3 == 0 && OUT4 == 0)//偏右,左移
	{
	    pwml = 16;
		pwmr = 20;
		Car_Go();
	}
///
	if(OUT1 == 1 && OUT2 == 1 && OUT3 == 0 && OUT4 == 0)//左大转
	{
//		Car_Stop();
//		Delay(20);
//		pwml = 5;
//		pwmr = 30;
//		Delay(20);
//		Car_Go();
		
		while(1)
		{
				IN1 = 0;
				IN2 = 1;
				if(OUT3 == 1) {
				IN1 = 1;
				IN2 = 0;
				break;}
				pwml = 18;
				pwmr = 18;
				Car_Go();
				
		}
	}
//	if(OUT1 == 0 && OUT2 == 0 && OUT3 == 0 && OUT4 == 1)//右大转
//	{
		Car_Stop();
		Delay(20);
		pwml = 5;
		pwmr = 30;
		Delay(20);
		Car_Go();
//		
//		while(1)
//		{
//			IN3 = 0;
//				IN4 = 1;
//				if(OUT2 == 1) {
//				IN3 = 1;
//				IN4 = 0;
//				break;}
//				pwml = 25;
//				pwmr = 25;
//				Car_Go();
//				
//		}
//	}
///
	if(OUT1 == 1 && OUT2 == 0 && OUT3 == 0 && OUT4 == 1)
	{
		pwml = 16;
		pwmr = 16;
		Car_Go();
	}
}

在上篇我说过,我们使用的是4个红外模块平行放置来检测黑线,在调整好红外灵敏度和模块间距离(当中间两个正好能检测黑线时),靠外侧的模块用来判断转弯(这里我们考虑了用while实现的直角转弯);

函数封装

#ifndef __CHACK_H__
#define __CHACK_H__
#inlcude "MotorSet.h"
void Chack();
#endif

最后把函数全部调用进main函数就ok了ヾ(✿゚▽゚)ノ

总结

这次的小车虽然只是个阶段性小项目,但是收获颇多!下次准备挑战21电赛f组试题。希望大家都能享受亲自把小车跑起来时的快乐!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐