基于51单片机的多功能智能语音循迹避障小车
目录
一.功能介绍及硬件准备
二.电机控制及调速
三.小车循迹方案
四.跟随功能实现
五.测速功能实现
六.OLED显示车速
七.摇头避障功能实现
八.SU-03T语音模块介绍
九.语音切换小车模式+OLED显示模式
一.功能介绍及硬件准备
这是一款基于51单片机开发的智能小车,通过这篇文章我会记录下来开发这款小车的全部过程。这款小车集成了循迹,避障,跟随,语音切换模式选择,并且将可以将车速显示到OLED屏幕上,也可以通过手机app蓝牙操控小车。(注:全文的代码采取分文件编程的写法)
硬件准备
小车底盘一个(两驱),5号4节电池盒一个,51单片机最小系统一个,HC04超声波模块一个,SG90舵机一个,红外避障模块传感器两个,红外光电反射传感器两个,L9110S电机驱动模块(L298n也可以使用),测速传感器一个,SU-03T离线语音模块一个,HC-08蓝牙模块一个,DC-DC电压转换模块两个,0.96寸OLED屏幕一个,杜邦线若干,热熔胶枪一个,也可以再准备一个面包板。
二.电机控制及调速
关于电机控制选用的是L9110s电机驱动模块,在淘宝里也很容易买到,也才不到2块钱,比l298n偏移很,但缺点就是容易发烫。
接线说明:
我们以L9110s电机驱动模块为新手小白讲一下这些模块怎么接线,后面就不多赘述模块如何接线了。模块通常的引脚就是VCC,GND,以及其他的控制或信号引脚。VCC就是模块的电源正极(大多数的模块都是5V供电,具体的参考模块说明书),接到单片机最小系统的VCC引脚上。GND就是模块的负极,接到单片机最小系统的GND引脚上。VCC或GND也可接到通过面包板引出的正负极上。剩下的引脚就接到单片机的IO口上即可。
控制小车前后左右:
控制小车的前后左右运动说白了就是控制两个电机的正反转,两个电机同时正转小车前进,同时反转小车后退,电机一个转一个不转小车实现转向。逻辑非常简单,下面就是L9110s电机驱动模块的真值表,并且该模块可同时控制两个电机。将两个电机的两根线
接入端子中就可以写代码控制了。
IA1输入高电平,IA1输入低电平,【OA1 OB1】电机正转;
IA1输入低电平,IA1输入高电平,【OA1 OB1】电机反转;
IA2输入高电平,IA2输入低电平,【OA2 OB2】电机正转;
IA2输入低电平,IA2输入高电平,【OA2 OB2】电机反转;
motor.c
#include "reg52.h"sbit RightCon1A = P3^2; //电机A由P3.2,P3.3控制
sbit RightCon1B = P3^3;sbit LeftCon1A = P3^4; //电机B由P3.4,P3.5控制
sbit LeftCon1B = P3^5;void goBack() //后退
{LeftCon1A = 0;LeftCon1B = 1;RightCon1A = 0;RightCon1B = 1;
}void goRight() //右转
{LeftCon1A = 0;LeftCon1B = 1;RightCon1A = 0;RightCon1B = 0;
}void goLeft() //左转
{LeftCon1A = 0;LeftCon1B = 0;RightCon1A = 0;RightCon1B = 1;
}void goForward() //前进
{LeftCon1A = 1;LeftCon1B = 0;RightCon1A = 1;RightCon1B = 0;
}void stop() //停车
{LeftCon1A = 0;LeftCon1B = 0;RightCon1A = 0;RightCon1B = 0;
}
蓝牙控制小车
蓝牙控制小车的核心思想就是采用串口中断,用手机app给蓝牙模块发送不同的字符串,单片机接收到字符串后进入串口中断,通过判断字符串内容来控制小车的前后左右。换句话说,蓝牙控制小车不需要配置任何蓝牙模块的相关代码,只需写好串口中断的控制即可实现蓝牙控制。
usart.c
#include "reg52.h"
#include "intrins.h"
#include <string.h>
#include "motor.h"#define SIZE 12
sfr AUXR = 0x8E;
char buffer[SIZE];void UartInit(void) //9600bps@11.0592MHz
{AUXR=0X01;SCON = 0x50; //配置串口工作方式1,REN使能(REN:串行使能接收位)TMOD &= 0xF0; TMOD |=0X20; //设定定时器1工作方式位,8位自动重装载TL1 = 0xFD; //设定定时器初值TH1 = 0xFD; //设定定时器初值(波特率9600初值)ET1 = 0; //紧止定时器1中断TR1 = 1; //启动定时器1EA=1; //开启总中断ES=1; //开始串口中断
}//发送M1-前进,发送M2-后退,发送M3-左转,发送M4-右转void Usart_Handler() interrupt 4
{static int i=0;char tmp;if(RI)//接收中断处理{RI=0;//清除中断标志位tmp=SBUF;if(tmp=='M'){i=0;}buffer[i++]=tmp;if(buffer[0]=='M'){switch(buffer[1]){case'1':goForward(); break;case'2':goBack(); break;case'3':goLeft(); break; case'4':goRight(); break; }} if(i==12){memset(buffer,'\0',SIZE); //清空串口接收区i=0;} }
}
小车调速
前面的代码实现的小车的前进都是让小车全速前进,电池的功率有多大小车前进的速度就有多快,6节干电池供电肯定会比四节干电池快的多。那么我们用单片机如何给小车调速,我们用PWM给小车进行调速。
调速原理:全速前进是LeftCon1A = 0; LeftCon1B = 1;完全停止是LeftCon1A = 0;LeftCon1B = 0;那么单位时间内,比如20ms, 有15ms是全速前进,5ms是完全停止, 速度就会比5ms全速前进,15ms完全停止获得的功率多,相应的速度更快!这就是PWM通过改变占空比调速的原理。
为了更好控制两个电机的不同状态打开两个定时器中断,定时器0控制左边电机,定时器2控制右边电机。使用两组定时器中断调速,这样就可以通过差速的方式控制小车的转向。左轮定时器0调速,右轮定时器1调速,那么左转就是右轮速度大于左轮!
time.c
#include "reg52.h"
#include "motor.h"char leftspeed;
char cntLeft=0;char rightspeed;
char cntRight=0;void Time0Init()
{//1. 配置定时器0工作模式位16位计时TMOD = 0x01;//2. 给初值,定一个0.5出来TL0=0x33;TH0=0xFE;//3. 开始计时TR0 = 1;TF0 = 0;//4. 打开定时器0中断ET0 = 1;//5. 打开总中断EAEA = 1;
}void Time1Init()
{//1. 配置定时器1工作模式位16位计时TMOD &= 0x0F;TMOD |= 0X1 <<4;//2. 给初值,定一个0.5出来TL1=0x33;TH1=0xFE;//3. 开始计时TR1 = 1;TF1 = 0;//4. 打开定时器0中断ET1 = 1;//5. 打开总中断EAEA = 1;
}void Time0Handler() interrupt 1
{cntLeft++; //统计爆表的次数. cnt=1的时候,报表了1//重新给初值TL0=0x33;TH0=0xFE;//控制PWM波if(cntLeft < leftspeed){goForwardLeft();}else{stopLeft();}if(cntLeft == 40){//爆表40次,经过了20mscntLeft = 0; //当100次表示1s,重新让cnt从0开始,计算下一次的1s}
}void Time1Handler() interrupt 3
{cntRight++; //统计爆表的次数. cnt=1的时候,报表了1//重新给初值TL1=0x33;TH1=0xFE;//控制PWM波if(cntRight < rightspeed){//右前进goForwardRight();}else{//停止stopRight();}if(cntRight == 40){//爆表40次,经过了20mscntRight = 0; //当100次表示1s,重新让cnt从0开始,计算下一次的1s}
}
三.小车循迹方案
循迹模块介绍:
我们选用的是TCRT5000传感器,传感器的红外发射二极管不断发射红外线,当发射出的红外线没有被反射回来或被反射回来但强度不够大时, 红外接收管一直处于关断状态,此时模块的输出端为高电平,指示二极管一直处于熄灭状态 被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和, 此时模块的输出端为低电平,指示二极管被点亮。
(注:该模块有一个数字信号输出DO和一个模拟信号输出AO,我们只使用了数字信号DO引脚,AO悬空即可)
总结就是一句话,没反射回来,D0输出高电平,灭灯!
循迹原理:
小车循迹是沿着黑色的线走,由于黑色具有较强的吸收能力,当循迹模块发射的红外线照射到黑线时,红外线将会被黑线吸收,导致循迹模块上光敏三极管处于关闭状态,此时模块上一个LED熄灭。在没有检测到黑线时,模块上两个LED 常亮。
总结就是一句话,有感应到黑线,D0输出高电平 ,灭灯!
小车行驶在直线赛道的时候,两个循迹模块分别是在黑线的两侧,不会吸收发射出的红外线。行驶在圆形赛道的时候,某一侧的循迹模块必然会接触到黑线部分,因此会给单片机一个高电平信号,单片机通过判断是那一侧的循迹模块发出的高电平从而控制小车往那个方向转向。
总结:
走直线时:两个循迹模块都是低电平。
左转时:左模块输出高电平,右模块输出低电平。
右转时:右模块输出高电平,左模块输出低电平。
还需注意的就是这个循迹模块的电压输入是3v-5v,而我们的电池盒提供的电压是6v,虽然不会烧坏模块,但实测的效果会大打折扣,所以使用DC-DC电压模块给循迹模块提供5v的电压。
根据上面讲的原理,开始写一个测试代码。
#include <reg52.h>
#include "motor.h"sbit leftSensor = P2^7; //左循迹模块
sbit rightSensor = P2^6; //右循迹模块void main()
{ while(1){if(leftSensor == 0 && rightSensor == 0){goForward();}if(leftSensor == 1 && rightSensor == 0){goLeft();}if(leftSensor == 0 && rightSensor == 1){goRight();}if(leftSensor == 1 && rightSensor == 1){stop();} }
}
循迹模块电位器调节:
经过测试这段代码就可以实现循迹的功能,但是把代码烧录进去之后还需要根据实际情况调节循迹模块上的电位器改变循迹模块的灵敏度。要是发现小车一放下就转圈圈,或者不按照黑线循迹,那么很有可能就是电位器的灵敏度的问题。比如家里地板颜色偏灰,这个时候就要把灵敏度调高。
PWM调速加入实现小车丝滑转弯:
上面的代码虽然已经可以实现循迹的功能,但是在实际测试中发现在转弯的时候一抽一抽。现在就改进一下小车“抽抽” 的这个问题。
上面的代码实现小车转弯的时候,相当于是一种急刹车式的转弯,小车在转弯前丝毫不减速。那么想要让小车丝滑转弯,那么就必须两个轮子都要有速度,而不是通过一个轮子转另一个轮子不转这种方式实现转弯。我们把之前写过的PWM调速的代码加入到循迹的代码中即可实现丝滑转弯。
#include "motor.h"
#include "usart.h"
#include "time.h"
#include <reg52.h>/*leftspeed,rightspeed这两个参数具体给多大根据小车跑动情况来随时修改
*/
sbit leftSensor = P2^7; //左循迹模块
sbit rightSensor = P2^6;//右循迹模块extern char leftspeed;
extern char rightspeed;void main()
{Time0Init();Time1Init(); while(1){if(leftSensor == 0 && rightSensor == 0){leftspeed = 40; rightspeed = 40; }if(leftSensor == 1 && rightSensor == 0){leftspeed = 15;rightspeed = 40; //右轮速度大于左轮,右转}if(leftSensor == 0 && rightSensor == 1){leftspeed = 40; //左轮速度大于右轮,右转rightspeed = 15;}if(leftSensor == 1 && rightSensor == 1){leftspeed = 0; rightspeed = 0; } }
}
实测可能会遇到的问题:
1.直线跑不直:在跑直线的时候可能跑着跑着就越来越斜的这种情况,这种情况的原因就是两个电机的速度不一致导致的。解决方法就是把转的快的那一边的电机速度一点一点给它调慢,直到小车彻底跑直为止。
2.转弯时跑出赛道:这个情况的出现就是转弯的时候电机转速不够,提高相应的电机转速即可。
四.跟随功能实现
原理和寻线是一样的,寻线红外观朝下,跟随朝前。用到的也是两个红外模块只不过发射管的位置不一样而已。
跟随小车的原理:
左边跟随模块能返回红外,输出低电平,右边不能返回,输出高电平,说明物体在左边,需要左转 右边跟随模块能返回红外,输出低电平,左边不能返回,输出高电平,说明物体在右边,需要右转
跟随代码如下:
#include "motor.h"
#include "reg52.h"sbit leftSensor = P2^5;
sbit rightSensor = P2^4;void main()
{while(1){if(leftSensor == 0 && rightSensor == 0){goForward();}if(leftSensor == 1 && rightSensor == 0){goRight();}if(leftSensor == 0 && rightSensor == 1){goLeft();}if(leftSensor == 1 && rightSensor == 1){stop();}}
}
五.测速功能实现
模块介绍:
用途:广泛用于电机转速检测,脉冲计数,位置限位等。
有遮挡,输出高电平;无遮挡,输出低电平
接线 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。换句话说就是一个脉冲就是走了1.0205CM。定时器可以设计成一秒,统计脉冲数,假设一秒有80脉冲,那么就是80cm/s。
代码逻辑:
接下来我们编程实现将车速通过串口发送给串口助手,也可以使用蓝牙模块发送到手机app上。我们先发送到串口助手上看看效果。
time.c
#include <REGX52.H>unsigned int cnt = 0;
extern unsigned int rightspeedCnt;
extern unsigned int leftspeedCnt;
unsigned int speedleft;
unsigned int speedright;
char singal;void Time0Init()
{//1. 配置定时器0工作模式位16位计时TMOD = 0x01;//2. 给初值,定一个0.5ms出来TL0=0x33;TH0=0xFE;//3. 开始计时TR0 = 1;TF0 = 0;//4. 打开定时器0中断ET0 = 1;//5. 打开总中断EAEA = 1;
}void Time0Handler() interrupt 1
{cnt++; //统计爆表的次数. cnt=1的时候,报表了1//重新给初值TL0=0x33;TH0=0xFE;if(cnt == 2000){//爆表2000次,经过了1s{cnt=0;singal=1;speedright = rightspeedCnt; //计算小车的速度,也就是拿到speedCnt的值speedleft = leftspeedCnt;rightspeedCnt=0;leftspeedCnt=0;//1秒后拿到speedCnt个格子,就能算出这1s的速度,格子清零} }
}
usart.c
#include "reg52.h"
#include "intrins.h"
#include <string.h>void UartInit(void) //9600bps@11.0592MHz
{SCON = 0x50; //配置串口工作方式1,REN使能接收TMOD &= 0x0F;TMOD |= 0x20;//定时器1工作方式位8位自动重装TH1 = 0xFD;TL1 = 0xFD;//9600波特率的初值TR1 = 1;//启动定时器EA = 1;//开启总中断
}void SendByte(char mydata)//发送字符
{SBUF = mydata;while(!TI);TI=0;
}void SendString(char *str)//发送字符串
{while(*str != '\0'){SendByte(*str);str++;}
}
main.c
#include "motor.h"
#include "usart.h"
#include "reg52.h"
#include "time.h"
#include "stdio.h"sbit speedIO1 = P3^2;//外部中断0
sbit speedIO2 = P3^3;//外部中断1
unsigned int leftspeedCnt = 0; //统计左轮格子,脉冲次数
unsigned int rightspeedCnt = 0; //统计右轮格子,脉冲次数
extern unsigned int speedleft; //左轮速度
extern unsigned int speedright; //右轮速度
extern char singal; //发送速度的信号
char SpeedMes_R[24]; //主程序发送右轮速度数据的字符串缓冲区
char SpeedMes_L[24]; //主程序发送左轮速度数据的字符串缓冲区void Ex0Init()
{EX0 = 1;//允外部中断IT0 = 1;//外部中断的下降沿触发
}void Ex1Init()
{EX1 = 1;//允外部中断IT1 = 1;//外部中断的下降沿触发
}void main()
{Time0Init();//定时器0初始化UartInit();//串口相关初始化Ex0Init();//外部中断初始化Ex1Init();while(1){if(singal){sprintf(SpeedMes_R,"rightspeed:%d cm/s",speedright);//串口数据的字符串拼装,speed是格子,每个格子1cmSendString(SpeedMes_R);//速度发出去SendString("\r\n"); sprintf(SpeedMes_L,"leftspeed:%d cm/s",speedleft);//串口数据的字符串拼装,speed是格子,每个格子1cmSendString(SpeedMes_L);//速度发出去SendString("\r\n"); singal = 0;//清0speed,下次由定时器1s后的中断处理中再置一 }}
}void rightspeedHandler() interrupt 0 //外部中断处理函数
{rightspeedCnt++; //每经过一共格子,加一
}void leftspeedHandler() interrupt 2 //外部中断处理函数
{leftspeedCnt++; //每经过一共格子,加一
}
六.OLED显示车速
车速可以再上位机中显示了,接下来我们将车速显示到OLED屏幕上。使用OLED屏幕需要先了解IIC或者SPI的协议,我使用的0.96寸IIC协议的OLED屏幕。这里就不多赘述OLED屏幕的使用和IIC协议了,我之前也写过有关IIC和OLED屏幕相关的文章,感兴趣的小伙伴可以去看一下。不想深究OLED原理的也可以直接拿厂家提供的代码直接使用。
由于OLED相关的代码过于冗长,就不在文章里展示了,我主页里的资源有这个小车的完整代码,我们主要展示主函数代码以及汉字取模软件的使用。
取模软件使用:
输入显示的文字
按下Ctrl+Enter,选择C51格式
文字的代码随即生成
本来是想用汉字显示到屏幕中,但是16*16的汉字显示的话屏幕太小了,后面的车速的内容就放不下了,所以最后决定用英文显示。这样的话就用不上取模软件了,直接在程序里面包含厂家提供的英文字模库即可。
main.c
#include "motor.h"
#include "usart.h"
#include "reg52.h"
#include "time.h"
#include "stdio.h"
#include "OLED.h"sbit speedIO1 = P3^2;//外部中断0
sbit speedIO2 = P3^3;//外部中断1
unsigned int leftspeedCnt = 0; //统计左轮格子,脉冲次数
unsigned int rightspeedCnt = 0; //统计右轮格子,脉冲次数
extern unsigned int speedleft; //左轮速度
extern unsigned int speedright; //右轮速度
extern char singal; //发送速度的信号
char SpeedMes_R[24]; //主程序发送右轮速度数据的字符串缓冲区
char SpeedMes_L[24]; //主程序发送左轮速度数据的字符串缓冲区void Ex0Init()
{EX0 = 1;//允外部中断IT0 = 1;//外部中断的下降沿触发
}void Ex1Init()
{EX1 = 1;//允外部中断IT1 = 1;//外部中断的下降沿触发
}void main()
{Time0Init();//定时器0初始化UartInit();//串口相关初始化Ex0Init();//外部中断0初始化Ex1Init();//外部中断1初始化Oled_Init();//OLED初始化Oled_Clear();//清屏while(1){if(singal){sprintf(SpeedMes_R,"R-speed:%d cm/s",speedright);//串口数据的字符串拼装,speed是格子,每个格子1cmSendString(SpeedMes_R);//速度发出去SendString("\r\n"); sprintf(SpeedMes_L,"L-speed:%d cm/s",speedleft);//串口数据的字符串拼装,speed是格子,每个格子1cmSendString(SpeedMes_L);//速度发出去SendString("\r\n"); singal = 0;//清0speed,下次由定时器1s后的中断处理中再置一 }Oled_Show_Str(1,1,SpeedMes_L); //显示左轮速度Oled_Show_Str(2,1,SpeedMes_R); //显示右轮速度}
}void rightspeedHandler() interrupt 0 //外部中断处理函数
{rightspeedCnt++; //每经过一共格子,加一
}void leftspeedHandler() interrupt 2 //外部中断处理函数
{leftspeedCnt++; //每经过一共格子,加一
}
七.摇头避障功能实现
避障功能是实现用的是超声波模块,其原理是通过发送和收超声波,利用时间差和声音传播速度, 计算出模块到前方障碍物的距离。当检测到小于指定距离时,小车停止。
怎么让它发送波:Trig给Trig端口至少10us的高电平
怎么知道它开始发了 Echo信号:由低电平跳转到高电平,表示开始发送波
怎么知道接收了返回波 Echo:由高电平跳转回低电平,表示波回来了
怎么算时间:Echo引脚维持高电平的时间! 波发出去的那一下,开始启动定时器 波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间
怎么算距离:距离 = 速度 (340m/s)* 时间/2
Hc04.c
#include "reg52.h"
#include "delay.h"sbit Trig = P2^3;
sbit Echo = P2^2;void Time1Init()
{ 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)* 时间/2return (time * 0.017);
}
摇头功能使用舵机实现
怎么控制舵机
向黄色信号线“灌入”PWM信号,PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右
0.5ms-------------0度; 2.5% 对应函数中占空比为250
1.0ms------------45度; 5.0% 对应函数中占空比为500
1.5ms------------90度; 7.5% 对应函数中占空比为750
2.0ms-----------135度; 10.0% 对应函数中占空比为1000
2.5ms-----------180度; 12.5% 对应函数中占空比为125
SG90.c
#include "reg52.h"
#include "delay.h"sbit sg90_con = P1^1;int jd; //定义角度
int cnt = 0;void Time0Init()
{//1. 配置定时器0工作模式位16位计时TMOD &= 0xF0; //设置定时器模式TMOD |= 0x01;//2. 给初值,定一个0.5出来TL0=0x33;TH0=0xFE;//3. 开始计时TR0 = 1;TF0 = 0;//4. 打开定时器0中断ET0 = 1;//5. 打开总中断EAEA = 1;
}void SG90_Middle()
{//中间位置jd = 3; //90度 1.5ms高电平cnt = 0;
}void SG90_Right()
{//右边位置jd = 1; //0度cnt = 0;
}void SG90_Left()
{//左边位置jd = 5; //135度cnt = 0;
}void Time0Handler() interrupt 1
{cnt++; //统计爆表的次数. cnt=1的时候,报表了1//重新给初值TL0=0x33;TH0=0xFE;//控制PWM波if(cnt < jd){sg90_con = 1;}else{sg90_con = 0;}if(cnt == 40){//爆表40次,经过了20mscnt = 0; //当100次表示1s,重新让cnt从0开始,计算下一次的1ssg90_con = 1;}
}
注意:如果舵机电压低于额定电压时,舵机可能会疯狂地不受控制的摇头,供电正常后这个问题就可以解决。(一开始我还以为是舵机坏了)
八.SU-03T语音模块介绍
接下来进入小车的最后一个阶段,语音控制。选用的是SU-03T这款语音模块,这款模块对小白特别友好,无需编程,不需要二次开发,通过厂家给的网站配置后即可使用,傻瓜式操作。而且这款模块的识别还是非常灵敏的,前端的界面设计的也非常好用。
SU-03T语音模块配置:
智能公元/AIOT快速产品化平台http://www.smartpi.cn/#/
登录厂家所提供的开发平台,点击创建产品->其它产品
选择纯离线 方案
选择我们使用的SU-03T
填写好产品名称,语言选择中文,如何点击保存。
接下来就进入了我们的配置界面,我们选择三个IO口分别切换我们的循迹模式,跟随模式,避障模式。把语音模块的三个IO口都设置为高电平
接着配置语音模块的唤醒词,这里可以多配置几条,并且可以设置灵敏度。
接着再定义应答语,根据自己的功能定义。
接着再设置每种词条的命令,我设置的是当说出某种词条的时候指定的IO口输出低电平。
然后其余的设置都比较简单,根据自己的爱好选择音调,语速之类的。
点击生成后就等待生成,大约半个小时左右。
生成完之后点击下载SDK,后续烧录的过程参考厂家提供的资料即可,我都会跟这个项目的源代码放在一起。
九.语音切换小车模式+OLED显示模式
语音模块的加入是我们实现的最后一个功能,也是我们之前所有功能的一个大汇总,所有的功能都是基于我们前面写过的代码。但是在实现这个功能之前我有一点需要强调。由于51单片机只有两组定时器,而我们的许多功能都用到了定时器,比如测速,电机调速,舵机,超声波避障。因此我们没有多余的定时器去分配给这么多功能,因此最后这个“大杂烩”小车我们选择抛弃测速,电机调速这两个功能。倘若选用更强大的MCU比如STM32就不存在这种取舍问题。
小车总体功能:当说出“进入循迹模式”,小车会进入循迹模式。说出“进入跟随模式”,小车会进入跟随模式。说出“进入避障模式”,小车会进入避障模式。并且OLED屏幕上会显示小车的模式。
OLED.c
#include "reg52.h"
#include "intrins.h"
#include "Oledfont.h"sbit scl = P1^2;
sbit sda = P1^3;void IIC_Start()
{scl = 0;sda = 1;scl = 1;_nop_();sda = 0;_nop_();
}void IIC_Stop()
{scl = 0;sda = 0;scl = 1;_nop_();sda = 1;_nop_();
}char IIC_ACK()
{char flag;sda = 1;//就在时钟脉冲9期间释放数据线_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;//scl拉低,让sda做好数据准备sda = dataSend & 0x80;//1000 0000获得dataSend的最高位,给sda_nop_();//发送数据建立时间scl = 1;//scl拉高开始发送_nop_();//数据发送时间scl = 0;//发送完毕拉低_nop_();//dataSend = dataSend << 1;}
}void Oled_Write_Cmd(char dataCmd)
{// 1. start()IIC_Start();// // 2. 写入从机地址 b0111 1000 0x78IIC_Send_Byte(0x78);// 3. ACKIIC_ACK();// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据IIC_Send_Byte(0x00);// 5. ACKIIC_ACK();//6. 写入指令/数据IIC_Send_Byte(dataCmd);//7. ACKIIC_ACK();//8. STOPIIC_Stop();
}void Oled_Write_Data(char dataData)
{// 1. start()IIC_Start();// // 2. 写入从机地址 b0111 1000 0x78IIC_Send_Byte(0x78);// 3. ACKIIC_ACK();// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据IIC_Send_Byte(0x40);// 5. ACKIIC_ACK();///6. 写入指令/数据IIC_Send_Byte(dataData);//7. ACKIIC_ACK();//8. STOPIIC_Stop();
}void Oled_Init(void){Oled_Write_Cmd(0xAE);//--display offOled_Write_Cmd(0x00);//---set low column addressOled_Write_Cmd(0x10);//---set high column addressOled_Write_Cmd(0x40);//--set start line address Oled_Write_Cmd(0xB0);//--set page addressOled_Write_Cmd(0x81); // contract controlOled_Write_Cmd(0xFF);//--128 Oled_Write_Cmd(0xA1);//set segment remap Oled_Write_Cmd(0xA6);//--normal / reverseOled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)Oled_Write_Cmd(0x3F);//--1/32 dutyOled_Write_Cmd(0xC8);//Com scan directionOled_Write_Cmd(0xD3);//-set display offsetOled_Write_Cmd(0x00);//Oled_Write_Cmd(0xD5);//set osc divisionOled_Write_Cmd(0x80);//Oled_Write_Cmd(0xD8);//set area color mode offOled_Write_Cmd(0x05);//Oled_Write_Cmd(0xD9);//Set Pre-Charge PeriodOled_Write_Cmd(0xF1);//Oled_Write_Cmd(0xDA);//set com pin configuartionOled_Write_Cmd(0x12);//Oled_Write_Cmd(0xDB);//set VcomhOled_Write_Cmd(0x30);//Oled_Write_Cmd(0x8D);//set charge pump enableOled_Write_Cmd(0x14);//Oled_Write_Cmd(0xAF);//--turn on oled panel
}void Oled_Clear()
{unsigned char i,j; //-128 --- 127for(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-2unsigned int i;Oled_Write_Cmd(0xb0+(row*2-2)); //page 0Oled_Write_Cmd(0x00+(col&0x0f)); //lowOled_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 1Oled_Write_Cmd(0x00+(col&0x0f)); //lowOled_Write_Cmd(0x10+(col>>4)); //highfor(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; }
}
main.c
#include "reg52.h"
#include "hc04.h"
#include "delay.h"
#include "sg90.h"
#include "motor.h"
#include "oled.h"#define Middle 0 //定义舵机状态标志位
#define Left 1
#define Right 2#define Following 1
#define Tracking 2
#define Avioding 3 //定义模式状态标志位//语音模块引脚定义
sbit A25 = P1^5; //跟随模式
sbit A26 = P1^6; //避障模式
sbit A27 = P1^7; //循迹模式//跟随红外模块引脚定义
sbit Fol_leftSensor = P2^5;
sbit Fol_rightSensor = P2^4;//循迹模块引脚定义
sbit Tra_leftSensor = P0^1;
sbit Tra_rightSensor = P0^2;char dir;
double M_distance; //正前方距离
double L_distance; //左侧距离
double R_distance; //右侧距离//跟随模式
void Following_Mode()
{if(Fol_leftSensor == 0 && Fol_rightSensor == 0){goForward(); }if(Fol_leftSensor == 1 && Fol_rightSensor == 0){goRight();}if(Fol_leftSensor == 0 && Fol_rightSensor == 1){goLeft();}if(Fol_leftSensor == 1 && Fol_rightSensor == 1){stop();}
}//循迹模式
void Tracking_Mode()
{if(Tra_leftSensor == 0 && Tra_rightSensor == 0){goForward();}if(Tra_leftSensor == 1 && Tra_rightSensor == 0){goLeft();}if(Tra_leftSensor == 0 && Tra_rightSensor == 1){goRight();}if(Tra_leftSensor == 1 && Tra_rightSensor == 1){stop();}
}//避障模式
void Avioding_Mode()
{if(dir != Middle){SG90_Middle(); dir = Middle;Delay300ms(); }M_distance = get_distance();if(M_distance > 25){goForward();//前进}else if(M_distance < 10){goBack();//距离过小时后退}else{stop();SG90_Left();Delay300ms();L_distance = get_distance();SG90_Middle();Delay300ms();SG90_Right();Delay300ms(); R_distance = get_distance();dir = Right;if(L_distance < R_distance){goRight();}if(L_distance > R_distance){goLeft();} }
}void main()
{int mark = 0;Time0Init();Time1Init();//舵机的初始位置SG90_Middle();Delay300ms(); Oled_Init();//OLED初始化Oled_Clear();//清屏Oled_Show_Str(2,2,"-----Ready----");while(1){//满足避障模式的条件if(A26 == 0 && A25 == 1 && A27 == 1){if(mark!=Avioding){Oled_Clear();Oled_Show_Str(2,2,"Avioding_Mode");}mark = Avioding; Avioding_Mode();}//满足跟随模式的条件if(A26 == 1 && A25 == 0 && A27 == 1){if(mark!=Following){Oled_Clear();Oled_Show_Str(2,2,"Following_Mode");}mark = Following;Following_Mode();}//满足循迹模式的条件if(A26 == 1 && A25 == 1 && A27 == 0){if(mark!=Tracking){Oled_Clear();Oled_Show_Str(2,2,"Tracking_Mode");}mark = Tracking;Tracking_Mode(); } }
}
基于51单片机的多功能智能语音循迹避障小车相关推荐
- c语言智能车跑道检测程序,基于单片机的智能循迹避障小车(附电路原理图,程序清单)...
基于单片机的智能循迹避障小车(附电路原理图,程序清单)(论文10000字) 摘要:目前,移动机器人的开发和研究越来越令人瞩目,而智能循迹壁障小车作为移动机器人的一个重要分支,非常值得我们探索和讨论.智 ...
- 基于STM32的智能循迹避障小车实验(小车运动部分)
写在前面 这个实验是关于智能小车的实验,现在的想法就是先做出一个循迹和避障功能,后续可能会再添加一些其他的模块. 我在做这个实验之前基本了解了F1系列开发板的大部分模块,如果没有学习之前的模块,建议先 ...
- 基于STC12C5616AD芯片智能循迹避障小车完整制作过程(详细教程)
前言:本篇文章适合小白阅读,其中有很基础的Keil 5的使用教程等.大多网友知道如何使用,因此大家可以看目录,对于自己而言比较基础的可以不用阅读,重点关注一些迷茫的部分. 智能循迹避障小车教程目录 智 ...
- 基于STM32F103C8T6的循迹避障小车完整制作过程(详细)----上篇(第123点)
基于STM32F103C8T6的循迹避障小车完整制作过程 本文适合小白观看 由于本人的一个小项目,要做一个基于STM32的循迹避障小车,前后花了约1周的时间,这个过程中也参考了很多大神分享的资料,学到 ...
- 基于STM32F103的红外循迹避障小车设计(含Proteus仿真)
基于STM32F103的红外循迹避障小车设计 红外循迹及红外避障实现较简单,无论是51单片机还是STM32单片机,其例程随处可见.但是完全可以运行的Proteus仿真,开源的并不多,更不要说基于STM ...
- 智能循迹避障小车的红外模块
智能循迹避障小车的红外模块 - 智能循迹避障小车的红外模块用于3个功能-----------循迹,避障,测速. 红外循迹与红外避障 红外循迹TCRT5000L 红外避障 .观察原理图我们可以发现,它们 ...
- 智能循迹避障小车C语言程序编写思路,基于单片机的智能小车红外避障循迹系统设计与制作...
余秀玲 余秀娟 摘 要:随着科技的高速发展,人们对生活质量的要求越来越高,无人驾驶汽车已经被广为研发和试用,由此智能小车的快速发展也是在情理之中.通过对基于单片机的智能小车的硬件及软件设计分析,实现红 ...
- 【DIY】Arduino智能循迹避障小车
文章目录 1.功能说明 2.硬件组成 3.软件安装 4.功能调试 小车展示: 1.功能说明 Arduino智能循迹停障小车是自动驾驶车辆的微型化,用几个简单的电子元器件实现循迹.停障.绕障等功能. 2 ...
- 基于STM32的智能循迹避障小车
[1]研究背景 随着计算机,微电子技术的快速发展,智能化技术的开发越来越快,智能程度也越来越高,应用的范围也得到了极大的扩展.因此,基于嵌入式技术的智能小车应运而生. 近来两年,智能小车在生活中有着广 ...
最新文章
- jetbrains intellij IDEA 常用插件和配置
- 转发和重定向的区别?
- Linux修改终端显示前缀及环境变量
- Java 线上问题排查神器 Arthas 快速上手与原理浅谈
- VTK:K均值聚类用法实战
- c#保存数据格式为.cvs_C#读取csv格式文件的方法
- Java遍历完数的一些思考
- Node编码格式的设置
- h5获取http请求头_java学习之路(2),http协议,request类
- 【报告分享】2021制造业数字化转型路线图.pdf(附下载链接)
- zw版【转发·台湾nvp系列Delphi例程】HALCON HistoToThresh1
- Spark-TaskSchedule和TaskScheduleImpl解释和过程
- 键盘皇者 RealForce 104Pro独家评测
- Spring IOC 容器源码分析 - 循环依赖的解决办法 1
- macbook air从win10回到macOS Sierra
- python3下载m3u8转mp4_Python3.6:根据m3u8下载mp4视频
- 51单片机的nop延时延时函数
- sybil attack (女巫攻击)
- 8.互 联 网 上 的 音 频 和 视 频 服 务
- 分享124个PHP源码,总有一款适合您