feat: Support for WS2812 RGB LEDs (#3909)

Co-authored-by: 3djc <lesitewebdejc@hotmail.com>
This commit is contained in:
Raphael Coeffic
2023-10-14 14:06:55 +10:00
committed by GitHub
co-authored by 3djc
parent f8ad3a7bda
commit b22a1b5d32
60 changed files with 788 additions and 122 deletions
+4
View File
@@ -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)))
+1
View File
@@ -164,6 +164,7 @@ namespace Board {
HasIntModuleHeartbeatGPIO,
HasTrainerModuleCPPM,
HasTrainerModuleSBUS,
HasLedStripGPIO
};
struct SwitchInfo
@@ -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;
@@ -73,6 +73,7 @@ enum AssignFunc {
FuncDisableTouch,
FuncSetScreen,
FuncDisableAudioAmp,
FuncRGBLed,
FuncCount,
FuncReserve = -1
};
@@ -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<CustomFunctionData>::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<CustomFunctionData>::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<CustomFunctionData>::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<CustomFunctionData>::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;
+15 -3
View File
@@ -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);
@@ -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)
+115
View File
@@ -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 <FreeRTOS/include/FreeRTOS.h>
#include <FreeRTOS/include/timers.h>
#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
+29
View File
@@ -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();
+1
View File
@@ -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
+3
View File
@@ -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;
@@ -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;
+7 -3
View File
@@ -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;
}
-5
View File
@@ -23,13 +23,8 @@
#include <stdint.h>
enum PulseGenerationType {
ETX_PWM=0,
ETX_TOGGLE
};
typedef struct {
uint8_t type;
uint8_t polarity;
uint16_t cmp_val;
} etx_timer_config_t;
+55 -1
View File
@@ -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 )
+1 -1
View File
@@ -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 {
+13
View File
@@ -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;
}
+9
View File
@@ -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);
}
+1 -1
View File
@@ -664,7 +664,7 @@ enum FunctionsActive {
FUNCTION_DISABLE_TOUCH,
#endif
#if defined(AUDIO_MUTE_GPIO)
FUNCTION_DISABLE_AUDIO_AMP
FUNCTION_DISABLE_AUDIO_AMP,
#endif
};
-2
View File
@@ -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,
};
+1
View File
@@ -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 + "/"
@@ -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[] = {
@@ -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<uint8_t>(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;
@@ -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[] = {
@@ -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[] = {
@@ -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[] = {
@@ -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[] = {
@@ -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[] = {
@@ -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[] = {
@@ -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[] = {
@@ -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[] = {
@@ -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[] = {
@@ -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);
}
@@ -24,6 +24,12 @@
#include <stdint.h>
#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);
@@ -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);
}
@@ -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)
@@ -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;
}
@@ -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);
@@ -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 <string.h>
// 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;
}
@@ -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);
+2
View File
@@ -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() {}
+13
View File
@@ -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
+1
View File
@@ -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;
+1
View File
@@ -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[];
+1
View File
@@ -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 "播放音频文件"
+1
View File
@@ -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")
+1
View File
@@ -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")
+2 -1
View File
@@ -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"
+2 -1
View File
@@ -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")
+1
View File
@@ -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"
+1
View File
@@ -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"
+1 -3
View File
@@ -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"
+1
View File
@@ -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"
+1
View File
@@ -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 "音源再生"
+1
View File
@@ -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"
+1
View File
@@ -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"
+1
View File
@@ -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"
+1
View File
@@ -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"
+1
View File
@@ -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 "播放音頻文件"
Regular → Executable
+1 -1
View File
@@ -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 + " ")