Introduction: Rainbows in MakeCode on a 4tronix Cube:Bit (RGB LED Cube)

This article is a follow-up to Instructables: MakeCode Fun on the 4tronix Cube:Bit (an RGB LED Cube) demonstrating two rainbow animations. The code is written in Makecode Blocks for the BBC micro:bit with an internal choice of a simple (discrete) rainbow palette or a more complex continuousspectrum from the sRGB colour space, the latter is used throughout this article.

The Cube:Bit is a three dimensional array of individually-addressable, multi-coloured LEDs which can be quickly assembled using only a screwdriver. The RGB LEDs use the WS2812 protocol and can easily be controlled with libraries which are often named NeoPixel after Adafruit's trademark for these LEDs. There's also a convenient MakeCode extension for the Cube:Bit which is used for this program.

The modern V2 micro:bit was used for this article. The code fits onto the "smaller" V1 but the update rate is slow with the current implementation.

This was created for the the Instructables Colors of the Rainbow contest and aided in various ways over the years by the works of Clive Sinclair, Richard Altwasser, John Grant, Steve Vickers, David Webb, Frank O'Hara and Ian Logan.

Supplies

Step 1: Colour Spaces

A rainbow (Wing-Chi Poon's photograph in Jasper National Park features above) is formed from the white light of the Sun being refracted by water drops. The almost continuous range of frequencies of light which are emitted by the Sun are refracted to different degrees by the water resulting in the individual colours becoming spread out and clearly visible. Most humans perceive these colours via three types of cone cell which are sensitive to red, green and blue light. These primary colours can be used to recreate the colours of the rainbow, i.e. red and green mixed appear as yellow. The secondary colour magenta, a mix of red and blue, is missing from the rainbow!

For accurate colour rendering a standard is required to specify colours with an output device which can ideally support that range of colours. The sRGB colour space has become the most common one used for images intended to be shown on a modern computer screen and other similar devices like a smartphone. The range of sRGB colours (gamut) is shown above as the triangle within the horseshoe-shaped CIE 1931 chromacity diagram. The colours beyond the triangle cannot be rendered on a typical computer screen so are simulated here with faux colours. The visible light electromagnetic spectrum shown alongside the photograph will also be an approximation. For comparison, digital cinema was introduced with the larger DCI-P3 colour space and many of the subsequent advanced technologies, like Dolby Vision, have now reached consumer products. The Apple iPhone 7 was the first smartphone with a wider gamut.

To generate a reasonably accurate rainbow on a computer monitor or an RGB LED (pixel), a function is needed to convert a frequency (or wavelength in a vacuum) of light into a (red, green, blue) triplet in the sRGB colour space. Various approaches to this are discussed in Stack Overflow: Convert light frequency to RGB? Haochen Xie's analytic approximation code based on Simple Analytic Approximations to the CIE XYZ Color Matching Functions is temptingly compact and can be easily implemented in MakeCode.

Step 2: Discovering a Mathematical Oddity

The function in MakeCode to produce an RGB colour from a wavelength appeared ok from a casual inspection but after a while it became obvious something was amiss as it didn't look correct particularly around the reds which should be at one end of the range of visible light.

The problematic function is recreated above on the 60 mini RGB LEDs on a Kitronik ZIP Halo HD. There's a transition from yellow to red and then it alternates in a very strange way. There's also a lack of graduation in the transitions between colours and a small blue peak in the violet.

MakeCode has a very useful simulator and debugger allowing the code to be executed and single-stepped in a browser. Some extensions also have a visual emulation in the simulator but this is not present for the Cube:Bit. The colour values produced by the function looked reasonable from variable inspection in the debugger adding to the confusion.

There are sometimes issues with floating-point calculations producing slightly different results, for example:

  • calculations are performed using different size floating-point representations, like 64bit (double in C) vs 32bit (float in C) in code, or 80bit vs 64bit in hardware;
  • final or interim values are stored at lower precision;
  • rounding algorithms are different;
  • (lengthy) calculations are re-ordered;
  • common mathematical functions produce very slightly different results;
  • presentation (typically rounding) of the result is different.

None of those is likely to cause the gross errors apparent in the broken function.

The culprit was eventually found via some searching which yielded a helpful forum post, MakeCode Forum: Math.pow floating point issues. The ** operator is only partially implemented on the micro:bit, for literal values it works but if variables are introduced it silently rounds the exponent to the nearest integer! This was checked and confirmed with some testing and later turned into a visual demonstration for this article. This is a confusing issue as the simulator and hardware behave differently.

Step 3: Resolving the Mathematical Oddity

The rainbow program needs to be able to calculate:

  1. e^x for negative real values of x and
  2. a^(1/2.4).

In the past it was common for floating-point to be implemented in software and there was a need for very efficient approximation of mathematical functions like exp. The Sinclair ZX81 did this amongst other things in an 8K ROM on a Z80 processor which only has integer addition and subtraction. The ZX Spectrum used the same code - borrowing this code is a tempting choice for a program making rainbows.

The implementation based on Chebyshev polynomials can be found in the book, The Complete Spectrum Rom Disassembly replicated on Spectrum ROM Routines 36C4: The 'EXPONENTIAL' Function. A C implementation is shown in an answer on Stack Exchange: Software Engineering: How to use chebyshev polynomials to calculate exponents (antilogs)?

This was easily converted to MakeCode to implement e^x. The a^(1/2.4) is for sRGB gamma correction and does not need to be perfect in this case. Since a^(1/2) is already present in MakeCode as the sqrt function this was substituted as a suitable, close alternative.

Step 4: Rainbows

This program produces rainbows, it has two modes selected with button B (the one nearest the viewer): the first spins a circular rainbow with gaps - these appear as arcs and disappear on the cube as it spins; the second is a section of a long rainbow which "falls" through the cube slowly and repeatedly.

Button A toggles a mode where the y coordinate (moving away from the viewer) changes the calculations slightly. This is useful if the cube is viewed from a slight angle above its centre since this gives the appearance of increased vertical colour resolution for the spectrum.

Step 5: Going Further

Once you've got your Cube:Bit making rainbows, here's some other ideas to explore

  • Experiment with other rainbow animations, perhaps drawing wavelengths across pixels or 3/4/5 pixel length lines on the Cube:Bit.
  • For V2 micro:bit, add some sounds effects.
  • Optimise the code for faster update rates mainly for the benefit of the V1 micro:bit. This can easily be done with simple lookup tables for the spinning arc mode.

Related projects:

Further reading:

Colors of the Rainbow Contest

Participated in the
Colors of the Rainbow Contest