基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)

文章超详细,包含全部代码

本项目基于stc89c52rc。pwm控速,语音控制切换 循迹 / 跟随 / 避障 / 蓝牙WIFI控制 等模式,包含测速屏显并传给上位机等功能。

目录

文章超详细,包含全部代码

一 L9110s电机驱动模块(后改为L1298N+18650电池x2)

控制电机动起来 

二 小车动起来

1.控制小车前后左右停

2.无线射频遥控 

三 蓝牙控制小车

1.串口

2.蓝牙控制

3.点动模式蓝牙控制

四 PWM调速

测试代码

左右轮分别调速

五 红外循迹、跟随

1.红外循迹模块

2.循迹原理

3.跟随

4.红外 循迹、跟随 代码整合

六 超声波避障

转头测距

代码:

结合小车运动实现避障

更改代码:

封装函数和改进

七 小车测速及OLED屏显

进行测速实验(发现问题与改进)

开始代码优化

蓝牙调速小车:

双轮测速

 OLED显示速度

八 WIFI/4G通信

 WiFi控制小车

Wifi传输速度

4G通信

九 SU-03T语音模块

十 功能整合——全能小车

全部代码:

主函数:

定时器及其中断

串口 

红外循迹跟随

超声波避障

OLED显示

 电机驱动


基于C51的智能小车

一 L9110s电机驱动模块(后改为L1298N+18650电池x2)

接通VCC,GND 模块电源指示灯亮, 以下资料来源官方,但是不对,根据下节课实际调试

IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;

IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;

IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;

IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;

 基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第1张图片

 和C51开发板的接线

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第2张图片

 接线中,单片机要单独接出一条gnd连接电源

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第3张图片

控制电机动起来 

控制轮子旋转,根据实际运行情况调整电机正反接线,保证两轮都向前

#include "reg52.h"

sbit RightA = P3^2;
sbit RightB = P3^3;

sbit LeftA = P3^4;
sbit LeftB = P3^5;

void main()
{
	LeftA = 1;
	LeftB = 0;
	
	RightA = 1;
	RightB = 0;
}

二 小车动起来

1.控制小车前后左右停

前后左右封装函数: 

void forward(){
	LeftA = 1;
	LeftB = 0;
	
	RightA = 1;
	RightB = 0;
}

void back(){
	LeftA = 0;
	LeftB = 1;
	
	RightA = 0;
	RightB = 1;
}

void left(){
	LeftA = 1;
	LeftB = 1;
	
	RightA = 1;
	RightB = 0;
}

void right(){
	LeftA = 1;
	LeftB = 0;
	
	RightA = 1;
	RightB = 1;
}

void stop(){
	LeftA = 1;
	LeftB = 1;
	
	RightA = 1;
	RightB = 1;
}

void main()
{
	while(1){
		forward();
		Delay2000ms();
		
		back();
		Delay2000ms();
		
		left();
		Delay2000ms();
		
		right();
		Delay2000ms();

        stop();
		Delay2000ms();
	}
}

2.无线射频遥控 

及其简单的IO操作,参考本人之前的文章,一看就会

(50条消息) C51:无线遥控电动车防盗器_我有在好好学习的博客-CSDN博客

 点动和自锁模块都通用

#include "reg52.h"
#include "motor.h"

sbit D0 = P2^4;
sbit D1 = P2^5;
sbit D2 = P2^6;
sbit D3 = P2^7;

void main()
{
	char dirc = 0;
	while(1){
		
		if(D0)	dirc = 1;
		else if(D1)	dirc = 2;
		else if(D2)	dirc = 3;
		else if(D3)	dirc = 4;
		else	dirc = 0;
		
		switch(dirc){
			case 1:
				forward();
				break;
			
			case 2:
				back();
				break;
			
			case 3:
				left();
				break;
			
			case 4:
				right();
				break;
			
			case 0:
				stop();
				break;
		}
	}
}

三 蓝牙控制小车

后续为了不臃肿,分文件编程,参考上个c51温湿度监测系统项目最后的分文件操作:

(41条消息) 基于C51的 温湿度监测系统(及蓝牙温控风扇)_我有在好好学习的博客-CSDN博客

1.串口

添加串口功能,用蓝牙进行控制,参考上面文章。

接收字符 f b l r s ,使得电机 前 后 左 右 停 。

main.c

#include "reg52.h"
#include "delay.h"
#include "motor.h"
#include "uart.h"

void main()
{
    //串口初始化
	UartInit();
	
	while(1){
		//给电机控制函数传入获得的串口指令
		motor_ON(get_Uart());
		
	}
}

motor.c中的函数

void motor_ON(char dir)
{
	switch(dir){
		case 'f':
			forward();
			break;
		
		case 'b':
			back();
			break;
		
		case 'l':
			left();
			break;
		
		case 'r':
			right();
			break;
		
		case 's':
			stop();
			break;
	}
}

uart.c

默认电机状态为停        motor_dirc = 's'

#include "reg52.h"
 
sfr AUXR = 0x8E;
 
char motor_dirc = 's';
 
//串口部分代码
void UartInit(void)		//[email protected]
{
	AUXR = 0x01;
	SCON = 0x50; //配置串口工作方式1,REN使能接收
	
	TMOD &= 0x0F;
	TMOD |= 0x20;//定时器1工作方式位8位自动重装
	
	TH1 = 0xFD;
	TL1 = 0xFD;//9600波特率的初值
	
	TR1 = 1;//启动定时器
	
	EA = 1;//开启总中断
	ES = 1;//开启串口中断
}
 
//发送字符
void sendByte(char data_msg)
{
	SBUF = data_msg;
	while(!TI);
	TI = 0;
}
 
//发送字符串
void sendString(char* str)
{
	while( *str != '\0'){
		sendByte(*str);
		str++;
	}
}

//调取蓝牙命令内容
char get_Uart(){
	return motor_dirc;
}
 
//蓝牙指令接收(中断)
void UART_HANDLER() interrupt 4
{
	char cmd;
	
	if(RI)//中断处理函数中,对于接收中断的响应
	{
		RI = 0;//清除接收中断标志位
		
		cmd = SBUF;//接收命令
		
		motor_dirc = cmd;
		
		sendString("收到串口指令,改变状态\r\n");
	}
}

2.蓝牙控制

给串口连接上蓝牙模块。

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第4张图片

 使用蓝牙串口助手,自定义设置好按键对应发送的字符,向设备发送命令。

最终实现了蓝牙控制小车方向。

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第5张图片

3.点动模式蓝牙控制

蓝牙助手改为长按模式0.2s发一次指令

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第6张图片

重点在于串口中断。

接收到指令也运动0.2s

//蓝牙指令接收(中断)
void UART_HANDLER() interrupt 4
{
	char cmd;
	
	if(RI)//中断处理函数中,对于接收中断的响应
	{
		RI = 0;//清除接收中断标志位
		
		cmd = SBUF;//接收命令
		
		motor_dirc = cmd;
		
		switch(motor_dirc){
			case 'f':
				forward();
				sendString("向前\r\n");
				break;
			
			case 'b':
				back();
				sendString("向后\r\n");
				break;
			
			case 'l':
				left();
				sendString("向左\r\n");
				break;
			
			case 'r':
				right();
				sendString("向右\r\n");
				break;
			
			case 's':
				stop();
				sendString("停止\r\n");
				break;
		}
	}
    //每次收到数据运动0.2秒
	Delay200ms();
}

