64x32 LED matrix display

Driving a 64×32 LED matrix display using our PTL-ino. Yes, this is 2,048 pixels and we have 2kB SRAM… what a perfect match! To make this work, we use – well – 1 byte per pixel. Format is RGB-332, i.e. 3 bits of red, 3 of green, and 2 of blue. And: no more SRAM, we do the rest in assembly!

Status du project
Date de début 02/2016
Status work in progress
Initiateur Magnus

Display and PTL-ino are interconnected as follows:

PTL-ino AVR Display pin Description
A0 PC0 R0 red (upper half display)
A1 PC1 R1 red (lower half display)
A2 PC2 G0 green (upper half display)
A3 PC3 G1 green (lower half display)
A4 PC4 B0 blue (upper half display)
A5 PC5 B1 blue (lower half display)
2 PD2 LATCH latches shift register content to column drive
3 PD3 /OE output enable (active low)
4 PD4 A row address bit 0
5 PD5 B row address bit 1
6 PD6 C row address bit 2
7 PD7 D row address bit 3
8 PB0 CLK shift register clock

We use a single “.ino” file for the code, which, however, is mostly one inline assembly function. This is done for the sake of easiness/laziness: it can be programmed without the need to think from the IDE. There are a few pitfalls, though:

  • Registers cannot be assumed to have power-on default values: not only does the boot loader change their contents, but also the C compiler.
  • Disable interrupts! We have no space for the stack (can it actually be placed into the register file?) and no need for them. Also, I prefer to count clock-cycles to get the timing right.
Addresses Bit Comment
7 6 5 4 3 2 1 0
0x100-0x4FF R1.0R0.0B1.1B0.1G1.1G0.1R1.1R0.1 lower 6 bits: bit plane 1
0x500-0x8FF G1.0G0.0B1.2B0.2G1.2G0.2R1.2R0.2 lower 6 bits: bit plane 2

These bytes can be directly output to the ports (pseudo code):

.rept 32
  ld   tmp,X+           ; 2 clk
  out  DATAPORT,tmp     ; 1 clk
  out  CLOCKPORT,clk1   ; 1 clk
  out  CLOCKPORT,clk0   ; 1 clk

5 clock cycles per shift.

Bit plane 0 is given by (note the blue!):


This has to be mangled from the content of two SRAM locations, e.g.: TODO

The image is generated row by row. For each row, all colours are shown at the same time and their brightness is given some kind of “binary PWM” (see Adafruit library for reference):

Brightness Output
0 0-00-0000
1 1-00-0000
2 0-11-0000
3 1-11-0000
4 0-00-1111
5 1-00-1111
6 0-11-1111
7 1-11-1111

The PTL-ino runs at 16 MHz, and the refresh rate should be 300 Hz. The sequence cycles through 16 rows (upper and lower half of display are steered simultaneously). This gives 16M/16/300=3333+1/3 cycles per row, which are used as follows:

Cycles Function
1900 show bit plane 2, load bit plane 0 (the one with bit mangling)
8 switch to next row
475 show bit plane 0, load bit plane 1
950 show bit plane 1, load bit plane 2
3333 (total)

At the same time, it is ensured that the UART input register is read and handled at least every 160 clock cycles.

A kind of RGB-332 is used. In typical 24-bit RGB colour pictures, each color channel intensity is given by an 8-bit value x, that is normalised as: x/255*100%. Analogously for 3-bit values, one has: x/7*100% and for 2-bits: x/3*100%. It is worth noticing that these discrete percentages are not compatible. Not all (actually, only very few) RGB-332 value have a perfect RGB-24 match. This is not too important here (errors are small), but also the 2-bit blue channel and the 3-bit red and green channels do not match well. This, however, is important for the “PWM” signal generation. The latter uses 3 bits, or 8 brightness levels, which are mapped as follows:

0 0 0 0
1 1 1
2 2 1 2
3 3 3
4 4 4
5 5 2 5
6 6 6
7 7 3 7

Btw: “normal” RGB-332 does not have any real grey…

  • Creating the RGB-332 color map:
    # from imagemagick manual:
    convert -size 16x16 xc: -channel R -fx '(i%8)/7' \
                            -channel G -fx '(j%8)/7' \
                            -channel B -fx '((i>>3&1)|(j>>2&2))/3' \
            -scale 600% colormap_332.png
    # but we use round(.../3*7)/7 for B (slightly worse but, hey, correct grays!)
    convert -size 16x16 xc: -channel R -fx '(i%8)/7' \
                            -channel G -fx '(j%8)/7' \
                            -channel B -fx 'round(((i>>3&1)|(j>>2&2))/3*7)/7' \
            -scale 600% colormap_332p.png

    colormap_332.png: , better colormap_332p.png:

  • Converting image to binary RGB-332 (this assumes that the image is already using the roughly correct colours):
    convert test.gif -define txt:compliance=SVG txt:- | tail -n +2 | tr -cs '0-9.\n'  ' ' |  awk '{printf("%02X\n",int($3*7/255+0.5)*32+int($4*7/255+0.5)*4+int($5*3/255+0.5))}' | xxd -r -p > test.bin
  • Dumping a trace with simulavr:
    # test 0.01s
    ${SIMULAVR} -f phaser.elf -t test.trace -F${F_CPU} -m10000000
    # record clock output
    cat -n test.trace | grep 'OUT 0x03, R0' > clk.txt
    # record UART checks (ensure that they are not spaced more than 160 clk)
    cat -n test.trace | grep 'LDS R16, 0xc' > uart.txt
    cat uart.txt | awk '{dt=$1-t;if (t>0 && dt>160) print "t =",t,": dt =",dt," > 160";t=$1}'
  • Test pattern:
  • projects/electronics/ptl-ino/led64x32.txt
  • Dernière modification: 2017/02/09 23:47
  • de magnustron