基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码

52RC循迹小车模块化代码

  • 前言
  • 说明
  • 一、环境
    • (一)硬件
      • 1. 配件
      • 2. 接线
    • (二)软件
  • 二、代码
    • (一)UPC
      • 1. 主函数
      • 2. 超声波模块
      • 3. TM1638模块
    • (二)LPC
      • 1. 主函数
      • 2. 电机控制
    • (三)ATK-ESP8266 (未使用模块)
  • 三、思路
    • (一)迷宫/普通循迹思路
    • (二)避障停止思路
    • (三)红外切换模式思路
    • (四)虚线循迹思路
  • 四、总结
  • 五、备份

前言

为什么叫模块化呢,当然是因为分开写的缘故。每个模块拼在一起,组合成一个可用的程序。

正好比赛结束了,来年应该也没机会参加了,干脆写下思路,做个纪念。

本例提供ESP8266控制小车的模块代码,但并没有装载,而实际上,下位机LPC已经预留了一个定时器待用,这个定时器可以用来控制串口波特率,也就是给ESP8266用,也可以用来控制占空比。当然,我是没有控制占空比的,因为只有两节电池,还得使用升压模块,尽可能提速,依靠算法来解决转向和偏线问题。

事先说明,本案例所有的代码程序均基于现成小车调试,因小车已被赛事方回收,这里只能大概罗列所需零部件和配置环境,另外,该小车为赛事方提供零件,由选手自由组装,样式规格不一。

注意:芯片的烧写通过其他的渠道(如普中51开发板)进行烧写后换芯片,或者直接使用USB转TTL的CH340烧写器,这里不再详细阐述。

说明

正常来讲,L298N转向应该是这样的。
基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第1张图片
但我在最初的时候弄反了,所以整个代码就变成了,IN1-高,IN2-低,电机是反转,所以,如果不想改代码的,那就把电机方向换成跟我一样,也就是IN1和IN2互换,IN3和IN4互换(如果你的方向是正转的话)。如果改代码的话,就是把LEFT_IN3LEFT_IN4的端口互换(前提是你的方向是正转,也就是上图的标准)

总之你看着情况改,循迹模块所在方向是小车前进方向。

一、环境

(一)硬件

1. 配件

  1. 直流减速电机×4、塑胶轮胎×4(图见下)
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第2张图片

  2. L298N电机驱动模块×1
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第3张图片

  3. 红外接收模块×1、遥控器×1
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第4张图片

  4. 亚博BST51开发板×2
    (图源:亚博智能天猫旗舰店商品售卖页)
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第5张图片

  5. 超声波模块×1
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第6张图片

  6. TM1638按键数码管×1(扩展部分)
    (图源:钦源盛数码天猫专营店该商品页面)
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第7张图片

  7. 18650电池盒×1、18650电池×2
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第8张图片

  8. TCRT5000I - 5路循迹模块×1
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第9张图片

  9. ATK-ESP8266模块×1
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第10张图片

  10. DC-DC直流可调升压模块×1
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第11张图片

  11. 5V1A输出充电宝×1、USB线×1、杜邦线若干。

  12. 组装完成图:

2. 接线

以图中上下两芯片称为上位机(UPC)和下位机(LPC)。

  1. 超声波Echo端接UPC的P1^6
  2. 超声波Trig端接UPC的P1^7
  3. TM1638的DIO接UPC的P1^3CLKP1^4STBP1^5
  4. UPC超声波检测障碍发送低电平端口ULTRASONIC_INT=P2^4LPC接收触发外部中断1端口ULTRASONIC_INT1=P3^3,将这两个口连接起来
  5. 红外接收模块IRIN接外部中断0端口P3^2
  6. 电机接口(以小车前进方向),左两轮、右两轮分别各自焊接起来,实际上可以视为只有两个轮子,其中,左轮接线到L298N的OUT3、OUT4,右轮接线到OUT1、OUT2
  7. L298N接线:IN1、IN2、ENA管右边两轮,IN3、IN4、ENB管左边两轮。具体接线分别为IN1-P1^0IN2-P1^1IN3-P1^2IN4-P1^3ENA-P1^4ENB-P1^5
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第12张图片
    另外,12V端口接升压模块,升压模块再接电池供电,L298N的板载供电跳线帽不要拔,5V端口不接,上面两个芯片由另外的独立电源供电。
  8. 升压模块接3.7V×2=7.4V电池,再升压为9.8V电压供给给L298N
  9. 红外循迹模块OUT1-OUT5分别接P2^0-P2^4
  10. ESP8266的RXDUPCTXDTXDRXD

(二)软件

  1. Keil uVision5(含Intel的80/82RC芯片包)
  2. XCOM V2.6
  3. 普中自动下载软件1.86
  4. CH340驱动

二、代码

(一)UPC

1. 主函数

  1. main.h
#ifndef __MAIN_H__
#define __MAIN_H__

#include 
#include "TM1638.h"
#include "Ultrasonic.h"
//#include "beep.h"
//#include "connect_wifi.h"

#define True 1
#define False 0

typedef unsigned int uint;
typedef unsigned char uchar;

sbit ECHO = P1^6;
sbit TRIG = P1^7;

sbit ULTRASONIC_INT = P2^4;

#endif
  1. main.c
#include "main.h"

