RGB123 256 leds and rfduino first hacks

I recently received 2 fun stuff from kickstarter projects, the first one is a 16×16 leds from RGB123 and the second one a bluetooth LTE arduino device from rfduino.

Some information of this hack in the next part of the article

 About RBG123 16×16 shield

The first point I want to check with the 256 led shield from RGB123 is the power consumption : as I want to use it in an embedded system, the 15A announced are a big issue in my design. My objective is to find a way to use it with a lower consumption, based on luminosity and number of used Led limitations.

Basically, the shield, when connected with no led on is requiring 250mA on 5v.

About RFDuino

RFDuino is compatible arduino shield (rfd22102) based on a RFD22301 component (price about 15$) containing a Arm Cortex – M0 capable of bluetooth LTE communication + an antenna. The core is the nRF51822 from Nordic, it’s a 32-bit ARM Cortex-M0.

My version has 128kB flash memory, 16KB Ram (8K stack, 8K apps), bluetooth capabilities from 250Kb to 2Mb, running at 16MHz. There is also a built-in temperature sensor

About WS2812b

RGB123 shield is based on WS2812b leds. These leds include a controleur and they are serial, 1 wire. It means to light on a led, you have to push a RVG value on the serial interface. This fill the first led from the series. Then you push a second RGB values. This  goes to the second led, third value goes to the third one and so one. Value are pushed with no pause between each. ad the end a pause (low signal) longer than 50uS light the leds all together.

RGB values are 8b each, so you send bit per bit starting from G and high bit first like this

g7-g6-g5-g4-g3-g2-g1-g0-r7-r6-r5-r4-r3-r2-r1-r0-b7-b6-b5-b4-b3-b2-b1-b0

The main but big issue with these Leds is the timing to load these values. As there is no clock signal you have to respect a certain timing. This timing for a 1 or a 0 is 1.25uS (800KHz) due to the encoding or 0 and 1 you have in fact in this timing 3 period to manage so you system need to be capable to generate 2.4Mhz signal to manage these leds.

  • 0 is 0.4uS High then 0.85uS Low signal
  • 1 is 0.85uS High then 0.4uS Low signal
  • > 50uS low is a reset
  • assuming high is maintaining the value

Timing is critical : as you see the only difference between a 0 and a 1 is the timing.

Said differently you have to manage 0.4uS sleeps between two signal state change. With arduino libs we do not have a such precision, going to assembly is mandatory. The bad news for me is that is a lib exists for AVR core, actually I did not find one for ARM.

Two options are available, the nice and complex one : based on on PWM configuration or the SPI configuration it should be possible to get a perfect timing. The worst one : taking a look to delayMicroseconds function and create a delaySubMicrosecond. As delayMicrosecond is based on a sequence of 11NOP, I can assume each are about 0.1uS but theorically speeking 1NOP = 1 cycle = 0.0625uS assuming that rest of time is passed on the loop control and fuction call. I will base my timing on this last asumption and avoid digitalWrite calls… let’s try that first !

Regarding the given timing, we must have a program running the following speed

  • 0 : T0H + T0L

0,4uS +/- 0,150uS  => 250ns to 550ns => 4 cycles to 8 cycles

0,85uS +/- 0,150uS => 700ns to 1uS => 12 cycles to 16 cycles

  • 1 : T1H + T1L

0,8uS +/- 0,150uS => 650ns to 950ns => 11 cycles to 15 cycles

0,45uS +/- 0,150uS => 300ns to 600ns => 5 cycles to 9 cycles

 

Remark : some projects like fastled_spi should also be a solution but it seems rfduino support is still in progress. And for sure I like to redo stuff and also simplify.

WS2812B are 5V leds but, according to my test, the control interface is 3.3V tolerant. So the RFDuino shield can be directly connected. No signal translation like 74hc4050N is required.

Quick start : lighting one led

Before being able to write the right code, as assembly language will be needed to manage the timing through cycles, I need to find how to get access to assembly. This tutorial worked well for this. To summarize :

  • First activate verbose compile output in arduino application preferences.
  • Compile the project and get in the output the path were .elf file has been stored
  • Open a command line and go to that directory
  • on Mac Os X, with an arm based arduino, type /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/g++_arm_none_eabi/bin/arm-none-eabi-objdump -S ./xxxxxxx.cpp.elf

Then, I must have the cortex M0 instruction set with cycle to evaluate the right program duration (here) and write the right assembly function to make it works. Here is my code to light a WS2812b led with a RFDuino :

