Orthogonal Fans with Pixelblaze 3D Mapping

I have built a control board for a trio of affordable PC case fans, replacing their bundled control hub of this Asiahorse Magic-i 120 V2 system. With my board I can control individual RGB LED inside these fans with help of a Pixelblaze LED controller. My initial tests used simple built-in linear patterns, but I wanted to use patterns that take advantage of 3D coordinate mapping. This is my favorite part of Pixelblaze and a core part of my Glow Flow project.

To get a 3D structure out of three fans, I did the easiest and most expedient thing: orient them orthogonal to each other with a few twist-ties. Now every fan motor axis lines up with one of three axes in 3D space. I have a Pixelblaze 3D coordinate test program already in hand, the RGB-XYZ 3D Sweep program I created during Glow Flow. All I had to do was create a 3D coordinate map in my Pixelblaze to describe LED layout in three-dimensional space.

I opened up the Pixelblaze mapping editor and… immediately got stuck. Where, exactly, should I map these LEDs? Physically, they are surface mounted on the central hub. However, their light diffused by translucent fan blade plastic, no longer fixed to a single location but distributed across entire fan diameter. Should I map the LEDs where they are physically? Or fan blade outer perimeter? Or somewhere in between? There really isn’t one single location for resulting output of a single LED.

I decided to experiment by writing my mapping code so a single variable controls how far from the center each location is. When pixelRadius is zero, everything is at the center and not very interesting. When set to 0.5, everything is mapped to the perimeter. I adjusted this value until the pattern “looked right” and that ended up at 0.4. I’m not satisfied with the empirical nature of this value, but I haven’t figured out a better way to account for diffusion.

Another problem with these LEDs is that they weren’t placed with precise coordinates in mind. I think LEDs were laid out on the circuit board relative to wiring bundle location, which is slightly offset from one of the four mounting arms. As a result, the LEDs aren’t aligned to any reference point on the exterior. To use an analog clock face as example, these LEDs are evenly placed but slightly offset from the hour numbers. Instead of lined up at 12, 1, 2, 3. They are at 12:10, 1:10, 2:10, etc.

These fans are physically squares with side lengths of 120mm. They can be installed in one of four orientations that are 90 degrees from each other and be mechanically fine. I usually choose my orientation based on whichever makes the wiring most convenient. But whatever the motivation, my 3D coordinate map would have to compensate for the resulting rotation.

The answer for both of the above rotation compensation concerns is an array fanRotationCorrection. One value for each fan, a sum of physical and LED offset rotations (in radians) added into coordinate map angle calculation for that fan.

Here is the result of my 3D pixel map running my RGB-XYZ sweep test program:

Here is my Pixelblaze Pixel Mapper code. [UPDATE: I later noticed that I got my Z-axis backwards. Be aware that both the video embedded in the tweet and this pixel mapper code are known to be flawed. At least they’re consistent with each other!]

function(pixelCount) {
  pixelPerFan = 12;
  pixelRadius = 0.4;
  fanRotationCorrection = [0.65, 0.65, 0.65];

  var map = [];
  for (i = 0; i < pixelCount; i++) {
    var ledNumber = i % pixelPerFan;
    var ledRadians = ((ledNumber/pixelPerFan) * Math.PI * 2);

    if (i < pixelPerFan) {
      ledRadians += Math.PI*fanRotationCorrection[0]
      map.push([0, 0.5 + Math.cos(ledRadians)*pixelRadius, 0.5 - Math.sin(ledRadians)*pixelRadius]);
    } else if (i < pixelPerFan*2) {
      ledRadians += Math.PI*fanRotationCorrection[1]
      map.push([0.5 - Math.cos(ledRadians)*pixelRadius, 0, 0.5 - Math.sin(ledRadians)*pixelRadius]);
    } else if (i < pixelPerFan*3) {
      ledRadians += Math.PI*fanRotationCorrection[2]
      map.push([0.5 - Math.cos(ledRadians)*pixelRadius, 0.5 + Math.sin(ledRadians)*pixelRadius, 0]);
    } else {
      // Unexpected pixels are placed at origin
  return map;

My Asiahorse investigation results were also posted to Pixelblaze forums. And now that I have full and complete control over these fans, I no longer need the hub that came in the box.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s