如何使用MPU-6050芯片来实现一个重力感应游戏

需求分析

功能需求

本项目以树莓派作为输入设备,在功能上需要能实现树莓派与UE4引擎的交互,这里作者采用的是使用UDP协议进行数据传输。因为游戏要求输入设备具有实时性,而且由于数据是逐帧传输,数据量非常庞大,少许数据的丢失对玩家产生不了太大影响,因此本项目采用了更具效率的UDP协议。

其次,本项目要求树莓派具有重力感应功能,这里作者使用了MPU-6050芯片来实现这个功能。

开发环境需求

网络通信结构

img

图 1 系统通信模型

电子元件连接

img

图 2 线路连接示意图

img

图 3 连接测试示意图

img

图 4 实物图

树莓派上的程序开始运行后会依次执行下列逻辑:

  1. 初始化MPU6050的IO

  2. 初始化两个按钮的IO

  3. 初始化Socket协议

  4. 不断循环读取MPU6050和两个按钮的数据,然后打包发送给游戏端

其报文的数据部分主要包括三个自由度的角速度、三个自由度的加速度、以及按钮是否被按下的数据。

两个按钮接入的时GPIO.7和GPIO.21,采用的是低电平有效。

代码部分

树莓派端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <wiringPiI2C.h>
#include <wiringPi.h>

#define Device_Address 0x68 // MPU6050 的地址
#define PWR_MGMT_1 0x6B
#define SMPLRT_DIV 0x19
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define INT_ENABLE 0x38
#define ACCEL_XOUT_H 0x3B
#define ACCEL_YOUT_H 0x3D
#define ACCEL_ZOUT_H 0x3F
#define GYRO_XOUT_H 0x43
#define GYRO_YOUT_H 0x45
#define GYRO_ZOUT_H 0x47
#define pin1 7
#define pin2 21

int fd;

void MPU6050_Init()
{
fd = wiringPiI2CSetup(Device_Address); /*Initializes I2C with device Address*/
wiringPiI2CWriteReg8(fd, SMPLRT_DIV, 0x07); /* Write to sample rate register */
wiringPiI2CWriteReg8(fd, PWR_MGMT_1, 0x01); /* Write to power management register */
wiringPiI2CWriteReg8(fd, CONFIG, 0); /* Write to Configuration register */
wiringPiI2CWriteReg8(fd, GYRO_CONFIG, 24); /* Write to Gyro Configuration register */
wiringPiI2CWriteReg8(fd, INT_ENABLE, 0x01); /*Write to interrupt enable register */

// 初始化按钮的IO
if(wiringPiSetup() == -1){
printf("setup wiringPi failed!\n");
exit(0);
}
pinMode(pin1,INPUT);
pinMode(pin2,INPUT);
}

short read_raw_data(int addr)
{
short high_byte, low_byte, value;
high_byte = wiringPiI2CReadReg8(fd, addr);
low_byte = wiringPiI2CReadReg8(fd, addr + 1);
value = (high_byte << 8) | low_byte;
return value;
}

char* MPU6050_Get_Data()
{
char *buf = (char *)malloc(45 * sizeof(char));

float Ax = 0, Ay = 0, Az = 0;
float Gx = 0, Gy = 0, Gz = 0;

// 从MPU6050获取三轴的加速度和角速度
Ax = read_raw_data(ACCEL_XOUT_H) / 16384.0;
Ay = read_raw_data(ACCEL_YOUT_H) / 16384.0;
Az = read_raw_data(ACCEL_ZOUT_H) / 16384.0;

Gx = read_raw_data(GYRO_XOUT_H) / 131;
Gy = read_raw_data(GYRO_YOUT_H) / 131;
Gz = read_raw_data(GYRO_ZOUT_H) / 131;
int button1 = digitalRead(pin1);
int button2 = digitalRead(pin2);
// 拼接成字符串
sprintf(buf, "%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%d,%d", Gx, Gy, Gz, Ax, Ay, Az,button1,button2);

return buf;
}

int main()
{

// 初始化 MPU6050
MPU6050_Init();

// 设置端口号
int port = 5150;

// 创建udp通信socket
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket == -1)
{
perror("socket failed!\n");
return -1;
}

// 设置目的IP地址
struct sockaddr_in address = { 0 };
address.sin_family = AF_INET; // 使用IPv4协议
address.sin_port = htons(port); // 设置接收方端口号
address.sin_addr.s_addr = inet_addr("192.168.13.37"); //设置接收方IP

