Posts Tagged ‘blinkenlichten’

Everyone and his brother has built LED cubes before, and while they are unmitigatedly awesome, I wanted to try something a little different. As far as I can tell, nobody has made an LED buckyball before!

Completed ball running some cheesy visualizations

And of course, the requirements for such a large, sparse shape are a little different. In a typical LED cube design, the leads of the LEDs themselves form the structural members and support base of the cube – the lead lengths more-or-less set the size and density of the cube, and since it sits on a large surface area, there’s plenty of room to bring out plenty of signals and drive them from a single many-pinned microcontroller (usually via Charlieplexing). In a large, sparse hollow sphere, the wiring for Charlieplexed LEDs would get unwieldly pretty fast. So instead, I used a bussed approach where each LED sits with its own tiny microcontroller on a serial bus, with a unique address programmed into each one. Such addressable “smart pixel” LED nodes have been around for a while and available off-the-shelf, but for various reasons (outlined below), I opted to design my own open source (GPL) version, dubbed das Blinkenlichten, and used these for the project. These nodes sit on a 3-wire (power, data, gnd) bus and speak over a self-clocking bit protocol. The ball contains 90 such nodes; one for each of the 60 junctions of the buckyball, plus 30 more in the “long” beams of the ball (more on that later too).

Sidebar: Addressable RGB LED nodes
Addressable “smart pixels” or RGB LED nodes have been around for a while; in the hobbyist community, BlinkM is probably the most well-known. However, at close to $13 a pop, a deployment of any significant number of these adds up very fast. In addition, the code was/is very much proprietary – not only closed source with dire warning against reverse-engineering, but only allowed for noncommercial applications at that. As of late 2004, there was really not much in the way of very inexpensive nodes or open-source designs to pick up and build on one’s own. Add to that patentissues surrounding many of these products (including pretty much all I2C-based designs), and the situation for open blinky hardware was bleak indeed. So I rolled my own that was dirt-cheap, minimal wire count and GPL’ed. By happy coincidence, it also avoids all the RGB LED control patents that I am aware of. Luckily, as of late 2011 there are many more inexpensive, open-source and even microcontrollerless (shift-register based) blinkies available in single or strips, so go nuts!

Buckyballs:
In real life, buckyballs are spherical molecules composed entirely of carbon. The LED ball models the most well-known of these molecules, carbon-60 (buckminsterfullerene), consisting of 60 carbon atoms. These atoms are arranged such that their bonds form 12 polygons and 20 hexagons, with one carbon atom at each vertex. The stitching, and often coloring, of soccer balls (footballs outside the US) mimics this structure.

Construction details:
I wanted the ball to consist of basically nothing but light – i.e. a bare minimum of visible wiring, electronics or any other opaque portions. Thus the light from the tiny LEDs would have to be well diffused throughout the entire structure. Playing around, I found that hot-glue sticks make almost perfect LED diffusers, and wasted no time cleaning out the local art&craft store’s supply for the ball’s structural elements. In retrospect, this was not the ideal solution: despite anything you might hear about the buckyball being a magically strong shape, it’s still not enough to hold the weight of 90 hot-glue sticks and electronics without sagging into more of a pear shape. In the end I had to add some internal “guy wires” (monofilament fishing line) to help it hold a spherical shape. As you can see, it’s still not perfect, so I’ll have some adjusting to do!

The ball consists of 12 pentagonal sections totaling 60 sticks, and 20 hexagonal sections formed by connecting 3 sticks between each neighboring trio of pentagons.

To create the physical ball, I needed some kind of scaffolding to hold the ball in shape during various stages of (in)completion, and ideally serve as a construction guide. For this, I bought a cheap inflatable soccer ball on eBay, blew it up, and assembled the ball around it in pieces. The markings on this ball (a promotional item for a foul-tasting yet inexplicitly popular domestic beer) are not 100% faithful to a real C60 molecule; the pentagonal sections are a bit small, resulting in the spokes between them (forming the hexagons) being a bit longer than the pentagon segments. I’ll just refer to these as the “long” sticks from now on.

