赞
踩
最近花时间做了一个小车,所以在这里做个总结。
电机选型:1:48的减速电机(这里的1:48是指减速比,是指电机通过牺牲速度来获得扭矩的方法。减速比越大,它的速度就越慢,扭矩就越大)
轮子:麦克纳姆轮(麦克纳姆轮是一种全向轮,由于其独特的构造,才使得麦克纳姆轮随意改变行驶方向的特性,但在安装的时候要注意麦克纳姆轮的安装方向)
轮子的正确分布方向:
关于麦克纳姆轮的详细介绍请看这篇博文:麦克纳姆轮(万向轮)驱动和玩法_麦克纳姆轮转向_
电机驱动选型:L298N
由于麦轮对扭矩要求比较大,所以普通的L9110S满足不了需求。而且当给L298N输入7V以上的电压时,可以向板外供电5V,所以优先选用L298N。
L298N的详细资料:链接:https://pan.baidu.com/s/1gyQNSwDTgpNtM-_t4V5dSQ 提取码:52x7
电池选型:选用18650锂电池,有试过别的电池,结果都是供电有问题。
怎么让小车动起来?即怎么用单片机控制电机转动?
很简单,给电机驱动模块的两个引脚一高一低,电机就会转动
代码实现:
/*****main.c*****/
#include "motor.h"
void main()
{
while(1)
{
Car_Move();
}
}
/*****motor.c*****/ #include "pin.h" #include "delay.h" void Go_Forward() //正转 { A1_1 = 0; //A1轮 A1_2 = 1; B1_1 = 0; //B1轮 B1_2 = 1; B2_1 = 0; //B2轮 B2_2 = 1; A2_1 = 0; //A2轮 A2_2 = 1; } void Go_Back() //反转 { A1_1 = 1; //A1轮 A1_2 = 0; B1_1 = 1; //B1轮 B1_2 = 0; B2_1 = 1; //B2轮 B2_2 = 0; A2_1 = 1; //A2轮 A2_2 = 0; } void Right_Tran() //向右平移 { A1_1 = 0; //A1轮 A1_2 = 1; B1_1 = 1; //B1轮 B1_2 = 0; B2_1 = 1; //B2轮 B2_2 = 0; A2_1 = 0; //A2轮 A2_2 = 1; } void Left_Tran() //向左平移 { A1_1 = 1; //A1轮 A1_2 = 0; B1_1 = 0; //B1轮 B1_2 = 1; B2_1 = 0; //B2轮 B2_2 = 1; A2_1 = 1; //A2轮 A2_2 = 0; } void Turn_Right() //右转 { A1_1 = 0; //A1轮 A1_2 = 1; B1_1 = 1; //B1轮 B1_2 = 0; B2_1 = 0; //B2轮 B2_2 = 1; A2_1 = 1; //A2轮 A2_2 = 0; } void Turn_Left() //左转 { A1_1 = 1; //A1轮 A1_2 = 0; B1_1 = 0; //B1轮 B1_2 = 1; B2_1 = 1; //B2轮 B2_2 = 0; A2_1 = 0; //A2轮 A2_2 = 1; } void Car_Move() { Go_Forward(); Delay1000ms(); Go_Back(); Delay1000ms(); Turn_Left(); Delay1000ms(); Turn_Right(); Delay1000ms(); Left_Tran(); Delay1000ms(); Right_Tran(); Delay1000ms(); }
/*****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); }
/*****pin.h*****/
#include <reg52.h>
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
只让小车自己跑感觉少了点意思,所以加入串口控制,发送命令来控制小车进行前后左右等运动,就像之前用串口控制灯的亮灭。
/*****main.c*****/
#include "uart.h"
void main()
{
UART_Init();
while(1)
{
}
}
/*****uart.c****/ #include "reg52.h" #include "string.h" #include "motor.h" #define SIZE 12 sfr AUXR = 0x8E; char buffer[SIZE]; void UART_Init() { SCON = 0x50; //配置SCON为8位UART,波特率可变,REN允许串行接收 TMOD &= 0x0F; //定时器1高位清零,地位不变 TMOD |= 0x20; //定时器1采用8位重装计数 TL1 = 0xFD; TH1 = 0xFD; //9600波特率对应初值 TR1 = 1; //启动定时器1 AUXR = 0x01; //禁止ALE信号输出 EA = 1; //开启总中断 ES = 1; //开启串口中断 } void UART_handler () interrupt 4 { static int i = 0; //静态变量,初始化执行一次 char tmp; if(RI) //如果接收到数据,硬件将RI自动置1,需要软件清0 { RI = 0; tmp = SBUF; if(tmp == 'M') { i = 0; } buffer[i] = tmp; i++; if(buffer[0] == 'M') { switch(buffer[1]) { case '1': Go_Forward(); //发送M1前进,依此类推 break; case '2': Go_Back(); break; case '3': Turn_Left(); break; case '4': Turn_Right(); break; case '5': Left_Tran(); break; case '6': Right_Tran(); break; default: Car_Stop(); break; } } if(i == SIZE) { i = 0; memset(buffer,'\0',SIZE); } } }
通过蓝牙模块能够控制小车的运动状态,如果想实现按下按键启动,松开按键停止的效果可以借助STC-ISP的工具来实现。
/*****main.c*****/
#include "uart.h"
#include "motor.h"
void main()
{
UART_Init();
while(1)
{
Car_Stop(); //通过在main函数离不断地调用Car_Stop();使小车一直处于停止状态,在串口那里发指令给延时让小车运动20ms,这样就会形成发一次指令运动20ms的效果,如果一直发指令,小车就会一直运动,停止发指令,小车就会调用Car_Stop();函数停止运动
}
}
/*****uart.c*****/ #include "reg52.h" #include "string.h" #include "motor.h" #include "delay.h" sbit LED1 = P3^7; #define SIZE 12 sfr AUXR = 0x8E; char buffer[SIZE]; void UART_Init() { SCON = 0x50; //配置SCON为8位UART,波特率可变,REN允许串行接收 TMOD &= 0x0F; //定时器1高位清零,地位不变 TMOD |= 0x20; //定时器1采用8位重装计数 TL1 = 0xFD; TH1 = 0xFD; //9600波特率对应初值 TR1 = 1; //启动定时器1 AUXR = 0x01; //禁止ALE信号输出 EA = 1; //开启总中断 ES = 1; //开启串口中断 } void UART_handler () interrupt 4 { static int i = 0; //静态变量,初始化执行一次 char tmp; if(RI) //如果接收到数据,硬件将RI自动置1,需要软件清0 { RI = 0; tmp = SBUF; if(tmp == 'M') { i = 0; } buffer[i] = tmp; i++; if(buffer[0] == 'M') { switch(buffer[1]) { case '1': Go_Forward(); Delay20ms(); //给延时让小车运动一会儿,以下同上 break; case '2': Go_Back(); Delay20ms(); break; case '3': Turn_Left(); Delay20ms(); break; case '4': Turn_Right(); Delay20ms(); break; case '5': Left_Tran(); Delay20ms(); break; case '6': Right_Tran(); Delay20ms(); break; default: Car_Stop(); break; } } if(i == SIZE) { i = 0; memset(buffer,'\0',SIZE); } } }
在前边的运动过程中,小车一直处于全速运行的状态,那么如何实现小车转的慢一会儿快一会儿?
用PWM来控制小车速度
前边用PWM来控制舵机,在20ms的一个脉冲内保持0.5ms的高电平,剩下的都为低电平,舵机就会转动0°;当在20ms的一个脉冲内保持2ms的高电平,剩下的都为低电平,舵机就会转动135°。
这里用PWM控制小车速度就是在20ms的一个周期内,如果小车在20ms内全速运行,那么小车的速度就达到了峰值;如果小车在10ms内全速运行,剩下时间内一直停止,那么小车的速度就是全速运行的一半。依此类推,如果小车全速运行的时间越短,停止的时间越长,那么小车的速度就会越慢,以此可以实现小车控制小车速度的功能。
代码实现:
#include "motor.h" #include "delay.h" #include "timer.h" extern char speed; void main() { Timer0Init(); while(1) { speed = 25; Delay2000ms(); speed = 50; Delay2000ms(); speed = 80; Delay2000ms(); } } /* extern的作用: C51头文件使用extern的目的是外部变量或函数声明。 使用时要注意: 1.在使用extern进行外部变量声明时,不能重新给变量赋值。例如: extern unsigned char i=0;(错误,编译器会报错) extern unsigned char i;(正确) 其他文件使用extern外部变量声明的变量时(i),可以赋值。(unsigned char i=0;) 2.extern只是声明不是定义。extern只是告诉编译器声明的变量(i),是在之前已经定义过的,且在这里要用到。 */
/*****timer.c*****/ #include "reg52.h" #include "motor.h" char cnt = 0; char speed; void Timer0Init() { TMOD &= 0xF0; TMOD |= 0x01; TL0 = 0x1A; TH0 = 0xFF; EA =1; ET0 = 1; TR0 = 1; TF0 = 0; } void Timer0 () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来 { cnt++; TL0 = 0x1A; TH0 = 0xFF; if(cnt < speed) { Go_Forward(); } else { Car_Stop(); } if(cnt == 80) { cnt = 0; } }
前边实现了对小车速度的控制,现在要实现差速运动,即左右轮速度不一样,那么就需要各自的前进函数和停止函数,以此来实现小车的差速运动。
代码实现:
/*****main.c*****/ #include "motor.h" #include "delay.h" #include "timer.h" extern char speedLeft; extern char speedRight; void main() { Timer0Init(); Timer1Init(); while(1) { speedLeft = 40; speedRight = 80; Delay1000ms(); speedLeft = 80; speedRight = 40; Delay1000ms(); } }
/*****motor.c*****/ #include "pin.h" #include "delay.h" void Go_Forward_Right() //正转 { B1_1 = 0; //B1轮 B1_2 = 1; A2_1 = 0; //A2轮 A2_2 = 1; } void Go_Forward_Left() { A1_1 = 0; //A1轮 A1_2 = 1; B2_1 = 0; //B2轮 B2_2 = 1; } void Car_Stop_Right() { B1_1 = 0; //B1轮 B1_2 = 0; A2_1 = 0; //A2轮 A2_2 = 0; } void Car_Stop_Left() { A1_1 = 0; //A1轮 A1_2 = 0; B2_1 = 0; //B2轮 B2_2 = 0; } void Go_Back() //反转 { A1_1 = 1; //A1轮 A1_2 = 0; B1_1 = 1; //B1轮 B1_2 = 0; B2_1 = 1; //B2轮 B2_2 = 0; A2_1 = 1; //A2轮 A2_2 = 0; } void Right_Tran() //向右平移 { A1_1 = 0; //A1轮 A1_2 = 1; B1_1 = 1; //B1轮 B1_2 = 0; B2_1 = 1; //B2轮 B2_2 = 0; A2_1 = 0; //A2轮 A2_2 = 1; } void Left_Tran() //向左平移 { A1_1 = 1; //A1轮 A1_2 = 0; B1_1 = 0; //B1轮 B1_2 = 1; B2_1 = 0; //B2轮 B2_2 = 1; A2_1 = 1; //A2轮 A2_2 = 0; } void Turn_Left() //左转 { A1_1 = 0; //A1轮 A1_2 = 1; B1_1 = 1; //B1轮 B1_2 = 0; B2_1 = 0; //B2轮 B2_2 = 1; A2_1 = 1; //A2轮 A2_2 = 0; } void Turn_Right() //右转 { A1_1 = 1; //A1轮 A1_2 = 0; B1_1 = 0; //B1轮 B1_2 = 1; B2_1 = 1; //B2轮 B2_2 = 0; A2_1 = 0; //A2轮 A2_2 = 1; }
/*****timer.c*****/ #include "reg52.h" #include "motor.h" char cntLeft = 0; char speedLeft; char cntRight = 0; char speedRight; void Timer0Init() { TMOD &= 0xF0; TMOD |= 0x01; TL0 = 0x1A; TH0 = 0xFF; EA =1; ET0 = 1; TR0 = 1; TF0 = 0; } void Timer1Init() { TMOD &= 0x0F; TMOD |= 0x10; TL1 = 0x1A; TH1 = 0xFF; EA =1; ET1 = 1; TR1 = 1; TF1 = 0; } void Timer0_Handler () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来 { cntLeft++; TL0 = 0x1A; TH0 = 0xFF; if(cntLeft < speedLeft) { Go_Forward_Left(); } else { Car_Stop_Left(); } if(cntLeft == 80) { cntLeft = 0; } } void Timer1_Handler () interrupt 3 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来 { cntRight++; TL1 = 0x1A; TH1 = 0xFF; if(cntRight < speedRight) { Go_Forward_Right(); } else { Car_Stop_Right(); } if(cntRight == 80) { cntRight = 0; } }
循迹小车就是利用TCRT5000红外循迹传感器,利用红外容易被黑色吸收,白色反射的原理进行循迹。
下面是TCRT5000的工作原理
根据循迹模块的工作原理我们就能对小车的运动方向做出判断:
当左循迹和右循迹都照到白色的地图上时,左右模块都能接收到返回来的红外输出低电平,此时开关指示灯亮,小车前进;
当左循迹模块压线,右循迹模块照到白色地图上时,左循迹模块发出的红外被黑线吸收,输出高电平,此时开关指示灯灭,右循迹模块能够反射回来,输出低电平,开关指示灯亮,所以此时小车应该左转;
当右循迹模块压线,左循迹模块照到白色地图上时,右循迹模块发出的红外被黑线吸收,输出高电平,此时开关指示灯灭,左循迹模快能够反射回来,输出低电平,开关指示灯亮,所以此时小车应该右转;
当小车跑出地图后,左右循迹模块发出的红外都被吸收,输出高电平,此时开关指示灯灭,所以此时小车应该停下。
代码实现:
/*****main.c*****/ #include "motor.h" #include "pin.h" //直行:左右传感器都反射回来输出低电平亮 //左转:左传感器压线被吸收返回高电平灭,右传感器反射回来输出低电平亮 //右转:右传感器压线被吸收返回高电平灭,左传感器反射回来输出低电平亮 //停止:左右传感器都检测跑出跑道,输高电平灭,停止 void main() { while(1) { if(Lefttrack == 0 && Righttrack == 0) { Go_Forward(); } if(Lefttrack == 1 && Righttrack == 0) { Turn_Left(); } if(Lefttrack == 0 && Righttrack == 1) { Turn_Right(); } if(Lefttrack == 1 && Righttrack == 1) { Car_Stop(); } } }
/*****pin.h*****/ #include <reg52.h> sbit A1_1 = P1^0; //麦轮分布:ABBA sbit A1_2 = P1^1; sbit A2_1 = P1^6; sbit A2_2 = P1^7; sbit B1_1 = P1^4; sbit B1_2 = P1^5; sbit B2_1 = P1^2; sbit B2_2 = P1^3; sbit Lefttrack = P2^7; sbit Righttrack = P2^6;
由于上个代码在左右转弯的时候,每个电机都是全速运行,所以会看到小车在拐弯地时候一卡一卡的。真正实际情况中,在转弯的时候每个轮子的速度都不一样,所以下面加入电机调速来优化循迹小车。
电机调速就利用上边PWM来调速,在转弯的时候给左右轮子不同的速度,使它转弯变得更平滑一些。还可以调节上边的电位器来改变红外反射的距离,配合代码调节循迹小车。
代码实现:
/*****main.c*****/ #include "motor.h" #include "pin.h" #include "timer.h" extern char speedLeft; extern char speedRight; void main() { Timer0Init(); Timer1Init(); while(1) { if(Lefttrack == 0 && Righttrack == 0) { speedLeft = 70; speedRight = 70; } if(Lefttrack == 1 && Righttrack == 0) //左转 { speedLeft = 25; speedRight = 75; //speedRight>speedLeft,左转 } if(Lefttrack == 0 && Righttrack == 1) //右转 { speedLeft = 70; speedRight = 25; //speedLeft>speedRight右转 } if(Lefttrack == 1 && Righttrack == 1) { speedLeft = 0; speedRight = 0; } } }
/*****timer.c*****/ #include "reg52.h" #include "motor.h" char cntLeft = 0; char speedLeft; char cntRight = 0; char speedRight; void Timer0Init() { TMOD &= 0xF0; TMOD |= 0x01; TL0 = 0x1A; TH0 = 0xFF; EA =1; ET0 = 1; TR0 = 1; TF0 = 0; } void Timer1Init() { TMOD &= 0x0F; TMOD |= 0x10; TL1 = 0x1A; TH1 = 0xFF; EA =1; ET1 = 1; TR1 = 1; TF1 = 0; } void Timer0_Handler () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来 { cntLeft++; TL0 = 0x1A; TH0 = 0xFF; if(cntLeft < speedLeft) { Go_Forward_Left(); } else { Car_Stop_Left(); } if(cntLeft == 80) { cntLeft = 0; } } void Timer1_Handler () interrupt 3 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来 { cntRight++; TL1 = 0x1A; TH1 = 0xFF; if(cntRight < speedRight) { Go_Forward_Right(); } else { Car_Stop_Right(); } if(cntRight == 80) { cntRight = 0; } }
跟随小车用的模块和循迹的是一样的,都是TCRT5000红外传感器,只不过循迹的传感器是在下方,跟随用到的是在前方。
根据跟随模块的工作原理对小车的运动方向做出判断:
当物体在正前方的时候,左跟随模块和右跟随模块检测到前边有物体被挡住的时候,红外就会反射回来,输出低电平,此时开关指示灯亮,所以小车应该前进;
当物体在左前方的时候,左跟随模块检测到左边有物体时,左跟随模块发射的红外就会被反射回来,输出低电平,此时开关指示灯亮;右跟随模块没有检测到物体,红外不会反射回来,输出高电平,开关指示灯灭,所以小车应该左转;
当物体在右前方的时候,右跟随模块检测到右边有物体时,右跟随模块发射的红外就会被反射回来,输出低电平,此时开关指示灯亮;左跟随模块没有检测到物体,红外不会反射回来,输出高电平,开关指示灯灭,所以小车应该右转;
当小车前面没有物体的时候,左右跟随模块发射红外都不会反射回来,输出高电平,此时开关指示灯灭,所以小车应该停下来。
代码实现:(具体的参数需要根据场地去调整)
/*****main.c*****/ #include "motor.h" #include "pin.h" //直行:左右传感器都反射回来输出低电平亮 //左转:左传感器检测到前方有物体,反射回来输出低电平亮,右传感器没有检测到输出高电平灭 //右转:右传感器检测到前方有物体,反射回来输出低电平亮,左传感器没有检测到输出高电平灭 //停止:左右传感器都没有检测到前方有物体,输出高电平灭 void main() { while(1) { if(Leftfollow == 0 && Rightfollow == 0) { Go_Forward(); } if(Leftfollow == 0 && Rightfollow == 1) { Turn_Left(); } if(Leftfollow == 1 && Rightfollow == 0) { Turn_Right(); } if(Leftfollow == 1 && Rightfollow == 1) { Car_Stop(); } } }
/*****pin.h*****/ #include <reg52.h> sbit A1_1 = P1^0; //麦轮分布:ABBA sbit A1_2 = P1^1; sbit A2_1 = P1^6; sbit A2_2 = P1^7; sbit B1_1 = P1^4; sbit B1_2 = P1^5; sbit B2_1 = P1^2; sbit B2_2 = P1^3; sbit Lefttrack = P2^7; sbit Righttrack = P2^6; sbit Rightfollow = P2^4; sbit Leftfollow = P2^5;
避障小车的原理是通过超声波配合舵机获取障碍物的距离,从而小车作出相应的前进、后退、左转、右转运动。
代码实现:
/*****main.c*****/ #include "SG90.h" #include "HC04.h" #include "delay.h" #include "motor.h" void main() { double disMiddle; double disLeft; double disRight; Timer0Init(); Timer1Init(); sgMiddle(); Delay300ms(); Delay300ms(); while(1) { disMiddle = Get_distance(); if(disMiddle > 30) { Go_Forward();//前进 } else if(disMiddle < 10) { Go_Back();//后退 // Delay200ms(); // Car_Stop(); } else { Car_Stop(); sgLeft(); //测左边距离 Delay300ms(); disLeft = Get_distance(); sgMiddle(); Delay300ms(); sgRight(); //测右边距离 Delay300ms(); disRight = Get_distance(); if(disLeft < disRight) { Turn_Right(); Delay150ms(); Car_Stop(); } if(disRight < disLeft) { Turn_Left(); Delay150ms(); Car_Stop(); } } sgMiddle(); Delay300ms(); } }
/*****Hc04.c*****/ #include "pin.h" #include "delay.h" void Timer1Init() { TMOD &= 0x0F; //设置定时器模式 TMOD |= 0x10; TL1 = 0; TH1 = 0; } void HC_START() { Trig = 0; Trig = 1; Delay10us(); Trig = 0; } double Get_distance() { double time; TL1 = 0; TH1 = 0; HC_START(); while(Echo == 0); TR1 = 1; while(Echo == 1); TR1 = 0; time = (TH1*256+TL1)*1.085; return (time*0.017); }
/*****SG90.c*****/ #include "reg52.h" #include "pin.h" int cnt = 0; int angel; int angel_back; void Timer0Init() { TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; TL0 = 0x33; TH0 = 0xFE; EA =1; ET0 = 1; TR0 = 1; TF0 = 0; } void sgLeft() { angel = 5; angel_back = angel; cnt = 0; } void sgMiddle() { angel = 3; if(angel_back != angel) { cnt = 0; } angel_back = angel; } void sgRight() { angel = 1; angel_back = angel; cnt = 0; } void Timer0 () interrupt 1 { cnt++; TL0 = 0x33; TH0 = 0xFE; if(cnt < angel) { sg90 = 1; } else { sg90 = 0; } if(cnt == 40) { cnt = 0; sg90 = 1; } }
/*****pin.h*****/ #include <reg52.h> sbit LED1 = P3^7; sbit LED2 = P3^6; sbit sg90 = P2^3; sbit Trig = P2^2; sbit Echo = P2^1; sbit A1_1 = P1^0; //麦轮分布:ABBA sbit A1_2 = P1^1; sbit A2_1 = P1^6; sbit A2_2 = P1^7; sbit B1_1 = P1^4; sbit B1_2 = P1^5; sbit B2_1 = P1^2; sbit B2_2 = P1^3;
测速模块的原理:接好VCC和GND,模块信号指示灯会亮,当模块的槽中无遮挡时,接收管导通,模块OUT输出高电平,遮挡时,OUT输出低电平,信号指示灯灭。将测速模块接到外部中断引脚,检测是否有遮挡来检测电机的转速。
小车的直径是5.8cm,周长为18.212cm,小车的码盘有20个格子,每经过一个各自会产生(遮挡)高电平和(不遮挡)低电平,那么一个脉冲就是5.8 * 3.14 / 20 = 0.9106 cm
定时器可以定时0.25ms,那么经过3600次爆表就经过了0.9s,此时统计脉冲数,即速度。
代码实现:
/*****main.c*****/ /*测速模块接到P3.2外部中断0引脚,设置下降沿触发,每当码盘经过测速模块时就会触 发一次下降沿,此时启动外部中断0,对speedCnt的值进行累加,同时定时器0在工作, 定时一个单位0.25ms,经过3600次爆表计时0.9s。此时统计speedCnt的值,将speedCnt的值 保存在speed中,然后复位进行下一次测速,最后通过sprintf完成数据转换将speed的值发 送到串口上去 */ #include "uart.h" #include "timer.h" #include "stdio.h" extern unsigned int speedB1; extern unsigned int speedA1; extern char signal; char speedMeg1[24]; char speedMeg2[24]; void main() { Timer0Init(); INT0_Init(); INT1_Init(); UART_Init(); while(1) { if(signal == 1) { sprintf(speedMeg1,"speedA1: %d cm/s\r\n",speedA1); //sprintf 格式化输出函数,将"speedA1: %d cm/s\r\n"这一串装到speedMeg1中,其中%d以speedA1来填充 SendString(speedMeg1); sprintf(speedMeg2,"speedB1: %d cm/s\r\n",speedB1); SendString(speedMeg2); signal = 0; } } }
/*****timer.c*****/ #include "reg52.h" #include "motor.h" unsigned int cnt = 0; char signal = 0; unsigned int speedA1; unsigned int speedCntA1; unsigned int speedB1; unsigned int speedCntB1; void Timer0Init() { TMOD &= 0xF0; TMOD |= 0x01; TL0 = 0x1A; TH0 = 0xFF; EA =1; ET0 = 1; TR0 = 1; TF0 = 0; } void INT0_Init() { EA = 1; EX0 = 1; //设置外部中断0打开 IT0 = 1; //设置外部中断由下降沿触发 } void INT1_Init() { EA = 1; EX1 = 1; IT1 = 1; } void Timer0 () interrupt 1 //PWM是50Hz,也就是20ms,将20ms分为80份,定一个0.25ms出来 { cnt++; TL0 = 0x1A; TH0 = 0xFF; if(cnt == 3600) //报表3600次,经过了0.9s { cnt = 0; signal = 1; speedA1 = speedCntA1; //测速模块A1轮 speedCntA1 = 0; speedB1 = speedCntB1; //测速模块B1轮 speedCntB1 = 0; } } void INT0_Handler () interrupt 0 { speedCntA1++; } void INT1_Handler () interrupt 2 { speedCntB1++; }
/*****pin.h*****/
#include <reg52.h>
sbit A1_1 = P1^0; //麦轮分布:ABBA
sbit A1_2 = P1^1;
sbit A2_1 = P1^6;
sbit A2_2 = P1^7;
sbit B1_1 = P1^4;
sbit B1_2 = P1^5;
sbit B2_1 = P1^2;
sbit B2_2 = P1^3;
sbit speedA1 = P3^2;
sbit speedB1 = P3^3;
这个既然能发送到串口,也就能发送到蓝牙模块,通过蓝牙模块进行小车的控制,并且小车的速度会实时的传到手机上。
利用之前开发oled屏幕的经验,把小车的速度实时的显示到oled屏幕上去,同时还发送到串口。
代码实现:
/*****main.c*****/ /*测速模块接到P3.2外部中断0引脚,设置下降沿触发,每当码盘经过测速模块时就会触 发一次下降沿,此时启动外部中断0,对speedCnt的值进行累加,同时定时器0在工作, 定时一个单位0.25ms,经过3600次爆表计时0.9s。此时统计speedCnt的值,将speedCnt的值 保存在speed中,然后复位进行下一次测速,最后通过sprintf完成数据转换将speed的值发 送到串口上去 */ #include "uart.h" #include "timer.h" #include "stdio.h" #include "oled.h" extern unsigned int speedB1; extern unsigned int speedA1; extern char signal; char speedMeg1[24]; char speedMeg2[24]; void main() { Timer0Init(); INT0_Init(); INT1_Init(); UART_Init(); Oled_RST(); Oled_Init(); Oled_Clean(); while(1) { if(signal == 1) { sprintf(speedMeg1,"speed_A1: %d cm/s",speedA1); SendString(speedMeg1); SendString("\r\n"); sprintf(speedMeg2,"speed_B1: %d cm/s",speedB1); SendString(speedMeg2); signal = 0; } Oled_Show_String(1,1,speedMeg1); //在第一行第一列显示A1轮的速度 Oled_Show_String(2,1,speedMeg2); //在第二行第一列显示B1轮的速度 } }
/*****oled.c*****/ #include "IIC.h" #include "intrins.h" #include "pin.h" #include "oledFun.h" void Oled_Write_Cmd(char cmd) { IIC_Start(); //起始信号 IIC_SendByte(0x78); //发送从机地址 IIC_ACK(); //应答信号 IIC_SendByte(0x00); //写命令0x00 IIC_ACK(); //应答信号 IIC_SendByte(cmd); //发送命令 IIC_ACK(); //应答信号 IIC_Stop(); //停止信号 } void Oled_Write_Data(char Data) { IIC_Start(); //起始信号 IIC_SendByte(0x78); //发送从机地址 IIC_ACK(); //应答信号 IIC_SendByte(0x40); //写数据0x40 IIC_ACK(); //应答信号 IIC_SendByte(Data); //发送命令 IIC_ACK(); //应答信号 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_Clean() { unsigned char i,j; //用char会造成越界-128 - 127 for(i=0;i<8;i++) { Oled_Write_Cmd(0xB0 + i); //Page 0 Oled_Write_Cmd(0x00); Oled_Write_Cmd(0x10); //第0列到第127列,写入数据后,列地址会自动偏移 for(j=0;j<128;j++) { Oled_Write_Data(0); //Page 0 -Page 7,第0列到第127列都写入0清屏 } } } void Delay200ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 2; j = 103; k = 147; do { do { while (--k); } while (--j); } while (--i); } void Oled_RST() { RES = 0; Delay200ms(); RES = 1; } void Oled_Show_Byte(char rows,char columns,char oledByte) { unsigned int i; //显示字符的上半部分 Oled_Write_Cmd(0xb0+(rows*2-2)); //选择行 //选择列 Oled_Write_Cmd(0x00+(columns&0x0f)); Oled_Write_Cmd(0x10+(columns>>4)); //显示数据 for(i=((oledByte-32)*16);i<((oledByte-32)*16+8);i++){ Oled_Write_Data(F8X16[i]); } //显示字符的下半部分 Oled_Write_Cmd(0xb0+(rows*2-1)); //选择行 //选择列 Oled_Write_Cmd(0x00+(columns&0x0f)); Oled_Write_Cmd(0x10+(columns>>4)); //显示数据 for(i=((oledByte-32)*16+8);i<((oledByte-32)*16+8+8);i++){ Oled_Write_Data(F8X16[i]); } } //OLED显示一个字符串 void Oled_Show_String(char rows,char columns,char *str) { while(*str != '\0'){ Oled_Show_Byte(rows,columns,*str); str++; columns += 8; } }
/*****pin.h*****/ #include <reg52.h> sbit A1_1 = P1^0; //麦轮分布:ABBA sbit A1_2 = P1^1; sbit A2_1 = P1^6; sbit A2_2 = P1^7; sbit B1_1 = P1^4; sbit B1_2 = P1^5; sbit B2_1 = P1^2; sbit B2_2 = P1^3; sbit speedA1 = P3^2; sbit speedB1 = P3^3; sbit SCL = P3^4; sbit SDA = P3^5; sbit RES = P3^6;
前边用过WIFi模块控制LED,它有两个模式,一个是当作客户端接入家里的路由器;另一个是它当作路由器,然后由别的设备当作客户端接入,这里采用第二种方式来控制小车。
这里我主要介绍用手机软件来进行连接小车WiFi模块的网络
代码实现:
/*****main.c*****/ #include "uart.h" #include "timer.h" #include "stdio.h" #include "oled.h" #include "esp8266.h" #include "delay.h" extern unsigned int speedB1; extern unsigned int speedA1; extern char signal; char speedMeg1[24]; char speedMeg2[24]; code char Send_Data[] = "AT+CIPSEND=0,34\r\n"; //这里如果字节开辟的不够大,串口会自动截取前边的数据进行发送 void main() { Timer0Init(); UART_Init(); Delay1000ms(); InitWifi(); Link_Client(); INT0_Init(); INT1_Init(); Oled_RST(); Oled_Init(); Oled_Clean(); while(1) { if(signal == 1) { SendString(Send_Data); Delay1000ms(); sprintf(speedMeg1,"speed_A1: %d cm/s",speedA1); SendString(speedMeg1); SendString("\r\n"); sprintf(speedMeg2,"speed_B1: %d cm/s",speedB1); SendString(speedMeg2); signal = 0; } Oled_Show_String(1,1,speedMeg1); Oled_Show_String(2,1,speedMeg2); } }
/*****uart.c*****/ #include "reg52.h" #include "string.h" #include "motor.h" #define SIZE 12 sfr AUXR = 0x8E; char buffer[SIZE]; extern char AT_OK_Flag; //发送AT指令返回标志 extern char Con_Client_Flag; //连接服务器成功标志 void UART_Init() { SCON = 0x50; //配置SCON为8位UART,波特率可变,REN允许串行接收 TMOD &= 0x0F; //定时器1高位清零,地位不变 TMOD |= 0x20; //定时器1采用8位重装计数 TL1 = 0xFD; TH1 = 0xFD; //9600波特率对应初值 TR1 = 1; //启动定时器1 AUXR = 0x01; //禁止ALE信号输出 EA = 1; //开启总中断 ES = 1; //开启串口中断 } void SendByte(char Senddata) { SBUF = Senddata; while(!TI); TI = 0; } void SendString(char *str) { while(*str != '\0') { SendByte(*str); str++; } } void UART_handler () interrupt 4 { static int i = 0; //静态变量,初始化执行一次 char tmp; if(RI) //如果接收到数据,硬件将RI自动置1,需要软件清0 { RI = 0; tmp = SBUF; if(tmp == 'M' || tmp == 'O' || tmp == '0') { i = 0; } buffer[i] = tmp; i++; if(buffer[0] == 'O' && buffer[1] == 'K') { AT_OK_Flag = 1; memset(buffer,'\0',SIZE); } if(buffer[0] == '0' && buffer[2] == 'C') { Con_Client_Flag = 1; memset(buffer,'\0',SIZE); } if(buffer[0] == 'M') { switch(buffer[1]) { case '1': Go_Forward(); break; case '2': Go_Back(); break; case '3': Turn_Left(); break; case '4': Turn_Right(); break; case '5': Left_Tran(); break; case '6': Right_Tran(); break; default: Car_Stop(); break; } } if(i == SIZE) { i = 0; memset(buffer,'\0',SIZE); } } }
/*****esp8266.c*****/ #include "uart.h" code char Double_Mode[] = "AT+CWMODE=2\r\n"; code char More_Con[] = "AT+CIPMUX=1\r\n"; code char Start_Ser[] = "AT+CIPSERVER=1\r\n"; char AT_OK_Flag = 0; char Con_Client_Flag = 0; void InitWifi() { SendString(Double_Mode); //配置双模模式 while(!AT_OK_Flag); AT_OK_Flag = 0; SendString(More_Con); //使能更多链接 while(!AT_OK_Flag); AT_OK_Flag = 0; } void Link_Client() { SendString(Start_Ser); //连接服务器 while(!Con_Client_Flag); AT_OK_Flag = 0; }
前边用4G模块控制LED,它的原理是家里使用的路由器的网络是局域网,外网不能访问,所以要有一个桥梁将局域网(内网)变成一个公网(外网)以供外网设备进行连接,可以借助花生壳软件进行内网穿透,然后外网设备就可以进行连接,最后由服务器去控制外网设备。
这里还是用手机软件来搭建一个服务器,然后用花生壳软件进行内网穿透,4G模块连接穿透后的IP就连接上了服务器,最后通过服务器发送指令就可以控制小车运动了,同时小车的速度还会实时传输到手机上。
注意:首先要建立TCP服务器,要不然花生壳软件会连接失败!!!
因为4G模块自己有心跳包,但我们这里要发送速度,为了避免造成影响,所以配置4G模块不发送心跳包。
代码没有改变(和蓝牙模块一样),4G模块主要是配置问题,配置好了它会自动进入透传模式。
语音小车用语音模块SU-03T,通过配置语音词条可以实现对小车的控制,
设置A25,A26,A27这三个引脚的初始状态都为高电平,然后通过配置三个引脚的电平状态来控制小车在避障、循迹、跟随、停止四种模式下切换。
最后将这三个引脚与单片机进行连接来控制小车的状态
代码实现:
/*****main.c*****/ #include "SG90.h" #include "HC04.h" #include "delay.h" #include "oled.h" #include "mode.h" #include "pin.h" #include "motor.h" #define Avoid 1 #define Follow 2 #define Track 3 #define Stop 4 extern char dir; void main() { int mark = 0; Timer0Init(); Timer1Init(); sgMiddle(); Delay300ms(); Delay300ms(); Oled_RST(); Oled_Init(); Oled_Clean(); Oled_Show_String(2,5,"****Ready****"); while(1) { if(A25 == 0 && A26 == 1 && A27 == 1) { if(mark != Track) //如果第二次循环进来还是循迹模式,那就不执行清屏函数,避免它一直刷屏 { Oled_Clean(); Oled_Show_String(2,1,"**Track_Mode**"); } Track_Mode(); mark = Track; } if(A25 == 1 && A26 == 0 && A27 == 1) { if(mark != Follow) { Oled_Clean(); Oled_Show_String(2,1,"**Follow_Mode**"); } Follow_Mode(); mark = Follow; } if(A25 == 1 && A26 == 1 && A27 == 0) { if(mark != Avoid) { Oled_Clean(); Oled_Show_String(2,1,"***Avoid_Mode***"); } Avoid_Mode(); mark = Avoid; } if(A25 == 0 && A26 == 0 && A27 == 0) { if(mark != Stop) { Oled_Clean(); Oled_Show_String(2,1,"***Stop_Mode***"); } Car_Stop(); mark = Stop; } } }
/*****mode.c*****/ #include "pin.h" #include "SG90.h" #include "HC04.h" #include "motor.h" #include "delay.h" double disMiddle; double disLeft; double disRight; void Avoid_Mode() { disMiddle = Get_distance(); if(disMiddle > 30) { Go_Forward();//前进 } else if(disMiddle < 10) { Go_Back();//后退 // Delay200ms(); // Car_Stop(); } else { Car_Stop(); sgLeft(); //测左边距离 Delay300ms(); disLeft = Get_distance(); sgMiddle(); Delay300ms(); sgRight(); //测右边距离 Delay300ms(); disRight = Get_distance(); if(disLeft < disRight) { Turn_Left(); Delay150ms(); Car_Stop(); } if(disRight < disLeft) { Turn_Right(); Delay150ms(); Car_Stop(); } } sgMiddle(); Delay300ms(); } void Track_Mode() { if(Lefttrack == 0 && Righttrack == 0) { Go_Forward(); } if(Lefttrack == 1 && Righttrack == 0) { Turn_Left(); } if(Lefttrack == 0 && Righttrack == 1) { Turn_Right(); } if(Lefttrack == 1 && Righttrack == 1) { Car_Stop(); } } void Follow_Mode() { if(Leftfollow == 0 && Rightfollow == 0) { Go_Forward(); } if(Leftfollow == 0 && Rightfollow == 1) { Turn_Left(); } if(Leftfollow == 1 && Rightfollow == 0) { Turn_Right(); } if(Leftfollow == 1 && Rightfollow == 1) { Car_Stop(); } }
显示效果:视频发到抖音去了(DX_dingding)
小车项目中遇到的问题:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。