From b22a1b5d32da8f85490a9bca443b845033b7caa0 Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Sat, 14 Oct 2023 06:06:55 +0200 Subject: [PATCH] feat: Support for WS2812 RGB LEDs (#3909) Co-authored-by: 3djc --- companion/src/firmwares/boards.cpp | 4 + companion/src/firmwares/boards.h | 1 + .../src/firmwares/customfunctiondata.cpp | 8 +- companion/src/firmwares/customfunctiondata.h | 1 + .../edgetx/yaml_customfunctiondata.cpp | 10 +- companion/src/modeledit/customfunctions.cpp | 18 +- .../src/boards/generic_stm32/module_ports.cpp | 5 +- radio/src/boards/generic_stm32/rgb_leds.cpp | 115 +++++++++ radio/src/boards/generic_stm32/rgb_leds.h | 29 +++ radio/src/dataconstants.h | 1 + radio/src/functions.cpp | 3 + .../gui/128x64/model_special_functions.cpp | 12 +- radio/src/gui/gui_common.cpp | 10 +- radio/src/hal/timer_driver.h | 5 - radio/src/lua/api_general.cpp | 56 +++- radio/src/lua/api_model.cpp | 2 +- radio/src/lua/interface.cpp | 13 + radio/src/opentx.cpp | 9 + radio/src/opentx.h | 2 +- radio/src/pulses/ppm.cpp | 2 - radio/src/sdcard.h | 1 + .../storage/yaml/yaml_datastructs_128x64.cpp | 1 + .../storage/yaml/yaml_datastructs_funcs.cpp | 2 + .../storage/yaml/yaml_datastructs_nv14.cpp | 1 + .../src/storage/yaml/yaml_datastructs_t20.cpp | 1 + .../storage/yaml/yaml_datastructs_tpro.cpp | 1 + .../src/storage/yaml/yaml_datastructs_x10.cpp | 1 + .../storage/yaml/yaml_datastructs_x12s.cpp | 1 + .../src/storage/yaml/yaml_datastructs_x9d.cpp | 1 + .../src/storage/yaml/yaml_datastructs_x9e.cpp | 1 + .../storage/yaml/yaml_datastructs_x9lite.cpp | 1 + .../storage/yaml/yaml_datastructs_xlites.cpp | 1 + .../common/arm/stm32/module_timer_driver.cpp | 8 +- .../src/targets/common/arm/stm32/stm32_dma.h | 46 ++++ .../common/arm/stm32/stm32_pulse_driver.cpp | 182 +++++++------ .../common/arm/stm32/stm32_pulse_driver.h | 14 +- .../targets/common/arm/stm32/stm32_timer.cpp | 20 ++ .../targets/common/arm/stm32/stm32_timer.h | 2 + .../targets/common/arm/stm32/stm32_ws2812.cpp | 241 ++++++++++++++++++ .../targets/common/arm/stm32/stm32_ws2812.h | 37 +++ radio/src/targets/simu/led_driver.cpp | 2 + radio/src/targets/taranis/board.cpp | 13 + radio/src/translations.cpp | 1 + radio/src/translations.h | 1 + radio/src/translations/cn.h | 1 + radio/src/translations/cz.h | 1 + radio/src/translations/da.h | 1 + radio/src/translations/de.h | 3 +- radio/src/translations/en.h | 3 +- radio/src/translations/es.h | 1 + radio/src/translations/fi.h | 1 + radio/src/translations/fr.h | 4 +- radio/src/translations/it.h | 1 + radio/src/translations/jp.h | 1 + radio/src/translations/nl.h | 1 + radio/src/translations/pl.h | 1 + radio/src/translations/pt.h | 1 + radio/src/translations/se.h | 1 + radio/src/translations/tw.h | 1 + radio/util/addtr.py | 2 +- 60 files changed, 788 insertions(+), 122 deletions(-) create mode 100644 radio/src/boards/generic_stm32/rgb_leds.cpp create mode 100644 radio/src/boards/generic_stm32/rgb_leds.h create mode 100644 radio/src/targets/common/arm/stm32/stm32_ws2812.cpp create mode 100644 radio/src/targets/common/arm/stm32/stm32_ws2812.h mode change 100644 => 100755 radio/util/addtr.py diff --git a/companion/src/firmwares/boards.cpp b/companion/src/firmwares/boards.cpp index 9f3326f49..59e36715c 100644 --- a/companion/src/firmwares/boards.cpp +++ b/companion/src/firmwares/boards.cpp @@ -572,6 +572,10 @@ int Boards::getCapability(Board::Type board, Board::Capability capability) // TX12, TX12MK2, ZORRO, BOXER, T8, TLITE, TPRO, LR3PRO, COMMANDO8 return (IS_FAMILY_HORUS_OR_T16(board) && !IS_HORUS_X12S(board)) || IS_FAMILY_T12(board); + case HasLedStripGPIO: + // No current radio do support that feature + return false; + case SportMaxBaudRate: if (IS_FAMILY_T16(board) || IS_FLYSKY_NV14(board) || IS_FLYSKY_EL18(board) || IS_TARANIS_X7_ACCESS(board) || (IS_TARANIS(board) && !IS_TARANIS_XLITE(board) && !IS_TARANIS_X7(board) && !IS_TARANIS_X9LITE(board))) diff --git a/companion/src/firmwares/boards.h b/companion/src/firmwares/boards.h index 940b7dad1..438f03641 100644 --- a/companion/src/firmwares/boards.h +++ b/companion/src/firmwares/boards.h @@ -164,6 +164,7 @@ namespace Board { HasIntModuleHeartbeatGPIO, HasTrainerModuleCPPM, HasTrainerModuleSBUS, + HasLedStripGPIO }; struct SwitchInfo diff --git a/companion/src/firmwares/customfunctiondata.cpp b/companion/src/firmwares/customfunctiondata.cpp index 5ce47efc2..d17feab30 100644 --- a/companion/src/firmwares/customfunctiondata.cpp +++ b/companion/src/firmwares/customfunctiondata.cpp @@ -127,6 +127,8 @@ QString CustomFunctionData::funcToString(const AssignFunc func, const ModelData return tr("Set Main Screen"); else if (func == FuncDisableAudioAmp) return tr("Audio Amp Off"); + else if (func == FuncRGBLed) + return tr("RGB leds"); else { return QString(CPN_STR_UNKNOWN_ITEM); } @@ -260,7 +262,8 @@ bool CustomFunctionData::isFuncAvailable(const int index, const ModelData * mode ((index >= FuncAdjustGV1 && index <= FuncAdjustGVLast) && !fw->getCapability(Gvars)) || ((index == FuncDisableTouch) && !IS_HORUS_OR_TARANIS(fw->getBoard())) || ((index == FuncSetScreen && !Boards::getCapability(fw->getBoard(), Board::HasColorLcd)) || - ((index == FuncDisableAudioAmp && !Boards::getCapability(fw->getBoard(), Board::HasAudioMuteGPIO)))) + ((index == FuncDisableAudioAmp && !Boards::getCapability(fw->getBoard(), Board::HasAudioMuteGPIO))) || + ((index == FuncRGBLed && !Boards::getCapability(fw->getBoard(), Board::HasLedStripGPIO)))) ); return !ret; } @@ -475,7 +478,8 @@ bool CustomFunctionData::isParamAvailable() const FuncBindExternalModule, FuncRacingMode, FuncDisableTouch, - FuncDisableAudioAmp + FuncDisableAudioAmp, + FuncRGBLed }; return funcList.contains(func) ? false : true; diff --git a/companion/src/firmwares/customfunctiondata.h b/companion/src/firmwares/customfunctiondata.h index e4582203c..6f5a503ca 100644 --- a/companion/src/firmwares/customfunctiondata.h +++ b/companion/src/firmwares/customfunctiondata.h @@ -73,6 +73,7 @@ enum AssignFunc { FuncDisableTouch, FuncSetScreen, FuncDisableAudioAmp, + FuncRGBLed, FuncCount, FuncReserve = -1 }; diff --git a/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp b/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp index 296401c62..f5d637f7f 100644 --- a/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp +++ b/companion/src/firmwares/edgetx/yaml_customfunctiondata.cpp @@ -39,7 +39,8 @@ static bool fnHasRepeat(AssignFunc fn) || (fn == FuncPlayHaptic) || (fn == FuncPlaySound) || (fn == FuncSetScreen) - || (fn == FuncPlayScript); + || (fn == FuncPlayScript) + || (fn == FuncRGBLed); } static const YamlLookupTable customFnLut = { @@ -68,6 +69,7 @@ static const YamlLookupTable customFnLut = { { FuncDisableTouch, "DISABLE_TOUCH" }, { FuncSetScreen, "SET_SCREEN"}, { FuncDisableAudioAmp, "DISABLE_AUDIO_AMP" }, + { FuncRGBLed, "RGB_LED" }, }; static const YamlLookupTable trainerLut = { @@ -172,6 +174,7 @@ Node convert::encode(const CustomFunctionData& rhs) def += temp; } break; case FuncPlayScript: + case FuncRGBLed: def += std::string(rhs.paramarm); break; case FuncReset: @@ -232,7 +235,7 @@ Node convert::encode(const CustomFunctionData& rhs) if (add_comma) { def += ","; } - if (rhs.func == FuncPlayScript) { + if (rhs.func == FuncPlayScript || rhs.func == FuncRGBLed) { def += ((rhs.repeatParam == 0) ? "On" : "1x"); } else if (rhs.repeatParam == 0) { def += "1x"; @@ -297,6 +300,7 @@ bool convert::decode(const Node& node, file_str.resize(getCurrentFirmware()->getCapability(VoicesMaxLength)); strncpy(rhs.paramarm, file_str.c_str(), sizeof(rhs.paramarm) - 1); } break; + case FuncRGBLed: case FuncPlayScript: { std::string file_str; getline(def, file_str, ','); @@ -388,7 +392,7 @@ bool convert::decode(const Node& node, } else if(fnHasRepeat(rhs.func)) { std::string repeat; getline(def, repeat); - if (rhs.func == FuncPlayScript) { + if (rhs.func == FuncPlayScript || rhs.func == FuncRGBLed) { rhs.repeatParam = (repeat == "1x") ? 1 : 0; } else if (repeat == "1x") { rhs.repeatParam = 0; diff --git a/companion/src/modeledit/customfunctions.cpp b/companion/src/modeledit/customfunctions.cpp index ef9fe88ee..2c662e2a0 100644 --- a/companion/src/modeledit/customfunctions.cpp +++ b/companion/src/modeledit/customfunctions.cpp @@ -96,6 +96,18 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, } } + if (IS_STM32(firmware->getBoard())) { + scriptsSet = getFilesSet(g.profile[g.id()].sdPath() + "/SCRIPTS/RGBLED", QStringList() << "*.lua", firmware->getCapability(VoicesMaxLength)); + for (int i = 0; i < fswCapability; i++) { + if (functions[i].func == FuncRGBLed) { + QString temp = functions[i].paramarm; + if (!temp.isEmpty()) { + scriptsSet.insert(temp); + } + } + } + } + CompanionIcon playIcon("play.png"); playIcon.addImage("stop.png", QIcon::Normal, QIcon::On); @@ -194,7 +206,7 @@ CustomFunctionsPanel::CustomFunctionsPanel(QWidget * parent, ModelData * model, tableLayout->addLayout(i, 4, repeatLayout); fswtchRepeat[i] = new QComboBox(this); fswtchRepeat[i]->setProperty("index", i); - if (functions[i].func == FuncPlayScript) + if (functions[i].func == FuncPlayScript || functions[i].func == FuncRGBLed) fswtchRepeat[i]->setModel(tabModelFactory->getItemModel(repeatLuaId)); else fswtchRepeat[i]->setModel(tabModelFactory->getItemModel(repeatId)); @@ -339,7 +351,7 @@ void CustomFunctionsPanel::functionEdited() functions[index].clear(); functions[index].swtch = swtch; functions[index].func = (AssignFunc)fswtchFunc[index]->currentData().toInt(); - if (functions[index].func == FuncPlayScript) + if (functions[index].func == FuncPlayScript || functions[index].func == FuncRGBLed) fswtchRepeat[index]->setModel(tabModelFactory->getItemModel(repeatLuaId)); else fswtchRepeat[index]->setModel(tabModelFactory->getItemModel(repeatId)); @@ -541,7 +553,7 @@ void CustomFunctionsPanel::refreshCustomFunction(int i, bool modified) widgetsMask |= CUSTOM_FUNCTION_NUMERIC_PARAM; } } - else if (func == FuncPlayScript) { + else if (func == FuncPlayScript || func == FuncRGBLed) { widgetsMask |= CUSTOM_FUNCTION_FILE_PARAM | CUSTOM_FUNCTION_REPEAT; if (modified) { Helpers::getFileComboBoxValue(fswtchParamArmT[i], cfn.paramarm, 8); diff --git a/radio/src/boards/generic_stm32/module_ports.cpp b/radio/src/boards/generic_stm32/module_ports.cpp index 5b918fc7a..724d5d056 100644 --- a/radio/src/boards/generic_stm32/module_ports.cpp +++ b/radio/src/boards/generic_stm32/module_ports.cpp @@ -22,6 +22,7 @@ #include "hal/module_port.h" #include "stm32_serial_driver.h" #include "stm32_softserial_driver.h" +#include "stm32_dma.h" #include "module_ports.h" #include "board.h" @@ -86,7 +87,7 @@ static_assert(__STM32_PULSE_IS_TIMER_CHANNEL_SUPPORTED(INTMODULE_TIMER_Channel), "Unsupported timer channel"); // Make sure the DMA channel is supported -static_assert(__STM32_PULSE_IS_DMA_STREAM_SUPPORTED(INTMODULE_TIMER_DMA_STREAM), +static_assert(__STM32_DMA_IS_STREAM_SUPPORTED(INTMODULE_TIMER_DMA_STREAM), "Unsupported DMA stream"); #if !defined(INTMODULE_TIMER_DMA_IRQHandler) @@ -198,7 +199,7 @@ static_assert(__STM32_PULSE_IS_TIMER_CHANNEL_SUPPORTED(EXTMODULE_TIMER_Channel), "Unsupported timer channel"); // Make sure the DMA channel is supported -static_assert(__STM32_PULSE_IS_DMA_STREAM_SUPPORTED(EXTMODULE_TIMER_DMA_STREAM_LL), +static_assert(__STM32_DMA_IS_STREAM_SUPPORTED(EXTMODULE_TIMER_DMA_STREAM_LL), "Unsupported DMA stream"); #if !defined(EXTMODULE_TIMER_DMA_IRQHandler) diff --git a/radio/src/boards/generic_stm32/rgb_leds.cpp b/radio/src/boards/generic_stm32/rgb_leds.cpp new file mode 100644 index 000000000..54fee303d --- /dev/null +++ b/radio/src/boards/generic_stm32/rgb_leds.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) EdgeTx + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#include "stm32_pulse_driver.h" +#include "rgb_leds.h" +#include "hal.h" +#include "opentx.h" + +#if defined(LED_STRIP_GPIO) + +#include "stm32_ws2812.h" +#include "stm32_dma.h" + +#if !defined(SIMU) +#include +#include +#endif + +extern const stm32_pulse_timer_t _led_timer; + +static TimerHandle_t rgbLedTimer = nullptr; +static StaticTimer_t rgbLedTimerBuffer; + +void rgbSetLedColor(uint8_t led, uint8_t r, uint8_t g, uint8_t b) +{ + ws2812_set_color(led, r, g, b); +} + +void rgbLedColorApply() +{ + ws2812_update(&_led_timer);; +} + +static void rgbLedTimerCb(TimerHandle_t xTimer) +{ + (void)xTimer; + + ws2812_update(&_led_timer); +} + +void rgbLedStart() +{ + if (!rgbLedTimer) { + rgbLedTimer = + xTimerCreateStatic("rgbLed", LED_STRIP_REFRESH_PERIOD / RTOS_MS_PER_TICK, pdTRUE, (void*)0, + rgbLedTimerCb, &rgbLedTimerBuffer); + } + + if (rgbLedTimer) { + if( xTimerStart( rgbLedTimer, 0 ) != pdPASS ) { + /* The timer could not be set into the Active state. */ + } + } +} + +void rgbLedStop() +{ + if (rgbLedTimer) { + if( xTimerStop( rgbLedTimer, 5 / RTOS_MS_PER_TICK ) != pdPASS ) { + /* The timer could not be stopped. */ + } + } +} + +const stm32_pulse_timer_t _led_timer = { + .GPIOx = LED_STRIP_GPIO, + .GPIO_Pin = LED_STRIP_GPIO_PIN_DATA, + .GPIO_Alternate = LED_STRIP_GPIO_PIN_AF, + .TIMx = LED_STRIP_TIMER, + .TIM_Freq = LED_STRIP_TIMER_FREQ, + .TIM_Channel = LED_STRIP_TIMER_CHANNEL, + .TIM_IRQn = (IRQn_Type)-1, + .DMAx = LED_STRIP_TIMER_DMA, + .DMA_Stream = LED_STRIP_TIMER_DMA_STREAM, + .DMA_Channel = LED_STRIP_TIMER_DMA_CHANNEL, + .DMA_IRQn = LED_STRIP_TIMER_DMA_IRQn, + .DMA_TC_CallbackPtr = nullptr, +}; + +// Make sure the timer channel is supported +static_assert(__STM32_PULSE_IS_TIMER_CHANNEL_SUPPORTED(LED_STRIP_TIMER_CHANNEL), + "Unsupported timer channel"); + +// Make sure the DMA channel is supported +static_assert(__STM32_DMA_IS_STREAM_SUPPORTED(LED_STRIP_TIMER_DMA_STREAM), + "Unsupported DMA stream"); + +#if !defined(LED_STRIP_TIMER_DMA_IRQHandler) + #error "Missing LED_STRIP_TIMER_DMA_IRQHandler definition" +#endif + +extern "C" void LED_STRIP_TIMER_DMA_IRQHandler() +{ + ws2812_dma_isr(&_led_timer); +} + +#endif diff --git a/radio/src/boards/generic_stm32/rgb_leds.h b/radio/src/boards/generic_stm32/rgb_leds.h new file mode 100644 index 000000000..535d1c556 --- /dev/null +++ b/radio/src/boards/generic_stm32/rgb_leds.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) EdgeTx + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#pragma once + +void rgbLedStart(); +void rgbLedStop(); +void rgbSetLedColor(unsigned char, unsigned char, unsigned char, unsigned char); +void rgbLedColorApply(); + +// void boardInitRGBLed(); diff --git a/radio/src/dataconstants.h b/radio/src/dataconstants.h index 556c59df6..b1e5a87c1 100644 --- a/radio/src/dataconstants.h +++ b/radio/src/dataconstants.h @@ -598,6 +598,7 @@ enum Functions { FUNC_SET_SCREEN, #endif FUNC_DISABLE_AUDIO_AMP, + FUNC_RGB_LED, #if defined(DEBUG) FUNC_TEST, // should remain the last before MAX as not added in Companion #endif diff --git a/radio/src/functions.cpp b/radio/src/functions.cpp index 191ad4582..cab7e0fd1 100644 --- a/radio/src/functions.cpp +++ b/radio/src/functions.cpp @@ -21,6 +21,7 @@ #include "opentx.h" #include "switches.h" +#include "boards/generic_stm32/rgb_leds.h" #if defined(COLORLCD) void setRequestedMainView(uint8_t view); @@ -542,6 +543,8 @@ const char* funcGetLabel(uint8_t func) case FUNC_DISABLE_AUDIO_AMP: return STR_SF_DISABLE_AUDIO_AMP; #endif + case FUNC_RGB_LED: + return STR_SF_RGBLEDS; #if defined(DEBUG) case FUNC_TEST: return STR_SF_TEST; diff --git a/radio/src/gui/128x64/model_special_functions.cpp b/radio/src/gui/128x64/model_special_functions.cpp index 9f93a48ec..1030116f7 100644 --- a/radio/src/gui/128x64/model_special_functions.cpp +++ b/radio/src/gui/128x64/model_special_functions.cpp @@ -58,6 +58,9 @@ void onCustomFunctionsFileSelectionMenu(const char * result) if (func == FUNC_PLAY_SCRIPT) { strcpy(directory, SCRIPTS_FUNCS_PATH); } + else if (func == FUNC_RGB_LED) { + strcpy(directory, SCRIPTS_RGB_PATH); + } else { strcpy(directory, SOUNDS_PATH); strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); @@ -305,7 +308,7 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF } #endif #if defined(SDCARD) - else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT) { + else if (func == FUNC_PLAY_TRACK || func == FUNC_BACKGND_MUSIC || func == FUNC_PLAY_SCRIPT || func==FUNC_RGB_LED) { if (ZEXIST(cfn->play.name)) lcdDrawSizedText(MODEL_SPECIAL_FUNC_3RD_COLUMN-6, y, cfn->play.name, sizeof(cfn->play.name), attr); else @@ -316,15 +319,18 @@ void menuSpecialFunctions(event_t event, CustomFunctionData * functions, CustomF if (func==FUNC_PLAY_SCRIPT) { strcpy(directory, SCRIPTS_FUNCS_PATH); } + else if (func==FUNC_RGB_LED) { + strcpy(directory, SCRIPTS_RGB_PATH); + } else { strcpy(directory, SOUNDS_PATH); strncpy(directory+SOUNDS_PATH_LNG_OFS, currentLanguagePack->id, 2); } - if (sdListFiles(directory, func==FUNC_PLAY_SCRIPT ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { + if (sdListFiles(directory, func==FUNC_PLAY_SCRIPT || func==FUNC_RGB_LED ? SCRIPTS_EXT : SOUNDS_EXT, sizeof(cfn->play.name), cfn->play.name)) { POPUP_MENU_START(onCustomFunctionsFileSelectionMenu); } else { - POPUP_WARNING(func==FUNC_PLAY_SCRIPT ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); + POPUP_WARNING(func==FUNC_PLAY_SCRIPT || func==FUNC_RGB_LED ? STR_NO_SCRIPTS_ON_SD : STR_NO_SOUNDS_ON_SD); } } break; diff --git a/radio/src/gui/gui_common.cpp b/radio/src/gui/gui_common.cpp index 91cd53d24..4ef6f8b2a 100644 --- a/radio/src/gui/gui_common.cpp +++ b/radio/src/gui/gui_common.cpp @@ -585,6 +585,7 @@ bool isAssignableFunctionAvailable(int function, CustomFunctionData * functions) #endif #if !defined(HAPTIC) case FUNC_HAPTIC: + return false; #endif #if !defined(DANGEROUS_MODULE_FUNCTIONS) case FUNC_RANGECHECK: @@ -595,11 +596,14 @@ bool isAssignableFunctionAvailable(int function, CustomFunctionData * functions) case FUNC_PLAY_SCRIPT: return false; #endif +#if !defined(AUDIO_MUTE_GPIO) case FUNC_DISABLE_AUDIO_AMP: -#if defined(AUDIO_MUTE_GPIO) - return true; -#endif return false; +#endif +#if !defined(LED_STRIP_GPIO) + case FUNC_RGB_LED: + return false; +#endif default: return true; } diff --git a/radio/src/hal/timer_driver.h b/radio/src/hal/timer_driver.h index 2f516eeeb..98d85f7fa 100644 --- a/radio/src/hal/timer_driver.h +++ b/radio/src/hal/timer_driver.h @@ -23,13 +23,8 @@ #include -enum PulseGenerationType { - ETX_PWM=0, - ETX_TOGGLE -}; typedef struct { - uint8_t type; uint8_t polarity; uint16_t cmp_val; } etx_timer_config_t; diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 7a4f0f309..c6b72a5a5 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -32,6 +32,10 @@ #include "hal/rotary_encoder.h" #include "switches.h" #include "input_mapping.h" +#if defined(LED_STRIP_GPIO) +#include "boards/generic_stm32/rgb_leds.h" +#endif + #if defined(LIBOPENUI) #include "libopenui.h" @@ -2845,6 +2849,50 @@ static int luaGetTrainerStatus(lua_State * L) return 1; } +#if defined(LED_STRIP_GPIO) +/*luadoc +@function setRGBLedColor(id, rvalue, bvalue, cvalue) + +@param id: integer identifying a led in the led chain + +@param rvalue: interger, value of red channel + +@param gvalue: interger, value of green channel + +@param bvalue: interger, value of blue channel + +@status current Introduced in 2.10 +*/ + +static int luaSetRgbLedColor(lua_State * L) +{ + uint8_t id = luaL_checkunsigned(L, 1); + uint8_t r = luaL_checkunsigned(L, 2); + uint8_t g = luaL_checkunsigned(L, 3); + uint8_t b = luaL_checkunsigned(L, 4); + + rgbSetLedColor(id, r, g, b); + + return 1; +} + +/*luadoc +@function applyRGBLedColors() + + Apply RGB led colors previously defined by setRGBLedColor + +@status current Introduced in 2.10 +*/ + +static int luaApplyRGBLedColors(lua_State * L) +{ + + rgbLedColorApply(); + + return 1; +} + +#endif #define KEY_EVENTS(xxx, yyy) \ { "EVT_"#xxx"_FIRST", LRO_NUMVAL(EVT_KEY_FIRST(yyy)) }, \ @@ -2932,6 +2980,10 @@ LROT_BEGIN(etxlib, NULL, 0) LROT_FUNCENTRY( getSourceIndex, luaGetSourceIndex ) LROT_FUNCENTRY( getSourceName, luaGetSourceName ) LROT_FUNCENTRY( sources, luaSources ) +#if defined(LED_STRIP_GPIO) + LROT_FUNCENTRY(setRGBLedColor, luaSetRgbLedColor ) + LROT_FUNCENTRY(applyRGBLedColors, luaApplyRGBLedColors ) +#endif LROT_END(etxlib, NULL, 0) LROT_BEGIN(etxcst, NULL, 0) @@ -3269,7 +3321,9 @@ LROT_BEGIN(etxcst, NULL, 0) LROT_NUMENTRY( PLAY_NOW, PLAY_NOW ) LROT_NUMENTRY( PLAY_BACKGROUND, PLAY_BACKGROUND ) LROT_NUMENTRY( TIMEHOUR, TIMEHOUR ) - +#if defined(LED_STRIP_GPIO) + LROT_NUMENTRY( LED_STRIP_LENGTH, LED_STRIP_LENGTH ) +#endif LROT_NUMENTRY( UNIT_RAW, UNIT_RAW ) LROT_NUMENTRY( UNIT_VOLTS, UNIT_VOLTS ) LROT_NUMENTRY( UNIT_AMPS, UNIT_AMPS ) diff --git a/radio/src/lua/api_model.cpp b/radio/src/lua/api_model.cpp index 4a76aebeb..edb6d587d 100644 --- a/radio/src/lua/api_model.cpp +++ b/radio/src/lua/api_model.cpp @@ -1371,7 +1371,7 @@ static int luaModelGetCustomFunction(lua_State *L) lua_newtable(L); lua_pushtableinteger(L, "switch", CFN_SWITCH(cfn)); lua_pushtableinteger(L, "func", CFN_FUNC(cfn)); - if (CFN_FUNC(cfn) == FUNC_PLAY_TRACK || CFN_FUNC(cfn) == FUNC_BACKGND_MUSIC || CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT) { + if (CFN_FUNC(cfn) == FUNC_PLAY_TRACK || CFN_FUNC(cfn) == FUNC_BACKGND_MUSIC || CFN_FUNC(cfn) == FUNC_PLAY_SCRIPT || CFN_FUNC(cfn) == FUNC_RGB_LED) { lua_pushtablenstring(L, "name", cfn->play.name); } else { diff --git a/radio/src/lua/interface.cpp b/radio/src/lua/interface.cpp index 8811a0c27..b6073d970 100644 --- a/radio/src/lua/interface.cpp +++ b/radio/src/lua/interface.cpp @@ -656,6 +656,19 @@ static bool luaLoadFunctionScript(uint8_t ref) return true; } } + + if (fn -> func == FUNC_RGB_LED && ZEXIST(fn -> play.name)) { + if (luaScriptsCount < MAX_SCRIPTS) { + ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; + sid.reference = ref; + return luaLoadFile(SCRIPTS_RGB_PATH, fn->play.name, sid); + } + else { + POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS); + return true; + } + } + return false; } diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 9d48e239f..b7c64ddcb 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -19,6 +19,11 @@ * GNU General Public License for more details. */ +#if !defined(SIMU) +#include "stm32_ws2812.h" +#include "boards/generic_stm32/rgb_leds.h" +#endif + #include "opentx.h" #include "io/frsky_firmware_update.h" #include "hal/adc_driver.h" @@ -1651,6 +1656,10 @@ void opentxInit() resetBacklightTimeout(); +#if defined(LED_STRIP_GPIO) && !defined(SIMU) + rgbLedStart(); +#endif + pulsesStart(); WDG_ENABLE(WDG_DURATION); } diff --git a/radio/src/opentx.h b/radio/src/opentx.h index 9b76ea5ad..8798a3875 100644 --- a/radio/src/opentx.h +++ b/radio/src/opentx.h @@ -664,7 +664,7 @@ enum FunctionsActive { FUNCTION_DISABLE_TOUCH, #endif #if defined(AUDIO_MUTE_GPIO) - FUNCTION_DISABLE_AUDIO_AMP + FUNCTION_DISABLE_AUDIO_AMP, #endif }; diff --git a/radio/src/pulses/ppm.cpp b/radio/src/pulses/ppm.cpp index 2ebf0e58e..4e712f546 100644 --- a/radio/src/pulses/ppm.cpp +++ b/radio/src/pulses/ppm.cpp @@ -142,7 +142,6 @@ static void* ppmInit(uint8_t module) auto delay = GET_MODULE_PPM_DELAY(module) * 2; etx_timer_config_t cfg = { - .type = ETX_PWM, .polarity = !GET_MODULE_PPM_POLARITY(module), .cmp_val = (uint16_t)delay, }; @@ -183,7 +182,6 @@ static void ppmSendPulses(void* ctx, uint8_t* buffer, int16_t* channels, uint8_t auto delay = GET_MODULE_PPM_DELAY(module) * 2; etx_timer_config_t cfg = { - .type = ETX_PWM, .polarity = !GET_MODULE_PPM_POLARITY(module), .cmp_val = (uint16_t)delay, }; diff --git a/radio/src/sdcard.h b/radio/src/sdcard.h index c106c301a..1e5e097e0 100644 --- a/radio/src/sdcard.h +++ b/radio/src/sdcard.h @@ -58,6 +58,7 @@ extern FIL g_oLogFile; #define SCRIPTS_FUNCS_PATH SCRIPTS_PATH PATH_SEPARATOR "FUNCTIONS" #define SCRIPTS_TELEM_PATH SCRIPTS_PATH PATH_SEPARATOR "TELEMETRY" #define SCRIPTS_TOOLS_PATH SCRIPTS_PATH PATH_SEPARATOR "TOOLS" +#define SCRIPTS_RGB_PATH SCRIPTS_PATH PATH_SEPARATOR "RGBLED" #define LEN_FILE_PATH_MAX (sizeof(SCRIPTS_TELEM_PATH)+1) // longest + "/" diff --git a/radio/src/storage/yaml/yaml_datastructs_128x64.cpp b/radio/src/storage/yaml/yaml_datastructs_128x64.cpp index afd300585..e7c102029 100644 --- a/radio/src/storage/yaml/yaml_datastructs_128x64.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_128x64.cpp @@ -90,6 +90,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SCREENSHOT, "SCREENSHOT" }, { FUNC_RACING_MODE, "RACING_MODE" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp index 33d88b46a..251e618d5 100644 --- a/radio/src/storage/yaml/yaml_datastructs_funcs.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_funcs.cpp @@ -1433,6 +1433,7 @@ static void r_customFn(void* user, uint8_t* data, uint32_t bitoffs, case FUNC_PLAY_TRACK: case FUNC_BACKGND_MUSIC: case FUNC_PLAY_SCRIPT: + case FUNC_RGB_LED: strncpy(cfn->play.name, val, std::min(l_sep, LEN_FUNCTION_NAME)); break; @@ -1639,6 +1640,7 @@ static bool w_customFn(void* user, uint8_t* data, uint32_t bitoffs, case FUNC_PLAY_TRACK: case FUNC_BACKGND_MUSIC: case FUNC_PLAY_SCRIPT: + case FUNC_RGB_LED: if (!wf(opaque, cfn->play.name, strnlen(cfn->play.name, LEN_FUNCTION_NAME))) return false; break; diff --git a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp index fc56ff139..2ee536b65 100644 --- a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp @@ -92,6 +92,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_DISABLE_TOUCH, "DISABLE_TOUCH" }, { FUNC_SET_SCREEN, "SET_SCREEN" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_t20.cpp b/radio/src/storage/yaml/yaml_datastructs_t20.cpp index 3dabfcf29..4d6470457 100644 --- a/radio/src/storage/yaml/yaml_datastructs_t20.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_t20.cpp @@ -90,6 +90,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SCREENSHOT, "SCREENSHOT" }, { FUNC_RACING_MODE, "RACING_MODE" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_tpro.cpp b/radio/src/storage/yaml/yaml_datastructs_tpro.cpp index 958b1f9bd..2012ca45e 100644 --- a/radio/src/storage/yaml/yaml_datastructs_tpro.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_tpro.cpp @@ -90,6 +90,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SCREENSHOT, "SCREENSHOT" }, { FUNC_RACING_MODE, "RACING_MODE" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_x10.cpp b/radio/src/storage/yaml/yaml_datastructs_x10.cpp index ff5e3216b..1a73037ad 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x10.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x10.cpp @@ -92,6 +92,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_DISABLE_TOUCH, "DISABLE_TOUCH" }, { FUNC_SET_SCREEN, "SET_SCREEN" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp index ff5e3216b..1a73037ad 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x12s.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x12s.cpp @@ -92,6 +92,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_DISABLE_TOUCH, "DISABLE_TOUCH" }, { FUNC_SET_SCREEN, "SET_SCREEN" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_x9d.cpp b/radio/src/storage/yaml/yaml_datastructs_x9d.cpp index 025bbef69..e84c66f48 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9d.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9d.cpp @@ -90,6 +90,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SCREENSHOT, "SCREENSHOT" }, { FUNC_RACING_MODE, "RACING_MODE" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_x9e.cpp b/radio/src/storage/yaml/yaml_datastructs_x9e.cpp index 1ee595873..40521c460 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9e.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9e.cpp @@ -90,6 +90,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SCREENSHOT, "SCREENSHOT" }, { FUNC_RACING_MODE, "RACING_MODE" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp b/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp index bbee2b35f..2341c5f00 100644 --- a/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_x9lite.cpp @@ -90,6 +90,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SCREENSHOT, "SCREENSHOT" }, { FUNC_RACING_MODE, "RACING_MODE" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/storage/yaml/yaml_datastructs_xlites.cpp b/radio/src/storage/yaml/yaml_datastructs_xlites.cpp index da96fa006..ffdfbb68c 100644 --- a/radio/src/storage/yaml/yaml_datastructs_xlites.cpp +++ b/radio/src/storage/yaml/yaml_datastructs_xlites.cpp @@ -90,6 +90,7 @@ const struct YamlIdStr enum_Functions[] = { { FUNC_SCREENSHOT, "SCREENSHOT" }, { FUNC_RACING_MODE, "RACING_MODE" }, { FUNC_DISABLE_AUDIO_AMP, "DISABLE_AUDIO_AMP" }, + { FUNC_RGB_LED, "RGB_LED" }, { 0, NULL } }; const struct YamlIdStr enum_TimerModes[] = { diff --git a/radio/src/targets/common/arm/stm32/module_timer_driver.cpp b/radio/src/targets/common/arm/stm32/module_timer_driver.cpp index e93570461..4dce6bc4d 100644 --- a/radio/src/targets/common/arm/stm32/module_timer_driver.cpp +++ b/radio/src/targets/common/arm/stm32/module_timer_driver.cpp @@ -28,7 +28,7 @@ static void* module_timer_init(void* hw_def, const etx_timer_config_t* cfg) bool polarity = cfg->polarity; uint32_t ocval = cfg->cmp_val; - uint32_t ocmode = (cfg->type == ETX_PWM) ? LL_TIM_OCMODE_FORCED_INACTIVE : LL_TIM_OCMODE_TOGGLE; + uint32_t ocmode = LL_TIM_OCMODE_FORCED_INACTIVE; stm32_pulse_init(timer, 0); stm32_pulse_config_output(timer, polarity, ocmode, ocval); @@ -46,16 +46,14 @@ static void module_timer_send(void* ctx, const etx_timer_config_t* cfg, const void* pulses, uint16_t length) { auto timer = (const stm32_pulse_timer_t*)ctx; - if (!stm32_pulse_if_not_running_disable(timer)) { - LL_DMA_DeInit(timer->DMAx, timer->DMA_Stream); + if (!stm32_pulse_if_not_running_disable(timer)) return; - } // Set polarity stm32_pulse_set_polarity(timer, cfg->polarity); // Start DMA request and re-enable timer - uint32_t ocmode = (cfg->type == ETX_PWM) ? LL_TIM_OCMODE_PWM1 : LL_TIM_OCMODE_TOGGLE; + uint32_t ocmode = LL_TIM_OCMODE_PWM1; uint32_t ocval = cfg->cmp_val; stm32_pulse_start_dma_req(timer, pulses, length, ocmode, ocval); } diff --git a/radio/src/targets/common/arm/stm32/stm32_dma.h b/radio/src/targets/common/arm/stm32/stm32_dma.h index e40ad5a4a..615e8bd20 100644 --- a/radio/src/targets/common/arm/stm32/stm32_dma.h +++ b/radio/src/targets/common/arm/stm32/stm32_dma.h @@ -24,6 +24,12 @@ #include #include "stm32_hal_ll.h" +#define __STM32_DMA_IS_STREAM_SUPPORTED(stream) \ + ((stream) == LL_DMA_STREAM_0 || (stream) == LL_DMA_STREAM_1 || \ + (stream) == LL_DMA_STREAM_2 || (stream) == LL_DMA_STREAM_3 || \ + (stream) == LL_DMA_STREAM_4 || (stream) == LL_DMA_STREAM_5 || \ + (stream) == LL_DMA_STREAM_6 || (stream) == LL_DMA_STREAM_7) + inline static bool stm32_dma_check_tc_flag(DMA_TypeDef* DMAx, uint32_t DMA_Stream) { switch(DMA_Stream) { @@ -64,4 +70,44 @@ inline static bool stm32_dma_check_tc_flag(DMA_TypeDef* DMAx, uint32_t DMA_Strea return true; } +inline static bool stm32_dma_check_ht_flag(DMA_TypeDef* DMAx, uint32_t DMA_Stream) +{ + switch(DMA_Stream) { + case LL_DMA_STREAM_0: + if (!LL_DMA_IsActiveFlag_HT0(DMAx)) return false; + LL_DMA_ClearFlag_HT0(DMAx); + break; + case LL_DMA_STREAM_1: + if (!LL_DMA_IsActiveFlag_HT1(DMAx)) return false; + LL_DMA_ClearFlag_HT1(DMAx); + break; + case LL_DMA_STREAM_2: + if (!LL_DMA_IsActiveFlag_HT2(DMAx)) return false; + LL_DMA_ClearFlag_HT2(DMAx); + break; + case LL_DMA_STREAM_3: + if (!LL_DMA_IsActiveFlag_HT3(DMAx)) return false; + LL_DMA_ClearFlag_HT3(DMAx); + break; + case LL_DMA_STREAM_4: + if (!LL_DMA_IsActiveFlag_HT4(DMAx)) return false; + LL_DMA_ClearFlag_HT4(DMAx); + break; + case LL_DMA_STREAM_5: + if (!LL_DMA_IsActiveFlag_HT5(DMAx)) return false; + LL_DMA_ClearFlag_HT5(DMAx); + break; + case LL_DMA_STREAM_6: + if (!LL_DMA_IsActiveFlag_HT6(DMAx)) return false; + LL_DMA_ClearFlag_HT6(DMAx); + break; + case LL_DMA_STREAM_7: + if (!LL_DMA_IsActiveFlag_HT7(DMAx)) return false; + LL_DMA_ClearFlag_HT7(DMAx); + break; + } + + return true; +} + void stm32_dma_enable_clock(DMA_TypeDef* DMAx); diff --git a/radio/src/targets/common/arm/stm32/stm32_pulse_driver.cpp b/radio/src/targets/common/arm/stm32/stm32_pulse_driver.cpp index eefdf5349..341e3157c 100644 --- a/radio/src/targets/common/arm/stm32/stm32_pulse_driver.cpp +++ b/radio/src/targets/common/arm/stm32/stm32_pulse_driver.cpp @@ -35,8 +35,49 @@ // - DMA IRQ prio configurable (now 7) // - Timer IRQ prio configurable (now 7) -void stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq) +static void init_dma_arr_mode(const stm32_pulse_timer_t* tim) { + // re-init DMA stream + LL_DMA_DeInit(tim->DMAx, tim->DMA_Stream); + + LL_DMA_InitTypeDef dmaInit; + LL_DMA_StructInit(&dmaInit); + + // Direction + dmaInit.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; + + // Source + dmaInit.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; + + // Destination + dmaInit.PeriphOrM2MSrcAddress = CONVERT_PTR_UINT(&tim->TIMx->ARR); + dmaInit.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; + + // Data width + if (IS_TIM_32B_COUNTER_INSTANCE(tim->TIMx)) { + dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; + dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; + } else { + dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD; + dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; + } + + dmaInit.Channel = tim->DMA_Channel; + dmaInit.Priority = LL_DMA_PRIORITY_VERYHIGH; + + stm32_dma_enable_clock(tim->DMAx); + LL_DMA_Init(tim->DMAx, tim->DMA_Stream, &dmaInit); +} + +int stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq) +{ + // check if timer is free first (clock disabled) + if (stm32_timer_is_clock_enabled(tim->TIMx)) return -1; + + // .. and GPIO pin is not used + uint32_t pin_mode = LL_GPIO_GetPinMode(tim->GPIOx, tim->GPIO_Pin); + if (pin_mode != LL_GPIO_MODE_INPUT) return -1; + if (tim->DMA_TC_CallbackPtr) { memset(tim->DMA_TC_CallbackPtr, 0, sizeof(stm32_pulse_dma_tc_cb_t)); } @@ -61,24 +102,30 @@ void stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq) stm32_timer_enable_clock(tim->TIMx); LL_TIM_Init(tim->TIMx, &timInit); - if (tim->DMAx) { - // Enable DMA IRQ + if (tim->DMAx && (int32_t)tim->DMA_IRQn >= 0) { + init_dma_arr_mode(tim); NVIC_EnableIRQ(tim->DMA_IRQn); NVIC_SetPriority(tim->DMA_IRQn, 7); } - // Enable timer IRQ - NVIC_EnableIRQ(tim->TIM_IRQn); - NVIC_SetPriority(tim->TIM_IRQn, 7); + if ((int32_t)tim->TIM_IRQn >= 0) { + NVIC_EnableIRQ(tim->TIM_IRQn); + NVIC_SetPriority(tim->TIM_IRQn, 7); + } + + return 0; } void stm32_pulse_deinit(const stm32_pulse_timer_t* tim) { // Disable IRQs - if (tim->DMAx) { + if ((int32_t)tim->DMA_IRQn >= 0) { NVIC_DisableIRQ(tim->DMA_IRQn); } - NVIC_DisableIRQ(tim->TIM_IRQn); + + if ((int32_t)tim->TIM_IRQn >= 0) { + NVIC_DisableIRQ(tim->TIM_IRQn); + } if (tim->DMAx) { LL_DMA_DeInit(tim->DMAx, tim->DMA_Stream); @@ -100,6 +147,25 @@ void stm32_pulse_deinit(const stm32_pulse_timer_t* tim) LL_GPIO_Init(tim->GPIOx, &pinInit); } +static inline bool _is_complementary_channel(uint32_t channel) +{ + return (channel == LL_TIM_CHANNEL_CH1N) || (channel == LL_TIM_CHANNEL_CH2N) || + (channel == LL_TIM_CHANNEL_CH3N); +} + +static const uint32_t _base_channel[] = { + LL_TIM_CHANNEL_CH1, LL_TIM_CHANNEL_CH1, + LL_TIM_CHANNEL_CH2, LL_TIM_CHANNEL_CH2, + LL_TIM_CHANNEL_CH3, LL_TIM_CHANNEL_CH3, + LL_TIM_CHANNEL_CH4 +}; + +static inline uint32_t _get_base_channel(uint32_t channel) +{ + auto idx = TIM_GET_CHANNEL_INDEX(channel); + return _base_channel[idx]; +} + void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity, uint32_t ocmode, uint32_t cmp_val) { @@ -109,14 +175,14 @@ void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity, ocInit.OCMode = ocmode; ocInit.CompareValue = cmp_val; - uint32_t channel = tim->TIM_Channel; - if (tim->TIM_Channel != LL_TIM_CHANNEL_CH1N) { + uint32_t channel = _get_base_channel(tim->TIM_Channel); + bool comp_ch = _is_complementary_channel(tim->TIM_Channel); + if (!comp_ch) { ocInit.OCState = LL_TIM_OCSTATE_ENABLE; ocInit.OCNState = LL_TIM_OCSTATE_DISABLE; } else { ocInit.OCState = LL_TIM_OCSTATE_DISABLE; ocInit.OCNState = LL_TIM_OCSTATE_ENABLE; - channel = LL_TIM_CHANNEL_CH1; } uint32_t ll_polarity; @@ -126,7 +192,7 @@ void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity, ll_polarity = LL_TIM_OCPOLARITY_LOW; } - if (tim->TIM_Channel != LL_TIM_CHANNEL_CH1N) { + if (!comp_ch) { ocInit.OCPolarity = ll_polarity; } else { ocInit.OCNPolarity = ll_polarity; @@ -138,7 +204,6 @@ void stm32_pulse_config_output(const stm32_pulse_timer_t* tim, bool polarity, if (IS_TIM_BREAK_INSTANCE(tim->TIMx)) { LL_TIM_EnableAllOutputs(tim->TIMx); } - } void stm32_pulse_set_polarity(const stm32_pulse_timer_t* tim, bool polarity) @@ -173,9 +238,8 @@ bool stm32_pulse_if_not_running_disable(const stm32_pulse_timer_t* tim) static void set_compare_reg(const stm32_pulse_timer_t* tim, uint32_t val) { - switch(tim->TIM_Channel){ + switch(_get_base_channel(tim->TIM_Channel)){ case LL_TIM_CHANNEL_CH1: - case LL_TIM_CHANNEL_CH1N: LL_TIM_OC_SetCompareCH1(tim->TIMx, val); break; case LL_TIM_CHANNEL_CH2: @@ -190,40 +254,39 @@ static void set_compare_reg(const stm32_pulse_timer_t* tim, uint32_t val) } } +void stm32_pulse_set_period(const stm32_pulse_timer_t* tim, uint32_t period) +{ + LL_TIM_SetAutoReload(tim->TIMx, period - 1); +} + void stm32_pulse_set_cmp_val(const stm32_pulse_timer_t* tim, uint32_t cmp_val) { set_compare_reg(tim, cmp_val); } +void stm32_pulse_start(const stm32_pulse_timer_t* tim) +{ + LL_TIM_EnableCounter(tim->TIMx); +} + +void stm32_pulse_stop(const stm32_pulse_timer_t* tim) +{ + LL_TIM_DisableCounter(tim->TIMx); +} + static void set_oc_mode(const stm32_pulse_timer_t* tim, uint32_t ocmode) { - uint32_t channel = tim->TIM_Channel; - if (channel == LL_TIM_CHANNEL_CH1N) - channel = LL_TIM_CHANNEL_CH1; - + uint32_t channel = _get_base_channel(tim->TIM_Channel); LL_TIM_OC_SetMode(tim->TIMx, channel, ocmode); } void stm32_pulse_wait_for_completed(const stm32_pulse_timer_t* tim) { - uint32_t channel = tim->TIM_Channel; - if (channel == LL_TIM_CHANNEL_CH1N) - channel = LL_TIM_CHANNEL_CH1; - + uint32_t channel = _get_base_channel(tim->TIM_Channel); while(LL_TIM_IsEnabledCounter(tim->TIMx) && LL_TIM_OC_GetMode(tim->TIMx, channel) != LL_TIM_OCMODE_FORCED_INACTIVE); } -static void force_start_level(const stm32_pulse_timer_t* tim) -{ - uint32_t channel = tim->TIM_Channel; - if (channel == LL_TIM_CHANNEL_CH1N) - channel = LL_TIM_CHANNEL_CH1; - - uint32_t mode = LL_TIM_OC_GetMode(tim->TIMx, channel); - LL_TIM_OC_SetMode(tim->TIMx, channel, LL_TIM_OCMODE_FORCED_ACTIVE); - LL_TIM_OC_SetMode(tim->TIMx, channel, mode); -} void stm32_pulse_start_dma_req(const stm32_pulse_timer_t* tim, const void* pulses, uint16_t length, @@ -232,62 +295,23 @@ void stm32_pulse_start_dma_req(const stm32_pulse_timer_t* tim, // Re-configure timer output set_compare_reg(tim, cmp_val); set_oc_mode(tim, ocmode); - - // re-init DMA stream - LL_DMA_DeInit(tim->DMAx, tim->DMA_Stream); - LL_DMA_InitTypeDef dmaInit; - LL_DMA_StructInit(&dmaInit); - - // Direction - dmaInit.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - - // Source - dmaInit.MemoryOrM2MDstAddress = CONVERT_PTR_UINT(pulses); - dmaInit.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - - // Destination - dmaInit.PeriphOrM2MSrcAddress = CONVERT_PTR_UINT(&tim->TIMx->ARR); - dmaInit.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - - // Data width - if (IS_TIM_32B_COUNTER_INSTANCE(tim->TIMx)) { - // TODO: try using 16-bit source as well - dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - } else { - dmaInit.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD; - dmaInit.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD; - } - - dmaInit.NbData = length; - dmaInit.Channel = tim->DMA_Channel; - dmaInit.Priority = LL_DMA_PRIORITY_VERYHIGH; - - LL_DMA_Init(tim->DMAx, tim->DMA_Stream, &dmaInit); + LL_DMA_SetDataLength(tim->DMAx, tim->DMA_Stream, length); + LL_DMA_SetMemoryAddress(tim->DMAx, tim->DMA_Stream, (uint32_t)pulses); // Enable TC IRQ LL_DMA_EnableIT_TC(tim->DMAx, tim->DMA_Stream); - if (ocmode == LL_TIM_OCMODE_PWM1) { - // preloads first period for PWM) - LL_TIM_SetCounter(tim->TIMx, 0x00); + // Reset counter + LL_TIM_SetCounter(tim->TIMx, 0x00); + + // only on PWM (preloads the first period) + if (ocmode == LL_TIM_OCMODE_PWM1) LL_TIM_GenerateEvent_UPDATE(tim->TIMx); - } else { - // Reset counter close to overflow - if (IS_TIM_32B_COUNTER_INSTANCE(tim->TIMx)) { - LL_TIM_SetCounter(tim->TIMx, 0xFFFFFFFF); - } else { - LL_TIM_SetCounter(tim->TIMx, 0xFFFF); - } - } LL_TIM_EnableDMAReq_UPDATE(tim->TIMx); LL_DMA_EnableStream(tim->DMAx, tim->DMA_Stream); - // Trigger update to effect the first DMA transaction - // and thus load ARR with the first duration - // start timer LL_TIM_EnableCounter(tim->TIMx); } diff --git a/radio/src/targets/common/arm/stm32/stm32_pulse_driver.h b/radio/src/targets/common/arm/stm32/stm32_pulse_driver.h index 1197f00d3..f32858eda 100644 --- a/radio/src/targets/common/arm/stm32/stm32_pulse_driver.h +++ b/radio/src/targets/common/arm/stm32/stm32_pulse_driver.h @@ -50,7 +50,7 @@ struct stm32_pulse_timer_t { stm32_pulse_dma_tc_cb_t* DMA_TC_CallbackPtr; }; -void stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq); +int stm32_pulse_init(const stm32_pulse_timer_t* tim, uint32_t freq); void stm32_pulse_deinit(const stm32_pulse_timer_t* tim); void stm32_pulse_config_input(const stm32_pulse_timer_t* tim); @@ -62,8 +62,13 @@ void stm32_pulse_set_polarity(const stm32_pulse_timer_t* tim, bool polarity); bool stm32_pulse_get_polarity(const stm32_pulse_timer_t* tim); +void stm32_pulse_set_period(const stm32_pulse_timer_t* tim, uint32_t period); + void stm32_pulse_set_cmp_val(const stm32_pulse_timer_t* tim, uint32_t cmp_val); +void stm32_pulse_start(const stm32_pulse_timer_t* tim); +void stm32_pulse_stop(const stm32_pulse_timer_t* tim); + void stm32_pulse_wait_for_completed(const stm32_pulse_timer_t* tim); // return true if it could be disabled without interrupting a pulse train, false otherwise @@ -81,9 +86,6 @@ void stm32_pulse_tim_update_isr(const stm32_pulse_timer_t* tim); #define __STM32_PULSE_IS_TIMER_CHANNEL_SUPPORTED(ch) \ ((ch) == LL_TIM_CHANNEL_CH1 || (ch) == LL_TIM_CHANNEL_CH1N || \ - (ch) == LL_TIM_CHANNEL_CH2 || (ch) == LL_TIM_CHANNEL_CH3 || \ + (ch) == LL_TIM_CHANNEL_CH2 || (ch) == LL_TIM_CHANNEL_CH2N || \ + (ch) == LL_TIM_CHANNEL_CH3 || (ch) == LL_TIM_CHANNEL_CH3N || \ (ch) == LL_TIM_CHANNEL_CH4) - -#define __STM32_PULSE_IS_DMA_STREAM_SUPPORTED(stream) \ - ((stream) == LL_DMA_STREAM_1 || (stream) == LL_DMA_STREAM_5 || \ - (stream) == LL_DMA_STREAM_7) diff --git a/radio/src/targets/common/arm/stm32/stm32_timer.cpp b/radio/src/targets/common/arm/stm32/stm32_timer.cpp index e5f3dc5e7..dc344556f 100644 --- a/radio/src/targets/common/arm/stm32/stm32_timer.cpp +++ b/radio/src/targets/common/arm/stm32/stm32_timer.cpp @@ -62,3 +62,23 @@ void stm32_timer_disable_clock(TIM_TypeDef *TIMx) LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_TIM14); } } + +bool stm32_timer_is_clock_enabled(TIM_TypeDef *TIMx) +{ + if (TIMx == TIM1) { + return LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_TIM1) != 0; + } else if (TIMx == TIM2) { + return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM2) != 0; + } else if (TIMx == TIM3) { + return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM3) != 0; + } else if (TIMx == TIM4) { + return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM4) != 0; + } else if (TIMx == TIM5) { + return LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_TIM5) != 0; + } else if (TIMx == TIM8) { + return LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_TIM8) != 0; + } + + // not supported + return false; +} diff --git a/radio/src/targets/common/arm/stm32/stm32_timer.h b/radio/src/targets/common/arm/stm32/stm32_timer.h index e597fd1a5..c7c137967 100644 --- a/radio/src/targets/common/arm/stm32/stm32_timer.h +++ b/radio/src/targets/common/arm/stm32/stm32_timer.h @@ -25,3 +25,5 @@ void stm32_timer_enable_clock(TIM_TypeDef *TIMx); void stm32_timer_disable_clock(TIM_TypeDef *TIMx); + +bool stm32_timer_is_clock_enabled(TIM_TypeDef *TIMx); diff --git a/radio/src/targets/common/arm/stm32/stm32_ws2812.cpp b/radio/src/targets/common/arm/stm32/stm32_ws2812.cpp new file mode 100644 index 000000000..93e796195 --- /dev/null +++ b/radio/src/targets/common/arm/stm32/stm32_ws2812.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) EdgeTx + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#include "stm32_ws2812.h" +#include "stm32_dma.h" + +#if defined(DEBUG_WS2812) + // LED_STRIP_DEBUG_GPIO && LED_STRIP_DEBUG_GPIO_PIN + #include "hal.h" +#endif + +#include "definitions.h" + +#include + +// Pixel values +static uint8_t _led_colors[WS2812_BYTES_PER_LED * WS2812_MAX_LEDS]; + +// Timer used +// static const stm32_pulse_timer_t* _led_timer; + +// LED strip length +static uint8_t _led_strip_len; + +// DMA buffer contains data for 2 LEDs and is filled +// half by half on DMA HT and TC IRQs +#define WS2821_DMA_BUFFER_HALF_LEN (WS2812_BYTES_PER_LED * 8) +#define WS2821_DMA_BUFFER_LEN (WS2821_DMA_BUFFER_HALF_LEN * 2) + +#define WS2812_FREQ 800000UL // 800 kHz +#define WS2812_TIMER_PERIOD 20UL +#define WS2812_ONE (3 * WS2812_TIMER_PERIOD / 4) +#define WS2812_ZERO (1 * WS2812_TIMER_PERIOD / 4) +#define WS2812_DMA_IRQ_PRIO 3 + +// Debug facility +#if defined(LED_STRIP_DEBUG_GPIO) && defined(LED_STRIP_DEBUG_GPIO_PIN) + +#define WS2812_DBG_INIT _led_dbg_init() + +#define WS2812_DBG_HIGH \ + LL_GPIO_SetOutputPin(LED_STRIP_DEBUG_GPIO, LED_STRIP_DEBUG_GPIO_PIN) + +#define WS2812_DBG_LOW \ + LL_GPIO_ResetOutputPin(LED_STRIP_DEBUG_GPIO, LED_STRIP_DEBUG_GPIO_PIN) + +static void _led_dbg_init() { + LL_GPIO_InitTypeDef pinInit{0}; + pinInit.Mode = LL_GPIO_MODE_OUTPUT; + pinInit.Pin = LED_STRIP_DEBUG_GPIO_PIN; + LL_GPIO_Init(LED_STRIP_DEBUG_GPIO, &pinInit); + WS2812_DBG_LOW; +} + +#else // LED_STRIP_DEBUG_GPIO && LED_STRIP_DEBUG_GPIO_PIN + +#define WS2812_DBG_INIT +#define WS2812_DBG_HIGH +#define WS2812_DBG_LOW + +#endif + +#if defined(STM32_SUPPORT_32BIT_TIMERS) +typedef uint32_t led_timer_value_t; +#else +typedef uint16_t led_timer_value_t; +#endif + +// DMA buffer contains pulses for 2 LED at a time +// (allows for refill at HT and TC) +static led_timer_value_t _led_dma_buffer[WS2821_DMA_BUFFER_LEN] __DMA; + +static uint8_t _led_seq_cnt; + +static void _fill_byte(uint8_t c, led_timer_value_t* dma_buffer) +{ + for (int i = 0; i < 8; i++) { + dma_buffer[i] = c & 1 ? WS2812_ONE : WS2812_ZERO; + c >>= 1; + } +} + +static void _fill_pulses(const uint8_t* colors, led_timer_value_t* dma_buffer, uint32_t len) +{ + for (uint32_t i = 0; i < len; i++) { + _fill_byte(*colors, dma_buffer); + dma_buffer += 8; + colors++; + } +} + +static inline uint32_t _calc_offset(uint8_t tc) +{ + return tc * WS2821_DMA_BUFFER_HALF_LEN; +} + +static void _update_dma_buffer(const stm32_pulse_timer_t* tim, uint8_t tc) +{ + WS2812_DBG_HIGH; + if (_led_seq_cnt < _led_strip_len) { + + auto idx = WS2812_BYTES_PER_LED * _led_seq_cnt; + auto offset = _calc_offset(tc); + _fill_pulses(&_led_colors[idx], &_led_dma_buffer[offset], WS2812_BYTES_PER_LED); + _led_seq_cnt++; + + } else if(_led_seq_cnt < _led_strip_len + WS2812_TRAILING_RESET) { + + // no need to reset the buffer after 2 cycles + if (_led_seq_cnt < _led_strip_len + 2) { + auto offset = _calc_offset(tc); + auto size = WS2821_DMA_BUFFER_HALF_LEN * sizeof(led_timer_value_t); + memset(&_led_dma_buffer[offset], 0, size); + } + _led_seq_cnt++; + + } else { + + LL_DMA_DisableIT_TC(tim->DMAx, tim->DMA_Stream); + LL_DMA_DisableIT_HT(tim->DMAx, tim->DMA_Stream); + LL_DMA_DisableStream(tim->DMAx, tim->DMA_Stream); + LL_TIM_CC_DisableChannel(tim->TIMx, tim->TIM_Channel); + } + WS2812_DBG_LOW; +} + +void ws2812_dma_isr(const stm32_pulse_timer_t* tim) +{ + if (LL_DMA_IsEnabledIT_HT(tim->DMAx, tim->DMA_Stream) && + stm32_dma_check_ht_flag(tim->DMAx, tim->DMA_Stream)) { + _update_dma_buffer(tim, 0); + } + + if (LL_DMA_IsEnabledIT_TC(tim->DMAx, tim->DMA_Stream) && + stm32_dma_check_tc_flag(tim->DMAx, tim->DMA_Stream)) { + _update_dma_buffer(tim, 1); + } +} + +static void _led_set_dma_periph_addr(const stm32_pulse_timer_t* tim) +{ + volatile uint32_t* cmp_reg = nullptr; + switch(tim->TIM_Channel) { + case LL_TIM_CHANNEL_CH1: + case LL_TIM_CHANNEL_CH1N: + cmp_reg = &tim->TIMx->CCR1; + break; + case LL_TIM_CHANNEL_CH2: + cmp_reg = &tim->TIMx->CCR2; + break; + case LL_TIM_CHANNEL_CH3: + cmp_reg = &tim->TIMx->CCR3; + break; + case LL_TIM_CHANNEL_CH4: + cmp_reg = &tim->TIMx->CCR4; + break; + } + + LL_DMA_SetPeriphAddress(tim->DMAx, tim->DMA_Stream, (uint32_t)cmp_reg); +} + +static void _init_timer(const stm32_pulse_timer_t* tim) +{ + stm32_pulse_init(tim, WS2812_FREQ * WS2812_TIMER_PERIOD); + stm32_pulse_config_output(tim, true, LL_TIM_OCMODE_PWM1, 0); + LL_TIM_SetAutoReload(tim->TIMx, WS2812_TIMER_PERIOD - 1); + + // pulse driver uses DMA to ARR, but we need CCRx + _led_set_dma_periph_addr(tim); + + LL_DMA_SetMode(tim->DMAx, tim->DMA_Stream, LL_DMA_MODE_CIRCULAR); + LL_DMA_SetDataLength(tim->DMAx, tim->DMA_Stream, WS2821_DMA_BUFFER_LEN); + LL_DMA_SetMemoryAddress(tim->DMAx, tim->DMA_Stream, (uint32_t)_led_dma_buffer); + + // we need to use a higher prio to avoid having + // issues with some other things used during boot + NVIC_SetPriority(tim->DMA_IRQn, WS2812_DMA_IRQ_PRIO); +} + +void ws2812_init(const stm32_pulse_timer_t* timer, uint8_t strip_len) +{ + WS2812_DBG_INIT; + + memset(_led_colors, 0, sizeof(_led_colors)); + memset(_led_dma_buffer, 0, sizeof(_led_dma_buffer)); + + if (strip_len <= WS2812_MAX_LEDS) { + _led_strip_len = strip_len; + } else { + _led_strip_len = WS2812_MAX_LEDS; + } + + _init_timer(timer); +} + +void ws2812_set_color(uint8_t led, uint8_t r, uint8_t g, uint8_t b) +{ + if (led >= _led_strip_len) return; + + uint8_t* pixel = &_led_colors[led * WS2812_BYTES_PER_LED]; + pixel[0] = g; + pixel[1] = r; + pixel[2] = b; +} + +void ws2812_update(const stm32_pulse_timer_t* tim) +{ + WS2812_DBG_HIGH; + if (!stm32_pulse_if_not_running_disable(tim)) return; + + _led_seq_cnt = 0; + memset(_led_dma_buffer, 0, sizeof(_led_dma_buffer)); + + LL_DMA_EnableIT_HT(tim->DMAx, tim->DMA_Stream); + LL_DMA_EnableIT_TC(tim->DMAx, tim->DMA_Stream); + LL_DMA_EnableStream(tim->DMAx, tim->DMA_Stream); + + LL_TIM_EnableDMAReq_UPDATE(tim->TIMx); + LL_TIM_CC_EnableChannel(tim->TIMx, tim->TIM_Channel); + LL_TIM_EnableCounter(tim->TIMx); + + WS2812_DBG_LOW; +} diff --git a/radio/src/targets/common/arm/stm32/stm32_ws2812.h b/radio/src/targets/common/arm/stm32/stm32_ws2812.h new file mode 100644 index 000000000..cc3323038 --- /dev/null +++ b/radio/src/targets/common/arm/stm32/stm32_ws2812.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) EdgeTx + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program 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. + */ + +#pragma once + +#include "stm32_pulse_driver.h" + +// RGB +#define WS2812_BYTES_PER_LED 3 +#define WS2812_MAX_LEDS 8 + +// Number of LED periods used for trailing reset +#define WS2812_TRAILING_RESET 10 + +void ws2812_init(const stm32_pulse_timer_t* timer, uint8_t strip_len); +void ws2812_update(const stm32_pulse_timer_t* timer); +void ws2812_dma_isr(const stm32_pulse_timer_t* timer); + +void ws2812_set_color(uint8_t led, uint8_t r, uint8_t g, uint8_t b); diff --git a/radio/src/targets/simu/led_driver.cpp b/radio/src/targets/simu/led_driver.cpp index ede5cc2c6..34d1c77a8 100644 --- a/radio/src/targets/simu/led_driver.cpp +++ b/radio/src/targets/simu/led_driver.cpp @@ -29,3 +29,5 @@ void ledOff() {} void fsLedOn(uint8_t) {} void fsLedOff(uint8_t) {} bool getFSLedState(uint8_t) { return false;} +void rgbSetLedColor(unsigned char, unsigned char, unsigned char, unsigned char) {} +void rgbLedColorApply() {} diff --git a/radio/src/targets/taranis/board.cpp b/radio/src/targets/taranis/board.cpp index 5ecc3d929..228b0ccf2 100644 --- a/radio/src/targets/taranis/board.cpp +++ b/radio/src/targets/taranis/board.cpp @@ -19,6 +19,8 @@ * GNU General Public License for more details. */ +#include "stm32_ws2812.h" + #include "hal/adc_driver.h" #include "hal/trainer_driver.h" #include "hal/switch_driver.h" @@ -28,6 +30,7 @@ #include "boards/generic_stm32/module_ports.h" #include "boards/generic_stm32/intmodule_heartbeat.h" #include "boards/generic_stm32/analog_inputs.h" +#include "boards/generic_stm32/rgb_leds.h" #include "debug.h" #include "rtc.h" @@ -224,6 +227,16 @@ void boardInit() timersInit(); usbInit(); +#if defined(LED_STRIP_GPIO) + extern const stm32_pulse_timer_t _led_timer; + + ws2812_init(&_led_timer, LED_STRIP_LENGTH); + for (uint8_t i = 0; i < LED_STRIP_LENGTH; i++) { + ws2812_set_color(i, 0, 0, 50); + } + ws2812_update(&_led_timer); +#endif + #if defined(DEBUG) serialSetMode(SP_AUX1, UART_MODE_DEBUG); // indicate AUX1 is used serialInit(SP_AUX1, UART_MODE_DEBUG); // early AUX1 init diff --git a/radio/src/translations.cpp b/radio/src/translations.cpp index ac2322179..7c4ed4ef1 100644 --- a/radio/src/translations.cpp +++ b/radio/src/translations.cpp @@ -186,6 +186,7 @@ const char STR_SF_SET_TIMER[] = TR_SF_SET_TIMER; const char STR_SF_SWITCH[] = TR_SF_SWITCH; const char STR_SF_TRAINER[] = TR_SF_TRAINER; const char STR_SF_VARIO[] = TR_SF_VARIO; +const char STR_SF_RGBLEDS[] = TR_SF_RGBLEDS; const char STR_SF_VOLUME[] = TR_SF_VOLUME; const char STR_SF_RACING_MODE[] = TR_SF_RACING_MODE; const char STR_SF_SAFETY[] = TR_SF_SAFETY; diff --git a/radio/src/translations.h b/radio/src/translations.h index e5aa2ef5c..a4d143416 100644 --- a/radio/src/translations.h +++ b/radio/src/translations.h @@ -324,6 +324,7 @@ extern const char STR_SF_SET_SCREEN[]; extern const char STR_SF_SWITCH[]; extern const char STR_SF_TRAINER[]; extern const char STR_SF_VARIO[]; +extern const char STR_SF_RGBLEDS[]; extern const char STR_SF_VOLUME[]; extern const char STR_SF_RACING_MODE[]; extern const char STR_SF_SCREENSHOT[]; diff --git a/radio/src/translations/cn.h b/radio/src/translations/cn.h index 7528be77f..25ece2306 100644 --- a/radio/src/translations/cn.h +++ b/radio/src/translations/cn.h @@ -90,6 +90,7 @@ #define TR_SF_FAILSAFE "设置失控保护" #define TR_SF_RANGE_CHECK "测距模式" #define TR_SF_MOD_BIND "模块对频" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "播放声音" #define TR_PLAY_TRACK "播放音频文件" diff --git a/radio/src/translations/cz.h b/radio/src/translations/cz.h index fb65c14d3..f00494036 100644 --- a/radio/src/translations/cz.h +++ b/radio/src/translations/cz.h @@ -104,6 +104,7 @@ #define TR_SF_FAILSAFE "Nastavit Failsafe" #define TR_SF_RANGE_CHECK "Kontrola dosahu" #define TR_SF_MOD_BIND "ModuleBind" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND TR3("\200\201Zvuk", "\200\201Zvuk", "Hrát zvuk") #define TR_PLAY_TRACK TR3("\200\201Stopa", "\200\201Stopa", "Přehrát wav") diff --git a/radio/src/translations/da.h b/radio/src/translations/da.h index 3a53e611b..b2f1a8f9f 100644 --- a/radio/src/translations/da.h +++ b/radio/src/translations/da.h @@ -102,6 +102,7 @@ #define TR_SF_FAILSAFE TR("Sæt fejlsik", "Sæt fejlsikring") #define TR_SF_RANGE_CHECK TR("Afst. check", "Afstand kontrol") #define TR_SF_MOD_BIND "Modul Bind" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Spil lyd" #define TR_PLAY_TRACK TR("Afspil lyd", "Afspil lydfil") diff --git a/radio/src/translations/de.h b/radio/src/translations/de.h index 78c0119d6..299e1c4ab 100644 --- a/radio/src/translations/de.h +++ b/radio/src/translations/de.h @@ -99,7 +99,8 @@ #define TR_SF_FAILSAFE "SetFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "ModuleBind" - +#define TR_SF_RGBLEDS "RGB leds" + #define TR_SOUND "Spiel Töne" #define TR_PLAY_TRACK "Sag Text" #define TR_PLAY_VALUE "Sag Wert" diff --git a/radio/src/translations/en.h b/radio/src/translations/en.h index 5cf319a42..200efbee4 100644 --- a/radio/src/translations/en.h +++ b/radio/src/translations/en.h @@ -95,7 +95,8 @@ #define TR_SF_FAILSAFE "SetFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "ModuleBind" - +#define TR_SF_RGBLEDS "RGB leds" + #define TR_SOUND "Play Sound" #define TR_PLAY_TRACK "Play Track" #define TR_PLAY_VALUE TR("Play Val","Play Value") diff --git a/radio/src/translations/es.h b/radio/src/translations/es.h index c1a364d9d..174c0b8ae 100644 --- a/radio/src/translations/es.h +++ b/radio/src/translations/es.h @@ -97,6 +97,7 @@ #define TR_SF_FAILSAFE "Failsafe" #define TR_SF_RANGE_CHECK "CheckRango" #define TR_SF_MOD_BIND "Enl.módulo" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Oir sonido" #define TR_PLAY_TRACK "Oir pista" diff --git a/radio/src/translations/fi.h b/radio/src/translations/fi.h index 3c48a95bc..fffec159c 100644 --- a/radio/src/translations/fi.h +++ b/radio/src/translations/fi.h @@ -102,6 +102,7 @@ #define TR_SF_FAILSAFE "SetFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "ModuleBind" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Play Sound" #define TR_PLAY_TRACK "Play Track" diff --git a/radio/src/translations/fr.h b/radio/src/translations/fr.h index 6139433c7..21223897a 100644 --- a/radio/src/translations/fr.h +++ b/radio/src/translations/fr.h @@ -101,6 +101,7 @@ #define TR_SF_FAILSAFE "Régle Failsafe" #define TR_SF_RANGE_CHECK "Test Port." #define TR_SF_MOD_BIND "Bind" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Jouer son" #define TR_PLAY_TRACK "Jouer fichier" @@ -129,13 +130,10 @@ #define TR_VFSWRESET TR_FSW_RESET_TIMERS, "Tout", TR_FSW_RESET_TELEM #define TR_FUNCSOUNDS TR("Bp1","Beep1"),TR("Bp2","Beep2"),TR("Bp3","Beep3"),TR("Wrn1","Warn1"),TR("Wrn2","Warn2"),TR("Chee","Cheep"),TR("Rata","Ratata"),"Tick",TR("Sirn","Siren"),"Ring",TR("SciF","SciFi"),TR("Robt","Robot"),TR("Chrp","Chirp"),"Tada",TR("Crck","Crickt"),TR("Alrm","AlmClk") - - #if LCD_W >= 212 #else #endif - #define LENGTH_UNIT_IMP "ft" #define SPEED_UNIT_IMP "mph" #define LENGTH_UNIT_METR "m" diff --git a/radio/src/translations/it.h b/radio/src/translations/it.h index 2f5f2ef6e..6f6b1eaae 100644 --- a/radio/src/translations/it.h +++ b/radio/src/translations/it.h @@ -100,6 +100,7 @@ #define TR_SF_FAILSAFE "SetFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "BindModulo" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Suona" #define TR_PLAY_TRACK "Suona Traccia" diff --git a/radio/src/translations/jp.h b/radio/src/translations/jp.h index 4e70a8191..6fb70d084 100644 --- a/radio/src/translations/jp.h +++ b/radio/src/translations/jp.h @@ -95,6 +95,7 @@ #define TR_SF_FAILSAFE "フェイルセーフ" #define TR_SF_RANGE_CHECK "レンジチェック" #define TR_SF_MOD_BIND "モジュールバインド" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "サウンド再生" #define TR_PLAY_TRACK "音源再生" diff --git a/radio/src/translations/nl.h b/radio/src/translations/nl.h index 204769af9..9f4627f0a 100644 --- a/radio/src/translations/nl.h +++ b/radio/src/translations/nl.h @@ -97,6 +97,7 @@ #define TR_SF_FAILSAFE "SetFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "ModuleBind" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Geluid" #define TR_PLAY_TRACK "Play Track" diff --git a/radio/src/translations/pl.h b/radio/src/translations/pl.h index cc25c4123..76d5421df 100644 --- a/radio/src/translations/pl.h +++ b/radio/src/translations/pl.h @@ -96,6 +96,7 @@ #define TR_SF_FAILSAFE "SetFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "ModuleBind" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "GrajDźwięk" #define TR_PLAY_TRACK "GrajŚcieżk" diff --git a/radio/src/translations/pt.h b/radio/src/translations/pt.h index 1b06ca4df..13641d628 100644 --- a/radio/src/translations/pt.h +++ b/radio/src/translations/pt.h @@ -98,6 +98,7 @@ #define TR_SF_FAILSAFE "DefFailsafe" #define TR_SF_RANGE_CHECK "RangeCheck" #define TR_SF_MOD_BIND "BindModulo" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Tocar Som" #define TR_PLAY_TRACK "Tocar Trilha" diff --git a/radio/src/translations/se.h b/radio/src/translations/se.h index a5fb0cab3..ded656712 100644 --- a/radio/src/translations/se.h +++ b/radio/src/translations/se.h @@ -106,6 +106,7 @@ #define TR_SF_FAILSAFE "Sätt failsafe" #define TR_SF_RANGE_CHECK "Range check" #define TR_SF_MOD_BIND "Bind modul" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "Spela ljud" #define TR_PLAY_TRACK "Spela upp" diff --git a/radio/src/translations/tw.h b/radio/src/translations/tw.h index c0fdc3d89..125024a89 100644 --- a/radio/src/translations/tw.h +++ b/radio/src/translations/tw.h @@ -96,6 +96,7 @@ #define TR_SF_FAILSAFE "設置失控保護" #define TR_SF_RANGE_CHECK "測距模式" #define TR_SF_MOD_BIND "模塊對頻" +#define TR_SF_RGBLEDS "RGB leds" #define TR_SOUND "播放聲音" #define TR_PLAY_TRACK "播放音頻文件" diff --git a/radio/util/addtr.py b/radio/util/addtr.py old mode 100644 new mode 100755 index c197869a3..5b91a49c9 --- a/radio/util/addtr.py +++ b/radio/util/addtr.py @@ -19,7 +19,7 @@ def addLine(filename, newline, after): def modifyTranslations(constant, translation, after): - for filename in glob.glob('translations/*.h.txt'): + for filename in glob.glob('translations/*.h'): newline = "#define " + constant + " " * max(1, 31 - len(constant)) + '"' + translation + '"' addLine(filename, newline, after + " ")