First, I assembled all the Blinkenlichten boards with a 5mm LED. Each board is about 10mm square and can sit flush against the end of a glue stick with only the solder tabs sticking out. Each of the 60 sticks forming the pentagons are end-lit, with the LED node installed by drilling a hole in one end the diameter of the LED and shoving it in. The remaining 30 “long” sticks (to form the hexagonal sections) have a hole drilled in its middle and the LED inserted perpendicular to the stick. I’ve found that using a drill bit a tiny bit smaller than the LED itself works well: the drilling action heats up the stick enough to partially melt the inner walls of the hole; quickly shoving in the LED after this allows the walls to stretch slightly to accommodate it, then glues it tightly in place.

Template for cutting pentagon sticks to size/shape

"Long" (hexagonal) sticks with the LED perpendicular

I first built the required number of pentagonal sections, with the length of each side matching that printed on the soccer ball. To do this, I made two simple templates in Inkscape and printed them out. One consists of two lines intersecting at 108 degrees (the interior angle of a pentagon), indicating the length to cut each stick to and the angle to cut the end at so that the 5 sticks will sit flush against each other when assembled. The other is a template for laying up the pentagon itself. A box cutter knife made short work of cutting the sticks to the right size and shape. With all the sticks cut to shape, I used more hot glue (for its intended purpose this time) to assemble the sticks into pentagons, being sure to fill in any gaps where the sticks mated.

Stack of assembled pentagons

With all 12 pentagons assembled, I wired their power, data and ground terminals together using 3 runs of color-coded magnet wire. I made a complete ring with each – although this was not strictly necessary, it provides two parallel current paths between any two points around the pentagon. Not a bad idea since the ones nearest where the power/data cable will plug in will be passing a lot of current. To avoid any surprises later, I quickly clipped the Arduino (running a simple test loop) to each assembled pentagon to make sure they were working. Finally, I tacked the wires together and onto the backside of each pentagon with hot glue. While I had the magnet wire handy, I took this opportunity to solder lengths of wire to each of the 30 “long” sticks as well.

Testing the pre-assembled segments to avoid surprises later

With the pentagons assembled and wired, I began assembling them onto on the inflated beach ball, adding the “long” sticks in between as needed. For this I just kind of winged it to avoid tolerance stack-up, marking and cutting one stick at a time for a good fit. After cutting to length, I cut a V-shaped notch in both ends of each stick so that they would mesh well with the pentagon corners they attached to. These were similarly glued into place, squeezing in extra hot glue to fill in any voids.

Sections being assembled on the beachball

Ball structurally completed, but not wired up

Finally! The buckyball looks like a buckyball. The beach ball was deflated and removed. By this stage each pentagon was already bussed together, so wiring consisted of using all the pre-wired “long” sticks to bridge neighboring pentagons together, folding or trimming any excess magnet wire and tacking it to the back of each stick (i.e. the inside surface of the ball.) After a final electrical test with the whole ball assembled, I sealed in all the LED boards and solder points with (you guessed it) more hot glue, made a hanger out of metal rod and cut two pieces of plexiglass to serve as the top and bottom supports for the rod. Bends in the hanging rod prevent the ball from sliding free of the rod or stretching out of shape.

Making endcaps for the top/bottom, which will support its weight when the hanging rod is passed through.

Completed ball running some cheesy visualizations

Electronics & Programming:
After some brief forays writing effects code in Processing and C (to run directly on the Arduino), I decided to write the visual effects script in Python for the most flexibility. The effects script outputs serial data to an Arduino (for the blog cred, yo), which serves as the bridge between the PC and the RGB LED array. Commands are sent as 3-byte packets to the Arduino consisting of a sync byte (0x55) followed by the Blinkenlichten address and command bytes. The Arduino clocks these out to the ball in the native Blinkenlichten single-wire format (not to be confused with Dallas 1-Wire(R) Protocol). See the Blinkenlichten page(s) for a full description of this protocol, but it basically consists of bits encoded as the ratio of ON-time to OFF-time on the wire (if the high “half” of the bit is longer than the low half, it is decoded as a 1 and vice versa), endcapped by a START (high for longer than the maximum bit ON-time) and END (low for longer than the maximum bit OFF-time) condition. Special commands exist for address discovery, address grouping, low-power mode and a couple other handy features.

