tttttx2 / ddcvcp

Arduino library to control external monitors over DDC/CI VCP by slightly abusing the VGA/DVI/HDMI port.
44 stars 6 forks source link

Trouble getting it to work #3

Closed mattboy9921 closed 1 year ago

mattboy9921 commented 2 years ago

I've been trying all night to get this library to work with my monitor. I generally use ClickMonitorDDC to switch the inputs so I know it is compatible. However, no command I send seems to work. It seems to connect correctly, it says "Found DDC/CI successfully." in the serial monitor. I tried with and without the 5v line. I also tried a shorter VGA cable but still no luck.

I was able to read values with ddc.getSource() but the value seems to change each time I read it, not sure why. Any command to set the value of something seems to do nothing. Am I missing something here? I would assume if it says it found DDC/CI, that I have the pins correct.

I am using an ESP32 devkit board.

tttttx2 commented 2 years ago

Currently getSource and setSource are hardcoded on 0x60, maybe your screen uses different commands for setting the input source? Maybe you could try with something like ddcutil if it works?

The found DDC/CI message is triggered after the ESP manages to send a transmission over I2C. Not sure if this is dependable enough to check proper pinout, but most likely it is.

A potential issue is that the ddc/ci standard has OEM specific commands and not all monitors are created equally, unfortunately. Could you check the capabilities of your screen with ddcutil to verify it supports 0x60 for input source? Or try if you can set the brightness instead of the input?

mattboy9921 commented 2 years ago

So these are the VCP codes according to ClickMonitorDDC:

image

If I run Input Select, it appears to be 0x60. I can put in 1, 3 or 17 and it switches to the input in question.

image

I did try the example code originally to have the brightness cycle but that never worked. The only command that seems to be doing something for me is getSource() but that is returning different values each time.

This is my code:

#include "DDCVCP.h" 
#include "inttypes.h"

DDCVCP ddc;

void setup() {
  Serial.begin(9600);

  while (!ddc.begin()) {
    Serial.print(F("-  Unable to find DDC/CI. Trying again in 5 seconds.\n"));
    delay(5000);
  }
  Serial.print(F("-  Found DDC/CI successfully.\n"));
}

void loop() {
    uint16_t connector;
    connector = ddc.getSource();
    Serial.printf("val = %04x\r\n", connector);
    Serial.printf("%" PRIu16 "\n", connector);
    delay(3000);
}

Which produces this in the serial monitor:

image

It switches between those three values randomly.

The only things I can think of is maybe something is weird about the clock signal, is it possible they could be mismatched? Could there be something about the voltages? The ESP is using 3.3v. The other thing I have read here is that you have to keep the line busy for the monitor to accept DDC signals. Maybe just the 4 pins aren't enough? But I thought the monitor detected VGA plugged in via the 5v signal.

tttttx2 commented 2 years ago

Nice, thanks for the detailed response!

The issue with needing to keep the input active for ddc to work might actually be related to your issue and to the other issue that is still open. I'll give that a try in the next few days, however, have limited capabilities in reproducing the issues on my screens here as they're mostly the same model.

Do you have the ability to sniff I2C commands to your screen generated from ClickMonitorDDC and compare them to the ones generated by the esp? Just in case I messed up the checksum calculation (my screen does not verify any checksums so it's kinda untested)

And yes, it might be a voltage issue. If you have a level shifter or a 5V arduino laying around, feel free to give it a try with that :)

mattboy9921 commented 2 years ago

I do have access to many different screen models (work in IT). The one I'm currently trying to interface with is a BenQ GL2460. If need be I can try it on other screens, but I can confirm that ClickMonitorDDC does work with the BenQ.

I'm not sure how I would go about sniffing I2C. Is there a program that can listen to the I2C signals being sent over the video output? Or maybe would I have to essentially get the video signal from a computer, plug the relevant pins into a bread board with an Arduino, then bridge all the pins to another cable that I'd plug into the monitor?

I will also try to use a 5V arduino when I get home and report back if that changes anything.

mattboy9921 commented 2 years ago

Well, I have spent the last few hours tinkering but haven't got much. The 5V Arduino was freezing before it could even write to the serial monitor. I added pull up resistors to the SDA and SCK pins, which actually let the Arduino establish I2C. I experimented with different resistor values, but nothing seemed to make a difference above about 680 ohm.

The trouble is that regardless of what I try to read, all I ever get is all 0's. Trying to write appears to be ignored as well. When I manually switch the monitor to the VGA input, it sits for a few seconds before switching back to DVI/HDMI, suggesting to me that the monitor does not think VGA is plugged in. Perhaps there is more needed than the 5V key pin to detect signal?

