Tero's Arduino Blog

Using Arduino with Ada

Driving Neopixel LEDs using only Ada

Inspired by my earlier delay experiments and its followup discussion on AVR-Ada mailinglist, I decided to put my delay functions in good use.

Neopixel RGB LEDs require exact timing and people usually use AVR assembler code to get the timing right. However, I wanted to see can I do it with plain Ada on normal Arduino.

Short answer is: yes, it is doable.

First, I ignored most of Adafruit's advices and created a simple setup:

Neopixel ring and Arduino

After that, I took the protocol timings from one blog and created my routines for sending data to Neopixels:

procedure Send_Bit (B : Boolean);
pragma Inline_Always (Send_Bit);

procedure Send_Bit (B : Boolean) is
begin
   if B then
      LED_Out_Port := True;
      Wait_T1H;
      LED_In_Port := True;
      Wait_T1L;
   else
      LED_Out_Port := True;
      Wait_T0H;
      LED_In_Port := True;
      Wait_T0L;
   end if;
end Send_Bit;

procedure Send_Byte (X : Interfaces.Unsigned_8) is
   use Interfaces;
   B : Unsigned_8 := X;
   Highest_Bit : constant := 2#1000_0000#;
begin
   for I in 1..8 loop
      Send_Bit ((B and Highest_Bit) = Highest_Bit);
      B := B * 2;
   end loop;
end Send_Byte;
pragma Inline_Always (Send_Byte);

procedure Send_Pixel (R, G, B : Interfaces.Unsigned_8) is
begin
   -- Neopixel wants order green red blue
   Send_Byte (G);
   Send_Byte (R);
   Send_Byte (B);
end Send_Pixel;
pragma Inline (Send_Pixel);

The tricky bits were the Wait_Txx procedures. They need to be correct to Neopixels to work properly.

After playing a few minutes with a logic analyzer (Saleae Logic), I found out that the Wait_Cycles procedure of AVR-Ada allows small enough delays, but the accuracy is not that good when speaking about delays less than 1 microsecond.

I ended up using following wait procedures:

T0H : constant := 100; -- 375ns
T0L : constant := 250; -- 750ns (rjmp included)
T1H : constant := 470; -- 687.5ns
T1L : constant := 60;  -- 625ns (rjmp included)
RES : constant := 6000;

procedure Wait_T1H is new Custom_Delay.Generic_Wait_NSecs
     (Crystal_Hertz => 16_000_000,
      Nano_Seconds => T1H);
pragma Inline (Wait_T1H);

procedure Wait_T1L is new Custom_Delay.Generic_Wait_NSecs
     (Crystal_Hertz => 16_000_000,
      Nano_Seconds => T1L);
pragma Inline (Wait_T1L);

procedure Wait_T0H is new Custom_Delay.Generic_Wait_NSecs
     (Crystal_Hertz => 16_000_000,
      Nano_Seconds => T0H);
pragma Inline (Wait_T0H);

procedure Wait_T0L is new Custom_Delay.Generic_Wait_NSecs
     (Crystal_Hertz => 16_000_000,
      Nano_Seconds => T0L );
pragma Inline (Wait_T0L);

procedure Wait_Reset is new Custom_Delay.Generic_Wait_USecs
     (Crystal_Hertz => 16_000_000,
      Micro_Seconds  => RES);
pragma Inline (Wait_Reset);

With the Neopixel code in place, the main program was simple to do:

procedure LED is
   use Interfaces;
   use Neopixel;

   procedure Wait_1000ms is new Custom_Delay.Generic_Wait_USecs
        (Crystal_Hertz => 16_000_000,
         Micro_Seconds => 1_000_000);
   pragma Inline (Wait_1000ms);

   procedure Wait_100ms is new Custom_Delay.Generic_Wait_USecs
        (Crystal_Hertz => 16_000_000,
         Micro_Seconds => 100_000);
   pragma Inline (Wait_100ms);

   A : Unsigned_8 := 200;
   B : Unsigned_8 := 100;
   C : Unsigned_8 := 10;
   A_Step : constant := 5;
   B_Step : constant := 6;
   C_Step : constant := 7;
   Loop_Counter : Unsigned_8 := 1;
begin
   AVR.Interrupts.Disable;

   Neopixel.Init;
   Neopixel.Reset;
   for I in 1..12 loop
      Send_Pixel (R => 0, G => 0, B => 0);
   end loop;
   Reset;

   Wait_1000ms;

   loop
      for I in Unsigned_8 range 1..12 loop
         if Loop_Counter = I then
            Send_Pixel (R => A, G => B, B => C);
         else
            Send_Pixel (R => 0, G => 0, B => 0);
         end if;
      end loop;
      Loop_Counter := Loop_Counter + 1;
      if Loop_Counter > 12 then
         Loop_Counter := 1;
      end if;

      Reset;
      Wait_100ms;

      A := A + A_Step;
      B := B + B_Step;
      C := C + C_Step;
   end loop;
end LED;

If you have a browser with flash support, you can see the end result below:

As usual, the code can be found from Sourcehut. In addition, in this case I put the hexfile there also.


Copyright © 2012, 2013 Tero Koskinen - Theme Skeleton; Blogging engine Pelican; Powered by Python