Obviously, with 90 tiny microcontrollers and 270 unique LED dice potentially drawing power at the same time, there’s no way an Arduino / USB port can supply sufficient power on its own. A small perfboard mates the Arduino (running off the USB supply) to a 5V/4A wall wart for power to the ball electronics. The USB and wall-wart power are *not* bridged together (although the grounds are). Even at 4A (claimed) output capability, the wall wart struggles to keep the ball lit at full tilt – currently (hah!) it takes on a reddish hue if all the LEDs are going near full brightness, because the power supply output drops below the 4V or so it takes to light the blue LEDs. Also, the “3-wire” cable through my window now consists of 5 strands of rainbow wire (power and ground doubled up) because it was getting a tad warm with only one power and one ground strand.

Arduino supplies the data, while a 5V/4A wall-wart (black cable) supplies the power. The flat rainbow wire, is the perfect shape for sneaking around a closed window and to the ball outside.

When I assembled the Blinkenlichten nodes, I just programmed them serially and threw the finished ones in a box. Needless to say, by the time nodes were pulled from this box and the ball assembled, the node IDs are in no particular order. To make things easier on myself, I numbered all the locations in a logical way by writing them on the beachball with marker, then map the “human-readable” number to the actual node ID at that location in software. If you noticed various numbers scrawled on the beachball, this is what they are for (red: pentagon locations, black: “long” stick locations, green: actual node ID at that spot, in hexadecimal).

As of this writing, the effects script has only a tiny handful of effects I could throw together quickly – one pays homage the well-known “snake” games of yore, a couple others simulate paint splotches or crystal growth in various ways, and one just fades the primary colors in and out. Obviously, many of these require the software to have knowledge of the physical structure and relationships of the LED array. For this, the script includes a “neighbor table” consisting of a 2D list of each (human-readable) node number’s immediate neighbors. A couple more tables identify other relationships, such as which nodes are in the same pentagon or hexagon section. The script makes (or will make ;-) extensive use of das Blinkenlichten’s address-grouping features to run high-speed complementary effects on different logical groups with a minimum of bus traffic.

Last night I finished throwing together a workable version of the Weatherball, currently displaying a color code at the end of my flagpole to indicate whether tomorrow holds any interesting weather. Apparently cities and radio stations have been doing it since the 1950s, but now I have my own! The data is grabbed from the NOAA’s National Digital Forecast Database server (XML) using a quick C++ program, and currently acts on five variables: chance of precipitation, chance of hail, chance of tornado, chance of extreme wind, and cloud cover percentage. Any ‘interesting’ values in these fields are evaluated in order of decreasing importance (beginning with hail/tornadoes), and the most significant weather condition is sent to the das Blinkenlichten node in the ball at the end of the flagpole.

In the spirit of virtually all known Weatherballs to date, here is a not-very-catchy jingle expressing the color code:

If the weather ball is yellow/green, the sun’s expected to be seen
If the weather ball is gray, anticipate a cloudy day
If the weather ball is blue, it probably will sprinkle too
If the weather ball’s maroon, be prepared for a typhoon
And if the weather ball is red, forget the beach – head for the basement instead!

(And if the weather ball draws half an amp, the electronics have gotten damp – the Blinkenlicht inside the ball has been outside for nearly a year (just waiting for me to get around to writing the software) and seems to still work fine, but I really should weatherproof that sucker.)

Weatherball showing no interesting weather for tomorrow (yellow-green). Know what it needs now? More Power!

Here are the main parts – the ball is supposed to be a curtain rod capper.

The program to fetch weather reports from the interwebs and drive the ball

The DON’T PANIC flag* (Hitchhikers Guide reference; the house number is 42) is showing a bit of weathering, but is still intact. The flag pole is a piece of lightweight enameled metal rod from the big-box hardware store, intended for hanging stuff in closets. The bubbly clear ball on the end is a decorative curtain-rod endcap from Ikea. A set screw on the side allows it to be attached to rods of various diameters. The exposed clear plastic extends through the inner diameter of the endcap some on the inside, allowing the LED node placed in the base to light up the ball (though not brightly enough for my tastes; may beef it up a bit later). The material can also be drilled out to embed the LED further into the ball, if desired.

* If McCain gets elected, it will be replaced by a bright red PANIC! flag, and the weatherball recoded to display the current terrist alert level. It will operate briefly in this manner while I search for a homebuyer and a cheap one-way ticket to Canada…

This is the protocol spec and PIC10 firmware download for Das Blinkenlichten (1.2). This version improves handling of the ‘Identify’ command by end devices. Backward compatibility to 1.1 is not affected.

