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

@@ -529,6 +529,12 @@ libogg:
vl53l1x:
debian:
stretch: [ros-kinetic-vl53l1x]
ws281x:
debian:
stretch: [ros-kinetic-ws281x]
led_msgs:
debian:
stretch: [ros-kinetic-led-msgs]
interactive_markers:
debian:
stretch: [ros-kinetic-interactive-markers]

View File

@@ -174,6 +174,7 @@ apt-get install -y --no-install-recommends \
ros-kinetic-rosserial \
ros-kinetic-usb-cam \
ros-kinetic-vl53l1x \
ros-kinetic-ws281x \
ros-kinetic-opencv3=3.3.19-0stretch \
ros-kinetic-rosshow

View File

@@ -80,6 +80,7 @@ add_service_files(
SetVelocity.srv
SetAttitude.srv
SetRates.srv
SetLEDEffect.srv
)
## Generate actions in the 'action' folder
@@ -164,6 +165,8 @@ add_executable(camera_markers src/camera_markers.cpp)
add_executable(vpe_publisher src/vpe_publisher.cpp)
add_executable(led src/led.cpp)
target_link_libraries(simple_offboard
${catkin_LIBRARIES}
${GeographicLib_LIBRARIES}
@@ -175,8 +178,12 @@ target_link_libraries(camera_markers ${catkin_LIBRARIES})
target_link_libraries(vpe_publisher ${catkin_LIBRARIES})
target_link_libraries(led ${catkin_LIBRARIES})
add_dependencies(simple_offboard clever_generate_messages_cpp)
add_dependencies(led clever_generate_messages_cpp)
## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use

View File

@@ -7,8 +7,9 @@
<arg name="main_camera" default="true"/>
<arg name="optical_flow" default="false"/>
<arg name="aruco" default="false"/>
<arg name="rc" default="true"/>
<arg name="rangefinder_vl53l1x" default="false"/>
<arg name="led" default="false"/>
<arg name="rc" default="true"/>
<!-- mavros -->
<include file="$(find clever)/launch/mavros.launch">
@@ -60,6 +61,9 @@
<param name="frame_id" value="rangefinder"/>
</node>
<!-- led strip -->
<include file="$(find clever)/launch/led.launch" if="$(arg led)"/>
<!-- rc backend -->
<node name="rc" pkg="clever" type="rc" output="screen" if="$(arg rc)"/>
</launch>

38
clever/launch/led.launch Normal file
View File

@@ -0,0 +1,38 @@
<launch>
<arg name="ws281x" default="true"/>
<arg name="led_effect" default="true"/>
<arg name="led_notify" default="true"/>
<!-- For additional help go to https://clever.copterexpress.com/led.html -->
<!-- ws281x led strip driver -->
<node pkg="ws281x" name="led" type="ws281x_node" clear_params="true" output="screen" if="$(arg ws281x)">
<param name="led_count" value="58"/>
<param name="gpio_pin" value="21"/>
<param name="brightness" value="255"/>
<param name="strip_type" value="WS2811_STRIP_GRB"/>
<param name="target_frequency" value="800000"/>
<param name="dma" value="10"/>
<param name="invert" value="false"/>
</node>
<!-- high level led effects control, events notification with leds -->
<node pkg="clever" name="led_effect" type="led" ns="led" clear_params="true" output="screen" if="$(arg led_effect)">
<param name="blink_rate" value="2"/>
<param name="fade_period" value="0.5"/>
<param name="rainbow_period" value="5"/>
<!-- events effects table -->
<rosparam param="notify" if="$(arg led_notify)">
startup: { r: 255, g: 255, b: 255 }
connected: { effect: rainbow }
disconnected: { effect: blink, r: 255, g: 50, b: 50 }
acro: { r: 245, g: 155, b: 0 }
stabilized: { r: 30, g: 180, b: 50 }
altctl: { r: 255, g: 255, b: 40 }
posctl: { r: 50, g: 100, b: 220 }
offboard: { r: 220, g: 20, b: 250 }
low_battery: { threshold: 3.7, effect: blink_fast, r: 255, g: 0, b: 0 }
error: { effect: flash, r: 255, g: 0, b: 0 }
</rosparam>
</node>
</launch>

View File

@@ -26,6 +26,7 @@
<depend>geometry_msgs</depend>
<depend>sensor_msgs</depend>
<depend>visualization_msgs</depend>
<depend>led_msgs</depend>
<depend>geographiclib</depend>
<depend>nodelet</depend>
<depend>mavros</depend>

321
clever/src/led.cpp Normal file
View File

@@ -0,0 +1,321 @@
/*
* High level control for the LED strip
* Indicate flight events with the LED strip
* Copyright (C) 2019 Copter Express Technologies
*
* Author: Oleg Kalachev <okalachev@gmail.com>
*
* Distributed under MIT License (available at https://opensource.org/licenses/MIT).
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*/
#include <ros/ros.h>
#include <string>
#include <boost/algorithm/string.hpp>
#include <clever/SetLEDEffect.h>
#include <led_msgs/SetLEDs.h>
#include <led_msgs/LEDState.h>
#include <led_msgs/LEDStateArray.h>
#include <sensor_msgs/BatteryState.h>
#include <mavros_msgs/State.h>
#include <rosgraph_msgs/Log.h>
clever::SetLEDEffect::Request current_effect;
int led_count;
ros::Timer timer;
ros::Time start_time;
double blink_rate, blink_fast_rate, flash_delay, fade_period, wipe_period, rainbow_period;
double low_battery_threshold;
bool blink_state;
led_msgs::SetLEDs set_leds;
led_msgs::LEDStateArray state, start_state;
ros::ServiceClient set_leds_srv;
mavros_msgs::State mavros_state;
int counter;
void callSetLeds()
{
bool res = set_leds_srv.call(set_leds);
if (!res) {
ROS_WARN_THROTTLE(5, "Error calling set_leds service");
} else if (!set_leds.response.success) {
ROS_WARN_THROTTLE(5, "Calling set_leds failed: %s", set_leds.response.message.c_str());
}
}
void rainbow(uint8_t n, uint8_t& r, uint8_t& g, uint8_t& b)
{
if (n < 255 / 3) {
r = n * 3;
g = 255 - n * 3;
b = 0;
} else if (n < 255 / 3 * 2) {
n -= 255 / 3;
r = 255 - n * 3;
g = 0;
b = n * 3;
} else {
n -= 255 / 3 * 2;
r = 0;
g = n * 3;
b = 255 - n * 3;
}
}
void fill(uint8_t r, uint8_t g, uint8_t b)
{
set_leds.request.leds.resize(led_count);
for (int i = 0; i < led_count; i++) {
set_leds.request.leds[i].index = i;
set_leds.request.leds[i].r = r;
set_leds.request.leds[i].g = g;
set_leds.request.leds[i].b = b;
}
callSetLeds();
}
void proceed(const ros::TimerEvent& event)
{
counter++;
uint8_t r, g, b;
set_leds.request.leds.clear();
set_leds.request.leds.resize(led_count);
if (current_effect.effect == "blink" || current_effect.effect == "blink_fast") {
blink_state = !blink_state;
// toggle all leds
if (blink_state) {
fill(current_effect.r, current_effect.g, current_effect.b);
} else {
fill(0, 0, 0);
}
} else if (current_effect.effect == "fade") {
// fade all leds from starting state
double passed = std::min((event.current_real - start_time).toSec() / fade_period, 1.0);
double one_minus_passed = 1 - passed;
for (int i = 0; i < led_count; i++) {
set_leds.request.leds[i].index = i;
set_leds.request.leds[i].r = one_minus_passed * start_state.leds[i].r + passed * current_effect.r;
set_leds.request.leds[i].g = one_minus_passed * start_state.leds[i].g + passed * current_effect.g;
set_leds.request.leds[i].b = one_minus_passed * start_state.leds[i].b + passed * current_effect.b;
}
callSetLeds();
if (passed >= 1.0) {
// fade finished
timer.stop();
}
} else if (current_effect.effect == "wipe") {
set_leds.request.leds.resize(1);
set_leds.request.leds[0].index = counter - 1;
set_leds.request.leds[0].r = current_effect.r;
set_leds.request.leds[0].g = current_effect.g;
set_leds.request.leds[0].b = current_effect.b;
callSetLeds();
if (counter == led_count) {
// wipe finished
timer.stop();
}
} else if (current_effect.effect == "rainbow_fill") {
rainbow(counter % 255, r, g, b);
for (int i = 0; i < led_count; i++) {
set_leds.request.leds[i].index = i;
set_leds.request.leds[i].r = r;
set_leds.request.leds[i].g = g;
set_leds.request.leds[i].b = b;
}
callSetLeds();
} else if (current_effect.effect == "rainbow") {
for (int i = 0; i < led_count; i++) {
int pos = (int)round(counter + (255.0 * i / led_count)) % 255;
rainbow(pos % 255, r, g, b);
set_leds.request.leds[i].index = i;
set_leds.request.leds[i].r = r;
set_leds.request.leds[i].g = g;
set_leds.request.leds[i].b = b;
}
callSetLeds();
}
}
bool setEffect(clever::SetLEDEffect::Request& req, clever::SetLEDEffect::Response& res)
{
res.success = true;
if (req.effect != "flash" && current_effect.effect == req.effect &&
current_effect.r == req.r && current_effect.g == req.g && current_effect.b == req.b) {
res.message = "Effect already set, skip";
return true;
}
if (req.effect == "") {
req.effect = "fill";
}
if (req.effect == "fill") {
fill(req.r, req.g, req.b);
} else if (req.effect == "blink") {
timer.setPeriod(ros::Duration(1 / blink_rate), true);
timer.start();
} else if (req.effect == "blink_fast") {
timer.setPeriod(ros::Duration(1 / blink_fast_rate), true);
timer.start();
} else if (req.effect == "fade") {
timer.setPeriod(ros::Duration(0.05), true);
timer.start();
} else if (req.effect == "wipe") {
timer.setPeriod(ros::Duration(wipe_period / led_count), true);
timer.start();
} else if (req.effect == "flash") {
ros::Duration delay(flash_delay);
fill(0, 0, 0);
delay.sleep();
fill(req.r, req.g, req.b);
delay.sleep();
fill(0, 0, 0);
delay.sleep();
fill(req.r, req.g, req.b);
delay.sleep();
fill(0, 0, 0);
delay.sleep();
if (current_effect.effect == "fill"||
current_effect.effect == "fade" ||
current_effect.effect == "wipe") {
// restore previous filling
for (int i = 0; i < led_count; i++) {
fill(current_effect.r, current_effect.g, current_effect.b);
}
callSetLeds();
}
return true; // this effect happens only once
} else if (req.effect == "rainbow_fill") {
timer.setPeriod(ros::Duration(rainbow_period / 255), true);
timer.start();
} else if (req.effect == "rainbow") {
timer.setPeriod(ros::Duration(rainbow_period / 255), true);
timer.start();
} else {
res.message = "Unknown effect: " + req.effect + ". Available effects are fill, fade, wipe, blink, blink_fast, flash, rainbow, rainbow_fill.";
ROS_ERROR("%s", res.message.c_str());
res.success = false;
return true;
}
// set current effect
current_effect = req;
counter = 0;
start_state = state;
start_time = ros::Time::now();
return true;
}
void handleState(const led_msgs::LEDStateArray& msg)
{
state = msg;
led_count = state.leds.size();
}
bool notify(const std::string& event)
{
if (ros::param::has("~notify/" + event + "/effect") ||
ros::param::has("~notify/" + event + "/r") ||
ros::param::has("~notify/" + event + "/g") ||
ros::param::has("~notify/" + event + "/b")) {
ROS_INFO_THROTTLE(5, "led: notify %s", event.c_str());
clever::SetLEDEffect effect;
effect.request.effect = ros::param::param("~notify/" + event + "/effect", std::string(""));
effect.request.r = ros::param::param("~notify/" + event + "/r", 0);
effect.request.g = ros::param::param("~notify/" + event + "/g", 0);
effect.request.b = ros::param::param("~notify/" + event + "/b", 0);
setEffect(effect.request, effect.response);
}
}
void handleMavrosState(const mavros_msgs::State& msg)
{
if (msg.connected && !mavros_state.connected) {
notify("connected");
} else if (!msg.connected && mavros_state.connected) {
notify("disconnected");
} else if (msg.armed && !mavros_state.armed) {
notify("armed");
} else if (!msg.armed && mavros_state.armed) {
notify("disarmed");
} else if (msg.mode != mavros_state.mode) {
// mode changed
std::string mode = boost::algorithm::to_lower_copy(msg.mode);
if (mode.find(".") != std::string::npos) {
// remove the part before "."
mode = mode.substr(mode.find(".") + 1);
}
notify(mode);
}
mavros_state = msg;
}
void handleLog(const rosgraph_msgs::Log& log)
{
if (log.level >= rosgraph_msgs::Log::ERROR) {
notify("error");
}
}
void handleBattery(const sensor_msgs::BatteryState& msg)
{
for (auto const& voltage : msg.cell_voltage) {
if (voltage < low_battery_threshold) {
// notify low battery every time
notify("low_battery");
}
}
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "led");
ros::NodeHandle nh, nh_priv("~");
nh_priv.param("blink_rate", blink_rate, 2.0);
nh_priv.param("blink_fast_rate", blink_fast_rate, blink_rate * 2);
nh_priv.param("fade_period", fade_period, 0.5);
nh_priv.param("wipe_period", wipe_period, 0.5);
nh_priv.param("flash_delay", flash_delay, 0.1);
nh_priv.param("rainbow_period", rainbow_period, 5.0);
nh_priv.param("notify/low_battery/threshold", low_battery_threshold, 3.7);
ros::service::waitForService("set_leds"); // cannot work without set_leds service
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>("set_leds", true);
// wait for leds count info
handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>("state", nh));
auto state_sub = nh.subscribe("state", 1, &handleState);
auto set_effect = nh.advertiseService("set_effect", &setEffect);
auto mavros_state_sub = nh.subscribe("/mavros/state", 1, &handleMavrosState);
auto battery_sub = nh.subscribe("/mavros/battery", 1, &handleBattery);
auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog);
timer = nh.createTimer(ros::Duration(0), &proceed, false, false);
ROS_INFO("led: ready");
notify("startup");
ros::spin();
}

