51单片机智能小车(循迹、跟随、避障、测速、蓝牙、wifie、4g、语音识别)总结-2
https://developer.aliyun.com/article/1506698
5.远程控制小车
5.1 蓝牙控制小车
- 使用蓝牙模块,串口透传
- 蓝牙模块,又叫做蓝牙串口模块
串口透传技术:
- 透传即透明传送,是指在数据的传输过程中,通过无线的方式这组数据不发生任何形式的改变,仿 佛传输过程是透明的一样,同时保证传输的质量,原封不动地到了最终接收者手里。
- 以太网,蓝牙,Zigbee, GPRS 等模块玩法一样,对嵌入式程序员来说,不需要关心通讯模块内部数据 及协议栈工作原理,只要通过串口编程获得数据即可
代码实现:
//main.c #include "motor.h" #include "delay.h" #include "uart.h" void main() { UartInit(); while(1){ stop(); } } //uart.c #include "reg52.h" #include "motor.h" #include "string.h" #include "delay.h" sbit D5 = P3^7; #define SIZE 12 sfr AUXR = 0x8E; char buffer[SIZE]; void UartInit(void) //9600bps@11.0592MHz { AUXR = 0x01; SCON = 0x50; //配置串口工作方式1,REN使能接收 TMOD &= 0x0F; TMOD |= 0x20;//定时器1工作方式位8位自动重装 TH1 = 0xFD; TL1 = 0xFD;//9600波特率的初值 TR1 = 1;//启动定时器 EA = 1;//开启总中断 ES = 1;//开启串口中断 } //M1qian M2 hou M3 zuo M4 you void Uart_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(); Delay10ms(); break; case '2': goBack(); Delay10ms(); break; case '3': goLeft(); Delay10ms(); break; case '4': goRight(); Delay10ms(); break; default: stop(); break; } } if(i == 12) { memset(buffer, '\0', SIZE); i = 0; } } } //motor.c #include "reg52.h" sbit RightCon1A = P3^2; sbit RightCon1B = P3^3; sbit LeftCon1A = P3^4; sbit LeftCon1B = P3^5; void goForward() { 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 goBack() { LeftCon1A = 1; LeftCon1B = 0; RightCon1A = 1; RightCon1B = 0; } void stop() { LeftCon1A = 0; LeftCon1B = 0; RightCon1A = 0; RightCon1B = 0; } //delay.c #include "intrins.h" void Delay10ms() //@11.0592MHz { unsigned char i, j; i = 18; j = 235; do { while (--j); } while (--i); } void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); }
5.2 蓝牙控制并测速小车
原理:运用上面讲到的蓝牙模块和测速模块
代码实现:
//main.c #include "motor.h" #include "delay.h" #include "uart.h" #include "reg52.h" #include "time.h" #include "stdio.h" #include "Oled.h" sbit speedIO = P3^2;//外部中断0 unsigned int speedCnt = 0; //统计格子,脉冲次数 extern unsigned int speed;//速度 extern char signal; //主程序发速度数据的通知 char speedMes[24]; //主程序发送速度数据的字符串缓冲区 void Ex0Init() { EX0 = 1;//允许中断 //EA = 1;在串口初始化函数中已经打开了总中断 IT0 = 1;//外部中断的下降沿触发 } void main() { Time0Init();//定时器0初始化 UartInit();//串口相关初始化 //外部中断初始化 Ex0Init(); Oled_Init(); Oled_Clear(); while(1){ if(signal){//定时器1s到点,把signal置一,主程序发送速度 sprintf(speedMes,"speed:%d cm/s",speed);//串口数据的字符串拼装,speed是格子,每个格子1cm SendString(speedMes);//速度发出去 signal = 0;//清0speed,下次由定时器1s后的中断处理中再置一 } Oled_Show_Str(2,2,speedMes); } } void speedHandler() interrupt 0 //外部中断处理函数 { speedCnt++;//码盘转动了一个格子 } //uart.c #include "reg52.h" #include "motor.h" #include "string.h" sbit D5 = P3^7; #define SIZE 12 sfr AUXR = 0x8E; char buffer[SIZE]; void UartInit(void) //9600bps@11.0592MHz { 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 mydata) { SBUF = mydata; while(!TI); TI = 0; } void SendString(char *str) { while(*str != '\0'){ SendByte(*str); str++; } } //M1qian M2 hou M3 zuo M4 you void Uart_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; default: stop(); break; } } if(i == 12) { memset(buffer, '\0', SIZE); i = 0; } } } //motor.c #include "reg52.h" sbit RightCon1A = P3^7; sbit RightCon1B = P3^3; sbit LeftCon1A = P3^4; sbit LeftCon1B = P3^5; void goForward() { 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 goBack() { LeftCon1A = 1; LeftCon1B = 0; RightCon1A = 1; RightCon1B = 0; } void stop() { LeftCon1A = 0; LeftCon1B = 0; RightCon1A = 0; RightCon1B = 0; } //time.c #include "motor.h" #include "reg52.h" extern unsigned int speedCnt; unsigned int speed; char signal = 0; unsigned int cnt = 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 Time0Handler() interrupt 1 { cnt++; //统计爆表的次数. cnt=1的时候,报表了1 //重新给初值 TL0=0x33; TH0=0xFE; if(cnt == 2000){//爆表2000次,经过了1s signal = 1; cnt = 0; //当100次表示1s,重新让cnt从0开始,计算下一次的1s //计算小车的速度,也就是拿到speedCnt的值 speed = speedCnt; speedCnt = 0;//1秒后拿到speedCnt个格子,就能算出这1s的速度,格子清零 } } //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 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; } }
5.3 wifi控制测速小车
- Wifi模块-ESP-01s
- 蓝牙,ESP-01s,Zigbee, NB-Iot等通信模块都是基于AT指令的设计
AT指令介绍:
- AT指令集是从终端设备(Terminal Equipment,TE)或数据终端设备(Data Terminal Equipment,DTE)向终端适配器(Terminal Adapter,TA)或数据电路终端设备(Data Circuit Terminal Equipment,DCE)发送的。
- 其对所传输的数据包大小有定义:即对于AT指令的发送,除AT两个字符外,最多可以接收1056个 字符的长度(包括最后的空字符)。
- 每个AT命令行中只能包含一条AT指令;对于由终端设备主动向PC端报告的URC指示或者response 响应,也要求一行最多有一个,不允许上报的一行中有多条指示或者响应。AT指令以回车作为结 尾,响应或上报以回车换行为结尾。
代码实现:
//main.c #include "motor.h" #include "delay.h" #include "uart.h" #include "reg52.h" #include "time.h" #include "stdio.h" #include "Oled.h" #include "esp8266.h" sbit speedIO = P3^2;//外部中断0 unsigned int speedCnt = 0; //统计格子,脉冲次数 extern unsigned int speed;//速度 extern char signal; //主程序发速度数据的通知 char speedMes[24]; //主程序发送速度数据的字符串缓冲区 //发送数据 char FSSJ[] = "AT+CIPSEND=0,5\r\n"; void Ex0Init() { EX0 = 1;//允许中断 //EA = 1;在串口初始化函数中已经打开了总中断 IT0 = 1;//外部中断的下降沿触发 } void main() { Time0Init();//定时器0初始化 UartInit();//串口相关初始化 Delay1000ms();//给espwifi模块上电时间 initWifi_AP(); //初始化wifi工作在ap模式 waitConnect(); //等待客户端的连接 //外部中断初始化 Ex0Init(); Oled_Init(); Oled_Clear(); while(1){ if(signal){//定时器1s到点,把signal置一,主程序发送速度 SendString(FSSJ); Delay1000ms(); sprintf(speedMes,"%dcms",speed);//串口数据的字符串拼装,speed是格子,每个格子1cm SendString(speedMes);//速度发出去 signal = 0;//清0speed,下次由定时器1s后的中断处理中再置一 } Oled_Show_Str(2,2,speedMes); } } void speedHandler() interrupt 0 //外部中断处理函数 { speedCnt++;//码盘转动了一个格子 } //uart.c #include "reg52.h" #include "motor.h" #include "string.h" sbit D5 = P3^7; #define SIZE 12 sfr AUXR = 0x8E; char buffer[SIZE]; extern char AT_OK_Flag; //OK返回值的标志位 extern char Client_Connect_Flag; void UartInit(void) //9600bps@11.0592MHz { 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 mydata) { SBUF = mydata; while(!TI); TI = 0; } void SendString(char *str) { while(*str != '\0'){ SendByte(*str); str++; } } //M1qian M2 hou M3 zuo M4 you void Uart_Handler() interrupt 4 { static int i = 0;//静态变量,被初始化一次 char tmp; if(RI)//中断处理函数中,对于接收中断的响应 { RI = 0;//清除接收中断标志位 tmp = SBUF; if(tmp == 'M' || tmp == 'O' || tmp == '0'){ i = 0; } buffer[i++] = tmp; //连接服务器等OK返回值指令的判断 if(buffer[0] == 'O' && buffer[1] == 'K'){ AT_OK_Flag = 1; memset(buffer, '\0', SIZE); } if(buffer[0] == '0' && buffer[2] == 'C'){ Client_Connect_Flag = 1; memset(buffer, '\0', SIZE); } //灯控指令 if(buffer[0] == 'M'){ switch(buffer[1]){ case '1': goForward(); break; case '2': goBack(); break; case '3': goLeft(); break; case '4': goRight(); break; default: stop(); break; } } if(i == 12) { memset(buffer, '\0', SIZE); i = 0; } } } //motor.c #include "reg52.h" sbit RightCon1A = P3^7; sbit RightCon1B = P3^3; sbit LeftCon1A = P3^4; sbit LeftCon1B = P3^5; void goForward() { 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 goBack() { LeftCon1A = 1; LeftCon1B = 0; RightCon1A = 1; RightCon1B = 0; } void stop() { LeftCon1A = 0; LeftCon1B = 0; RightCon1A = 0; RightCon1B = 0; } //time.c #include "motor.h" #include "reg52.h" extern unsigned int speedCnt; unsigned int speed; char signal = 0; unsigned int cnt = 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 Time0Handler() interrupt 1 { cnt++; //统计爆表的次数. cnt=1的时候,报表了1 //重新给初值 TL0=0x33; TH0=0xFE; if(cnt == 2000){//爆表2000次,经过了1s signal = 1; cnt = 0; //当100次表示1s,重新让cnt从0开始,计算下一次的1s //计算小车的速度,也就是拿到speedCnt的值 speed = speedCnt; speedCnt = 0;//1秒后拿到speedCnt个格子,就能算出这1s的速度,格子清零 } } //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 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; } } //esp8266.c #include "uart.h" //1 工作在路由模式 code char LYMO[] = "AT+CWMODE=2\r\n"; //2 使能多链接 code char DLJ[] = "AT+CIPMUX=1\r\n"; //3 建立TCPServer code char JLFW[] = "AT+CIPSERVER=1\r\n"; // default port = 333 char AT_OK_Flag = 0; //OK返回值的标志位 char Client_Connect_Flag = 0; void initWifi_AP() { SendString(LYMO); while(!AT_OK_Flag); AT_OK_Flag = 0; SendString(DLJ); while(!AT_OK_Flag); AT_OK_Flag = 0; } void waitConnect() { SendString(JLFW); while(!Client_Connect_Flag); AT_OK_Flag = 0; } //delay.c #include "intrins.h" void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); }
5.4 4g控制小车
原理:运用EC03-DNC4G通信模块
模块介绍:
- 基于串口AT指令的开发方式
- 有两种工作模式,默认是透传模式,通过其他方式进入AT指令模式
- 注意插卡不要出错,下图红色位置为SIM卡状态灯,亮才是正常
代码不做修改,直接基于蓝牙小车整合, 4g模块只要做好外网透传就可以了
6.语音控制小车
6.1语音模块配置:
使用SU-03T / LD3320
具体介绍看我之前写过的博客:SU-03T语音模块的使用_罗小白的干爹的博客-CSDN博客
6.2 语音控制小车开发和调试代码
代码示例:
//main.c #include "reg52.h" #include "hc04.h" #include "delay.h" #include "sg90.h" #include "Oled.h" #include "motor.h" #define MIDDLE 0 #define LEFT 1 #define RIGHT 2 #define BZ 1 #define XJ 2 #define GS 3 sbit A25 = P1^5; sbit A26 = P1^6; sbit A27 = P1^7; sbit leftSensorX = P2^7; sbit rightSensorX = P2^6; sbit leftSensorG = P2^5; sbit rightSensorG = P2^4; char dir; double disMiddle; double disLeft; double disRight; void xunjiMode() { if(leftSensorX == 0 && rightSensorX == 0){ goForward(); } if(leftSensorX == 1 && rightSensorX == 0){ goLeft(); } if(leftSensorX == 0 && rightSensorX == 1){ goRight(); } if(leftSensorX == 1 && rightSensorX == 1){ //停 stop(); } } void gensuiMode() { if(leftSensorG == 0 && rightSensorG == 0){ goForward(); } if(leftSensorG == 1 && rightSensorG == 0){ goRight(); } if(leftSensorG == 0 && rightSensorG == 1){ goLeft(); } if(leftSensorG == 1 && rightSensorG == 1){ //停 stop(); } } void bizhangMode() { if(dir != MIDDLE){ sgMiddle(); dir = MIDDLE; Delay300ms(); } disMiddle = get_distance(); if(disMiddle > 35){ //前进 goForward(); }else if(disMiddle < 10){ goBack(); }else { //停止 stop(); //测左边距离 sgLeft(); Delay300ms(); disLeft = get_distance(); sgMiddle(); Delay300ms(); sgRight(); dir = RIGHT; Delay300ms(); disRight = get_distance(); if(disLeft < disRight){ goRight(); Delay150ms(); stop(); } if(disRight < disLeft){ goLeft(); Delay150ms(); stop(); } } } void main() { int mark = 0; Time0Init(); Time1Init(); //舵机的初始位置 sgMiddle(); Delay300ms(); Delay300ms(); dir = MIDDLE; Oled_Init(); Oled_Clear(); Oled_Show_Str(2,2,"-----Ready----"); while(1){ //满足寻迹模式的条件 if(A25 == 0 && A26 == 1 && A27 == 1){ if(mark != XJ){ Oled_Clear(); Oled_Show_Str(2,2,"-----XunJi----"); } mark = XJ; xunjiMode(); } //满足跟随模式的条件 if(A25 == 1 && A26 == 0 && A27 == 1){ if(mark != GS){ Oled_Clear(); Oled_Show_Str(2,2,"-----GenSui----"); } mark = GS; gensuiMode(); } //满足避障模式的条件 if(A25 == 1 && A26 == 1 && A27 == 0){ if(mark != BZ){ Oled_Clear(); Oled_Show_Str(2,2,"-----BiZhang----"); } mark = BZ; bizhangMode(); } } } //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)* 时间/2 return (time * 0.017); } //delay.c #include "intrins.h" void Delay2000ms() //@11.0592MHz { unsigned char i, j, k; i = 15; j = 2; k = 235; do { do { while (--k); } while (--j); } while (--i); } void Delay10us() //@11.0592MHz { unsigned char i; i = 2; while (--i); } void Delay300ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 3; j = 26; k = 223; do { do { while (--k); } while (--j); } while (--i); } void Delay150ms() //@11.0592MHz { unsigned char i, j, k; i = 2; j = 13; k = 237; do { do { while (--k); } while (--j); } while (--i); } void Delay450ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 4; j = 39; k = 209; do { do { while (--k); } while (--j); } while (--i); } //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 sgMiddle() { //中间位置 jd = 3; //90度 1.5ms高电平 cnt = 0; } void sgLeft() { //左边位置 jd = 5; //135度 1.5ms高电平 cnt = 0; } void sgRight() { //右边位置 jd = 1; //0度 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; } } //motor.c #include "reg52.h" sbit RightCon1A = P3^7; sbit RightCon1B = P3^3; sbit LeftCon1A = P3^4; sbit LeftCon1B = P3^5; void goForward() { 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 goBack() { LeftCon1A = 1; LeftCon1B = 0; RightCon1A = 1; RightCon1B = 0; } void stop() { LeftCon1A = 0; LeftCon1B = 0; RightCon1A = 0; RightCon1B = 0; } //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 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; } }