Friday, March 30, 2018

DDC I2C KVM with mouse + keyboard controlled by hand gestures

The goal

Make a device to switch mouse+keyboard between computers and change video input in monitors at the same time.


Huge! Everything what normal KVM gives without real hardware KVM.


Main device
  • 1 x Attiny45
  • 2 x IR emitting diodes
  • 1 x BP104 (PIN photo diode)
  • 1 x FE1.1s (4 port hub)
  • 1 x A5W-K relay
  • 1 x BC327
  • 1 x dual USB sockets
  • 2 x single mini USB sockets
  • 2 x 3-pin headers
  • 2 x IN4001/4 diodes (for power)
  • 1 x IN4001/4 or other diode (for relay)
  • 2 x 470uF electrolytic condenser
  • 1 x 1k resistor (0603)
  • 2 x 680 ohm or 1 x 330 ohm resistor (0603)
  • 1 x 10 ohm resistor (normal size)
  • 1 x 2k7 resistor (for FE1.1s REXT) (0603)
  • 1 x 12 MHz crystal (for FE1.1s  XOUT/XIN)
  • 1 x 4.7uF for FE1.1s VD33 decoupling
  • 1 x ?? (probably 4.7uF) for FE1.1s VD18 decoupling
VGA - VGA adapter
  • 2 x VGA male sockets
  • 2 x VGA female sockets
  • 2 x 4.7k resistor (smaller size)
  • 2 x 10 ohm resistor (smaller size)

Firmware + Hardware


Video showing how everything works.


About "skeleton" laptop wallpapers:
Makeup Artist: Mademoiselle Mu
Photographer: Pauline Darley
All rights reserved.

Some devices used in Eagle design files can have their own licenses and copyrights.


I'm injecting DDC commands via VGA i2c pins, to change active input on the monitors and at same time with small relay I'm replugging usb hub from server to laptop and vice versa. Keyboard and mouse is connected to the hub. Switch is initialized by hand gestures detected by two IR diodes and one IR receive diode in the middle. In the future, I'll attach here also SRAM based hardware clipboard to copy some small stuff between my machines.

Evolution of home workplace

At the beginning when you have your on flat, you probably own a laptop. After some time you notice that it would be good to have external monitor to make your work more efficient.

PC under the desk in only a shell, no ghost inside.

One monitor on the wall is OK, but two external ones would be better, right? It would be good if they have additional inputs so in "the future" or "eventually" you could connect some other signal sources to them.

Three monitors with additional led backlight.

OK, you have laptop and two external monitors, nice setup, but you miss some storage for making backups, right? In laptop you have semi failing proof SSD and HDD at this time and having a copy of you hard work/hobby is a must.

You're buying a small home server (HP microserver gen8 in my case) with standard stock CPU and some disk for storage. First you install some FreeNas, but you like more raw setups ... so you install FreeBSD and configure your ZFS storage manually, of course with iSCSI - because why not?

HP microserver gen8.

Then you probably buy a better CPU, it will have more cores and less TDP (Xeon E3 1220L v2 17W in my case). So now ... hmm you have CPU with VT-d, maybe let's only "try" to setup some visualization at home microserver, yes only "try", nothing permanent ...

OK, now it kind of works,  you have FreeBSD with ZFS as a qemu-kvm virtual machine which consumes server's AHCI controller via VT-d, nice! You also can spawn another virtual machines that are using iSCSI from ZFS (via hypervisor) as their storage - really nice!

But you still want more and more ...

Taking the risk

You have read that GPU passthrough on HP microserver gen8 server is impossible and for sure not supported, after several days of struggling you will pass.

After some ticks() of your biological clock and feeling too idle() for too many seconds measured from 1970-01-01 with additional lack of other interesting interrupts() you decide to context_swtich() and try again ... you always do!