void main()
{
	init_timer0();
	init_TM1638();
	//init_esp8266();
	
	while (True)
	{
		ultrasonic_launch();
		process_ul_data();
		Write_DATA(4 * 2, tab[num[0]]);
		Write_DATA(5 * 2, tab[num[1]]);
		Write_DATA(6 * 2, tab[num[2]]);
		Write_DATA(7 * 2, numSymbols[num[3]]);
		if (num[0] == 0 && num[1] == 0 && num[2] <= 3 && num[3] <= 5)
			ULTRASONIC_INT = 0;
		else
			ULTRASONIC_INT = 1;
	}
}

2. 超声波模块

Ultrasonic.h

#ifndef __ULTRASONIC_H__
#define __ULTRASONIC_H__

#include "reg52.h"

#define DIG P0
#define ON 1
#define OFF 0

sbit Trig = P1^7;
sbit Echo = P1^6;

unsigned int distance = 0;
static unsigned int num[4];

// 初始化定时器0
void init_timer0()
{
	TMOD |= 0x01;
	TH0 = 0;
	TL0 = 0;
	ET0 = 1;
}

void delay(unsigned int i)
{
	while (i --)
		;
}

void process_ul_data()
{
	num[0] = distance / 1000 % 10;
	num[1] = distance / 100 % 10;
	num[2] = distance / 10 % 10;
	num[3] = distance / 1 % 10;

}

void ultrasonic_launch()
{
	unsigned int time = 0;
	Trig = 1;
	delay(20);
	Trig = 0;
	while (Echo == 0)
		;
	TR0 = 1;
	while (Echo)
		;
	TR0 = 0;
	time = TH0 * 256 + TL0;
	distance = (int)(time * 0.017);
	TH0 = 0;
	TL0 = 0;
	process_ul_data();
}

// 定时器0中断
void timer0() interrupt 1 using 2
{
	TH0 = 0;
	TL0 = 0;
}

#endif

3. TM1638模块

TM1638.h(官方的,非本人写,微改引脚)

#ifndef        _TM1638_H
#define        _TM1638_H

#include        

#define        DATA_COMMAND        0X40
#define        DISP_COMMAND        0x80
#define        ADDR_COMMAND        0XC0

//TM1638模块引脚定义
sbit DIO=P1^3;
sbit CLK=P1^4;
sbit STB=P1^5;

//共阴数码管显示代码
unsigned char code tab[]= {
	0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
    0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
	0x40 // ' - '
};
unsigned char code numSymbols[] = {
	0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF
};

void TM1638_Write(unsigned char DATA) //写数据函数
{
	unsigned char i;
	for(i=0; i<8; i++)
		{
			CLK=0;
			if(DATA&0X01)
				DIO=1;
			else
				DIO=0;
			DATA>>=1;
			CLK=1;
		}
}

void Write_COM(unsigned char cmd)    //发送命令字
{
	STB=0;
	TM1638_Write(cmd);
	STB=1;
}

void Write_DATA(unsigned char add,unsigned char DATA) //指定地址写入数据
{
	Write_COM(0x44);
	STB=0;
	TM1638_Write(0xc0|add);
	TM1638_Write(DATA);
	STB=1;
}

//TM1638初始化函数
void init_TM1638(void)
{
	unsigned char i;
	Write_COM(0x8b);       //亮度 (0x88-0x8f)8级亮度可调
	Write_COM(0x40);       //采用地址自动加1
	STB=0;                           //
	TM1638_Write(0xc0);    //设置起始地址

	for(i=0; i<16; i++)         //传送16个字节的数据
		TM1638_Write(0x00);
	STB=1;
}
#endif

(二)LPC

1. 主函数

  1. main.h
#ifndef __MAIN_H__
#define __MAIN_H__

#include 
#include "pwm_control.h"
//#include "connect_wifi.h"

#define True 1
#define False 0
#define ON 1
#define OFF 0

#define REMOTE_CONTROL 0x00
#define TRACKING_GP 0x01
#define TRACKING_MAZE 0x02
#define TRACKING_ZEBRA 0x03

typedef int flag;
typedef unsigned int uint;
typedef unsigned char uchar;

sbit IRIN = P3^2;
sbit ULTRASONIC_INT1 = P3^3;

#endif
  1. main.c
#include "main.h"

// 定时器计数
static uint T0_CT = 0;
static uint T1_CT = 0;
static uint T2_CT = 0;

// 左右轮占空比,在需要时可以使用,目前保留待用(限T0)
uint t0_vue_left = 0;
uint t0_vue_right = 0;

// 延时设值
uint t1_vue = 0;
uint t2_vue = 0;

// 定时器1是否已经使用过一次,只有再次判到其他条件才能复位
flag is_timing_flag = False;

// 循迹模式:TRACKING_GP(默认)
flag flag_mod_tracking = REMOTE_CONTROL;

// 红外遥控事件,收到红外则处理
flag flag_infrared = OFF;

// 红外所需基本参数
uchar Time;
uchar IrValue[6];


// 控制占空比(可选)
/********************************************************************/
void set_speed(uint left, uint right)
{
	t0_vue_left = left;
	t0_vue_right = right;
}

// 维持占空比,维持(非调整)各电机运转速度(可选)
/********************************************************************/
void keep_motor_speed()
{
	if (T0_CT >= 100)
		T0_CT = 0;

	if (T0_CT <= t0_vue_right)
		RIGHT_ENA = ON;
	else
		RIGHT_ENA = OFF;

	if (T0_CT <= t0_vue_left)
		LEFT_ENB = ON;
	else
		LEFT_ENB = OFF;
}

