1
0
Fork 0
mirror of https://github.com/betaflight/betaflight.git synced 2025-07-24 16:55:36 +03:00
betaflight/src/main/io/ledstrip.c
Michael Keller d6e7ed3560 Added 'ledstrip_visual_beeper' feature.
Added build condition for LED strip related config option.

Added 'ledstrip_visual_beeper' feature.

Added build condition for LED strip related config option.
2016-05-17 08:46:21 +12:00

1118 lines
33 KiB
C

/*
* This file is part of Cleanflight.
*
* Cleanflight is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cleanflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdarg.h>
#include <platform.h>
#include <build_config.h>
#ifdef LED_STRIP
#include <common/color.h>
#include <common/maths.h>
#include <common/typeconversion.h>
#include "drivers/light_ws2811strip.h"
#include "drivers/system.h"
#include "drivers/serial.h"
#include "drivers/sensor.h"
#include "drivers/accgyro.h"
#include "drivers/gpio.h"
#include "drivers/timer.h"
#include "drivers/pwm_rx.h"
#include <common/printf.h>
#include "common/axis.h"
#include "io/rc_controls.h"
#include "sensors/battery.h"
#include "sensors/sensors.h"
#include "sensors/boardalignment.h"
#include "sensors/gyro.h"
#include "sensors/acceleration.h"
#include "sensors/barometer.h"
#include "io/ledstrip.h"
#include "io/beeper.h"
#include "io/escservo.h"
#include "io/gimbal.h"
#include "io/serial.h"
#include "flight/failsafe.h"
#include "flight/mixer.h"
#include "flight/pid.h"
#include "flight/imu.h"
#include "telemetry/telemetry.h"
#include "config/runtime_config.h"
#include "config/config.h"
#include "config/config_profile.h"
#include "config/config_master.h"
static bool ledStripInitialised = false;
static bool ledStripEnabled = true;
static void ledStripDisable(void);
//#define USE_LED_ANIMATION
//#define USE_LED_RING_DEFAULT_CONFIG
// timers
#ifdef USE_LED_ANIMATION
static uint32_t nextAnimationUpdateAt = 0;
#endif
static uint32_t nextIndicatorFlashAt = 0;
static uint32_t nextWarningFlashAt = 0;
static uint32_t nextRotationUpdateAt = 0;
#define LED_STRIP_20HZ ((1000 * 1000) / 20)
#define LED_STRIP_10HZ ((1000 * 1000) / 10)
#define LED_STRIP_5HZ ((1000 * 1000) / 5)
#if MAX_LED_STRIP_LENGTH > WS2811_LED_STRIP_LENGTH
#error "Led strip length must match driver"
#endif
// H S V
#define LED_BLACK { 0, 0, 0}
#define LED_WHITE { 0, 255, 255}
#define LED_RED { 0, 0, 255}
#define LED_ORANGE { 30, 0, 255}
#define LED_YELLOW { 60, 0, 255}
#define LED_LIME_GREEN { 90, 0, 255}
#define LED_GREEN {120, 0, 255}
#define LED_MINT_GREEN {150, 0, 255}
#define LED_CYAN {180, 0, 255}
#define LED_LIGHT_BLUE {210, 0, 255}
#define LED_BLUE {240, 0, 255}
#define LED_DARK_VIOLET {270, 0, 255}
#define LED_MAGENTA {300, 0, 255}
#define LED_DEEP_PINK {330, 0, 255}
const hsvColor_t hsv_black = LED_BLACK;
const hsvColor_t hsv_white = LED_WHITE;
const hsvColor_t hsv_red = LED_RED;
const hsvColor_t hsv_orange = LED_ORANGE;
const hsvColor_t hsv_yellow = LED_YELLOW;
const hsvColor_t hsv_limeGreen = LED_LIME_GREEN;
const hsvColor_t hsv_green = LED_GREEN;
const hsvColor_t hsv_mintGreen = LED_MINT_GREEN;
const hsvColor_t hsv_cyan = LED_CYAN;
const hsvColor_t hsv_lightBlue = LED_LIGHT_BLUE;
const hsvColor_t hsv_blue = LED_BLUE;
const hsvColor_t hsv_darkViolet = LED_DARK_VIOLET;
const hsvColor_t hsv_magenta = LED_MAGENTA;
const hsvColor_t hsv_deepPink = LED_DEEP_PINK;
#define LED_DIRECTION_COUNT 6
const hsvColor_t * const defaultColors[] = {
&hsv_black,
&hsv_white,
&hsv_red,
&hsv_orange,
&hsv_yellow,
&hsv_limeGreen,
&hsv_green,
&hsv_mintGreen,
&hsv_cyan,
&hsv_lightBlue,
&hsv_blue,
&hsv_darkViolet,
&hsv_magenta,
&hsv_deepPink
};
typedef enum {
COLOR_BLACK = 0,
COLOR_WHITE,
COLOR_RED,
COLOR_ORANGE,
COLOR_YELLOW,
COLOR_LIME_GREEN,
COLOR_GREEN,
COLOR_MINT_GREEN,
COLOR_CYAN,
COLOR_LIGHT_BLUE,
COLOR_BLUE,
COLOR_DARK_VIOLET,
COLOR_MAGENTA,
COLOR_DEEP_PINK,
} colorIds;
typedef enum {
DIRECTION_NORTH = 0,
DIRECTION_EAST,
DIRECTION_SOUTH,
DIRECTION_WEST,
DIRECTION_UP,
DIRECTION_DOWN
} directionId_e;
typedef struct modeColorIndexes_s {
uint8_t north;
uint8_t east;
uint8_t south;
uint8_t west;
uint8_t up;
uint8_t down;
} modeColorIndexes_t;
// Note, the color index used for the mode colors below refer to the default colors.
// if the colors are reconfigured the index is still valid but the displayed color might
// be different.
// See colors[] and defaultColors[] and applyDefaultColors[]
static const modeColorIndexes_t orientationModeColors = {
COLOR_WHITE,
COLOR_DARK_VIOLET,
COLOR_RED,
COLOR_DEEP_PINK,
COLOR_BLUE,
COLOR_ORANGE
};
static const modeColorIndexes_t headfreeModeColors = {
COLOR_LIME_GREEN,
COLOR_DARK_VIOLET,
COLOR_ORANGE,
COLOR_DEEP_PINK,
COLOR_BLUE,
COLOR_ORANGE
};
static const modeColorIndexes_t horizonModeColors = {
COLOR_BLUE,
COLOR_DARK_VIOLET,
COLOR_YELLOW,
COLOR_DEEP_PINK,
COLOR_BLUE,
COLOR_ORANGE
};
static const modeColorIndexes_t angleModeColors = {
COLOR_CYAN,
COLOR_DARK_VIOLET,
COLOR_YELLOW,
COLOR_DEEP_PINK,
COLOR_BLUE,
COLOR_ORANGE
};
#ifdef MAG
static const modeColorIndexes_t magModeColors = {
COLOR_MINT_GREEN,
COLOR_DARK_VIOLET,
COLOR_ORANGE,
COLOR_DEEP_PINK,
COLOR_BLUE,
COLOR_ORANGE
};
#endif
static const modeColorIndexes_t baroModeColors = {
COLOR_LIGHT_BLUE,
COLOR_DARK_VIOLET,
COLOR_RED,
COLOR_DEEP_PINK,
COLOR_BLUE,
COLOR_ORANGE
};
uint8_t ledGridWidth;
uint8_t ledGridHeight;
uint8_t ledCount;
uint8_t ledsInRingCount;
ledConfig_t *ledConfigs;
hsvColor_t *colors;
#ifdef USE_LED_RING_DEFAULT_CONFIG
const ledConfig_t defaultLedStripConfig[] = {
{ CALCULATE_LED_XY( 2, 2), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 2, 1), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 2, 0), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 1, 0), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 0, 0), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 0, 1), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 0, 2), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 1, 2), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 1, 1), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 1, 1), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 1, 1), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 1, 1), 3, LED_FUNCTION_THRUST_RING},
};
#elif defined(USE_COLIBTI_RACE_LED_DEFAULT_CONFIG)
const ledConfig_t defaultLedStripConfig[] = {
{ CALCULATE_LED_XY( 0, 0), 6, LED_DIRECTION_WEST | LED_FUNCTION_WARNING | LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 0, 1), 6, LED_DIRECTION_WEST | LED_FUNCTION_WARNING | LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 0, 8), 6, LED_DIRECTION_WEST | LED_FUNCTION_WARNING | LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 7, 15), 6, LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 8, 15), 6, LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 7, 14), 6, LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 8, 14), 6, LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 15, 8), 6, LED_DIRECTION_EAST | LED_FUNCTION_WARNING | LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 15, 1), 6, LED_DIRECTION_EAST | LED_FUNCTION_WARNING | LED_FUNCTION_COLOR },
{ CALCULATE_LED_XY( 15, 0), 6, LED_DIRECTION_EAST | LED_FUNCTION_WARNING | LED_FUNCTION_COLOR },
};
#else
const ledConfig_t defaultLedStripConfig[] = {
{ CALCULATE_LED_XY(15, 15), 0, LED_DIRECTION_SOUTH | LED_DIRECTION_EAST | LED_FUNCTION_INDICATOR | LED_FUNCTION_ARM_STATE },
{ CALCULATE_LED_XY(15, 8), 0, LED_DIRECTION_EAST | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY(15, 7), 0, LED_DIRECTION_EAST | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY(15, 0), 0, LED_DIRECTION_NORTH | LED_DIRECTION_EAST | LED_FUNCTION_INDICATOR | LED_FUNCTION_ARM_STATE },
{ CALCULATE_LED_XY( 8, 0), 0, LED_DIRECTION_NORTH | LED_FUNCTION_FLIGHT_MODE },
{ CALCULATE_LED_XY( 7, 0), 0, LED_DIRECTION_NORTH | LED_FUNCTION_FLIGHT_MODE },
{ CALCULATE_LED_XY( 0, 0), 0, LED_DIRECTION_NORTH | LED_DIRECTION_WEST | LED_FUNCTION_INDICATOR | LED_FUNCTION_ARM_STATE },
{ CALCULATE_LED_XY( 0, 7), 0, LED_DIRECTION_WEST | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 0, 8), 0, LED_DIRECTION_WEST | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 0, 15), 0, LED_DIRECTION_SOUTH | LED_DIRECTION_WEST | LED_FUNCTION_INDICATOR | LED_FUNCTION_ARM_STATE },
{ CALCULATE_LED_XY( 7, 15), 0, LED_DIRECTION_SOUTH | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 8, 15), 0, LED_DIRECTION_SOUTH | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 7, 7), 0, LED_DIRECTION_UP | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 8, 7), 0, LED_DIRECTION_UP | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 7, 8), 0, LED_DIRECTION_DOWN | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 8, 8), 0, LED_DIRECTION_DOWN | LED_FUNCTION_FLIGHT_MODE | LED_FUNCTION_WARNING },
{ CALCULATE_LED_XY( 8, 9), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 9, 10), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY(10, 11), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY(10, 12), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 9, 13), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 8, 14), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 7, 14), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 6, 13), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 5, 12), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 5, 11), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 6, 10), 3, LED_FUNCTION_THRUST_RING},
{ CALCULATE_LED_XY( 7, 9), 3, LED_FUNCTION_THRUST_RING},
};
#endif
/*
* 6 coords @nn,nn
* 4 direction @##
* 6 modes @####
* = 16 bytes per led
* 16 * 32 leds = 512 bytes storage needed worst case.
* = not efficient to store led configs as strings in flash.
* = becomes a problem to send all the data via cli due to serial/cli buffers
*/
typedef enum {
X_COORDINATE,
Y_COORDINATE,
DIRECTIONS,
FUNCTIONS,
RING_COLORS
} parseState_e;
#define PARSE_STATE_COUNT 5
static const char chunkSeparators[PARSE_STATE_COUNT] = {',', ':', ':',':', '\0' };
static const char directionCodes[] = { 'N', 'E', 'S', 'W', 'U', 'D' };
#define DIRECTION_COUNT (sizeof(directionCodes) / sizeof(directionCodes[0]))
static const uint8_t directionMappings[DIRECTION_COUNT] = {
LED_DIRECTION_NORTH,
LED_DIRECTION_EAST,
LED_DIRECTION_SOUTH,
LED_DIRECTION_WEST,
LED_DIRECTION_UP,
LED_DIRECTION_DOWN
};
static const char functionCodes[] = { 'I', 'W', 'F', 'A', 'T', 'R', 'C' };
#define FUNCTION_COUNT (sizeof(functionCodes) / sizeof(functionCodes[0]))
static const uint16_t functionMappings[FUNCTION_COUNT] = {
LED_FUNCTION_INDICATOR,
LED_FUNCTION_WARNING,
LED_FUNCTION_FLIGHT_MODE,
LED_FUNCTION_ARM_STATE,
LED_FUNCTION_THROTTLE,
LED_FUNCTION_THRUST_RING,
LED_FUNCTION_COLOR
};
// grid offsets
uint8_t highestYValueForNorth;
uint8_t lowestYValueForSouth;
uint8_t highestXValueForWest;
uint8_t lowestXValueForEast;
void determineLedStripDimensions(void)
{
ledGridWidth = 0;
ledGridHeight = 0;
uint8_t ledIndex;
const ledConfig_t *ledConfig;
for (ledIndex = 0; ledIndex < ledCount; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if (GET_LED_X(ledConfig) >= ledGridWidth) {
ledGridWidth = GET_LED_X(ledConfig) + 1;
}
if (GET_LED_Y(ledConfig) >= ledGridHeight) {
ledGridHeight = GET_LED_Y(ledConfig) + 1;
}
}
}
void determineOrientationLimits(void)
{
bool isOddHeight = (ledGridHeight & 1);
bool isOddWidth = (ledGridWidth & 1);
uint8_t heightModifier = isOddHeight ? 1 : 0;
uint8_t widthModifier = isOddWidth ? 1 : 0;
highestYValueForNorth = (ledGridHeight / 2) - 1;
lowestYValueForSouth = (ledGridHeight / 2) + heightModifier;
highestXValueForWest = (ledGridWidth / 2) - 1;
lowestXValueForEast = (ledGridWidth / 2) + widthModifier;
}
void updateLedCount(void)
{
const ledConfig_t *ledConfig;
uint8_t ledIndex;
ledCount = 0;
ledsInRingCount = 0;
if( ledConfigs == 0 ){
return;
}
for (ledIndex = 0; ledIndex < MAX_LED_STRIP_LENGTH; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if (ledConfig->flags == 0 && ledConfig->xy == 0) {
break;
}
ledCount++;
if ((ledConfig->flags & LED_FUNCTION_THRUST_RING)) {
ledsInRingCount++;
}
}
}
void reevalulateLedConfig(void)
{
updateLedCount();
determineLedStripDimensions();
determineOrientationLimits();
}
#define CHUNK_BUFFER_SIZE 10
#define NEXT_PARSE_STATE(parseState) ((parseState + 1) % PARSE_STATE_COUNT)
bool parseLedStripConfig(uint8_t ledIndex, const char *config)
{
char chunk[CHUNK_BUFFER_SIZE];
uint8_t chunkIndex;
uint8_t val;
uint8_t parseState = X_COORDINATE;
bool ok = true;
if (ledIndex >= MAX_LED_STRIP_LENGTH) {
return !ok;
}
ledConfig_t *ledConfig = &ledConfigs[ledIndex];
memset(ledConfig, 0, sizeof(ledConfig_t));
while (ok) {
char chunkSeparator = chunkSeparators[parseState];
memset(&chunk, 0, sizeof(chunk));
chunkIndex = 0;
while (*config && chunkIndex < CHUNK_BUFFER_SIZE && *config != chunkSeparator) {
chunk[chunkIndex++] = *config++;
}
if (*config++ != chunkSeparator) {
ok = false;
break;
}
switch((parseState_e)parseState) {
case X_COORDINATE:
val = atoi(chunk);
ledConfig->xy |= CALCULATE_LED_X(val);
break;
case Y_COORDINATE:
val = atoi(chunk);
ledConfig->xy |= CALCULATE_LED_Y(val);
break;
case DIRECTIONS:
for (chunkIndex = 0; chunk[chunkIndex] && chunkIndex < CHUNK_BUFFER_SIZE; chunkIndex++) {
for (uint8_t mappingIndex = 0; mappingIndex < DIRECTION_COUNT; mappingIndex++) {
if (directionCodes[mappingIndex] == chunk[chunkIndex]) {
ledConfig->flags |= directionMappings[mappingIndex];
break;
}
}
}
break;
case FUNCTIONS:
for (chunkIndex = 0; chunk[chunkIndex] && chunkIndex < CHUNK_BUFFER_SIZE; chunkIndex++) {
for (uint8_t mappingIndex = 0; mappingIndex < FUNCTION_COUNT; mappingIndex++) {
if (functionCodes[mappingIndex] == chunk[chunkIndex]) {
ledConfig->flags |= functionMappings[mappingIndex];
break;
}
}
}
break;
case RING_COLORS:
if (atoi(chunk) < CONFIGURABLE_COLOR_COUNT) {
ledConfig->color = atoi(chunk);
} else {
ledConfig->color = 0;
}
break;
default :
break;
}
parseState++;
if (parseState >= PARSE_STATE_COUNT) {
break;
}
}
if (!ok) {
memset(ledConfig, 0, sizeof(ledConfig_t));
}
reevalulateLedConfig();
return ok;
}
void generateLedConfig(uint8_t ledIndex, char *ledConfigBuffer, size_t bufferSize)
{
char functions[FUNCTION_COUNT];
char directions[DIRECTION_COUNT];
uint8_t index;
uint8_t mappingIndex;
ledConfig_t *ledConfig = &ledConfigs[ledIndex];
memset(ledConfigBuffer, 0, bufferSize);
memset(&functions, 0, sizeof(functions));
memset(&directions, 0, sizeof(directions));
for (mappingIndex = 0, index = 0; mappingIndex < FUNCTION_COUNT; mappingIndex++) {
if (ledConfig->flags & functionMappings[mappingIndex]) {
functions[index++] = functionCodes[mappingIndex];
}
}
for (mappingIndex = 0, index = 0; mappingIndex < DIRECTION_COUNT; mappingIndex++) {
if (ledConfig->flags & directionMappings[mappingIndex]) {
directions[index++] = directionCodes[mappingIndex];
}
}
sprintf(ledConfigBuffer, "%u,%u:%s:%s:%u", GET_LED_X(ledConfig), GET_LED_Y(ledConfig), directions, functions, ledConfig->color);
}
void applyDirectionalModeColor(const uint8_t ledIndex, const ledConfig_t *ledConfig, const modeColorIndexes_t *modeColors)
{
// apply up/down colors regardless of quadrant.
if ((ledConfig->flags & LED_DIRECTION_UP)) {
setLedHsv(ledIndex, &colors[modeColors->up]);
}
if ((ledConfig->flags & LED_DIRECTION_DOWN)) {
setLedHsv(ledIndex, &colors[modeColors->down]);
}
// override with n/e/s/w colors to each n/s e/w half - bail at first match.
if ((ledConfig->flags & LED_DIRECTION_WEST) && GET_LED_X(ledConfig) <= highestXValueForWest) {
setLedHsv(ledIndex, &colors[modeColors->west]);
}
if ((ledConfig->flags & LED_DIRECTION_EAST) && GET_LED_X(ledConfig) >= lowestXValueForEast) {
setLedHsv(ledIndex, &colors[modeColors->east]);
}
if ((ledConfig->flags & LED_DIRECTION_NORTH) && GET_LED_Y(ledConfig) <= highestYValueForNorth) {
setLedHsv(ledIndex, &colors[modeColors->north]);
}
if ((ledConfig->flags & LED_DIRECTION_SOUTH) && GET_LED_Y(ledConfig) >= lowestYValueForSouth) {
setLedHsv(ledIndex, &colors[modeColors->south]);
}
}
typedef enum {
QUADRANT_NORTH_EAST = 1,
QUADRANT_SOUTH_EAST,
QUADRANT_SOUTH_WEST,
QUADRANT_NORTH_WEST
} quadrant_e;
void applyQuadrantColor(const uint8_t ledIndex, const ledConfig_t *ledConfig, const quadrant_e quadrant, const hsvColor_t *color)
{
switch (quadrant) {
case QUADRANT_NORTH_EAST:
if (GET_LED_Y(ledConfig) <= highestYValueForNorth && GET_LED_X(ledConfig) >= lowestXValueForEast) {
setLedHsv(ledIndex, color);
}
return;
case QUADRANT_SOUTH_EAST:
if (GET_LED_Y(ledConfig) >= lowestYValueForSouth && GET_LED_X(ledConfig) >= lowestXValueForEast) {
setLedHsv(ledIndex, color);
}
return;
case QUADRANT_SOUTH_WEST:
if (GET_LED_Y(ledConfig) >= lowestYValueForSouth && GET_LED_X(ledConfig) <= highestXValueForWest) {
setLedHsv(ledIndex, color);
}
return;
case QUADRANT_NORTH_WEST:
if (GET_LED_Y(ledConfig) <= highestYValueForNorth && GET_LED_X(ledConfig) <= highestXValueForWest) {
setLedHsv(ledIndex, color);
}
return;
}
}
void applyLedModeLayer(void)
{
const ledConfig_t *ledConfig;
uint8_t ledIndex;
for (ledIndex = 0; ledIndex < ledCount; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if (!(ledConfig->flags & LED_FUNCTION_THRUST_RING)) {
if (ledConfig->flags & LED_FUNCTION_COLOR) {
setLedHsv(ledIndex, &colors[ledConfig->color]);
} else {
setLedHsv(ledIndex, &hsv_black);
}
}
if (!(ledConfig->flags & LED_FUNCTION_FLIGHT_MODE)) {
if (ledConfig->flags & LED_FUNCTION_ARM_STATE) {
if (!ARMING_FLAG(ARMED)) {
setLedHsv(ledIndex, &hsv_green);
} else {
setLedHsv(ledIndex, &hsv_blue);
}
}
continue;
}
applyDirectionalModeColor(ledIndex, ledConfig, &orientationModeColors);
if (FLIGHT_MODE(HEADFREE_MODE)) {
applyDirectionalModeColor(ledIndex, ledConfig, &headfreeModeColors);
#ifdef MAG
} else if (FLIGHT_MODE(MAG_MODE)) {
applyDirectionalModeColor(ledIndex, ledConfig, &magModeColors);
#endif
#ifdef BARO
} else if (FLIGHT_MODE(BARO_MODE)) {
applyDirectionalModeColor(ledIndex, ledConfig, &baroModeColors);
#endif
} else if (FLIGHT_MODE(HORIZON_MODE)) {
applyDirectionalModeColor(ledIndex, ledConfig, &horizonModeColors);
} else if (FLIGHT_MODE(ANGLE_MODE)) {
applyDirectionalModeColor(ledIndex, ledConfig, &angleModeColors);
}
}
}
typedef enum {
WARNING_FLAG_NONE = 0,
WARNING_FLAG_LOW_BATTERY = (1 << 0),
WARNING_FLAG_FAILSAFE = (1 << 1),
WARNING_FLAG_ARMING_DISABLED = (1 << 2)
} warningFlags_e;
static uint8_t warningFlags = WARNING_FLAG_NONE;
void applyLedWarningLayer(uint8_t updateNow)
{
const ledConfig_t *ledConfig;
uint8_t ledIndex;
static uint8_t warningFlashCounter = 0;
if (updateNow && warningFlashCounter == 0) {
warningFlags = WARNING_FLAG_NONE;
if (feature(FEATURE_VBAT) && getBatteryState() != BATTERY_OK) {
warningFlags |= WARNING_FLAG_LOW_BATTERY;
}
if (feature(FEATURE_FAILSAFE) && failsafeIsActive()) {
warningFlags |= WARNING_FLAG_FAILSAFE;
}
if (!ARMING_FLAG(ARMED) && !ARMING_FLAG(OK_TO_ARM)) {
warningFlags |= WARNING_FLAG_ARMING_DISABLED;
}
}
if (warningFlags || warningFlashCounter > 0) {
const hsvColor_t *warningColor = &hsv_black;
if ((warningFlashCounter & 1) == 0) {
if (warningFlashCounter < 4 && (warningFlags & WARNING_FLAG_ARMING_DISABLED)) {
warningColor = &hsv_green;
}
if (warningFlashCounter >= 4 && warningFlashCounter < 12 && (warningFlags & WARNING_FLAG_LOW_BATTERY)) {
warningColor = &hsv_red;
}
if (warningFlashCounter >= 12 && warningFlashCounter < 16 && (warningFlags & WARNING_FLAG_FAILSAFE)) {
warningColor = &hsv_yellow;
}
} else {
if (warningFlashCounter >= 12 && warningFlashCounter < 16 && (warningFlags & WARNING_FLAG_FAILSAFE)) {
warningColor = &hsv_blue;
}
}
for (ledIndex = 0; ledIndex < ledCount; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if (!(ledConfig->flags & LED_FUNCTION_WARNING)) {
continue;
}
setLedHsv(ledIndex, warningColor);
}
}
if (updateNow && (warningFlags || warningFlashCounter)) {
warningFlashCounter++;
if (warningFlashCounter == 20) {
warningFlashCounter = 0;
}
}
}
#define INDICATOR_DEADBAND 25
void applyLedIndicatorLayer(uint8_t indicatorFlashState)
{
const ledConfig_t *ledConfig;
static const hsvColor_t *flashColor;
if (!rxIsReceivingSignal()) {
return;
}
if (indicatorFlashState == 0) {
flashColor = &hsv_orange;
} else {
flashColor = &hsv_black;
}
uint8_t ledIndex;
for (ledIndex = 0; ledIndex < ledCount; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if (!(ledConfig->flags & LED_FUNCTION_INDICATOR)) {
continue;
}
if (rcCommand[ROLL] > INDICATOR_DEADBAND) {
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_NORTH_EAST, flashColor);
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_SOUTH_EAST, flashColor);
}
if (rcCommand[ROLL] < -INDICATOR_DEADBAND) {
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_NORTH_WEST, flashColor);
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_SOUTH_WEST, flashColor);
}
if (rcCommand[PITCH] > INDICATOR_DEADBAND) {
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_NORTH_EAST, flashColor);
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_NORTH_WEST, flashColor);
}
if (rcCommand[PITCH] < -INDICATOR_DEADBAND) {
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_SOUTH_EAST, flashColor);
applyQuadrantColor(ledIndex, ledConfig, QUADRANT_SOUTH_WEST, flashColor);
}
}
}
void applyLedThrottleLayer()
{
const ledConfig_t *ledConfig;
hsvColor_t color;
uint8_t ledIndex;
for (ledIndex = 0; ledIndex < ledCount; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if (!(ledConfig->flags & LED_FUNCTION_THROTTLE)) {
continue;
}
getLedHsv(ledIndex, &color);
int scaled = scaleRange(rcData[THROTTLE], PWM_RANGE_MIN, PWM_RANGE_MAX, -60, +60);
scaled += HSV_HUE_MAX;
color.h = (color.h + scaled) % HSV_HUE_MAX;
setLedHsv(ledIndex, &color);
}
}
#define ROTATION_SEQUENCE_LED_COUNT 6 // 2 on, 4 off
#define ROTATION_SEQUENCE_LED_WIDTH 2
#define RING_PATTERN_NOT_CALCULATED 255
void applyLedThrustRingLayer(void)
{
const ledConfig_t *ledConfig;
hsvColor_t ringColor;
uint8_t ledIndex;
// initialised to special value instead of using more memory for a flag.
static uint8_t rotationSeqLedCount = RING_PATTERN_NOT_CALCULATED;
static uint8_t rotationPhase = ROTATION_SEQUENCE_LED_COUNT;
bool nextLedOn = false;
uint8_t ledRingIndex = 0;
for (ledIndex = 0; ledIndex < ledCount; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if ((ledConfig->flags & LED_FUNCTION_THRUST_RING) == 0) {
continue;
}
bool applyColor = false;
if (ARMING_FLAG(ARMED)) {
if ((ledRingIndex + rotationPhase) % rotationSeqLedCount < ROTATION_SEQUENCE_LED_WIDTH) {
applyColor = true;
}
} else {
if (nextLedOn == false) {
applyColor = true;
}
nextLedOn = !nextLedOn;
}
if (applyColor) {
ringColor = colors[ledConfig->color];
} else {
ringColor = hsv_black;
}
setLedHsv(ledIndex, &ringColor);
ledRingIndex++;
}
uint8_t ledRingLedCount = ledRingIndex;
if (rotationSeqLedCount == RING_PATTERN_NOT_CALCULATED) {
// update ring pattern according to total number of ring leds found
rotationSeqLedCount = ledRingLedCount;
// try to split in segments/rings of exactly ROTATION_SEQUENCE_LED_COUNT leds
if ((ledRingLedCount % ROTATION_SEQUENCE_LED_COUNT) == 0) {
rotationSeqLedCount = ROTATION_SEQUENCE_LED_COUNT;
} else {
// else split up in equal segments/rings of at most ROTATION_SEQUENCE_LED_COUNT leds
while ((rotationSeqLedCount > ROTATION_SEQUENCE_LED_COUNT) && ((rotationSeqLedCount % 2) == 0)) {
rotationSeqLedCount >>= 1;
}
}
// trigger start over
rotationPhase = 1;
}
rotationPhase--;
if (rotationPhase == 0) {
rotationPhase = rotationSeqLedCount;
}
}
#ifdef USE_LED_ANIMATION
static uint8_t previousRow;
static uint8_t currentRow;
static uint8_t nextRow;
void updateLedAnimationState(void)
{
static uint8_t frameCounter = 0;
uint8_t animationFrames = ledGridHeight;
previousRow = (frameCounter + animationFrames - 1) % animationFrames;
currentRow = frameCounter;
nextRow = (frameCounter + 1) % animationFrames;
frameCounter = (frameCounter + 1) % animationFrames;
}
static void applyLedAnimationLayer(void)
{
const ledConfig_t *ledConfig;
if (ARMING_FLAG(ARMED)) {
return;
}
uint8_t ledIndex;
for (ledIndex = 0; ledIndex < ledCount; ledIndex++) {
ledConfig = &ledConfigs[ledIndex];
if (GET_LED_Y(ledConfig) == previousRow) {
setLedHsv(ledIndex, &hsv_white);
scaleLedValue(ledIndex, 50);
} else if (GET_LED_Y(ledConfig) == currentRow) {
setLedHsv(ledIndex, &hsv_white);
} else if (GET_LED_Y(ledConfig) == nextRow) {
scaleLedValue(ledIndex, 50);
}
}
}
#endif
void updateLedStrip(void)
{
if (!(ledStripInitialised && isWS2811LedStripReady())) {
return;
}
if (IS_RC_MODE_ACTIVE(BOXLEDLOW) && !(masterConfig.ledstrip_visual_beeper && isBeeperOn())) {
if (ledStripEnabled) {
ledStripDisable();
ledStripEnabled = false;
}
} else {
ledStripEnabled = true;
}
if (!ledStripEnabled){
return;
}
uint32_t now = micros();
bool indicatorFlashNow = (int32_t)(now - nextIndicatorFlashAt) >= 0L;
bool warningFlashNow = (int32_t)(now - nextWarningFlashAt) >= 0L;
bool rotationUpdateNow = (int32_t)(now - nextRotationUpdateAt) >= 0L;
#ifdef USE_LED_ANIMATION
bool animationUpdateNow = (int32_t)(now - nextAnimationUpdateAt) >= 0L;
#endif
if (!(
indicatorFlashNow ||
rotationUpdateNow ||
warningFlashNow
#ifdef USE_LED_ANIMATION
|| animationUpdateNow
#endif
)) {
return;
}
static uint8_t indicatorFlashState = 0;
// LAYER 1
applyLedModeLayer();
applyLedThrottleLayer();
// LAYER 2
if (warningFlashNow) {
nextWarningFlashAt = now + LED_STRIP_10HZ;
}
applyLedWarningLayer(warningFlashNow);
// LAYER 3
if (indicatorFlashNow) {
uint8_t rollScale = ABS(rcCommand[ROLL]) / 50;
uint8_t pitchScale = ABS(rcCommand[PITCH]) / 50;
uint8_t scale = MAX(rollScale, pitchScale);
nextIndicatorFlashAt = now + (LED_STRIP_5HZ / MAX(1, scale));
if (indicatorFlashState == 0) {
indicatorFlashState = 1;
} else {
indicatorFlashState = 0;
}
}
applyLedIndicatorLayer(indicatorFlashState);
#ifdef USE_LED_ANIMATION
if (animationUpdateNow) {
nextAnimationUpdateAt = now + LED_STRIP_20HZ;
updateLedAnimationState();
}
applyLedAnimationLayer();
#endif
if (rotationUpdateNow) {
applyLedThrustRingLayer();
uint8_t animationSpeedScale = 1;
if (ARMING_FLAG(ARMED)) {
animationSpeedScale = scaleRange(rcData[THROTTLE], PWM_RANGE_MIN, PWM_RANGE_MAX, 1, 10);
}
nextRotationUpdateAt = now + LED_STRIP_5HZ/animationSpeedScale;
}
ws2811UpdateStrip();
}
bool parseColor(uint8_t index, const char *colorConfig)
{
const char *remainingCharacters = colorConfig;
hsvColor_t *color = &colors[index];
bool ok = true;
uint8_t componentIndex;
for (componentIndex = 0; ok && componentIndex < HSV_COLOR_COMPONENT_COUNT; componentIndex++) {
uint16_t val = atoi(remainingCharacters);
switch (componentIndex) {
case HSV_HUE:
if (val > HSV_HUE_MAX) {
ok = false;
continue;
}
colors[index].h = val;
break;
case HSV_SATURATION:
if (val > HSV_SATURATION_MAX) {
ok = false;
continue;
}
colors[index].s = (uint8_t)val;
break;
case HSV_VALUE:
if (val > HSV_VALUE_MAX) {
ok = false;
continue;
}
colors[index].v = (uint8_t)val;
break;
}
remainingCharacters = strstr(remainingCharacters, ",");
if (remainingCharacters) {
remainingCharacters++;
} else {
if (componentIndex < 2) {
ok = false;
}
}
}
if (!ok) {
memset(color, 0, sizeof(hsvColor_t));
}
return ok;
}
void applyDefaultColors(hsvColor_t *colors, uint8_t colorCount)
{
memset(colors, 0, colorCount * sizeof(hsvColor_t));
for (uint8_t colorIndex = 0; colorIndex < colorCount && colorIndex < (sizeof(defaultColors) / sizeof(defaultColors[0])); colorIndex++) {
*colors++ = *defaultColors[colorIndex];
}
}
void applyDefaultLedStripConfig(ledConfig_t *ledConfigs)
{
memset(ledConfigs, 0, MAX_LED_STRIP_LENGTH * sizeof(ledConfig_t));
memcpy(ledConfigs, &defaultLedStripConfig, sizeof(defaultLedStripConfig));
reevalulateLedConfig();
}
void ledStripInit(ledConfig_t *ledConfigsToUse, hsvColor_t *colorsToUse)
{
ledConfigs = ledConfigsToUse;
colors = colorsToUse;
ledStripInitialised = false;
}
void ledStripEnable(void)
{
reevalulateLedConfig();
ledStripInitialised = true;
ws2811LedStripInit();
}
static void ledStripDisable(void)
{
setStripColor(&hsv_black);
ws2811UpdateStrip();
}
#endif