diff --git a/OpenVR/samples/driver_arduinohmd/driver_arduinohmd.cpp b/OpenVR/samples/driver_arduinohmd/driver_arduinohmd.cpp index f9c28f2..9dd9495 100644 --- a/OpenVR/samples/driver_arduinohmd/driver_arduinohmd.cpp +++ b/OpenVR/samples/driver_arduinohmd/driver_arduinohmd.cpp @@ -57,16 +57,16 @@ inline void HmdMatrix_SetIdentity( HmdMatrix34_t *pMatrix ) // keys for use with the settings API -static const char * const k_pch_steamvr_Section = "steamvr"; -static const char * const k_pch_arduinoHMD_SerialNumber_String = "serialNumber"; -static const char * const k_pch_arduinoHMD_ModelNumber_String = "modelNumber"; -static const char * const k_pch_arduinoHMD_WindowX_Int32 = "windowX"; -static const char * const k_pch_arduinoHMD_WindowY_Int32 = "windowY"; +static const char * const k_pch_steamvr_Section = "SteamVR"; +static const char * const k_pch_arduinoHMD_SerialNumber_String = "SerialNumber"; +static const char * const k_pch_arduinoHMD_ModelNumber_String = "ModelNumber"; +static const char * const k_pch_arduinoHMD_WindowX_Int32 = "WindowX"; +static const char * const k_pch_arduinoHMD_WindowY_Int32 = "WindowY"; static const char * const k_pch_arduinoHMD_WindowWidth_Int32 = "windowWidth"; static const char * const k_pch_arduinoHMD_WindowHeight_Int32 = "windowHeight"; -static const char * const k_pch_arduinoHMD_RenderWidth_Int32 = "renderWidth"; -static const char * const k_pch_arduinoHMD_RenderHeight_Int32 = "renderHeight"; -static const char * const k_pch_arduinoHMD_SecondsFromVsyncToPhotons_Float = "secondsFromVsyncToPhotons"; +static const char * const k_pch_arduinoHMD_RenderWidth_Int32 = "RenderWidth"; +static const char * const k_pch_arduinoHMD_RenderHeight_Int32 = "RenderHeight"; +static const char * const k_pch_arduinoHMD_SecondsFromVsyncToPhotons_Float = "SecondsFromVsyncToPhotons"; static const char * const k_pch_arduinoHMD_DisplayFrequency_Float = "displayFrequency"; // own output settings @@ -75,14 +75,16 @@ static const char * const k_pch_arduinoHMD_DistortionK2_Float = "DistortionK2"; static const char * const k_pch_arduinoHMD_ZoomWidth_Float = "ZoomWidth"; static const char * const k_pch_arduinoHMD_ZoomHeight_Float = "ZoomHeight"; static const char * const k_pch_arduinoHMD_FOV_Float = "FOV"; +static const char * const k_pch_arduinoHMD_IPD_Float = "IPD"; static const char * const k_pch_arduinoHMD_DistanceBetweenEyes_Int32 = "DistanceBetweenEyes"; static const char * const k_pch_arduinoHMD_ScreenOffsetX_Int32 = "ScreenOffsetX"; static const char * const k_pch_arduinoHMD_Stereo_Bool = "Stereo"; static const char * const k_pch_arduinoHMD_DebugMode_Bool = "DebugMode"; // arduino hmd settings -static const char * const k_pch_arduinoHMD_Section = "arduinohmd"; +static const char * const k_pch_arduinoHMD_Section = "ArduinoHMD"; static const char * const k_pch_arduinoHMD_ArduinoRequire_Bool = "ArduinoRequire"; +static const char * const k_pch_arduinoHMD_ArduinoRotationType_Bool = "RotationType"; static const char * const k_pch_arduinoHMD_COM_port_Int32 = "COMPort"; static const char * const k_pch_arduinoHMD_CenteringKey_String = "CenteringKey"; static const char * const k_pch_arduinoHMD_CrouchPressKey_String = "CrouchPressKey"; @@ -90,15 +92,26 @@ static const char * const k_pch_arduinoHMD_CrouchOffset_Float = "CrouchOffset"; HANDLE hSerial; int32_t comPortNumber; + +bool ArduinoRotationType = false; // 1 - quat, 0 - ypr bool HMDConnected = false, HMDInitCentring = false, ArduinoNotRequire = false; -float ArduinoIMU[3] = { 0, 0, 0 }, yprOffset[3] = { 0, 0, 0 }; // Yaw, Pitch, Roll -float LastArduinoIMU[3] = { 0, 0, 0 }; +std::thread *pArduinoReadThread = NULL; + +// Position double fPos[3] = { 0, 0, 0 }; #define StepPos 0.0033; #define StepRot 0.2; -std::thread *pArduinoReadThread = NULL; +// YPR type +float ArduinoIMU[3] = { 0, 0, 0 }, yprOffset[3] = { 0, 0, 0 }; // Yaw, Pitch, Roll +float LastArduinoIMU[3] = { 0, 0, 0 }; + +// Quaternion type +struct QuaternionF { + float w, x, y, z; +}; +QuaternionF ArduinoIMUQuat = { 0, 0, 0, 0 }, HMDQuatOffset = { 0, 0, 0, 0 }, LastArduinoIMUQuat = { 0, 0, 0, 0 }; double DegToRad(double f) { return f * (3.14159265358979323846 / 180); @@ -142,11 +155,32 @@ bool CorrectAngleValue(float Value) return false; } +bool CorrectQuatValue(float Value) +{ + if (Value > 1 || Value < -1) + return false; + else + return true; +} + void SetCentering() { - yprOffset[0] = ArduinoIMU[0]; - yprOffset[1] = ArduinoIMU[1]; - yprOffset[2] = ArduinoIMU[2]; + if (ArduinoRotationType == false) { + yprOffset[0] = ArduinoIMU[0]; + yprOffset[1] = ArduinoIMU[1]; + yprOffset[2] = ArduinoIMU[2]; + } + else { + float length = std::sqrt(ArduinoIMUQuat.x * ArduinoIMUQuat.x + ArduinoIMUQuat.y * ArduinoIMUQuat.y + ArduinoIMUQuat.z * ArduinoIMUQuat.z + ArduinoIMUQuat.w * ArduinoIMUQuat.w); + ArduinoIMUQuat.w /= length; + ArduinoIMUQuat.x /= length; + ArduinoIMUQuat.y /= length; + ArduinoIMUQuat.z /= length; + HMDQuatOffset.w = ArduinoIMUQuat.w; + HMDQuatOffset.x = ArduinoIMUQuat.x; + HMDQuatOffset.z = ArduinoIMUQuat.y; + HMDQuatOffset.z = ArduinoIMUQuat.z; + } } void ArduinoIMURead() @@ -154,28 +188,61 @@ void ArduinoIMURead() DWORD bytesRead; while (HMDConnected) { - ReadFile(hSerial, &ArduinoIMU, sizeof(ArduinoIMU), &bytesRead, 0); - if (CorrectAngleValue(ArduinoIMU[0]) == false || CorrectAngleValue(ArduinoIMU[1]) == false || CorrectAngleValue(ArduinoIMU[2]) == false) - { - // last correct values - ArduinoIMU[0] = LastArduinoIMU[0]; - ArduinoIMU[1] = LastArduinoIMU[1]; - ArduinoIMU[2] = LastArduinoIMU[2]; + // YPR + if (ArduinoRotationType == false) { + ReadFile(hSerial, &ArduinoIMU, sizeof(ArduinoIMU), &bytesRead, 0); + + if (CorrectAngleValue(ArduinoIMU[0]) == false || CorrectAngleValue(ArduinoIMU[1]) == false || CorrectAngleValue(ArduinoIMU[2]) == false) + { + // last correct values + ArduinoIMU[0] = LastArduinoIMU[0]; + ArduinoIMU[1] = LastArduinoIMU[1]; + ArduinoIMU[2] = LastArduinoIMU[2]; + + PurgeComm(hSerial, PURGE_TXCLEAR | PURGE_RXCLEAR); + } + else if (CorrectAngleValue(ArduinoIMU[0]) && CorrectAngleValue(ArduinoIMU[1]) && CorrectAngleValue(ArduinoIMU[2])) // save last correct values + { + LastArduinoIMU[0] = ArduinoIMU[0]; + LastArduinoIMU[1] = ArduinoIMU[1]; + LastArduinoIMU[2] = ArduinoIMU[2]; + + if (HMDInitCentring == false) + if (ArduinoIMU[0] != 0 || ArduinoIMU[1] != 0 || ArduinoIMU[2] != 0) { + SetCentering(); + HMDInitCentring = true; + } + } + + // Quaternion + } else { + ReadFile(hSerial, &ArduinoIMUQuat, sizeof(ArduinoIMUQuat), &bytesRead, 0); + + if (CorrectQuatValue(ArduinoIMUQuat.w) == false || CorrectQuatValue(ArduinoIMUQuat.x) == false || CorrectQuatValue(ArduinoIMUQuat.y) == false || CorrectQuatValue(ArduinoIMUQuat.z) == false) + { + // last correct values + ArduinoIMUQuat.w = LastArduinoIMUQuat.w; + ArduinoIMUQuat.x = LastArduinoIMUQuat.x; + ArduinoIMUQuat.y = LastArduinoIMUQuat.y; + ArduinoIMUQuat.z = LastArduinoIMUQuat.z; + + PurgeComm(hSerial, PURGE_TXCLEAR | PURGE_RXCLEAR); + } + else if (CorrectQuatValue(ArduinoIMUQuat.w) && CorrectQuatValue(ArduinoIMUQuat.x) && CorrectQuatValue(ArduinoIMUQuat.y) && CorrectQuatValue(ArduinoIMUQuat.z)) // save last correct values + { + LastArduinoIMUQuat.w = ArduinoIMUQuat.w; + LastArduinoIMUQuat.x = ArduinoIMUQuat.x; + LastArduinoIMUQuat.y = ArduinoIMUQuat.y; + LastArduinoIMUQuat.z = ArduinoIMUQuat.z; + + if (HMDInitCentring == false) + if (ArduinoIMUQuat.w != 0 || ArduinoIMUQuat.x != 0 || ArduinoIMUQuat.y != 0 || ArduinoIMUQuat.z != 0) { + SetCentering(); + HMDInitCentring = true; + } + } - PurgeComm(hSerial, PURGE_TXCLEAR | PURGE_RXCLEAR); - } - else if (CorrectAngleValue(ArduinoIMU[0]) && CorrectAngleValue(ArduinoIMU[1]) && CorrectAngleValue(ArduinoIMU[2])) // save last correct values - { - LastArduinoIMU[0] = ArduinoIMU[0]; - LastArduinoIMU[1] = ArduinoIMU[1]; - LastArduinoIMU[2] = ArduinoIMU[2]; - - if (HMDInitCentring == false) - if (ArduinoIMU[0] != 0 || ArduinoIMU[1] != 0 || ArduinoIMU[2] != 0) { - SetCentering(); - HMDInitCentring = true; - } } if (bytesRead == 0) Sleep(1); @@ -344,7 +411,7 @@ class CDeviceDriver : public vr::ITrackedDeviceServerDriver, public vr::IVRDispl m_ulPropertyContainer = vr::k_ulInvalidPropertyContainer; //DriverLog( "Using settings values\n" ); - m_flIPD = vr::VRSettings()->GetFloat(k_pch_steamvr_Section, k_pch_SteamVR_IPD_Float ); + m_flIPD = vr::VRSettings()->GetFloat(k_pch_steamvr_Section, k_pch_arduinoHMD_IPD_Float); char buf[1024]; vr::VRSettings()->GetString(k_pch_steamvr_Section, k_pch_arduinoHMD_SerialNumber_String, buf, sizeof( buf ) ); @@ -661,9 +728,17 @@ class CDeviceDriver : public vr::ITrackedDeviceServerDriver, public vr::IVRDispl SetCentering(); // Set head tracking rotation - pose.qRotation = EulerAngleToQuaternion(DegToRad( OffsetYPR(ArduinoIMU[2], yprOffset[2]) ), - DegToRad( OffsetYPR(ArduinoIMU[0], yprOffset[0]) * -1 ), - DegToRad( OffsetYPR(ArduinoIMU[1], yprOffset[1]) * -1 )); + if (ArduinoRotationType == false) { // YPR + pose.qRotation = EulerAngleToQuaternion(DegToRad( OffsetYPR(ArduinoIMU[2], yprOffset[2]) ), + DegToRad( OffsetYPR(ArduinoIMU[0], yprOffset[0]) * -1 ), + DegToRad( OffsetYPR(ArduinoIMU[1], yprOffset[1]) * -1 )); + } else { // Quaternion + // Centered + pose.qRotation.w = ArduinoIMUQuat.w * LastArduinoIMUQuat.w - ArduinoIMUQuat.x * LastArduinoIMUQuat.x - ArduinoIMUQuat.y * LastArduinoIMUQuat.y - ArduinoIMUQuat.z * LastArduinoIMUQuat.z; + pose.qRotation.x = ArduinoIMUQuat.w * LastArduinoIMUQuat.x + ArduinoIMUQuat.x * LastArduinoIMUQuat.w + ArduinoIMUQuat.y * LastArduinoIMUQuat.z - ArduinoIMUQuat.z * LastArduinoIMUQuat.y; + pose.qRotation.y = ArduinoIMUQuat.w * LastArduinoIMUQuat.y - ArduinoIMUQuat.x * LastArduinoIMUQuat.z + ArduinoIMUQuat.y * LastArduinoIMUQuat.w + ArduinoIMUQuat.z * LastArduinoIMUQuat.x; + pose.qRotation.z = ArduinoIMUQuat.w * LastArduinoIMUQuat.z + ArduinoIMUQuat.x * LastArduinoIMUQuat.y - ArduinoIMUQuat.y * LastArduinoIMUQuat.x + ArduinoIMUQuat.z * LastArduinoIMUQuat.w; + } //Set head position tracking pose.vecPosition[0] = fPos[0]; // X @@ -754,6 +829,7 @@ EVRInitError CServerDriver::Init( vr::IVRDriverContext *pDriverContext ) VR_INIT_SERVER_DRIVER_CONTEXT( pDriverContext ); //InitDriverLog( vr::VRDriverLog() ); + ArduinoRotationType = vr::VRSettings()->GetInt32(k_pch_arduinoHMD_Section, k_pch_arduinoHMD_ArduinoRotationType_Bool); ArduinoNotRequire = !vr::VRSettings()->GetInt32(k_pch_arduinoHMD_Section, k_pch_arduinoHMD_ArduinoRequire_Bool); comPortNumber = vr::VRSettings()->GetInt32(k_pch_arduinoHMD_Section, k_pch_arduinoHMD_COM_port_Int32); diff --git a/README.RU.md b/README.RU.md index f18e775..f63a583 100644 --- a/README.RU.md +++ b/README.RU.md @@ -18,7 +18,9 @@ Быстро включать и выключать расширенный VR монитор шлема можно, с помощью оболочки `HMD Assistant` или [MultiMonitorTool](https://www.nirsoft.net/utils/multi_monitor_tool.html). ## Arduino и трекеры вращения -Для отслеживания поворотов головы понадобится купить [Arduino Nano](http://ali.pub/2oy73f) и подключить к ней плату с датчиками вращения, например, [MPU 6050 GY-521](http://ali.pub/2oy76c), MPU 9250, MPU 3200 GY-85 или любую другую при наличии прошивки с выводом значений Yaw, Pitch, Roll (Рысканье, Тангаж, Крен), и калибровкой. Вывод данных происходит бинарный (3 float значения), пример можно посмотреть [тут](https://github.com/TrueOpenVR/TrueOpenVR-DIY/blob/master/HMD/Arduino/Arduino.Output.Bin.ino). +Подойдут любые COM трекеры с выводом 3 float значения yaw, pitch, roll или 4 float значения кватерниона w, x, y, z. Скорость вывода должна составлять. + +Для отслеживания поворотов головы понадобится купить [Arduino Nano](http://ali.pub/2oy73f) и подключить к ней плату с датчиками вращения, например, [MPU 6050 GY-521](http://ali.pub/2oy76c), MPU 9250, MPU 3200 GY-85 или любую другую при наличии прошивки с выводом трёх float значений yaw, pitch, roll (рысканье, тангаж, крен) или 4 float значения кватерниона w, x, y, z и калибровкой. Вывод данных происходит бинарный (3 float или 4 float значения), пример можно посмотреть [тут](https://github.com/TrueOpenVR/TrueOpenVR-DIY/blob/master/HMD/Arduino/Arduino.Output.Bin.ino). При использовании кватерниона измените значение `RotationQuaternion` на `true`. Скорость вывода должна составлять `115200`. Готовая прошивка Arduino есть для [MPU 3200 GY-85](http://alli.pub/5wxnyl), называется она [Razor AHRS](https://github.com/Razor-AHRS/razor-9dof-ahrs/tree/master/Arduino). Вместе с ней идет программа для калибровки и демонстрации. После калибровки замените файл "Output.ino", в папке с прошивкой, на [этот](https://github.com/TrueOpenVR/TrueOpenVR-DIY/blob/master/HMD/Arduino/Razor_AHRS/Output.ino). Здесь важно отметить, что появились новые ревизии GY-85, которые несовместимы с этой прошивкой. Прошивкой поддерживаются следующие сенсоры: акселерометр ADXL345, гироскоп ITG-3200 и магнитометры HMC5843, HMC5883L. Инструкцию по калибровке можно найти на [youtube](https://www.youtube.com/watch?v=J7K_TnzQBZk). @@ -29,8 +31,6 @@ ![](https://user-images.githubusercontent.com/9499881/52521728-e200dc80-2c94-11e9-9628-68ea3ef3dacd.png) -Скорость должна быть изменена на `115200`, если по умолчанию задана другая. - ## Параметры файла конфигурации Название | Описание ------------ | ------------- @@ -38,16 +38,16 @@ COMPort | Номер COM порта Arduino, можно посмотреть в CenteringKey | Код кнопки центрирования изображения, изменить кнопку можно в файле конфигурации, вписав [нужное название кнопки](https://github.com/r57zone/DualShock4-emulator/blob/master/BINDINGS.RU.md)). CrouchPressKey | Код кнопки приседания, изменить кнопку можно в файле конфигурации, вписав [нужное название кнопки](https://github.com/r57zone/DualShock4-emulator/blob/master/BINDINGS.RU.md)). Необходимо для связи с другими драйверами, например, используя контроллеры Razer Hydra и используя [этот драйвер](https://github.com/r57zone/Razer-Hydra-SteamVR-driver) можно приседать. CrouchOffset | Высота приседания по нажатию кнопки. +FOV | Градус поля зрения. Можно увеличить, в зависимости от линз VR гарнитуры. +IPD | Межзрачковое расстояние. DistanceBetweenEyes | Расстояние между стерео изображениями, чем больше, тем ближе. DistortionK1, DistortionK2 | Коэффициенты искажения линз. ScreenOffsetX | Сдвиг изображения по горизонтали. ZoomHeight, ZoomWidth | Коэффициенты масштабирования стерео изображений. -FOV | Градус поля зрения. Можно увеличить, в зависимости от линз VR гарнитуры. -ipd | Межзрачковое расстояние. -displayFrequency | Частота обновления экрана. -renderWidth, renderHeight | Разрешение рендера изображения для одного глаза. -windowWidth, windowHeight | Высота и ширина выводимого окна. -windowX, windowY | Смещение окна, требуется для отображения на других мониторах (расширенных). Например, для отображения на втором дисплее, который отображается справа, нужно указать значение 1920 (при условии, что первый дисплей имеет разрешение 1920 на 1080). Точные данные можно просмотреть, с помощью [MultiMonitorTool утилиты](https://www.nirsoft.net/utils/multi_monitor_tool.html), которая также может выключать и включить второй монитор, через bat-файл. +DisplayFrequency | Частота обновления экрана. +RenderWidth, RenderHeight | Разрешение рендера изображения для одного глаза. +WindowWidth, WindowHeight | Высота и ширина выводимого окна. +WindowX, WindowY | Смещение окна, требуется для отображения на других мониторах (расширенных). Например, для отображения на втором дисплее, который отображается справа, нужно указать значение 1920 (при условии, что первый дисплей имеет разрешение 1920 на 1080). Точные данные можно просмотреть, с помощью [MultiMonitorTool утилиты](https://www.nirsoft.net/utils/multi_monitor_tool.html), которая также может выключать и включить второй монитор, через bat-файл. DebugMode | Режим отладки, заблокирован на 30 FPS. Рекомендуется после проверки отключить (поставить `false`). ArduinoRequire | Требование подключенного Arduino, для запуска драйвера. Параметр необходим для быстрых тестов контроллеров, чтобы не подключать и одевать шлем для тестов. Для отключения измените на `false`. diff --git a/README.md b/README.md index fa110eb..a377ca2 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ If you are using an Android smartphone, then it will probably be easier to use [ You can quick turn on and turn off the extended VR monitor of the HMD using the `HMD Assistant` utility or [MultiMonitorTool](https://www.nirsoft.net/utils/multi_monitor_tool.html). ## Arduino & rotation trackers -To track the turns of the head, you will need to buy [Arduino Nano](http://ali.pub/2oy73f) and connect a board with rotation sensors to it, for example, [MPU 6050 GY-521](http://ali.pub/2oy76c), MPU 9250, MPU 3200 GY-85 or any other if there is a firmware with the output of Yaw, Pitch, Roll values and calibration. The data output is binary (3 float values), an example can be viewed [here](https://github.com/TrueOpenVR/TrueOpenVR-DIY/blob/master/HMD/Arduino/Arduino.Output.Bin.ino). +To track the turns of the head, you will need to buy [Arduino Nano](http://ali.pub/2oy73f) and connect a board with rotation sensors to it, for example, [MPU 6050 GY-521](http://ali.pub/2oy76c), MPU 9250, MPU 3200 GY-85 or any other firmware with output of three float values yaw, pitch, roll or 4 float values of quaternion w, x, y, z and calibration. The data output is binary (3 float or 4 float values), an example can be viewed [here](https://github.com/TrueOpenVR/TrueOpenVR-DIY/blob/master/HMD/Arduino/Arduino.Output.Bin.ino). The output speed should be `115200`. There is a ready-made Arduino firmware for the [MPU 3200 GY-85](http://alli.pub/5wxnyl), it is called [Razor AHRS](https://github.com/Razor-AHRS/razor-9dof-ahrs/tree/master/Arduino). Along with it comes a program for calibration and demonstration. After calibration, replace the file "Output.ino", in the firmware folder, with [this](https://github.com/TrueOpenVR/TrueOpenVR-DIY/blob/master/HMD/Arduino/Razor_AHRS/Output.ino). It is important to note here that there are new revisions of GY-85 that are incompatible with this firmware. The following sensors are supported by the firmware: the ADXL345 accelerometer, the ITG-3200 gyroscope and the HMC5843, HMC5883L magnetometers. The calibration instructions can be found on [youtube](https://www.youtube.com/watch?v=J7K_TnzQBZk). @@ -31,8 +31,6 @@ There is a ready-made Arduino firmware for [MPU 6050 GY-521](http://ali.pub/2oy7 ![](https://user-images.githubusercontent.com/9499881/52521728-e200dc80-2c94-11e9-9628-68ea3ef3dacd.png) -The speed should be changed to `115200` if a different one is set by default. - ## Configuration file options Name | Description ------------ | ------------- @@ -40,16 +38,16 @@ COMPort | The number of the Arduino COM port can be found in the Devices Manager CenteringKey | The code of the picture centering key, you can change the key in the configuration file by typing [the desired name code](https://github.com/r57zone/DualShock4-emulator/blob/master/BINDINGS.md)). CrouchPressKey | The code of the crouch key, you can change the key in the configuration file by typing [the desired name code](https://github.com/r57zone/DualShock4-emulator/blob/master/BINDINGS.md)). It is necessary for communication with other drivers, for example, using Razer Hydra controllers and using [this driver](https://github.com/r57zone/Razer-Hydra-SteamVR-driver) you can crouch. CrouchOffset | The height of the crouch at the press of a button. +FOV | Degree of field of view. You can zoom in, depending on the VR headset lenses. +IPD | Interpupillary distance. DistanceBetweenEyes | The distance between stereo images, the larger the closer. DistortionK1, DistortionK2 | Lens distortion factors. ScreenOffsetX | Horizontal image shift. ZoomHeight, ZoomWidth | Scaling factors of stereo images. -FOV | Degree of field of view. You can zoom in, depending on the VR headset lenses. -ipd | Interpupillary distance. -displayFrequency | Screen refresh rate. -renderWidth, renderHeight | Image rendering resolution for one eye. -windowWidth, windowHeight | Height and width of the displayed window. -windowX, windowY | Window offset is required for display on other monitors. For example, to display on the second display, which is displayed on the right, you need to specify the value 1920 (provided that the first display has a resolution of 1920 by 1080). The exact data can be viewed using the [MultiMonitorTool utility](https://www.nirsoft.net/utils/multi_monitor_tool.html), and also with it you can turn off and turn on the second monitor via a bat file. +DisplayFrequency | Screen refresh rate. +RenderWidth, RenderHeight | Image rendering resolution for one eye. +WindowWidth, WindowHeight | Height and width of the displayed window. +WindowX, WindowY | Window offset is required for display on other monitors. For example, to display on the second display, which is displayed on the right, you need to specify the value 1920 (provided that the first display has a resolution of 1920 by 1080). The exact data can be viewed using the [MultiMonitorTool utility](https://www.nirsoft.net/utils/multi_monitor_tool.html), and also with it you can turn off and turn on the second monitor via a bat file. DebugMode | Debug mode, locked at 30 FPS. After checking, it is recommended to set it to `false` (disable). ArduinoRequire | Requires a connected Arduino to start the driver. The parameter is necessary for quick tests of controllers, so as not to connect and put on a hmd for tests. To disable, change to `false`.