// 不精准延时
/********************************************************************/
void delay(uint i)
{
	while (i--)
		;
}

// T2延时
/********************************************************************/
void delay_timer2(uint set_timing)
{
	t2_vue = set_timing;
	TR2 = ON;
	while (TR2)
		;
}

// 初始化定时器0(可选)
/********************************************************************/
void init_timer0()
{
	TMOD |= 0x01;
	ET0 = ON;
	TH0 = 0xff;
	TL0 = 0x9c;
}

// 初始化定时器1、2
/********************************************************************/
void init_timer()
{
	TMOD |= 0x10;

	RCLK = TCLK = 0;
	CP_RL2 = EXEN2 = 0;

	ET1 = ET2 = ON;

	TH1 = TH2 = RCAP2H = 0xfc;
	TL1 = TL2 = RCAP2L = 0x18;
	//init_timer0();	//(可选)
}

// 外部中断0初始化
/********************************************************************/
void init_int0()
{
	IT0 = 1;
	EX0 = 1;
	IRIN = 1;
}

// 外部中断1初始化
/********************************************************************/
void init_int1()
{
	IT1 = 0;
	EX1 = ON;
}

// WiFi控制模式(保留待用)
/********************************************************************/
void wifi_control()
{
//	uchar tmp = P0 & 0x0f;
//	switch (tmp)
//	{
//		case 0x0a:	// IN1 = 0 IN2 = 1 IN3 = 0 IN4 = 1 向前 1010
//			motor_go_straight();
//			break;
//
//		case 0x05:	// IN1 = 1 IN2 = 0 IN3 = 1 IN4 = 0 向后 0101
//			motor_back_reversal();
//			break;
//
//		case 0x09:	// IN1 = 1 IN2 = 0 IN3 = 0 IN4 = 1 向左 1001
//			motor_turn_left();
//			break;
//
//		case 0x06:	// IN1 = 0 IN2 = 1 IN3 = 1 IN4 = 0 向右 0110
//			motor_turn_right();
//			break;
//
//		case 0x00:	// IN1 = 0 IN2 = 0 IN3 = 0 IN4 = 0 制动
//			motor_stop();
//			break;
//
//		default:
//			motor_stop();
//	}
}

// 普通循迹
/********************************************************************/
void tracking_gp()
{
	uchar tmp = P2 & 0x1f;
	switch (tmp)
	{
	case 0x1f: // 11111 - 11111
	case 0x18: // 11000 - 00011 TL
	case 0x10: // 10000 - 00001 TL
		if (is_timing_flag == OFF)
		{
			if (!TR1)
			{
				t1_vue = 500;
				TR1 = ON;
			}
		}
		if (TR1)
			motor_turn_right();
		else
			motor_turn_left();
		break;

	case 0x03: // 00011 - 11000 TR
	case 0x01: // 00001 - 10000 TR
		motor_turn_right();
		delay_timer2(320);
		is_timing_flag = OFF;
		break;
	case 0x00: // 00000 - 00000 -
		motor_go_straight();
		delay_timer2(120);
		motor_stop();
		delay_timer2(1500);
		motor_turn_right();
		delay_timer2(450);
		is_timing_flag = OFF;
		break;
	case 0x0f: // 01111 - 11110 →
	case 0x07: // 00111 - 11100 CR
	case 0x17: // 10111 - 11101 CR
		motor_turn_right();
		delay_timer2(20);
		is_timing_flag = OFF;
		break;
	case 0x1e: // 11110 - 01111 ←
	case 0x1d: // 11101 - 10111 CL
	case 0x1c: // 11100 - 00111 CL
		motor_turn_left();
		delay_timer2(20);
		is_timing_flag = OFF;
		break;
	case 0x1b: // 11011 - 11011 |

	case 0x13: // 10011 - 11001 CR

	case 0x19: // 11001 - 10011 CL
		motor_go_straight();
		is_timing_flag = OFF;
		break;
	case 0x0a: // 01010 - 01010 P
		motor_stop();
		break;
	default:;
	}
}

// 迷宫循迹
/********************************************************************/
void tracking_maze()
{
	uchar tmp = P2 & 0x1f;
	switch (tmp)
	{
	case 0x1f: // 11111 - 11111
	case 0x18: // 11000 - 00011 TL
	case 0x10: // 10000 - 00001 TL
		if (is_timing_flag == OFF)
		{
			if (!TR1)
			{
				t1_vue = 460;
				TR1 = ON;
			}
		}
		if (TR1)
			motor_turn_right();
		else
			motor_turn_left();
		break;

	case 0x03: // 00011 - 11000 TR
	case 0x01: // 00001 - 10000 TR
		motor_turn_right();
		delay_timer2(300);
		is_timing_flag = OFF;
		break;

	case 0x00: // 00000 - 00000 -
		motor_turn_right();
		delay_timer2(300);
		is_timing_flag = OFF;
		break;

	case 0x0f: // 01111 - 11110 CR
	case 0x07: // 00111 - 11100 CR
	case 0x17: // 10111 - 11101 CR
		motor_turn_right();
		delay_timer2(20);
		is_timing_flag = OFF;
		break;

	case 0x1e: // 11110 - 01111 CL
	case 0x1d: // 11101 - 10111 CL
	case 0x1c: // 11100 - 00111 CL
		motor_turn_left();
		delay_timer2(10);
		is_timing_flag = OFF;
		break;

	case 0x1b: // 11011 - 11011 |
	case 0x13: // 10011 - 11001 CR
	case 0x19: // 11001 - 10011 CL
		motor_go_straight();
		is_timing_flag = OFF;
		break;

	case 0x0a: // 01010 - 01010 P
		motor_stop();
		break;
	default:;
	}
}