void ws2812_write1value( uint32_t pin, uint32_t r, uint32_t g, uint32_t b) {
   uint32_t bits[24];
  // to not loss time in computation later, transform the 
  // int value into 32 bit
  // value directly orgnized to be send to GPIO port
  int j=0;
  for (int i=7 ; i >= 0 ; i--,j++) bits[j] = ( ( g >> i ) & 0x01 ) << pin;
  for (int i=7 ; i >= 0 ; i--,j++) bits[j] = ( ( r >> i ) & 0x01 ) << pin;
  for (int i=7 ; i >= 0 ; i--,j++) bits[j] = ( ( b >> i ) & 0x01 ) << pin;

  volatile uint32_t * outset = &NRF_GPIO->OUTSET;
  volatile uint32_t * outclr = &NRF_GPIO->OUTCLR;
  uint32_t * _ptbits = bits;
  int _b = 1 << pin;

  noInterrupts();
  asm volatile (
    "headD:\n\t"
      "push {r2,r3,r4,r5,r6,r7}\n\t"
      "mov  r5,%2\n\t"            // _ptbits (r1) 
      "mov  r7,%0\n\t"            // outset  (r3)
      "mov  r3,%1\n\t"            // outclr  (r2)
      "mov  r2,%3\n\t"            // _b (r0) : value to write in register set/clr
      "mov  r4,r5\n\t"
      "add  r4,#100\n\t"          // r4 = b table ending address ( while ( _ptbits < r4 ) {  
      "ldr  r6,[r5,#0] \n\t"      // _b = *_ptbits // 2 cycles (out of the main loop because too long )
      "loopD: \n\t"                                // 5 cycles before write 1
        "add r5,#4\n\t"           // _ptbits++     // cycle 1
        "cmp r5,r4\n\t"           // _ptbits < r4 (end) // cycle 2 
        "beq endloopD \n\t"                             // cycle 3 - 3 to 5 when taken
        "str r2,[r7,#0] \n\t"     // *outset = _b       //  cycle 4-5 
        "cmp r6,#0 \n\t"          // if ( _b != 00 )  { // cycle 1
        "beq elseD \n\t"                                // cycle 2 - 2 to 4 when taken
        "thenD:\n\t"                   // case 1 ( 11-15 cycle high then 5 - 9 cycle low )
           "ldr  r6,[r5,#0] \n\t"      // _b = *_ptbits    // cycle 3-4 
           "nop\n\t"                                    // cycle 5
           "nop\n\t"                                    // cycle 6
           "nop\n\t"                                    // cycle 7
           "nop\n\t"                                    // cycle 8
           "nop\n\t"                                    // cycle 9
           "nop\n\t"                                    // cycle 10
           "nop\n\t"                                    // cycle 11
           "str r2,[r3,#0] \n\t"     // *outclr = _b    // cycle 12 - 13 
                                                        // -- now 6 cycle for 0  
           "b loopD \n\t"                               // cycle 1 to 3 
                                       // --> 5 more cycle after = total 8 cycles 

        "elseD:\n\t"                 // case 0          // else : 4-8 cycles for 1 and 12-16 cycles for 0
           "str r2,[r3,#0] \n\t"     // *outclr = _b    // cycle 5-6 
                                        // -- now 14 cycle for 0 (6 on top of loop=
           "ldr r6,[r5,#0] \n\t"     // _b = *_ptbits   // cycle 1-2 (out of the main loop because too long )
           "nop\n\t"                                    // cycle 3
           "nop\n\t"                                    // cycle 4
           "nop\n\t"                                    // cycle 5
           "nop\n\t"                                    // cycle 6
           "b loopD \n\t"                               // cycle 7 to 9 
                                        // --> 5 more cycles after  

      "endloopD: \n\t"
      "pop {r2,r3,r4,r5,r6,r7}\n\t" 
    ::
    "r" (outset),   // %0
    "r" (outclr),   // %1
    "r" ( _ptbits), // %2
    "r" (_b)        // %3
 );
 interrupts(); 
 delayMicroseconds(60);


void ws2812_init( uint32_t pin ) {
   pinMode(pin, OUTPUT);
   // 50 us low voltage for reset
   NRF_GPIO->OUTCLR = ( 1 << pin );
}

void setup() {
  ws2812_init(toRGB);
}

void loop() {
  ws2812_write1value(toRGB,0x55,0x00,0x00);
  delay(1000);               // wait for a second
  ws2812_write1value(toRGB,0x00,0x00,0x00);
  delay(1000);               // wait 
}

 Now, let’s play for real

The next step is to manage the 16×16 led matrix and light any led with any color. For this I am maintaining a table of 256 int. Each int is a 32bit array containing the 24bits color code, ordered to be ready to be sent directly to the leds.

The procedure to show up led has been written in assembly and for each Led, the 24bits are proceeded one by one is an inline procedure. It built a longer code but it was the ore easiest way to preserve the timing. I’m sure someone can rewrite it in a smaller function. As the source in GNU/GPL, feel free to optimize it and push me your result.

Here is the full sketch lighting all these lights on !

test_rfduino_rgb.ino

Have fun and let me a message if you are using it !

 

2 thoughts on “RGB123 256 leds and rfduino first hacks

  1. I also backed both projects, with the intention of using the RFduino to sequence some WS2811-type one-wire RGB strips! I had not yet found the Specification page for RFduino, thank you for that link. It answers more of my questions than reading the example sketches.

    I have had good luck with my projects to design sequences for shift register-based LEDs. I’ll use your example to set up the RFduino, and add my sequences. (The square format will mean solving a few more puzzles. 🙂 I’ll be happy to share sketches as well. Best regards.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.