Add LED strip support layer (#168)

Co-authored-by: sfalexrog <sfalexrog@gmail.com>
This commit is contained in:
Oleg Kalachev
2019-08-29 22:17:49 +03:00
committed by GitHub
parent 1773a1ccae
commit f9b1a82b5e
18 changed files with 877 additions and 175 deletions

View File

@@ -67,6 +67,7 @@
* [Types of power connectors](connectortypes.md)
* [Connecting 4 in 1 ESCs](4in1.md)
* [Soldering safety](tb.md)
* [LED strip (legacy)](leds_old.md)
* [Contribution Guidelines](contributing.md)
* Clever-based projects
* [Copter spheric guard](shield.md)

View File

@@ -1,124 +1,154 @@
# Working with a LED strip on Raspberry 3
# Working with a LED strip
## Connecting and determining the type of the strip
> **Note** The following applies to image version 0.18 and up. See [previous version of the article](leds_old.md) for older images.
> **Note** The following is applicable to image versions 0.14 and up. For versions 0.13 and older see [an older revision of this article](https://github.com/CopterExpress/clever/blob/v0.16/docs/en/leds.md)
Clever drone kits contain addressable LED strips based on *ws281x* drivers. Each LED may be set to any one of 16 million possible colors (each color is encoded by a 24-bit number). This allows making the Clever flight more spectacular, as well as show flight modes, display stages of current user program, and notify the pilot of other events.
Connect the +5v and GND leads of your LED to a power source and the DIN (data in) lead to GPIO18 or GPIO21.
<img src="../assets/clever-led.png" class="center" width=600>
<img src="../assets/led_connection.png" height="400px" alt="leds">
Our [Raspberry Pi image](image.md) contains preinstalled modules for interfacing with the LED strip. They allow the user to:
* manage LED strip effects and animations (high-level control);
* control individual LED colors (low-level control);
* configure the strip to display flight events.
> **Caution** LED strip can consume a lot of power! Powering it from a Raspyerry Pi may overload the computer's power circuitry. Consider using a separate BEC as a power source.
<!-- -->
## High-level control
> **Note** If you are using [GPIO](gpio.md) along with the LED strip, connect the strip to GPIO21. Otherwise you may experience unintended strip behavior.
1. Connect the +5v and GND leads of your LED to a power source and the DIN (data in) lead to GPIO21. Consult the [assembly instructions](assemble_4.md#Connecting-the-LED-strip-to-Raspberry-Pi) for details.
2. Enable LED strip support in `~/catkin_ws/src/clever/clever/launch/clever.launch`:
## ROS and Python compatibility
```xml
<arg name="led" default="true"/>
```
LED strip library requires you to run your Python scripts with `sudo`. In order to make it work with ROS nodes you have to add the following lines to your `/etc/sudoers` file on the Raspberry Pi:
3. Configure the *ws281x* parameters in `~/catkin_ws/src/clever/clever/launch/led.launch`. Change the number of addressable LEDs and the GPIO pin used for control to match your configuration:
```
Defaults env_keep += "PYTHONPATH"
Defaults env_keep += "PATH"
Defaults env_keep += "ROS_ROOT"
Defaults env_keep += "ROS_MASTER_URI"
Defaults env_keep += "ROS_PACKAGE_PATH"
Defaults env_keep += "ROS_LOCATIONS"
Defaults env_keep += "ROS_HOME"
Defaults env_keep += "ROS_LOG_DIR"
```
```xml
<param name="led_count" value="30"/> <!-- Number of LEDs in the strip -->
<param name="gpio_pin" value="21"/> <!-- GPIO data pin -->
```
## Sample program for the LED strip
High-level interface allows changing current effect (or animation) on the strip. It is exposed as the `/led/set_effect` service. It has the following arguments:
The following code lights up the first 10 LEDs on the LED strip. You may use it to check whether your LED strip works correctly:
* `effect` is the name of requested effect.
* `r`, `g`, `b` are [RGB](https://en.wikipedia.org/wiki/RGB) components of effect color. Each component is an integer in a 0 to 255 range.
Currently available effects are:
* `fill` (or an empty string) fills the whole strip with the requested color;
* `blink` turns the strip on and off, setting it to the requested color;
* `blink_fast` is the same, but faster;
* `fade` fades smoothly to the requested color;
* `wipe` fills the strip with the requested color one LED at a time;
* `flash` blinks twice and returns to the previous effect;
* `rainbow` creates a rainbow-like shifting effect;
* `rainbow_fill` cycles the strip through rainbow colors, filling the whole strip with the same color.
Python example:
```python
import time
import rospy
from clever.srv import SetLEDEffect
from rpi_ws281x import Adafruit_NeoPixel
from rpi_ws281x import Color
# ...
set_effect = rospy.ServiceProxy('led/set_effect', SetLEDEffect) # define proxy to ROS-service
LED_COUNT = 10 # Number of LED pixels
LED_PIN = 21 # GPIO pin for the strip
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 10 # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0 # Set to '1' for GPIOs 13, 19, 41, 45 or 53
# ..
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT)
set_effect(r=255, g=0, b=0) # fill strip with red color
rospy.sleep(2)
strip.begin()
set_effect(r=0, g=100, b=0) # fill strip with green color
rospy.sleep(2)
set_effect(effect='fade', r=0, g=0, b=255) # fade to blue color
rospy.sleep(2)
def colorWipe(strip, color, wait_ms=50):
"""Wipe color across strip a pixel at a time."""
for i in range(strip.numPixels()):
strip.setPixelColor(i, color)
strip.show()
time.sleep(wait_ms/1000.0)
set_effect(effect='flash', r=255, g=0, b=0) # flash twice with red color
rospy.sleep(5)
set_effect(effect='blink', r=255, g=255, b=255) # blink with white color
rospy.sleep(5)
print('Color wipe animations.')
colorWipe(strip, Color(255, 0, 0), wait_ms=100) # Red wipe
colorWipe(strip, Color(0, 255, 0), wait_ms=100) # Blue wipe
colorWipe(strip, Color(0, 0, 255), wait_ms=100) # Green wipe
colorWipe(strip, Color(0, 0, 0), wait_ms=100) # Turn LEDs off
set_effect(effect='rainbow') # show rainbow
```
> **Note** You may also want to use additional test scripts from the [LED library repository](https://github.com/rpi-ws281x/rpi-ws281x-python/blob/master/examples).
You can also set colors from your Bash shell:
Save the script and run it as root:
```
sudo python led_test.py
```bash
rosservice call /led/set_effect "{effect: 'fade', r: 0, g: 0, b: 255}"
```
## Basic LED library functions
```bash
rosservice call /led/set_effect "{effect: 'rainbow'}"
```
You'll need to import `Adafruit_NeoPixel` class and `Color` function into your program to interact with the LED strip. Additionally, you'll want the `time` module to add delays to your animations:
## Configuring event visualizations
It is possible to display current flight controller status and notify the user about some events with the LED strip. This is configured in the `~/catkin_ws/src/clever/clever/launch/led.launch` file in the *events effects table* section. Here is a sample configuration:
```xml
startup: { r: 255, g: 255, b: 255 }
connected: { effect: rainbow }
disconnected: { effect: blink, r: 255, g: 50, b: 50 }
<!-- ... -->
```
The left part is one of the possible events that the strip reacts to. The right part contains the effect description that you want to execute for this event. Here is the list of supported events:
* `startup` Clever system startup;
* `connected` successful connection to the flight controller;
* `disconnected` connection to the flight controller lost;
* `armed` flight controller transitioned to armed state;
* `disarmed` flight controller transitioned to disarmed state;
* `stabilized`, `acro`, `rattitude`, `altctl`, `posctl`, `offboard`, `mission`, `rtl`, `land` transition to said flight mode;
* `error` an error occured in one of ROS nodes or in the flight controller (*ERROR* message in `/rosout`);
* `low_battery`  low battery (threshold is set in the `threshold` parameter).
> **Note** You need to [calibrate the power sensor](power.md#calibrating-the-power-sensor) for the `low_battery` event to work properly.
In order to disable LED strip notifications set `led_notify` argument in `~/catkin_ws/src/clever/clever/launch/led.launch` to `false`:
```xml
<arg name="led_notify" default="false"/>
```
## Low-level control
You can use the `/led/set_leds` ROS service to control individual LEDs. It accepts an array of LED indices and desired colors.
Python example:
```python
from rpi_ws281x import Adafruit_NeoPixel
from rpi_ws281x import Color
import time
import rospy
from led_msgs.srv import SetLEDs
from led_msgs.msg import LEDStateArray, LEDState
# ...
set_leds = rospy.ServiceProxy('led/set_leds', SetLEDs) # define proxy to ROS service
# ...
# switch LEDs number 0, 1 and 2 to red, green and blue color:
set_leds([LEDState(0, 255, 0, 0), LEDState(1, 0, 255, 0), LEDState(2, 0, 0, 255)])
```
Instantiate the `Adafruit_NeoPixel` object and call its `begin()` method to start working with the strip:
You can also use this service from the your Bash shell:
```
# Strip object instantiation (parameter description is provided in a code snippet above)
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT)
strip.begin()
```bash
rosservice call /led/set_leds "leds:
- index: 0
r: 50
g: 100
b: 200"
```
Main strip control methods:
Current LED strip state is published in the `/led/state` ROS topic. You can view the contents of this topic from your Bash shell:
+ `numPixels()` returns the number of pixels in the strip. Convenient for whole strip operations.
+ `setPixelColor(pos, color)` sets the pixel color at `pos` to `color`. Color should be a 24-bit value, where the first 8 bits are for the red channel, the next 8 bits are for the green channel, and the last 8 bits are for the blue channel. You may use the `Color(red, green, blue)` convenience function to convert colors to this format. Each color value should be an integer in the \[0..255\] range, where 0 means zero brightness and 255 means full brightness.
+ `SetPixelColorRGB(pos, red, green, blue)` sets the pixel at `pos` to the color value with components `red`, `green` and `blue`. Each component value shoule be an integer in the \[0..255\] range, where 0 means zero brighness and 255 means full brightness.
+ `show()` updates the strip state. Any changes to the strip state are only pushed to the actual strip after calling this method.
## Does it have to be this way?
The LED strip type used in the Clever kits use the following protocol: a data source (a Raspberry Pi, for example) sends a bit stream, 24 bits per LED. Each LED reads the first 24 bits from the stream and sets its color accordingly while passing the rest of the stream to the next LED. Zeroes and ones are encoded by different pulse lengths.
This LED strip is supported by the [rpi_ws281x](https://github.com/jgarff/rpi_ws281x) library. The library uses the DMA (direct memory access) module of the Raspberry CPU and can utilize one of the three periphery channels: PWM, PCM or SPI. This allows the library to drive the strip consistently in a multitasking environment.
Each channel has its caveats. Using the PWM prevents you from using the builtin Raspberry audio subsystem; using the PCM channel will prevent you from adding I2S (digital audio) devices, although the analog audio will work. SPI requires you to change your GPU and buffer size and prevents you from using any SPI devices.
Some DMA channels are reserved for system use. DMA channel 5 is used for SD card reads and writes, and setting LED_DMA to 5 will corrupt your filesystem. DMA channel 10 is considered to be safe.
You have the following options for the LED strip:
1. If you don't need onboard audio, you may use the PWM channel and connect the LED strip to one of the following GPIO pins: 12, 18, 40 or 52 for PWM0 and 13, 19, 41, 45 or 53 for PWM1.
2. If you don't care about SPI devices, you may use the SPI channel for the LED with GPIO pins 10 or 38. You'll have to perform the following adjustments:
+ increase the SPI device buffer by adding `spidev.bufsiz=32768` option to `/boot/cmdline.txt`;
+ set the GPU frequency to 250 MHz by adding `core_freq=250` to `/boot/cmdline.txt`;
+ reboot your Raspberry Pi: `sudo reboot`.
3. If you care about audio and SPI devices, you may want to use the PCM channel (GPIO 21 or 31). You don't have to reconfigure your Raspberry.
The default option is 3, because it allows the builtin audio system to work and does not require any modifications to the boot sequence.
```bash
rostopic echo /led/state
```

124
docs/en/leds_old.md Normal file
View File

@@ -0,0 +1,124 @@
# Working with a LED strip on Raspberry 3
## Connecting and determining the type of the strip
> **Note** The following is applicable to image versions 0.14 and up. For versions 0.13 and older see [an older revision of this article](https://github.com/CopterExpress/clever/blob/v0.16/docs/en/leds.md)
Connect the +5v and GND leads of your LED to a power source and the DIN (data in) lead to GPIO18 or GPIO21.
<img src="../assets/led_connection.png" height="400px" alt="leds">
> **Caution** LED strip can consume a lot of power! Powering it from a Raspyerry Pi may overload the computer's power circuitry. Consider using a separate BEC as a power source.
<!-- -->
> **Note** If you are using [GPIO](gpio.md) along with the LED strip, connect the strip to GPIO21. Otherwise you may experience unintended strip behavior.
## ROS and Python compatibility
LED strip library requires you to run your Python scripts with `sudo`. In order to make it work with ROS nodes you have to add the following lines to your `/etc/sudoers` file on the Raspberry Pi:
```
Defaults env_keep += "PYTHONPATH"
Defaults env_keep += "PATH"
Defaults env_keep += "ROS_ROOT"
Defaults env_keep += "ROS_MASTER_URI"
Defaults env_keep += "ROS_PACKAGE_PATH"
Defaults env_keep += "ROS_LOCATIONS"
Defaults env_keep += "ROS_HOME"
Defaults env_keep += "ROS_LOG_DIR"
```
## Sample program for the LED strip
The following code lights up the first 10 LEDs on the LED strip. You may use it to check whether your LED strip works correctly:
```python
import time
from rpi_ws281x import Adafruit_NeoPixel
from rpi_ws281x import Color
LED_COUNT = 10 # Number of LED pixels
LED_PIN = 21 # GPIO pin for the strip
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 10 # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0 # Set to '1' for GPIOs 13, 19, 41, 45 or 53
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT)
strip.begin()
def colorWipe(strip, color, wait_ms=50):
"""Wipe color across strip a pixel at a time."""
for i in range(strip.numPixels()):
strip.setPixelColor(i, color)
strip.show()
time.sleep(wait_ms/1000.0)
print('Color wipe animations.')
colorWipe(strip, Color(255, 0, 0), wait_ms=100) # Red wipe
colorWipe(strip, Color(0, 255, 0), wait_ms=100) # Blue wipe
colorWipe(strip, Color(0, 0, 255), wait_ms=100) # Green wipe
colorWipe(strip, Color(0, 0, 0), wait_ms=100) # Turn LEDs off
```
> **Note** You may also want to use additional test scripts from the [LED library repository](https://github.com/rpi-ws281x/rpi-ws281x-python/blob/master/examples).
Save the script and run it as root:
```
sudo python led_test.py
```
## Basic LED library functions
You'll need to import `Adafruit_NeoPixel` class and `Color` function into your program to interact with the LED strip. Additionally, you'll want the `time` module to add delays to your animations:
```python
from rpi_ws281x import Adafruit_NeoPixel
from rpi_ws281x import Color
import time
```
Instantiate the `Adafruit_NeoPixel` object and call its `begin()` method to start working with the strip:
```
# Strip object instantiation (parameter description is provided in a code snippet above)
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT)
strip.begin()
```
Main strip control methods:
+ `numPixels()` returns the number of pixels in the strip. Convenient for whole strip operations.
+ `setPixelColor(pos, color)` sets the pixel color at `pos` to `color`. Color should be a 24-bit value, where the first 8 bits are for the red channel, the next 8 bits are for the green channel, and the last 8 bits are for the blue channel. You may use the `Color(red, green, blue)` convenience function to convert colors to this format. Each color value should be an integer in the \[0..255\] range, where 0 means zero brightness and 255 means full brightness.
+ `SetPixelColorRGB(pos, red, green, blue)` sets the pixel at `pos` to the color value with components `red`, `green` and `blue`. Each component value shoule be an integer in the \[0..255\] range, where 0 means zero brighness and 255 means full brightness.
+ `show()` updates the strip state. Any changes to the strip state are only pushed to the actual strip after calling this method.
## Does it have to be this way?
The LED strip type used in the Clever kits use the following protocol: a data source (a Raspberry Pi, for example) sends a bit stream, 24 bits per LED. Each LED reads the first 24 bits from the stream and sets its color accordingly while passing the rest of the stream to the next LED. Zeroes and ones are encoded by different pulse lengths.
This LED strip is supported by the [rpi_ws281x](https://github.com/jgarff/rpi_ws281x) library. The library uses the DMA (direct memory access) module of the Raspberry CPU and can utilize one of the three periphery channels: PWM, PCM or SPI. This allows the library to drive the strip consistently in a multitasking environment.
Each channel has its caveats. Using the PWM prevents you from using the builtin Raspberry audio subsystem; using the PCM channel will prevent you from adding I2S (digital audio) devices, although the analog audio will work. SPI requires you to change your GPU and buffer size and prevents you from using any SPI devices.
Some DMA channels are reserved for system use. DMA channel 5 is used for SD card reads and writes, and setting LED_DMA to 5 will corrupt your filesystem. DMA channel 10 is considered to be safe.
You have the following options for the LED strip:
1. If you don't need onboard audio, you may use the PWM channel and connect the LED strip to one of the following GPIO pins: 12, 18, 40 or 52 for PWM0 and 13, 19, 41, 45 or 53 for PWM1.
2. If you don't care about SPI devices, you may use the SPI channel for the LED with GPIO pins 10 or 38. You'll have to perform the following adjustments:
+ increase the SPI device buffer by adding `spidev.bufsiz=32768` option to `/boot/cmdline.txt`;
+ set the GPU frequency to 250 MHz by adding `core_freq=250` to `/boot/cmdline.txt`;
+ reboot your Raspberry Pi: `sudo reboot`.
3. If you care about audio and SPI devices, you may want to use the PCM channel (GPIO 21 or 31). You don't have to reconfigure your Raspberry.
The default option is 3, because it allows the builtin audio system to work and does not require any modifications to the boot sequence.