// 斑马线循迹
/********************************************************************/
void tracking_zebra()
{
	uchar tmp = P2 & 0x1f;
	switch (tmp)
	{
		case 0x1f:	// 11111 - 11111
			break;

		case 0x1b:	// 11011 - 11011 |
			motor_go_straight();
			is_timing_flag = OFF;
			break;


		case 0x07:	// 00111 - 11100 CR
		case 0x17:	// 10111 - 11101 CR
		case 0x0f:	// 01111 - 11110 CR
			motor_stop();
			motor_turn_right();
			delay_timer2(10);
			break;

		case 0x13:	// 10011 - 11001 CR
		case 0x15:	// 10101 - 10101 |
		case 0x0b:	// 01011 - 11010 TR
		case 0x03:	// 00011 - 11000 TR
		case 0x01:	// 00001 - 10000 TR
			motor_turn_right();
			delay_timer2(420);
			motor_stop();
			break;


		case 0x1c:	// 11100 - 00111 CL
		case 0x1d:	// 11101 - 10111 CL
		case 0x1e:	// 11110 - 01111 CL
			motor_stop();
			motor_turn_left();
			delay_timer2(10);
			break;

		case 0x19:	// 11001 - 10011 CL
		case 0x1a:	// 11010 - 01011 TL
		case 0x18:	// 11000 - 00011 TL
		case 0x10:	// 10000 - 00001 TL
			motor_turn_left();
			delay_timer2(420);
			motor_stop();
			break;

/* 左转再右转 未实现*/
//		case 0x0b:	// 01011 - 11010 TR
//		case 0x1a:	// 11010 - 01011 TL
//		case 0x15:	// 10101 - 10101 TR then TL
//			if (is_timing_flag == OFF)
//			{
//				if (!TR1)
//				{
//					t1_vue = 390;
//					TR1 = ON;
//				}
//			}
//			if (TR1)
//				motor_turn_right();
//			else
//				motor_turn_left();
//			break;

		case 0x0a:	// 01010 - 01010 P
			motor_stop();
			break;

		default:
			;
	}
}

// INT0红外接收处理函数:切换模式、电机运动
/********************************************************************/
void process_infrared()
{
	switch (IrValue[2] / 16)
	{
	case 0:
		switch (IrValue[2] % 16)
		{
		case 7: // 0x07
			// TL
			if (flag_mod_tracking == REMOTE_CONTROL || flag_mod_tracking == TRACKING_ZEBRA)
				motor_turn_left();
			break;
		case 8: // 0x08
			// MOD: TRACKING_ZEBRA
			motor_stop();
			flag_mod_tracking = TRACKING_ZEBRA;
			break;
		case 9: // 0x09
			// TR
			if (flag_mod_tracking == REMOTE_CONTROL || flag_mod_tracking == TRACKING_ZEBRA)
				motor_turn_right();
			break;
		case 12: // 0x0c
			// MOD: REMOTE_CONTROL
			motor_stop();
			flag_mod_tracking = REMOTE_CONTROL;
			break;
		}
		break;

	case 1:
		switch (IrValue[2] % 16)
		{
		case 5: // 0x15
			// stop
			motor_stop();
			delay_timer2(2000);
			break;
		case 8: // 0x18
			// MOD: TRACKING_GP
			motor_stop();
			flag_mod_tracking = TRACKING_GP;
			break;
		case 9: // 0x19
			// back
			if (flag_mod_tracking == REMOTE_CONTROL || flag_mod_tracking == TRACKING_ZEBRA)
				motor_back_reversal();
			break;
		}
		break;

	case 4:
		switch (IrValue[2] % 16)
		{
		case 0: // 0x40
			// Go
			if (flag_mod_tracking == REMOTE_CONTROL || flag_mod_tracking == TRACKING_ZEBRA)
				motor_go_straight();
			break;
		case 5: // 0x45
			// stop
			motor_stop();
			delay_timer2(2000);
			break;
		}
		break;

	case 5:
		switch (IrValue[2] % 16)
		{
		case 14: // 0x5e
			// MOD: TRACKING_MAZE
			motor_stop();
			flag_mod_tracking = TRACKING_MAZE;
			break;
		}
		break;
	}
	flag_infrared = OFF;
}

// MAIN函数
/********************************** MAIN **********************************/
void main()
{
	EA = 1;
	init_int0();
	init_int1();
	init_timer();
	init_motor();
	set_speed(80, 80);

	while (True)
	{
		/*需要时打开定时器控制占空比,可选*/
//		if (flag_mod_tracking == TRACKING_ZEBRA)
//			TR0 = ON;
//		else
//			TR0 = OFF;
	
		if (flag_mod_tracking == REMOTE_CONTROL)
			wifi_control();
		else if (flag_mod_tracking == TRACKING_GP)
			tracking_gp();
		else if (flag_mod_tracking == TRACKING_MAZE)
			tracking_maze();
		else if (flag_mod_tracking == TRACKING_ZEBRA)
			tracking_zebra();
		if (flag_infrared)
			process_infrared();
	}
}