main.c        没收到指令就保持停滞状态。 

void main()
{
  //串口初始化
	UartInit();
	
	while(1){
		//没收到指令就不动
		stop();
	}
}

四 PWM调速

参考本人之前的文章

(45条消息) C51:PWM+舵机角度控制_我有在好好学习的博客-CSDN博客

原理: 全速前进是 LeftA = 0; LeftB = 1; 完全停止是 LeftA = 0;LeftB = 0; 那么单位时间内,比如20ms, 有15ms是全速前进,5ms是完全停止,速度就会比5ms全速前进,15ms完全停止获得的功率多,相应的速度更快!

开发:借用PWM的舵机控制代码

测试代码

#include "reg52.h"
#include "intrins.h"

sbit RightA = P3^2;
sbit RightB = P3^3;

sbit LeftA = P3^4;
sbit LeftB = P3^5;

int speed;//0~40

int cnt;

void Time0Init()//500微秒
{
//1. 配置定时器0工作模式位16位计时
	TMOD = 0x01;
	
//2. 给初值,定一个10ms出来
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	
//3. 开始计时,定时器"数数"
	TR0 = 1;
	TF0 = 0;
	
//4. 打开定时器0中断
	ET0 = 1;
	
//5. 打开总中断EA
	EA = 1;
}

void Delay2000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 15;
	j = 2;
	k = 235;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void forward(){
	LeftA = 1;
	LeftB = 0;
	
	RightA = 1;
	RightB = 0;
}

void stop(){
	LeftA = 1;
	LeftB = 1;
	
	RightA = 1;
	RightB = 1;
}

void main()
{
	speed = 40;
	cnt = 0;
	
	Time0Init();
	
	Delay2000ms();
	
	while(1){
		speed = 40;//快
		Delay2000ms();
		
		speed = 30;//中
		Delay2000ms();
		
		speed = 20;//慢
		Delay2000ms();
		
		speed = 10;//跑不动
		Delay2000ms();

    speed = 0;//停
		Delay2000ms();
	}
}

void Time0Handler() interrupt 1
{
	cnt++; //统计爆表的次数
	
//重新给初值
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
//按照给定速度参数跑
	if(cnt < speed){
		forward();
	}else{
		stop();
	}
	
	if(cnt == 40){//爆表40次,经过了20ms
		
	cnt = 0; //计算下一次的20ms
	stop();
	}
}

根据speed的大小测试对车速的影响,设置合适的速度

左右轮分别调速

 整合起来,分别通过两个 speed_Right、speed_Left 参数数值实现左右轮调速:

void PWM_Right(){
	RightA = 1;
	RightB = 0;
}

void PWM_Left(){
	LeftA = 1;
	LeftB = 0;
}

void PWM_STOP_Right(){
	RightA = 1;
	RightB = 1;
}

void PWM_STOP_Left(){
	LeftA = 1;
	LeftB = 1;
}
#include "reg52.h"
#include "motor.h"

int speed_Right = 40;//0~40

int cnt_Right =0;

int speed_Left = 40;//0~40

int cnt_Left =0;

//定时器0
void Time0Init()//500微秒
{
	
//1. 配置定时器0工作模式位16位计时
	TMOD = 0x01;
	
//2. 给初值,定一个10ms出来
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	
//3. 开始计时,定时器"数数"
	TR0 = 1;
	TF0 = 0;
	
//4. 打开定时器0中断
	ET0 = 1;
	
//5. 打开总中断EA
	EA = 1;
}

//定时器1
void Time1Init()//500微秒
{
	
	//1. 配置定时器1工作模式位16位计时
	TMOD &= 0x0F;
	TMOD |= 0x1 << 4;
	
//2. 给初值,定一个10ms出来
	TL1 = 0x33;		//设置定时初值
	TH1 = 0xFE;		//设置定时初值
	
//3. 开始计时,定时器"数数"
	TR1 = 1;
	TF1 = 0;
	
//4. 打开定时器0中断
	ET1 = 1;
	
//5. 打开总中断EA
	EA = 1;
}

//右轮调速
void Time0Handler() interrupt 1
{
	cnt_Right++; //统计爆表的次数
	
//重新给初值
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	
//按照给定速度参数跑
	if(cnt_Right < speed_Right){
		PWM_Right();
	}else{
		PWM_STOP_Right();
	}
	if(cnt_Right == 40){//爆表40次,经过了20ms
		
	cnt_Right = 0; //计算下一次的20ms
	//stop();
	}
}

//左轮调速
void Time1Handler() interrupt 3
{
	cnt_Left++; //统计爆表的次数
	
//重新给初值
	TL1 = 0x33;		//设置定时初值
	TH1 = 0xFE;		//设置定时初值
	
//按照给定速度参数跑
	if(cnt_Left < speed_Left){
		PWM_Left();
	}else{
		PWM_STOP_Left();
	}
	if(cnt_Left == 40){//爆表40次,经过了20ms
		
	cnt_Left = 0; //计算下一次的20ms
	}
}
#include "reg52.h"
#include "delay.h"
#include "motor.h"
#include "uart.h"
#include "pwm.h"

extern char motor_dirc;

void main()
{
  //串口初始化
	UartInit();
	//定时器开启
	Time0Init();
	Time1Init();
	
	while(1){
		motor_dirc = SBUF;
	}
}

五 红外循迹、跟随

1.红外循迹模块

TCRT5000传感器的红外发射二极管不断发射红外线
当发射出的红外线没有被反射回来或被反射回来但强度不够大时,
红外接收管一直处于关断状态,此时模块的输出端为高电平,指示二极管一直处于熄灭状态
被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和,
此时模块的输出端为低电平,指示二极管被点亮

 总结就是一句话,没反射回来,D0输出高电平,灭灯

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第7张图片基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第8张图片

 接线方式

VCC:接电源正极(3-5V)

GND:接电源负极

DO:TTL开关信号输出0、1

AO:模拟信号输出(不同距离输出不同的电压,此脚一般可以不接)

2.循迹原理

由于黑色具有较强的吸收能力,当循迹模块发射的红外线照射到黑线时,红外线将会被黑线吸收,导致循迹模块上光敏三极管处于关闭状态,此时模块上一个LED熄灭。在没有检测到黑线时,模块上两个LED常亮

总结就是一句话,有感应到黑线,D0输出高电平 ,灭灯

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第9张图片

 循迹模块安装在小车车头两侧

下方小车两个模块都能反射回来红外,输出低电平,灯亮,直走

上方小车左模块遇到黑线,红外被吸收,左模块输出高电平,右模块输出低电平,左转,反之右转

3.跟随

原理和寻线是一样的,寻线红外观朝下,跟随朝前

左边跟随模块能返回红外,输出低电平,右边不能返回,输出高电平,说明物体在左边,需要左转

右边跟随模块能返回红外,输出低电平,左边不能返回,输出高电平,说明物体在右边,需要右转

4.红外 循迹、跟随 代码整合

#include "reg52.h"
#include "delay.h"

sbit Finder_Left = P2^0;

sbit Finder_Right = P2^1;

sbit Flow_Left = P2^3;

sbit Flow_Right = P2^2;

extern char speed_Right;//0~40

extern char speed_Left;//0~40

