My4TH can now drive NeoPixel LEDs
First of all:
The original My4TH cannot control NeoPixels directly. Instead, you need to install the special
firmware ROM "my4th-neo", which is included in the "my4th-roms" ZIP file available in the
Downloads section.
The firmware requires My4TH to be clocked at 10 MHz.
What are NeoPixel?
Nowadays, NeoPixel is a generic term for digitally controllable and daisy-chainable RGB LEDs.
But originally, the brand name NeoPixel was created by Adafruit for WS2812-compatible RGB LEDs.
A WS2812 LED has four pins: VCC, GND, data input and data output.
There is no limit to the number of LEDs that can be daisy-chained together. Each individual LED
in the chain is controlled by a 24-bit wide serial data word, which contains the 8-bit brightness
levels for the colors red, green and blue. The data rate of the bitstream is typically around
800 kHz. At this speed, it’s possible to drive a 32 x 32 LED matrix with a refresh rate of 30 Hz.
Generating the Serial Data
Modern MCUs are fast enough to generate the serial data stream in software. There are many
libraries available on the internet for nearly every hardware platform. But the My4TH platform
is much too slow. It should be clear that a driver for WS2812 LEDs can not be written in Forth.
Also controlling the LEDs through an lower-level assembly program is not possible. It is also
impossible to generate the bitstream within the My4TH microcode while adhering to the timing
specifications defined in the WS2812 datasheet.
Fortunately, though, it’s not as difficult as it might seem at first glance from the
WS2812 spec sheet. As
Josh Levine discovered,
it isn’t actually that difficult to ensure a working bitstream timing if you take the
maximum permissible tolerances into account. He also found that the required reset time
(referred to as RES or TLL in the datasheet) is just 6 µs. Armed with this knowledge,
I managed to implement a working bitstream transmitter in the My4TH microcode!
Work Around Platform Limitations: Memory and Speed
To speed up the transmission and limit the memory required for the color data, the transmission routine
uses only an 8-bit color value (1 byte) per LED instead of 24 bits. This reduces the number of available
colors to 255 plus black (all LEDs off), but it also reduces memory requirements. Due to the tight
timing constraints, the transmission routine must be informed in advance of how many LEDs are
to be controlled. Therefore, the user must prepare a buffer containing the color values for all
LEDs in the chain. Unfortunately, the counter for the number of LEDs to be controlled is limited
to 8 bits, as the tight timing does not allow for the use of a 16-bit counter. For this reason,
My4TH can control a maximum of 256 WS2812 LEDs.
My4TH-neo Color Table
This table shows all 256 colors that My4TH-neo can display on WS2812 LEDs.
Click on the table to open it in high resolution in a new window:
The Assembly Instruction that controls the LEDs
The My4TH-neo ROM contains very complex microcode for controlling the LEDs. To make room for the microcode,
I had to remove support for the Forth Deck from the ROM. Don’t confuse it with the My4TH-nfd ROM.
My4TH-neo is just as slow as the original My4TH ROM, but it does not support the LCD and the keyboard.
As a little bonus, it also includes the improved text editing features.
The new instruction has the opcode 0x6F, and its mnemonic is "NEO". The instruction expects
two input values: The PTR register must point to the LED color data in RAM (one byte per LED), and
the accumulator must be set to the number of LEDs in the chain. If the chain contains 256 LEDs,
the accumulator is set to zero. Note that the NEO instruction itself is only one byte long.
The Forth Way
Of course it is also possible to control the LED string with a program written in Forth. The corresponding
Forth word is "neo":
neo ( ptr cnt -- )
The word takes two inputs: A pointer to the serial data stream and the count of bytes (colors) to send,
which must not exceed 256. Note that each LED is controlled by a single byte (8 bit color code).
The ROM contains a table that translates between the color byte and the 24 bit RGB value needed
by the NeoPixel LEDs.
Connecting the LEDs to My4TH
The serial data stream is output at pin 18 of J3 (Output 7). If My4TH is only controlling a few LEDs,
you can take the power supply for this LEDs from pin 2 (+5V) and pin 20 (GND) of J3. Note that a larger
number of LEDs (like the 8x8 LED matrix in my examples) quickly consumes a lot of power, especially
if the LEDs are set to a very bright level. In this case, the LEDs cannot be powered via USB.
Important to note: The remaining digital outputs 3 to 6 of J3 remain usable, but their state
is reset to zero every time the LEDs are updated. For example, you can still connect a serial
shift register such as the 74HC595 to expand the output port. But faster would be an I/O port
expander connected via I2C.
Example Projects
This ring clock consists of a NeoPixel ring with 12 LEDs and a DS1307 RTC board:
----[001]-------------------------------------------------------
\ Display time on a NeoPixel ring with 12 LEDs (by D. Kuschel)
\ Uses a RTC chip clock driver that exports the word GETTIME
decimal ( You can use my DS1307 or PCF8523 RTC driver )
5 constant brightness ( range 0 - 15 )
3 constant red 6 constant yellow 8 constant green
10 constant cyan 14 constant purple 12 constant blue
\ the hour is blue, minutes blink colorful, see order below:
create mcol red c, yellow c, green c, cyan c, purple c,
create leds 12 allot
: col! ( pos col -- ) brightness 4 lshift or swap leds + c! ;
: .clock ( sec min hour -- ) \ display time on the ring
leds 12 0 fill 12 mod blue col! \ all LEDs off, set hour led
dup 5 / swap 5 mod mcol + c@ \ calculate minute pos and color
rot 1 and 0<> if col! else 2drop then leds 12 neo ; \ blink
: clock begin GETTIME .clock rinp 1 and 0= if quit then again ;
clock \ Run the clock. Note: First, set the time using SETTIME
----[EOF]-------------------------------------------------------
Note: You can use these RTC drivers for this project.
This matrix clock consists of three 8x8 LED matrix modules and a DS1307 RTC board.
The first LED is at the bottom left, and the last LED in the string is at the top right edge:
----[001]-------------------------------------------------------
\ Display time on a 24 x 8 NeoPixel LED matrix (by D. Kuschel)
\ Uses a RTC chip clock driver that exports the word GETTIME
hex ( You can use my DS1307 or PCF8523 RTC driver )
43 constant charcol 46 constant dotcol
4B constant hseccol 2B constant mseccol
0B constant lseccol ( 4 x 7 pixel charset below: )
: n0 s" .#####.#.....##.....#.#####." ;
: n1 s" ....#.......#.#######......." ;
: n2 s" ##...#.#.#...##..#..##...##." ;
: n3 s" .#....##..#..##..##.#.##..##" ;
: n4 s" ...##.....#.#.#######...#..." ;
: n5 s" #..#####..#..##..#..#.##...#" ;
: n6 s" .#####.#..#..##..#..#.##...." ;
: n7 s" ......###....#..##..#....###" ;
: n8 s" .##.##.#..#..##..#..#.##.##." ;
: n9 s" ....##.#..#..##..#..#.#####." ;
----[002]-------------------------------------------------------
create cset ' n0 , ' n1 , ' n2 , ' n3 , ' n4 ,
' n5 , ' n6 , ' n7 , ' n8 , ' n9 ,
create tcol lseccol c, mseccol c, hseccol c,
create lcol mseccol c, lseccol c, 0 c,
create leds c0 allot \ frame buffer
: 2+ 1+ 1+ ; ( n -- n+2 )
: chptr ( nbr -- ptr ) \ convert numbers 0-9 to charset ptr
cells cset + @ execute drop ;
: prep-cset \ prepare charset, set color to "charcol"
a 0 do i chptr 1c 0 do
dup c@ [char] # = if charcol else 0 then over c! 1+
loop drop loop ;
prep-cset forget prep-cset
----[003]-------------------------------------------------------
: cout ( char column -- ) \ copy number-bitmap into framebuffer
swap chptr swap 3 lshift 1+ leds + ( csetptr pixmemptr )
4 0 do 2dup 7 move 8 + swap 7 + swap loop 2drop ;
: .sec ( sec -- ) \ update only the seconds-display (moving dot)
2+ 3 /mod dup 14 = if drop 0 then
dup 0= if 13 else dup 1- then
rot swap over
lcol + c@ swap 2+ 3 lshift leds + c!
tcol + c@ swap 2+ 3 lshift leds + c!
leds c0 neo ; \ output the frame buffer to the LED matrix
: .clock ( sec min hour -- ) \ print out time with sec/hour/min
leds c0 0 fill dotcol leds 5b + 2dup c! 2+ c!
a /mod dup 0<> if 1 cout else drop then 6 cout
a /mod d cout 13 cout .sec ;
----[004]-------------------------------------------------------
: run
\ initialise, print time and remember current second
gettime rot dup >r rot rot .clock
begin
\ get time, check if second has changed
gettime rot dup r> dup >r =
if 2drop drop else
\ only if second has changed, remember new second
r> drop dup >r
\ if second is 0, update hole display (this is slow)
dup 0= if rot rot .clock
\ otherwise update only the seconds
else .sec 2drop then
then
again ;
run \ start the clock program
----[EOF]-------------------------------------------------------
Note: You can use these RTC drivers for this project.
My4TH can now play Game Of Life as well.
All you need is the my4th-neo ROM, four 8x8 LED matrix modules, and a small assembler program:
The four LED matrix modules are connected as follows:
Software Installation:
Download the Game Of Life ZIP archive.
The archive contains the source code for the assembler program and the program’s binary file.
Upload the binary file to your My4TH board, for example to blocks 60 and 61:
$ my4th write /dev/ttyUSB0 binary 60 gameoflife.bin
On your My4TH, start the program with
60 bload
Now enjoy the evolution of these fascinating patterns!
|