View File

@@ -0,0 +1,7 @@
string effect
uint8 r
uint8 g
uint8 b
---
bool success
string message

View File

@@ -32,6 +32,10 @@
<node name="rc" pkg="clever" type="rc" required="true" output="screen"/>
<node pkg="clever" name="led_effect" type="led" ns="led" clear_params="true" output="screen" required="true">
<rosparam param="notify">startup: { r: 255, g: 255, b: 255 }</rosparam>
</node>
<param name="test_module" value="$(find clever)/test/basic.py"/>
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch>

BIN
docs/assets/clever-led.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

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.

View File

@@ -71,6 +71,7 @@
* [Модуль ESP8266](esp8266_bridge.md)
* [Сборка и модификация образа Клевера](image_building.md)
* [Подключение регулятора 4 в 1](4in1.md)
* [Светодиодная лента (legacy)](leds_old.md)
* [Вклад в Клевер](contributing.md)
* Мероприятия
* [Олимпиада НТИ 2019](nti2019.md)

View File

@@ -250,7 +250,7 @@
## Подключение светодиодной ленты к Raspberry Pi
1. Питание для ленты берется от второго BEC. Подключите контакты *«-»* и *«+»* к *Ground* и *5v* на ленте соответственно.
2. Контакт *D* можно подключить к любому свободному пину "GPIO" на Raspberry.
2. Подключите контакт *D* к GPIO-пину на Raspberry. Рекомендуется использовать пин GPIO21.
<img src="../assets/4/31_1.png" width=300 class="zoom center border">