// 定时器0中断函数:控制占空比(可选)
/*------------------------------------------------------------------*/
void timer0() interrupt 1
{
	TH0 = 0xff;
	TL0 = 0x9c;
	T0_CT ++;
	keep_motor_speed();
}

// 定时器1中断函数:转向计数
/*------------------------------------------------------------------*/
void timer1() interrupt 3
{
	TH1 = 0xfc;
	TL1 = 0x18;
	T1_CT++;
	if (T1_CT == t1_vue)
	{
		T1_CT = t1_vue = 0;
		is_timing_flag = ON;
		TR1 = OFF;
	}
}

// 定时器2中断函数:延时功能
/*------------------------------------------------------------------*/
void timer2() interrupt 5
{
	TH2 = RCAP2H = 0xfc;
	TL2 = RCAP2L = 0x18;
	TF2 = 0;
	T2_CT++;

	if (T2_CT == t2_vue)
	{
		T2_CT = t2_vue = 0;
		TR2 = OFF;
	}
}

// 外部中断1中断函数:检测到超声波达标停止运动
/*------------------------------------------------------------------*/
void int1() interrupt 2
{
	if (ULTRASONIC_INT1 == 0)
		motor_stop();
}

// 外部中断0中断函数:控制模式、运作
/*------------------------------------------------------------------*/
void int0() interrupt 0
{
	uchar j, k;
	uint err;
	Time = 0;
	delay(700);
	if (IRIN == 0)
	{
		err = 1000;
		while ((IRIN == 0) && (err > 0))
		{
			delay(1);
			err--;
		}
		if (IRIN == 1)
		{
			err = 500;
			while ((IRIN == 1) && (err > 0))
			{
				delay(1);
				err--;
			}
			for (k = 0; k < 4; k++)
			{
				for (j = 0; j < 8; j++)
				{

					err = 60;
					while ((IRIN == 0) && (err > 0))
					{
						delay(1);
						err--;
					}
					err = 500;
					while ((IRIN == 1) && (err > 0))
					{
						delay(10);
						Time++;
						err--;
						if (Time > 30)
						{
							return;
						}
					}
					IrValue[k] >>= 1;
					if (Time >= 8)
					{
						IrValue[k] |= 0x80;
					}
					Time = 0;
				}
			}
			flag_infrared = ON;
		}
		if (IrValue[2] != ~IrValue[3])
		{
			return;
		}
	}
}

2. 电机控制

  1. pwm_control.h
#ifndef __PWM_CONTROL_H__
#define __PWM_CONTROL_H__

#include 
#include "main.h"

sbit RIGHT_IN1 = P1 ^ 0;
sbit RIGHT_IN2 = P1 ^ 1;

sbit LEFT_IN3 = P1 ^ 2;
sbit LEFT_IN4 = P1 ^ 3;

sbit LEFT_ENB = P1 ^ 4;
sbit RIGHT_ENA = P1 ^ 5;

void init_motor();
void motor_go_straight();
void motor_back_reversal();
void motor_stop();
void motor_turn_left();
void motor_turn_right();

#endif
  1. pwm_control.c
#include "pwm_control.h"

#define ON 1
#define OFF 0

// 初始化电机函数
/********************************************************************/
void init_motor()
{
	motor_stop();
	LEFT_ENB = ON;
	RIGHT_ENA = ON;
}

// 向前
/********************************************************************/
void motor_go_straight()
{
	motor_stop();
	LEFT_IN3 = 0;
	LEFT_IN4 = 1;
	RIGHT_IN1 = 0;
	RIGHT_IN2 = 1;
}

// 向后
/********************************************************************/
void motor_back_reversal()
{
	motor_stop();
	LEFT_IN3 = 1;
	LEFT_IN4 = 0;
	RIGHT_IN1 = 1;
	RIGHT_IN2 = 0;
}

// 制动
/********************************************************************/
void motor_stop()
{
	LEFT_IN3 = 0;
	LEFT_IN4 = 0;
	RIGHT_IN1 = 0;
	RIGHT_IN2 = 0;
}

// 左转
/********************************************************************/
void motor_turn_left()
{
	motor_stop();
	LEFT_IN3 = 1;
	LEFT_IN4 = 0;
	RIGHT_IN1 = 0;
	RIGHT_IN2 = 1;
}

// 右转
/********************************************************************/
void motor_turn_right()
{
	motor_stop();
	LEFT_IN3 = 0;
	LEFT_IN4 = 1;
	RIGHT_IN1 = 1;
	RIGHT_IN2 = 0;
}


(三)ATK-ESP8266 (未使用模块)

这里仍然使用的是定时器2,需要可以自行更改为定时器0

  1. connect_wifi.h
#ifndef __CONNECT_WIFI_H__
#define __CONNECT_WIFI_H__

#include 
#include 
#include "pwm_control.h"

void init_uart();
void init_esp8266();
void send_char(unsigned char ch);
void send_string(unsigned char *str);
void send_msg(unsigned char msg[]);
void process_mcu_receive_data();
void delay_us();
void delay_s();

#endif
  1. connect_wifi.c
#include "connect_wifi.h"

unsigned char receive[50];
unsigned char sign = 0;

