目錄
一.功能介紹及硬件準備
二.電機控制及調速
三.小車循跡方案
四.跟隨功能實現
五.測速功能實現
六.OLED顯示車速
七.搖頭避障功能實現
八.SU-03T語音模塊介紹
九.語音切換小車模式+OLED顯示模式
一.功能介紹及硬件準備
這是一款基于51單片機開發(fā)的智能小車,通過這篇文章我會記錄下來開發(fā)這款小車的全部過程。這款小車集成了循跡,避障,跟隨,語音切換模式選擇,并且將可以將車速顯示到OLED屏幕上,也可以通過手機app藍牙操控小車。(注:全文的代碼采取分文件編程的寫法)
硬件準備
小車底盤一個(兩驅),5號4節(jié)電池盒一個,51單片機最小系統一個,HC04超聲波模塊一個,SG90舵機一個,紅外避障模塊傳感器兩個,紅外光電反射傳感器兩個,L9110S電機驅動模塊(L298n也可以使用),測速傳感器一個,SU-03T離線語音模塊一個,HC-08藍牙模塊一個,DC-DC電壓轉換模塊兩個,0.96寸OLED屏幕一個,杜邦線若干,熱熔膠槍一個,也可以再準備一個面包板。
二.電機控制及調速
關于電機控制選用的是L9110s電機驅動模塊,在淘寶里也很容易買到,也才不到2塊錢,比l298n偏移很,但缺點就是容易發(fā)燙。
接線說明:
我們以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給藍牙模塊發(fā)送不同的字符串,單片機接收到字符串后進入串口中斷,通過判斷字符串內容來控制小車的前后左右。換句話說,藍牙控制小車不需要配置任何藍牙模塊的相關代碼,只需寫好串口中斷的控制即可實現藍牙控制。
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; //啟動定時器1
EA=1; //開啟總中斷
ES=1; //開始串口中斷
}
//發(fā)送M1-前進,發(fā)送M2-后退,發(fā)送M3-左轉,發(fā)送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); //清空串口接收區(qū)
i=0;
}
}
}
小車調速
前面的代碼實現的小車的前進都是讓小車全速前進,電池的功率有多大小車前進的速度就有多快,6節(jié)干電池供電肯定會比四節(jié)干電池快的多。那么我們用單片機如何給小車調速,我們用PWM給小車進行調速。?
調速原理:全速前進是LeftCon1A = 0; LeftCon1B = 1;完全停止是LeftCon1A = 0;LeftCon1B = 0;那么單位時間內,比如20ms, 有15ms是全速前進,5ms是完全停止, 速度就會比5ms全速前進,15ms完全停止獲得的功率多,相應的速度更快!這就是PWM通過改變占空比調速的原理。
為了更好控制兩個電機的不同狀態(tài)打開兩個定時器中斷,定時器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. 打開總中斷EA
EA = 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. 打開總中斷EA
EA = 1;
}
void Time0Handler() interrupt 1
{
cntLeft++; //統計爆表的次數. cnt=1的時候,報表了1
//重新給初值
TL0=0x33;
TH0=0xFE;
//控制PWM波
if(cntLeft < leftspeed){
goForwardLeft();
}else{
stopLeft();
}
if(cntLeft == 40){//爆表40次,經過了20ms
cntLeft = 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次,經過了20ms
cntRight = 0; //當100次表示1s,重新讓cnt從0開始,計算下一次的1s
}
}
三.小車循跡方案
循跡模塊介紹:
我們選用的是TCRT5000傳感器,傳感器的紅外發(fā)射二極管不斷發(fā)射紅外線,當發(fā)射出的紅外線沒有被反射回來或被反射回來但強度不夠大時, 紅外接收管一直處于關斷狀態(tài),此時模塊的輸出端為高電平,指示二極管一直處于熄滅狀態(tài) 被檢測物體出現在檢測范圍內時,紅外線被反射回來且強度足夠大,紅外接收管飽和, 此時模塊的輸出端為低電平,指示二極管被點亮。
(注:該模塊有一個數字信號輸出DO和一個模擬信號輸出AO,我們只使用了數字信號DO引腳,AO懸空即可)
總結就是一句話,沒反射回來,D0輸出高電平,滅燈!
循跡原理:
小車循跡是沿著黑色的線走,由于黑色具有較強的吸收能力,當循跡模塊發(fā)射的紅外線照射到黑線時,紅外線將會被黑線吸收,導致循跡模塊上光敏三極管處于關閉狀態(tài),此時模塊上一個LED熄滅。在沒有檢測到黑線時,模塊上兩個LED 常亮。
總結就是一句話,有感應到黑線,D0輸出高電平 ,滅燈!
小車行駛在直線賽道的時候,兩個循跡模塊分別是在黑線的兩側,不會吸收發(fā)射出的紅外線。行駛在圓形賽道的時候,某一側的循跡模塊必然會接觸到黑線部分,因此會給單片機一個高電平信號,單片機通過判斷是那一側的循跡模塊發(fā)出的高電平從而控制小車往那個方向轉向。
總結:
走直線時:兩個循跡模塊都是低電平。
左轉時:左模塊輸出高電平,右模塊輸出低電平。
右轉時:右模塊輸出高電平,左模塊輸出低電平。
還需注意的就是這個循跡模塊的電壓輸入是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();
}
}
}
循跡模塊電位器調節(jié):
經過測試這段代碼就可以實現循跡的功能,但是把代碼燒錄進去之后還需要根據實際情況調節(jié)循跡模塊上的電位器改變循跡模塊的靈敏度。要是發(fā)現小車一放下就轉圈圈,或者不按照黑線循跡,那么很有可能就是電位器的靈敏度的問題。比如家里地板顏色偏灰,這個時候就要把靈敏度調高。
PWM調速加入實現小車絲滑轉彎:
上面的代碼雖然已經可以實現循跡的功能,但是在實際測試中發(fā)現在轉彎的時候一抽一抽?,F在就改進一下小車“抽抽” 的這個問題。
上面的代碼實現小車轉彎的時候,相當于是一種急剎車式的轉彎,小車在轉彎前絲毫不減速。那么想要讓小車絲滑轉彎,那么就必須兩個輪子都要有速度,而不是通過一個輪子轉另一個輪子不轉這種方式實現轉彎。我們把之前寫過的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.轉彎時跑出賽道:這個情況的出現就是轉彎的時候電機轉速不夠,提高相應的電機轉速即可。
四.跟隨功能實現
原理和尋線是一樣的,尋線紅外觀朝下,跟隨朝前。用到的也是兩個紅外模塊只不過發(fā)射管的位置不一樣而已。
跟隨小車的原理:
左邊跟隨模塊能返回紅外,輸出低電平,右邊不能返回,輸出高電平,說明物體在左邊,需要左轉 右邊跟隨模塊能返回紅外,輸出低電平,左邊不能返回,輸出高電平,說明物體在右邊,需要右轉
跟隨代碼如下:
#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。
代碼邏輯:
接下來我們編程實現將車速通過串口發(fā)送給串口助手,也可以使用藍牙模塊發(fā)送到手機app上。我們先發(fā)送到串口助手上看看效果。
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. 打開總中斷EA
EA = 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)//發(fā)送字符
{
SBUF = mydata;
while(!TI);
TI=0;
}
void SendString(char *str)//發(fā)送字符串
{
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; //發(fā)送速度的信號
char SpeedMes_R[24]; //主程序發(fā)送右輪速度數據的字符串緩沖區(qū)
char SpeedMes_L[24]; //主程序發(fā)送左輪速度數據的字符串緩沖區(qū)
void Ex0Init()
{
EX0 = 1;//允外部中斷
IT0 = 1;//外部中斷的下降沿觸發(fā)
}
void Ex1Init()
{
EX1 = 1;//允外部中斷
IT1 = 1;//外部中斷的下降沿觸發(fā)
}
void main()
{
Time0Init();//定時器0初始化
UartInit();//串口相關初始化
Ex0Init();//外部中斷初始化
Ex1Init();
while(1){
if(singal){
sprintf(SpeedMes_R,"rightspeed:%d cm/s",speedright);//串口數據的字符串拼裝,speed是格子,每個格子1cm
SendString(SpeedMes_R);//速度發(fā)出去
SendString("\r\n");
sprintf(SpeedMes_L,"leftspeed:%d cm/s",speedleft);//串口數據的字符串拼裝,speed是格子,每個格子1cm
SendString(SpeedMes_L);//速度發(fā)出去
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; //發(fā)送速度的信號
char SpeedMes_R[24]; //主程序發(fā)送右輪速度數據的字符串緩沖區(qū)
char SpeedMes_L[24]; //主程序發(fā)送左輪速度數據的字符串緩沖區(qū)
void Ex0Init()
{
EX0 = 1;//允外部中斷
IT0 = 1;//外部中斷的下降沿觸發(fā)
}
void Ex1Init()
{
EX1 = 1;//允外部中斷
IT1 = 1;//外部中斷的下降沿觸發(fā)
}
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是格子,每個格子1cm
SendString(SpeedMes_R);//速度發(fā)出去
SendString("\r\n");
sprintf(SpeedMes_L,"L-speed:%d cm/s",speedleft);//串口數據的字符串拼裝,speed是格子,每個格子1cm
SendString(SpeedMes_L);//速度發(fā)出去
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++; //每經過一共格子,加一
}
七.搖頭避障功能實現
避障功能是實現用的是超聲波模塊,其原理是通過發(fā)送和收超聲波,利用時間差和聲音傳播速度, 計算出模塊到前方障礙物的距離。當檢測到小于指定距離時,小車停止。
怎么讓它發(fā)送波:Trig給Trig端口至少10us的高電平
怎么知道它開始發(fā)了 Echo信號:由低電平跳轉到高電平,表示開始發(fā)送波
怎么知道接收了返回波 Echo:由高電平跳轉回低電平,表示波回來了
怎么算時間:Echo引腳維持高電平的時間! 波發(fā)出去的那一下,開始啟動定時器 波回來的拿一下,我們開始停止定時器,計算出中間經過多少時間
怎么算距離:距離 = 速度 (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() //發(fā)送超聲波
{
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
}
double get_distance() //獲取距離
{
double time;
//定時器數據清零,以便下一次測距
TH1 = 0;
TL1 = 0;
//1. Trig ,給Trig端口至少10us的高電平
startHC();
//2. echo由低電平跳轉到高電平,表示開始發(fā)送波
while(Echo == 0);
//波發(fā)出去的那一下,開始啟動定時器
TR1 = 1;
//3. 由高電平跳轉回低電平,表示波回來了
while(Echo == 1);
//波回來的那一下,我們開始停止定時器
TR1 = 0;
//4. 計算出中間經過多少時間
time = (TH1 * 256 + TL1)*1.085;//us為單位
//5. 距離 = 速度 (340m/s)* 時間/2
return (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. 打開總中斷EA
EA = 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次,經過了20ms
cnt = 0; //當100次表示1s,重新讓cnt從0開始,計算下一次的1s
sg90_con = 1;
}
}
注意:如果舵機電壓低于額定電壓時,舵機可能會瘋狂地不受控制的搖頭,供電正常后這個問題就可以解決。(一開始我還以為是舵機壞了)
八.SU-03T語音模塊介紹
接下來進入小車的最后一個階段,語音控制。選用的是SU-03T這款語音模塊,這款模塊對小白特別友好,無需編程,不需要二次開發(fā),通過廠家給的網站配置后即可使用,傻瓜式操作。而且這款模塊的識別還是非常靈敏的,前端的界面設計的也非常好用。
?SU-03T語音模塊配置:
智能公元/AIOT快速產品化平臺??????http://www.smartpi.cn/#/
登錄廠家所提供的開發(fā)平臺,點擊創(chuàng)建產品->其它產品
選擇純離線 方案
?
?選擇我們使用的SU-03T
?填寫好產品名稱,語言選擇中文,如何點擊保存。
接下來就進入了我們的配置界面,我們選擇三個IO口分別切換我們的循跡模式,跟隨模式,避障模式。把語音模塊的三個IO口都設置為高電平
接著配置語音模塊的喚醒詞,這里可以多配置幾條,并且可以設置靈敏度。
接著再定義應答語,根據自己的功能定義。
接著再設置每種詞條的命令,我設置的是當說出某種詞條的時候指定的IO口輸出低電平。
然后其余的設置都比較簡單,根據自己的愛好選擇音調,語速之類的。
點擊生成后就等待生成,大約半個小時左右。
生成完之后點擊下載SDK,后續(xù)燒錄的過程參考廠家提供的資料即可,我都會跟這個項目的源代碼放在一起。
九.語音切換小車模式+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_();//發(fā)送數據建立時間
scl = 1;//scl拉高開始發(fā)送
_nop_();//數據發(fā)送時間
scl = 0;//發(fā)送完畢拉低
_nop_();//
dataSend = dataSend << 1;
}
}
void Oled_Write_Cmd(char dataCmd)
{
// 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(dataCmd);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}
void Oled_Write_Data(char dataData)
{
// 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(dataData);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}
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;
}
}
main.c文章來源:http://www.zghlxwxcb.cn/news/detail-778750.html
#include "reg52.h"
#include "hc04.h"
#include "delay.h"
#include "sg90.h"
#include "motor.h"
#include "oled.h"
#define Middle 0 //定義舵機狀態(tài)標志位
#define Left 1
#define Right 2
#define Following 1
#define Tracking 2
#define Avioding 3 //定義模式狀態(tài)標志位
//語音模塊引腳定義
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();
}
}
}
?
文章來源地址http://www.zghlxwxcb.cn/news/detail-778750.html
到了這里,關于基于51單片機的多功能智能語音循跡避障小車的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!