I don’t have pretty datasheets, application notes, timing diagrams, or much example code / pseudocode, but I’m sick of sitting on this thing until I get around to those :-P So I figured I’d just stick it on the internet and see what happens. The rest might be filled in in my future spare time (or by some nice person in the comments)…

Description:
Das Blinkenlichten is an open-source RGB LED lighting system and protocol written for the PIC10F200 microcontroller. A complete node consists of this chip, an RGB LED, three current-limiting resistors and a small capacitor for power-supply smoothing. A single chain can have up to 255 unique nodes (or as many non-unique ones as the wires will carry power/data for, without burning up!). Everything is coordinated by a single master device, which can be any low-cost microcontroller or equivalent with a free I/O pin. The beginnings and goals, etc. of the design are described on this page.

3 colors (R,G,B) can each be set to one of 9 intensity levels – total 729 possible colors

Set Group Address command: control groups of LEDs at a time with a single command

Deferred Update command: Allows simultaneous color changes even with a large number of devices.

Identify command: Discover the addresses present on the bus.

Power Save command: Stop all clocks and enter low-power mode until the next command

Arduino/Wiring library and exampleLibrary and example sketch showing common usages (move to /hardware/libraries in your Wiring folder, close and re-open Wiring. The example will then appear under Sketchbook -> Examples -> Library-Blinken). This library is released under the GPL.

Firmware and source code (PIC10 assembler)
Version 1.3 (“It’s Log!” edition) can be donwloaded in this zip file. A few lines at the beginning will have to be set according to your hardware configuration (see below). The “#define MYADDR …” line contains the address the chip should respond to – change this and rebuild for each chip you program. This version adds a logorithmic color table so that the color intensities you specify will appear linear (evenly-spaced steps) to the human eye. Adding this meant some idiotproofing had to be removed – sending an invalid (>8) intensity value will cause undesired operation. Starting with 1.3, this code is released under the GNU General Public License v3.

Version 1.2 (‘dumb’ linear color table) can be donwloaded in this zip file. A few lines at the beginning will have to be set according to your hardware configuration (see below). The “#define MYADDR …” line contains the address the chip should respond to – change this and rebuild for each chip you program.

Hardware/Schematics:

Basic schematic for an individual node. The schematic shown uses a common-anode LED (common is connected to VCC); for a common-cathode, tie the LED common to Ground instead and change the appropriate #define at the beginning of the code. Different LED packages have different pinouts; test and make sure of them before wiring it up – or especially making boards. (The R/G/B connections to the LED can be easily remapped in software, but you have to get the LED’s common right the first time!)

Basic wiring diagram for a complete string with several nodes and a controller. There is a pulldown resistor between the data wire and ground (recommended value 100k ~ 1Meg). This is necessary for the ‘IDentify’ command to work, and it also helps prevent your string of lights from going haywire if the controller takes some time to start up and the voltage on the line is floating.

The maximum cable length, and maximum nodes reliably driven on it (or maximum speed), depends on how strong your controller drives the data line. As you add length and extra loads (inductance and capacitance) to this wire, the voltages on it will take longer to rise and fall, and be more difficult for the nodes to reliably consider a ‘1’ or ‘0’. I’ve run them with 6+m of cable without incident, but if you run into problems, reducing the data rate should help. If you don’t care about the IDentify feature, you can add a beefy buffer here to drive the data line harder. If driving long strings, you might also want to add decent-sized electrolytic capacitors between the power and ground wires at intervals along the string.

PCB and Parts List (Bill Of Materials)This file contains a parts list for both DIP/breadboard and tiny surface-mount versions. Also included is an EAGLE schematic and Gerber files for a miniaturized, wearable board using surface-mount components. These data (schematics, BOM and Gerbers) are released into the public domain.

A picture of the finished SMT boards (panel) is shown below. With a jumbo 10mm RGB LED, the board is almost completely concealed behind the LED and only the sewable connections stick out.

Programming the chips
To write the firmware to these chips requires access to a PIC programmer. The official ones made by Microchip Inc. are quite expensive; fortunately there are many clones out there that work just as well, and some open-source designs you can build yourself. Just make sure that the programmer supports the PIC10 devices; some very simple/cheap designs (e.g. direct to PC serial port) may be designed only for “low voltage program” devices and won’t generate the +13V or so necessary to program this chip. The DIP variants can be programmed in or out-of-circuit; for the SMT variants, you will probably need to solder them to your board (with an appropriate pin header / etc for the programming lines), program the chip, and THEN add the LED (since it shares some of the programming pins and will interfere). For the ready-made SMT circuit board above, the following drawing indicates the pinout of the programming header on the edge of the board. For this, I soldered the wires from the programmer to the “wrong” side of a 5-pin 1.25mm connector (such as Molex 53047-05), then just press the other end into the PCB during program.

