LEDSerialExpander 0.2.2
Expander Board Python Driver
License: MIT
Requires Python: >=3.4

Python Raspberry Pi Serial Expander Board driver
======================

This is a driver written in python and optimized with cython for the [Pixelblaze Output Expander Board](https://github.com/simap/pixelblaze_output_expander) for use on Raspberry Pi (it could work for other systems with slight modifications, but this has not been tested). 

The raspberry pi hardware is not optimized for tasks such as driving neopixels through its GPIO. Drivers exist such as neopixel_write, but cannot drive larger arrays or pass 100fps for smaller ones, and a lot of precious CPU cycles are lost in driving them. Also, you can only run one strip, the Expander board allows for up to 64 in certain setups.  

This board makes use of the UART TX port on your device. Any raspberry pi with a serial interface can be used, as long as the port and baudrate that are passed are correct, and your UART is set up and working (Enable by going to sudo raspi-config -> interface options -> serial -> turn OFF serial terminal/turn ON serial interface -> reboot). After doing this, GPIO14 should be activiated for serial use. You will know that the expander board is recieving valid serial data if it begins blinking an orange LED. 

Currently only ws281x strips are supported by this python code (APA strips are also supported by this board) 

Installation
-------------------

```bash
python3 -m pip install LEDSerialExpander
```

Usage
-------------------
Import the Library:
```python
from LEDSerialExpander import LEDSerialExpander
```

Configure your strip with a dictionary, with the keys as the board pin numbers: 
```python
strips = {0: {'size':80, 'order': 'RGB' ,'type':1},
          1: {'size':30, 'order': 'RGBW' ,'type':1},
          4: {'size':72, 'order': 'RGB' ,'type':1}}

display = LEDSerialExpander(strips)
```
The type:1 indicated is for WS281x strips, which are the only ones that this driver currently supports.

Pass data to be written either by a bytearray() that contains data for all strips in channel-sequential order:

```python
data = bytearray([0 for _ in range(576)]) 
#your code to manipulate data

display.write(data)
```

or alternatively, pass a dict with individual channels as keys containing their own bytearrays: 

```python
data_dict = {0: data1,
             1: data2,
             4: data3}

display.write(data_dict) 
```

For a working example, check example.py

Other Parameters
-------------------
<ul>
<li>
<b> uart: </b>
default: "/dev/ttyS0"
This controls the uart tx port. Raspberry pi 3, 4, and zero W will by default use "/dev/ttyS0". Others might use "/dev/ttyAMA0". 
</li>

<li>
<b>baud:</b>
default: 2000000
The baudrate is the speed at which the serial connection operates. It has been discovered that for RPI ZERO W, a baudrate of 2304000 (a standard baudrate multiple) was needed for the connection to work, while on the PI 4, only 2000000 worked. Others remain untested.
</li>

<li>
<b>fps_show:</b>
default: False
If set to True, this will print the FPS being displayed every second to the console
</li>

<li>
<b>draw_wait:</b>
default: .0036 
Time in seconds to wait after draw command is sent before new data gets passed. .0036 is mentioned as safe by board creator. This however tends to cap the framerate. See below for more details.
</li>
</ul>

Notes on fast framerates:
-------------------
<ul>
<li>
This driver is capable of displaying pixels at a relatively high framerates (300+ FPS). This might however take some expiriementing. For larger setups (such as over 150 pixels), the data transfer rate will ultimately influence how many times per second you can pass pixel data. For smaller setups, it is possible to push your setup over 300 FPS. In order to achieve this, a parameter was included that can be passed to override the board creator's recommended wait time after a draw command is sent to the board. (draw_wait)  
</li>
<li>
I would advise distributing your pixels as much as possible between the 8 channels. Then pass a draw_wait smaller than .0036, potentially as small as 0. For two strips of 70 pixels, I was able to pass 0.0001 and achieve over 400 FPS without graphical errors. 
</li>
<li>Figuring out an optimal draw_wait time might take some experimenting. You will know that your value is too low if you visually see frames getting skipped.
</li>
<li> 
Pure python in itself is rather inefficient when doing lots of computations repetitively, CPU time is wasted due to the global interpreter lock, type checking, etc.Keep this in mind when trying to push higher framerates.</li>
<li>Consider wrapping the write() in a <code>multiprocessing</code> process, if your computations begin to overtake your CPU and influence the framerate. </li>
</ul>

Troubleshooting:
------------------- 
If you are not seeing the orange on the expander board (valid data recieved), things to check: 

<ul>
<li>Make sure that ground is continuous across your strip, the expander board, and your pi (or other device), and all other wiring in order.</li> 
<li>Your UART interface may not be turned on, or may be set to a different port. You can test using an application called minicom ( <code>sudo apt-get install minicom</code> ), connecting your UX and RX pins on your board, and opening two terminals, the TX controlled by your TX port (like /dev/ttyS0) with command: <code>minicom -b 9600 -o -D /dev/ttyS0</code> and RX connected to your RX port (usually /dev/serial0) with command: <code>minicom -b 9600 -o -D /dev/serial0</code> Typing anything into the TX terminal should echo back on the RX window.</li> 
<li>The default baudrate is not syncing. Set to a different rate.</li> 
</ul>