//初始化串口
/********************************************************************/
void init_uart()
{
	PCON &= 0x7f;
	SCON = 0x50;
	T2CON = 0x34;
	TH2 = RCAP2H = 0xff;
	TL2 = RCAP2L = 0xdc;
	TR2 = 1;
	ES = 1;
}

// 初始化ESP8266,设置AP模式
/********************************************************************/
void init_esp8266()
{
  	init_uart();
	delay_s();
	send_string("AT+CWMODE=2\r\n");
	delay_us();
	send_string("AT+CIPMUX=1\r\n");
	delay_us();
	send_string("AT+CIPSERVER=1,8086\r\n");
}

// 发送位数据
/********************************************************************/
void send_char(unsigned char ch)
{
	SBUF = ch;
	while (TI == 0)
		;
	TI = 0;
}

// 发送字节数据
/********************************************************************/
void send_string(unsigned char *str)
{
	while (*str != '\0')
	{
		send_char(*str);
		str++;
	}
}

// 透传发送消息
/********************************************************************/
void send_msg(unsigned char msg[])
{
	unsigned int i;
	char cmd[] = "AT+CIPSEND=0,1\r\n";
		cmd[11] = i + '0';
		send_string(cmd);
		delay_us();
		send_string(msg);
		delay_us();
//	for (i = 0; i < 5; i++)
//	{
//		cmd[11] = i + '0';
//		send_string(cmd);
//		delay_us();
//		send_string(msg);
//		delay_us();
//	}

	memset(receive, 0, 50);
}

// 单片机接收数据处理
/********************************************************************/
void process_mcu_receive_data()
{
	switch (receive[0])
	{
		case '0':
			send_msg("0x00");
			motor_stop();
			break;
		case '1':
			motor_turn_left();
			send_msg("0x01");
			break;
		case '2':
			motor_turn_right();
			send_msg("0x02");
			break;
		case '3':
			motor_go_straight();
			send_msg("0x03");
			break;
		case '4':
			motor_back_reversal();
			send_msg("0x04");
			break;
		case '5':
			motor_stop();
			send_msg("0x05");
			break;
	}
	
}

// 串口中断,此程序只把+IPD,x,x:后的有用数据存进receive数组里
/********************************************************************/
void uart() interrupt 4
{
	if (RI)
	{
		RI = 0;
		if (sign == 1)
		{
			receive[0] = SBUF;
			sign = 0; //保存receive[0]的数据
		}
		if (SBUF == ':')
			sign = 1;
	}
	process_mcu_receive_data();
}

// 短延时
/********************************************************************/
void delay_us()
{
	unsigned char count = 1000;
	while (count--)
		;
}

// 长延时
/********************************************************************/
void delay_s()
{
	unsigned char a, b, c, d;
	for (a = 5; a > 0; a--)
		for (b = 4; b > 0; b--)
			for (c = 116; c > 0; c--)
				for (d = 214; d > 0; d--)
					;
}

三、思路

已知:检测到黑线,循迹模块输出低电平给LPC
假设:小车前进方向左侧检测到黑线,为01111↑,根据接线方式,得到的BIN应该是xxx11110↓,那么得到的HEX为0x1E。

下面均基于小车前进方向进行阐述,主要讲解迷宫循迹,因为虚线循迹还不是很完善,有20%的概率出错。普通循迹和迷宫循迹只有5%的概率。

(一)迷宫/普通循迹思路

  1. 当遇到11011,直走就好了;
  2. 遇到10111或11101 ↑,稍微左转或右转,这个稍微指的是延时10ms即可,10011或11001也类似;
  3. 遇到01111或11110、00111或11100,同样稍微左转或右转,只不过这个稍微的延时为40ms即可。(注意:因为驱动电压不同,我的电压为9.8V,延时所需时间更短);
  4. 遇到右直角即10000或11000,那么延时380ms约85°~95°(9.8V);
  5. 遇到十字路口,或者T型路口,右转90°;
  6. 重点来了:遇到左直角或全空,也就是00011或00001,或11111,那么先向右转一定的角度(90°或150°),判断是否有右线,如果没有,再一直左转,看看前面有没有黑线,如果没有就继续向左转,判断有没有左线,如果还没有就继续向左转,判断回来的路线还在不在(不在就是被你拿走了),整个路线约旋转270°,看似低效率,其实还蛮高的。具体右转多少度就得看你是走迷宫还是走锐角,迷宫标准是90°,锐角是170°通常,循迹板占了一定长度,所以大概只需转150°这样即可。
  7. 说是这么说,但你怎么保证在转的过程没有个100°的线呢,又不一定正好是90°,而且要是直走也突然感应不到黑线(夹在两个红外循迹中间)那也得转90°,效率是不是太低了。
  8. 没错,为了解决这个问题,我引入了一个定时器,除了精准延时定时器T2外,定时器T1就是为了解决这个问题。
  9. 假设给定一个标志flag,这个标志用于判断T1是否延时过了,也就是是否打开过一次定时器T1,如果打开,那再次遇到全1的时候,不再左转,只会右转,完整思路如下。
  10. 假设遇到11111或左直角,先检测flag是不是开启过了,如果没有,那么允许打开定时器T1,并且设定延时值为500ms约160°。
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第13张图片
    这时候执行下一行,同时T1已经开始计时,可以理解为后台计时。下一行是判断T1是否已经打开,如果打开就向右转,至于转多少你不用理,只要T1开着就右转,只要关着就左转,右转的过程假设小车前进方向2点钟有黑线,那么小车就会执行其他条件进行矫正,从而打断了右转90°的过程,之后会执行某个操作,这个操作等会说。
    左右转
    那么再来看看定时器T1中断,在没有达到延时预设值时就继续++,如果达到了,那么重置计数值T1_CT和延时值t1_vue,设置flag为ON,即已经打开过T1,并且关掉定时器T1。
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第14张图片
    回到刚才的转向代码,假设右转过程都没有遇到黑线,并且T1已经关了,又因为刚才的flag已经ON了,也就意味着T1不能再打开,那么小车会开始向左转,左转过程中如果遇到黑线就继续执行对应条件,没有的话继续左转就调头了(因为回头肯定有路)。
  11. 那么遇到黑线执行操作再遇到全空,T1又不能打开,那不是完蛋了?没错,所以刚刚我说的某个骚操作就是为了解决这个问题。
  12. 当转向过程遇到黑线后,除了执行对应条件调整方向外,还应该添加一个指令,将flag置为OFF,也就是允许下次遇到全空时再次打开定时器T1。is_timing_flag = OFF;
    基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第15张图片
    这样一来,就解决了转向过程遇到黑线以及继续循迹后无法再次右转的问题。不懂这样讲清楚没,不懂就问。

