Ambilight on the ODROID-C2 Using LibreElec: Adapting the Ambilight for the ODROID-C2

In this article, I’d like to share how I managed to build a working Ambilight system using an ODROID-C2 and LibreElec. Other guides I previously found mainly targeted the Raspberry Pi, so I had to collect the information using Google and a bit of trial and error.

The high-level design uses Hyperion installed as an add-on for Kodi, from the LibreElec repo– installing by HyperCon is not possible due to the CPU for it being unknown–and an Arduino for controlling the LEDs using the FastLED library. The Arduino is connected to the ODROID-C2 via USB cable (the Arduino contains an USB-to-Serial converter). My TV is FullHD, so I needed no magic (i.e. RAM overclock and stuff like this) for 4K.

I used the below guides as a base for assembling and configuration. But since these guides are aimed toward use with the Raspberry Pi, I needed to adapt for ODROID-C2.

Raspberry Pi 3 Mediacenter + Hyperion Ambilight + NO soldering: https://goo.gl/q8q6PK
Amblight project/guide – Hyperion – WS2801/ WS2812B / APA102: https://goo.gl/CEgT1U

It turns out that my configuration is affected by the rather annoying bug described here: https://goo.gl/HUwa2k

Video playback of starts to slow down after an arbitrary amount of time (between 30-70 minutes in my case) while the audio continues to play at proper speed, causing the audio and video to go out of sync with each other. After 30-60 seconds there is a small pause and skip, then afterwards the audio and video are in sync again, until it happens again in 30-70 minutes.

The above topic in the LibreElec forum is still unresolved, as the issue is proving quite difficult to fix. If anyone has a working system (ODROID-C2 running LE 8/Kodi 17 with Hyperion) that is not affected by this bug, please feel free to contribute the Hyperion config file! I plan to fiddle with Hyperion settings to see if something makes a difference, but that may take quite a bit of time.

Hardware

  • ODROID-C2 plus case and power supply from Hardkernel, which cost about €72 when I bought it from https://www.pollin.de last year
  • Kingston 16GB Class10 MicroSD card, which cost about €8 from eBay
  • Chinese clone of the Arduino Uno R3 board plus case and USB cable, which cost about €8.50 from eBay
  • WS2812B 3-pin RGB LED strip, 30 LEDs per meter, 5m roll, which cost about €13.50 from eBay
  • 5V 10A power supply. I had one laying around, so there was no cost, but you could probably find one for €10-20 on eBay or Aliexpress
  • DIN power plug, which cost about €1.50 from eBay
  • 3-pin JST male and female connector cables, which cost about €1.50 from eBay for 5 pieces
  • 3-pin connectors 10mm for WS2812 set, which cost about €2 from eBay for 5 pieces
  • Dupont male jumper cables, which cost about €1 from eBay for 40 pieces
  • Heat shrink tubes, which cost about €EUR 1 from eBay for 70 pieces
  • 500 ohm resistor, or 2 x 1K ohm resistors wired in parallel
  • HDMI cable
  • Electric welder for soldering the plugs and cables

