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/color.h"
32 #include "common/maths.h"
33 #include "common/printf.h"
34 #include "common/typeconversion.h"
35 #include "common/utils.h"
37 #include "config/config_profile.h"
38 #include "config/feature.h"
39 #include "config/parameter_group.h"
40 #include "config/parameter_group_ids.h"
42 #include "drivers/light_ws2811strip.h"
43 #include "drivers/serial.h"
44 #include "drivers/system.h"
46 #include "fc/config.h"
47 #include "fc/rc_controls.h"
48 #include "fc/runtime_config.h"
50 #include "flight/failsafe.h"
51 #include "flight/imu.h"
52 #include "flight/mixer.h"
53 #include "flight/navigation.h"
54 #include "flight/pid.h"
55 #include "flight/servos.h"
57 #include "io/beeper.h"
58 #include "io/gimbal.h"
60 #include "io/ledstrip.h"
61 #include "io/serial.h"
65 #include "sensors/acceleration.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"
72 #include "telemetry/telemetry.h"
74 PG_REGISTER_WITH_RESET_FN(ledStripConfig_t
, ledStripConfig
, PG_LED_STRIP_CONFIG
, 0);
76 static bool ledStripInitialised
= false;
77 static bool ledStripEnabled
= true;
79 static void ledStripDisable(void);
81 //#define USE_LED_ANIMATION
83 #define HZ_TO_US(hz) ((int32_t)((1000 * 1000) / (hz)))
85 #define MAX_TIMER_DELAY (5 * 1000 * 1000)
87 #if LED_MAX_STRIP_LENGTH > WS2811_LED_STRIP_LENGTH
88 # error "Led strip length must match driver"
108 const hsvColor_t hsv
[] = {
110 [COLOR_BLACK
] = { 0, 0, 0},
111 [COLOR_WHITE
] = { 0, 255, 255},
112 [COLOR_RED
] = { 0, 0, 255},
113 [COLOR_ORANGE
] = { 30, 0, 255},
114 [COLOR_YELLOW
] = { 60, 0, 255},
115 [COLOR_LIME_GREEN
] = { 90, 0, 255},
116 [COLOR_GREEN
] = {120, 0, 255},
117 [COLOR_MINT_GREEN
] = {150, 0, 255},
118 [COLOR_CYAN
] = {180, 0, 255},
119 [COLOR_LIGHT_BLUE
] = {210, 0, 255},
120 [COLOR_BLUE
] = {240, 0, 255},
121 [COLOR_DARK_VIOLET
] = {270, 0, 255},
122 [COLOR_MAGENTA
] = {300, 0, 255},
123 [COLOR_DEEP_PINK
] = {330, 0, 255},
125 // macro to save typing on default colors
126 #define HSV(color) (hsv[COLOR_ ## color])
128 STATIC_UNIT_TESTED
uint8_t ledGridRows
;
130 STATIC_UNIT_TESTED
int8_t highestYValueForNorth
;
131 STATIC_UNIT_TESTED
int8_t lowestYValueForSouth
;
132 STATIC_UNIT_TESTED
int8_t highestXValueForWest
;
133 STATIC_UNIT_TESTED
int8_t lowestXValueForEast
;
135 STATIC_UNIT_TESTED ledCounts_t ledCounts
;
137 static const modeColorIndexes_t defaultModeColors
[] = {
138 // NORTH EAST SOUTH WEST UP DOWN
139 [LED_MODE_ORIENTATION
] = {{ COLOR_WHITE
, COLOR_DARK_VIOLET
, COLOR_RED
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
140 [LED_MODE_HEADFREE
] = {{ COLOR_LIME_GREEN
, COLOR_DARK_VIOLET
, COLOR_ORANGE
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
141 [LED_MODE_HORIZON
] = {{ COLOR_BLUE
, COLOR_DARK_VIOLET
, COLOR_YELLOW
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
142 [LED_MODE_ANGLE
] = {{ COLOR_CYAN
, COLOR_DARK_VIOLET
, COLOR_YELLOW
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
143 [LED_MODE_MAG
] = {{ COLOR_MINT_GREEN
, COLOR_DARK_VIOLET
, COLOR_ORANGE
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
144 [LED_MODE_BARO
] = {{ COLOR_LIGHT_BLUE
, COLOR_DARK_VIOLET
, COLOR_RED
, COLOR_DEEP_PINK
, COLOR_BLUE
, COLOR_ORANGE
}},
147 static const specialColorIndexes_t defaultSpecialColors
[] = {
148 {{ [LED_SCOLOR_DISARMED
] = COLOR_GREEN
,
149 [LED_SCOLOR_ARMED
] = COLOR_BLUE
,
150 [LED_SCOLOR_ANIMATION
] = COLOR_WHITE
,
151 [LED_SCOLOR_BACKGROUND
] = COLOR_BLACK
,
152 [LED_SCOLOR_BLINKBACKGROUND
] = COLOR_BLACK
,
153 [LED_SCOLOR_GPSNOSATS
] = COLOR_RED
,
154 [LED_SCOLOR_GPSNOLOCK
] = COLOR_ORANGE
,
155 [LED_SCOLOR_GPSLOCKED
] = COLOR_GREEN
,
159 void pgResetFn_ledStripConfig(ledStripConfig_t
*ledStripConfig
)
161 memset(ledStripConfig
->ledConfigs
, 0, LED_MAX_STRIP_LENGTH
* sizeof(ledConfig_t
));
162 // copy hsv colors as default
163 memset(ledStripConfig
->colors
, 0, ARRAYLEN(hsv
) * sizeof(hsvColor_t
));
164 BUILD_BUG_ON(LED_CONFIGURABLE_COLOR_COUNT
< ARRAYLEN(hsv
));
165 for (unsigned colorIndex
= 0; colorIndex
< ARRAYLEN(hsv
); colorIndex
++) {
166 ledStripConfig
->colors
[colorIndex
] = hsv
[colorIndex
];
168 memcpy_fn(&ledStripConfig
->modeColors
, &defaultModeColors
, sizeof(defaultModeColors
));
169 memcpy_fn(&ledStripConfig
->specialColors
, &defaultSpecialColors
, sizeof(defaultSpecialColors
));
170 ledStripConfig
->ledstrip_visual_beeper
= 0;
171 ledStripConfig
->ledstrip_aux_channel
= THROTTLE
;
173 for (int i
= 0; i
< USABLE_TIMER_CHANNEL_COUNT
; i
++) {
174 if (timerHardware
[i
].usageFlags
& TIM_USE_LED
) {
175 ledStripConfig
->ioTag
= timerHardware
[i
].tag
;
179 ledStripConfig
->ioTag
= IO_TAG_NONE
;
182 static int scaledThrottle
;
183 static int scaledAux
;
185 static void updateLedRingCounts(void);
187 STATIC_UNIT_TESTED
void updateDimensions(void)
190 int minX
= LED_XY_MASK
;
192 int minY
= LED_XY_MASK
;
194 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
195 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
197 int ledX
= ledGetX(ledConfig
);
198 maxX
= MAX(ledX
, maxX
);
199 minX
= MIN(ledX
, minX
);
200 int ledY
= ledGetY(ledConfig
);
201 maxY
= MAX(ledY
, maxY
);
202 minY
= MIN(ledY
, minY
);
205 ledGridRows
= maxY
- minY
+ 1;
208 lowestXValueForEast
= (minX
+ maxX
) / 2 + 1;
209 highestXValueForWest
= (minX
+ maxX
- 1) / 2;
211 lowestXValueForEast
= LED_XY_MASK
/ 2;
212 highestXValueForWest
= lowestXValueForEast
- 1;
215 lowestYValueForSouth
= (minY
+ maxY
) / 2 + 1;
216 highestYValueForNorth
= (minY
+ maxY
- 1) / 2;
218 lowestYValueForSouth
= LED_XY_MASK
/ 2;
219 highestYValueForNorth
= lowestYValueForSouth
- 1;
224 STATIC_UNIT_TESTED
void updateLedCount(void)
226 int count
= 0, countRing
= 0, countScanner
= 0;
228 for (int ledIndex
= 0; ledIndex
< LED_MAX_STRIP_LENGTH
; ledIndex
++) {
229 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
236 if (ledGetFunction(ledConfig
) == LED_FUNCTION_THRUST_RING
)
239 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_LARSON_SCANNER
))
243 ledCounts
.count
= count
;
244 ledCounts
.ring
= countRing
;
245 ledCounts
.larson
= countScanner
;
248 void reevaluateLedConfig(void)
252 updateLedRingCounts();
255 // get specialColor by index
256 static const hsvColor_t
* getSC(ledSpecialColorIds_e index
)
258 return &ledStripConfig()->colors
[ledStripConfig()->specialColors
.color
[index
]];
261 static const char directionCodes
[LED_DIRECTION_COUNT
] = { 'N', 'E', 'S', 'W', 'U', 'D' };
262 static const char baseFunctionCodes
[LED_BASEFUNCTION_COUNT
] = { 'C', 'F', 'A', 'L', 'S', 'G', 'R' };
263 static const char overlayCodes
[LED_OVERLAY_COUNT
] = { 'T', 'O', 'B', 'N', 'I', 'W' };
265 #define CHUNK_BUFFER_SIZE 11
267 bool parseLedStripConfig(int ledIndex
, const char *config
)
269 if (ledIndex
>= LED_MAX_STRIP_LENGTH
)
280 static const char chunkSeparators
[PARSE_STATE_COUNT
] = {',', ':', ':', ':', '\0'};
282 ledConfig_t
*ledConfig
= &ledStripConfigMutable()->ledConfigs
[ledIndex
];
283 memset(ledConfig
, 0, sizeof(ledConfig_t
));
285 int x
= 0, y
= 0, color
= 0; // initialize to prevent warnings
286 int baseFunction
= 0;
287 int overlay_flags
= 0;
288 int direction_flags
= 0;
290 for (enum parseState_e parseState
= 0; parseState
< PARSE_STATE_COUNT
; parseState
++) {
291 char chunk
[CHUNK_BUFFER_SIZE
];
293 char chunkSeparator
= chunkSeparators
[parseState
];
295 while (*config
&& *config
!= chunkSeparator
&& chunkIndex
< (CHUNK_BUFFER_SIZE
- 1)) {
296 chunk
[chunkIndex
++] = *config
++;
298 chunk
[chunkIndex
++] = 0; // zero-terminate chunk
299 if (*config
!= chunkSeparator
) {
302 config
++; // skip separator
304 switch (parseState
) {
312 for (char *ch
= chunk
; *ch
; ch
++) {
313 for (ledDirectionId_e dir
= 0; dir
< LED_DIRECTION_COUNT
; dir
++) {
314 if (directionCodes
[dir
] == *ch
) {
315 direction_flags
|= LED_FLAG_DIRECTION(dir
);
322 for (char *ch
= chunk
; *ch
; ch
++) {
323 for (ledBaseFunctionId_e fn
= 0; fn
< LED_BASEFUNCTION_COUNT
; fn
++) {
324 if (baseFunctionCodes
[fn
] == *ch
) {
330 for (ledOverlayId_e ol
= 0; ol
< LED_OVERLAY_COUNT
; ol
++) {
331 if (overlayCodes
[ol
] == *ch
) {
332 overlay_flags
|= LED_FLAG_OVERLAY(ol
);
340 if (color
>= LED_CONFIGURABLE_COLOR_COUNT
)
343 case PARSE_STATE_COUNT
:; // prevent warning
347 *ledConfig
= DEFINE_LED(x
, y
, color
, direction_flags
, baseFunction
, overlay_flags
, 0);
349 reevaluateLedConfig();
354 void generateLedConfig(ledConfig_t
*ledConfig
, char *ledConfigBuffer
, size_t bufferSize
)
356 char directions
[LED_DIRECTION_COUNT
+ 1];
357 char baseFunctionOverlays
[LED_OVERLAY_COUNT
+ 2];
359 memset(ledConfigBuffer
, 0, bufferSize
);
361 char *dptr
= directions
;
362 for (ledDirectionId_e dir
= 0; dir
< LED_DIRECTION_COUNT
; dir
++) {
363 if (ledGetDirectionBit(ledConfig
, dir
)) {
364 *dptr
++ = directionCodes
[dir
];
369 char *fptr
= baseFunctionOverlays
;
370 *fptr
++ = baseFunctionCodes
[ledGetFunction(ledConfig
)];
372 for (ledOverlayId_e ol
= 0; ol
< LED_OVERLAY_COUNT
; ol
++) {
373 if (ledGetOverlayBit(ledConfig
, ol
)) {
374 *fptr
++ = overlayCodes
[ol
];
379 // TODO - check buffer length
380 sprintf(ledConfigBuffer
, "%u,%u:%s:%s:%u", ledGetX(ledConfig
), ledGetY(ledConfig
), directions
, baseFunctionOverlays
, ledGetColor(ledConfig
));
384 // the ordering is important, see below how NSEW is mapped to NE/SE/NW/SW
385 QUADRANT_NORTH
= 1 << 0,
386 QUADRANT_SOUTH
= 1 << 1,
387 QUADRANT_EAST
= 1 << 2,
388 QUADRANT_WEST
= 1 << 3,
391 static quadrant_e
getLedQuadrant(const int ledIndex
)
393 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
395 int x
= ledGetX(ledConfig
);
396 int y
= ledGetY(ledConfig
);
399 if (y
<= highestYValueForNorth
)
400 quad
|= QUADRANT_NORTH
;
401 else if (y
>= lowestYValueForSouth
)
402 quad
|= QUADRANT_SOUTH
;
403 if (x
>= lowestXValueForEast
)
404 quad
|= QUADRANT_EAST
;
405 else if (x
<= highestXValueForWest
)
406 quad
|= QUADRANT_WEST
;
411 static hsvColor_t
* getDirectionalModeColor(const int ledIndex
, const modeColorIndexes_t
*modeColors
)
413 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
414 const int ledDirection
= ledGetDirection(ledConfig
);
416 for (unsigned i
= 0; i
< LED_DIRECTION_COUNT
; i
++) {
417 if (ledDirection
& (1 << i
)) {
418 return &ledStripConfigMutable()->colors
[modeColors
->color
[i
]];
425 // map flight mode to led mode, in order of priority
426 // flightMode == 0 is always active
427 static const struct {
430 } flightModeToLed
[] = {
431 {HEADFREE_MODE
, LED_MODE_HEADFREE
},
433 {MAG_MODE
, LED_MODE_MAG
},
436 {BARO_MODE
, LED_MODE_BARO
},
438 {HORIZON_MODE
, LED_MODE_HORIZON
},
439 {ANGLE_MODE
, LED_MODE_ANGLE
},
440 {0, LED_MODE_ORIENTATION
},
443 static void applyLedFixedLayers()
445 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
446 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
447 hsvColor_t color
= *getSC(LED_SCOLOR_BACKGROUND
);
449 int fn
= ledGetFunction(ledConfig
);
450 int hOffset
= HSV_HUE_MAX
;
453 case LED_FUNCTION_COLOR
:
454 color
= ledStripConfig()->colors
[ledGetColor(ledConfig
)];
457 case LED_FUNCTION_FLIGHT_MODE
:
458 for (unsigned i
= 0; i
< ARRAYLEN(flightModeToLed
); i
++)
459 if (!flightModeToLed
[i
].flightMode
|| FLIGHT_MODE(flightModeToLed
[i
].flightMode
)) {
460 const hsvColor_t
*directionalColor
= getDirectionalModeColor(ledIndex
, &ledStripConfig()->modeColors
[flightModeToLed
[i
].ledMode
]);
461 if (directionalColor
) {
462 color
= *directionalColor
;
465 break; // stop on first match
469 case LED_FUNCTION_ARM_STATE
:
470 color
= ARMING_FLAG(ARMED
) ? *getSC(LED_SCOLOR_ARMED
) : *getSC(LED_SCOLOR_DISARMED
);
473 case LED_FUNCTION_BATTERY
:
475 hOffset
+= scaleRange(calculateBatteryPercentageRemaining(), 0, 100, -30, 120);
478 case LED_FUNCTION_RSSI
:
480 hOffset
+= scaleRange(rssi
* 100, 0, 1023, -30, 120);
487 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_THROTTLE
)) {
488 hOffset
+= scaledAux
;
491 color
.h
= (color
.h
+ hOffset
) % (HSV_HUE_MAX
+ 1);
493 setLedHsv(ledIndex
, &color
);
498 static void applyLedHsv(uint32_t mask
, const hsvColor_t
*color
)
500 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
501 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
502 if ((*ledConfig
& mask
) == mask
)
503 setLedHsv(ledIndex
, color
);
508 WARNING_ARMING_DISABLED
,
513 static void applyLedWarningLayer(bool updateNow
, timeUs_t
*timer
)
515 static uint8_t warningFlashCounter
= 0;
516 static uint8_t warningFlags
= 0; // non-zero during blinks
519 // keep counter running, so it stays in sync with blink
520 warningFlashCounter
++;
521 warningFlashCounter
&= 0xF;
523 if (warningFlashCounter
== 0) { // update when old flags was processed
525 if (batteryConfig()->voltageMeterSource
!= VOLTAGE_METER_NONE
&& getBatteryState() != BATTERY_OK
)
526 warningFlags
|= 1 << WARNING_LOW_BATTERY
;
527 if (feature(FEATURE_FAILSAFE
) && failsafeIsActive())
528 warningFlags
|= 1 << WARNING_FAILSAFE
;
529 if (!ARMING_FLAG(ARMED
) && !ARMING_FLAG(OK_TO_ARM
))
530 warningFlags
|= 1 << WARNING_ARMING_DISABLED
;
532 *timer
+= HZ_TO_US(10);
536 const hsvColor_t
*warningColor
= NULL
;
538 bool colorOn
= (warningFlashCounter
% 2) == 0; // w_w_
539 warningFlags_e warningId
= warningFlashCounter
/ 4;
540 if (warningFlags
& (1 << warningId
)) {
542 case WARNING_ARMING_DISABLED
:
543 warningColor
= colorOn
? &HSV(GREEN
) : &HSV(BLACK
);
545 case WARNING_LOW_BATTERY
:
546 warningColor
= colorOn
? &HSV(RED
) : &HSV(BLACK
);
548 case WARNING_FAILSAFE
:
549 warningColor
= colorOn
? &HSV(YELLOW
) : &HSV(BLUE
);
555 applyLedHsv(LED_MOV_OVERLAY(LED_FLAG_OVERLAY(LED_OVERLAY_WARNING
)), warningColor
);
559 static void applyLedBatteryLayer(bool updateNow
, timeUs_t
*timer
)
561 static bool flash
= false;
563 int timerDelayUs
= HZ_TO_US(1);
567 switch (getBatteryState()) {
570 timerDelayUs
= HZ_TO_US(1);
573 case BATTERY_WARNING
:
575 timerDelayUs
= HZ_TO_US(2);
580 timerDelayUs
= HZ_TO_US(8);
586 *timer
+= timerDelayUs
;
589 const hsvColor_t
*bgc
= getSC(LED_SCOLOR_BACKGROUND
);
590 applyLedHsv(LED_MOV_FUNCTION(LED_FUNCTION_BATTERY
), bgc
);
594 static void applyLedRssiLayer(bool updateNow
, timeUs_t
*timer
)
596 static bool flash
= false;
598 int timerDelay
= HZ_TO_US(1);
601 int state
= (rssi
* 100) / 1023;
605 timerDelay
= HZ_TO_US(1);
606 } else if (state
> 20) {
608 timerDelay
= HZ_TO_US(2);
611 timerDelay
= HZ_TO_US(8);
615 *timer
+= timerDelay
;
618 const hsvColor_t
*bgc
= getSC(LED_SCOLOR_BACKGROUND
);
619 applyLedHsv(LED_MOV_FUNCTION(LED_FUNCTION_RSSI
), bgc
);
624 static void applyLedGpsLayer(bool updateNow
, timeUs_t
*timer
)
627 static uint8_t gpsPauseCounter
= 0;
628 const uint8_t blinkPauseLength
= 4;
631 static uint8_t gpsFlashCounter
= 0;
632 if (gpsPauseCounter
> 0) {
634 } else if (gpsFlashCounter
>= GPS_numSat
) {
636 gpsPauseCounter
= blinkPauseLength
;
641 *timer
+= HZ_TO_US(2.5f
);
644 const hsvColor_t
*gpsColor
;
646 if (GPS_numSat
== 0 || !sensors(SENSOR_GPS
)) {
647 gpsColor
= getSC(LED_SCOLOR_GPSNOSATS
);
649 bool colorOn
= gpsPauseCounter
== 0; // each interval starts with pause
650 if (STATE(GPS_FIX
)) {
651 gpsColor
= colorOn
? getSC(LED_SCOLOR_GPSLOCKED
) : getSC(LED_SCOLOR_BACKGROUND
);
653 gpsColor
= colorOn
? getSC(LED_SCOLOR_GPSNOLOCK
) : getSC(LED_SCOLOR_GPSNOSATS
);
657 applyLedHsv(LED_MOV_FUNCTION(LED_FUNCTION_GPS
), gpsColor
);
662 #define INDICATOR_DEADBAND 25
664 static void applyLedIndicatorLayer(bool updateNow
, timeUs_t
*timer
)
666 static bool flash
= 0;
669 if (rxIsReceivingSignal()) {
670 // calculate update frequency
671 int scale
= MAX(ABS(rcCommand
[ROLL
]), ABS(rcCommand
[PITCH
])); // 0 - 500
672 scale
= scale
- INDICATOR_DEADBAND
; // start increasing frequency right after deadband
673 *timer
+= HZ_TO_US(5 + (45 * scale
) / (500 - INDICATOR_DEADBAND
)); // 5 - 50Hz update, 2.5 - 25Hz blink
677 *timer
+= HZ_TO_US(5);
684 const hsvColor_t
*flashColor
= &HSV(ORANGE
); // TODO - use user color?
686 quadrant_e quadrants
= 0;
687 if (rcCommand
[ROLL
] > INDICATOR_DEADBAND
) {
688 quadrants
|= QUADRANT_EAST
;
689 } else if (rcCommand
[ROLL
] < -INDICATOR_DEADBAND
) {
690 quadrants
|= QUADRANT_WEST
;
692 if (rcCommand
[PITCH
] > INDICATOR_DEADBAND
) {
693 quadrants
|= QUADRANT_NORTH
;
694 } else if (rcCommand
[PITCH
] < -INDICATOR_DEADBAND
) {
695 quadrants
|= QUADRANT_SOUTH
;
698 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
699 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
700 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_INDICATOR
)) {
701 if (getLedQuadrant(ledIndex
) & quadrants
)
702 setLedHsv(ledIndex
, flashColor
);
707 #define ROTATION_SEQUENCE_LED_COUNT 6 // 2 on, 4 off
708 #define ROTATION_SEQUENCE_LED_WIDTH 2 // 2 on
710 static void updateLedRingCounts(void)
713 // try to split in segments/rings of exactly ROTATION_SEQUENCE_LED_COUNT leds
714 if ((ledCounts
.ring
% ROTATION_SEQUENCE_LED_COUNT
) == 0) {
715 seqLen
= ROTATION_SEQUENCE_LED_COUNT
;
717 seqLen
= ledCounts
.ring
;
718 // else split up in equal segments/rings of at most ROTATION_SEQUENCE_LED_COUNT leds
719 // TODO - improve partitioning (15 leds -> 3x5)
720 while ((seqLen
> ROTATION_SEQUENCE_LED_COUNT
) && ((seqLen
% 2) == 0)) {
724 ledCounts
.ringSeqLen
= seqLen
;
727 static void applyLedThrustRingLayer(bool updateNow
, timeUs_t
*timer
)
729 static uint8_t rotationPhase
;
730 int ledRingIndex
= 0;
733 rotationPhase
= rotationPhase
> 0 ? rotationPhase
- 1 : ledCounts
.ringSeqLen
- 1;
735 *timer
+= HZ_TO_US(5 + (45 * scaledThrottle
) / 100); // 5 - 50Hz update rate
738 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
739 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
740 if (ledGetFunction(ledConfig
) == LED_FUNCTION_THRUST_RING
) {
743 if (ARMING_FLAG(ARMED
)) {
744 applyColor
= (ledRingIndex
+ rotationPhase
) % ledCounts
.ringSeqLen
< ROTATION_SEQUENCE_LED_WIDTH
;
746 applyColor
= !(ledRingIndex
% 2); // alternating pattern
750 const hsvColor_t
*ringColor
= &ledStripConfig()->colors
[ledGetColor(ledConfig
)];
751 setLedHsv(ledIndex
, ringColor
);
759 typedef struct larsonParameters_s
{
760 uint8_t currentBrightness
;
763 } larsonParameters_t
;
765 static int brightnessForLarsonIndex(larsonParameters_t
*larsonParameters
, uint8_t larsonIndex
)
767 int offset
= larsonIndex
- larsonParameters
->currentIndex
;
768 static const int larsonLowValue
= 8;
771 return (larsonLowValue
);
774 return (larsonParameters
->currentBrightness
);
776 if (larsonParameters
->direction
== offset
) {
777 return (larsonParameters
->currentBrightness
- 127);
780 return (255 - larsonParameters
->currentBrightness
);
784 static void larsonScannerNextStep(larsonParameters_t
*larsonParameters
, int delta
)
786 if (larsonParameters
->currentBrightness
> (255 - delta
)) {
787 larsonParameters
->currentBrightness
= 127;
788 if (larsonParameters
->currentIndex
>= ledCounts
.larson
|| larsonParameters
->currentIndex
< 0) {
789 larsonParameters
->direction
= -larsonParameters
->direction
;
791 larsonParameters
->currentIndex
+= larsonParameters
->direction
;
793 larsonParameters
->currentBrightness
+= delta
;
797 static void applyLarsonScannerLayer(bool updateNow
, timeUs_t
*timer
)
799 static larsonParameters_t larsonParameters
= { 0, 0, 1 };
802 larsonScannerNextStep(&larsonParameters
, 15);
803 *timer
+= HZ_TO_US(60);
806 int scannerLedIndex
= 0;
807 for (unsigned i
= 0; i
< ledCounts
.count
; i
++) {
809 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[i
];
811 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_LARSON_SCANNER
)) {
813 getLedHsv(i
, &ledColor
);
814 ledColor
.v
= brightnessForLarsonIndex(&larsonParameters
, scannerLedIndex
);
815 setLedHsv(i
, &ledColor
);
821 // blink twice, then wait ; either always or just when landing
822 static void applyLedBlinkLayer(bool updateNow
, timeUs_t
*timer
)
824 const uint16_t blinkPattern
= 0x8005; // 0b1000000000000101;
825 static uint16_t blinkMask
;
828 blinkMask
= blinkMask
>> 1;
830 blinkMask
= blinkPattern
;
832 *timer
+= HZ_TO_US(10);
835 bool ledOn
= (blinkMask
& 1); // b_b_____...
837 for (int i
= 0; i
< ledCounts
.count
; ++i
) {
838 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[i
];
840 if (ledGetOverlayBit(ledConfig
, LED_OVERLAY_BLINK
) ||
841 (ledGetOverlayBit(ledConfig
, LED_OVERLAY_LANDING_FLASH
) && scaledThrottle
< 50)) {
842 setLedHsv(i
, getSC(LED_SCOLOR_BLINKBACKGROUND
));
848 #ifdef USE_LED_ANIMATION
849 static void applyLedAnimationLayer(bool updateNow
, timeUs_t
*timer
)
851 static uint8_t frameCounter
= 0;
852 const int animationFrames
= ledGridRows
;
854 frameCounter
= (frameCounter
+ 1 < animationFrames
) ? frameCounter
+ 1 : 0;
855 *timer
+= HZ_TO_US(20);
858 if (ARMING_FLAG(ARMED
))
861 int previousRow
= frameCounter
> 0 ? frameCounter
- 1 : animationFrames
- 1;
862 int currentRow
= frameCounter
;
863 int nextRow
= (frameCounter
+ 1 < animationFrames
) ? frameCounter
+ 1 : 0;
865 for (int ledIndex
= 0; ledIndex
< ledCounts
.count
; ledIndex
++) {
866 const ledConfig_t
*ledConfig
= &ledStripConfig()->ledConfigs
[ledIndex
];
868 if (ledGetY(ledConfig
) == previousRow
) {
869 setLedHsv(ledIndex
, getSC(LED_SCOLOR_ANIMATION
));
870 scaleLedValue(ledIndex
, 50);
871 } else if (ledGetY(ledConfig
) == currentRow
) {
872 setLedHsv(ledIndex
, getSC(LED_SCOLOR_ANIMATION
));
873 } else if (ledGetY(ledConfig
) == nextRow
) {
874 scaleLedValue(ledIndex
, 50);
890 #ifdef USE_LED_ANIMATION
897 static timeUs_t timerVal
[timTimerCount
];
899 // function to apply layer.
900 // function must replan self using timer pointer
901 // when updateNow is true (timer triggered), state must be updated first,
902 // before calculating led state. Otherwise update started by different trigger
903 // may modify LED state.
904 typedef void applyLayerFn_timed(bool updateNow
, timeUs_t
*timer
);
906 static applyLayerFn_timed
* layerTable
[] = {
907 [timBlink
] = &applyLedBlinkLayer
,
908 [timLarson
] = &applyLarsonScannerLayer
,
909 [timBattery
] = &applyLedBatteryLayer
,
910 [timRssi
] = &applyLedRssiLayer
,
912 [timGps
] = &applyLedGpsLayer
,
914 [timWarning
] = &applyLedWarningLayer
,
915 [timIndicator
] = &applyLedIndicatorLayer
,
916 #ifdef USE_LED_ANIMATION
917 [timAnimation
] = &applyLedAnimationLayer
,
919 [timRing
] = &applyLedThrustRingLayer
922 void ledStripUpdate(timeUs_t currentTimeUs
)
924 if (!(ledStripInitialised
&& isWS2811LedStripReady())) {
928 if (IS_RC_MODE_ACTIVE(BOXLEDLOW
) && !(ledStripConfig()->ledstrip_visual_beeper
&& isBeeperOn())) {
929 if (ledStripEnabled
) {
931 ledStripEnabled
= false;
935 ledStripEnabled
= true;
937 const uint32_t now
= currentTimeUs
;
939 // test all led timers, setting corresponding bits
940 uint32_t timActive
= 0;
941 for (timId_e timId
= 0; timId
< timTimerCount
; timId
++) {
942 // sanitize timer value, so that it can be safely incremented. Handles inital timerVal value.
943 const timeDelta_t delta
= cmpTimeUs(now
, timerVal
[timId
]);
944 // max delay is limited to 5s
945 if (delta
< 0 && delta
> -MAX_TIMER_DELAY
)
946 continue; // not ready yet
947 timActive
|= 1 << timId
;
948 if (delta
>= 100 * 1000 || delta
< 0) {
949 timerVal
[timId
] = now
;
954 return; // no change this update, keep old state
956 // apply all layers; triggered timed functions has to update timers
958 scaledThrottle
= ARMING_FLAG(ARMED
) ? scaleRange(rcData
[THROTTLE
], PWM_RANGE_MIN
, PWM_RANGE_MAX
, 0, 100) : 0;
959 scaledAux
= scaleRange(rcData
[ledStripConfig()->ledstrip_aux_channel
], PWM_RANGE_MIN
, PWM_RANGE_MAX
, 0, HSV_HUE_MAX
+ 1);
961 applyLedFixedLayers();
963 for (timId_e timId
= 0; timId
< ARRAYLEN(layerTable
); timId
++) {
964 uint32_t *timer
= &timerVal
[timId
];
965 bool updateNow
= timActive
& (1 << timId
);
966 (*layerTable
[timId
])(updateNow
, timer
);
971 bool parseColor(int index
, const char *colorConfig
)
973 const char *remainingCharacters
= colorConfig
;
975 hsvColor_t
*color
= &ledStripConfigMutable()->colors
[index
];
978 static const uint16_t hsv_limit
[HSV_COLOR_COMPONENT_COUNT
] = {
979 [HSV_HUE
] = HSV_HUE_MAX
,
980 [HSV_SATURATION
] = HSV_SATURATION_MAX
,
981 [HSV_VALUE
] = HSV_VALUE_MAX
,
983 for (int componentIndex
= 0; result
&& componentIndex
< HSV_COLOR_COMPONENT_COUNT
; componentIndex
++) {
984 int val
= atoi(remainingCharacters
);
985 if(val
> hsv_limit
[componentIndex
]) {
989 switch (componentIndex
) {
1000 remainingCharacters
= strchr(remainingCharacters
, ',');
1001 if (remainingCharacters
) {
1002 remainingCharacters
++; // skip separator
1004 if (componentIndex
< HSV_COLOR_COMPONENT_COUNT
- 1) {
1011 memset(color
, 0, sizeof(*color
));
1018 * Redefine a color in a mode.
1020 bool setModeColor(ledModeIndex_e modeIndex
, int modeColorIndex
, int colorIndex
)
1023 if (colorIndex
< 0 || colorIndex
>= LED_CONFIGURABLE_COLOR_COUNT
)
1025 if (modeIndex
< LED_MODE_COUNT
) { // modeIndex_e is unsigned, so one-sided test is enough
1026 if(modeColorIndex
< 0 || modeColorIndex
>= LED_DIRECTION_COUNT
)
1028 ledStripConfigMutable()->modeColors
[modeIndex
].color
[modeColorIndex
] = colorIndex
;
1029 } else if (modeIndex
== LED_SPECIAL
) {
1030 if (modeColorIndex
< 0 || modeColorIndex
>= LED_SPECIAL_COLOR_COUNT
)
1032 ledStripConfigMutable()->specialColors
.color
[modeColorIndex
] = colorIndex
;
1033 } else if (modeIndex
== LED_AUX_CHANNEL
) {
1034 if (modeColorIndex
< 0 || modeColorIndex
>= 1)
1036 ledStripConfigMutable()->ledstrip_aux_channel
= colorIndex
;
1045 colors
= ledStripConfigMutable()->colors
;
1046 modeColors
= ledStripConfig()->modeColors
;
1047 specialColors
= ledStripConfig()->specialColors
;
1048 ledStripInitialised
= false;
1051 void ledStripEnable(void)
1053 reevaluateLedConfig();
1054 ledStripInitialised
= true;
1056 ws2811LedStripInit(ledStripConfig()->ioTag
);
1059 static void ledStripDisable(void)
1061 setStripColor(&HSV(BLACK
));
1063 ws2811UpdateStrip();