//寻线功能
void checkWay(){
	//左边触线,左转
	if(Finder_Left && !Finder_Right){
		
		speed_Right = 40;
		speed_Left = 10;
		
	}
	//右边触线,左转
	else if(Finder_Right && !Finder_Left){

		speed_Right = 7;
		speed_Left = 40;

	}
	//车悬空,停止移动
	else if(Finder_Right && Finder_Left){

		speed_Right = 0;
		speed_Left = 0;
	}
	//直线,直行
	else{

		speed_Right = 36;
		speed_Left = 36;
	}
}

//跟随功能
void flow(){
	//左边有人,左转
	if(Flow_Right && !Flow_Left){
		
		speed_Right = 40;
		speed_Left = 5;
		
	}
	//右边有人,右转
	else if(Flow_Left && !Flow_Right){

		speed_Right = 5;
		speed_Left = 40;

	}
	//车悬空,停止移动
	else if(Flow_Right && Flow_Left){


		speed_Right = 0;
		speed_Left = 0;
	}
	//人在前方,直行
	else{

		speed_Right = 40;
		speed_Left = 40;
	}
}

六 超声波避障

转头测距

 使用超声波模块测距+舵机模块左右摇头实现发现障碍物和寻找出路

首先写一段超声波测距,前方距离小于30cm就左右摇头测左右的距离

代码:

#include "reg52.h"
#include "pwm.h"
#include "delay.h"

extern char angle;

void main()
{
	double disMiddle;
	double disLeft;
	double disRight;

	//定时器开启
	Time0Init();
	Time1Init();
	
	angle = 3;
	Delay300ms();
	
	while(1){
		if(get_distance() < 30){
			
			//角度0 左
			angle = 1;
			disLeft = get_distance();
			Delay300ms();
			
			//角度90 前
			angle = 3;
			disMiddle = get_distance();
			Delay300ms();
			
			//角度180 右
			angle = 5;
			disRight = get_distance();
			Delay300ms();
			
			//回到90度向前
			angle = 3;
			Delay300ms();
		}
	}
}
#include "reg52.h"
#include "motor.h"
#include "delay.h"

char cnt_Right =0;

//舵机引脚
sbit sg90_con = P1^1;

//超声波
sbit Trig 		= P1^5;
sbit Echo 		= P1^6;

char angle;

//定时器0
void Time0Init()//500微秒
{
	//1. 配置定时器0工作模式位16位计时
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;
	//2. 给初值,定一个0.5出来
	TL0=0x33;
	TH0=0xFE;
	//3. 开始计时
	TR0 = 1;
	TF0 = 0;
	//4. 打开定时器0中断
	ET0 = 1;
	//5. 打开总中断EA
	EA = 1;
}

void Time1Init(void)//测距使用;T1
{
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;
	TH1 = 0;
	TL1 = 0;
	//设置定时器0工作模式1,初始值设定0开始数数,不着急启动定时器
}

void startHC(){//发出超声波
	Trig = 0;
	Trig = 1;
	Delay10us();
	Trig = 0;
}

//测距功能:
double get_distance()
{
		double time;
		//定时器数据清零,以便下一次测距
		TH1 = 0;
		TL1 = 0;
	//1. Trig ,给Trig端口至少10us的高电平
		startHC();
		//2. echo由低电平跳转到高电平,表示开始发送波
		while(Echo == 0);
		//波发出去的那一下,开始启动定时器
		TR1 = 1;
		//3. 由高电平跳转回低电平,表示波回来了
		while(Echo == 1);
		//波回来的那一下,我们开始停止定时器
		TR1 = 0;
		//4. 计算出中间经过多少时间
		time = (TH1 * 256 + TL1)*1.085;//us为单位
		//5. 距离 = 速度 (340m/s)* 时间/2
		return  (time * 0.017);
}


void Time0Handler() interrupt 1
{
	cnt_Right++; //统计爆表的次数
	
//重新给初值
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	
	if(cnt_Right < angle){
		sg90_con = 1;
	}else{
		sg90_con = 0;
	}
	
	if(cnt_Right == 40){//爆表40次,经过了20ms
		
	cnt_Right = 0; //计算下一次的20ms
	sg90_con = 1;
		
	}
}

结合小车运动实现避障

摇头测距哪边更宽阔就往哪边转

更改while循环中的代码,即可完成超声波避障

更改代码:

		//距离过近,后退
		if(get_distance() < 10){
			back();
			Delay100ms();
		}
		//距离小于30,测距转向
		else if(get_distance() < 30){
			//距离小于30,停车
			stop();
			
			//摇头测距
			angle = 1;//角度0 右
			disRight = get_distance();
			Delay300ms();
			
			angle = 3;//角度90 前
			disMiddle = get_distance();
			Delay300ms();
			
			angle = 5;//角度180 左
			disLeft = get_distance();
			Delay300ms();
			
			angle = 3;//回到90度向前
			Delay300ms();
			
			//根据测距判断左右转
			if(disLeft >= disRight){
				left();
				Delay100ms();
			}else if(disLeft < disRight){
				right();
				Delay100ms();
			}
		}
		//安全距离,前进
		else{
			forward();
		}

封装函数和改进

调整了小车的过近后退距离阈值,另外小车有时会在一个地方一直左右转头,用一个标志位稍作调整。

#include "reg52.h"
#include "delay.h"
#include "motor.h"
#include "pwm.h"

double disMiddle;
double disLeft;
double disRight;

extern char angle;
	
//防止一直原地左右摆头
char same_dirc_flag = 0;
	
void evadible()
{
			//距离过近,后退
		if(get_distance() < 15){
			back();
			same_dirc_flag = 0;
			Delay100ms();
		}
		
		//距离小于30,测距转向
		else if(get_distance() < 30){
			//距离小于30,停
			stop();
			
			//摇头测距
			angle = 1;//角度0 右
			disRight = get_distance();
			Delay300ms();
			
			angle = 3;//角度90 前
			disMiddle = get_distance();
			Delay300ms();
			
			angle = 5;//角度180 左
			disLeft = get_distance();
			Delay300ms();
			
			angle = 3;//回到90度向前
			Delay300ms();
			
			//根据测距判断左右转,避免原地左右转
			if(disLeft >= disRight && same_dirc_flag != 'r'){
				left();
				same_dirc_flag = 'l';
				Delay100ms();
			}else if(disLeft < disRight && same_dirc_flag != 'l'){
				right();
				same_dirc_flag = 'r';
				Delay100ms();
			}else if(same_dirc_flag == 'l'){
				left();
				same_dirc_flag = 'r';
				Delay100ms();
			}else if(same_dirc_flag == 'r'){
				right();
				same_dirc_flag = 'r';
				Delay100ms();
			}
		}
		
		//安全距离,前进
		else{
			forward();
			same_dirc_flag = 0;
		}
		
}

七 小车测速及OLED屏显

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第10张图片

用途:广泛用于电机转速检测,脉冲计数,位置限位等。

有遮挡,输出高电平;无遮挡,输出低电平

接线:P3.2 P3.3 为外部中断

VCC 接电源正极3.3-5V

GND 接电源负极

DO TTL开关信号输出

AO 此模块不起作用

测速原理和单位换算

轮子走一圈,经过一个周长,C = 2x3.14x半径= 3.14 x 直径(6.5cm)

对应的码盘也转了一圈,码盘有20个格子,每经过一个格子,会遮挡(高电平)和不遮挡(低电平),

那么一个脉冲就是走了 3.14 * 6.5 cm /20 = 1.0205CM

定时器可以设计成一秒,统计脉冲数,一个脉冲就是1cm