Software

  • LibreElec 8.2.1 MR image for ODROID-C2 downloaded from https://libreelec.tv
  • Hyperion AddOn for Kodi from LibreElec repo
  • HyperCon tool for Hyperion (https://goo.gl/7F5fDc)
  • Arduino IDE (https://goo.gl/wqP28G)
  • FastLED library for Arduino (download ZIP – https://github.com/FastLED/FastLED)
  • Control sketch (script) for Arduino (https://pastebin.com/2L9ZBhYe)

 
#include "FastLED.h"

// How many leds in your strip?
#define NUM_LEDS 92

// For led chips like Neopixels, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
#define DATA_PIN 12
//#define CLOCK_PIN 13

#define COLOR_ORDER GRB

// Adalight sends a "Magic Word" (defined in /etc/boblight.conf) before sending the pixel data
uint8_t prefix[] = {'A', 'd', 'a'}, hi, lo, chk, i;

// Baudrate, higher rate allows faster refresh rate and more LEDs (defined in /etc/boblight.conf)
#define serialRate 115200

// Define the array of leds
CRGB leds[NUM_LEDS];

void setup() {
 // Uncomment/edit one of the following lines for your leds arrangement.
 // FastLED.addLeds<TM1803, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<TM1804, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<TM1809, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS);
 FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
 // FastLED.addLeds<UCS1903, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<UCS1903B, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<GW6205, DATA_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<GW6205_400, DATA_PIN, RGB>(leds, NUM_LEDS);

 // FastLED.addLeds<WS2801, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<SM16716, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<LPD8806, RGB>(leds, NUM_LEDS);

 // FastLED.addLeds<WS2801, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<SM16716, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
 // FastLED.addLeds<LPD8806, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);

 // initial RGB flash
 LEDS.showColor(CRGB(255, 0, 0));
 delay(500);
 LEDS.showColor(CRGB(0, 255, 0));
 delay(500);
 LEDS.showColor(CRGB(0, 0, 255));
 delay(500);
 LEDS.showColor(CRGB(0, 0, 0));

 Serial.begin(serialRate);
 Serial.print("Ada\n"); // Send "Magic Word" string to host
}

void loop() {
 // wait for first byte of Magic Word
 for(i = 0; i < sizeof prefix; ++i) {
 waitLoop: while (!Serial.available()) ;;
 // Check next byte in Magic Word
 if(prefix[i] == Serial.read()) continue;
 // otherwise, start over
 i = 0;
 goto waitLoop;
}

// Hi, Lo, Checksum

while (!Serial.available()) ;;
 hi=Serial.read();
 while (!Serial.available()) ;;
 lo=Serial.read();
 while (!Serial.available()) ;;
 chk=Serial.read();

// if checksum does not match go back to wait
 if (chk != (hi ^ lo ^ 0x55))
 {
  i=0;
  goto waitLoop;
 }

memset(leds, 0, NUM_LEDS * sizeof(struct CRGB));
// read the transmission data and set LED values
for (uint8_t i = 0; i < NUM_LEDS; i++) { byte r, g, b; while(!Serial.available()); r = Serial.read(); while(!Serial.available()); g = Serial.read(); while(!Serial.available()); b = Serial.read(); leds[i].r = r; leds[i].g = g; leds[i].b = b; } // shows new values FastLED.show(); }

Assembling the LED strip

The LED strip I purchased already had a 3-pin JST female socket built to the starting end, plus two additional wires (without a socket) for 5V and GND. I soldered used the latter two wires to the DIN power plug so that I could power the LEDs from the power supply. Make sure to use a plug that is compatible with your power supply!

I took a male JST connector cable and soldered Dupont wires to the end of GND and DATA wires so I could plug them into the pinouts of the Arduino board. The 5V wire is not used here, since it is not connected, so it can be cut and/or insulated. I soldered the 500 ohm resistor between the DATA wire and the Dupont cable so that control signals could go through it. All solder-joints have been secured by heat shrink tubes.

I started HyperCon on my computer and planned the configuration and the LED layout on the TV, making sure to measure the dimensions of my TV. I explored other configuration options on the Hyperion Wiki site and saved the LED config to the hyperion.config.json file. I then used the calculated LED numbers to cut the strip into appropriately sized pieces and used the 3-pin connector cables to attach them to each other. The 3-pin connector cables are required for the corners of the TV as the LED strip alone cannot be bent for 90 degrees.

Please note that these LED strips have a direction. Only the starting end can be used for input controls and distinct LEDs are addressed following each other. Look for “DI (Data Input) marking on the strip, near the LEDs themselves. The other end has “DO (Data Out) marking. You can use Google to find the WS2812b specifications.

My experience is that the 3-pin connectors cannot hold the strip too tightly, so exercise caution when mounting it to the back of your TV. If you have experience in precision soldering, you may be better off soldering wires to connect the strip parts instead of using the 3-pin connectors.

I installed LibreElec onto the micro SD card as per the instructions on https://libreelec.tv, then assembled the ODROID-C2 and configured Kodi to my taste. I went to Settings -> Services -> Control in Kodi interface, and enabled both “Allow remote control from applications on this system” and “Allow remote control from applications on other systems”. I installed Arduino IDE to my computer, then extracted the FastLed library ZIP file to a separate subfolder under the \libraries folder. When I started the Arduino IDE after this, the FastLED library became available under the Sketch -> Available libraries menu.

I attached the Arduino board to my computer using the USB cable. I copied the sketch into Arduino IDE, set the number of LEDs in the strip in row #4 (you will have this number from HyperCon), and the pin used for LED control in row #9. Then I uploaded the sketch into the Arduino board, which only took a few seconds.

Assembly
I used the adhesive on the back the LED strip to stick it to the back of my TV. Some extra adhesive was required in a few places in order for it to stick firmly. I unplugged the Arduino board from my PC and plugged it into one of the USB ports on the ODROID-C2. I plugged the Dupont cable jumper soldered to the GND wire of the strip to the GND pin and the Dupont cable jumper soldered to the DATA wire of the strip to pin 12 of the Arduino. Finally, I plugged the power supply into the power plug soldered to the end of the strip.

Software configuration
LibreElec loaded the CH341 module automatically upon connection of the Arduino board to USB, and device /dev/ttyUSB0 appeared. This might, however, be different for you.
I installed the Hyperion add-on on the Kodi interface, from the LibreElec repo.
I opened the previously saved hypercon.config.json file on my computer, and edited it in 3 locations: device, frame-grabber, and effects. Updating the effects folder is necessary because it is different when Hyperion is installed as an add-on, instead of installing by HyperCon.

Note that other guides state to use 100000, 200000, and so on for the baud rate. For me, this resulted a repeated “Unable to open RS232 device (IO Exception (25): Inappropriate ioctl for device” error message in the Hyperion log. Switching to a 115200 baud rate made the issue go away. Make sure to notice the reversed “GRB” color order!

// DEVICE CONFIGURATION
"device" :
 {
 "name" : "MyHyperionConfig",
 "type" : "adalight",
 "output" : "/dev/ttyUSB0",
 "rate" : 115200,
 "delayAfterConnect" : 0,
 "colorOrder" : "grb"
 },

"amlgrabber" :
 {
 "width" : 64,
 "height" : 64,
 "frequency_Hz" : 20.0,
 "priority" : 880
 },
 "framegrabber" :
 {
 "width" : 64,
 "height" : 64,
 "frequency_Hz" : 10.0,
 "priority" : 890
 },

"effects" :
 {
 "paths" :
 [
 "/storage/.kodi/addons/service.hyperion/effects"
 ]
 },

I uploaded my updated hyperion.config.json file to /storage/.kodi/userdata/addon_data/service.hyperion/ folder in LibreElec. This is the folder where the Hyperion add-on looks for the config file. The UserData share can be used for the upload if the SMB server is enabled. I then rebooted LibreElec and, the Ambilight system now works properly for me. Hopefully I haven’t forgot anything above and someone will make use of this information. Any comments or suggestions for improvement are welcome!

For comments, questions, and suggestions, please visit the original forum post at https://forum.odroid.com/viewtopic.php?f=144&t=29334.

Be the first to comment

Leave a Reply