View File

@@ -1,125 +1,154 @@
# Работа со светодиодной лентой на Raspberry 3
# Работа со светодиодной лентой
## Подключение и определение типа ленты
> **Note** Документация для версии образа, начиная с 0.18. Для более ранних версий см. [предыдущую версию статьи](leds_old.md).
> **Note** Документация для версии образа, начиная с 0.14. Для более ранних версий см. [документацию для версий 0.13](https://github.com/CopterExpress/clever/blob/v0.13/docs/leds.md)
Адресуемая RGB-светодиодная лента типа *ws281x*, которая входит в наборы "Клевер", позволяет выставлять произвольные 24-битные цвета на каждый из отдельных светодиодов. Это позволяет сделать полет Клевера более ярким, а также визуально получать информацию о полетных режимах, этапе выполнения пользовательской программы и других событиях.
Подключите светодиодную ленту к питанию +5v - 5v, земле GND - GND и сигнальному порту DIN - GPIO30, GPIO21, GPIO18.
<img src="../assets/clever-led.png" class="center" width=600>
<img src="../assets/led_connection.png" height="400px" alt="leds">
На образе [для RPi](image.md) предустановлены необходимые модули для работы с лентой. Они позволяют:
> **Caution** Обратите внимание, что светодиодную ленту нужно питать от стабильного источника энергии. Если вы подключите питание напрямую к Raspberry, то это создаст слишком большую нагрузку на ваш микрокомпьютер. Для снятия нагрузки с Raspberry можно подключить питание к преобразователю `BEC`.
* управлять эффектами/анимациями на ленте;
* управлять лентой на низком уровне (переключением цветов отдельных светодиодов);
* настраивать реакцию ленты на полетные события.
<!-- -->
> **Caution** Обратите внимание, что светодиодную ленту нужно питать от стабильного источника энергии. Если вы подключите питание напрямую к Raspberry, то это создаст слишком большую нагрузку на ваш микрокомпьютер. Для снятия нагрузки с Raspberry можно подключить питание к преобразователю BEC.
> **Note** При работе с [GPIO](gpio.md) следует подключать ленту к пину GPIO21. В противном случае управление LED-лентой будет работать некорректно.
## Высокоуровневое управление лентой
## Совместимость с ROS и Python
1. Для работы с лентой подключите ее к питанию +5v 5v, земле GND GND и сигнальному порту DIN GPIO21. Обратитесь [к инструкции по сборке](assemble_4.md#Подключение-светодиодной-ленты-к-Raspberry-Pi) для подробностей.
2. Включите поддержку LED-ленты в файле `~/catkin_ws/src/clever/clever/launch/clever.launch`:
Чтобы корректно работать со светодиодной лентой вам нужно добавить в окружение необходимые пути к библиотекам Python и пакетам ROS, для этого необходимо добавить в файл `/etc/sudoers` следующие строки:
```xml
<arg name="led" default="true"/>
```
```
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"
```
3. Настройте параметры подключения ленты *ws281x* в файле `~/catkin_ws/src/clever/clever/launch/led.launch`. Необходимо ввести верное количество светодиодов в ленте и GPIO-пин, использованный для подключения (если он отличается от *GPIO21*):
## Пример программы для светодиодной ленты на RPI3
```xml
<param name="led_count" value="30"/> <!-- количество светодиодов в ленте -->
<param name="gpio_pin" value="21"/> <!-- GPIO-пин для подключения -->
```
Для проверки работоспособности ленты можете использовать приведенный ниже код, данный код поочередно зажжет первые 10 диодов 3 цветами и в конце их погасит.
Высокоуровневое управления лентой позволяет управлять текущим эффектом (анимацией) на ленте. Для этого используется ROS-сервис `/led/set_effect`. Параметры сервиса:
* `effect` название необходимого эффекта.
* `r`, `g`, `b` цвет эффекта в формате [RGB](https://ru.wikipedia.org/wiki/RGB). Значения изменяются от 0 до 255.
Список доступных эффектов:
* `fill` (или пустая строка) залить всю ленту цветом;
* `blink` мигание цветом;
* `blink_fast` ускоренное мигание цветом;
* `fade` плавное перетекание в цвет;
* `wipe`  "надвигание" нового цвета;
* `flash` быстро мигнуть цветом 2 раза и вернуться к предыдущему эффекту;
* `rainbow` переливание ленты цветами радуги;
* `rainbow_fill` переливать заливку по цветам радуги.
Пример работы с сервисом из Python:
```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 # Количество светодиодов в ленте
LED_PIN = 21 # GPIO пин, к которому вы подсоединяете светодиодную ленту
LED_FREQ_HZ = 800000 # Частота несущего сигнала (обычно 800 кГц)
LED_DMA = 10 # DMA-канал для генерации сигнала (обычно 10)
LED_BRIGHTNESS = 255 # Яркость: 0 - наименьшая, 255 - наибольшая
LED_INVERT = False # True для инвертирования сигнала (для подключения через NPN транзистор)
LED_CHANNEL = 0 # '1' для GPIO 13, 19, 41, 45 или 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):
"""Заполнение ленты цветом по одному светодиоду."""
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) # Заполнение красным
colorWipe(strip, Color(0, 255, 0), wait_ms=100) # Заполнение зелёным
colorWipe(strip, Color(0, 0, 255), wait_ms=100) # Заполнение синим
colorWipe(strip, Color(0, 0, 0), wait_ms=100) # Выключение ленты
set_effect(effect='rainbow') # show rainbow
```
> **Note** Вы так же можете использовать тестовый код разработчиков данного модуля. Вы можете его [скачать](https://github.com/rpi-ws281x/rpi-ws281x-python/tree/master/examples "Github разработчика") из репозитория разработчика.
Сохраните программу в ваш скрипт и запустите его используя права администратора:
Также лентой можно управлять из командной сроки (Bash):
```bash
sudo python YourScriptName.py
rosservice call /led/set_effect "{effect: 'fade', r: 0, g: 0, b: 255}"
```
## Основные функции используемые для работы со светодиодной лентой
```bash
rosservice call /led/set_effect "{effect: 'rainbow'}"
```
Для подключения библиотеки и её корректной работы требуется подключить следующие модули: `Adafruit_NeoPixel` и `Color` - для работы ленты и `time` для управления задержками.
## Настройка реакции ленты на события
Клевер умеет показывать LED-лентой текущее состояние полетного контроллера и сигнализировать о событиях. Данная функция настраивается в файле `~/catkin_ws/src/clever/clever/launch/led.launch` в разделе *events effects table*. Пример настройки:
```xml
startup: { r: 255, g: 255, b: 255 }
connected: { effect: rainbow }
disconnected: { effect: blink, r: 255, g: 50, b: 50 }
<!-- ... -->
```
В левой части таблицы указывается событие, на которая лента должна среагировать. В правой части указывается эффект (анимация), который необходимо включить при возникновении события. Список поддерживаемых событий:
* `startup` запуск всех систем Клевера;
* `connected` успешное подключение к полетному контроллеру;
* `disconnected` разрыв связи с полетным контроллером;
* `armed` переключение полетного контроллера в состояние Armed;
* `disarmed` переключение полетного контроллера в состояние Disarmed;
* `stabilized`, `acro`, `rattitude`, `altctl`, `posctl`, `offboard`, `mission`, `rtl`, `land` переключение полетных режимов;
* `error` возникновение ошибки в ROS-нодах или полетном контроллере (*ERROR*-сообщение в топике `/rosout`);
* `low_battery` – низкий заряд батареи (порог настраивается в параметре `threshold`).
> **Note** Для корректной работы сигнализации LED-лентой о низком заряде батареи необходимо корректная [калибровка электропитания](power.md#Калибровка-делителя-напряжения).
Для того, чтобы отключить реакцию светодиодной ленты на события, установите аргумент `led_notify` в файле `~/catkin_ws/src/clever/clever/launch/led.launch` в значение `false`:
```xml
<arg name="led_notify" default="false"/>
```
## Низкоуровневое управление лентой
Для управления отдельными светодиодами используется ROS-сервис `/led/set_leds`. В параметрах задается массив номеров и RGB-цветов светодиодов, которые необходимо переключить.
Пример работы с сервисом из Python:
```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)])
```
Для работы с лентой необходимо создать объект типа `Adafruit_NeoPixel` и инициализировать библиотеку:
Сервис можно использовать из командной строки:
```python
# Создание объекта NeoPixel c заданной конфигурацией
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"
```
Основные функции, которые используются для управления лентой:
При использовании ленты в ROS-топике `/led/state` публикуется текущие цвета светодиодов. Просмотр топика из командной строки:
+ `numPixels()` - возвращает количество пикселей в ленте. Удобно для цикличного управления всей лентой целиком.
+ `setPixelColor(pos, color)` устанавливает цвет пикселя в позиции `pos` в цвет `color`. Цвет должен быть 24 битным значением, где первые 8 бит - красный цвет \(red\), следующие 8 бит - зелёный цвет \(green\) и последние 8 бит - голубой \(blue\). Для получения значения `color` можно использовать функцию `Color(red, green, blue)`, которая составляет это значение из 3х компонент. Каждый компонент должен находиться в диапазоне 0-255, где 0 отсутствие цвета, а 255 наибольшая доступная яркость компонента в светодиодном модуле.
+ `setPixelColorRGB(pos, red, green, blue)` устанавливает цвет пикселя в позиции pos в цвет, состоящий из компонент `red`, `green`, `blue`. Каждый компонент должен находиться в диапазоне 0255, где 0 отсутствие цвета, а 255 наибольшая доступная яркость компонента в светодиодном модуле.
+ `show()` обновляет состояние ленты. Только после её использования все программные изменения перемещаются на светодиодную ленту.
## Почему именно так и можно ли по-другому?
Основной тип ленты, который используется для Клевера 3 управляются по принципу: для массива светодиодов в ленте отправляется пакет данных по 24 бита на светодиод; каждый светодиод считывает первые 24 бита из пришедших к нему данных и устанавливает соответствующий цвет, остальные данные он отправляет следующему светодиоду в ленте. Нули и единицы задаются разными сочетаниями длительностей высокого и низкого уровня в импульсе.
Используемый тип ленты поддерживаются для управления библиотекой [rpi_ws281x](https://github.com/jgarff/rpi_ws281x), при этом для управления используется модуль DMA \(direct memory access\) процессора распберри и один из каналов передачи данных: PWM, PCM или SPI, что гарантирует отсутствие задержек в управлении \(а управляется всё на многозадачной операционке, это важно\).
Есть некоторые особенности работы с каналами, например при передаче данных с помощью PWM \(ШИМ\) перестаёт работать встроенная аудиосистема распберри, при передаче данных по PCM блокируется использование подключенных цифровых аудиоустройств \(при этом встроенная система работает\), а при использовании SPI \(кстати, требуется специальная настройка размера буфера и частоты GPU распберри для правильной работы\) лента блокирует все остальные устройства, подключенные по этому каналу.
Есть некоторые особенности выбора канала DMA для управления лентой: некоторые каналы используются системой, поэтому их использование может привести к неприятным последствиям, например использование 5 канала рушит файловую систему Raspberry, т.к. этот канал используется при чтении-записи на SD карту. Безопасный канал 10, он же установлен по умолчанию в приведённой выше библиотеке.
Поэтому сценарии использования LED-ленты следующие:
1. Если нам не важна работоспособность встроенного аудио на распберри \(и мы его не используем, т. к. аудио и лента будут выдавать белиберду в этом случае\), то можно использовать PWM канал \(для этого требуется подключить вход ленты к одному из следующих GPIO портов распберри: 12, 18, 40, или 52 для PWM0 канала и 13, 19, 41, 45 или 53 для PWM1 канала\).
2. Если нам не важно наличие на шине SPI других устройств, то можно управлять лентой по каналу SPI \(GPIO на распберри 10 или 38\).
Здесь требуется произвести следующие настройки \(только для Raspberry Pi 3\):
+ увеличить размер буфера передачи данных для поддержки длинных лент, добавив стройку `spidev.bufsiz=32768` в файл `/boot/cmdline.txt`;
+ установить частоту GPU для правильной частоты работы SPI, добавив строку `core_freq=250` в файл `/boot/config.txt`.
+ перезагрузить вашу Raspberry, используя команду `sudo reboot`
3. Если нам важна и работа аудио, и подключение к SPI устройств кроме лед ленты, то можно управлять лентой по каналу PCM \(GPIO 21 или 31\). При этом никаких дополнительных манипуляций с Raspberry не требуется.
Исходя из вышеперечисленных способов управления лентой, наилучшим вариантом, позволяющим управлять лентой, сохранить работоспособность встроенной аудиосистемы и возможность подключения всяческих устройств и датчиков по SPI, является управление по каналу PCM \(GPIO 21\) с использованием 10 канала DMA.
```bash
rostopic echo /led/state
```

125
docs/ru/leds_old.md Normal file
View File

@@ -0,0 +1,125 @@
# Работа со светодиодной лентой на Raspberry 3
## Подключение и определение типа ленты
> **Note** Документация для версии образа, начиная с 0.14. Для более ранних версий см. [документацию для версий 0.13](https://github.com/CopterExpress/clever/blob/v0.13/docs/leds.md)
Подключите светодиодную ленту к питанию +5v - 5v, земле GND - GND и сигнальному порту DIN - GPIO30, GPIO21, GPIO18.
<img src="../assets/led_connection.png" height="400px" alt="leds">
> **Caution** Обратите внимание, что светодиодную ленту нужно питать от стабильного источника энергии. Если вы подключите питание напрямую к Raspberry, то это создаст слишком большую нагрузку на ваш микрокомпьютер. Для снятия нагрузки с Raspberry можно подключить питание к преобразователю `BEC`.
<!-- -->
> **Note** При работе с [GPIO](gpio.md) следует подключать ленту к пину GPIO21. В противном случае управление LED-лентой будет работать некорректно.
## Совместимость с ROS и Python
Чтобы корректно работать со светодиодной лентой вам нужно добавить в окружение необходимые пути к библиотекам Python и пакетам ROS, для этого необходимо добавить в файл `/etc/sudoers` следующие строки:
```
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"
```
## Пример программы для светодиодной ленты на RPI3
Для проверки работоспособности ленты можете использовать приведенный ниже код, данный код поочередно зажжет первые 10 диодов 3 цветами и в конце их погасит.
```python
import time
from rpi_ws281x import Adafruit_NeoPixel
from rpi_ws281x import Color
LED_COUNT = 10 # Количество светодиодов в ленте
LED_PIN = 21 # GPIO пин, к которому вы подсоединяете светодиодную ленту
LED_FREQ_HZ = 800000 # Частота несущего сигнала (обычно 800 кГц)
LED_DMA = 10 # DMA-канал для генерации сигнала (обычно 10)
LED_BRIGHTNESS = 255 # Яркость: 0 - наименьшая, 255 - наибольшая
LED_INVERT = False # True для инвертирования сигнала (для подключения через NPN транзистор)
LED_CHANNEL = 0 # '1' для GPIO 13, 19, 41, 45 или 53
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT)
strip.begin()
def colorWipe(strip, color, wait_ms=50):
"""Заполнение ленты цветом по одному светодиоду."""
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) # Заполнение красным
colorWipe(strip, Color(0, 255, 0), wait_ms=100) # Заполнение зелёным
colorWipe(strip, Color(0, 0, 255), wait_ms=100) # Заполнение синим
colorWipe(strip, Color(0, 0, 0), wait_ms=100) # Выключение ленты
```
> **Note** Вы так же можете использовать тестовый код разработчиков данного модуля. Вы можете его [скачать](https://github.com/rpi-ws281x/rpi-ws281x-python/tree/master/examples "Github разработчика") из репозитория разработчика.
Сохраните программу в ваш скрипт и запустите его используя права администратора:
```bash
sudo python YourScriptName.py
```
## Основные функции используемые для работы со светодиодной лентой
Для подключения библиотеки и её корректной работы требуется подключить следующие модули: `Adafruit_NeoPixel` и `Color` - для работы ленты и `time` для управления задержками.
```python
from rpi_ws281x import Adafruit_NeoPixel
from rpi_ws281x import Color
import time
```
Для работы с лентой необходимо создать объект типа `Adafruit_NeoPixel` и инициализировать библиотеку:
```python
# Создание объекта NeoPixel c заданной конфигурацией
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT)
# Инициализация библиотеки, должна быть выполнена перед другими функциями
strip.begin()
```
Основные функции, которые используются для управления лентой:
+ `numPixels()` - возвращает количество пикселей в ленте. Удобно для цикличного управления всей лентой целиком.
+ `setPixelColor(pos, color)` устанавливает цвет пикселя в позиции `pos` в цвет `color`. Цвет должен быть 24 битным значением, где первые 8 бит - красный цвет \(red\), следующие 8 бит - зелёный цвет \(green\) и последние 8 бит - голубой \(blue\). Для получения значения `color` можно использовать функцию `Color(red, green, blue)`, которая составляет это значение из 3х компонент. Каждый компонент должен находиться в диапазоне 0-255, где 0 отсутствие цвета, а 255 наибольшая доступная яркость компонента в светодиодном модуле.
+ `setPixelColorRGB(pos, red, green, blue)` устанавливает цвет пикселя в позиции pos в цвет, состоящий из компонент `red`, `green`, `blue`. Каждый компонент должен находиться в диапазоне 0255, где 0 отсутствие цвета, а 255 наибольшая доступная яркость компонента в светодиодном модуле.
+ `show()` обновляет состояние ленты. Только после её использования все программные изменения перемещаются на светодиодную ленту.
## Почему именно так и можно ли по-другому?
Основной тип ленты, который используется для Клевера 3 управляются по принципу: для массива светодиодов в ленте отправляется пакет данных по 24 бита на светодиод; каждый светодиод считывает первые 24 бита из пришедших к нему данных и устанавливает соответствующий цвет, остальные данные он отправляет следующему светодиоду в ленте. Нули и единицы задаются разными сочетаниями длительностей высокого и низкого уровня в импульсе.
Используемый тип ленты поддерживаются для управления библиотекой [rpi_ws281x](https://github.com/jgarff/rpi_ws281x), при этом для управления используется модуль DMA \(direct memory access\) процессора распберри и один из каналов передачи данных: PWM, PCM или SPI, что гарантирует отсутствие задержек в управлении \(а управляется всё на многозадачной операционке, это важно\).
Есть некоторые особенности работы с каналами, например при передаче данных с помощью PWM \(ШИМ\) перестаёт работать встроенная аудиосистема распберри, при передаче данных по PCM блокируется использование подключенных цифровых аудиоустройств \(при этом встроенная система работает\), а при использовании SPI \(кстати, требуется специальная настройка размера буфера и частоты GPU распберри для правильной работы\) лента блокирует все остальные устройства, подключенные по этому каналу.
Есть некоторые особенности выбора канала DMA для управления лентой: некоторые каналы используются системой, поэтому их использование может привести к неприятным последствиям, например использование 5 канала рушит файловую систему Raspberry, т.к. этот канал используется при чтении-записи на SD карту. Безопасный канал 10, он же установлен по умолчанию в приведённой выше библиотеке.
Поэтому сценарии использования LED-ленты следующие:
1. Если нам не важна работоспособность встроенного аудио на распберри \(и мы его не используем, т. к. аудио и лента будут выдавать белиберду в этом случае\), то можно использовать PWM канал \(для этого требуется подключить вход ленты к одному из следующих GPIO портов распберри: 12, 18, 40, или 52 для PWM0 канала и 13, 19, 41, 45 или 53 для PWM1 канала\).
2. Если нам не важно наличие на шине SPI других устройств, то можно управлять лентой по каналу SPI \(GPIO на распберри 10 или 38\).
Здесь требуется произвести следующие настройки \(только для Raspberry Pi 3\):
+ увеличить размер буфера передачи данных для поддержки длинных лент, добавив стройку `spidev.bufsiz=32768` в файл `/boot/cmdline.txt`;
+ установить частоту GPU для правильной частоты работы SPI, добавив строку `core_freq=250` в файл `/boot/config.txt`.
+ перезагрузить вашу Raspberry, используя команду `sudo reboot`
3. Если нам важна и работа аудио, и подключение к SPI устройств кроме лед ленты, то можно управлять лентой по каналу PCM \(GPIO 21 или 31\). При этом никаких дополнительных манипуляций с Raspberry не требуется.
Исходя из вышеперечисленных способов управления лентой, наилучшим вариантом, позволяющим управлять лентой, сохранить работоспособность встроенной аудиосистемы и возможность подключения всяческих устройств и датчиков по SPI, является управление по каналу PCM \(GPIO 21\) с использованием 10 канала DMA.

View File

@@ -33,6 +33,7 @@
{ "from": "snippets.html", "to": "ru/snippets.html" },
{ "from": "camera_frame.html", "to": "ru/camera_frame.html" },
{ "from": "camera.html", "to": "ru/camera.html" },
{ "from": "led.html", "to": "ru/leds.html" },
{ "from": "leds.html", "to": "ru/leds.html" },
{ "from": "rviz.html", "to": "ru/rviz.html" },
{ "from": "sitl.html", "to": "ru/sitl.html" },
@@ -50,6 +51,8 @@
{ "from": "snippets/", "to": "ru/snippets.html" },
{ "from": "optical_flow/", "to": "ru/optical_flow.html" },
{ "from": "laser/", "to": "ru/laser.html" },
{ "from": "led/", "to": "ru/leds.html" },
{ "from": "leds/", "to": "ru/leds.html" },
{ "from": "ru/microsd_images.html", "to": "ru/image.html" },
{ "from": "en/microsd_images.html", "to": "en/image.html" }