diff --git a/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h b/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h index 1bdeb0a15b..5a3cd89ee0 100644 --- a/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h +++ b/Source/Core/Core/HW/WiimoteCommon/WiimoteReport.h @@ -63,7 +63,7 @@ union wm_buttons // also just called "core data" u8 down : 1; u8 up : 1; u8 plus : 1; - u8 acc_x_lsb : 2; // LSB of accelerometer (10 bits in total) + u8 acc_bits : 2; u8 unknown : 1; u8 two : 1; @@ -71,8 +71,7 @@ union wm_buttons // also just called "core data" u8 b : 1; u8 a : 1; u8 minus : 1; - u8 acc_y_lsb : 1; // LSB of accelerometer (9 bits in total) - u8 acc_z_lsb : 1; // LSB of accelerometer (9 bits in total) + u8 acc_bits2 : 2; u8 home : 1; }; }; @@ -109,6 +108,23 @@ struct wm_ir_extended }; static_assert(sizeof(wm_ir_extended) == 3, "Wrong size"); +// Nine bytes for one object +// first 3 bytes are the same as extended +struct wm_ir_full : wm_ir_extended +{ + u8 xmin : 7; + u8 : 1; + u8 ymin : 7; + u8 : 1; + u8 xmax : 7; + u8 : 1; + u8 ymax : 7; + u8 : 1; + u8 zero; + u8 intensity; +}; +static_assert(sizeof(wm_ir_full) == 9, "Wrong size"); + // Nunchuk union wm_nc_core { diff --git a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp index 3d3a88460c..9275eaeb46 100644 --- a/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/EmuSubroutines.cpp @@ -46,10 +46,6 @@ void Wiimote::ReportMode(const wm_report_mode* const dr) WARN_LOG(WIIMOTE, "Game requested invalid report mode: 0x%02x", dr->mode); return; } - else if (dr->mode > RT_REPORT_CORE_ACCEL_IR10_EXT6) - { - PanicAlert("Wiimote: Unsupported Reporting mode: 0x%02x", dr->mode); - } // TODO: A real wiimote sends a report immediately. // even on REPORT_CORE and continuous off when the buttons haven't changed. diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index ac039daa15..461a5aef55 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -13,12 +13,12 @@ #include #include +#include "Common/BitUtils.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Config/Config.h" #include "Common/MathUtil.h" #include "Common/MsgHandler.h" -#include "Common/BitUtils.h" #include "Core/Config/SYSCONFSettings.h" #include "Core/Config/WiimoteInputSettings.h" @@ -101,11 +101,16 @@ static const ReportFeatures reporting_mode_features[] = { {2, 0, 10, 9, 23}, // 0x37: Core Buttons and Accelerometer with 10 IR bytes and 6 Extension Bytes {2, 3, 10, 6, 23}, - // UNSUPPORTED (but should be easy enough to implement): + // Ugly padding members so 0x3d,3e,3f are properly placed in the array: + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, // 0x3d: 21 Extension Bytes {0, 0, 0, 21, 23}, // 0x3e / 0x3f: Interleaved Core Buttons and Accelerometer with 36 IR bytes - {0, 0, 0, 0, 23}, + {2, 1, 0, 18, 23}, }; // Used for extension calibration data: @@ -335,13 +340,13 @@ void Wiimote::Reset() memcpy(&m_motion_plus_logic.reg_data.ext_identifier, motion_plus_id, sizeof(motion_plus_id)); // calibration hackery - static const u8 c1[16] = {0x78, 0xd9, 0x78, 0x38, 0x77, 0x9d, 0x2f, 0x0c, 0xcf, 0xf0, 0x31, 0xad, - 0xc8, 0x0b, 0x5e, 0x39}; - static const u8 c2[16] = {0x6f, 0x81, 0x7b, 0x89, 0x78, 0x51, 0x33, 0x60, 0xc9, 0xf5, 0x37, 0xc1, - 0x2d, 0xe9, 0x15, 0x8d}; + static const u8 c1[16] = {0x78, 0xd9, 0x78, 0x38, 0x77, 0x9d, 0x2f, 0x0c, + 0xcf, 0xf0, 0x31, 0xad, 0xc8, 0x0b, 0x5e, 0x39}; + static const u8 c2[16] = {0x6f, 0x81, 0x7b, 0x89, 0x78, 0x51, 0x33, 0x60, + 0xc9, 0xf5, 0x37, 0xc1, 0x2d, 0xe9, 0x15, 0x8d}; - //std::copy(std::begin(c1), std::end(c1), m_motion_plus_logic.reg_data.calibration_data); - //std::copy(std::begin(c2), std::end(c2), m_motion_plus_logic.reg_data.calibration_data + 0x10); + // std::copy(std::begin(c1), std::end(c1), m_motion_plus_logic.reg_data.calibration_data); + // std::copy(std::begin(c2), std::end(c2), m_motion_plus_logic.reg_data.calibration_data + 0x10); // status memset(&m_status, 0, sizeof(m_status)); @@ -610,7 +615,8 @@ void Wiimote::GetButtonData(u8* const data) reinterpret_cast(data)->hex |= m_status.buttons.hex; } -void Wiimote::GetAccelData(u8* const data) +// Produces 10-bit precision values. +void Wiimote::GetAccelData(s16* x, s16* y, s16* z) { const bool sideways_modifier_toggle = m_hotkeys->getSettingsModifier()[0]; const bool upright_modifier_toggle = m_hotkeys->getSettingsModifier()[1]; @@ -661,26 +667,14 @@ void Wiimote::GetAccelData(u8* const data) EmulateDynamicShake(&m_accel, m_shake_dynamic_data, m_shake_dynamic, shake_config, m_shake_dynamic_step.data()); - // TODO: kill these ugly looking offsets - wm_accel& accel = *reinterpret_cast(data + 4); - wm_buttons& core = *reinterpret_cast(data + 2); - // We now use 2 bits more precision, so multiply by 4 before converting to int - s16 x = (s16)(4 * (m_accel.x * ACCEL_RANGE + ACCEL_ZERO_G)); - s16 y = (s16)(4 * (m_accel.y * ACCEL_RANGE + ACCEL_ZERO_G)); - s16 z = (s16)(4 * (m_accel.z * ACCEL_RANGE + ACCEL_ZERO_G)); + *x = (s16)(4 * (m_accel.x * ACCEL_RANGE + ACCEL_ZERO_G)); + *y = (s16)(4 * (m_accel.y * ACCEL_RANGE + ACCEL_ZERO_G)); + *z = (s16)(4 * (m_accel.z * ACCEL_RANGE + ACCEL_ZERO_G)); - x = MathUtil::Clamp(x, 0, 0x3ff); - y = MathUtil::Clamp(y, 0, 0x3ff); - z = MathUtil::Clamp(z, 0, 0x3ff); - - accel.x = (x >> 2) & 0xFF; - accel.y = (y >> 2) & 0xFF; - accel.z = (z >> 2) & 0xFF; - - core.acc_x_lsb = x & 0x3; - core.acc_y_lsb = (y >> 1) & 0x1; - core.acc_z_lsb = (z >> 1) & 0x1; + *x = MathUtil::Clamp(*x, 0, 0x3ff); + *y = MathUtil::Clamp(*y, 0, 0x3ff); + *z = MathUtil::Clamp(*z, 0, 0x3ff); } inline void LowPassFilter(double& var, double newval, double period) @@ -694,9 +688,6 @@ inline void LowPassFilter(double& var, double newval, double period) void Wiimote::UpdateIRData(bool use_accel) { - // IR data is stored at offset 0x37 - u8* const data = m_camera_logic.reg_data.camera_data; - u16 x[4], y[4]; memset(x, 0xFF, sizeof(x)); @@ -781,15 +772,20 @@ void Wiimote::UpdateIRData(bool use_accel) y[i] = static_cast(lround((v[i].y + 1) / 2 * (camHeight - 1))); } + // IR data is read from offset 0x37 on real hardware + u8* const data = m_camera_logic.reg_data.camera_data; + // A maximum of 36 bytes. + std::fill_n(data, sizeof(wm_ir_full) * 4, 0xff); + // Fill report with valid data when full handshake was done if (m_camera_logic.reg_data.data[0x30]) + { // ir mode switch (m_camera_logic.reg_data.mode) { // basic case 1: { - memset(data, 0xFF, 10); wm_ir_basic* const irdata = reinterpret_cast(data); for (unsigned int i = 0; i < 2; ++i) { @@ -810,12 +806,11 @@ void Wiimote::UpdateIRData(bool use_accel) irdata[i].y2hi = y[i * 2 + 1] >> 8; } } + break; } - break; // extended case 3: { - memset(data, 0xFF, 12); wm_ir_extended* const irdata = reinterpret_cast(data); for (unsigned int i = 0; i < 4; ++i) if (x[i] < 1024 && y[i] < 768) @@ -828,14 +823,39 @@ void Wiimote::UpdateIRData(bool use_accel) irdata[i].size = 10; } - } - break; - // full - case 5: - PanicAlert("Full IR report"); - // UNSUPPORTED break; } + // full + case 5: + { + wm_ir_full* const irdata = reinterpret_cast(data); + for (unsigned int i = 0; i < 4; ++i) + if (x[i] < 1024 && y[i] < 768) + { + irdata[i].x = static_cast(x[i]); + irdata[i].xhi = x[i] >> 8; + + irdata[i].y = static_cast(y[i]); + irdata[i].yhi = y[i] >> 8; + + irdata[i].size = 10; + + // TODO: implement these sensibly: + // TODO: do high bits of x/y min/max need to be set to zero? + irdata[i].xmin = 0; + irdata[i].ymin = 0; + irdata[i].xmax = 0; + irdata[i].ymax = 0; + irdata[i].zero = 0; + irdata[i].intensity = 0; + } + break; + } + default: + WARN_LOG(WIIMOTE, "Game is requesting IR data before setting IR mode."); + break; + } + } } void Wiimote::Update() @@ -863,13 +883,21 @@ void Wiimote::Update() return; } + if (RT_REPORT_CORE == m_reporting_mode && !m_reporting_auto) + { + // TODO: we only need to send a report if the data changed when reporting_auto is disabled + // probably only sensible to check this with RT_REPORT_CORE + // and just always send a report with the other modes + // return; + } + const ReportFeatures& rptf = reporting_mode_features[m_reporting_mode - RT_REPORT_CORE]; s8 rptf_size = rptf.total_size; if (Movie::IsPlayingInput() && Movie::PlayWiimote(m_index, data, rptf, m_extension->active_extension, m_ext_logic.ext_key)) { if (rptf.core_size) - m_status.buttons = *reinterpret_cast(data + 2); + m_status.buttons = *reinterpret_cast(data + rptf.GetCoreOffset()); } else { @@ -881,8 +909,7 @@ void Wiimote::Update() // hotkey/settings modifier m_hotkeys->GetState(); // data is later accessed in UpdateButtonsStatus and GetAccelData - // Data starts at byte 2 in the report - u8* feature_ptr = data + 2; + u8* feature_ptr = data + rptf.GetCoreOffset(); // core buttons if (rptf.core_size) @@ -895,7 +922,38 @@ void Wiimote::Update() if (rptf.accel_size) { // TODO: GetAccelData has hardcoded payload offsets.. - GetAccelData(data); + s16 x, y, z; + GetAccelData(&x, &y, &z); + + auto& core = *reinterpret_cast(data + rptf.GetCoreOffset()); + + // Special cases for the interleaved reports: + // only 8 bits of precision. + if (RT_REPORT_INTERLEAVE1 == m_reporting_mode) + { + *feature_ptr = (x >> 2) & 0xff; + core.acc_bits = (z >> 4) & 0b11; + core.acc_bits2 = (z >> 6) & 0b11; + } + if (RT_REPORT_INTERLEAVE2 == m_reporting_mode) + { + *feature_ptr = (y >> 2) & 0xff; + core.acc_bits = (z >> 0) & 0b11; + core.acc_bits2 = (z >> 2) & 0b11; + } + else + { + auto& accel = *reinterpret_cast(feature_ptr); + accel.x = (x >> 2) & 0xFF; + accel.y = (y >> 2) & 0xFF; + accel.z = (z >> 2) & 0xFF; + + // Set the LSBs + core.acc_bits = x & 0b11; + core.acc_bits2 = (y >> 1) & 0x1; + core.acc_bits2 |= ((z >> 1) & 0x1) << 1; + } + feature_ptr += rptf.accel_size; } @@ -909,8 +967,16 @@ void Wiimote::Update() if (!m_status.ir) WARN_LOG(WIIMOTE, "Game is reading IR data without enabling IR logic first."); - m_i2c_bus.BusRead(IRCameraLogic::DEVICE_ADDR, offsetof(IRCameraLogic::RegData, camera_data), - rptf.ir_size, feature_ptr); + u8 camera_data_offset = offsetof(IRCameraLogic::RegData, camera_data); + + if (RT_REPORT_INTERLEAVE2 == m_reporting_mode) + { + // Interleave2 reads the 2nd half of the "FULL" IR data + camera_data_offset += sizeof(wm_ir_full) * 2; + } + + m_i2c_bus.BusRead(IRCameraLogic::DEVICE_ADDR, camera_data_offset, rptf.ir_size, feature_ptr); + feature_ptr += rptf.ir_size; } @@ -944,15 +1010,17 @@ void Wiimote::Update() Movie::CheckWiimoteStatus(m_index, data, rptf, m_extension->active_extension, m_ext_logic.ext_key); - // don't send a data report if auto reporting is off - if (false == m_reporting_auto && data[1] >= RT_REPORT_CORE) - return; - // send data report if (rptf_size) { Core::Callback_WiimoteInterruptChannel(m_index, m_reporting_channel, data, rptf_size); } + + // The interleaved reporting modes toggle back and forth: + if (RT_REPORT_INTERLEAVE1 == m_reporting_mode) + m_reporting_mode = RT_REPORT_INTERLEAVE2; + else if (RT_REPORT_INTERLEAVE2 == m_reporting_mode) + m_reporting_mode = RT_REPORT_INTERLEAVE1; } void Wiimote::ControlChannel(const u16 channel_id, const void* data, u32 size) @@ -1213,9 +1281,9 @@ void Wiimote::MotionPlusLogic::Update() if (6 == i2c_bus.BusRead(ACTIVE_DEVICE_ADDR, 0x00, 6, data)) { // Passthrough data modifications via wiibrew.org - // Data passing through drops the least significant bit of the axes of the left (or only) joystick - // Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] - // Bits 0 and 1 of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before + // Data passing through drops the least significant bit of the axes of the left (or only) + // joystick Bit 0 of Byte 4 is overwritten [by the 'extension_connected' flag] Bits 0 and 1 + // of Byte 5 are moved to bit 0 of Bytes 0 and 1, overwriting what was there before SetBit(data[0], 0, Common::ExtractBit(data[5], 0)); SetBit(data[1], 0, Common::ExtractBit(data[5], 1)); diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index 97c4fbd327..573f1b6bc2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -269,6 +269,9 @@ public: // TODO: change int to u16 or something int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) { + // TODO: the real bus seems to read in blocks of 8. + // peripherals NACK after each 8 bytes. + // INFO_LOG(WIIMOTE, "i2c bus read: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count); for (auto& slave : m_slaves) { @@ -286,6 +289,7 @@ public: int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) { // TODO: write in blocks of 6 to simulate the real bus + // sometimes it writes in blocks of 8, (speaker data) // this might trigger activation writes more accurately // INFO_LOG(WIIMOTE, "i2c bus write: 0x%02x @ 0x%02x (%d)", slave_addr, addr, count); @@ -389,7 +393,7 @@ protected: void UpdateButtonsStatus(); void GetButtonData(u8* data); - void GetAccelData(u8* data); + void GetAccelData(s16* x, s16* y, s16* z); void UpdateIRData(bool use_accel); private: @@ -399,6 +403,8 @@ private: struct IRCameraLogic : public I2CSlave { + // TODO: some of this memory is write-only and should return error 7. + struct RegData { // Contains sensitivity and other unknown data @@ -501,6 +507,8 @@ private: static const u8 DATA_FORMAT_ADPCM = 0x00; static const u8 DATA_FORMAT_PCM = 0x40; + // TODO: It seems reading address 0x00 should always return 0xff. + struct { // Speaker reports result in a write of samples to addr 0x00 (which also plays sound) @@ -746,6 +754,7 @@ private: DynamicData m_shake_dynamic_data; // Wiimote accel data + // TODO: can this member be eliminated? AccelData m_accel; // Wiimote index, 0-3 diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index 36d18f8be1..ceb1cf5a01 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -688,8 +688,7 @@ static void SetWiiInputDisplayString(int remoteID, const u8* const data, std::memcpy(&dt, accelData, sizeof(dt)); display_str += - StringFromFormat(" ACC:%d,%d,%d", dt.x << 2 | buttons.acc_x_lsb, - dt.y << 2 | buttons.acc_y_lsb << 1, dt.z << 2 | buttons.acc_z_lsb << 1); + StringFromFormat(" ACC:%d,%d,%d (LSB not shown)", dt.x << 2, dt.y << 2, dt.z << 2); } } diff --git a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp index 71d62f5fe3..bc7ab87703 100644 --- a/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp +++ b/Source/Core/DolphinQt/TAS/WiiTASInputWindow.cpp @@ -350,11 +350,12 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp if (m_remote_orientation_box->isVisible() && accel_data && buttons_data) { wm_accel& accel = *reinterpret_cast(accel_data); - wm_buttons& buttons = *reinterpret_cast(buttons_data); + //wm_buttons& buttons = *reinterpret_cast(buttons_data); - u16 accel_x = (accel.x << 2) & (buttons.acc_x_lsb & 0b11); - u16 accel_y = (accel.y << 2) & ((buttons.acc_y_lsb & 0b1) << 1); - u16 accel_z = (accel.z << 2) & ((buttons.acc_z_lsb & 0b1) << 1); + // TODO: lsb + u16 accel_x = (accel.x << 2); // & (buttons.acc_x_lsb & 0b11); + u16 accel_y = (accel.y << 2); // & ((buttons.acc_y_lsb & 0b1) << 1); + u16 accel_z = (accel.z << 2); // &((buttons.acc_z_lsb & 0b1) << 1); GetSpinBoxU16(m_remote_orientation_x_value, accel_x); GetSpinBoxU16(m_remote_orientation_y_value, accel_y); @@ -364,9 +365,10 @@ void WiiTASInputWindow::GetValues(u8* report_data, WiimoteEmu::ReportFeatures rp accel.y = accel_y >> 2; accel.z = accel_z >> 2; - buttons.acc_x_lsb = accel_x & 0b11; - buttons.acc_y_lsb = (accel_y >> 1) & 0b1; - buttons.acc_z_lsb = (accel_z >> 1) & 0b1; + // TODO: lsb + //buttons.acc_x_lsb = accel_x & 0b11; + //buttons.acc_y_lsb = (accel_y >> 1) & 0b1; + //buttons.acc_z_lsb = (accel_z >> 1) & 0b1; } if (m_ir_box->isVisible() && ir_data && !m_use_controller->isChecked())