Renard-to-GECE
Codename: Horae
Update 2014:
I found a bug in the code, it has been updated. I used a uint8_t for channel count, which meant the code to read the Renard protocol only worked up to 255 channels. I have updated it to use a uint16_t for the channel count.
Update 2012:
This project was retired and only used for our 2011 season. We have since upgraded to a SanDevices E681 controller for our GECE pixels.
Original Article:
This year we invested quite a bit of money into GE ColorEffects lights after seeing darco’s hacked Christmas lights on hackaday. I wasn’t sure how I planned on controlling them at the time, but half of the reason I wanted to get involved with synchronized Christmas lights was to play with and learn electronics. These lights were a perfect example of both something I found fun to play with, and educational.
Before buying these lights, we planned on using C7s to outline the roof line. I was going to have to build custom SSRs to handle the large current requirements of C7 incandescent lights. After purchasing enough of these to outline our roof line, I needed a way to control the lights. Since our original Vixen profile contained only 1 string per roof line segment, the easy way to change our sequences and profile was to make them act like dumb-RGB strips. This was basically taking out the pixel level control, but keeping the RGB aspect of the bulbs. I had a couple of options for controlling the GECEs including an E681 from a group buy, or developing my own controller. I liked the idea of rolling my own for the sake of learning Also, the E681 group buy was relatively late for the planning/building phase.
I started work on my controller knowing that since I did purchase two E681 controllers in a group buy, if I couldn’t get my controller working I could use the E681’s grouping feature to avoid any additional sequencing. I ended up having a couple of design hurdles which pushed me close to lights-on time which I was able to iron out last minute so the E681 was saved for 2012.
Design
Receiving RS-485
To keep the Vixen profile simple I wanted to read the same RS485 data that was being sent to all other Renard equipment. That meant I needed to do RS485 to TTL conversion as well as implement code to read Renard data on the Arduino. Both were very simple. For the RS485 to TTL I simply cut a small piece of perfboard and used jumper wire to get power in, and TTL out. I wired the A/B lines to an RJ45 jack so I could simply run cat-5 to the planned enclosure like I was running to all other Renard boards.
I used ST485BN ICs as well as MAX485E ICs for the controllers. I did notice that I could leave the RE^ and DE pins floating on the ST485BN, but not the MAX485. That taught me an “EE 101” rule quoted from Matt_Edwards: “All input pins MUST be connected somewhere. It is the responsibility of the engineer to discover the best place to connect input pins, not the FGM”.
Talking To GECEs
To get some Arduino code written that would talk to the GECEs I made a simple controller to send a sequence of hard-coded commands to 2 strings of GECEs using 2 separate pins of an Arduino clone (called an Anardiono from anatools.com’s eBay store). I used Halloween to test the reliability of this configuration. I simply had an orange/purple chase across the house with the controller in the middle. This worked flawlessly for a couple of weeks for our Halloween lights and gave me some confidence in the lights, and my ability to control them. This also proved the Anarduino should work for my application as well. For halloween it was simply stuffed into a tupperware container along with the stock power supply and a string of 33 bulbs on one side and 27 on the other.
After having hard coded control working it was time to upgrade the code and start reading data in, processing that, and controlling the GECEs per the sequenced commands coming from the PC.
Renard
The Halloween setup was hard coded to chase with 50ms between bulbs, then a half-second delay at the end of the chase before starting over. For Christmas I needed the Arduino to read serial data to control the lights. I was already using Renard controllers for the rest of the display, so I chose to use Renard for my project as well. I didn’t want to use generic serial for a couple of reasons. First, it would mean I needed an additional COM port set up on the machine for the roof channels. Second, the generic serial output in Vixen has no sync bytes, so in order to detect the beginning of an update some very complicated timing code would need to be written.
As it turns out, implementation of the Renard code was not very difficult. Since the protocol is documented on the DIYC wiki, I was able to write the code for that in about 20 minutes, debug it and have that portion working in about 30 minutes. I did run into some other issues that didn’t have to do with this code, but reading serial data was suspect.
Here is the basic outline of the Renard code used (without the hard-coded start addresses and other one-off pieces):
#define XMAS_PIN 8 #define xmas_color_t uint16_t #define XMAS_LIGHT_COUNT (50) #define XMAS_CHANNEL_MAX (0xF) #define XMAS_DEFAULT_INTENSITY (0xCC) #define XMAS_COLOR(r,g,b) ((r)+((g)<<4)+((b)<<8)) #define XMAS_COLOR_WHITE XMAS_COLOR(XMAS_CHANNEL_MAX,XMAS_CHANNEL_MAX,XMAS_CHANNEL_MAX) #define XMAS_COLOR_BLACK XMAS_COLOR(0,0,0) bool sync; void xmas_begin(uint8_t pin); void xmas_one(uint8_t pin); void xmas_zero(uint8_t pin); void xmas_end(uint8_t pin); void xmas_begin(uint8_t pin) { digitalWrite(pin,1); delayMicroseconds(7); digitalWrite(pin,0); } void xmas_one(uint8_t pin) { digitalWrite(pin,0); delayMicroseconds(11); //This results in a 20 uS long low digitalWrite(pin,1); delayMicroseconds(7); digitalWrite(pin,0); } void xmas_zero(uint8_t pin) { digitalWrite(pin,0); delayMicroseconds(2); digitalWrite(pin,1); delayMicroseconds(20-3); digitalWrite(pin,0); } void xmas_end(uint8_t pin) { digitalWrite(pin,0); delayMicroseconds(40); // Can be made shorter } void xmas_set_color(uint8_t led,uint8_t intensity,xmas_color_t color, uint8_t pin) { uint8_t i; cli(); //Disable interrupts while we write to GECEs xmas_begin(pin); //6-Bit bulb address (MSB first) for(i=6;i;i--,(led<<=1)) { if(led&(1<<5)) xmas_one(pin); else xmas_zero(pin); } //8-Bit brightness (MSB first) for(i=8;i;i--,(intensity<<=1)) { if(intensity&(1<<7)) xmas_one(pin); else xmas_zero(pin); } //12 bit Color (Blue, Green, Red) (MSB first) for(i=12;i;i--,(color<<=1)) { if(color&(1<<11)) xmas_one(pin); else xmas_zero(pin); } xmas_end(pin); sei(); //Re-enable interrupts now that we're done writing to GECEs } void xmas_set_all_one_address(uint8_t pin) { for (int i = 0; i < XMAS_LIGHT_COUNT; ++i) xmas_set_color(0,XMAS_DEFAULT_INTENSITY,XMAS_COLOR_BLACK,pin); } void setup() { delay(10); Serial.begin(57600); delay(10); pinMode(5, OUTPUT); digitalWrite(5, LOW); pinMode(XMAS_PIN, OUTPUT); digitalWrite(XMAS_PIN, 0); xmas_set_all_one_address(XMAS_PIN); sync = false; } void wait_for_serial() { while ( ! Serial.available() > 0 ) { } } int renardReadBytes( uint8_t *bytes, uint16_t bytes_size ) { int in_byte = 0; int bytes_read; for ( bytes_read = 0; bytes_read < bytes_size; ) { wait_for_serial(); in_byte = Serial.read(); switch (in_byte) { case(0x7E): // We saw the sync byte, start over! sync = true; return bytes_read; case(0x7D): // Skip the pad byte continue; case(0x7F): // Escape character, we need to read one more byte to get our actual data wait_for_serial(); in_byte = Serial.read(); switch (in_byte) { case(0x2F): // renard wants an 0x7D in_byte = 0x7D; case(0x30): // renard wants an 0x7E in_byte = 0x7E; case(0x31): // renard wants an 0x7F in_byte = 0x7F; } } bytes[bytes_read++] = in_byte; } return bytes_read; } int renardRead( uint8_t *bytes, uint16_t byte_count ) { int in_byte = 0; while ( ! sync ) { wait_for_serial(); in_byte = Serial.read(); if ( in_byte == 0x7E ) // Sync byte signifies start of packet sync = true; } if ( sync ) { sync = false; wait_for_serial(); in_byte = Serial.read(); if ( in_byte == 0x80 ) // Read from here { return renardReadBytes(bytes, byte_count); } } return 0; } void loop() { uint8_t bytes[4]; uint16_t bytes_read; bytes_read = renardRead(&bytes[0], 4); if ( bytes_read == 4 ) { // We have now received 4 bytes of data per string, do something with them! uint8_t red = bytes[0]; uint8_t green = bytes[1]; uint8_t blue = bytes[2]; uint8_t white = bytes[3]; red = ( red+(white/3) >= (XMAS_CHANNEL_MAX<<4) ? (XMAS_CHANNEL_MAX<<4) : red+(white/3) ); green = ( green+(white/3) >= (XMAS_CHANNEL_MAX<<4) ? (XMAS_CHANNEL_MAX<<4) : green+(white/3) ); blue = ( blue+(white/3) >= (XMAS_CHANNEL_MAX<<4) ? (XMAS_CHANNEL_MAX<<4) : blue+(white/3) ); xmas_set_color(0,XMAS_DEFAULT_INTENSITY,XMAS_COLOR((red>>4),(green>>4),(blue>>4)),XMAS_PIN); } }
The code itself was easy to get up and running, but I was having troubles writing to the GECEs at this point. Originally I thought I was losing serial data and came up with a series of tests to ensure I was reading everything. Things like hard-coded ramps and computed if I was receiving increasing values were quick tests I could to ensure I was receiving what I thought I was receiving on the ATMega. I also came across this page detailing baud rates matched with clock frequency. I noted that the existing Renard controllers used 18.432MHz clocks to match all standard bit rates correctly while the arduino uses a 16MHz clock. I changed my code to try 38400 and the Vixen plugin, but nothing changed. After these tests I came to the conclusion that I was receiving all data on the arduino that I expected and I looked into other possibilities.
After a couple posts on DIYC, reading other people’s GECE code, and a day to think about the problem I developed a new theory. I thought that the serial library written in the Arduino sdk that makes parsing serial data so easy was likely messing up my timing. Since I was using simple “digitalWrite()” and “delayMicroseconds()” calls, I theorized that the Serial library was likely firing interrupts that made my writes to the GECEs out of their tolerance. I simply modified my code to have “cli()” before I wrote to the GECEs to clear interrupts, and “sei()” afterward to re-enable them. This immediately cleared up the problems I was having. After some back-of-the-envelope calculations I was able to conclude that the time needed to write to the GECEs was not long enough for me to miss any incoming data.
Arduino Clone
Instead of spending over $30 per controller by using an Arduino, I found a “clone” available on eBay called the Anarduino. It is a minimalist design which basically only has the necessary components to support the ATMega328. It requires a 5V source which I was able to get from the stock GECE controller for my initial revision. Since the stock GECE puts out 5.6V, I put a 7805 linear voltage regulator on the output to feed as input to the anarduino. This worked for the Halloween application so I didn’t think there was anything wrong with that configuration. Once I had the lights up for Christmas I had a problem which I realized was a brown-out reset of the ATMega µC. I took a voltage reading and the input to the anarduino was about 4V constant and would dip to below 3.2V when the LEDs on that line would illuminate white. This caused the reset. I had completely forgotten about the Vdo. Turns out the dropout voltage of the 7805 I was using is about 1.5V, not leaving enough left for the µC. Once I replaced the standard 7805 with an LDO variety, all was well.
Tying It All Together
I had hopes of a cleaner mounting design but due to lack of creativity, time and money I simply used jumper wire for leads and wire nutted the connections inside of a TA-200 demarcation enclosure. I was able to fit everything I needed inside of these enclosures to keep them protected from the elements. The total cost for 3 controllers to drive 229 pixels was about $50, which I think is pretty good when compared to other options.
Pingback: August Updates | Christmas On Quiet Hills