tttttx2 commented 1 year ago

Sorry for the delay in responding. I'll get back to you within the next few days.

I'm planning to add the function of emulating additional VGA signals in a limited way, so we can try if that fixes your issue. Maybe simply adding a pixel clock, and if required line clock, could solve the issue. The screen most likely switches back automatically as right now I'm not creating the full VGA signal, but rather just a small (and optional) part of it. Personally on my screens I disabled the automatic switching out of personal preference, but it would probably still go to stsndby then.

mattboy9921 commented 1 year ago

I really appreciate your efforts with this, thank you.

My monitors do have the feature to auto switch to an input but I have those options turned off, I think they might relate only to digital signals. Interestingly, the VGA input never says "No signal detected" but again, that might just be that that the monitor doesn't detect the signals you are mentioning implementing.

As soon as you post an update, I'll test it out and let you know the results.

mattboy9921 commented 1 year ago

@tttttx2 any progress update on this? I'm eager to try it out 👀

tttttx2 commented 1 year ago

Indeed I do have an update for this. Was messing around with some timers but in the end decided to Frankenstein some existing stuff together, I'll do it properly if that actually fixes things. maybe ;)

Basically I used the bitluni ESP32lib to generate the hsync/vsync timing signals (so if you test it, install that one first in the arduino IDE. Should be in the library manager by default I think). At least on my monitor this works, the VGA input does stay active (just a black picture basically), and the brightness toggles through.