Eventually you check every possible setting with every known kernel patch and it appears that is impossible because you always fail on error like:
"Device is ineligible for IOMMU domain attach due to platform RMRR requirement.
Contact your platform vendor."
So what if you bypass this code in kernel? For sure you're totally bored and careless to do this...
static bool device_is_rmrr_locked(struct device *dev)
 return false;   // feeling lucky today?

 if (!device_has_rmrr(dev))
  return false;

 if (dev_is_pci(dev)) {
  struct pci_dev *pdev = to_pci_dev(dev);

  if (IS_USB_DEVICE(pdev) || IS_GFX_DEVICE(pdev))
   return false;

 return true;
... and ...

Oh shit it works!

Now you've got a real problem!

Why? Because it works and  yes, you want to use it - and you "need" to use it!.

You're greedy as hell aren't you?

So you're buying a keyboard, maybe something better than the cheap ones, let's buy some mechanical monster - it will last longer and for programmer that generates tons of kbd interrupts() for sure will be better.

OK, you have a mechanical keyboard connected to your microserver and play with several virtual machines with Nvidia NVS300 passthrough connected to two separate monitors by both DVI (DBS-59)

Wait ... but ... what with the laptop? There is no space on your desk to handle this whole equipment, and remember that you're one very sophisticated piece of Carbon and you really want to utilize it all!

Don't panic, don't be angry! I've got solution for all of your problems.

DDC inject to the rescue

Keyboard+mouse HUB-based switch which will also be able to select proper monitor input when you switch between your machines.

How, you say?

Because of DDC which is a shortcut for Display Data Chanel. It's used by your computer when talking to monitor to get its model, supported resolutions and also (if supported) to completely control your monitor.

But how to interface it?

It's regular I2C protocol, so it's no rocket science involved. On the Internet you will find how to use I2C port via your VGA slot on your computer. So the same way you can communicate in opposite direction - from your custom I2C device to your (DDC) on your monitor.

I've got lucky, because my monitors have active I2C lines on VGA all the time (or maybe it's a standard?), even when I use different inputs, so talking with them was quite easy to implement.

Basically you need to solder VGA - VGA passthrough with additional resistors on I2C lines and use monitor side for communication.

Use screws to set the best sockets position for soldering.

For me if it's a "regular" laptop that won't pull down I2C lines when it's off you can use 10ohms resistors. For some other device that not necessary need to communicate with your monitor, you should use 4k7 resistors.

Heads of the screws are too big, I eventually glued everything.

I've got digital microscope from China which appears to puling down I2C/DDC on shut down, so at some point my device was not working ... then I've notice the cause. The perfect solution would be to put here some transistors and make "regular" and "ours" I2C transmission working at the same time.

Eventually you can remove the screws after you fill everything with hot glue.

Not very nice looking ...

Don't forget about connecting grounds (missing on picture above) and if you have some reflections (blurry image) on VGA input, add additional tin foil around glue layer. If you still got blurry image, try not to wrap cables like I did on this picture, but put tin foil directly on glue layer and then cables on top.

Get the payloads

I'm really lazy in way of "hacking" things, so I've used ddccontrol/gddccontrol in debug mode to determine payload which is needed to be send to control monitor inputs.

gddccontrol -vvv after changing input from Digital to Analog

Then using BusPirate I was able to detect correct write address, and inject my DDC commands into I2C data stream to successfully change active monitor input!

Notice that there are more devices reported by i2c scan - because I've connected to monitor ddc when laptop was also connected.

Rick Sanchez gave up, but I was able to change monitor input using buspirate.


Here are the buspirate payloads:
[ 0x6E 0x51 0x84 0x03 0x60 0x00 0x01 0xd9 ] # analog
[ 0x6E 0x51 0x84 0x03 0x60 0x00 0x03 0xdb ] # DVI
[ 0x6E 0x51 0x84 0x03 0x60 0x00 0x11 0xc9 ] # hdmi
I need to control two monitors at the same time and attiny45 can handle only one I2C, do I need to buy I2C hub? It would be for sure an elegant solution, but it's close to midnight and I don't "have time" for this.

Inject the payloads

Let's play in Bit-Banging game!

Yes, but ... I don't really want to move on the Bit-Banging board, don't want to roll the dice and wait two queues ... oh wait I've stopped on Logic Analyzer field - Yay! It's a shortcut!

Using cheap Saleae clone and Pulse View, I was able to record transmission, and replay it with same timing using _dealy_us and _delay_ms.

Replayed I2C transmission.

So I need to sacrifice 4 pins to control two monitors? Looks too much for me, let's try make SCL common and since the payloads are same length I can clock data at the same time, hopefully monitors won't have nothing against?

Common SCL I2C transmission (ACK is missing because monitors were not connected)

