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:
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.