In addition to the pins you already had, you have to hookup H_Sync and V_Sync (think ESP32 Pin 33/32 to 14/13 on VGA if I'm not mistaken). Personally I just hook up the GPIO straight to the VGA connector, no resistors or anything in between, but YMMV.

The example code is here: https://github.com/tttttx2/ddcvcp/blob/master/examples/setBrightness_activeSignalGeneration/setBrightness_activeSignalGeneration.ino

Let me know if that works

mattboy9921 commented 1 year ago

So I have tried the new code. It appears the monitor does see the signal and will stay on the source. This happens regardless of connecting the RGB pins. When connected, I do see a weird pattern of dots on the screen, but shouldn't matter.

Even with this, I am not able to read or write DDC correctly. I tried the set brightness given in the example with no luck. I have it reading source and brightness every 3 seconds but it randomly gives me a value of 0000, b80b or ffff but that is it. It does not change if I change the input or the brightness manually. This was the same behavior I saw before the new code.

I also tried disabling DDC but it was still reading the same values. Perhaps there is something wrong with how it is sending/receiving the data?

Edit:

I just tried an Acer monitor I have and it was able to change the brightness, but not super reliably. Randomly it would change between 10, 50 and 100 but it wasn't every 3 seconds like the code would try. Maybe the commands are happening too fast? Or there's something it is missing in the commands and it has to wait for a time out or for something to match up?

tttttx2 commented 1 year ago

Ah nice, so we're getting somewhere at least ;)

The commands might be too fast, no idea how long screens take to process them. Possibly also the esp32 lib blocking some things intermittently, as that uses quite some more processing power, didn't test that.

Regarding the pixels: if rgb pins are connected, you should now have a completely black screen. Maybe there is some kind of interference issue, often I had issues in unrelated projects coming from some usb power supplies. If possible, you could try the following options and see if one of them reduces the strange pixels: USB port on the monitor, USB port on the laptop while running on battery, USB Powerbank. If this was indeed an interference issue, that might even fix the I2C communication if we're lucky.

mattboy9921 commented 1 year ago

So I did try it in a hub rather than direct connected to my laptop. Other options wouldn't work for me since the laptop battery is dead and my monitor doesn't have USB ports :P

Unfortunately no difference was made. What I do want to try is to use a second ESP32 to sniff the data going to the monitor. I have to figure out how that would work but I think it should be possible. I've used this code here but I can't make any sense of it.

What do you suggest to continue troubleshooting? I really want this to work and feel like it is so close.

tttttx2 commented 1 year ago

Ah sad. Maybe from a usb powerbank, or from a usb wall adaptor directly? It should work even if the serial console is ignored. Because the strange pixel behaviour really sounds strange.

What kind of compiling/flashing/board options did you set? And what esp32 board are you using? Maybe we can find some difference there to my setup.

Never tried an esp32 for I2C sniffing, personally I use a saleae clone from aliexpress for that.

If you're comfortable to share this here, in what country are you residing in? If postage isn't too much I could put another board together and ship it maybe.

mattboy9921 commented 1 year ago

Do you think the strange pixel behavior might be related to why this particular screen is having trouble receiving commands?

I'm using the Arduino IDE, board is set to ESP32 Dev Module as seen below:

image

I just got the boards off aliexpress, they say ESP32 DEVKITV1 on them.

I am in the US, if that helps.

mattboy9921 commented 1 year ago

I have an update. I have managed to read some meaningful data from the monitor! I basically rewrote a sketch using Wire and your code so I could enable serial printing of the char sequence sent back. I am getting:

6E80BE

Which to my understanding, 6E is just the I2C address 37 shifted left 1, 80 is the length of the message and BE is the checksum. So the monitor is sending back a null message. I am certain the message I am sending:

51820110XX

Where XX is the checksum computed by:

(_I2CAddress << 1) ^ _I2CHost ^ 0x82 ^ 0x01 ^ 0x60

Should be valid, no? I can confirm my monitor can have brightness controlled via DDC using the program within Windows, so I know it isn't sending a null as a response to an unsupported VCP code. I am not sure what is missing, however, that causes it to respond that way. My full code:

#include <Wire.h>

// I2C Address
uint8_t _I2CAddress = 0x37;
uint8_t _I2CHost = 0x51;

void setup() {
  // Start serial output
  Serial.begin(9600);
  Serial.write("Booting...\n");

  // Join I2C buss
  bool i2cSuccess = false;
  while (!i2cSuccess) {
    Wire.begin(_I2CHost);
    Wire.beginTransmission(_I2CAddress);
    if (Wire.endTransmission() == 0) {
      i2cSuccess = true;
    }
  }
  Serial.write("I2C connection successful!\n");
}

void loop() {
  Wire.beginTransmission(_I2CAddress);
  Wire.write(_I2CHost);
  Wire.write(0x82);
  Wire.write(0x01);
  Wire.write(0x10);
  Wire.write((_I2CAddress << 1) ^ _I2CHost ^ 0x82 ^ 0x01 ^ 0x10); //XOR checksum. Include all bytes sent, including slave address!
  Wire.endTransmission();
  delay(40);
  Wire.requestFrom(_I2CAddress, 12);
  byte response[12];
  int i = 0;
  while (Wire.available())   // slave may send less than requested
  {
    byte c = Wire.read();    // receive a byte as character
    response[i] = c;
    i++;
    Serial.print(c, HEX);         // print the character
  }

  Serial.print("\n");

  delay (1000);
}

Edit:

After a number of minutes, I randomly sometimes get the response:

6E882010006406C6

As above 6E is the address. 88 is length. My best guess for 20 is it is supposed to be 02 but is being read weird? 02 is the VCP Feature reply op code. 10 is then supposed to be the Result Code, I am assuming 10 as in brightness op code? 00 means NoError 64 I am assuming is max value (high and low byte), 64 is 100 06 is the current brightness, which is correct, it is set to 6% C6 would then be the checksum.

So every so many tries, I get a proper read out of it. How can I get it to work every time? :P

mattboy9921 commented 1 year ago

Another update! I went and got a USB logic analyzer. I cannot figure out how to get it working, unless my signals being sent are just that bad.

But I did make some progress while tinkering. I was able to finally get the monitor to change brightness. So far the best solution has been to disable the internal pull up resistors on the SDA and SCL lines and only use an external pull up resister on the SDA line.

The monitor is not responding to all the signals, it is still random but more reliable. I'd say 50% of the time, it is setting it correctly.

I am going to continue to experiment with different ESP/Arduinos, different pull up configurations etc and see what ends up most reliable.

tttttx2 commented 1 year ago

Oh nice, thanks for the very detailed updates and sorry for being a bit slow right now, gotta have a more in depth look in a few days. Think I should be able to ship mine to you, although could take quite a while to arrive. Feel free to send me a shipping address (maybe push it to a private repo and invite me or something so it's not public, or whatever works)

If you have some captures from the logic analyzer maybe I can have a look at it as well

mattboy9921 commented 1 year ago

It has certainly been a bit since I had time to work on this project. I actually received a free Acer Predator X34 monitor so I swapped that into my setup. I went out and purchased this adapter since the new monitor does not have VGA, only HDMI and DP.

The good news is I was able to load up the example code and see the brightness switch via the ESP32. The bad news is, this Acer monitor does not support VCP 0x60, therefore I cannot switch inputs via DDC. I have spent a while researching each VCP it does support in hopes there would be a work around, alas I have come up blank.

So in conclusion, I speculate that the issue lied somewhere in my wiring to the VGA input. Flakey connection or cross talk was just making it unreliable. I'd say this issue is solved for now. My new solution is to get myself some sort of DisplayPort switcher and use the ESP32 to toggle it.