//char buf[40] = { 0 };
// 循环发送数据
while (1)
{
// 获取并记录数据
char* buf = MPU6050_Get_Data();
printf("%s\n",buf);
// 发送数据
sendto(udp_socket, buf, strlen(buf), 0, (struct sockaddr*)&address, sizeof(address));

//清空存留消息
free(buf);
//delay(100);
}

// 关闭通信socket
close(udp_socket);
return 0;
}

UE4端(这里只列出通信部分)

.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
UCLASS(Config=Game)
class AaircraftPawn : public APawn
{
public:
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void BeginPlay() override;
public:
FSocket* ListenSocket;
FUdpSocketReceiver* UDPReceiver;
TSharedPtr<FInternetAddr> RemoteAddr;

UFUNCTION(BlueprintCallable, Category = "UDP")
bool StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort);

UFUNCTION(BlueprintPure, Category = "UDP")
bool DataRecv(FString &string);

FORCEINLINE void ScreenMsg(const FString& Msg)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, *Msg);
}
FORCEINLINE void ScreenMsg(const FString& Msg, const float Value)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %f"), *Msg, Value));
}
FORCEINLINE void ScreenMsg(const FString& Msg, const FString& Msg2)
{
GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s %s"), *Msg, *Msg2));
}
};

.cpp文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
AaircraftPawn::AaircraftPawn()
{
// Initialize the Socket
ListenSocket = NULL;
UDPReceiver = nullptr;
}

void AaircraftPawn::BeginPlay() // Initialize the game
{
Super::BeginPlay();
bool isInstall = StartUDPReceiver("ListenSocket", "192.168.197.37", 5150);
if (isInstall)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, TEXT("UDPReceiver Start"));
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, TEXT("Fail to Install UDP"));
}

}

void AaircraftPawn::EndPlay(const EEndPlayReason::Type EndPlayReason) // When the game ends
{
Super::EndPlay(EndPlayReason);
// UDPReceiver empty
delete UDPReceiver;
UDPReceiver = nullptr;

// Clear all sockets
if (ListenSocket)
{
ListenSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenSocket);
}
}

void AaircraftPawn::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
FString str;
if (DataRecv(str))
{
// The data is captured here
TArray<FString> dataArr;
str.ParseIntoArray(dataArr, TEXT(","), true);
}
}

// Initialize the UDP
bool AaircraftPawn::StartUDPReceiver(const FString& YourChosenSocketName, const FString& TheIP, const int32 ThePort)
{
FIPv4Address Addr;
FIPv4Address::Parse(TheIP, Addr);
FIPv4Endpoint Endpoint(FIPv4Address::Any, ThePort); // All IP addresses are local
ListenSocket = FUdpSocketBuilder(*YourChosenSocketName)
.AsNonBlocking() // Set socket operations to non-blocking
.AsReusable() // Make the bound address reusable by other sockets
.BoundToEndpoint(Endpoint) // Set the port binding to the local endpoint
.WithReceiveBufferSize(2 * 1024 * 1024) // Set the size of the received data
;

int32 BufferSize = 2 * 1024 * 1024;
ListenSocket->SetSendBufferSize(BufferSize, BufferSize);
ListenSocket->SetReceiveBufferSize(BufferSize, BufferSize);

if (!ListenSocket)
{
ScreenMsg("No socket");
return false;
}

return true;
}

bool AaircraftPawn::DataRecv(FString& string)
{
if (!ListenSocket)
{
ScreenMsg("No sender socket");
return false;
}

TSharedRef<FInternetAddr> targetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr();
TArray<uint8> ReceivedData; // Define a receiver
uint32 Size;

// Query the socket to see if there is any pending data in the queue
if (ListenSocket->HasPendingData(Size))
{
string = "";
uint8* Recv = new uint8[Size];
int32 BytesRead = 0;

// Adjust the array to a given number of elements. The new element will be initialized
ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));
ListenSocket->RecvFrom(ReceivedData.GetData(), ReceivedData.Num(), BytesRead, *targetAddr);
char ansiiData[1024];
memcpy(ansiiData, ReceivedData.GetData(), BytesRead); // Copy data to the receiver
ansiiData[BytesRead] = 0; // Determine the data end
FString debugData = ANSI_TO_TCHAR(ansiiData); // String conversion
string = debugData;
}
else
{
return false;
}

return true;
}

演示截图

img