假设一秒有80脉冲,那么就是80cm/s

进行测速实验(发现问题与改进)

利用外部中断,计时器计时1秒,测算传感器被激发了几次。

但是由于代码写的太屎山,光是计时就已经分别用掉了两个计时器,此时串口就没有计时器可用了。

所以可以看出,c51资源很珍贵,我们在做项目时一定要节约资源。

开始代码优化

本项目主控作为只有8位的c51,资源是及其有限的,节约资源也就成l51开发的重点,不然是无法

完成一个出色的功能复杂的项目的。

代码优化空间很大,例如pwm调速,没必要使用双计时器分别调速,一个计时器即可完成调速。

于是优化了之前的代码:

蓝牙调速小车:

定时器pwm调速部分

#include "reg52.h"
#include "motor.h"

char speed_Right = 0;//0~40

char speed_Left = 0;//0~40

char cnt =0;

//定时器0
void Time0Init()//500微秒
{
	
//1. 配置定时器0工作模式位16位计时
	TMOD = 0x01;
	
//2. 给初值,定一个10ms出来
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	
//3. 开始计时,定时器"数数"
	TR0 = 1;
	TF0 = 0;
	
//4. 打开定时器0中断
	ET0 = 1;
	
//5. 打开总中断EA
	EA = 1;
}

//双轮调速
void Time0Handler() interrupt 1
{
	cnt++; //统计爆表的次数
	
//重新给初值
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	
//按照给定速度参数跑:右轮
	if(cnt < speed_Right){
		PWM_Right();
	}else{
		PWM_STOP_Right();
	}
//按照给定速度参数跑:左轮
	if(cnt < speed_Left){
		PWM_Left();
	}else{
		PWM_STOP_Left();
	}
	if(cnt == 40){//爆表40次,经过了20ms
		
	cnt = 0; //计算下一次的20ms
	//stop();
	}
}

 串口中断部分,实现了使用蓝牙遥控调速,速度串口传输显示,这是很方便调参的方法。

//蓝牙指令接收(中断)
void UART_HANDLER() interrupt 4
{
	char cmd;
	
	if(RI)//中断处理函数中,对于接收中断的响应
	{
		RI = 0;//清除接收中断标志位
		
		cmd = SBUF;//接收命令
		
		motor_dirc = cmd;
		
		switch(motor_dirc){
			case 'f':
				
				speed_Right = high;
				speed_Left = high;
			
				sendString("向前\r\n");
				break;
		
			case 'l':
				
				speed_Right = high;
				speed_Left = low;
			
				sendString("向左\r\n");
				break;
			
			case 'r':
				
				speed_Right = low;
				speed_Left = high;
			
				sendString("向右\r\n");
				break;
			
			case 'u':
				
				if(high < 40){
					high += 5;
					sendString("加速,当前速度:");
					
					sendByte(high/10 + 0x30);
					sendByte(high%10 + 0x30);
					
					sendString("\r\n");
					
					speed_Right = high;
					speed_Left = high;
				}
				else{
					sendString("已达到最高速:");
					
					sendByte(high/10 + 0x30);
					sendByte(high%10 + 0x30);
					
					sendString("\r\n");
				}
				
				break;
			
			case 'd':
				
				if(high > 10){
					high -= 5;
					sendString("减速,当前速度:");
					
					sendByte(high/10 + 0x30);
					sendByte(high%10 + 0x30);
					
					sendString("\r\n");
					
					speed_Right = high;
					speed_Left = high;
				}
				else{
					sendString("已达到最低速:");
					
					sendByte(high/10 + 0x30);
					sendByte(high%10 + 0x30);
					
					sendString("\r\n");
				}
				
				break;
			
			case 's':
				
				speed_Right = 0;
				speed_Left = 0;
			
				sendString("向前\r\n");
				break;
		}
	}
	//Delay200ms();
	
	//speed_Right = 0;
	//speed_Left = 0;
}

 轻松搞定,实时显示设置的占空比

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第11张图片

双轮测速

同理我们可以优化测速代码实现双轮测速

#include "reg52.h"
#include "motor.h"

int cnt = 0;
int Check_speed_Right = 0;//cm/s
int speed_Right_cm_s = 0;//cm/s

int Check_speed_Left = 0;//cm/s
int speed_Left_cm_s = 0;//cm/s

char signal = 0;

//定时器0
void Time0Init()//500微秒
{
	
//1. 配置定时器0工作模式位16位计时
	TMOD = 0x01;
	
//2. 给初值,定一个10ms出来
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	
//3. 开始计时,定时器"数数"
	TR0 = 1;
	TF0 = 0;
	
//4. 打开定时器0中断
	ET0 = 1;
	
//5. 打开总中断EA
	EA = 1;
}

//右轮外部中断
void Left_EX0_init(){
	//打开外部中断
	EX0 = 1;
 
	//下降沿触发
	IT0 = 1;
}

//左轮外部中断
void Right_EX1_init(){
	//打开外部中断
	EX1 = 1;
 
	//下降沿触发
	IT1 = 1;
}

//右轮外部中断,计数
void EX0_handler() interrupt 0
{
	Check_speed_Right++;
}

//左轮外部中断,计数
void EX1_handler() interrupt 2
{
	Check_speed_Left++;
}

//双轮测速
void Time0Handler() interrupt 1
{
	cnt++; //统计爆表的次数
	
//重新给初值
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值

//测速
	if(cnt == 2000){//爆表2000次,经过了1s
		
		speed_Right_cm_s = Check_speed_Right;//取得右轮速度数值
		
		Check_speed_Right = 0;//每秒归零
		
		speed_Left_cm_s = Check_speed_Left;//取得左轮速度数值
		
		Check_speed_Left = 0;//每秒归零
		
		signal = 1;//信号告知main函数接收数据
		
		cnt = 0; //计算下一次的1s
	}
}

传输字符串,数据类型不同,所以可以拼接一个字符串

sprintf();

#include "reg52.h"
#include "uart.h"
#include "timer.h"
#include "stdio.h"

extern int speed_Right_cm_s;
extern int speed_Left_cm_s;

extern char signal;

int speed;

char speedMes[24];  //主程序发送速度数据的字符串缓冲区

void main()
{
	Time0Init();

	Left_EX0_init();

	Right_EX1_init();
	
	//串口初始化
	UartInit();
	
	while(1){
		if(signal)
		{	
			speed = (speed_Right_cm_s + speed_Left_cm_s)/2;
			
			//串口数据的字符串拼装,speed是格子,每个格子1cm
			sprintf(speedMes,"Speed: %d ; R: %d ; L: %d",speed,speed_Right_cm_s,speed_Left_cm_s);
			
			sendString(speedMes);//速度发出去
			sendString("\r\n");
			signal = 0;
		}
	}
}

轻松搞定双轮速度,平均速度

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第12张图片

 OLED显示速度

参考本人上一篇文章

(48条消息) C51:IIC_OLED_我有在好好学习的博客-CSDN博客

这里开始引入官方提供的字库文档。

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第13张图片

//========================官方提供的代码================================
void Oled_Show_Char(char row,char col,char oledChar){ //row*2-2
	unsigned int  i;
	Oled_Write_Cmd(0xb0+(row*2-2));                           //page 0
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high	
	for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}

	Oled_Write_Cmd(0xb0+(row*2-1));                           //page 1
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high
	for(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}		
}


