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.