Yep driving the DDC's is finally finished, let's move to the USB hub, lazy lazy lazy ... I've just disassembled some laying around 2.0 hub to get the FE1.1s chip ...

This USB HUB was really not very good quality.

D+ D- switching is implemented via small low voltage relay that was also laying around for several years in the drawer, his time has come.

PCB design

Let's move for one moment to the pcb design, I've got Razer keyboard which has mini usb socket at the top, so it would be good if my device can be pluggable in that area. It would be more convenient to "switch" the inputs in some close hand range.

Razer keyboard mini USB socket is placed on top of the keyboard.

Consequence of this decision was to implement simple IR gesture detection based on two IR tx diodes and one BP104 rx diode in between. Now there's no risk of breaking Razor mini usb socket, it was not designed by manufacturer to hold my device's weight and additional mechanical switch impact.
Attiny45 has really low number of pins and in this project I was able to utilize it completely. RESET pin can be used as ADC, so after "not" doing some math I checked how strong is the default pullup using 100k resistor and after realizing that is weak enough to not hurt rx diode I've just connected BP104 to RESET pin.

The amount of reflected IR light that hits photo diode during left-right/right-left hand gesture is not strong enough to pull down RESET pin below attiny45 reset threshold, but it can be noticed by ADC.

OK, so Attiny45 has 8 pins in total, two are for VCC and GND, three for I2C-DDC, one for photo diode's ADC (RESET pin) and I need two pins to control two IR tx leds which I will drive via pulldown. I need two pins because don't want to power them at full cycle of internal while() loop.

Sorry for poor screens form my oscilloscope but during firmware mismatch it's choking when saving to usb storage. Also the ground is shifted on below measurements, so you need to believe me that It's just like I wrote in the text. Eventually I take new screens with proper grounding.

IR diodes power cycle - active low.

Now, after doing some heavy math like adding all of above, it's clear to me that I have minus one pin to control the relay ... that's really not a very good thing to have minus one pin on a microcontroller.

So I've got another idea - let use IR tx diodes power cycle to feed impulses to BC327 and some big capacitor on coil side to prevent Mr. Relay heart palpitation.

Again, it's working!

With smaller delay, frequency of the impulses is higher and 470uF capacitor don't have time to discharge completely. It's connected with relay coil in parallel and it can push over 3V which is successfully making enough electromotive force to hold the relay switch in desired state.

Relay ON power cycle.

With larger delay, capacitor discharges and voltage is around 0.3V or less.

Relay OFF power cycle

I think it's enough for optimization like for one evening, right?

Here is the working prototype on atmega32u4.

Atmega32u4 prototype.

You probably wondering what is the role of this additional 7" display - it's also a touchscreen and it's used to control my HP microserver's virtual machines, but I will cover this in different post, because it's kind of complicated setup ...

On the right side you can also see my hacked SDS1022C oscilloscope with 7" lcd and a touchscreen.

Custom power supply (on the left) waits for implementing overload protection.

Baofeng UV-82, (on top of power supply) was also hacked by me.

Second prototype was made on Attiny45.

It's PCB time!

From now it was easy:

Design in eagle:

Eagle schematic.

Printout - this one has two errors that are now fixed in Eagle files on my github.

Folded double sided PCB printout.

PCB preparation.
Adjusting printout size.

Then a very "warm" welcome from my custom made PCB laminator

Just 4 iterations and we have beautiful PCB.

Removing extra paper from between the routes it's boring but needs to be done. Remember to do it with slightly wet PCB, it's easier and less probable to break the toner.

Cleaning from paper leftovers (gray).

It takes some time and precision.

After etching and drilling:

Etched and drilled PCB.

Yep, basically most of my equipment is made or hacked by me, same as this drill holder.

Using custom stand for drill.

Somebody said vias?

Preparation for making vias.

You need just a piece of resistor leg.

Vias making.

Via top view.

Via bottom view.

Working prototype

And the final result, quite nice like for home made, don't you think?

Final version.

Right side.


Without USB plugs.

Final  working prototype plugged into the keyboard.

It's working!

Future plans

Since I have two additional D+/D- inputs I will build hardware clipboard to copy some small stuff like url's and maybe small program source files between my machines.

No, I don't want to connect just simple usb pendrive to do it for me ;)

The end

Thanks for your attention!


  1. This is so amazing, not just this but also all the other hacks that you have done. Kudos and Respect to you.