/******************************************************************************/
// 函数名称:Oled_Show_Char 
// 输入参数:oledChar 
// 输出参数:无 
// 函数功能:OLED显示单个字符
/******************************************************************************/
void Oled_Show_Str(char row,char col,char *str){
	while(*str!=0){
		Oled_Show_Char(row,col,*str);
		str++;
		col += 8;	
	}		
}

//================================================================================


#include "reg52.h"
#include "timer.h"
#include "stdio.h"
#include "oled.h"

extern int speed_Right_cm_s;
extern int speed_Left_cm_s;

extern char signal;

int speed;

char speedMes[24];  //主程序发送速度数据的字符串缓冲区
char speedMes1[24];
char speedMes2[24];

void main()
{
	Time0Init();

	Left_EX0_init();

	Right_EX1_init();
	
	//串口初始化
	UartInit();
	Oled_Init();
	
	while(1){
		if(signal)
		{	
			speed = (speed_Right_cm_s + speed_Left_cm_s)/2;
			
			//串口数据的字符串拼装,speed是格子,每个格子1cm
			sprintf(speedMes,"Speed: %d cm/s",speed);
			sprintf(speedMes1,"Right: %d cm/s",speed_Right_cm_s);
			sprintf(speedMes2,"Left : %d cm/s",speed_Left_cm_s);
			
			Oled_Show_Str(1,10,speedMes);
			Oled_Show_Str(2,10,speedMes1);
			Oled_Show_Str(3,10,speedMes2);
			signal = 0;
		}
	}
}

同时用蓝牙控制运动,运行效果:

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第14张图片

八 WIFI/4G通信

模块为ESP8266

在蓝牙串口控制小车的代码进行修改,蓝牙因为本身就透传,所以比较简单。

 这里需要把esp8266设置为服务器。

参考本人文章:

(49条消息) C51:蓝牙/Wifi/4G 无线控制开关_我有在好好学习的博客-CSDN博客

 WiFi控制小车

#include "reg52.h"
#include "delay.h"
#include "uart.h"
#include "wifi.h"

code char CIPSEND[] = "AT+CIPSEND=0,8\r\n";// 发送8个字节在连接0通道上

void main()
{
 //串口初始化
	UartInit();
	
	BuildWifi();

	while(1){
		sendString(CIPSEND);		
		Delay1000ms();
		sendString("---52---");
		Delay1000ms();
	}
}

 新建一个wifi分文件,给模块发送AT指令。

用一些标志位保证连接过程的正常进行,用LED灯充当WIFI状态指示灯方便观察。

#include "reg52.h"
#include 

#include "uart.h"
#include "delay.h"

//========wifi=========
sbit D5 = P3^7;
sbit D6 = P3^6;//WIFI状态指示灯

//1 配置成双模
code char CWMODE[] = "AT+CWMODE=2\r\n";
//2 使能多链接
code char CIPMUX[] = "AT+CIPMUX=1\r\n";
//3 建立TCPServer
code char CIPSERVER[] = "AT+CIPSERVER=1\r\n";// default port = 333

char OK_FLAG = 0;
char Connect_Mark = 1;
char Client_Mark = 0;

//==========创建wifi服务器==================
void BuildWifi()
{
	while(1){
			
		Delay1000ms();//给上电时间
		
		//闪一下
		D6 = 0;
		Delay200ms();
		D6 = 1; 
		
//1 配置成双模
		sendString(CWMODE);
		//判断是否OK
		while(!OK_FLAG)
		{
			//判断如果ERROR,跳出循环
			if(!Connect_Mark) break;
		}
		//复位OK标志位
		OK_FLAG = 0;
		//ERROR则重新开始连接
		if(!Connect_Mark){
			Connect_Mark = 1;
			continue;
		}
		
		//闪一下
		D6 = 0;
		Delay200ms();
		D6 = 1;
		
//2 使能多链接
		sendString(CIPMUX);
		//判断是否OK
		while(!OK_FLAG)
		{
			//判断如果ERROR,跳出循环
			if(!Connect_Mark) break;
		}
		//复位OK标志位
		OK_FLAG = 0;
		//ERROR则重新开始连接
		if(!Connect_Mark){
			Connect_Mark = 1;
			continue;
		}
		
		//闪一下
		D6 = 0;
		Delay200ms();
		D6 = 1;
		
//3 建立TCPServer
		sendString(CIPSERVER);
		//判断是否OK
		while(!OK_FLAG)
		{
			//判断如果ERROR,跳出循环
			if(!Connect_Mark) break;
		}
		//复位OK标志位
		OK_FLAG = 0;
		//ERROR则重新开始连接
		if(!Connect_Mark){
			Connect_Mark = 1;
			continue;
		}
		
//完成退出循环
		break;
		
	}
	
	D6 = 0;//点亮D6,代表连接服务器并打开透传模式成功
	while(!Client_Mark);
	D5 = 0;//点亮D6,代表用户连接
}

 串行中断的代码进行扩充,以适应与Wifi模块的通信。

#include "reg52.h"
#include "motor.h"
#include "delay.h"
#include "wifi.h"
#include 

sbit D5 = P3^7;
sbit D6 = P3^6;//WIFI状态指示灯

sfr AUXR = 0x8E;

char cmd;

char buffer[12];

extern char OK_FLAG;
extern char Connect_Mark;
extern char Client_Mark;

char motor_dirc = 's';

//..............

//串口指令接收(中断)
void UART_HANDLER() interrupt 4
{
	static int i = 0;
	
	if(RI){
		RI = 0;
		
		cmd = SBUF;
 
		if(cmd == 'O' || cmd == 'E' || cmd == '0' || cmd == ':' || cmd == 'M'){
			i = 0;
		}
		buffer[i++] = cmd;
		
//连接服务器等OK返回值指令的判断
		if(buffer[0] == 'O' && buffer[1] == 'K'){
			OK_FLAG = 1;
			memset(buffer, '\0', 12);
		}

//失败持续闪灯4秒,然后重启
		if(buffer[0] == 'E' && buffer[1] == 'R'	&& buffer[2] == 'R'){
			for(i = 0;i<10;i++){
					Delay200ms();
					D6 = 1;
					Delay200ms();
					D6 = 0;
			}
			//改变连接标志位
			Connect_Mark = 0;
			
			i = 0;
			memset(buffer, '\0', 12);
		}
//用户连接与否判断
		if(buffer[0] == '0' && buffer[2] == 'C'){
			Client_Mark = 1;
			memset(buffer, '\0', 12);
		}
		
		
//开关灯指令
		if(buffer[0] == ':' && buffer[1] == 'o' && buffer[2] == 'p'){
			D5 = 0;
			memset(buffer, '\0', 12);
		}
		else if(buffer[0] == ':' && buffer[1] == 'c' && buffer[2] == 'l'){
			D5 = 1;
			memset(buffer, '\0', 12);
		}
		
		if(buffer[0] == 'M')
		{
			motor_dirc = buffer[1];
			switch(motor_dirc){
				case 'f':
					forward();
					memset(buffer, '\0', 12);
					break;
				
				case 'b':
					back();
					memset(buffer, '\0', 12);
					break;
				
				case 'l':
					left();
					memset(buffer, '\0', 12);
					break;
				
				case 'r':
					right();
					memset(buffer, '\0', 12);
					break;
				
				case 's':
					stop();
					memset(buffer, '\0', 12);
					break;
			}
		}

		if(i == 12)
		{
			i = 0;
			memset(buffer, '\0', 12);
		}
	}
}

