Skip to content

kmart/benq-rd280ua

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BenQ RD280UA monitor

Introduction

Picked up a BenQ RD280UA monitor some time ago. This monitor supports BenQs MoonHalo, Eye Care etc. BenQ offers the Display Pilot 2 software for controlling the monitor from a PC. But this software is unfortunately currently not available on Linux, only on Mac and Windows.

As I suspected that I2C was used for the DDC communication to the monitor and because I had an I2C Driver unit lying around, I took some inspiration from the ddcutil sniffing page to see if there was any I2C traffic flowing when running the Pilot 2 software on a Windows PC. To make the setup a bit easier I obtained a HDMI breakout board from Aliexpress for the purpose.

HDMI breakout

The breakout board and and the I2C Driver can be seen in the image above. The GND line on the I2C Driver is connected to pin 17 on the HDMI breakout board and the SCL and SDA lines to pin 15 and 16 respectively.

The setup worked very well. The breakout board with the I2C Driver connected caused no issues with the HDMI signal. No problem with 4K and HDR.

After some trials I was able to see some traffic using the I2C Driver capture script by running the command

$ python3 samples/capture.py /dev/ttyUSB0

This gave output in CSV format like this:

STOP,,,
START,WRITE,55,ACK
BYTE,WRITE,81,ACK
BYTE,WRITE,130,ACK
BYTE,WRITE,1,ACK
BYTE,WRITE,217,ACK
BYTE,WRITE,101,ACK
STOP,,,
START,READ,55,ACK
BYTE,READ,110,ACK
BYTE,READ,136,ACK
BYTE,READ,2,ACK
BYTE,READ,0,ACK

Reformatting, using a throwaway script, makes the output bit easier to read:

 WRITE 0x37 0x51 0x82 0x01 0xd1 0x6d
  READ 0x37 0x6e 0x88 0x02 0x00 0xd1 0x00 0x00 0x02 0x00 0x00 0x67
 WRITE 0x37 0x51 0x82 0x01 0x62 0xde
  READ 0x37 0x6e 0x88 0x02 0x00 0x62 0x00 0x00 0x32 0x00 0x00 0xe4
 WRITE 0x37 0x51 0x82 0x01 0x8d 0x31
  READ 0x37 0x6e 0x88 0x02 0x00 0x8d 0x00 0x00 0x02 0x00 0x01 0x3a
 WRITE 0x37 0x51 0x84 0x03 0xd7 0x02 0x20 0x4d
 WRITE 0x37 0x51 0x84 0x03 0xd7 0x02 0x10 0x7d
 WRITE 0x37 0x51 0x82 0x01 0xdc 0x60
  READ 0x37 0x6e 0x88 0x02 0x00 0xdc 0x00 0x00 0x32 0x00 0x12 0x48
 WRITE 0x37 0x51 0x82 0x01 0xd9 0x65
  READ 0x37 0x6e 0x88 0x02 0x00 0xd9 0x00 0x07 0x0a 0x03 0x08 0x6b
 WRITE 0x37 0x51 0x82 0x01 0xd7 0x6b
  READ 0x37 0x6e 0x88 0x02 0x00 0xd7 0x00 0x02 0x31 0x02 0x10 0x42
 WRITE 0x37 0x51 0x82 0x01 0xe2 0x5e

A pattern emerged quite soon. First a WRITE line with 6 bytes followed by a READ line with 12 bytes. This sequence went on and on as long as I didn't do anything in the Pilot 2 software. But as soon I pressed a button, f.ex. the button for turning the MoonHalo light on, there would be a WRITE line with 8 bytes in the output. This WRITE line was not followed by a READ line.

Basically it turns out that the 8 byte WRITE lines are commands to the monitor and the 6 bytes WRITE lines with corresponding READ lines are "status/query" commands reading the current settings from the monitor.

Testing

The WRITE line for switching the MoonHalo light on and off turned out to the following two commands:

0x37 0x51 0x84 0x03 0xd7 0x02 0x20 0x4d   On
0x37 0x51 0x84 0x03 0xd7 0x02 0x10 0x7d   Off

To verify this the i2ctransfer command can be used. To switch the light on:

$ i2ctransfer -y 12 w7@0x37 0x51 0x84 0x03 0xd7 0x02 0x20 0x4d

and off:

$ i2ctransfer -y 12 w7@0x37 0x51 0x84 0x03 0xd7 0x02 0x10 0x7d

(The i2ctransfer command is typically located in the /usr/sbin/ directory, which might not be present in the PATH environment variable. In order to be able to run the i2ctransfer command as a normal user, the user must be a member of the i2c group.)

