这段时间在玩游戏的时候将我许久未用的游戏手柄拿了出来,玩着玩着突发奇想,能不能用单片机来接收游戏手柄的信息之后操控许多事情,例如利用游戏手柄来控制智能小车等,记得前段时间做的播种小车,利用的是手机APP控制,控制起来总是觉得有所别扭,缺乏感觉。
而且现在基本上都是介绍单片机做成手柄的,很少有介绍一个手柄如何连接上单片机。( ** 视频在文末 ** )
我的游戏手柄支持蓝牙/USB/2.4G连接,当然连接到单片机上那么我觉得最好而且最容易实现的当然是蓝牙,查阅资料得知,游戏手柄基本上是BLE蓝牙即低功耗蓝牙,那么我就先介绍一下什么是低功耗蓝牙。
BLE简介
BLE(Bluetooth Low
Energy),也称为蓝牙低功耗,是一种无线个人区域网络技术,用于短距离数据交换。它在经典蓝牙的基础上进行了优化,专注于降低能耗,同时仍然提供足够的通信性能。
它具有非常低的电量消耗,并且可以在非常短的时间内进行连接和数据传输。不过也有很多缺点,包括通讯距离短,适合近距离的无线交互。
BLE蓝牙通讯特点
BLE通讯主要由 广播(Advertising)和连接(Connection) 两部分组成。
广播是BLE设备宣布其存在并传递少量数据的一种方式,主要有Advertising Packets(包含设备的基本信息,如设备名称、服务UUID等)和Scan
Response Packets(设备在接收到扫描请求时返回的额外信息)。
广播包结构包括Preamble(1字节,固定为0xAA),Access
Address(4字节,广播信道的地址,固定为0x8E89BED6),PDU(可变长度,包含实际的数据),和CRC(3字节,用于校验数据完整性)。
连接建立后,数据通过连接事件(Connection
Events)进行传输,每个连接事件包括主设备(Central)和从设备(Peripheral)之间的一次完整的数据交换。
连接事件结构包括Header(包含包类型和长度信息),LL Data(链路层数据,包括控制包或用户数据),和MIC(用于确保数据完整性和防止篡改)。
数据通过预定义的服务和特性(Characteristic)进行传输,每个特性都有一个唯一的UUID(通用唯一标识符)。
服务是一个逻辑功能单元,包含多个特性,例如心率服务包含测量值和传感器位置等特性;
特性是一个具体的数据单元,包括一个值和可选的描述符(提供关于特性值的额外信息,如格式、范围等)。
如果比较难明白的话,我们直接用手机的BLE调试助手连接我的手柄查看具体的。
一个BLE蓝牙设备会向外广播信息,而我们就需要知道特定的服务UUID和特性UUID来获取信息,例如这里我知道我需要的服务UUID如下。
特性UUID如下,这样子我就可以接收到设备发出的信息了,这些信息包括了手柄的摇杆,各个按键状态信息等等。
单片机连接手柄
我们首先需要一款能够支持BLE的单片机或者模块,这里我选择使用ESP32,它可以连接BLE设备并且可以对信息处理。代码不多做赘述,到时候贴在文末,只说几个部分。
使用的ESP32库自带的BLE库,设备地址是一开始扫描的,这个相信大家有很多办法获取MAC.
class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pClient) { Serial.println("Connected to the target device."); }
void onDisconnect(BLEClient* pClient) { Serial.println("Disconnected from the target device."); }};
这段是BLE蓝牙设备连接的回调函数。
设置服务UUID和特性UUID,以及设置好接收到通知的回调函数,之后就可以尝试连接了,我们接收到的数据是20个字节长度的数据,包含了摇杆位置信息,手柄上各个按键的信息,我们只需要将其转码我们所需要的数据,就可以实现手柄的信息的提取。
#include "BLEDevice.h"#include <Wire.h>
BLEClient* pClient;BLERemoteService* pRemoteService;BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAddress targetAddress("a4:c1:38:91:43:57"); // 替换为目标设备的MAC地址
int Lx,Ly;
class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pClient) { Serial.println("Connected to the target device."); }
void onDisconnect(BLEClient* pClient) { Serial.println("Disconnected from the target device."); }};
void setup() { Serial.begin(115200); Serial.println("Starting BLE client...");
BLEDevice::init("");
// 创建客户端 pClient = BLEDevice::createClient(); pClient->setClientCallbacks(new MyClientCallback());
// 尝试连接目标设备 if (pClient->connect(targetAddress)) { Serial.println("Connected to device."); } else { Serial.println("Failed to connect."); return; }
// 获取远程服务 pRemoteService = pClient->getService("00001812-0000-1000-8000-00805f9b34fb"); // 替换为目标设备的服务UUID if (pRemoteService == nullptr) { Serial.print("Failed to find service UUID: "); Serial.println("SERVICE_UUID"); pClient->disconnect(); return; } Serial.println("Found the service.");
// 获取远程特性 pRemoteCharacteristic = pRemoteService->getCharacteristic("00002a4d-0000-1000-8000-00805f9b34fb"); // 替换为目标设备的特性UUID if (pRemoteCharacteristic == nullptr) { Serial.print("Failed to find characteristic UUID: "); Serial.println("CHARACTERISTIC_UUID"); pClient->disconnect(); return; } Serial.println("Found the characteristic.");
// 读取特性值 if (pRemoteCharacteristic->canRead()) { std::string value = pRemoteCharacteristic->readValue(); Serial.print("Characteristic value: "); Serial.println(value.c_str()); }
// 设置特性通知 if (pRemoteCharacteristic->canNotify()) { pRemoteCharacteristic->registerForNotify(notifyCallback); }
}
void loop() {
}
// 通知回调函数void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { // Serial.print("Notify callback for characteristic: "); // Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); // Serial.print(" of data length "); // Serial.println(length); // Serial.print("data: "); //Serial.write(pData, length); //Serial.println(); Lx = pData[0]; Ly = pData[1]; Serial.print("x:"+String(Lx)); Serial.println(",y:"+String(Ly));}