Wifi传输速度

结合Oled显示的代码进行扩展,此时变量会很多很大,要节约内存防止编译不通过。

代码基本区别不大,注意细节调整,比如CIPSEND的大小。

#include "reg52.h"
#include "timer.h"
#include "stdio.h"
#include "oled.h"
#include "uart.h"
#include "WiFi.h"

extern int speed_Right;
extern int speed_Left;

extern char signal;

int speed;

char speedMes[14];  //主程序发送速度数据的字符串缓冲区
char speedMes1[14];
char speedMes2[13];

//准备Wifi传输
extern code char CIPSEND[];

void main()
{
	Time0Init();

	Left_EX0_init();

	Right_EX1_init();
	
	//串口初始化
	UartInit();
	
	BuildWifi();
	
	Oled_Init();
	
	Oled_Clear();
	
	while(1){
		if(signal)
		{	
			speed = (speed_Right + speed_Left)/2;
			
			//串口数据的字符串拼装,speed是格子,每个格子1cm
			sprintf(speedMes,"Speed: %d cm/s",speed);
			sprintf(speedMes1,"Right: %d cm/s",speed_Right);
			sprintf(speedMes2,"Left : %d cm/s",speed_Left);
			
			//oled屏显
			Oled_Show_Str(1,10,speedMes);
			Oled_Show_Str(2,10,speedMes1);
			Oled_Show_Str(3,10,speedMes2);
			
			//wifi传输
			sendString(speedMes);
			
			//标志位归0
			signal = 0;
			
			//准备下一次wifi传输
			sendString(CIPSEND);
		}
	}
}

4G通信

代码不增反减,配置依然可以参考我之前的文章。做好外网透传就好了。

(50条消息) C51:蓝牙/Wifi/4G 无线控制开关_我有在好好学习的博客-CSDN博客

 这里就不做过多赘述。

九 SU-03T语音模块

SU-03T LD3320,都是常见的非特定人语音识别模块,这里选用无需二次开发的SU-03T,用官方网站可以快速配置SDK。

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第15张图片

 网盘资料
https://pan.baidu.com/s/1dXQSla_8D5O3D7UCsHZ0Dw 提取码3k5a

 在官方网站可以快速配置,等待生成SDK

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第16张图片

 基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第17张图片

 基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第18张图片

 基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第19张图片

 下载SDK

基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第20张图片

 接线基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第21张图片

 基于C51的语音智能车(含循迹/跟随/避障/测速屏显/蓝牙WIFI4G控制等功能)_第22张图片

烧录完成就可以使用了

十 功能整合——全能小车

整合循迹、避障、跟随、串口控制功能。串口选用蓝牙即可,比WiFi和4g更稳定好用。

比较考验综合能力,难点在于在定时器0的中断上添加大量代码时,如何平衡性能。一个中断上的大量代码可能会使得单片机吃不消,所以要判断不同模式下功能的切换和取舍。并且定时器1为串口使用,避障模式需要0和1双定时器,这时就要判断切换定时器1的使用状态。

全部代码:

少量如软件延时,就不细写了。

主函数:

#include "reg52.h"
#include "timer.h"
#include "oled.h"
#include "uart.h"
#include "find.h"
#include "evadible.h"
#include "delay.h"
#include "motor.h"

//蓝牙、循迹、跟随、避障
#define UART	0
#define XunJi	1
#define GenSui	2
#define BiZhang	3

//语音模块接口
sbit A25 = P2^5;
sbit A26 = P2^6;
sbit A27 = P2^7;

//默认小车串口模式,定时器1供串口使用
char mode = UART;
char timer1_mark = UART;

//是否需要开启pwm
extern char pwm_flag;


//==========主函数==========
void main()
{
	//定时器0初始化(pwm调速及测速)
	Time0Init();

	//外部中断初始化(测速)
	Left_EX0_init();
	Right_EX1_init();
	
	//串口初始化
	UartInit();
	
	//Oled初始化并清屏
	Oled_Init();
	Oled_Clear();
	
	//一直循环判断
	while(1){

	//判断语音模块的输入结果
		if(!A25)		mode = XunJi;
		else if(!A26)	mode = BiZhang;
		else if(!A27)	mode = GenSui;
		else			mode = UART;
		
	//选择工作模式
		switch(mode){
			
		//======蓝牙(串口)模式================
			case UART:
				
				//判断定时器1状态和是否切换给串口使用
				if(timer1_mark != UART){
					stop();
					UartInit();
				}
				timer1_mark = UART;
				
				//默认不使用pwm调速为蓝牙模式
				pwm_flag = 0;
				
				break;
			
		//=======循迹模式======================
			case XunJi:
				
				//判断定时器1状态和是否切换给串口使用
				if(timer1_mark != UART){
					stop();
					UartInit();
				}
				timer1_mark = UART;
			
				//使用PWM调速,红外循迹
				pwm_flag = 1;
				checkWay();
				
				break;
		
		//=======跟随模式======================
			case GenSui:
				
				//判断定时器1状态和是否切换给串口使用
				if(timer1_mark != UART){
					stop();
					UartInit();
				}
				timer1_mark = UART;
			
				//使用PWM调速,红外跟随
				pwm_flag = 1;
				follow();
				break;
			
				
		//=======避障模式======================
			case BiZhang:
				//将定时器1用来测距
				if(timer1_mark != BiZhang){
					stop();
					TR1 = 0;
					Time1Init();
				}
				timer1_mark = BiZhang;
				
				//不使用PWM调速,也不输出测速结果
				pwm_flag = 2;
				evadible();//超声波避障
				
				break;
		}
	}
}

定时器及其中断

#include "reg52.h"
#include "motor.h"
#include "uart.h"
#include "stdio.h"
#include "oled.h"
#include "delay.h"

//舵机引脚
sbit sg90_con = P1^1;

//超声波
sbit Trig 		= P1^2;
sbit Echo 		= P1^3;

//舵机角度
char angle = 3;

//计时次数标记
int cnt = 0;
int cnt_pwm = 0;

//累计每秒脉冲次数(测速)
int Check_speed_Right = 0;//cm/s
int Check_speed_Left = 0;//cm/s

//传输显示的速度值(测速)
int speed_Right_cm_s = 0;//cm/s
int speed_Left_cm_s = 0;//cm/s

//pwm调速标志位及调速参数
char pwm_flag = 0;
char speed_Right = 0;//0~40
char speed_Left = 0;//0~40

//两轮平均速度
int speed;
//速度信息字符串
char speedMes[14];  //主程序发送速度数据的字符串缓冲区
char speedMes1[14];
char speedMes2[13];

//定时器0
void Time0Init()//500微秒
{
	//1. 配置定时器0工作模式位16位计时
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;
	//2. 给初值,定一个0.5出来
	TL0=0x33;
	TH0=0xFE;
	//3. 开始计时
	TR0 = 1;
	TF0 = 0;
	//4. 打开定时器0中断
	ET0 = 1;
	//5. 打开总中断EA
	EA = 1;
}

//==========超声波测距==========
//测距使用;T1
void Time1Init(void)
{
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;
	TH1 = 0;
	TL1 = 0;
	//设置定时器0工作模式1,初始值设定0开始数数,不着急启动定时器
}

//发出超声波
void startHC(){
	Trig = 0;
	Trig = 1;
	Delay10us();
	Trig = 0;
}