(二)避障停止思路

  1. 超声波计算距离,然后取值发送个TM1638数码管,这些百度就有了,不多说。
  2. 当达到某个距离时,给ULTRASONIC_INT置0,引发下位机的外部中断。
  3. 下位机外部中断后,执行刹车函数,就这样,没了,是不是很简单。
  4. 对了,这里用的是低电平触发中断,而不是下降沿触发。

(三)红外切换模式思路

  1. 当检测到按键红外时引发外部中断,接着获取得到的红外值,根据值去处理对应的事件。
  2. 这个处理不需要放在外部中断只需要放在主循环中即可。

(四)虚线循迹思路

  1. 基于已有的迷宫循迹算法,其实只是增加了类似10101之类的条件。
  2. 在11111的时候不做处理,也可以做处理,设置延时时间,如果超过延时则倒退,并且向右检测再向左检测。这里我并没有实现,因为时间关系,但另一组实现了该功能,但因为接线方式不同,无法应用到本例。这里提供部分代码作为参考,注意,该实例使用了占空比。
  3. 它其实也是跑得挺好的,虚线,只不过我理解不了,有兴趣还是可以了解下。

下面是部分代码:

uchar Duty_left,Duty_right,j=0,i=0,k=0,h=0,g=0,flag=0;t=0,m=0,n=0,f=0,v=0;
uint_16 hwdate=0x00;

void ting(){
    IN1=0;
    IN2=0;
    IN3=0;
    IN4=0;
}

//向左矫正(用于left2检测到黑线)
void correct_left()
{
 Duty_left=40;
 Duty_right=15;
	IN1=1;       
    IN2=0;
    IN3=1;
    IN4=0;  
}
//向右校正(用于right4检测到黑线)
void correct_right()
{
  Duty_left=15;
  Duty_right=40;
	IN1=1;
    IN2=0;
    IN3=1;
    IN4=0; 
}


//小车左转赋值(用于left1检测到黑线)
void Left_turning()
{	
    Duty_left=50;
    Duty_right=50;
    IN1=1;
    IN2=0;
    IN3=0;
    IN4=1;
}

//小车右转赋值(用于right5检测到黑线)
void Right_turning()
{ 
    Duty_left=50;
    Duty_right=50;
    IN1=0;        //转弯时一个正转一个反转
    IN2=1;
    IN3=1;
    IN4=0;
}

//小车左转赋值(用于left1检测到黑线)
void Left_turning1()
{	
    Duty_left=42;
    Duty_right=42;
    IN1=1;
    IN2=0;
    IN3=0;
    IN4=1;
}

//小车右转赋值(用于right5检测到黑线)
void Right_turning1()
{ 
   Duty_left=42;
    Duty_right=42;
    IN1=0;        //转弯时一个正转一个反转
    IN2=1;
    IN3=1;
    IN4=0;
 }


//小车直走前行,不拐弯速度赋值
void forward_move()
{
    Duty_left=40;
    Duty_right=40;
    IN1=1;
    IN2=0;
    IN3=1;
    IN4=0;
}
//小车直走前行,不拐弯速度赋值
void forward_move1()
{
    Duty_left=27;
    Duty_right=35;
    IN1=1;
    IN2=0;
    IN3=1;
    IN4=0;
}
//小车后退
void houtui(){
	Duty_left=15;
    Duty_right=45;
    IN1=0;
    IN2=1;
    IN3=0;
    IN4=1;
}

