B-C-Mike / PoC-rPi-LED-matrix

Look mum I can have high speed DMA on rPi. // This is improvement (prof of concept) to hzeller's code. My method uses different hardware to reduce CPU load.
The Unlicense
10 stars 3 forks source link

PoC-rPi-LED-matrix

Look mum I can have high speed DMA on rPi. // This is improvement (prof of concept) to hzeller's code. My method uses different hardware to reduce CPU load.

What is the LED matrix?

What is the DPI interface found in rPi boards?

How to drive LED matrix?

Available solutions for driving the matrix?

Enabling the DPI interface.

enable DPI output interface

dtoverlay=dpi24 framebuffer_priority=2 enable_dpi_lcd=1

display_default_lcd=0

dpi_group=2 dpi_mode=87 max_framebuffers=2

dpi_output_format=0xc017 dpi_timings= 240 0 10 20 10 360 0 1 1 1 0 0 0 60 0 40000000 3

^ horizontal ^ vertical ^ pixel clock

Additional overlays and parameters are documented /boot/overlays/README

Where: 
- Horizontal resolution have to be greater than length of the string, times two. 64px wide panel need 130-140px at least. Too little might give You troubles with too high horizontal refresh rate. My panel gives up above 200hz. 
- Vertical resolution need to fit all lines we like to push in. 32px height panel requires 16 lines per bitplane (reduced brightness) and 16 misc lines on top. 
- Pixel clock - start with low value, then ramp up as high as You need. LED panel require 2 pixels for 1 clock pulse. Typical panels accepts up to 35Mhz so pixel clock can be as high as 70 millions (70000000). 

Hardware setup. 
-
- RGB LED matrix requires 3 separate inputs. 5V logic. 
  - Data input. 6 pins (2 sets of RGB) unique for each panel. 
  - Row control. 3, 4 or 5 lines, shared across all connected panels. Sometimes as simple as binary counting, sometimes have own shift register. 
  - Clock, Enable and Latch. 3 lines shared across all connected panels.
- DPI interface sends 5 signals. 3v3 logic. 
  - 24 bits as the image data. Easy to encode informations. 
  - Horizontal synch. 
  - Vertical synch. 
  - Pixel clock, always active. Basically unusable in this project. 
  - Data valid / data enable, basically a gating signal for pixel clock. 
- ![](img/schemat.png)
  - Voltage translators between rPi and matrix are mandatory. 
  - All data pins, clock and OE have to be attached to DPI data output (24 pins). 
  - Extra outputs on rPi DPI interface can be used (and abused) to some point but can't be reconfigured. Enable pin and horizontal synch can be used to count lines and/or latch data. Vertical synch can be used as synchronization and refresh rate indicator. Pixel clock is useless :( 
  - Pixel clock for the matrix have to stop as the latch pulse comes. This mean the clock signal have to be generated via DPI data output. Dedicated clock output can't be used. 
  - Output enable for the matrix also have to be generated, otherwise it's hard to achieve dimming. 
  - I decide to add binary counter to ease on code side. Horizontal sync triggers the latch pin and count up lines on the matrix. Lines of the matrix (counter) are synched with lines of framebuffer thanks to the reset input connected to vertical synch. 
  - All pins can be reconfigured across DPI data pins. Bare in mind GPIO 0, 1, 2, 3 have strict functions and can't be reconfigured. 

Theory of operation. 
-
- Software does NOT send any signal to the panel (via GPIO pins). 
- Software makes the data conversion from standard video buffer (width * height * RGB) to bitplanes, assembly them together, add clock and control signals, then send it all to the framebuffer. 
- Conversion is done once per frame. DPI interface will take care of refreshing LED matrix. 
- LED panel requires 2 DPI pixels per 1 LED. Both with the same data. Each with opposite state of the CLK line (software generated clock). 
- LED panel requires 2 DPI lines to drive 1 line of matrix: 
  - DPI line 1 sends data to line 1 of the panel. 
  - DPI line 2 drives the ENable pin for line 1 of the panel (brightness control). 
  - DPI line 2 sends data to line 2 of the panel. 
  - DPI line 3 sends data to line 2 of the panel. 
  - DPI lines 3 and 4 controls line 3 of the panel. 
  - ...
- 16 lines of framebuffer covers whole screen (1/16 drive). 16 lines are considered as single bitplane. 
- Next bitplane can be displayed as 16 lines with brightness reduced by half or as 32 lines (display the same content, double the brightness). 
- Framebuffer have to be big enough to fit all lines and all horizontal pixels. Otherwise software will crash. 
- It is possible to calculate framebuffer size and set it via command, but setting that in config.txt is way easier. 

Example 1. Can I have some output. `1-line.py`
-
This is the simplest test for the idea. Also a sandbox to play with. Software assembles few example lines. Your task is to push them into the framebuffer. If it works, play with it more. If it doesn't work and You changed any piece of generating code then You have to apply same changes to the next examples. 
![](img/1a_first_line.jpg)
^ Just a single line. I'm proud it's working. 

![](img/1b_dashes.jpg)
^ I can generate and display dots, dashes, anything binary (8 colors). 

![](img/1c_rainbow.jpg)
^ Simple rainbow using lookup table. 

![](img/1d_gradient.jpg)
```f.write(WW_line)
for n in range (1, 12):
    for line in range(0, n):
        f.write(WW_line) 
    for line in range(n, 16):
        f.write(bright_line) 

^ This is how I can get gradient. Just write more lines in the same place of the screen. Takes 1.5 second on rPi 2 (single core load)

Example 2. Displaying test pattern. 2-rainbow.py

4 bitplanes can create simple rainbow pattern. Each line have the same brightness settings. Bit 4 is repeated once, bit 5 twice, bit 6 - 4 times, bit 7 (MSB) - 8 times. There are also 13 empty lines to counteract image offset created by 4040 binary counter and the way hardware works.

Example 3. Mirror main screen. 3-mirror-fb0.py

Let's just grab a piece of image from main framebuffer (fb0), convert and throw into panel (fb1). This demo gives me about 0.7 FPS (1.3s) to convert 2048 pixels at 24b color depth (8b per color). With 12b color depth (4b per color, 4 bitplanes) speed is up to 1.4 FPS on rPi 2 (single core). Refresh rate of the screen is still around 400 hz for this single panel setup. Not kidding. DPI interface just spits out the bytes by itself.

Desktop (LCD monitor) Piece of desktop displayed on LED matrix
----------------------- ------------------------------

Example 4. Gain some speed with NumPy. 4-speed.py

Basically python is SLOW. Every loop cost time, every command cost time. What's the alternative? NumPy. Can do a lot with single python command, just faster. By rewriting code and eliminating loops i bumped the spped incrementally to 10, 25 and 40 FPS (this version is included here). Now i have usable speed to playback video (yess, screen capture from /dev/fb0) directly from 'tube. Good job rPi 2. BTW, please don't judge me by this code. I'm not proud of it. Just leave it as one of the steps.

Example 5. NumPy'fy everything. 5-numpify.py and 6-improvements.py

Rewriting time again. Added scaling opion. Removed as much as possible from main loop and pre-generate as much look-up tables as possible. 100 FPS speed for worst case scenario, up to 150FPS with no scaling and reduced output scale. Now it's time to say enough and write documentation. Both 5 and 6th revision works the same. 6 just looks a bit better and some options are easier to change.

Example 7. Make it work on standard hardware 7-standard-pcb.py

Calculating the speed.

Calculating brightness.

Summary.