PS4手柄控制无人驾驶小车车

系列文章目录

提示:
第一章 基于索尼PS4手柄的遥控器双向控制



前言

随着人工智能的不断发展,做无人驾驶调试肯定需要一个遥控器来调试,对于价值比较大的无人驾驶车辆会有专业的遥控器控制,但是对于刚入门的小白可能没有那么多机会接触高端无人驾驶设备,这里另辟蹊径用打游戏的手柄进行控制。


提示:以下是本篇文章正文内容,下面案例可供参考

一、PS手柄是什么?

示例:PS系列手柄 是索尼提供的游戏机的手柄,该工具是为了解决游戏玩家设计的。

二、一般如何用PS手柄?

1.案例一

之前我用一个PS3做小车双向控制,优点是通过修改硬件把蓝牙换成了Lora,实现远距离双向互传。我的应用场景之一:可以通过ROS发送相关反馈给手柄,比如震动几下等等,这样在机器人调试过程中可以模拟车辆在ROS或者真实小车发生碰撞的情况下让手柄震动给开发者人员。
同时,手柄会把按键的模拟量和键值数据发送给通过无线传输发送给ROS,进而控制小车。

但是也有美中不足的事情:
1.PS3实在太丑了,,相比PS4
2.PS4多了RGB灯,所以可以反向反馈灯光,PS3则只能反馈震动
3.PS4内部有陀螺仪,但是由于我买的国产的,内部IMU芯片没有焊接,ε=(´ο`*)))4.重要的事情再说一遍,PS4还是比PS3帅啊

2.常见的使用方法

常见使用蓝牙与工控机这里我用的是NUC11,配准是11代i71185G7,16G内存,系统安装的是Ubuntu16.04:

sudo apt-get install jstest-gtk
sudo jstest /dev/input/js0

然后在PS4手柄与工控机连接前后对比/dev/input/下面你会看到多出5个文件,分别是一个js0、event*/event*+1、event*+2和一个mouse,这三个分别对应jstest通过JavaScript解析后的键值,和三个未解析的键值,好像有一个是IMU数据,因为一直在发送数据,但是我的PS4是国产的没有IMU。。。另外一个是鼠标数据,pyPS4Controller 库,然后就是再安装这个库在Ubuntu上开发。

pip install pyPS4Controller

优点:不需要额外接收器,成本低直接可以在Linux系统上开发
缺点:1.不灵活 2.很难做到反向控制手柄的灯效和震动等等,很难做ROS与手柄的双向互动,目前只能拿到手柄的键值数据单向控制小车车。

该处使用的url网络请求的数据。


三、!!!我的解决方案

1.首先要了解PS4与工控机的通信机制–蓝牙

蓝牙通讯具有两种工作模式:命令响应工作模式和自动
连接工作模式,在自动连接工作模式下模块又可分为主(Master)、从(Slave)和回环(Loopback)三种工作角色。当模块处于自动连接工作模式时,将自动根据事先设定的方式连接的数据传输。

通过两天的摸索:
1.SPP 规范库(Initialisethesppprofilelib)在第一次配对前需要初始化(这个是蓝牙规范,不需要看懂)
2.—查询设备类为 0x1f1f的蓝牙设备,也是蓝牙规范,为此我拆解了PS4,通过一些设备和方法得到一些信息,PS4的蓝牙芯片是用的是RDA5850的芯片,通过查询数据手册看到这是一个多媒体蓝牙芯片,感觉集成化程度很高啊,不管了我们首先要做的是自己做个蓝牙接收器能够连接上这个PS4,我给出两种方案:
1.单片机+蓝牙模块(双模蓝牙才OK)
4.集成单片机的蓝牙芯片,esp8266或者esp32

PS4控制板背面紫光的多媒体蓝牙芯片

2.让PS4连接上蓝牙

在连接蓝牙模块之前我们先连接一次手机,看一下这个PS4的MAC地址是多少我通过一个HC蓝牙助手的安卓app看到的
红色画圈的地方就是我的PS4的蓝牙MAC
下面是PS4的反馈代码,用VSCODE就可以开发PS4接受模组了

#include "PS4Controller.h"

#include <esp_bt_defs.h>
#include <esp_bt_main.h>

extern "C" {
#include "ps4.h"
}

#define ESP_BD_ADDR_HEX_PTR(addr) 
  (uint8_t*)addr + 0, (uint8_t*)addr + 1, (uint8_t*)addr + 2, 
  (uint8_t*)addr + 3, (uint8_t*)addr + 4, (uint8_t*)addr + 5

PS4Controller::PS4Controller() {}

bool PS4Controller::begin() {
  ps4SetEventObjectCallback(this, &PS4Controller::_event_callback);
  ps4SetConnectionObjectCallback(this, &PS4Controller::_connection_callback);

  if (!btStarted() && !btStart()) {
    log_e("btStart failed");
    return false;
  }

  esp_bluedroid_status_t btState = esp_bluedroid_get_status();
  if (btState == ESP_BLUEDROID_STATUS_UNINITIALIZED) {
    if (esp_bluedroid_init()) {
      log_e("esp_bluedroid_init failed");
      return false;
    }
  }

  if (btState != ESP_BLUEDROID_STATUS_ENABLED) {
    if (esp_bluedroid_enable()) {
      log_e("esp_bluedroid_enable failed");
      return false;
    }
  }

  ps4Init();
  return true;
}

#define ESP_BD_ADDR_STR         "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"
bool PS4Controller::begin(const char* mac) {
  esp_bd_addr_t addr;
    
  if (sscanf(mac, ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX_PTR(addr)) != ESP_BD_ADDR_LEN) {
    log_e("Could not convert %sn to a MAC address", mac);
    return false;
  }

  ps4SetBluetoothMacAddress(addr);

  return begin();
}

void PS4Controller::end() {}

bool PS4Controller::isConnected() { return ps4IsConnected(); }
//下面这个函数是通过上位机发送指令控制PS4手柄的RGB灯颜色的
void PS4Controller::setLed(uint8_t r, uint8_t g, uint8_t b) {
  output.r = r;
  output.g = g;
  output.b = b;
}
//下面这个函数是通过上位机发送指令控制PS4手柄的震动的基础库
void PS4Controller::setRumble(uint8_t small, uint8_t large) {
  output.smallRumble = small;
  output.largeRumble = large;
}

void PS4Controller::setFlashRate(uint8_t onTime, uint8_t offTime) {
  output.flashOn = onTime / 10;
  output.flashOff = offTime / 10;
}

void PS4Controller::sendToController() { ps4SetOutput(output); }

void PS4Controller::attach(callback_t callback) { _callback_event = callback; }

void PS4Controller::attachOnConnect(callback_t callback) {
  _callback_connect = callback;
}

void PS4Controller::attachOnDisconnect(callback_t callback) {
  _callback_disconnect = callback;
}

void PS4Controller::_event_callback(
  void* object, ps4_t data, ps4_event_t event) {
  PS4Controller* This = (PS4Controller*)object;

  memcpy(&This->data, &data, sizeof(ps4_t));
  memcpy(&This->event, &event, sizeof(ps4_event_t));

  if (This->_callback_event) {
    This->_callback_event();
  }
}

void PS4Controller::_connection_callback(void* object, uint8_t isConnected) {
  PS4Controller* This = (PS4Controller*)object;

  if (isConnected) {
    delay(250);  // ToDo: figure out how to know when the channel is free again
                 // so this delay can be removed

    if (This->_callback_connect) {
      This->_callback_connect();
    }
  }
  else {
    if (This->_callback_disconnect) {
      This->_callback_disconnect();
    }
  }
}

#if !defined(NO_GLOBAL_INSTANCES)
PS4Controller PS4;
#endif

然后在PS4手柄与工控机连接前后对比/dev/input/下面你会看到多出5个文件,分别是一个js0、event*/event*+1、event*+2和一个mouse,这三个分别对应jstest通过JavaScript解析后的键值,和三个未解析的键值,好像有一个是IMU数据,因为一直在发送数据,但是我的PS4是国产的没有IMU。。。另外一个是鼠标数据,pyPS4Controller 库,然后就是再安装这个库在Ubuntu上开发。

#include "ps4.h"

#include <esp_system.h>
#include <string.h>

#include "ps4_int.h"

/********************************************************************************/
/*                              PS4接受数据的常量部分 */
/********************************************************************************/

static const uint8_t hid_cmd_payload_ps4_enable[] = {0x43, 0x02};

/********************************************************************************/
/*                        本地变量 */
/********************************************************************************/

static ps4_connection_callback_t ps4_connection_cb = NULL;
static ps4_connection_object_callback_t ps4_connection_object_cb = NULL;
static void* ps4_connection_object = NULL;

static ps4_event_callback_t ps4_event_cb = NULL;
static ps4_event_object_callback_t ps4_event_object_cb = NULL;
static void* ps4_event_object = NULL;

static bool is_active = false;

/********************************************************************************/
/*                     下面这些是基础功能 */
/********************************************************************************/

/*******************************************************************************
**
** Function         ps4Init
**
** Description      初始化蓝牙接受服务,然后连接手柄
**
**
** Returns          void
**
*******************************************************************************/
void ps4Init() {
  sppInit();
  ps4_l2cap_init_services();
}

/*******************************************************************************
**
** Function         ps4IsConnected
**
** Description      当两个设备之间成功发生握手后返回一个值
**                  
**
**
** Returns          bool
**
*******************************************************************************/
bool ps4IsConnected() { return is_active; }

/*******************************************************************************
**
** Function         ps4Enable
**
** Description      PS4使能后持续发送数据
**                  
**
**
** Returns          void
**
*******************************************************************************/
void ps4Enable() {
  uint16_t length = sizeof(hid_cmd_payload_ps4_enable);
  hid_cmd_t hidCommand;

  hidCommand.code = hid_cmd_code_set_report | hid_cmd_code_type_feature;
  hidCommand.identifier = hid_cmd_identifier_ps4_enable;

  memcpy(hidCommand.data, hid_cmd_payload_ps4_enable, length);

  ps4_l2cap_send_hid(&hidCommand, length);
  ps4SetLed(32, 32, 200);
}

/*******************************************************************************
**
** Function         ps4Cmd
**
** Description      发送给PS4让其震动或者灯光的控制的命令
**
**
** Returns          void
**
*******************************************************************************/
void ps4Cmd(ps4_cmd_t cmd) {
  hid_cmd_t hidCommand = {.data = {0x80, 0x00, 0xFF}};
  uint16_t length = sizeof(hidCommand.data);

  hidCommand.code = hid_cmd_code_set_report | hid_cmd_code_type_output;
  hidCommand.identifier = hid_cmd_identifier_ps4_control;

  hidCommand.data[ps4_control_packet_index_small_rumble] = cmd.smallRumble;  // Small Rumble
  hidCommand.data[ps4_control_packet_index_large_rumble] = cmd.largeRumble;  // Big rumble

  hidCommand.data[ps4_control_packet_index_red] = cmd.r;    // Red
  hidCommand.data[ps4_control_packet_index_green] = cmd.g;  // Green
  hidCommand.data[ps4_control_packet_index_blue] = cmd.b;   // Blue

  // Time to flash bright (255 = 2.5 seconds)
  hidCommand.data[ps4_control_packet_index_flash_on_time] = cmd.flashOn;
  // Time to flash dark (255 = 2.5 seconds)
  hidCommand.data[ps4_control_packet_index_flash_off_time] = cmd.flashOff;

  ps4_l2cap_send_hid(&hidCommand, length);
}

/*******************************************************************************
**
** Function         ps4SetLedOnly
**
** Description      设置灯光颜色RGB三原色控制的数据
**
**
** Returns          void
**
*******************************************************************************/
void ps4SetLed(uint8_t r, uint8_t g, uint8_t b) {
  ps4_cmd_t cmd = {0};

  cmd.r = r;
  cmd.g = g;
  cmd.b = b;

  ps4Cmd(cmd);
}

/*******************************************************************************
**
** Function         ps4SetOutput
**
** Description      设置回调
**
**
** Returns          void
**
*******************************************************************************/
void ps4SetOutput(ps4_cmd_t prevCommand) { ps4Cmd(prevCommand); }

/*******************************************************************************
**
** Function         ps4SetConnectionCallback
**
** Description      当PS4发送一次手柄键值给接收机时候,进行一次中断处理的回调
**                  
**
**
** Returns          void
**
*******************************************************************************/
void ps4SetConnectionCallback(ps4_connection_callback_t cb) {
  ps4_connection_cb = cb;
}

/*******************************************************************************
**
** Function         ps4SetConnectionObjectCallback
**
** Description      当PS4连接的时候进行连接通知回调
**                  
**
**
** Returns          void
**
*******************************************************************************/
void ps4SetConnectionObjectCallback(void* object, ps4_connection_object_callback_t cb) {
  ps4_connection_object_cb = cb;
  ps4_connection_object = object;
}

/*******************************************************************************
**
** Function         ps4SetEventCallback
**
** Description      接收PS4的回调事件
**
**
** Returns          void
**
*******************************************************************************/
void ps4SetEventCallback(ps4_event_callback_t cb) { ps4_event_cb = cb; }

/*******************************************************************************
**
** Function         ps4SetEventObjectCallback
**
** Description      PS4接受控制事件
**
**
** Returns          void
**
*******************************************************************************/
void ps4SetEventObjectCallback(void* object, ps4_event_object_callback_t cb) {
  ps4_event_object_cb = cb;
  ps4_event_object = object;
}

/*******************************************************************************
**
** Function         ps4SetBluetoothMacAddress
**
** Description      设置接收机需要连接的PS4手柄位移MAC地址
**
**
** Returns          void
**
*******************************************************************************/
void ps4SetBluetoothMacAddress(const uint8_t* mac) {
  // The bluetooth MAC address is derived from the base MAC address
  // https://docs.espressif.com/projects/esp-idf/en/stable/api-reference/system/system.html#mac-address
  uint8_t baseMac[6];
  memcpy(baseMac, mac, 6);
  baseMac[5] -= 2;
  esp_base_mac_addr_set(baseMac);
}

/********************************************************************************/
/*                     本地化的一些功能 */
/********************************************************************************/

void ps4ConnectEvent(uint8_t is_connected) {
    if (is_connected) {
        ps4Enable();
    } else {
        is_active = false;
    }
}


void ps4PacketEvent(ps4_t ps4, ps4_event_t event) {
    // Trigger packet event, but if this is the very first packet
    // after connecting, trigger a connection event instead
    if (is_active) {
        if(ps4_event_cb != NULL) {
            ps4_event_cb(ps4, event);
        }

        if (ps4_event_object_cb != NULL && ps4_event_object != NULL) {
            ps4_event_object_cb(ps4_event_object, ps4, event);
        }
    } else {
        is_active = true;

        if(ps4_connection_cb != NULL) {
            ps4_connection_cb(is_active);
        }

        if (ps4_connection_object_cb != NULL && ps4_connection_object != NULL) {
            ps4_connection_object_cb(ps4_connection_object, is_active);
        }
    }
}

等我完成PS4的回环流程后再重新组织架构文章。


总结

提示:这里对文章进行总结:
目的:使用PS4连接小车,可以遥控控制一些开关或者其他,当小车在ROS里发生碰撞会反馈给手柄发生碰撞震动,ROS小车的电量或者一些状态通过PS4的RGB灯光进行反馈,实时可以看得到。
完成度:50%,后面开发完成了再整理一下发出来。
祝大家元旦快乐,2022年元旦。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>