The -y 12 parameter refers to the I2C bus the monitor is connected to. This address can be found by running the ddcutil detect command:

ddcutil detect
Display 1
   I2C bus:  /dev/i2c-12
   DRM_connector:           card0-DP-2
   EDID synopsis:
      Mfg id:               BNQ - UNK
      Model:                BenQ RD280UA
   ...

To see what I2C devices that are connected to this bus:

$ /usr/sbin/i2cdetect -y 12
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 -- -- -- -- -- -- 37 -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- 4a 4b -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

It turns out the at address 0x37 is the address used for monitor control. The address is not included in the length of a I2C command, which is why the length given to i2ctransfer is set to w7@.

Checksum

Something that can be seen from the

$ i2ctransfer -y 12 w7@0x37 0x51 0x84 0x03 0xd7 0x02 0x10 0x7d

command, is that the last byte must be some sort of checksum. After some fumbling it turns out that if you take the XOR of all the values you get the 0x59 value.

0x37 0x51 0x84 0x03 0xd7 0x02 0x10 0x7d  =>  0x59

And by replacing the last byte with 0x59 you will get 0x7d back again.

This proved to be true for all the collected commands and makes it easy to both calculate and to verify the checksum value.

To calculate the XOR value the calc-crc.py script can be used. Example:

$ ./scripts/calc-crc.py 0x37 0x51 0x84 0x03 0xd7 0x02 0x20 0x59
Checksum value: 0x4d

Command format

The 8 byte long commands follows the same structure (here the command for turning on the MoonHalo light):

address command checksum
0x37 0x51 0x84 0x03 0xd7 0x02 0x20 0x4d

Commands

Below follows an overview of all the found I2C sequences that triggers a change on the monitor. The checksum byte is removed from the entries as this value can be calculated anyway. The organization of the commands follows the same structure as the menu on the monitor when using the monitor controls.

For convenience a full list of commands with checksum added can be seen here.

Input

Input
0x37 0x51 0x84 0x03 0x60 0x00 0x13 USB-C
0x37 0x51 0x84 0x03 0x60 0x00 0x11 HDMI
0x37 0x51 0x84 0x03 0x60 0x00 0x0f DP

Coding Booster

MoonHalo

Switch
0x37 0x51 0x84 0x03 0xd7 0x02 0x20 On
0x37 0x51 0x84 0x03 0xd7 0x02 0x10 Off
Light Mode
0x37 0x51 0x84 0x03 0xd7 0x01 0x20 270°
0x37 0x51 0x84 0x03 0xd7 0x02 0x20 360°
Brightness
0x37 0x51 0x84 0x03 0xd9 0x03 [0x01 ... 0x0a] 1 ... 10
Color Temperature
0x37 0x51 0x84 0x03 0xd9 [0x01 ... 0x07] 0x07 1 ... 7

EcoPrivacy

Activation Time
0x37 0x51 0x84 0x03 0xe7 0x00 0x00 Off
0x37 0x51 0x84 0x03 0xe7 0x00 0x05 5 sec
0x37 0x51 0x84 0x03 0xe7 0x00 0x0a 10 sec
0x37 0x51 0x84 0x03 0xe7 0x00 0x14 20 sec
0x37 0x51 0x84 0x03 0xe7 0x00 0x1e 30 sec
0x37 0x51 0x84 0x03 0xe7 0x00 0x3c 60 sec
Sensor Sensitivity
0x37 0x51 0x84 0x03 0xe9 0x00 0x01 Near
0x37 0x51 0x84 0x03 0xe9 0x00 0x02 Middle
0x37 0x51 0x84 0x03 0xe9 0x00 0x03 Far

Eye Care

Night Hour Protection

Switch
0x37 0x51 0x84 0x03 0xd1 0x00 0x01 On
0x37 0x51 0x84 0x03 0xd1 0x00 0x00 Off
0x37 0x51 0x84 0x03 0xd1 0x00 0x02 Auto
Level
0x37 0x51 0x84 0x03 0xd0 0x00 [0x01 ... 0x0a] 1 ... 10

Low Blue Light Plus

Value
0x37 0x51 0x84 0x03 0x19 0x00 [0x00 ... 0x05] 0 ... 5

B.I. Gen2

On/Off
0x37 0x51 0x84 0x03 0xe2 0x00 0xff On
0x37 0x51 0x84 0x03 0xe2 0x00 0x00 Off
Light Meter
0x37 0x51 0x84 0x03 0xe3 0x00 0x01 On
0x37 0x51 0x84 0x03 0xe3 0x00 0x00 Off
Sensor Sensitivity
0x37 0x51 0x84 0x03 0xe5 0x00 [0x00 ... 0x0a] 0 ... 10
Color Weakness
0x37 0x51 0x84 0x03 0xfd [0x00 ... 0x0a] 0x03 Red 0 ... 10
0x37 0x51 0x84 0x03 0xfd [0x00 ... 0x0a] 0x04 Green 0 ... 10