//测距功能:
double get_distance()
{
		double time;
		//定时器数据清零,以便下一次测距
		TH1 = 0;
		TL1 = 0;
	//1. Trig ,给Trig端口至少10us的高电平
		startHC();
		//2. echo由低电平跳转到高电平,表示开始发送波
		while(Echo == 0);
		//波发出去的那一下,开始启动定时器
		TR1 = 1;
		//3. 由高电平跳转回低电平,表示波回来了
		while(Echo == 1);
		//波回来的那一下,我们开始停止定时器
		TR1 = 0;
		//4. 计算出中间经过多少时间
		time = (TH1 * 256 + TL1)*1.085;//us为单位
		//5. 距离 = 速度 (340m/s)* 时间/2
		return  (time * 0.017);
}
//===========外部中断测速==============
//右轮外部中断
void Left_EX0_init(){
	//打开外部中断
	EX0 = 1;
 
	//下降沿触发
	IT0 = 1;
}

//左轮外部中断
void Right_EX1_init(){
	//打开外部中断
	EX1 = 1;
 
	//下降沿触发
	IT1 = 1;
}

//右轮外部中断,计数
void EX0_handler() interrupt 0
{
	Check_speed_Right++;
}

//左轮外部中断,计数
void EX1_handler() interrupt 2
{
	Check_speed_Left++;
}

//========舵机转动、双轮调速、测速(中断)========
void Time0Handler() interrupt 1
{
	cnt++; //统计爆表的次数
	cnt_pwm++;
	
//重新给初值
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值

//舵机pwm转动
	if(cnt_pwm < angle){
		sg90_con = 1;
	}else{
		sg90_con = 0;
	}
	
//pwm调速
	if(pwm_flag == 1)
	{
		//按照给定速度参数跑:右轮
		if(cnt_pwm < speed_Right){
			PWM_Right();
		}else{
			PWM_STOP_Right();
		}
		//按照给定速度参数跑:左轮
		if(cnt_pwm < speed_Left){
			PWM_Left();
		}else{
			PWM_STOP_Left();
		}
	}
	
	if(cnt_pwm == 40){//开始下一次的20ms
		cnt_pwm = 0;
		sg90_con = 1;
	}
	
//测速并传输显示
	if(cnt == 2000){//爆表2000次,经过了1s
		
			//右测速
			speed_Right_cm_s = Check_speed_Right;//取得右轮速度数值
				
			Check_speed_Right = 0;//每秒归零
			//左测速
			speed_Left_cm_s = Check_speed_Left;//取得左轮速度数值
				
			Check_speed_Left = 0;//每秒归零
		
		if(!pwm_flag)//pwm调速或测距时,不测速Oled显示。因为输出信息量太大,影响pwm和测距。
		{
			//计算综合速度
			speed = (speed_Right_cm_s + speed_Left_cm_s)/2;
					
			//串口数据的字符串拼装,speed是格子,每个格子1cm
			sprintf(speedMes,"Speed: %d cm/s",speed);
			sprintf(speedMes1,"Right: %d cm/s",speed_Right_cm_s);
			sprintf(speedMes2,"Left : %d cm/s",speed_Left_cm_s);
			
			//蓝牙传输速度信息
			sendString(speedMes);
			sendString("\r\n");	

			//Oled显示速度和左右分别速度
			Oled_Show_Str(1,10,speedMes);
			Oled_Show_Str(2,10,speedMes1);
			Oled_Show_Str(3,10,speedMes2);
		}
		
		//开始下一次的1s
		cnt = 0; 
	}
}

串口 

#include "reg52.h"
#include "motor.h"
#include "delay.h"
 
sfr AUXR = 0x8E;
 
char motor_dirc = 's';

extern char pwm_flag;
 
//串口部分代码
void UartInit(void)		//[email protected]
{
	AUXR = 0x01;
	SCON = 0x50; //配置串口工作方式1,REN使能接收
	
	TMOD &= 0x0F;
	TMOD |= 0x20;//定时器1工作方式位8位自动重装
	
	TH1 = 0xFD;
	TL1 = 0xFD;//9600波特率的初值
	
	TR1 = 1;//启动定时器
	
	EA = 1;//开启总中断
	ES = 1;//开启串口中断
}
 
//发送字符
void sendByte(char data_msg)
{
	SBUF = data_msg;
	while(!TI);
	TI = 0;
}
 
//发送字符串
void sendString(char* str)
{
	while( *str != '\0'){
		sendByte(*str);
		str++;
	}
}
 
//蓝牙指令接收(中断)
void UART_HANDLER() interrupt 4
{
	char cmd;
	
	if(RI)//中断处理函数中,对于接收中断的响应
	{
		RI = 0;//清除接收中断标志位
		
		cmd = SBUF;//接收命令
		
		//根据命令运动
		motor_dirc = cmd;
		
		if(!pwm_flag)
		{
			switch(motor_dirc){
				case 'f':
					forward();
					break;
				
				case 'b':
					back();
					break;
				
				case 'l':
					left();
					break;
				
				case 'r':
					right();
					break;
				
				case 's':
					stop();
					break;
			}
		//每次收到命令运动0.2秒,不然就停,模拟点动模式
		Delay200ms();
		stop();
		}
	}
}

红外循迹跟随

#include "reg52.h"
#include "delay.h"

sbit Finder_Left = P2^0;

sbit Finder_Right = P2^1;

sbit Flow_Left = P2^2;

sbit Flow_Right = P2^3;

extern char speed_Right;//0~40

extern char speed_Left;//0~40

//寻线功能
void checkWay(){
	//左边触线,左转
	if(Finder_Left && !Finder_Right){
		
		speed_Right = 37;
		speed_Left = 1;
		
	}
	//右边触线,左转
	else if(Finder_Right && !Finder_Left){

		speed_Right = 1;
		speed_Left = 36;

	}
	//车悬空,停止移动
	else if(Finder_Right && Finder_Left){

		speed_Right = 0;
		speed_Left = 0;
	}
	//直线,直行
	else{

		speed_Right = 18;
		speed_Left = 17;
	}
}

//跟随功能
void follow(){
	//左边有人,左转
	if(Flow_Right && !Flow_Left){
		
		speed_Right = 37;
		speed_Left = 1;
		
	}
	//右边有人,右转
	else if(Flow_Left && !Flow_Right){

		speed_Right = 1;
		speed_Left = 36;

	}
	//车悬空,停止移动
	else if(Flow_Right && Flow_Left){


		speed_Right = 0;
		speed_Left = 0;
	}
	//人在前方,直行
	else{

		speed_Right = 19;
		speed_Left = 18;
	}
}

超声波避障

#include "reg52.h"
#include "delay.h"
#include "motor.h"
#include "timer.h"

double disMiddle;
double disLeft;
double disRight;

extern char angle;

//防止一直原地左右摆头
char same_dirc_flag = 0;
	
