基于PAJ7620的主从机通信远程控制蓝牙智能小车

这是大三上传感器大作业做的一个主从机控制小车。期间参考了网上的一些博客,涉及主从机通信、蓝牙配置、传感器的使用等。

一、实物图片

在这里插入图片描述

二、系统架构

本次实验涉及较多外设,我们在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);
}

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿知

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值