ds4: use controller calibration values for accel/gyro

This commit is contained in:
Jake 2017-05-18 22:06:31 -05:00 committed by Ivan
parent 80fc471067
commit 674acd8431
5 changed files with 2000 additions and 107 deletions

1702
Utilities/CRC.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,9 +8,18 @@
namespace namespace
{ {
const u32 THREAD_TIMEOUT = 1000; const auto THREAD_SLEEP = 1ms; //ds4 has new data every ~4ms,
const u32 THREAD_SLEEP = 1; //ds4 has new data every ~4ms, const auto THREAD_SLEEP_INACTIVE = 100ms;
const u32 THREAD_SLEEP_INACTIVE = 100;
const u32 DS4_ACC_RES_PER_G = 8192;
const u32 DS4_GYRO_RES_PER_DEG_S = 16; // technically this could be 1024, but keeping it at 16 keeps us within 16 bits of precision
const u32 DS4_FEATURE_REPORT_0x02_SIZE = 37;
const u32 DS4_FEATURE_REPORT_0x05_SIZE = 41;
const u32 DS4_FEATURE_REPORT_0x81_SIZE = 7;
const u32 DS4_INPUT_REPORT_0x11_SIZE = 78;
const u32 DS4_OUTPUT_REPORT_0x05_SIZE = 32;
const u32 DS4_OUTPUT_REPORT_0x11_SIZE = 78;
const u32 DS4_INPUT_REPORT_GYRO_X_OFFSET = 13;
inline u16 Clamp0To255(f32 input) inline u16 Clamp0To255(f32 input)
{ {
@ -102,6 +111,16 @@ namespace
return std::tuple<u16, u16>(Clamp0To255((outX + 1) * 127.f), Clamp0To255(((outY * -1) + 1) * 127.f)); return std::tuple<u16, u16>(Clamp0To255((outX + 1) * 127.f), Clamp0To255(((outY * -1) + 1) * 127.f));
}*/ }*/
inline s16 GetS16LEData(const u8* buf)
{
return (s16)(((u16)buf[0] << 0) + ((u16)buf[1] << 8));
}
inline u32 GetU32LEData(const u8* buf)
{
return (u32)(((u32)buf[0] << 0) + ((u32)buf[1] << 8) + ((u32)buf[2] << 16) + ((u32)buf[3] << 24));
}
} }
DS4PadHandler::~DS4PadHandler() DS4PadHandler::~DS4PadHandler()
@ -376,12 +395,13 @@ void DS4PadHandler::ProcessData()
pad.m_buttons[12 + i - 4].m_value = pressed ? 255 : 0; pad.m_buttons[12 + i - 4].m_value = pressed ? 255 : 0;
} }
// these values come already calibrated from our DS4Thread,
// all we need to do is convert to ds3 range
// accel // accel
// todo: scaling and double check these f32 accelX = (((s16)((u16)(buf[20] << 8) | buf[19])) / static_cast<f32>(DS4_ACC_RES_PER_G)) * -1;
// *i think* this is the constant for getting accel into absolute 'g' format...also need to flip them f32 accelY = (((s16)((u16)(buf[22] << 8) | buf[21])) / static_cast<f32>(DS4_ACC_RES_PER_G)) * -1;
f32 accelX = (((s16)((u16)(buf[20] << 8) | buf[21])) / 8315.f) * -1; f32 accelZ = (((s16)((u16)(buf[24] << 8) | buf[23])) / static_cast<f32>(DS4_ACC_RES_PER_G)) * -1;
f32 accelY = (((s16)((u16)(buf[22] << 8) | buf[23])) / 8315.f) * -1;
f32 accelZ = (((s16)((u16)(buf[24] << 8) | buf[25])) / 8315.f) * -1;
// now just use formula from ds3 // now just use formula from ds3
accelX = accelX * 113 + 512; accelX = accelX * 113 + 512;
@ -392,12 +412,15 @@ void DS4PadHandler::ProcessData()
pad.m_sensors[1].m_value = Clamp0To1023(accelY); pad.m_sensors[1].m_value = Clamp0To1023(accelY);
pad.m_sensors[2].m_value = Clamp0To1023(accelZ); pad.m_sensors[2].m_value = Clamp0To1023(accelZ);
// todo: scaling check // gyroX is yaw, which is all that we need
// gyroX looks to be yaw, which is what we need f32 gyroX = (((s16)((u16)(buf[16] << 8) | buf[15])) / static_cast<f32>(DS4_GYRO_RES_PER_DEG_S)) * -1;
const int gyroX = (((s16)((u16)(buf[16] << 8) | buf[17])) / 128) * -1; //const int gyroY = ((u16)(buf[14] << 8) | buf[13]) / 256;
//const int gyroY = ((u16)(buf[14] << 8) | buf[15]) / 256; //const int gyroZ = ((u16)(buf[18] << 8) | buf[17]) / 256;
//const int gyroZ = ((u16)(buf[18] << 8) | buf[19]) / 256;
pad.m_sensors[3].m_value = Clamp0To1023(gyroX + 512); // convert to ds3
gyroX = gyroX * (123.f / 90.f) + 512;
pad.m_sensors[3].m_value = Clamp0To1023(gyroX);
i++; i++;
} }
@ -428,8 +451,10 @@ void DS4Thread::SetRumbleData(u32 port, u8 largeVibrate, u8 smallVibrate)
{ {
if (i == port) if (i == port)
{ {
controller.second.newVibrateData = controller.second.largeVibrate != largeVibrate || controller.second.smallVibrate != smallVibrate;
controller.second.largeVibrate = largeVibrate; controller.second.largeVibrate = largeVibrate;
controller.second.smallVibrate = smallVibrate; controller.second.smallVibrate = smallVibrate;
break;
} }
++i; ++i;
} }
@ -461,6 +486,127 @@ std::array<std::array<u8, 64>, MAX_GAMEPADS> DS4Thread::GetControllerData()
return rtnData; return rtnData;
} }
bool DS4Thread::GetCalibrationData(DS4Device* ds4Dev)
{
std::array<u8, 64> buf;
if (ds4Dev->btCon)
{
for (int tries = 0; tries < 3; ++tries) {
buf[0] = 0x05;
if (hid_get_feature_report(ds4Dev->hidDevice, buf.data(), DS4_FEATURE_REPORT_0x05_SIZE) <= 0)
return false;
const u8 btHdr = 0xA3;
const u32 crcHdr = CRCPP::CRC::Calculate(&btHdr, 1, crcTable);
const u32 crcCalc = CRCPP::CRC::Calculate(buf.data(), (DS4_FEATURE_REPORT_0x05_SIZE - 4), crcTable, crcHdr);
const u32 crcReported = GetU32LEData(&buf[DS4_FEATURE_REPORT_0x05_SIZE - 4]);
if (crcCalc != crcReported)
LOG_WARNING(HLE, "[DS4] Calibration CRC check failed! Will retry up to 3 times. Received 0x%x, Expected 0x%x", crcReported, crcCalc);
else break;
if (tries == 2)
return false;
}
}
else
{
buf[0] = 0x02;
if (hid_get_feature_report(ds4Dev->hidDevice, buf.data(), DS4_FEATURE_REPORT_0x02_SIZE) <= 0)
return false;
}
ds4Dev->calibData[DS4CalibIndex::PITCH].bias = GetS16LEData(&buf[1]);
ds4Dev->calibData[DS4CalibIndex::YAW].bias = GetS16LEData(&buf[3]);
ds4Dev->calibData[DS4CalibIndex::ROLL].bias = GetS16LEData(&buf[5]);
s16 pitchPlus, pitchNeg, rollPlus, rollNeg, yawPlus, yawNeg;
if (ds4Dev->btCon)
{
pitchPlus = GetS16LEData(&buf[7]);
yawPlus = GetS16LEData(&buf[9]);
rollPlus = GetS16LEData(&buf[11]);
pitchNeg = GetS16LEData(&buf[13]);
yawNeg = GetS16LEData(&buf[15]);
rollNeg = GetS16LEData(&buf[17]);
}
else
{
pitchPlus = GetS16LEData(&buf[7]);
pitchNeg = GetS16LEData(&buf[9]);
yawPlus = GetS16LEData(&buf[11]);
yawNeg = GetS16LEData(&buf[13]);
rollPlus = GetS16LEData(&buf[15]);
rollNeg = GetS16LEData(&buf[17]);
}
const s32 gyroSpeedScale = GetS16LEData(&buf[19]) + GetS16LEData(&buf[21]);
ds4Dev->calibData[DS4CalibIndex::PITCH].sensNumer = gyroSpeedScale * DS4_GYRO_RES_PER_DEG_S;
ds4Dev->calibData[DS4CalibIndex::PITCH].sensDenom = pitchPlus - pitchNeg;
ds4Dev->calibData[DS4CalibIndex::YAW].sensNumer = gyroSpeedScale * DS4_GYRO_RES_PER_DEG_S;
ds4Dev->calibData[DS4CalibIndex::YAW].sensDenom = yawPlus - yawNeg;
ds4Dev->calibData[DS4CalibIndex::ROLL].sensNumer = gyroSpeedScale * DS4_GYRO_RES_PER_DEG_S;
ds4Dev->calibData[DS4CalibIndex::ROLL].sensDenom = rollPlus - rollNeg;
const s16 accelXPlus = GetS16LEData(&buf[23]);
const s16 accelXNeg = GetS16LEData(&buf[25]);
const s16 accelYPlus = GetS16LEData(&buf[27]);
const s16 accelYNeg = GetS16LEData(&buf[29]);
const s16 accelZPlus = GetS16LEData(&buf[31]);
const s16 accelZNeg = GetS16LEData(&buf[33]);
const s32 accelXRange = accelXPlus - accelXNeg;
ds4Dev->calibData[DS4CalibIndex::X].bias = accelXPlus - accelXRange / 2;
ds4Dev->calibData[DS4CalibIndex::X].sensNumer = 2 * DS4_ACC_RES_PER_G;
ds4Dev->calibData[DS4CalibIndex::X].sensDenom = accelXRange;
const s32 accelYRange = accelYPlus - accelYNeg;
ds4Dev->calibData[DS4CalibIndex::Y].bias = accelYPlus - accelYRange / 2;
ds4Dev->calibData[DS4CalibIndex::Y].sensNumer = 2 * DS4_ACC_RES_PER_G;
ds4Dev->calibData[DS4CalibIndex::Y].sensDenom = accelYRange;
const s32 accelZRange = accelZPlus - accelZNeg;
ds4Dev->calibData[DS4CalibIndex::Z].bias = accelZPlus - accelZRange / 2;
ds4Dev->calibData[DS4CalibIndex::Z].sensNumer = 2 * DS4_ACC_RES_PER_G;
ds4Dev->calibData[DS4CalibIndex::Z].sensDenom = accelZRange;
return true;
}
void DS4Thread::CheckAddDevice(hid_device* hidDevice, hid_device_info* hidDevInfo)
{
std::string serial = "";
DS4Device ds4Dev;
ds4Dev.hidDevice = hidDevice;
// There isnt a nice 'portable' way with hidapi to detect bt vs wired as the pid/vid's are the same
// Let's try getting 0x81 feature report, which should will return mac address on wired, and should error on bluetooth
std::array<u8, 64> buf{};
buf[0] = 0x81;
if (hid_get_feature_report(hidDevice, buf.data(), DS4_FEATURE_REPORT_0x81_SIZE) > 0)
{
serial = fmt::format("%x%x%x%x%x%x", buf[6], buf[5], buf[4], buf[3], buf[2], buf[1]);
}
else
{
ds4Dev.btCon = true;
std::wstring wSerial(hidDevInfo->serial_number);
serial = std::string(wSerial.begin(), wSerial.end());
}
if (!GetCalibrationData(&ds4Dev))
{
LOG_ERROR(HLE, "[DS4] Failed getting calibration data, ignoring controller!");
hid_close(hidDevice);
return;
}
ds4Dev.path = hidDevInfo->path;
hid_set_nonblocking(hidDevice, 1);
controllers.emplace(serial, ds4Dev);
}
void DS4Thread::on_init(const std::shared_ptr<void>& _this) void DS4Thread::on_init(const std::shared_ptr<void>& _this)
{ {
const int res = hid_init(); const int res = hid_init();
@ -479,29 +625,8 @@ void DS4Thread::on_init(const std::shared_ptr<void>& _this)
hid_device* dev = hid_open_path(devInfo->path); hid_device* dev = hid_open_path(devInfo->path);
if (dev) if (dev)
{ CheckAddDevice(dev, devInfo);
hid_set_nonblocking(dev, 1);
// There isnt a nice 'portable' way with hidapi to detect bt vs wired as the pid/vid's are the same
// Let's try getting 0x81 feature report, which should will return mac address on wired, and should error on bluetooth
std::array<u8, 7> buf{};
buf[0] = 0x81;
if (hid_get_feature_report(dev, buf.data(), buf.size()) > 0)
{
std::string serial = fmt::format("%x%x%x%x%x%x", buf[6], buf[5], buf[4], buf[3], buf[2], buf[1]);
controllers.emplace(serial, DS4Device{ dev, devInfo->path, false });
}
else
{
// this kicks bt into sending the correct data
std::array<u8, 64> buf{};
buf[0] = 0x2;
hid_get_feature_report(dev, buf.data(), buf.size());
std::wstring wSerial(devInfo->serial_number);
std::string serialNum = std::string(wSerial.begin(), wSerial.end());
controllers.emplace(serialNum, DS4Device{ dev, devInfo->path, true});
}
}
devInfo = devInfo->next; devInfo = devInfo->next;
} }
} }
@ -524,6 +649,46 @@ DS4Thread::~DS4Thread()
hid_exit(); hid_exit();
} }
void DS4Thread::SendVibrateData(const DS4Device& device)
{
std::array<u8, 78> outputBuf{0};
// write rumble state
if (device.btCon)
{
outputBuf[0] = 0x11;
outputBuf[1] = 0xC4;
outputBuf[3] = 0x07;
outputBuf[6] = device.smallVibrate;
outputBuf[7] = device.largeVibrate;
outputBuf[8] = 0x00; // red
outputBuf[9] = 0x00; // green
outputBuf[10] = 0xff; // blue
const u8 btHdr = 0xA2;
const u32 crcHdr = CRCPP::CRC::Calculate(&btHdr, 1, crcTable);
const u32 crcCalc = CRCPP::CRC::Calculate(outputBuf.data(), (DS4_OUTPUT_REPORT_0x11_SIZE - 4), crcTable, crcHdr);
outputBuf[74] = (crcCalc >> 0) & 0xFF;
outputBuf[75] = (crcCalc >> 8) & 0xFF;
outputBuf[76] = (crcCalc >> 16) & 0xFF;
outputBuf[77] = (crcCalc >> 24) & 0xFF;
hid_write_control(device.hidDevice, outputBuf.data(), DS4_OUTPUT_REPORT_0x11_SIZE);
}
else
{
outputBuf[0] = 0x05;
outputBuf[1] = 0x07;
outputBuf[4] = device.smallVibrate;
outputBuf[5] = device.largeVibrate;
outputBuf[6] = 0x00; // red
outputBuf[7] = 0x00; // green
outputBuf[8] = 0xff; // blue
hid_write(device.hidDevice, outputBuf.data(), DS4_OUTPUT_REPORT_0x05_SIZE);
}
}
void DS4Thread::on_task() void DS4Thread::on_task()
{ {
while (!Emu.IsStopped()) while (!Emu.IsStopped())
@ -537,9 +702,8 @@ void DS4Thread::on_task()
u32 online = 0; u32 online = 0;
u32 i = 0; u32 i = 0;
std::array<u8, 64> buf{}; std::array<u8, 78> buf{};
std::array<u8, 67> btBuf{};
std::array<u8, 78> outputBuf{0};
for (auto & controller : controllers) for (auto & controller : controllers)
{ {
@ -547,14 +711,14 @@ void DS4Thread::on_task()
if (controller.second.hidDevice == nullptr) if (controller.second.hidDevice == nullptr)
{ {
// try to connect // try to reconnect
hid_device* dev = hid_open_path(controller.second.path.c_str()); hid_device* dev = hid_open_path(controller.second.path.c_str());
if (dev) if (dev)
{ {
hid_set_nonblocking(dev, 1); hid_set_nonblocking(dev, 1);
if (controller.second.btCon) if (controller.second.btCon)
{ {
// this kicks bt into sending the correct data // We already have calibration data, but we still need this to kick BT into sending correct 0x11 reports
std::array<u8, 64> buf{}; std::array<u8, 64> buf{};
buf[0] = 0x2; buf[0] = 0x2;
hid_get_feature_report(dev, buf.data(), buf.size()); hid_get_feature_report(dev, buf.data(), buf.size());
@ -570,9 +734,7 @@ void DS4Thread::on_task()
online++; online++;
if (controller.second.btCon) const int res = hid_read(controller.second.hidDevice, buf.data(), controller.second.btCon ? 78 : 64);
{
const int res = hid_read(controller.second.hidDevice, btBuf.data(), btBuf.size());
if (res == -1) if (res == -1)
{ {
// looks like controller disconnected or read error, deal with it on next loop // looks like controller disconnected or read error, deal with it on next loop
@ -585,66 +747,46 @@ void DS4Thread::on_task()
if (res == 0) if (res == 0)
continue; continue;
// not the report we want int offset = 0;
if (btBuf[0] != 0x11) // check report and set offset
if (controller.second.btCon && buf[0] == 0x11 && res == 78)
{
offset = 2;
const u8 btHdr = 0xA1;
const u32 crcHdr = CRCPP::CRC::Calculate(&btHdr, 1, crcTable);
const u32 crcCalc = CRCPP::CRC::Calculate(buf.data(), (DS4_INPUT_REPORT_0x11_SIZE - 4), crcTable, crcHdr);
const u32 crcReported = GetU32LEData(&buf[DS4_INPUT_REPORT_0x11_SIZE - 4]);
if (crcCalc != crcReported) {
LOG_WARNING(HLE, "[DS4] Data packet CRC check failed, ignoring! Received 0x%x, Expected 0x%x", crcReported, crcCalc);
continue; continue;
if (res != 67)
fmt::throw_exception("unexpected ds4 bt packet size");
// shave off first two bytes that are bluetooth specific
memcpy(padData[i].data(), &btBuf[2], 64);
} }
}
else if (!controller.second.btCon && buf[0] == 0x01 && res == 64)
offset = 0;
else else
{
const int res = hid_read(controller.second.hidDevice, buf.data(), buf.size());
if (res == -1 || (res != 0 && res != 64))
{
// looks like controller disconnected or read error, deal with it on next loop
hid_close(controller.second.hidDevice);
controller.second.hidDevice = nullptr;
continue;
}
// no data? keep going
if (res == 0)
continue; continue;
memcpy(padData[i].data(), buf.data(), 64); int calibOffset = offset + DS4_INPUT_REPORT_GYRO_X_OFFSET;
} for (int i = 0; i < DS4CalibIndex::COUNT; ++i)
outputBuf.fill(0);
// write rumble state
if (controller.second.btCon)
{ {
outputBuf[0] = 0x11; const s16 rawValue = GetS16LEData(&buf[calibOffset]);
outputBuf[1] = 0x80; const s16 calValue = ApplyCalibration(rawValue, controller.second.calibData[i]);
outputBuf[3] = 0xff; buf[calibOffset++] = ((u16)calValue >> 0) & 0xFF;
outputBuf[6] = controller.second.smallVibrate; buf[calibOffset++] = ((u16)calValue >> 8) & 0xFF;
outputBuf[7] = controller.second.largeVibrate;
outputBuf[8] = 0x00; // red
outputBuf[9] = 0x00; // green
outputBuf[10] = 0xff; // blue
hid_write_control(controller.second.hidDevice, outputBuf.data(), 78);
} }
else
memcpy(padData[i].data(), &buf[offset], 64);
if (controller.second.newVibrateData)
{ {
outputBuf[0] = 0x05; SendVibrateData(controller.second);
outputBuf[1] = 0xff; controller.second.newVibrateData = false;
outputBuf[4] = controller.second.smallVibrate;
outputBuf[5] = controller.second.largeVibrate;
outputBuf[6] = 0x00; // red
outputBuf[7] = 0x00; // green
outputBuf[8] = 0xff; // blue
hid_write(controller.second.hidDevice, outputBuf.data(), 64);
} }
i++; i++;
} }
std::this_thread::sleep_for((online > 0) ? 1ms : 100ms); std::this_thread::sleep_for((online > 0) ? THREAD_SLEEP : THREAD_SLEEP_INACTIVE);
} }
} }

View File

@ -2,20 +2,45 @@
#include "Emu/Io/PadHandler.h" #include "Emu/Io/PadHandler.h"
#include "Utilities/Thread.h" #include "Utilities/Thread.h"
#include "Utilities/CRC.h"
#include "hidapi.h" #include "hidapi.h"
#include <limits>
const u32 MAX_GAMEPADS = 7; const u32 MAX_GAMEPADS = 7;
class DS4Thread final : public named_thread class DS4Thread final : public named_thread
{ {
private: private:
enum DS4CalibIndex
{
// gyro
PITCH = 0,
YAW,
ROLL,
// accel
X,
Y,
Z,
COUNT
};
struct DS4CalibData
{
s16 bias;
s32 sensNumer;
s32 sensDenom;
};
struct DS4Device struct DS4Device
{ {
hid_device* hidDevice; hid_device* hidDevice{ nullptr };
std::string path; std::string path{ "" };
bool btCon; bool btCon{ false };
u8 largeVibrate; std::array<DS4CalibData, DS4CalibIndex::COUNT> calibData;
u8 smallVibrate; bool newVibrateData{true};
u8 largeVibrate{0};
u8 smallVibrate{0};
}; };
const u16 DS4_VID = 0x054C; const u16 DS4_VID = 0x054C;
@ -34,6 +59,8 @@ private:
semaphore<> mutex; semaphore<> mutex;
CRCPP::CRC::Table<u32, 32> crcTable{ CRCPP::CRC::CRC_32() };
public: public:
void on_init(const std::shared_ptr<void>&) override; void on_init(const std::shared_ptr<void>&) override;
@ -46,6 +73,24 @@ public:
DS4Thread() = default; DS4Thread() = default;
~DS4Thread(); ~DS4Thread();
private:
bool GetCalibrationData(DS4Device* ds4Device);
void CheckAddDevice(hid_device* hidDevice, hid_device_info* hidDevInfo);
void SendVibrateData(const DS4Device& device);
inline s16 ApplyCalibration(s32 rawValue, const DS4CalibData& calibData)
{
const s32 biased = rawValue - calibData.bias;
const s32 quot = calibData.sensNumer / calibData.sensDenom;
const s32 rem = calibData.sensNumer % calibData.sensDenom;
const s32 output = (quot * biased) + ((rem * biased) / calibData.sensDenom);
if (output > std::numeric_limits<s16>::max())
return std::numeric_limits<s16>::max();
else if (output < std::numeric_limits<s16>::min())
return std::numeric_limits<s16>::min();
else return static_cast<s16>(output);
}
}; };
class DS4PadHandler final : public PadHandlerBase class DS4PadHandler final : public PadHandlerBase

View File

@ -409,6 +409,7 @@
<ClInclude Include="..\Utilities\bit_set.h" /> <ClInclude Include="..\Utilities\bit_set.h" />
<ClInclude Include="..\Utilities\cfmt.h" /> <ClInclude Include="..\Utilities\cfmt.h" />
<ClInclude Include="..\Utilities\cond.h" /> <ClInclude Include="..\Utilities\cond.h" />
<ClInclude Include="..\Utilities\CRC.h" />
<ClInclude Include="..\Utilities\dynamic_library.h" /> <ClInclude Include="..\Utilities\dynamic_library.h" />
<ClInclude Include="..\Utilities\event.h" /> <ClInclude Include="..\Utilities\event.h" />
<ClInclude Include="..\Utilities\GDBDebugServer.h" /> <ClInclude Include="..\Utilities\GDBDebugServer.h" />

View File

@ -1774,5 +1774,8 @@
<ClInclude Include="Emu\Cell\lv2\sys_ss.h"> <ClInclude Include="Emu\Cell\lv2\sys_ss.h">
<Filter>Emu\Cell\lv2</Filter> <Filter>Emu\Cell\lv2</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="..\Utilities\CRC.h">
<Filter>Utilities</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>