void detect_infrared2(){
   hwdate = P2 & 0x1F;			//读取循迹的值
		switch(hwdate)
		{
			case 0x1b:  //11011  直走
			forward_move1();
			t=1;
			break;

			case 0x1d:  //11101 向右矫正
			Right_turning();v=1;
			break;

			case 0x17:	//10111  向左矫正			
			Left_turning();f=1;
			break;

			case 0x0f:
			Left_turning();m=1;
			break;

			case 0x1e:
			Right_turning();n=1;
		    break;
			
			//左直角	 //00011  00111 01011 10011	 
		    case 0x03:
		    case 0x07:
		    case 0x13:
		    case 0x0b:
		    case 0x01: 
		    h=1;
			Left_turning();
			delay_timer1(150);
			break;

			//右直角	//11000  11100 11010 11110 11001
			case 0x18:
			case 0x1c: 
			case 0x1a:
		    case 0x10:
			case 0x19: 
			k=1;
			Right_turning();
			delay_timer1(150);   
			break;

			case 0x09:
			Left_turning();delay_timer1(50);
			break;
				
	    //11111
		case 0x1f:
		if(k==1) {
		Right_turning();
		k=0;t=0;
		} 

		else if (h==1){
		Left_turning();
		h=0;t=0;
		}

		else if(f==1){
		Left_turning();
		f=0;t=0;
		}

		else if(m==1){
		Left_turning();
		m=0;t=0;
		}

		else if(n==1){
		Right_turning();
		n=0; t=0;
		}

		else if(v==1){
		Right_turning();
		v=0;t=0;
		}
		
		else if(t==1)
		{
		flag++;
        if(flag>20){
//		Left_turning();
        houtui();
		flag=0;t=0;
		}
		delay(1500);
		}
		break;

		case 0x0A: ting();delay_timer1(5000); 
		break;
		}

}

四、总结

      总结就是没总结,其实虚线循迹还是有点问题的,遇到直角的时候速度太快会转不过来,另外就是WiFi模块加进去还没弄好,在备份代码里面是好的,但移植到决赛代码里面就出了BUG,单独使用没问题。

其他的,等我想到先再补充吧。

五、备份

这些是我初赛用的代码,还有一些其他东西,各种奇奇怪怪的,反正都是之前写的代码。
本来想上传文件的,但有点懒,还是算了,反正也不是重点。
基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第16张图片
基于STC89C52RC的小车黑线钝角、锐角、迷宫、虚线循迹模块化代码_第17张图片
初赛用到了占空比:main.c

#include "main.h"

uint t2_vue = 0;	// 定时器2延时值
static uint T0_CT = 0;

flag flag_is_timing0 = False;	// 判断定时器0是否使用过一次,需手动置False
flag motor_statu = ON;	// 电机状态

uint left_time = 60;
uint right_time = 60;

// 定时器2延时
/********************************************************************/
void delay_timer2(uint set_timing)
{
	t2_vue = set_timing;
	TR2 = ON;
	while (TR2)
		;
}

// 初始化定时器0、2
/********************************************************************/
void init_timer()
{
	TMOD |= 0x01;
	RCLK = TCLK = 0;
	CP_RL2 = EXEN2 = 0;
	ET0 = ET2 = ON;
	TH0 = 0xff;
	TL0 = 0xf7;
	TH2 = RCAP2H = 0xfc;
	TL2 = RCAP2L = 0x18;
	TR0 = ON;
}

// 循迹
/********************************************************************/
void tracking()
{
	uchar tmp = P2 & 0x1f;
	switch (tmp)
	{
		case 0x1f:	// 11111 - 11111
			break;
		case 0x18:	// 11000 - 00011 TL
		case 0x14:	// 10100 - 00101 V
		case 0x10:	// 10000 - 00001 TL
		case 0x03:	// 00011 - 11000 TR
		case 0x01:	// 00001 - 10000 TR
		case 0x0e:	// 01110 - 01110 V
		case 0x05:	// 00101 - 10100 V
		case 0x00:	// 00000 - 00000 -
		case 0x1b:	// 11011 - 11011 |
		case 0x17:	// 10111 - 11101 CR
		case 0x1d:	// 11101 - 10111 CL
			
		case 0x0f:	// 01111 - 11110 CR
		case 0x07:	// 00111 - 11100 CR
		case 0x13:	// 10011 - 11001 CR
			
		case 0x1e:	// 11110 - 01111 CL
		case 0x1c:	// 11100 - 00111 CL
		case 0x19:	// 11001 - 10011 CL
		case 0x0a:	// 01010 - 01010 P
		
		default:
			;
	}
}

// 主函数
/*##################################################################*/
void main()
{
	EA = 1;
	init_esp8266();
	init_motor();
	init_timer();
	while (True)
	{
		
	}
}

// 定时器0中断:控制占空比
/*------------------------------------------------------------------*/
void Timer0() interrupt 1
{static uint T0_CT = 0;
	TR0 = OFF;
	TH0=0xff;
	TL0=0xf7;
	
	T0_CT ++;
//	tracking();
	if (T0_CT >= 100)
		T0_CT = 0;

//	if (T0_CT <= left_time)
//		RIGHT_ENA = ON;
//	else
//		RIGHT_ENA = OFF;

//	if (T0_CT <= right_time)
//		LEFT_ENB = ON;
//	else
//		LEFT_ENB = OFF;
	TR0 = ON;
}

// 定时器2中断:延时功能
/*------------------------------------------------------------------*/
void Timer2() interrupt 5
{
	static uint T2_CT = 0;
	TH2 = RCAP2H = 0xfc;
	TL2 = RCAP2L = 0x18;
	TF2 = 0;
	T2_CT ++;
	
	if (T2_CT == t2_vue)
	{
		T2_CT = 0;
		t2_vue = 0;
		TR2 = OFF;
	}
}

其实这些没什么,只是希望大家能多注意备份,因为很多我之前能跑的代码,改着改着就动都不动了

最后,大家加油,奥里给!!

你可能感兴趣的:(单片机,单片机)