这是大三上传感器大作业做的一个主从机控制小车。期间参考了网上的一些博客,涉及主从机通信、蓝牙配置、传感器的使用等。
一、实物图片
二、系统架构
本次实验涉及较多外设,我们在IDE中编写相关代码,控制小车的前进后退等运动状态。我们使用了两块Arduino UNO,通过蓝牙模块,以短距离低成本的方式进行数据传输来实现主从机的通信。主机采集手势传感器数据并通过I2C传输到从机,当从机接收到主机发来的信号时会进行判断,驱动电机的转动,来确定小车下一步的运动状态。
图3 系统改造架构图
三、主要外设
(1)PAJ7620手势传感器
图4 手势传感器
PAJ7260手势传感器将手势识别功能与通用的I²C接口转换成单片机构成图像分析系统,它可以识别上、下、左、右、前、后、顺时针、逆时针、挥手9种手势,同时它还内置了近距离检测,这款传感器在节电上也有很大的灵活性。
(2)HC01蓝牙模块
图5 蓝牙模块
HC01模块是一款高性能主机一体化蓝牙串口模块,集成蓝牙功能的PCBA板,用于短距离低成本的通信。
(3)L298N电机驱动模块
图6 L298N电机驱动模块
L298N电机驱动模块通过ENA,IN1,IN2,IN3,IN4,ENB六个pin脚的输入,来控制两个电机的转动及转动速度,同时承担Arduino UNO的供电。电机的转速,通过输出PWM来控制,以此完成小车速度的改变,及转弯的实现,大大提高了小车的灵活性。
四、手势控制对应关系
五、应用场景与待改进
我们最初设计此项目用于水下远程遥控作业,比如通过加装摄像头进行水底的探查和搜救,通过岸上操纵主机对从机的运动进行控制,但考虑到实际情况我们选择了陆上的小车来模拟对应功能。本次实验我们仅通过远程遥控进行小车的控制,实际上我们最初规划的还有几种自动运动模式,如循迹模式、超声波避障模式,通过不同手势触发对应的工作模式,可以随时进行模式切换,实现多功能集成。
六、参考文章
【蓝牙配置】
https://blog.csdn.net/Marilynmontu/article/details/81532503
【毕业设计 —— 基于STM32手势控制显示系统的设计】
https://blog.csdn.net/fightingboom/article/details/102990024
【PAJ7620U2+蓝牙主从机实现远程手势控制】
https://blog.csdn.net/qqliuzhitong/article/details/118893516
七、代码实现
【传感器端 】发出控制信号,通过蓝牙传输到从机控制车辆运行。
#include <Wire.h>
#include "paj7620.h"
#include<SoftwareSerial.h>
//新建一个Serial_p对象,rx:6,tx:5
//连接蓝牙的master
SoftwareSerial Serial_p(6,5);
#define GES_REACTION_TIME 500 // You can adjust the reaction time according to the actual circumstance.
#define GES_ENTRY_TIME 800 // When you want to recognize the Forward/Backward gestures, your gestures' reaction time must less than GES_ENTRY_TIME(0.8s).
#define GES_QUIT_TIME 1000
//上:a 下:b 左:c 右:d 前:e
char command_char='e';
void setup()
{
uint8_t error = 0;
Serial.begin(9600);
Serial_p.begin(9600);
Serial.println("Serial baud:9600");
Serial_p.println("Serial baud:9600");
Serial.println("Underwater fish master program!");
error = paj7620Init(); // initialize Paj7620 registers
if (error)
{
Serial.print("INIT ERROR,CODE:");
Serial.println(error);
}
else
{
Serial.println("INIT OK");
}
Serial.println("Please input your gestures:\n");
}
void loop()
{
uint8_t data = 0, data1 = 0, error;
error = paj7620ReadReg(0x43, 1, &data); // Read Bank_0_Reg_0x43/0x44 for gesture result.
if (!error)
{
switch (data) // When different gestures be detected, the variable 'data' will be set to different values by paj7620ReadReg(0x43, 1, &data).
{
case GES_RIGHT_FLAG:
delay(GES_ENTRY_TIME);
paj7620ReadReg(0x43, 1, &data);
if(data == GES_FORWARD_FLAG)
{
Serial.println("Forward");
Serial_p.print('e');
delay(GES_QUIT_TIME);
}
else if(data == GES_BACKWARD_FLAG)
{
Serial.println("Backward");
delay(GES_QUIT_TIME);
}
else
{
Serial.println("Right");
Serial_p.print('d');
}
break;
case GES_LEFT_FLAG:
delay(GES_ENTRY_TIME);
paj7620ReadReg(0x43, 1, &data);
if(data == GES_FORWARD_FLAG)
{
Serial.println("Forward");
Serial_p.print('e');
delay(GES_QUIT_TIME);
}
else if(data == GES_BACKWARD_FLAG)
{
Serial.println("Backward");
delay(GES_QUIT_TIME);
}
else
{
Serial.println("Left");
Serial_p.print('c');
}
break;
case GES_UP_FLAG:
delay(GES_ENTRY_TIME);
paj7620ReadReg(0x43, 1, &data);
if(data == GES_FORWARD_FLAG)
{
Serial.println("Forward");
Serial_p.print('e');
delay(GES_QUIT_TIME);
}
else if(data == GES_BACKWARD_FLAG)
{
Serial.println("Backward");
delay(GES_QUIT_TIME);
}
else
{
Serial.println("Up");
Serial_p.print('a');
}
break;
case GES_DOWN_FLAG:
delay(GES_ENTRY_TIME);
paj7620ReadReg(0x43, 1, &data);
if(data == GES_FORWARD_FLAG)
{
Serial.println("Forward");
Serial_p.print('e');
delay(GES_QUIT_TIME);
}
else if(data == GES_BACKWARD_FLAG)
{
Serial.println("Backward");
delay(GES_QUIT_TIME);
}
else
{
Serial.println("Down");
Serial_p.print('b');
}
break;
case GES_FORWARD_FLAG:
Serial.println("Forward");
Serial_p.print('e');
delay(GES_QUIT_TIME);
break;
case GES_BACKWARD_FLAG:
Serial.println("Backward");
delay(GES_QUIT_TIME);
break;
case GES_CLOCKWISE_FLAG:
Serial.println("Clockwise");
break;
case GES_COUNT_CLOCKWISE_FLAG:
Serial.println("anti-clockwise");
break;
default:
paj7620ReadReg(0x44, 1, &data1);
if (data1 == GES_WAVE_FLAG)
{
Serial.println("wave");
}
break;
}
}
delay(100);
}
【小车端】改自循迹代码,但由于时间精力有限,疫情导致教学周期缩短,就没有仔细写速度控制等代码。仅供参考。
#include<SoftwareSerial.h>
SoftwareSerial Serial_p(12, 13); //新建一个Serial_p对象,rx:12,tx:13, 连接蓝牙的master
char command_char = 'e'; //蓝牙指令 a-前进-UP b-掉头-DOWN c-左转-Left d-右转-Right e-停止
char command_char_last = 'e';
#define IN1 2
#define IN2 4
#define IN3 7
#define IN4 5
#define ENA 3
#define ENB 6
#define SPEED_LINE 80 //直线速度
#define SPEED_ADJUST_LOW 0 //调整速度,低速一侧
#define SPEED_ADJUST_HIGH 80 //调整速度,高速一侧
#define SPEED_TURN_LOW -50 //转弯速度,低速一侧
#define SPEED_TURN_HIGH 100 //转弯速度,高速一侧
#define SPEED_CYCLE 100 //直角弯转圈速度
#define SPEED_TRIG 200 //制动速度
#define TIME_TRIG 10 //制动时间,单位ms
#define TIME_STOP 500 //静止时间,单位ms
#define TIME_ADJUST 1 //小弯调整时间,单位ms
#define TIME_TURN_MAX 3000 //转弯最大时间,单位ms
#define TIME_CYCLE_MAX 5000 //旋转最大时间,单位ms
//速度误差值,以左轮为速度基准,小车跑直线时右轮速度误差,得意跑直线时实际设置的R-L
//12个数组分别为-240,-200,-160,-120,-80,-40,40,80,120,160,200,240时的误差
static int motor_offset[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int t = 0; //延时计时时间
int speed_l = 0, speed_r = 0; //当前小车左右轮的设置速度
void Move(int L, int R);
void setup() {
// put your setup code here, to run once:
//串口初始化
Serial.begin(9600);
Serial_p.begin(9600);
//管脚初始化
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(ENA, OUTPUT);
pinMode(ENB, OUTPUT);
// GO('e');
}
void loop() {
if (Serial_p.available() > 0)
{
// Serial.print(Serial_p.read());
// Serial.println(type(Serial_p.read());
command_char = Serial_p.read();
GO(command_char);
}
}
/**
函数功能:信号选择
入口参数:蓝牙指令
返回参数:
*/
void GO(char x)
{
switch (x) {
//直行
case 'a':
Move(SPEED_LINE, SPEED_LINE);
Serial.println("Go Stright!");
delay(3000);
break;
case 'b':
Move(-SPEED_LINE, -SPEED_LINE);
Serial.println("Go Stright!");
delay(3000);
break;
//左转
case 'c':
//让小车静止下来,保证小车旋转时不受之前的轮子速度影响
if (speed_l > 0 && speed_r > 0)
Move(-SPEED_TRIG, -SPEED_TRIG);
if (speed_l > 0 && speed_r < 0)
Move(SPEED_TRIG, -SPEED_TRIG);
if (speed_l > 0 && speed_r < 0)
Move(-SPEED_TRIG, SPEED_TRIG);
delay(TIME_TRIG);
Move(0, 0);
delay(TIME_STOP);
t = 0;
while (t++ < TIME_CYCLE_MAX) //旋转直到只有左边第二个灯检测到黑线或超时
Move(SPEED_ADJUST_HIGH, SPEED_ADJUST_LOW);
delay(1);
Move(0, 0);
Serial.println("Turn Left!");
break;
//右转
case 'd':
//让小车静止下来,保证小车旋转时不受之前的轮子速度影响
if (speed_l > 0 && speed_r > 0)
Move(-SPEED_TRIG, -SPEED_TRIG);
else if (speed_l > 0 && speed_r < 0)
Move(SPEED_TRIG, -SPEED_TRIG);
else if (speed_l > 0 && speed_r < 0)
Move(-SPEED_TRIG, SPEED_TRIG);
else if (speed_l < 0 && speed_r < 0)
Move(SPEED_TRIG, -SPEED_TRIG);
delay(TIME_TRIG);
Move(0, 0);
delay(TIME_STOP);
//原地旋转
t = 0;
while (t++ < TIME_CYCLE_MAX) //旋转直到只有右边第二个灯检测到黑线或超时
Move(SPEED_ADJUST_LOW, SPEED_ADJUST_HIGH);
delay(1);
Move(0, 0);
Serial.println("Turn Right!");
break;
case 'e':
//让小车静止下来,保证小车旋转时不受之前的轮子速度影响
if (speed_l > 0 && speed_r > 0)
Move(-SPEED_TRIG, -SPEED_TRIG);
else if (speed_l > 0 && speed_r < 0)
Move(SPEED_TRIG, -SPEED_TRIG);
else if (speed_l > 0 && speed_r < 0)
Move(-SPEED_TRIG, SPEED_TRIG);
else if (speed_l < 0 && speed_r < 0)
Move(SPEED_TRIG, -SPEED_TRIG);
delay(TIME_TRIG);
Move(0, 0);
delay(TIME_STOP);
Serial.println("Stop!");
break;
default:
delay(1);
}
}
/**
函数功能:设置小车的两个轮子转速
入口参数:L左轮输出速度,范围0~255
入口参数:R右轮输出速度,范围0~255
返回参数:无
*/
void Move(int L, int R) {
int area, remainder; //区间,余数
//记录设置速度,制动时使用
speed_l = L;
speed_r = R;
//左轮方向设置
if (L > 0) {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
}
else if (L < 0) {
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
} else {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
}
//右轮方向设置
if (R > 0) {
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
else {
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
//速度计算
area = R / 40;
remainder = R % 40;
if (R > 0 && R <= 40)
R = R - motor_offset[6];
else if (R > 40 && R < 240)
R = R - (motor_offset[area + 6] + (motor_offset[area + 6 + 1] - motor_offset[area + 6]) * remainder / 40);
else if (R >= 240)
R = R - motor_offset[11];
else if (R < 0 && R >= -40)
R = R - motor_offset[5];
else if (R < -40 && R > -240)
R = R - (motor_offset[area + 6] + (motor_offset[area + 6 - 1] - motor_offset[area + 6]) * (-remainder) / 40);
else if (R <= -240)
R = R - motor_offset[0];
else
R = 0;
L = abs(L);
R = abs(R);
if (L > 0xff) L = 0xff;
if (R > 0xff) R = 0xff;
//速度设置
analogWrite(ENA, L);
analogWrite(ENB, R);
}