For Color Weakness either the Red or the Green scale can be changed. That is if the red color is set to a value greater than zero then the green color will have zero as its value.

Color Mode

Mode
0x37 0x51 0x84 0x03 0xdc 0x00 0x31 Coding - Dark Theme
0x37 0x51 0x84 0x03 0xdc 0x00 0x30 Coding - Light Theme
0x37 0x51 0x84 0x03 0xdc 0x00 0x0f M-Book
0x37 0x51 0x84 0x03 0xdc 0x00 0x32 Cinema
0x37 0x51 0x84 0x03 0xdc 0x00 0x1f ePaper
0x37 0x51 0x84 0x03 0xdc 0x00 0x0a sRGB
0x37 0x51 0x84 0x03 0xdc 0x00 0x12 User

For each of the modes the following characteristics can be adjusted, but not all modes supports all characteristics.

Brightness
0x37 0x51 0x84 0x03 0x10 0x00 [0x00 ... 0x64] 0 ... 100
Contrast
0x37 0x51 0x84 0x03 0x12 0x00 [0x00 ... 0x64] 0 ... 100
Sharpness
0x37 0x51 0x84 0x03 0x87 0x00 [0x01 ... 0x0a] 1 ... 10
Saturation
0x37 0x51 0x84 0x03 0x8a 0x00 [0x01 ... 0x0a] 1 ... 10
Gamma
0x37 0x51 0x84 0x03 0x72 0x00 0x50 1
0x37 0x51 0x84 0x03 0x72 0x00 0x64 2
0x37 0x51 0x84 0x03 0x72 0x00 0x78 3
0x37 0x51 0x84 0x03 0x72 0x00 0x8c 4
0x37 0x51 0x84 0x03 0x72 0x00 0xa0 5
Color Temperature
0x37 0x51 0x84 0x03 0x14 0x00 0x05 Normal
0x37 0x51 0x84 0x03 0x14 0x00 0x08 Bluish
0x37 0x51 0x84 0x03 0x14 0x00 0x04 Reddish
0x37 0x51 0x84 0x03 0x14 0x00 0x0b User Define

If Color Temperature is set to User Define then the Red, Green and Blue colors can be set explicitly.

Red
0x37 0x51 0x84 0x03 0x16 0x00 [0x00 ... 0x64] 0 ... 100
Green
0x37 0x51 0x84 0x03 0x18 0x00 [0x00 ... 0x64] 0 ... 100
Blue
0x37 0x51 0x84 0x03 0x1a 0x00 [0x00 ... 0x64] 0 ... 100

The following command resets the colors.

Reset Color
0x37 0x51 0x84 0x03 0x08 0x00 0x01

Audio

Volume Level
0x37 0x51 0x84 0x03 0x62 0x00 [0x00 ... 0x32] 0 ... 50

The menu for Audio control on the monitor includes in addition the following:

  • Mute On/Off
  • Audio Scenario (Standard, Dialogue, Music)

Unfortunately the Pilot 2 software don't seems to have options for adjusting these settings.

Monitoring commands

As mentioned in the introduction the collected traces also includes WRITE lines that are 6 byte long, together with corresponding READ lines returning 12 bytes. These WRITE lines reads the current settings from the monitor.

There are 22 unique WRITE lines that gets repeated for reading the settings from the monitor. These 22 lines are repeated over and over again. The 22 lines are:

0x37 0x51 0x82 0x01 0xdc 0x60
0x37 0x51 0x82 0x01 0xd9 0x65
0x37 0x51 0x82 0x01 0xd7 0x6b
0x37 0x51 0x82 0x01 0xe2 0x5e
0x37 0x51 0x82 0x01 0x10 0xac
0x37 0x51 0x82 0x01 0x12 0xae
0x37 0x51 0x82 0x01 0x87 0x3b
0x37 0x51 0x82 0x01 0x14 0xa8
0x37 0x51 0x82 0x01 0x16 0xaa
0x37 0x51 0x82 0x01 0x18 0xa4
0x37 0x51 0x82 0x01 0x1a 0xa6
0x37 0x51 0x82 0x01 0x8a 0x36
0x37 0x51 0x82 0x01 0x72 0xce
0x37 0x51 0x82 0x01 0xfd 0x41
0x37 0x51 0x82 0x01 0xe6 0x5a
0x37 0x51 0x82 0x01 0xe7 0x5b
0x37 0x51 0x82 0x01 0xe9 0x55
0x37 0x51 0x82 0x01 0x60 0xdc
0x37 0x51 0x82 0x01 0x19 0xa5
0x37 0x51 0x82 0x01 0xd1 0x6d
0x37 0x51 0x82 0x01 0x62 0xde
0x37 0x51 0x82 0x01 0x8d 0x31