void evadible()
{
			//距离过近,后退
		if(get_distance() < 15){
			back();
			Delay100ms();
		}
		
		//距离小于30,测距转向
		else if(get_distance() < 30){
			//距离小于30,停
			stop();
			
			//摇头测距
			angle = 1;//角度0 右
			disRight = get_distance();
			Delay300ms();
			
			angle = 3;//角度90 前
			disMiddle = get_distance();
			Delay300ms();
			
			angle = 5;//角度180 左
			disLeft = get_distance();
			Delay300ms();
			
			angle = 3;//回到90度向前
			Delay100ms();
			
			//根据测距判断左右转,避免原地左右转
			if(disLeft >= disRight && same_dirc_flag != 'r'){
				left();
				same_dirc_flag = 'l';
				Delay100ms();
			}else if(disLeft < disRight && same_dirc_flag != 'l'){
				right();
				same_dirc_flag = 'r';
				Delay100ms();
			}else if(same_dirc_flag == 'l'){
				left();
				same_dirc_flag = 'r';
				Delay100ms();
			}else if(same_dirc_flag == 'r'){
				right();
				same_dirc_flag = 'r';
				Delay100ms();
			}
		}
		
		//安全距离,前进
		else{
			forward();
			same_dirc_flag = 0;
		}
}

OLED显示

#include "reg52.h"
#include 
#include "oledfont.h"

sbit SCL = P0^1;
sbit SDA = P0^3;

//开始信号
void IIC_START()
{
	SCL = 0;//防止雪花
	SDA = 1;	
	SCL = 1;
	_nop_();
	SDA = 0;
	_nop_();
}

//结束信号
void IIC_END()
{
	SCL = 0;//防止雪花
	SDA = 0;	
	SCL = 1;
	_nop_();
	SDA = 1;
	_nop_();
}

//应答信号
char IIC_ACK()
{
	char flag;
	
	SDA = 1;
	_nop_();
	SCL = 1;
	_nop_();
	flag = SDA;
	_nop_();
	SCL = 0;
	_nop_();
	
	return flag;
}

//传输一个字节
void IIC_Send_Byte(char dataSend)
{
	int i;
	
	for(i = 0;i<8;i++)
	{
		SCL = 0;
		SDA = dataSend & 0x80;//1000 0000 获得dataSend最高位给SDA
		_nop_();//数据建立时间
		SCL = 1;//SCL拉高开始发送
		_nop_();//数据发送时间
		SCL = 0;//发送完拉低
		_nop_();
		
		dataSend <<= 1;
	}
}

//写入指令
void Oled_Write_Cmd(char OLED_cmd)
{
	// 1. start()
	IIC_START();
	// 2. 写入从机地址 b0111 1000 0x78
	IIC_Send_Byte(0x78);
	// 3. ACK
	IIC_ACK();
	// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
	IIC_Send_Byte(0x00);
	// 5. ACK
	IIC_ACK();
	///6. 写入指令
	IIC_Send_Byte(OLED_cmd);
	//7. ACK
	IIC_ACK();
	//8. STOP
	IIC_END();
}

//写入数据
void Oled_Write_Data(char OLED_data)
{
	// 1. start()
	IIC_START();
	// 2. 写入从机地址 b0111 1000 0x78
	IIC_Send_Byte(0x78);
	// 3. ACK
	IIC_ACK();
	// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
	IIC_Send_Byte(0x40);
	// 5. ACK
	IIC_ACK();
	///6. 写入数据
	IIC_Send_Byte(OLED_data);
	//7. ACK
	IIC_ACK();
	//8. STOP
	IIC_END();
}

//OLED初始化代码,直接复制粘贴
void Oled_Init(void){
	Oled_Write_Cmd(0xAE);//--display off
	Oled_Write_Cmd(0x00);//---set low column address
	Oled_Write_Cmd(0x10);//---set high column address
	Oled_Write_Cmd(0x40);//--set start line address
	Oled_Write_Cmd(0xB0);//--set page address
	Oled_Write_Cmd(0x81); // contract control
	Oled_Write_Cmd(0xFF);//--128
	Oled_Write_Cmd(0xA1);//set segment remap
	Oled_Write_Cmd(0xA6);//--normal / reverse
	Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
	Oled_Write_Cmd(0x3F);//--1/32 duty
	Oled_Write_Cmd(0xC8);//Com scan direction
	Oled_Write_Cmd(0xD3);//-set display offset
	Oled_Write_Cmd(0x00);//
	Oled_Write_Cmd(0xD5);//set osc division
	Oled_Write_Cmd(0x80);//
	Oled_Write_Cmd(0xD8);//set area color mode off
	Oled_Write_Cmd(0x05);//
	Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
	Oled_Write_Cmd(0xF1);//
	Oled_Write_Cmd(0xDA);//set com pin configuartion
	Oled_Write_Cmd(0x12);//
	Oled_Write_Cmd(0xDB);//set Vcomh
	Oled_Write_Cmd(0x30);//
	Oled_Write_Cmd(0x8D);//set charge pump enable
	Oled_Write_Cmd(0x14);//
	Oled_Write_Cmd(0xAF);//--turn on oled panel
}

//清屏
void Oled_Clear()
{
	unsigned char i,j; //-128 --- 127
	for(i=0;i<8;i++){
		Oled_Write_Cmd(0xB0 + i);//page0--page7
		//每个page从0列
		Oled_Write_Cmd(0x00);
		Oled_Write_Cmd(0x10);
		//0到127列,依次写入0,每写入数据,列地址自动偏移
		for(j = 0;j<128;j++){
			Oled_Write_Data(0);
		}
	}
}

//在屏幕上显示
//===============================官方提供的代码============================================
void Oled_Show_Char(char row,char col,char oledChar){ //row*2-2
	unsigned int  i;
	Oled_Write_Cmd(0xb0+(row*2-2));                           //page 0
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high	
	for(i=((oledChar-32)*16);i<((oledChar-32)*16+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}

	Oled_Write_Cmd(0xb0+(row*2-1));                           //page 1
	Oled_Write_Cmd(0x00+(col&0x0f));                          //low
	Oled_Write_Cmd(0x10+(col>>4));                            //high
	for(i=((oledChar-32)*16+8);i<((oledChar-32)*16+8+8);i++){
		Oled_Write_Data(F8X16[i]);                            //写数据oledTable1
	}		
}


/******************************************************************************/
// 函数名称:Oled_Show_Char 
// 输入参数:oledChar 
// 输出参数:无 
// 函数功能:OLED显示单个字符
/******************************************************************************/
void Oled_Show_Str(char row,char col,char *str){
	while(*str!=0){
		Oled_Show_Char(row,col,*str);
		str++;
		col += 8;	
	}		
}

 电机驱动

#include "reg52.h"
#include "delay.h"

sbit RightA = P1^4;
sbit RightB = P1^5;

sbit LeftA = P1^6;
sbit LeftB = P1^7;

void forward(){
	LeftA = 1;
	LeftB = 0;
	
	RightA = 1;
	RightB = 0;
}

void back(){
	LeftA = 0;
	LeftB = 1;
	
	RightA = 0;
	RightB = 1;
}

void left(){
	LeftA = 0;
	LeftB = 1;
	
	RightA = 1;
	RightB = 0;
}

void right(){
	LeftA = 1;
	LeftB = 0;
	
	RightA = 0;
	RightB = 1;
}

void stop(){
	LeftA = 1;
	LeftB = 1;
	
	RightA = 1;
	RightB = 1;
}

void PWM_Right(){
	RightA = 1;
	RightB = 0;
}

void PWM_Left(){
	LeftA = 1;
	LeftB = 0;
}

void PWM_STOP_Right(){
	RightA = 1;
	RightB = 1;
}

void PWM_STOP_Left(){
	LeftA = 1;
	LeftB = 1;
}

你可能感兴趣的:(C51,单片机,嵌入式硬件)