A Raspberry Pi Pico based project to bridge HDMI CEC (Consumer Electronics Control) and USB HID keyboard control (especially for use with Kodi).
Micro/mini desktops are plentiful as second hand, budget friendly media players, especially when installed with Kodi (eg. LibreELEC). However, many of these devices do not support HDMI-CEC and require the user to use additional peripherals (eg. wireless keyboard, game controller).
In this project we use a Pico to both:
- handle the CEC protocol on the HDMI port
- adapt the user control messages into USB keyboard inputs
- HDMI CEC frame send and receive
- EDID parsing to determine HDMI physical address
- LibreELEC recognises Pico-CEC as an USB HID keyboard
- HDMI CEC basic user control messages are properly mapped to Kodi shortcuts,
including:
- navigations arrows
- select
- back
- play
- pause
- numbers 0-9
To avoid cloning unneeded code, clone like this:
git -c submodule.active="lib/tinyusb" -c submodule.active=":(exclude,glob){lib,hw}/*" clone --recurse-submodules
Alternatively, clone everything in pico-sdk and tinyUSB:
git clone --recurse-submodules
This project uses the 'normal' CMake based build. To build the default image:
$ git clone <blah blah as above>
$ cd pico-cec
$ mkdir build && cd build
$ cmake ..
$ make
The CMake project supports three options:
- PICO_BOARD: specify variant of Pico board, defaults to Seeed XIAO RP2040
- CEC_PIN: specify GPIO pin for HDMI CEC, defaults to GPIO3
Example invocation to specify:
- use Raspberry Pi Pico development board
- use GPIO pin 11
$ cmake -DPICO_BOARD=pico -DCEC_PIN=11 ..
$ make
Assuming a successful build, the build directory will contain pico-cec.uf2
,
this can be written to the Pico as per normal:
- connect the Pico to computer via USB cable
- reset the Pico by holding 'Boot' and pressing 'Reset'
- Pico now presents as a USB mass storage device
- copy
pico-cec.uf2
to the Pico - disconnect
This is currently working with:
- a Sharp 60" TV (physical address 0x1000)
- directly connected to TV HDMI input 1
- through a Denon AVR connected to the Sharp TV (physical address 0x1100)
- Pico-CEC connected to Denon AVR HDMI input 1
- Denon AVR connected to TV HDMI input 1
- a Sony 60" TV (physical address 0x1000)
- directly connected to TV HDMI input 1
The hardware connections are extremely simple. Both HDMI CEC and the Pico are 3.3V obviating the need for level shifters. The DDC bus (for EDID) is I2C and 5V, however, the Pico appears to be 5V tolerant.
Additionally, we rely on the GPIO input/output impedance states to read or drive the CEC bus. DDC is I2C requiring data and clock lines. Thus, we need to directly connect four wires:
- HDMI CEC pin 13 direct to a GPIO
- HDMI CEC ground pin 17 direct to GND
- HDMI DDC clock pin 15 direct to SCL
- HDMI DDC data pin 16 direct to SDA
For the Seeed Studio XIAO RP2040:
- HDMI pin 13 --> D10
- HDMI pin 17 --> GND
- HDMI pin 15 --> D5
- HDMI pin 16 --> D4
After this we:
- connect
Pico-CEC
to the HDMI output of the PC - connect the HDMI cable from the TV to
Pico-CEC
- connect a USB cable from
Pico-CEC
to the PC
The enclosure is a reasonably simple three piece sandwich 3d print modelled with OpenSCAD. It is designed to be printed as three separate pieces which are bolted together with M3 nuts and bolts. An exploded preview of the result can be found in this STL.
The software is extremely simple and built on FreeRTOS tasks:
- cec_task
- interact with HDMI CEC sending user control message inputs to a queue
- hid_task
- read the user control messages from the queue and send to the USB task
- usbd_task
- generate an HID keyboard input for the USB host
- blink_task
- heart beat, no blink == no work
The CEC task comprises three major components:
recv_frame
- receives and validates CEC packets from the CEC GPIO pin
- edge interrupt driven state machine
- rewritten from busy wait loop to reduce CPU load
send_frame
- formats and sends CEC packets on the CEC GPIO pin
- alarm interrupt driven state machine
- rewritten from busy wait loop to reduce CPU load
- main control loop
- manages CEC send and receive
All the HDMI frame handling was rewritten to be hardware/timer interrupt driven to meet real-time constraints. Attempts to increase the FreeRTOS tick timer along with busy wait loops were simply unable to consistently meet the CEC timing windows.
These are simple FreeRTOS tasks effectively taken straight from the TinyUSB examples.
This project uses:
- FreeRTOS
- pico-sdk
- tinyusb
- Seeed Studio XIAO RP2040 (chosen for form factor)
- https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html
- Originally prototyped on the Raspberry Pi Pico board (still works with minor build time tweaks)
- HDMI male/female passthrough adapter
- Listed as 'HDMI Male and Female Test Board MINI Connector with Board PCB 2.54mm pitch 19/20pin DP HD A Female To Male Adapter Board'
- Model number: WP-905
- https://www.aliexpress.com/item/1005004791079117.html
- custom 3d printed housing
Component | Quantity | Price (June 2024) (AUD) |
---|---|---|
Seeed Studio XIAO RP2040 | 1 | 11.50 |
HDMI male/female adapter | 1 | 4.30 |
M3x10mm bolt & nut | 2 | 0.16 |
M3x20mm bolt & nut | 2 | 0.17 |
Random short wires | 2 | basically free |
Scunge 3D print from friend | 1 | mostly free |
Umpteen hours of engineering | 1 | priceless |
Total | 16.13 |
- more blinken LEDs
- most Pico boards appear to have an RGB LED, use it
- perhaps:
- R - FreeRTOS crash handlers?
- G - HDMI message received?
- B - HID keyboard message sent?
- pass cec-compliance
- survive cec-compliance fuzzing
- port to ESP32?
Inspiration and/or ground work was obtained from the following: