2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
28 #include "build/build_config.h"
30 #include "common/axis.h"
31 #include "common/maths.h"
32 #include "common/printf.h"
33 #include "common/typeconversion.h"
34 #include "common/string_light.h"
35 #include "common/utils.h"
37 #include "config/feature.h"
38 #include "config/parameter_group.h"
39 #include "config/parameter_group_ids.h"
41 #include "drivers/light_ws2811strip.h"
42 #include "drivers/serial.h"
43 #include "drivers/time.h"
45 #include "fc/config.h"
46 #include "fc/rc_controls.h"
47 #include "fc/rc_modes.h"
48 #include "fc/runtime_config.h"
50 #include "flight/failsafe.h"
51 #include "flight/mixer.h"
52 #include "flight/servos.h"
53 #include "flight/pid.h"
54 #include "flight/imu.h"
56 #include "io/ledstrip.h"
57 #include "io/beeper.h"
58 #include "io/serial.h"
61 #include "navigation/navigation.h"
64 #include "sensors/acceleration.h"
65 #include "sensors/diagnostics.h"
66 #include "sensors/barometer.h"
67 #include "sensors/battery.h"
68 #include "sensors/boardalignment.h"
69 #include "sensors/gyro.h"
70 #include "sensors/sensors.h"
71 #include "sensors/pitotmeter.h"
73 #include "telemetry/telemetry.h"
76 PG_REGISTER_WITH_RESET_FN(ledStripConfig_t
, ledStripConfig
, PG_LED_STRIP_CONFIG
, 1);
78 static bool ledStripInitialised
= false;
79 static bool ledStripEnabled
= true;
81 static void ledStripDisable(void);
83 //#define USE_LED_ANIMATION
85 #define LED_STRIP_HZ(hz) ((int32_t)((1000 * 1000) / (hz)))
86 #define LED_STRIP_MS(ms) ((int32_t)(1000 * (ms)))
88 #if LED_MAX_STRIP_LENGTH > WS2811_LED_STRIP_LENGTH
89 # error "Led strip length must match driver"
92 const hsvColor_t hsv
[] = {
94 [COLOR_BLACK
] = { 0, 0, 0},
95 [COLOR_WHITE
] = { 0, 255, 255},
96 [COLOR_RED
] = { 0, 0, 255},
97 [COLOR_ORANGE
] = { 30, 0, 255},
98 [COLOR_YELLOW
] = { 60, 0, 255},
99 [COLOR_LIME_GREEN
] = { 90, 0, 255},
100 [COLOR_GREEN
] = {120, 0, 255},
101 [COLOR_MINT_GREEN
] = {150, 0, 255},
102 [COLOR_CYAN
] = {180, 0, 255},
103 [COLOR_LIGHT_BLUE
] = {210, 0, 255},
104 [COLOR_BLUE
] = {240, 0, 255},
105 [COLOR_DARK_VIOLET
] = {270, 0, 255},
106 [COLOR_MAGENTA
] = {300, 0, 255},
107 [COLOR_DEEP_PINK
] = {330, 0, 255},
109 // macro to save typing on default colors
110 #define HSV(color) (hsv[COLOR_ ## color])
112 STATIC_UNIT_TESTED
uint8_t ledGridWidth
;
113 STATIC_UNIT_TESTED
uint8_t ledGridHeight
;
115 STATIC_UNIT_TESTED
uint8_t highestYValueForNorth
;
116 STATIC_UNIT_TESTED
uint8_t lowestYValueForSouth
;
117 STATIC_UNIT_TESTED
uint8_t highestXValueForWest
;
118 STATIC_UNIT_TESTED
uint8_t lowestXValueForEast
;
120 STATIC_UNIT_TESTED ledCounts_t ledCounts
;
122 static const modeColorIndexes_t defaultModeColors
[] = {
123 // NORTH EAST SOUTH WEST UP DOWN
124 [LED_MODE_ORIENTATION
] = {{ COLOR_WHITE
, COLOR_DARK_VIOLET
, COLOR_RED
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
125 [LED_MODE_HEADFREE
] = {{ COLOR_LIME_GREEN
, COLOR_DARK_VIOLET
, COLOR_ORANGE
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
126 [LED_MODE_HORIZON
] = {{ COLOR_BLUE
, COLOR_DARK_VIOLET
, COLOR_YELLOW
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
127 [LED_MODE_ANGLE
] = {{ COLOR_CYAN
, COLOR_DARK_VIOLET
, COLOR_YELLOW
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
128 [LED_MODE_MAG
] = {{ COLOR_MINT_GREEN
, COLOR_DARK_VIOLET
, COLOR_ORANGE
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
129 [LED_MODE_BARO
] = {{ COLOR_LIGHT_BLUE
, COLOR_DARK_VIOLET
, COLOR_RED
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
132 static const specialColorIndexes_t defaultSpecialColors
[] = {
133 {{ [LED_SCOLOR_DISARMED
] = COLOR_GREEN
,
134 [LED_SCOLOR_ARMED
] = COLOR_BLUE
,
135 [LED_SCOLOR_ANIMATION
] = COLOR_WHITE
,
136 [LED_SCOLOR_BACKGROUND
] = COLOR_BLACK
,
137 [LED_SCOLOR_BLINKBACKGROUND
] = COLOR_BLACK
,
138 [LED_SCOLOR_GPSNOSATS
] = COLOR_RED
,
139 [LED_SCOLOR_GPSNOLOCK
] = COLOR_ORANGE
,
140 [LED_SCOLOR_GPSLOCKED
] = COLOR_GREEN
,
141 [LED_SCOLOR_STROBE
] = COLOR_WHITE
,
145 void pgResetFn_ledStripConfig(ledStripConfig_t
*instance
)
147 memset(instance
->ledConfigs
, 0, LED_MAX_STRIP_LENGTH
* sizeof(ledConfig_t
));
148 // copy hsv colors as default
149 memset(instance
->colors
, 0, ARRAYLEN(hsv
) * sizeof(hsvColor_t
));
150 BUILD_BUG_ON(LED_CONFIGURABLE_COLOR_COUNT
< ARRAYLEN(hsv
));
151 for (unsigned colorIndex
= 0; colorIndex
< ARRAYLEN(hsv
); colorIndex
++) {
152 instance
->colors
[colorIndex
] = hsv
[colorIndex
];
154 memcpy_fn(&instance
->modeColors
, &defaultModeColors
, sizeof(defaultModeColors
));
155 memcpy_fn(&instance
->specialColors
, &defaultSpecialColors
, sizeof(defaultSpecialColors
));
158 static int scaledThrottle
;
160 static void updateLedRingCounts(void);
162 STATIC_UNIT_TESTED
void determineLedStripDimensions(void)
167 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
168 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
170 maxX
= MAX(ledGetX(ledConfig
), maxX
);
171 maxY
= MAX(ledGetY(ledConfig
), maxY
);
173 ledGridWidth
= maxX
+ 1;
174 ledGridHeight
= maxY
+ 1;
177 STATIC_UNIT_TESTED
void determineOrientationLimits(void)
179 highestYValueForNorth
= MIN((ledGridHeight
/ 2) - 1, 0);
180 lowestYValueForSouth
= (ledGridHeight
+ 1) / 2;
181 highestXValueForWest
= MIN((ledGridWidth
/ 2) - 1, 0);
182 lowestXValueForEast
= (ledGridWidth
+ 1) / 2;
185 STATIC_UNIT_TESTED
void updateLedCount(void)
187 int count
= 0, countRing
= 0, countScanner
= 0;
189 const ledConfig_t configNotSet
= {};
190 for (int ledIndex
= 0; ledIndex
< LED_MAX_STRIP_LENGTH
; ledIndex
++) {
191 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
193 if (!memcmp(ledConfig
, &configNotSet
, sizeof(ledConfig_t
)))
198 if (ledGetFunction(ledConfig
) == LED_FUNCTION_THRUST_RING
)
201 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_LARSON_SCANNER
))
205 ledCounts
.count
= count
;
206 ledCounts
.ring
= countRing
;
207 ledCounts
.larson
= countScanner
;
210 void reevaluateLedConfig(void)
213 determineLedStripDimensions();
214 determineOrientationLimits();
215 updateLedRingCounts();
218 // get specialColor by index
219 static const hsvColor_t
* getSC(ledSpecialColorIds_e index
)
221 return &ledStripConfig()->colors
[ledStripConfig()->specialColors
.color
[index
]];
224 static const char directionCodes
[LED_DIRECTION_COUNT
] = { 'N', 'E', 'S', 'W', 'U', 'D' };
225 static const char baseFunctionCodes
[LED_BASEFUNCTION_COUNT
] = { 'C', 'F', 'A', 'L', 'S', 'G', 'R', 'H' };
226 static const char overlayCodes
[LED_OVERLAY_COUNT
] = { 'T', 'O', 'B', 'N', 'I', 'W', 'E' };
228 #define CHUNK_BUFFER_SIZE 11
230 bool parseLedStripConfig(int ledIndex
, const char *config
)
232 if (ledIndex
>= LED_MAX_STRIP_LENGTH
)
243 static const char chunkSeparators
[PARSE_STATE_COUNT
] = {',', ':', ':',':', '\0'};
245 ledConfig_t
*ledConfig
= &ledStripConfigMutable()->ledConfigs
[ledIndex
];
246 memset(ledConfig
, 0, sizeof(ledConfig_t
));
248 int x
= 0, y
= 0, color
= 0; // initialize to prevent warnings
249 int baseFunction
= 0;
250 int overlay_flags
= 0;
251 int direction_flags
= 0;
253 for (enum parseState_e parseState
= 0; parseState
< PARSE_STATE_COUNT
; parseState
++) {
254 char chunk
[CHUNK_BUFFER_SIZE
];
256 char chunkSeparator
= chunkSeparators
[parseState
];
258 while (*config
&& *config
!= chunkSeparator
&& chunkIndex
< (CHUNK_BUFFER_SIZE
- 1)) {
259 chunk
[chunkIndex
++] = *config
++;
261 chunk
[chunkIndex
++] = 0; // zero-terminate chunk
262 if (*config
!= chunkSeparator
) {
265 config
++; // skip separator
267 switch (parseState
) {
275 for (char *ch
= chunk
; *ch
; ch
++) {
276 for (ledDirectionId_e dir
= 0; dir
< LED_DIRECTION_COUNT
; dir
++) {
277 if (directionCodes
[dir
] == *ch
) {
278 direction_flags
|= LED_FLAG_DIRECTION(dir
);
285 for (char *ch
= chunk
; *ch
; ch
++) {
286 for (ledBaseFunctionId_e fn
= 0; fn
< LED_BASEFUNCTION_COUNT
; fn
++) {
287 if (baseFunctionCodes
[fn
] == *ch
) {
293 for (ledOverlayId_e ol
= 0; ol
< LED_OVERLAY_COUNT
; ol
++) {
294 if (overlayCodes
[ol
] == *ch
) {
295 overlay_flags
|= LED_FLAG_OVERLAY(ol
);
302 color
= fastA2I(chunk
);
303 if (color
>= LED_CONFIGURABLE_COLOR_COUNT
)
306 case PARSE_STATE_COUNT
:; // prevent warning
310 DEFINE_LED(ledConfig
, x
, y
, color
, direction_flags
, baseFunction
, overlay_flags
, 0);
312 reevaluateLedConfig();
317 void generateLedConfig(ledConfig_t
*ledConfig
, char *ledConfigBuffer
, size_t bufferSize
)
319 char directions
[LED_DIRECTION_COUNT
+ 1];
320 char baseFunctionOverlays
[LED_OVERLAY_COUNT
+ 2];
322 memset(ledConfigBuffer
, 0, bufferSize
);
324 char *dptr
= directions
;
325 for (ledDirectionId_e dir
= 0; dir
< LED_DIRECTION_COUNT
; dir
++) {
326 if (ledGetDirectionBit(ledConfig
, dir
)) {
327 *dptr
++ = directionCodes
[dir
];
332 char *fptr
= baseFunctionOverlays
;
333 *fptr
++ = baseFunctionCodes
[ledGetFunction(ledConfig
)];
335 for (ledOverlayId_e ol
= 0; ol
< LED_OVERLAY_COUNT
; ol
++) {
336 if (ledGetOverlayBit(ledConfig
, ol
)) {
337 *fptr
++ = overlayCodes
[ol
];
342 // TODO - check buffer length
343 tfp_sprintf(ledConfigBuffer
, "%u,%u:%s:%s:%u", ledGetX(ledConfig
), ledGetY(ledConfig
), directions
, baseFunctionOverlays
, ledGetColor(ledConfig
));
347 // the ordering is important, see below how NSEW is mapped to NE/SE/NW/SW
348 QUADRANT_NORTH
= 1 << 0,
349 QUADRANT_SOUTH
= 1 << 1,
350 QUADRANT_EAST
= 1 << 2,
351 QUADRANT_WEST
= 1 << 3,
352 QUADRANT_NORTH_EAST
= 1 << 4,
353 QUADRANT_SOUTH_EAST
= 1 << 5,
354 QUADRANT_NORTH_WEST
= 1 << 6,
355 QUADRANT_SOUTH_WEST
= 1 << 7,
356 QUADRANT_NONE
= 1 << 8,
357 QUADRANT_NOTDIAG
= 1 << 9, // not in NE/SE/NW/SW
359 QUADRANT_ANY
= QUADRANT_NORTH
| QUADRANT_SOUTH
| QUADRANT_EAST
| QUADRANT_WEST
| QUADRANT_NONE
,
362 static quadrant_e
getLedQuadrant(const int ledIndex
)
364 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
366 int x
= ledGetX(ledConfig
);
367 int y
= ledGetY(ledConfig
);
370 if (y
<= highestYValueForNorth
)
371 quad
|= QUADRANT_NORTH
;
372 else if (y
>= lowestYValueForSouth
)
373 quad
|= QUADRANT_SOUTH
;
374 if (x
>= lowestXValueForEast
)
375 quad
|= QUADRANT_EAST
;
376 else if (x
<= highestXValueForWest
)
377 quad
|= QUADRANT_WEST
;
379 if ((quad
& (QUADRANT_NORTH
| QUADRANT_SOUTH
))
380 && (quad
& (QUADRANT_EAST
| QUADRANT_WEST
)) ) { // is led in one of NE/SE/NW/SW?
381 quad
|= 1 << (4 + ((quad
& QUADRANT_SOUTH
) ? 1 : 0) + ((quad
& QUADRANT_WEST
) ? 2 : 0));
383 quad
|= QUADRANT_NOTDIAG
;
386 if ((quad
& (QUADRANT_NORTH
| QUADRANT_SOUTH
| QUADRANT_EAST
| QUADRANT_WEST
)) == 0)
387 quad
|= QUADRANT_NONE
;
392 static const struct {
393 uint8_t dir
; // ledDirectionId_e
394 uint16_t quadrantMask
; // quadrant_e
395 } directionQuadrantMap
[] = {
396 {LED_DIRECTION_SOUTH
, QUADRANT_SOUTH
},
397 {LED_DIRECTION_NORTH
, QUADRANT_NORTH
},
398 {LED_DIRECTION_EAST
, QUADRANT_EAST
},
399 {LED_DIRECTION_WEST
, QUADRANT_WEST
},
400 {LED_DIRECTION_DOWN
, QUADRANT_ANY
},
401 {LED_DIRECTION_UP
, QUADRANT_ANY
},
404 static const hsvColor_t
* getDirectionalModeColor(const int ledIndex
, const modeColorIndexes_t
*modeColors
)
406 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
408 quadrant_e quad
= getLedQuadrant(ledIndex
);
409 for (unsigned i
= 0; i
< ARRAYLEN(directionQuadrantMap
); i
++) {
410 ledDirectionId_e dir
= directionQuadrantMap
[i
].dir
;
411 quadrant_e quadMask
= directionQuadrantMap
[i
].quadrantMask
;
413 if (ledGetDirectionBit(ledConfig
, dir
) && (quad
& quadMask
))
414 return &ledStripConfig()->colors
[modeColors
->color
[dir
]];
419 // map flight mode to led mode, in order of priority
420 // flightMode == 0 is always active
421 static const struct {
424 } flightModeToLed
[] = {
425 {HEADFREE_MODE
, LED_MODE_HEADFREE
},
426 {HEADING_MODE
, LED_MODE_MAG
},
428 {NAV_ALTHOLD_MODE
, LED_MODE_BARO
},
430 {HORIZON_MODE
, LED_MODE_HORIZON
},
431 {ANGLE_MODE
, LED_MODE_ANGLE
},
432 {0, LED_MODE_ORIENTATION
},
435 static void applyLedFixedLayers(void)
437 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
438 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
439 hsvColor_t color
= *getSC(LED_SCOLOR_BACKGROUND
);
441 int fn
= ledGetFunction(ledConfig
);
442 int hOffset
= HSV_HUE_MAX
;
446 case LED_FUNCTION_COLOR
:
447 color
= ledStripConfig()->colors
[ledGetColor(ledConfig
)];
450 case LED_FUNCTION_FLIGHT_MODE
:
451 for (unsigned i
= 0; i
< ARRAYLEN(flightModeToLed
); i
++)
452 if (!flightModeToLed
[i
].flightMode
|| FLIGHT_MODE(flightModeToLed
[i
].flightMode
)) {
453 const hsvColor_t
*directionalColor
= getDirectionalModeColor(ledIndex
, &ledStripConfig()->modeColors
[flightModeToLed
[i
].ledMode
]);
454 if (directionalColor
) {
455 color
= *directionalColor
;
458 break; // stop on first match
462 case LED_FUNCTION_ARM_STATE
:
463 color
= ARMING_FLAG(ARMED
) ? *getSC(LED_SCOLOR_ARMED
) : *getSC(LED_SCOLOR_DISARMED
);
466 case LED_FUNCTION_BATTERY
:
468 hOffset
+= scaleRange(calculateBatteryPercentage(), 0, 100, -30, 120);
471 case LED_FUNCTION_RSSI
:
473 hOffset
+= scaleRange(getRSSI() * 100, 0, 1023, -30, 120);
476 case LED_FUNCTION_CHANNEL
:
477 channel
= ledGetColor(ledConfig
) - 1;
479 hOffset
= scaleRange(rxGetChannelValue(channel
), PWM_RANGE_MIN
, PWM_RANGE_MAX
, -1, 360);
480 // add black and white to range of colors
483 } else if (hOffset
> HSV_HUE_MAX
) {
492 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_THROTTLE
)) {
493 hOffset
+= ((scaledThrottle
- 10) * 4) / 3;
496 color
.h
= (color
.h
+ hOffset
) % (HSV_HUE_MAX
+ 1);
498 setLedHsv(ledIndex
, &color
);
503 static void applyLedHsv(uint32_t mask
, uint32_t ledOperation
, const hsvColor_t
*color
)
505 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
506 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
507 if ((*((uint32_t *)ledConfig
) & mask
) == ledOperation
)
508 setLedHsv(ledIndex
, color
);
513 WARNING_ARMING_DISABLED
,
519 static void applyLedWarningLayer(bool updateNow
, timeUs_t
*timer
)
521 static uint8_t warningFlashCounter
= 0;
522 static uint8_t warningFlags
= 0; // non-zero during blinks
525 // keep counter running, so it stays in sync with blink
526 warningFlashCounter
++;
527 warningFlashCounter
&= 0xF;
529 if (warningFlashCounter
== 0) { // update when old flags was processed
531 if (feature(FEATURE_VBAT
) && getBatteryState() != BATTERY_OK
)
532 warningFlags
|= 1 << WARNING_LOW_BATTERY
;
533 if (failsafeIsActive())
534 warningFlags
|= 1 << WARNING_FAILSAFE
;
535 if (!ARMING_FLAG(ARMED
) && isArmingDisabled())
536 warningFlags
|= 1 << WARNING_ARMING_DISABLED
;
537 if (!isHardwareHealthy())
538 warningFlags
|= 1 << WARNING_HW_ERROR
;
540 *timer
+= LED_STRIP_HZ(10);
544 const hsvColor_t
*warningColor
= NULL
;
546 bool colorOn
= (warningFlashCounter
% 2) == 0; // w_w_
547 warningFlags_e warningId
= warningFlashCounter
/ 4;
548 if (warningFlags
& (1 << warningId
)) {
550 case WARNING_ARMING_DISABLED
:
551 warningColor
= colorOn
? &HSV(GREEN
) : &HSV(BLACK
);
553 case WARNING_LOW_BATTERY
:
554 warningColor
= colorOn
? &HSV(RED
) : &HSV(BLACK
);
556 case WARNING_FAILSAFE
:
557 warningColor
= colorOn
? &HSV(YELLOW
) : &HSV(BLUE
);
559 case WARNING_HW_ERROR
:
560 warningColor
= colorOn
? &HSV(BLUE
) : &HSV(BLACK
);
566 applyLedHsv(LED_OVERLAY_MASK
, LED_MOV_OVERLAY(LED_FLAG_OVERLAY(LED_OVERLAY_WARNING
)), warningColor
);
570 static void applyLedBatteryLayer(bool updateNow
, timeUs_t
*timer
)
572 static bool flash
= false;
578 state
= getBatteryState();
585 case BATTERY_WARNING
:
594 *timer
+= LED_STRIP_HZ(timeOffset
);
599 const hsvColor_t
*bgc
= getSC(LED_SCOLOR_BACKGROUND
);
600 applyLedHsv(LED_FUNCTION_MASK
, LED_MOV_FUNCTION(LED_FUNCTION_BATTERY
), bgc
);
604 static void applyLedRssiLayer(bool updateNow
, timeUs_t
*timer
)
606 static bool flash
= false;
612 state
= (getRSSI() * 100) / 1023;
617 } else if (state
> 20) {
624 *timer
+= LED_STRIP_HZ(timeOffset
);
629 const hsvColor_t
*bgc
= getSC(LED_SCOLOR_BACKGROUND
);
630 applyLedHsv(LED_FUNCTION_MASK
, LED_MOV_FUNCTION(LED_FUNCTION_RSSI
), bgc
);
635 static void applyLedGpsLayer(bool updateNow
, timeUs_t
*timer
)
637 static uint8_t gpsFlashCounter
= 0;
638 static uint8_t gpsPauseCounter
= 0;
639 const uint8_t blinkPauseLength
= 4;
642 if (gpsPauseCounter
> 0) {
644 } else if (gpsFlashCounter
>= (gpsSol
.numSat
- 1)) {
646 gpsPauseCounter
= blinkPauseLength
;
651 *timer
+= LED_STRIP_HZ(2.5);
654 const hsvColor_t
*gpsColor
;
656 if (gpsSol
.numSat
== 0 || !sensors(SENSOR_GPS
)) {
657 gpsColor
= getSC(LED_SCOLOR_GPSNOSATS
);
659 bool colorOn
= gpsPauseCounter
== 0; // each interval starts with pause
660 if (STATE(GPS_FIX
)) {
661 gpsColor
= colorOn
? getSC(LED_SCOLOR_GPSLOCKED
) : getSC(LED_SCOLOR_BACKGROUND
);
663 gpsColor
= colorOn
? getSC(LED_SCOLOR_GPSNOLOCK
) : getSC(LED_SCOLOR_GPSNOSATS
);
667 applyLedHsv(LED_FUNCTION_MASK
, LED_MOV_FUNCTION(LED_FUNCTION_GPS
), gpsColor
);
672 #define INDICATOR_DEADBAND 25
674 static void applyLedIndicatorLayer(bool updateNow
, timeUs_t
*timer
)
676 static bool flash
= 0;
679 if (rxIsReceivingSignal()) {
680 // calculate update frequency
681 int scale
= (STATE(AIRPLANE
) || STATE(ROVER
)) ? ABS(rcCommand
[ROLL
]) : MAX(ABS(rcCommand
[ROLL
]), ABS(rcCommand
[PITCH
])); // 0 - 500
682 scale
+= (50 - INDICATOR_DEADBAND
); // start increasing frequency right after deadband
683 *timer
+= LED_STRIP_HZ(5) * 50 / MAX(50, scale
); // 5 - 50Hz update, 2.5 - 25Hz blink
687 *timer
+= LED_STRIP_HZ(5); // try again soon
694 const hsvColor_t
*flashColor
= &HSV(ORANGE
); // TODO - use user color?
696 if (STATE(AIRPLANE
) || STATE(ROVER
)) {
697 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
698 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
699 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_INDICATOR
)) {
700 if (((rcCommand
[ROLL
] > INDICATOR_DEADBAND
) && (ledGetX(ledConfig
) >= 8)) || ((rcCommand
[ROLL
] < -INDICATOR_DEADBAND
) && (ledGetX(ledConfig
) < 8)))
701 setLedHsv(ledIndex
, flashColor
);
705 quadrant_e quadrants
= 0;
706 if (rcCommand
[ROLL
] > INDICATOR_DEADBAND
) {
707 quadrants
|= QUADRANT_NORTH_EAST
| QUADRANT_SOUTH_EAST
;
708 } else if (rcCommand
[ROLL
] < -INDICATOR_DEADBAND
) {
709 quadrants
|= QUADRANT_NORTH_WEST
| QUADRANT_SOUTH_WEST
;
711 if (rcCommand
[PITCH
] > INDICATOR_DEADBAND
) {
712 quadrants
|= QUADRANT_NORTH_EAST
| QUADRANT_NORTH_WEST
;
713 } else if (rcCommand
[PITCH
] < -INDICATOR_DEADBAND
) {
714 quadrants
|= QUADRANT_SOUTH_EAST
| QUADRANT_SOUTH_WEST
;
717 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
718 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
719 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_INDICATOR
)) {
720 if (getLedQuadrant(ledIndex
) & quadrants
)
721 setLedHsv(ledIndex
, flashColor
);
727 #define ROTATION_SEQUENCE_LED_COUNT 6 // 2 on, 4 off
728 #define ROTATION_SEQUENCE_LED_WIDTH 2 // 2 on
730 static void updateLedRingCounts(void)
733 // try to split in segments/rings of exactly ROTATION_SEQUENCE_LED_COUNT leds
734 if ((ledCounts
.ring
% ROTATION_SEQUENCE_LED_COUNT
) == 0) {
735 seqLen
= ROTATION_SEQUENCE_LED_COUNT
;
737 seqLen
= ledCounts
.ring
;
738 // else split up in equal segments/rings of at most ROTATION_SEQUENCE_LED_COUNT leds
739 // TODO - improve partitioning (15 leds -> 3x5)
740 while ((seqLen
> ROTATION_SEQUENCE_LED_COUNT
) && ((seqLen
% 2) == 0)) {
744 ledCounts
.ringSeqLen
= seqLen
;
747 static void applyLedThrustRingLayer(bool updateNow
, timeUs_t
*timer
)
749 static uint8_t rotationPhase
;
750 int ledRingIndex
= 0;
753 rotationPhase
= rotationPhase
> 0 ? rotationPhase
- 1 : ledCounts
.ringSeqLen
- 1;
755 int scale
= scaledThrottle
; // ARMING_FLAG(ARMED) ? scaleRange(rxGetChannelValue(THROTTLE), PWM_RANGE_MIN, PWM_RANGE_MAX, 10, 100) : 10;
756 *timer
+= LED_STRIP_HZ(5) * 10 / scale
; // 5 - 50Hz update rate
759 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
760 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
761 if (ledGetFunction(ledConfig
) == LED_FUNCTION_THRUST_RING
) {
764 if (ARMING_FLAG(ARMED
)) {
765 applyColor
= (ledRingIndex
+ rotationPhase
) % ledCounts
.ringSeqLen
< ROTATION_SEQUENCE_LED_WIDTH
;
767 applyColor
= !(ledRingIndex
% 2); // alternating pattern
771 const hsvColor_t
*ringColor
= &ledStripConfig()->colors
[ledGetColor(ledConfig
)];
772 setLedHsv(ledIndex
, ringColor
);
780 typedef struct larsonParameters_s
{
781 uint8_t currentBrightness
;
784 } larsonParameters_t
;
786 static int brightnessForLarsonIndex(larsonParameters_t
*larsonParameters
, uint8_t larsonIndex
)
788 int offset
= larsonIndex
- larsonParameters
->currentIndex
;
789 static const int larsonLowValue
= 8;
792 return (larsonLowValue
);
795 return (larsonParameters
->currentBrightness
);
797 if (larsonParameters
->direction
== offset
) {
798 return (larsonParameters
->currentBrightness
- 127);
801 return (255 - larsonParameters
->currentBrightness
);
805 static void larsonScannerNextStep(larsonParameters_t
*larsonParameters
, int delta
)
807 if (larsonParameters
->currentBrightness
> (255 - delta
)) {
808 larsonParameters
->currentBrightness
= 127;
809 if (larsonParameters
->currentIndex
>= ledCounts
.larson
|| larsonParameters
->currentIndex
< 0) {
810 larsonParameters
->direction
= -larsonParameters
->direction
;
812 larsonParameters
->currentIndex
+= larsonParameters
->direction
;
814 larsonParameters
->currentBrightness
+= delta
;
818 static void applyLarsonScannerLayer(bool updateNow
, timeUs_t
*timer
)
820 static larsonParameters_t larsonParameters
= { 0, 0, 1 };
823 larsonScannerNextStep(&larsonParameters
, 15);
824 *timer
+= LED_STRIP_HZ(60);
827 int scannerLedIndex
= 0;
828 for (unsigned i
= 0; i
< ledCounts
.count
; i
++) {
830 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[i
];
832 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_LARSON_SCANNER
)) {
834 getLedHsv(i
, &ledColor
);
835 ledColor
.v
= brightnessForLarsonIndex(&larsonParameters
, scannerLedIndex
);
836 setLedHsv(i
, &ledColor
);
842 // blink twice, then wait ; either always or just when landing
843 static void applyLedBlinkLayer(bool updateNow
, timeUs_t
*timer
)
845 const uint16_t blinkPattern
= 0x8005; // 0b1000000000000101;
846 static uint16_t blinkMask
;
849 blinkMask
= blinkMask
>> 1;
851 blinkMask
= blinkPattern
;
853 *timer
+= LED_STRIP_HZ(10);
856 bool ledOn
= (blinkMask
& 1); // b_b_____...
857 for (int i
= 0; i
< ledCounts
.count
; ++i
) {
858 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[i
];
861 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_STROBE
)) {
862 setLedHsv(i
, getSC(LED_SCOLOR_STROBE
));
865 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_BLINK
) ||
866 (ledGetOverlayBit(ledConfig
, LED_OVERLAY_LANDING_FLASH
) && scaledThrottle
< 55 && scaledThrottle
> 10)) {
867 setLedHsv(i
, getSC(LED_SCOLOR_BLINKBACKGROUND
));
873 #ifdef USE_LED_ANIMATION
874 static void applyLedAnimationLayer(bool updateNow
, timeUs_t
*timer
)
876 static uint8_t frameCounter
= 0;
877 const int animationFrames
= ledGridHeight
;
879 frameCounter
= (frameCounter
+ 1 < animationFrames
) ? frameCounter
+ 1 : 0;
880 *timer
+= LED_STRIP_HZ(20);
883 if (ARMING_FLAG(ARMED
))
886 int previousRow
= frameCounter
> 0 ? frameCounter
- 1 : animationFrames
- 1;
887 int currentRow
= frameCounter
;
888 int nextRow
= (frameCounter
+ 1 < animationFrames
) ? frameCounter
+ 1 : 0;
890 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
891 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
893 if (ledGetY(ledConfig
) == previousRow
) {
894 setLedHsv(ledIndex
, getSC(LED_SCOLOR_ANIMATION
));
895 scaleLedValue(ledIndex
, 50);
896 } else if (ledGetY(ledConfig
) == currentRow
) {
897 setLedHsv(ledIndex
, getSC(LED_SCOLOR_ANIMATION
));
898 } else if (ledGetY(ledConfig
) == nextRow
) {
899 scaleLedValue(ledIndex
, 50);
915 #ifdef USE_LED_ANIMATION
922 static timeUs_t timerVal
[timTimerCount
];
924 // function to apply layer.
925 // function must replan self using timer pointer
926 // when updateNow is true (timer triggered), state must be updated first,
927 // before calculating led state. Otherwise update started by different trigger
928 // may modify LED state.
929 typedef void applyLayerFn_timed(bool updateNow
, timeUs_t
*timer
);
931 static applyLayerFn_timed
* layerTable
[timTimerCount
] = {
932 [timBlink
] = &applyLedBlinkLayer
,
933 [timLarson
] = &applyLarsonScannerLayer
,
934 [timBattery
] = &applyLedBatteryLayer
,
935 [timRssi
] = &applyLedRssiLayer
,
937 [timGps
] = &applyLedGpsLayer
,
939 [timWarning
] = &applyLedWarningLayer
,
940 [timIndicator
] = &applyLedIndicatorLayer
,
941 #ifdef USE_LED_ANIMATION
942 [timAnimation
] = &applyLedAnimationLayer
,
944 [timRing
] = &applyLedThrustRingLayer
947 void ledStripUpdate(timeUs_t currentTimeUs
)
949 if (!(ledStripInitialised
&& isWS2811LedStripReady())) {
953 if (IS_RC_MODE_ACTIVE(BOXLEDLOW
)) {
954 if (ledStripEnabled
) {
956 ledStripEnabled
= false;
960 ledStripEnabled
= true;
962 // test all led timers, setting corresponding bits
963 uint32_t timActive
= 0;
964 for (timId_e timId
= 0; timId
< timTimerCount
; timId
++) {
965 // sanitize timer value, so that it can be safely incremented. Handles inital timerVal value.
966 // max delay is limited to 5s
967 timeDelta_t delta
= cmpTimeUs(currentTimeUs
, timerVal
[timId
]);
968 if (delta
< 0 && delta
> -LED_STRIP_MS(5000))
969 continue; // not ready yet
970 timActive
|= 1 << timId
;
971 if (delta
>= LED_STRIP_MS(100) || delta
< 0) {
972 timerVal
[timId
] = currentTimeUs
;
977 return; // no change this update, keep old state
979 // apply all layers; triggered timed functions has to update timers
981 scaledThrottle
= ARMING_FLAG(ARMED
) ? scaleRange(rxGetChannelValue(THROTTLE
), PWM_RANGE_MIN
, PWM_RANGE_MAX
, 10, 100) : 10;
983 applyLedFixedLayers();
985 for (timId_e timId
= 0; timId
< ARRAYLEN(layerTable
); timId
++) {
986 timeUs_t
*timer
= &timerVal
[timId
];
987 bool updateNow
= timActive
& (1 << timId
);
988 (*layerTable
[timId
])(updateNow
, timer
);
993 bool parseColor(int index
, const char *colorConfig
)
995 const char *remainingCharacters
= colorConfig
;
997 hsvColor_t
*color
= &ledStripConfigMutable()->colors
[index
];
1000 static const uint16_t hsv_limit
[HSV_COLOR_COMPONENT_COUNT
] = {
1001 [HSV_HUE
] = HSV_HUE_MAX
,
1002 [HSV_SATURATION
] = HSV_SATURATION_MAX
,
1003 [HSV_VALUE
] = HSV_VALUE_MAX
,
1005 for (int componentIndex
= 0; result
&& componentIndex
< HSV_COLOR_COMPONENT_COUNT
; componentIndex
++) {
1006 int val
= fastA2I(remainingCharacters
);
1007 if (val
> hsv_limit
[componentIndex
]) {
1011 switch (componentIndex
) {
1015 case HSV_SATURATION
:
1022 remainingCharacters
= strchr(remainingCharacters
, ',');
1023 if (remainingCharacters
) {
1024 remainingCharacters
++; // skip separator
1026 if (componentIndex
< HSV_COLOR_COMPONENT_COUNT
- 1) {
1033 memset(color
, 0, sizeof(*color
));
1040 * Redefine a color in a mode.
1042 bool setModeColor(ledModeIndex_e modeIndex
, int modeColorIndex
, int colorIndex
)
1045 if (colorIndex
< 0 || colorIndex
>= LED_CONFIGURABLE_COLOR_COUNT
)
1047 if (modeIndex
< LED_MODE_COUNT
) { // modeIndex_e is unsigned, so one-sided test is enough
1048 if (modeColorIndex
< 0 || modeColorIndex
>= LED_DIRECTION_COUNT
)
1050 ledStripConfigMutable()->modeColors
[modeIndex
].color
[modeColorIndex
] = colorIndex
;
1051 } else if (modeIndex
== LED_SPECIAL
) {
1052 if (modeColorIndex
< 0 || modeColorIndex
>= LED_SPECIAL_COLOR_COUNT
)
1054 ledStripConfigMutable()->specialColors
.color
[modeColorIndex
] = colorIndex
;
1061 void ledStripInit(void)
1063 ledStripInitialised
= false;
1066 void ledStripEnable(void)
1068 reevaluateLedConfig();
1069 ledStripInitialised
= true;
1071 ws2811LedStripInit();
1074 static void ledStripDisable(void)
1076 setStripColor(&HSV(BLACK
));
1078 ws2811UpdateStrip();