Start condition: Bus goes HIGH and stays high for longer than the longest possible loop run (min. ~ 17uS), so that all devices are guaranteed to catch it.

Data bits consist of a low period (low half) followed by a high period (high half). A 1 is denoted by making the LOW half longer than the HIGH half, and a 0 by making the HIGH half longer. Ideally, all bits should total the same length, but since the low half sets the baud rate on a bit-per-bit basis, this is not required. However, any half should be a minimum 18 device clocks (18/1MHz=18uS) for most accurate timing, and should not exceed 255 device clocks (255uS).

Stop condition: Give some time for the cmd to be processed before sending a new one (bus idles low). If you’re in a hurry, this time depends on the specific cmd. Otherwise, you can just wait about 90uS (time for the longest 1-way cmd, activate_deferred, to complete) and not worry about it. (*See special timing notes for Identify cmd.) This is the preferred approach, but you could also just make the START condition longer.

Address BYTE format:
bit <76543210>
AAAAAAAA

where AAAAAAAA is an 8-bit device address (or group address). Address 0 is the broadcast address. Since the ‘Group Address’ cmd only supports addresses up to 64 (0x40), I recommend a handful of low addresses (0x01 ~ 0x0F) be set asdie for group addresses if you plan to use this feature.

Extended Commands
11xxxxxx : Set Group Addr to value xxxxxx
10XXyyyy : Poke "Virtual reg" XX with contents yyyy (see below), where XX is the address of a virtual 4-bit reg and yyyy is the value to poke.

Vreg 01 ~ 03 allow a deferred update to be sent for the R, G and B channel respectively. The new intensity value(s) are stored in memory, but the old intensity values continue to be displayed until an activate_deferred command is executed, at which point the new intensities are displayed. This will be particularly useful for trickling new values over the bus, then sending a single activate_deferred to all devices (addr 0) to give the appearance of a simultaneous update.

Vreg 00 is a virtual register among virtual registers: Rather than writing a value to it, you write to it setting an individual bit to perform the requested action. Once the action is performed, the bit can be considered automatically cleared.

Unused (bit 3): “No-Op” – Dummy command, doesn’t do anything.

Identify (bit 2): On receipt of this cmd by a given device address, this device shall pull the data line HIGH (internal weak pull-up) for a period of about 512 device clocks (or whatever, plenty long enough for master device to see it). Normal operation is then resumed. (Note that this may disrupt other devices on the bus, who interpret the pullup signal as a new START command. If this is bothersome an Identify command may be followed immediately by a dummy command if a device responds. The device’s response will be seamlessly eaten by the dummy cmd’s START, so it just looks like an extra-long start bit to all devices.)

Activate_deferred (bit 1): Replaces the currently displayed intensities with the contents of the Defer (R,G,B) regs if they contain a valid update.

Power_save (bit 0): This command will effectively stop the CPU and any pulse modulation activities and enter a low-power SLEEP mode. The device will remain in SLEEP mode until the next bus activity occurs, at which point it will re-awaken. Technically it will be waking up occasionally due to WDT, but these activity periods will be brief.

Identify all the devices on the bus (pseudocode):
for (id = 1 to 255)
{
Send1Wire(id, b'10000100'); // Extended cmd: Identify
Delay(100); //delay 100 uS, to give device time to respond
DATA_WIRE = 'INPUT'; // However you switch this pin to an input on your preferred platform
gotResponse = DATA_WIRE; // read the data line to check if anyone is responding (pulling the data line high)
DATA_WIRE = 'OUTPUT'; // Switch it back to an output (retaining same value as was read)
if (gotResponse == 1) // Was there a response?
{
print("Found device " , id); // do something with this information, e.g. store active IDs to a table
Send1Wire(0, b'10001000'); // send dummy command if there was a response.
// Or you can just wait a few hundred msec for the other devices to reset
}
}

Specific firmware points of interest