The last byte in each line is the checksum. The XOR of a line gives the value 0x59. And by replacing the last byte with this value you'll get the checksum value back again.

As seen in the introduction the READ lines are 12 bytes long, including the first byte which always is 0x37. With the i2ctransfer command the leading byte is always skipped and only 11 bytes should be read back. Example:

i2ctransfer -y 12 w5@0x37 0x51 0x82 0x01 0x18 0xa4 r11
0x6e 0x88 0x02 0x00 0x18 0x00 0x00 0x64 0x00 0x64 0xac

The XOR of the returned bytes is turns out to always be 0x50. Similar as for commands the responses can be verified against this value.

Getting the monitor settings

After some experimentation running the commands above could indeed be used to read the current settings from the monitor. What you get back from running all the 22 command and reading the response is a 22 by 10 two-dimensional array, excluding the checksum byte from the response, containing the current settings.

By first "dumping" the settings followed by running one of the monitor control commands and then followed by a new dump and comparing the dumps, it was possible to find out which index in the two-dimensional array that represented a specific setting.

Below follows is an overview of what was found. The order is again the same as in the menu displayed on the monitor when using the monitor controls.

Input

pos value
USB-C [9][17] 0x13
HDMI [9][17] 0x11
DP [9][17] 0x0f

Coding Booster

pos value
MoonHalo On [9][2] 0x20
Off [9][2] 0x10
270° [8][2] 0x01
360° [8][2] 0x02
Brightness [9][1] 0x01 ... 0x0e
Temperature [8][1] 0x01 ... 0x07
EcoPrivacy Activation Time Off [9][15] 0x00
5 Sec [9][15] 0x05
10 Sec [9][15] 0x0a
20 Sec [9][15] 0x14
30 Sec [9][15] 0x1e
60 Sec [9][15] 0x3c
Sensor Sensitivity Near [9][16] 0x01
Middle [9][16] 0x02
Far [9][16] 0x03

Eye Care

pos value
Night Hours Protection Switch On [9][19] 0x01
Off [9][19] 0x00
Auto [9][19] 0x02
Low Blue Light Plus [9][18] 0x00 ... 0x05
B.I. Gen2 On [9][3] 0xff
Off [9][3] 0x00
Color Weakness R [9][13] 0x03
[8][13] 0x00 ... 0x0a
G [9][13] 0x04
[8][13] 0x00 ... 0x0a

Color Mode

pos value pos value
Coding - Dark Theme [9][0] 0x31
Coding - Light Theme [9][0] 0x30
M-Book [9][0] 0x0f
Cinema [9][0] 0x32
ePaper [9][0] 0x1f
sRGB [9][0] 0x0a
User [9][0] 0x12 Brightness [9][4] 0x00 ... 0x64
Contrast [9][5] 0x00 ... 0x64
Sharpness [9][6] 0x00 ... 0x0a
Saturation [9][11] 0x01 ... 0x0a
Gamma 1 [9][12] 0x50
Gamma 2 [9][12] 0x64
Gamma 3 [9][12] 0x78
Gamma 4 [9][12] 0x8c
Gamma 5 [9][12] 0x0a
Temperature (Normal) [9][7] 0x05
Temperature (Bluish) [9][7] 0x08
Temperature (Reddish) [9][7] 0x04
Temperature (User Define) [9][7] 0x0b
Red [9][8] 0x00 ... 0x64
Green [9][9] 0x00 ... 0x64
Blue [9][10] 0x00 ... 0x64

Audio

pos value
Volume [9][20] 0x00 ... 0x32
Mute On [9][21] 0x01
Mute Off [9][21] 0x02

Mute on/off was toggled by using the controls on the monitor.

Scripting

When writing I2C commands to the monitor, ensure that there is a delay between each command sent. Otherwise the command might have no effect, resulting in garbage being read back or overloading the monitor. A suitable delay could be from 200 to 500 msec.

While the monitor settings can be read in an endless loop, it is probably best to stop reading the settings as soon as no commands are being sent to the monitor. For reference the Display Pilot 2 software stops reading the settings when the application is minimized or is out of "focus".

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Contributors

Languages