The intensity of the LED colors is controlled using pulse width modulation (PWM). The basic operation is that the intensity value for each color (0 ~ 8) is converted to that many ‘1’s and stored in a register, and this register’s contents are continually rotated in circles. Each time, the last bit is used to determine the on/off state of the LED. So with 8 bits in a register, it can be on 0/8 of the time (off) or 1/8 of the time or … 8/8 of the time (full brightness).

To provide the fastest possible update rate, it’s necessary to squeeze as much performance out of these cheap tiny PICs as possible. The PIC10 supports only a handful of instructions, no interrupts, and only a rudimentary 8-bit timer. For the PIC10F200, the entire program is limited by memory to 255 instructions. Therefore we can’t afford to be too sloppy.

The code uses look-up tables in place of any loops/math wherever possible. The chip does not support table-indexing operations in ROM (Flash), so this is done using computed GOTO: In many microprocessors including this one, the Program Counter register (which acts as the processor’s bookmark in the code’s execution) can be modified by the program directly. Thus, by writing a new value to the Program Counter you can force the processor to lose its place, resuming program execution from the address you just wrote. By performing math operations directly on the program counter, this method can be used to index a lookup table nearby. In the example below, the intensity value is added to the program counter to make it an index into a table stored just after it. The ‘retlw’ instruction returns from the function with a specific value saved in a register. This code snippet converts the (0 ~ 8) value to a value containing that many ‘1’s, using only a few clock cycles.

This same method is used to allow the PWM loops to keep running while receiving data. Each time a bit is received, the PWM loop “jump table” is called. The count of the number of bits received is used as the index into the jump table; instead of a list of data values, each table entry contains a jump (GOTO) to the address of either the red, green or blue updater.

Determining between ‘1’ and ‘0’ bits on the data wire also takes just a few instructions. For each bit, the line is held low for some amount of time by the controller, then held high. Whether the low or high half was longer determines whether it was a 1 or 0. The way to measure this can be thought of as a stopwatch that counts up during the first half, then down during the second half. If the count goes negative, the second half was longer and we record a ‘0’, else we record a ‘1’. The ‘stopwatch’ here is the chip’s 8-bit counter/timer register. But it can’t count down, it can only count up! So a little cheating: when finished timing the first half, we complement (invert) the contents of the timer register. Now it’s still counting up, but if the original count was 7, now it’s (256-7) or 249. If the 2nd half is longer, the timer will reach its maximum value (255) and roll over to zero again like an old car odometer (and ending at a low number again, the Most Significant Bit will be a 0). Otherwise it will be a high number, and the Most Significant Bit will be a 1. So, at the end of one up/down cycle on the data line, the timer’s MSB will automatically contain the correct bit as it was sent on the data wire.

You might also have noticed that the PIC10 only has 3 pins that can function as outputs; according to the datasheet the 4th (which I’ve used for the data wire) is input only. The 3 output pins are already being used to drive the 3 LED colors. So how does a node send data back to the controller in response to an IDentify command? There is a register setting that enables weak pullup resistors (~10k-20k) on all the pins. On the output pins this does nothing, but on the input (data) pin this applies voltage to the wire, overpowering the even weaker pulldown resistor (~100k) added to the controller side. (The controller stops driving the I/O pin briefly to await this response.) So by toggling the pullup resistor on and off, we can send data the ‘wrong’ direction :-)

Version History and Compatibility:

v0.x (2005) Nora Nightlight edition. Quick n dirty hack with hardcoded timer to distinguish 1/0 data bits. Set Group Address is the only valid extended command. Didn’t get around to touching it again for a long time.

v1.0 (2007) Beloved edition, demoed at VNV Nation concert April 07. Changed from fixed-frequency to variable baudrate data encoding/decoding; re-ordered some bits in the command packet format to make more sense.

v1.2 (2008) More-Proper edition. Improved handling of IDentify cmd; now can avoid flashes during identify as non-responding devices on the bus reset. Compatibility with v1.1 devices is not affected.

v1.3 (2009) It’s Log! edition. This version implements a logorithmic color scale in order to better match the human visual response (intensity steps now appear evenly-spaced). Compatibility with v1.1 and v1.2 devices is not affected, unless you are sending invalid (>8) intensity values, which you shouldn’t be!

v1.1, v1.2, v1.3 are backward / forward compatible and can be used on the same bus. V1.0 supports only the basic command set (Set Colors and Set Group Address). Versions prior to 1.0 are not compatible at all…luckily, they basically don’t exist in the wild.