diff --git a/Makefile b/Makefile
index 1a0e09d36a..5d4c39945f 100644
--- a/Makefile
+++ b/Makefile
@@ -665,6 +665,7 @@ COMMON_SRC = \
build/version.c \
$(TARGET_DIR_SRC) \
main.c \
+ common/bitarray.c \
common/encoding.c \
common/filter.c \
common/maths.c \
@@ -677,6 +678,7 @@ COMMON_SRC = \
config/config_streamer.c \
drivers/adc.c \
drivers/buf_writer.c \
+ drivers/bus_i2c_config.c \
drivers/bus_i2c_soft.c \
drivers/bus_spi.c \
drivers/bus_spi_soft.c \
@@ -735,6 +737,7 @@ FC_SRC = \
fc/fc_rc.c \
fc/rc_adjustments.c \
fc/rc_controls.c \
+ fc/rc_modes.c \
fc/cli.c \
fc/settings.c \
flight/altitude.c \
@@ -896,7 +899,11 @@ SPEED_OPTIMISED_SRC := $(SPEED_OPTIMISED_SRC) \
io/osd_slave.c
SIZE_OPTIMISED_SRC := $(SIZE_OPTIMISED_SRC) \
+ drivers/bus_i2c_config.c \
drivers/serial_escserial.c \
+ drivers/serial_pinconfig.c \
+ drivers/serial_uart_init.c \
+ drivers/serial_uart_pinconfig.c \
drivers/vtx_rtc6705_soft_spi.c \
drivers/vtx_rtc6705.c \
drivers/vtx_common.c \
@@ -907,9 +914,6 @@ SIZE_OPTIMISED_SRC := $(SIZE_OPTIMISED_SRC) \
config/feature.c \
config/parameter_group.c \
config/config_streamer.c \
- drivers/serial_pinconfig.c \
- drivers/serial_uart_init.c \
- drivers/serial_uart_pinconfig.c \
io/serial_4way.c \
io/serial_4way_avrootloader.c \
io/serial_4way_stk500v2.c \
@@ -1014,7 +1018,8 @@ STM32F7xx_COMMON_SRC = \
drivers/serial_uart_stm32f7xx.c \
drivers/serial_uart_hal.c
-F7EXCLUDES = drivers/bus_spi.c \
+F7EXCLUDES = \
+ drivers/bus_spi.c \
drivers/bus_i2c.c \
drivers/timer.c \
drivers/serial_uart.c
@@ -1023,6 +1028,7 @@ SITLEXCLUDES = \
drivers/adc.c \
drivers/bus_spi.c \
drivers/bus_i2c.c \
+ drivers/bus_i2c_config.c \
drivers/dma.c \
drivers/pwm_output.c \
drivers/timer.c \
diff --git a/src/main/blackbox/blackbox.c b/src/main/blackbox/blackbox.c
index c665bddb48..40cf9c3bad 100644
--- a/src/main/blackbox/blackbox.c
+++ b/src/main/blackbox/blackbox.c
@@ -47,6 +47,7 @@
#include "fc/config.h"
#include "fc/controlrate_profile.h"
#include "fc/rc_controls.h"
+#include "fc/rc_modes.h"
#include "fc/runtime_config.h"
#include "flight/failsafe.h"
@@ -322,10 +323,10 @@ typedef struct blackboxSlowState_s {
} __attribute__((__packed__)) blackboxSlowState_t; // We pack this struct so that padding doesn't interfere with memcmp()
//From mixer.c:
-extern uint16_t motorOutputHigh, motorOutputLow;
+extern float motorOutputHigh, motorOutputLow;
//From rc_controls.c
-extern uint32_t rcModeActivationMask;
+extern boxBitmask_t rcModeActivationMask;
static BlackboxState blackboxState = BLACKBOX_STATE_DISABLED;
@@ -753,7 +754,7 @@ static void writeSlowFrame(void)
*/
static void loadSlowState(blackboxSlowState_t *slow)
{
- slow->flightModeFlags = rcModeActivationMask; //was flightModeFlags;
+ memcpy(&slow->flightModeFlags, &rcModeActivationMask, sizeof(slow->flightModeFlags)); //was flightModeFlags;
slow->stateFlags = stateFlags;
slow->failsafePhase = failsafePhase();
slow->rxSignalReceived = rxIsReceivingSignal();
@@ -861,7 +862,7 @@ static void blackboxStart(void)
*/
blackboxBuildConditionCache();
- blackboxModeActivationConditionPresent = isModeActivationConditionPresent(modeActivationConditions(0), BOXBLACKBOX);
+ blackboxModeActivationConditionPresent = isModeActivationConditionPresent(BOXBLACKBOX);
blackboxResetIterationTimers();
@@ -870,7 +871,7 @@ static void blackboxStart(void)
* it finally plays the beep for this arming event.
*/
blackboxLastArmingBeep = getArmingBeepTimeMicros();
- blackboxLastFlightModeFlags = rcModeActivationMask; // record startup status
+ memcpy(&blackboxLastFlightModeFlags, &rcModeActivationMask, sizeof(blackboxLastFlightModeFlags)); // record startup status
blackboxSetState(BLACKBOX_STATE_PREPARE_LOG_FILE);
}
@@ -1187,6 +1188,9 @@ static bool sendFieldDefinition(char mainFrameChar, char deltaFrameChar, const v
*/
static bool blackboxWriteSysinfo(void)
{
+ const uint16_t motorOutputLowInt = lrintf(motorOutputLow);
+ const uint16_t motorOutputHighInt = lrintf(motorOutputHigh);
+
// Make sure we have enough room in the buffer for our longest line (as of this writing, the "Firmware date" line)
if (blackboxDeviceReserveBufferSpace(64) != BLACKBOX_RESERVE_SUCCESS) {
return false;
@@ -1202,7 +1206,7 @@ static bool blackboxWriteSysinfo(void)
BLACKBOX_PRINT_HEADER_LINE("minthrottle", "%d", motorConfig()->minthrottle);
BLACKBOX_PRINT_HEADER_LINE("maxthrottle", "%d", motorConfig()->maxthrottle);
BLACKBOX_PRINT_HEADER_LINE("gyro_scale","0x%x", castFloatBytesToInt(1.0f));
- BLACKBOX_PRINT_HEADER_LINE("motorOutput", "%d,%d", motorOutputLow,motorOutputHigh);
+ BLACKBOX_PRINT_HEADER_LINE("motorOutput", "%d,%d", motorOutputLowInt,motorOutputHighInt);
BLACKBOX_PRINT_HEADER_LINE("acc_1G", "%u", acc.dev.acc_1G);
BLACKBOX_PRINT_HEADER_LINE_CUSTOM(
@@ -1383,10 +1387,10 @@ static void blackboxCheckAndLogFlightMode(void)
flightLogEvent_flightMode_t eventData; // Add new data for current flight mode flags
// Use != so that we can still detect a change if the counter wraps
- if (rcModeActivationMask != blackboxLastFlightModeFlags) {
+ if (memcmp(&rcModeActivationMask, &blackboxLastFlightModeFlags, sizeof(blackboxLastFlightModeFlags))) {
eventData.lastFlags = blackboxLastFlightModeFlags;
- blackboxLastFlightModeFlags = rcModeActivationMask;
- eventData.flags = rcModeActivationMask;
+ memcpy(&blackboxLastFlightModeFlags, &rcModeActivationMask, sizeof(blackboxLastFlightModeFlags));
+ memcpy(&eventData.flags, &rcModeActivationMask, sizeof(eventData.flags));
blackboxLogEvent(FLIGHT_LOG_EVENT_FLIGHTMODE, (flightLogEventData_t *) &eventData);
}
diff --git a/src/main/cms/cms_menu_osd.c b/src/main/cms/cms_menu_osd.c
index bca4df0538..2a811bc7b3 100644
--- a/src/main/cms/cms_menu_osd.c
+++ b/src/main/cms/cms_menu_osd.c
@@ -137,7 +137,7 @@ OSD_Entry menuOsdActiveElemsEntries[] =
{"YAW PID", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_YAW_PIDS], 0},
{"PROFILES", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_PIDRATE_PROFILE], 0},
{"DEBUG", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_DEBUG], 0},
- {"BATT WARN", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_MAIN_BATT_WARNING], 0},
+ {"WARNINGS", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_WARNINGS], 0},
{"DISARMED", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_DISARMED], 0},
{"PIT ANG", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_PITCH_ANGLE], 0},
{"ROL ANG", OME_VISIBLE, NULL, &osdConfig_item_pos[OSD_ROLL_ANGLE], 0},
diff --git a/src/main/target/ALIENFLIGHTF3/initialisation.c b/src/main/common/bitarray.c
similarity index 57%
rename from src/main/target/ALIENFLIGHTF3/initialisation.c
rename to src/main/common/bitarray.c
index f435cb994c..53ca99127c 100644
--- a/src/main/target/ALIENFLIGHTF3/initialisation.c
+++ b/src/main/common/bitarray.c
@@ -15,34 +15,25 @@
* along with Cleanflight. If not, see .
*/
-#include
#include
+#include
-#include "platform.h"
-#include "drivers/bus_i2c.h"
-#include "drivers/bus_spi.h"
-#include "hardware_revision.h"
+#include "bitarray.h"
-void targetBusInit(void)
+#define BITARRAY_BIT_OP(array, bit, op) ((array)[(bit) / (sizeof((array)[0]) * 8)] op (1 << ((bit) % (sizeof((array)[0]) * 8))))
+
+bool bitArrayGet(const void *array, unsigned bit)
{
- #ifdef USE_SPI
- #ifdef USE_SPI_DEVICE_1
- spiInit(SPIDEV_1);
- #endif
- #ifdef USE_SPI_DEVICE_2
- spiInit(SPIDEV_2);
- #endif
- #ifdef USE_SPI_DEVICE_3
- if (hardwareRevision == AFF3_REV_2) {
- spiInit(SPIDEV_3);
- }
- #endif
- #ifdef USE_SPI_DEVICE_4
- spiInit(SPIDEV_4);
- #endif
- #endif
+ return BITARRAY_BIT_OP((uint32_t*)array, bit, &);
+}
+
+void bitArraySet(void *array, unsigned bit)
+{
+ BITARRAY_BIT_OP((uint32_t*)array, bit, |=);
+}
+
+void bitArrayClr(void *array, unsigned bit)
+{
+ BITARRAY_BIT_OP((uint32_t*)array, bit, &=~);
+}
- #ifdef USE_I2C
- i2cInit(I2C_DEVICE);
- #endif
-}
\ No newline at end of file
diff --git a/src/main/common/bitarray.h b/src/main/common/bitarray.h
new file mode 100644
index 0000000000..48032bb297
--- /dev/null
+++ b/src/main/common/bitarray.h
@@ -0,0 +1,20 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cleanflight is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cleanflight. If not, see .
+ */
+
+bool bitArrayGet(const void *array, unsigned bit);
+void bitArraySet(void *array, unsigned bit);
+void bitArrayClr(void *array, unsigned bit);
diff --git a/src/main/common/utils.h b/src/main/common/utils.h
index 983bd6879d..001181977f 100644
--- a/src/main/common/utils.h
+++ b/src/main/common/utils.h
@@ -20,6 +20,8 @@
#include
#include
+#define NOOP do {} while (0)
+
#define ARRAYLEN(x) (sizeof(x) / sizeof((x)[0]))
#define ARRAYEND(x) (&(x)[ARRAYLEN(x)])
diff --git a/src/main/config/config_eeprom.c b/src/main/config/config_eeprom.c
index b94b3512d5..0993ac50d5 100644
--- a/src/main/config/config_eeprom.c
+++ b/src/main/config/config_eeprom.c
@@ -38,10 +38,7 @@ static uint16_t eepromConfigSize;
typedef enum {
CR_CLASSICATION_SYSTEM = 0,
- CR_CLASSICATION_PROFILE1 = 1,
- CR_CLASSICATION_PROFILE2 = 2,
- CR_CLASSICATION_PROFILE3 = 3,
- CR_CLASSICATION_PROFILE_LAST = CR_CLASSICATION_PROFILE3,
+ CR_CLASSICATION_PROFILE_LAST = CR_CLASSICATION_SYSTEM,
} configRecordFlags_e;
#define CR_CLASSIFICATION_MASK (0x3)
@@ -178,23 +175,12 @@ static const configRecord_t *findEEPROM(const pgRegistry_t *reg, configRecordFla
bool loadEEPROM(void)
{
PG_FOREACH(reg) {
- configRecordFlags_e cls_start, cls_end;
- if (pgIsSystem(reg)) {
- cls_start = CR_CLASSICATION_SYSTEM;
- cls_end = CR_CLASSICATION_SYSTEM;
+ const configRecord_t *rec = findEEPROM(reg, CR_CLASSICATION_SYSTEM);
+ if (rec) {
+ // config from EEPROM is available, use it to initialize PG. pgLoad will handle version mismatch
+ pgLoad(reg, rec->pg, rec->size - offsetof(configRecord_t, pg), rec->version);
} else {
- cls_start = CR_CLASSICATION_PROFILE1;
- cls_end = CR_CLASSICATION_PROFILE_LAST;
- }
- for (configRecordFlags_e cls = cls_start; cls <= cls_end; cls++) {
- int profileIndex = cls - cls_start;
- const configRecord_t *rec = findEEPROM(reg, cls);
- if (rec) {
- // config from EEPROM is available, use it to initialize PG. pgLoad will handle version mismatch
- pgLoad(reg, profileIndex, rec->pg, rec->size - offsetof(configRecord_t, pg), rec->version);
- } else {
- pgReset(reg, profileIndex);
- }
+ pgReset(reg);
}
}
return true;
@@ -225,25 +211,11 @@ static bool writeSettingsToEEPROM(void)
.flags = 0
};
- if (pgIsSystem(reg)) {
- // write the only instance
- record.flags |= CR_CLASSICATION_SYSTEM;
- config_streamer_write(&streamer, (uint8_t *)&record, sizeof(record));
- crc = crc16_ccitt_update(crc, (uint8_t *)&record, sizeof(record));
- config_streamer_write(&streamer, reg->address, regSize);
- crc = crc16_ccitt_update(crc, reg->address, regSize);
- } else {
- // write one instance for each profile
- for (uint8_t profileIndex = 0; profileIndex < PG_PROFILE_COUNT; profileIndex++) {
- record.flags = 0;
- record.flags |= ((profileIndex + 1) & CR_CLASSIFICATION_MASK);
- config_streamer_write(&streamer, (uint8_t *)&record, sizeof(record));
- crc = crc16_ccitt_update(crc, (uint8_t *)&record, sizeof(record));
- const uint8_t *address = reg->address + (regSize * profileIndex);
- config_streamer_write(&streamer, address, regSize);
- crc = crc16_ccitt_update(crc, address, regSize);
- }
- }
+ record.flags |= CR_CLASSICATION_SYSTEM;
+ config_streamer_write(&streamer, (uint8_t *)&record, sizeof(record));
+ crc = crc16_ccitt_update(crc, (uint8_t *)&record, sizeof(record));
+ config_streamer_write(&streamer, reg->address, regSize);
+ crc = crc16_ccitt_update(crc, reg->address, regSize);
}
configFooter_t footer = {
diff --git a/src/main/config/parameter_group.c b/src/main/config/parameter_group.c
index 58dbe51e1a..a61b454820 100644
--- a/src/main/config/parameter_group.c
+++ b/src/main/config/parameter_group.c
@@ -34,15 +34,9 @@ const pgRegistry_t* pgFind(pgn_t pgn)
return NULL;
}
-static uint8_t *pgOffset(const pgRegistry_t* reg, uint8_t profileIndex)
+static uint8_t *pgOffset(const pgRegistry_t* reg)
{
- const uint16_t regSize = pgSize(reg);
-
- uint8_t *base = reg->address;
- if (!pgIsSystem(reg)) {
- base += (regSize * profileIndex);
- }
- return base;
+ return reg->address;
}
static void pgResetInstance(const pgRegistry_t *reg, uint8_t *base)
@@ -59,18 +53,9 @@ static void pgResetInstance(const pgRegistry_t *reg, uint8_t *base)
}
}
-void pgReset(const pgRegistry_t* reg, int profileIndex)
+void pgReset(const pgRegistry_t* reg)
{
- pgResetInstance(reg, pgOffset(reg, profileIndex));
-}
-
-void pgResetCurrent(const pgRegistry_t *reg)
-{
- if (pgIsSystem(reg)) {
- pgResetInstance(reg, reg->address);
- } else {
- pgResetInstance(reg, *reg->ptr);
- }
+ pgResetInstance(reg, pgOffset(reg));
}
bool pgResetCopy(void *copy, pgn_t pgn)
@@ -83,44 +68,26 @@ bool pgResetCopy(void *copy, pgn_t pgn)
return false;
}
-void pgLoad(const pgRegistry_t* reg, int profileIndex, const void *from, int size, int version)
+void pgLoad(const pgRegistry_t* reg, const void *from, int size, int version)
{
- pgResetInstance(reg, pgOffset(reg, profileIndex));
+ pgResetInstance(reg, pgOffset(reg));
// restore only matching version, keep defaults otherwise
if (version == pgVersion(reg)) {
const int take = MIN(size, pgSize(reg));
- memcpy(pgOffset(reg, profileIndex), from, take);
+ memcpy(pgOffset(reg), from, take);
}
}
-int pgStore(const pgRegistry_t* reg, void *to, int size, uint8_t profileIndex)
+int pgStore(const pgRegistry_t* reg, void *to, int size)
{
const int take = MIN(size, pgSize(reg));
- memcpy(to, pgOffset(reg, profileIndex), take);
+ memcpy(to, pgOffset(reg), take);
return take;
}
-
-void pgResetAll(int profileCount)
+void pgResetAll()
{
PG_FOREACH(reg) {
- if (pgIsSystem(reg)) {
- pgReset(reg, 0);
- } else {
- // reset one instance for each profile
- for (int profileIndex = 0; profileIndex < profileCount; profileIndex++) {
- pgReset(reg, profileIndex);
- }
- }
- }
-}
-
-void pgActivateProfile(int profileIndex)
-{
- PG_FOREACH(reg) {
- if (!pgIsSystem(reg)) {
- uint8_t *ptr = pgOffset(reg, profileIndex);
- *(reg->ptr) = ptr;
- }
+ pgReset(reg);
}
}
diff --git a/src/main/config/parameter_group.h b/src/main/config/parameter_group.h
index cbfe84de82..4890a95f13 100644
--- a/src/main/config/parameter_group.h
+++ b/src/main/config/parameter_group.h
@@ -34,14 +34,9 @@ typedef enum {
PGR_PGN_MASK = 0x0fff,
PGR_PGN_VERSION_MASK = 0xf000,
PGR_SIZE_MASK = 0x0fff,
- PGR_SIZE_SYSTEM_FLAG = 0x0000, // documentary
- PGR_SIZE_PROFILE_FLAG = 0x8000 // start using flags from the top bit down
+ PGR_SIZE_SYSTEM_FLAG = 0x0000 // documentary
} pgRegistryInternal_e;
-enum {
- PG_PROFILE_COUNT = 3
-};
-
// function that resets a single parameter group instance
typedef void (pgResetFunc)(void * /* base */, int /* size */);
@@ -60,8 +55,6 @@ typedef struct pgRegistry_s {
static inline uint16_t pgN(const pgRegistry_t* reg) {return reg->pgn & PGR_PGN_MASK;}
static inline uint8_t pgVersion(const pgRegistry_t* reg) {return (uint8_t)(reg->pgn >> 12);}
static inline uint16_t pgSize(const pgRegistry_t* reg) {return reg->size & PGR_SIZE_MASK;}
-static inline uint16_t pgIsSystem(const pgRegistry_t* reg) {return (reg->size & PGR_SIZE_PROFILE_FLAG) == 0;}
-static inline uint16_t pgIsProfile(const pgRegistry_t* reg) {return (reg->size & PGR_SIZE_PROFILE_FLAG) == PGR_SIZE_PROFILE_FLAG;}
#define PG_PACKED __attribute__((packed))
@@ -89,19 +82,11 @@ extern const uint8_t __pg_resetdata_end[];
#define PG_FOREACH(_name) \
for (const pgRegistry_t *(_name) = __pg_registry_start; (_name) < __pg_registry_end; _name++)
-#define PG_FOREACH_PROFILE(_name) \
- PG_FOREACH(_name) \
- if(pgIsSystem(_name)) \
- continue; \
- else \
- /**/
-
// Reset configuration to default (by name)
-// Only current profile is reset for profile based configs
-#define PG_RESET_CURRENT(_name) \
+#define PG_RESET(_name) \
do { \
extern const pgRegistry_t _name ##_Registry; \
- pgResetCurrent(&_name ## _Registry); \
+ pgReset(&_name ## _Registry); \
} while(0) \
/**/
@@ -124,15 +109,6 @@ extern const uint8_t __pg_resetdata_end[];
struct _dummy \
/**/
-// Declare profile config
-#define PG_DECLARE_PROFILE(_type, _name) \
- extern _type *_name ## _ProfileCurrent; \
- static inline const _type* _name(void) { return _name ## _ProfileCurrent; } \
- static inline _type* _name ## Mutable(void) { return _name ## _ProfileCurrent; } \
- struct _dummy \
- /**/
-
-
// Register system config
#define PG_REGISTER_I(_type, _name, _pgn, _version, _reset) \
_type _name ## _System; \
@@ -195,43 +171,6 @@ extern const uint8_t __pg_resetdata_end[];
/**/
#endif
-#ifdef UNIT_TEST
-# define _PG_PROFILE_CURRENT_DECL(_type, _name) \
- _type *_name ## _ProfileCurrent = &_name ## _Storage[0];
-#else
-# define _PG_PROFILE_CURRENT_DECL(_type, _name) \
- _type *_name ## _ProfileCurrent;
-#endif
-
-// register profile config
-#define PG_REGISTER_PROFILE_I(_type, _name, _pgn, _version, _reset) \
- STATIC_UNIT_TESTED _type _name ## _Storage[PG_PROFILE_COUNT]; \
- _PG_PROFILE_CURRENT_DECL(_type, _name) \
- extern const pgRegistry_t _name ## _Registry; \
- const pgRegistry_t _name ## _Registry PG_REGISTER_ATTRIBUTES = { \
- .pgn = _pgn | (_version << 12), \
- .size = sizeof(_type) | PGR_SIZE_PROFILE_FLAG, \
- .address = (uint8_t*)&_name ## _Storage, \
- .ptr = (uint8_t **)&_name ## _ProfileCurrent, \
- _reset, \
- } \
- /**/
-
-#define PG_REGISTER_PROFILE(_type, _name, _pgn, _version) \
- PG_REGISTER_PROFILE_I(_type, _name, _pgn, _version, .reset = {.ptr = 0}) \
- /**/
-
-#define PG_REGISTER_PROFILE_WITH_RESET_FN(_type, _name, _pgn, _version) \
- extern void pgResetFn_ ## _name(_type *); \
- PG_REGISTER_PROFILE_I(_type, _name, _pgn, _version, .reset = {.fn = (pgResetFunc*)&pgResetFn_ ## _name}) \
- /**/
-
-#define PG_REGISTER_PROFILE_WITH_RESET_TEMPLATE(_type, _name, _pgn, _version) \
- extern const _type pgResetTemplate_ ## _name; \
- PG_REGISTER_PROFILE_I(_type, _name, _pgn, _version, .reset = {.ptr = (void*)&pgResetTemplate_ ## _name}) \
- /**/
-
-
// Emit reset defaults for config.
// Config must be registered with PG_REGISTER__WITH_RESET_TEMPLATE macro
#define PG_RESET_TEMPLATE(_type, _name, ...) \
@@ -245,10 +184,8 @@ extern const uint8_t __pg_resetdata_end[];
const pgRegistry_t* pgFind(pgn_t pgn);
-void pgLoad(const pgRegistry_t* reg, int profileIndex, const void *from, int size, int version);
-int pgStore(const pgRegistry_t* reg, void *to, int size, uint8_t profileIndex);
-void pgResetAll(int profileCount);
-void pgResetCurrent(const pgRegistry_t *reg);
+void pgLoad(const pgRegistry_t* reg, const void *from, int size, int version);
+int pgStore(const pgRegistry_t* reg, void *to, int size);
+void pgResetAll();
bool pgResetCopy(void *copy, pgn_t pgn);
-void pgReset(const pgRegistry_t* reg, int profileIndex);
-void pgActivateProfile(int profileIndex);
+void pgReset(const pgRegistry_t* reg);
diff --git a/src/main/config/parameter_group_ids.h b/src/main/config/parameter_group_ids.h
index 77895849d0..e4b0bb3f59 100644
--- a/src/main/config/parameter_group_ids.h
+++ b/src/main/config/parameter_group_ids.h
@@ -106,7 +106,8 @@
#define PG_VTX_CONFIG 515
#define PG_SONAR_CONFIG 516
#define PG_ESC_SENSOR_CONFIG 517
-#define PG_BETAFLIGHT_END 517
+#define PG_I2C_CONFIG 518
+#define PG_BETAFLIGHT_END 518
// OSD configuration (subject to change)
diff --git a/src/main/drivers/bus.h b/src/main/drivers/bus.h
new file mode 100644
index 0000000000..d1c1961896
--- /dev/null
+++ b/src/main/drivers/bus.h
@@ -0,0 +1,24 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cleanflight is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cleanflight. If not, see .
+ */
+
+#pragma once
+
+#include "platform.h"
+
+#ifdef TARGET_BUS_INIT
+void targetBusInit(void);
+#endif
diff --git a/src/main/drivers/bus_i2c.h b/src/main/drivers/bus_i2c.h
index dd5ee8a8ec..bd27125994 100644
--- a/src/main/drivers/bus_i2c.h
+++ b/src/main/drivers/bus_i2c.h
@@ -19,14 +19,10 @@
#include "platform.h"
+#include "config/parameter_group.h"
#include "drivers/io_types.h"
#include "drivers/rcc_types.h"
-#define I2C_SHORT_TIMEOUT ((uint32_t)0x1000)
-#define I2C_LONG_TIMEOUT ((uint32_t)(10 * I2C_SHORT_TIMEOUT))
-#define I2C_DEFAULT_TIMEOUT I2C_SHORT_TIMEOUT
-
-
#ifndef I2C_DEVICE
#define I2C_DEVICE I2CINVALID
#endif
@@ -36,39 +32,27 @@ typedef enum I2CDevice {
I2CDEV_1 = 0,
I2CDEV_2,
I2CDEV_3,
-#ifdef USE_I2C_DEVICE_4
I2CDEV_4,
-#endif
- I2CDEV_COUNT
} I2CDevice;
-typedef struct i2cDevice_s {
- I2C_TypeDef *dev;
- ioTag_t scl;
- ioTag_t sda;
- rccPeriphTag_t rcc;
- bool overClock;
-#if !defined(STM32F303xC)
- uint8_t ev_irq;
- uint8_t er_irq;
+#if defined(STM32F1) || defined(STM32F3)
+#define I2CDEV_COUNT 2
+#elif defined(STM32F4)
+#define I2CDEV_COUNT 3
+#elif defined(STM32F7)
+#define I2CDEV_COUNT 4
+#else
+#define I2CDEV_COUNT 4
#endif
-#if defined(STM32F7)
- uint8_t af;
-#endif
-} i2cDevice_t;
-typedef struct i2cState_s {
- volatile bool error;
- volatile bool busy;
- volatile uint8_t addr;
- volatile uint8_t reg;
- volatile uint8_t bytes;
- volatile uint8_t writing;
- volatile uint8_t reading;
- volatile uint8_t* write_p;
- volatile uint8_t* read_p;
-} i2cState_t;
+typedef struct i2cConfig_s {
+ ioTag_t ioTagScl[I2CDEV_COUNT];
+ ioTag_t ioTagSda[I2CDEV_COUNT];
+ bool overClock[I2CDEV_COUNT];
+ bool pullUp[I2CDEV_COUNT];
+} i2cConfig_t;
+void i2cHardwareConfigure(void);
void i2cInit(I2CDevice device);
bool i2cWriteBuffer(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t len_, uint8_t *data);
bool i2cWrite(I2CDevice device, uint8_t addr_, uint8_t reg, uint8_t data);
diff --git a/src/main/drivers/bus_i2c_config.c b/src/main/drivers/bus_i2c_config.c
new file mode 100644
index 0000000000..74af3f76fe
--- /dev/null
+++ b/src/main/drivers/bus_i2c_config.c
@@ -0,0 +1,262 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cleanflight is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cleanflight. If not, see .
+ */
+
+/*
+ * Created by jflyper
+ */
+
+#include
+#include
+#include
+
+#include "platform.h"
+
+#include "build/build_config.h"
+#include "build/debug.h"
+
+#include "drivers/io.h"
+#include "drivers/bus_i2c.h"
+#include "drivers/bus_i2c_impl.h"
+
+#include "config/parameter_group.h"
+#include "config/parameter_group_ids.h"
+
+#if defined(USE_I2C) && !defined(SOFT_I2C)
+
+#ifdef I2C_FULL_RECONFIGURABILITY
+#if I2CDEV_COUNT >= 1
+#ifndef I2C1_SCL
+#define I2C1_SCL NONE
+#endif
+#ifndef I2C1_SDA
+#define I2C1_SDA NONE
+#endif
+#endif
+
+#if I2CDEV_COUNT >= 2
+#ifndef I2C2_SCL
+#define I2C2_SCL NONE
+#endif
+#ifndef I2C2_SDA
+#define I2C2_SDA NONE
+#endif
+#endif
+
+#if I2CDEV_COUNT >= 3
+#ifndef I2C3_SCL
+#define I2C3_SCL NONE
+#endif
+#ifndef I2C3_SDA
+#define I2C3_SDA NONE
+#endif
+#endif
+
+#if I2CDEV_COUNT >= 4
+#ifndef I2C4_SCL
+#define I2C4_SCL NONE
+#endif
+#ifndef I2C4_SDA
+#define I2C4_SDA NONE
+#endif
+#endif
+
+#else // I2C_FULL_RECONFIGURABILITY
+
+// Backward compatibility for exisiting targets
+
+#ifdef STM32F1
+#ifndef I2C1_SCL
+#define I2C1_SCL PB8
+#endif
+#ifndef I2C1_SDA
+#define I2C1_SDA PB9
+#endif
+#ifndef I2C2_SCL
+#define I2C2_SCL PB10
+#endif
+#ifndef I2C2_SDA
+#define I2C2_SDA PB11
+#endif
+#endif // STM32F1
+
+#ifdef STM32F3
+#ifndef I2C1_SCL
+#define I2C1_SCL PB6
+#endif
+#ifndef I2C1_SDA
+#define I2C1_SDA PB7
+#endif
+#ifndef I2C2_SCL
+#define I2C2_SCL PA9
+#endif
+#ifndef I2C2_SDA
+#define I2C2_SDA PA10
+#endif
+#endif // STM32F3
+
+#ifdef STM32F4
+#ifndef I2C1_SCL
+#define I2C1_SCL PB6
+#endif
+#ifndef I2C1_SDA
+#define I2C1_SDA PB7
+#endif
+#ifndef I2C2_SCL
+#define I2C2_SCL PB10
+#endif
+#ifndef I2C2_SDA
+#define I2C2_SDA PB11
+#endif
+#ifndef I2C3_SCL
+#define I2C3_SCL PA8
+#endif
+#ifndef I2C3_SDA
+#define I2C3_SDA PC9
+#endif
+#endif // STM32F4
+
+#ifdef STM32F7
+#ifndef I2C1_SCL
+#define I2C1_SCL PB6
+#endif
+#ifndef I2C1_SDA
+#define I2C1_SDA PB7
+#endif
+#ifndef I2C2_SCL
+#define I2C2_SCL PB10
+#endif
+#ifndef I2C2_SDA
+#define I2C2_SDA PB11
+#endif
+#ifndef I2C3_SCL
+#define I2C3_SCL PA8
+#endif
+#ifndef I2C3_SDA
+#define I2C3_SDA PB4
+#endif
+#ifndef I2C4_SCL
+#define I2C4_SCL PD12
+#endif
+#ifndef I2C4_SDA
+#define I2C4_SDA PD13
+#endif
+#endif // STM32F7
+
+#endif // I2C_FULL_RECONFIGURABILITY
+
+// Backward compatibility for overclocking and internal pullup.
+// These will eventually be configurable through PG-based configurator
+// (and/or probably through some cli extension).
+
+#ifndef I2C1_OVERCLOCK
+#define I2C1_OVERCLOCK false
+#endif
+#ifndef I2C2_OVERCLOCK
+#define I2C2_OVERCLOCK false
+#endif
+#ifndef I2C3_OVERCLOCK
+#define I2C3_OVERCLOCK false
+#endif
+#ifndef I2C4_OVERCLOCK
+#define I2C4_OVERCLOCK false
+#endif
+
+// Default values for internal pullup
+
+#if defined(USE_I2C_PULLUP)
+#define I2C1_PULLUP true
+#define I2C2_PULLUP true
+#define I2C3_PULLUP true
+#define I2C4_PULLUP true
+#else
+#define I2C1_PULLUP false
+#define I2C2_PULLUP false
+#define I2C3_PULLUP false
+#define I2C4_PULLUP false
+#endif
+
+typedef struct i2cDefaultConfig_s {
+ I2CDevice device;
+ ioTag_t ioTagScl, ioTagSda;
+ bool overClock;
+ bool pullUp;
+} i2cDefaultConfig_t;
+
+static const i2cDefaultConfig_t i2cDefaultConfig[] = {
+#ifdef USE_I2C_DEVICE_1
+ { I2CDEV_1, IO_TAG(I2C1_SCL), IO_TAG(I2C1_SDA), I2C1_OVERCLOCK, I2C1_PULLUP },
+#endif
+#ifdef USE_I2C_DEVICE_2
+ { I2CDEV_2, IO_TAG(I2C2_SCL), IO_TAG(I2C2_SDA), I2C2_OVERCLOCK, I2C2_PULLUP },
+#endif
+#ifdef USE_I2C_DEVICE_3
+ { I2CDEV_3, IO_TAG(I2C3_SCL), IO_TAG(I2C3_SDA), I2C3_OVERCLOCK, I2C3_PULLUP },
+#endif
+#ifdef USE_I2C_DEVICE_4
+ { I2CDEV_4, IO_TAG(I2C4_SCL), IO_TAG(I2C4_SDA), I2C4_OVERCLOCK, I2C4_PULLUP },
+#endif
+};
+
+PG_DECLARE(i2cConfig_t, i2cConfig);
+PG_REGISTER_WITH_RESET_FN(i2cConfig_t, i2cConfig, PG_I2C_CONFIG, 0);
+
+void pgResetFn_i2cConfig(i2cConfig_t *i2cConfig)
+{
+ memset(i2cConfig, 0, sizeof(*i2cConfig));
+
+ for (size_t index = 0 ; index < ARRAYLEN(i2cDefaultConfig) ; index++) {
+ const i2cDefaultConfig_t *defconf = &i2cDefaultConfig[index];
+ i2cConfig->ioTagScl[defconf->device] = defconf->ioTagScl;
+ i2cConfig->ioTagSda[defconf->device] = defconf->ioTagSda;
+ i2cConfig->overClock[defconf->device] = defconf->overClock;
+ i2cConfig->pullUp[defconf->device] = defconf->pullUp;
+ }
+}
+
+void i2cHardwareConfigure(void)
+{
+ const i2cConfig_t *pConfig = i2cConfig();
+
+ for (int index = 0 ; index < I2CDEV_COUNT ; index++) {
+ const i2cHardware_t *hardware = &i2cHardware[index];
+
+ if (!hardware->reg) {
+ continue;
+ }
+
+ I2CDevice device = hardware->device;
+ i2cDevice_t *pDev = &i2cDevice[device];
+
+ memset(pDev, 0, sizeof(*pDev));
+
+ for (int pindex = 0 ; pindex < I2C_PIN_SEL_MAX ; pindex++) {
+ if (pConfig->ioTagScl[device] == hardware->sclPins[pindex])
+ pDev->scl = IOGetByTag(pConfig->ioTagScl[device]);
+ if (pConfig->ioTagSda[device] == hardware->sdaPins[pindex])
+ pDev->sda = IOGetByTag(pConfig->ioTagSda[device]);
+ }
+
+ if (pDev->scl && pDev->sda) {
+ pDev->hardware = hardware;
+ pDev->reg = hardware->reg;
+ pDev->overClock = pConfig->overClock[device];
+ pDev->pullUp = pConfig->pullUp[device];
+ }
+ }
+}
+
+#endif // defined(USE_I2C) && !defined(USE_SOFT_I2C)
diff --git a/src/main/drivers/bus_i2c_hal.c b/src/main/drivers/bus_i2c_hal.c
index 006a19c1ad..ac8508447a 100644
--- a/src/main/drivers/bus_i2c_hal.c
+++ b/src/main/drivers/bus_i2c_hal.c
@@ -17,114 +17,118 @@
#include
#include
+#include
#include
-#include "drivers/bus_i2c.h"
#include "drivers/io.h"
+#include "drivers/io_impl.h"
#include "drivers/nvic.h"
#include "drivers/time.h"
+#include "drivers/rcc.h"
-#include "io_impl.h"
-#include "rcc.h"
+#include "drivers/bus_i2c.h"
+#include "drivers/bus_i2c_impl.h"
-
-#if !defined(SOFT_I2C) && defined(USE_I2C)
+#if defined(USE_I2C) && !defined(SOFT_I2C)
#define CLOCKSPEED 800000 // i2c clockspeed 400kHz default (conform specs), 800kHz and 1200kHz (Betaflight default)
static void i2cUnstick(IO_t scl, IO_t sda);
-#if defined(USE_I2C_PULLUP)
-#define IOCFG_I2C IO_CONFIG(GPIO_MODE_AF_OD, GPIO_SPEED_FREQ_VERY_HIGH, GPIO_PULLUP)
-#else
-#define IOCFG_I2C IOCFG_AF_OD
-#endif
+#define IOCFG_I2C_PU IO_CONFIG(GPIO_MODE_AF_OD, GPIO_SPEED_FREQ_VERY_HIGH, GPIO_PULLUP)
+#define IOCFG_I2C IO_CONFIG(GPIO_MODE_AF_OD, GPIO_SPEED_FREQ_VERY_HIGH, GPIO_NOPULL)
-#ifndef I2C1_SCL
-#define I2C1_SCL PB6
-#endif
+#define GPIO_AF4_I2C GPIO_AF4_I2C1
-#ifndef I2C1_SDA
-#define I2C1_SDA PB7
+const i2cHardware_t i2cHardware[I2CDEV_COUNT] = {
+#ifdef USE_I2C_DEVICE_1
+ {
+ .device = I2CDEV_1,
+ .reg = I2C1,
+ .sclPins = { DEFIO_TAG_E(PB6), DEFIO_TAG_E(PB8) },
+ .sdaPins = { DEFIO_TAG_E(PB7), DEFIO_TAG_E(PB9) },
+ .rcc = RCC_APB1(I2C1),
+ .ev_irq = I2C1_EV_IRQn,
+ .er_irq = I2C1_ER_IRQn,
+ },
#endif
-
-#ifndef I2C2_SCL
-#define I2C2_SCL PB10
+#ifdef USE_I2C_DEVICE_2
+ {
+ .device = I2CDEV_2,
+ .reg = I2C2,
+ .sclPins = { DEFIO_TAG_E(PB10), DEFIO_TAG_E(PF1) },
+ .sdaPins = { DEFIO_TAG_E(PB11), DEFIO_TAG_E(PF0) },
+ .rcc = RCC_APB1(I2C2),
+ .ev_irq = I2C2_EV_IRQn,
+ .er_irq = I2C2_ER_IRQn,
+ },
#endif
-#ifndef I2C2_SDA
-#define I2C2_SDA PB11
+#ifdef USE_I2C_DEVICE_3
+ {
+ .device = I2CDEV_3,
+ .reg = I2C3,
+ .sclPins = { DEFIO_TAG_E(PA8) },
+ .sdaPins = { DEFIO_TAG_E(PC9) },
+ .rcc = RCC_APB1(I2C3),
+ .ev_irq = I2C3_EV_IRQn,
+ .er_irq = I2C3_ER_IRQn,
+ },
#endif
-
-#ifndef I2C3_SCL
-#define I2C3_SCL PA8
-#endif
-#ifndef I2C3_SDA
-#define I2C3_SDA PB4
-#endif
-
-#if defined(USE_I2C_DEVICE_4)
-#ifndef I2C4_SCL
-#define I2C4_SCL PD12
-#endif
-#ifndef I2C4_SDA
-#define I2C4_SDA PD13
-#endif
-#endif
-
-static i2cDevice_t i2cHardwareMap[] = {
- { .dev = I2C1, .scl = IO_TAG(I2C1_SCL), .sda = IO_TAG(I2C1_SDA), .rcc = RCC_APB1(I2C1), .overClock = I2C1_OVERCLOCK, .ev_irq = I2C1_EV_IRQn, .er_irq = I2C1_ER_IRQn, .af = GPIO_AF4_I2C1 },
- { .dev = I2C2, .scl = IO_TAG(I2C2_SCL), .sda = IO_TAG(I2C2_SDA), .rcc = RCC_APB1(I2C2), .overClock = I2C2_OVERCLOCK, .ev_irq = I2C2_EV_IRQn, .er_irq = I2C2_ER_IRQn, .af = GPIO_AF4_I2C2 },
- { .dev = I2C3, .scl = IO_TAG(I2C3_SCL), .sda = IO_TAG(I2C3_SDA), .rcc = RCC_APB1(I2C3), .overClock = I2C2_OVERCLOCK, .ev_irq = I2C3_EV_IRQn, .er_irq = I2C3_ER_IRQn, .af = GPIO_AF4_I2C3 },
-#if defined(USE_I2C_DEVICE_4)
- { .dev = I2C4, .scl = IO_TAG(I2C4_SCL), .sda = IO_TAG(I2C4_SDA), .rcc = RCC_APB1(I2C4), .overClock = I2C2_OVERCLOCK, .ev_irq = I2C4_EV_IRQn, .er_irq = I2C4_ER_IRQn, .af = GPIO_AF4_I2C4 }
+#ifdef USE_I2C_DEVICE_4
+ {
+ .device = I2CDEV_4,
+ .reg = I2C4,
+ .sclPins = { DEFIO_TAG_E(PD12), DEFIO_TAG_E(PF14) },
+ .sdaPins = { DEFIO_TAG_E(PD13), DEFIO_TAG_E(PF15) },
+ .rcc = RCC_APB1(I2C4),
+ .ev_irq = I2C4_EV_IRQn,
+ .er_irq = I2C4_ER_IRQn,
+ },
#endif
};
-typedef struct{
- I2C_HandleTypeDef Handle;
-}i2cHandle_t;
-static i2cHandle_t i2cHandle[I2CDEV_COUNT];
+i2cDevice_t i2cDevice[I2CDEV_COUNT];
void I2C1_ER_IRQHandler(void)
{
- HAL_I2C_ER_IRQHandler(&i2cHandle[I2CDEV_1].Handle);
+ HAL_I2C_ER_IRQHandler(&i2cDevice[I2CDEV_1].handle);
}
void I2C1_EV_IRQHandler(void)
{
- HAL_I2C_EV_IRQHandler(&i2cHandle[I2CDEV_1].Handle);
+ HAL_I2C_EV_IRQHandler(&i2cDevice[I2CDEV_1].handle);
}
void I2C2_ER_IRQHandler(void)
{
- HAL_I2C_ER_IRQHandler(&i2cHandle[I2CDEV_2].Handle);
+ HAL_I2C_ER_IRQHandler(&i2cDevice[I2CDEV_2].handle);
}
void I2C2_EV_IRQHandler(void)
{
- HAL_I2C_EV_IRQHandler(&i2cHandle[I2CDEV_2].Handle);
+ HAL_I2C_EV_IRQHandler(&i2cDevice[I2CDEV_2].handle);
}
void I2C3_ER_IRQHandler(void)
{
- HAL_I2C_ER_IRQHandler(&i2cHandle[I2CDEV_3].Handle);
+ HAL_I2C_ER_IRQHandler(&i2cDevice[I2CDEV_3].handle);
}
void I2C3_EV_IRQHandler(void)
{
- HAL_I2C_EV_IRQHandler(&i2cHandle[I2CDEV_3].Handle);
+ HAL_I2C_EV_IRQHandler(&i2cDevice[I2CDEV_3].handle);
}
#ifdef USE_I2C_DEVICE_4
void I2C4_ER_IRQHandler(void)
{
- HAL_I2C_ER_IRQHandler(&i2cHandle[I2CDEV_4].Handle);
+ HAL_I2C_ER_IRQHandler(&i2cDevice[I2CDEV_4].handle);
}
void I2C4_EV_IRQHandler(void)
{
- HAL_I2C_EV_IRQHandler(&i2cHandle[I2CDEV_4].Handle);
+ HAL_I2C_EV_IRQHandler(&i2cDevice[I2CDEV_4].handle);
}
#endif
@@ -147,12 +151,22 @@ static bool i2cHandleHardwareFailure(I2CDevice device)
bool i2cWriteBuffer(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t len_, uint8_t *data)
{
+ if (device == I2CINVALID || device > I2CDEV_COUNT) {
+ return false;
+ }
+
+ I2C_HandleTypeDef *pHandle = &i2cDevice[device].handle;
+
+ if (!pHandle->Instance) {
+ return false;
+ }
+
HAL_StatusTypeDef status;
if(reg_ == 0xFF)
- status = HAL_I2C_Master_Transmit(&i2cHandle[device].Handle,addr_ << 1,data, len_, I2C_DEFAULT_TIMEOUT);
+ status = HAL_I2C_Master_Transmit(pHandle ,addr_ << 1, data, len_, I2C_DEFAULT_TIMEOUT);
else
- status = HAL_I2C_Mem_Write(&i2cHandle[device].Handle,addr_ << 1, reg_, I2C_MEMADD_SIZE_8BIT,data, len_, I2C_DEFAULT_TIMEOUT);
+ status = HAL_I2C_Mem_Write(pHandle ,addr_ << 1, reg_, I2C_MEMADD_SIZE_8BIT,data, len_, I2C_DEFAULT_TIMEOUT);
if(status != HAL_OK)
return i2cHandleHardwareFailure(device);
@@ -167,12 +181,22 @@ bool i2cWrite(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t data)
bool i2cRead(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t len, uint8_t* buf)
{
+ if (device == I2CINVALID || device > I2CDEV_COUNT) {
+ return false;
+ }
+
+ I2C_HandleTypeDef *pHandle = &i2cDevice[device].handle;
+
+ if (!pHandle->Instance) {
+ return false;
+ }
+
HAL_StatusTypeDef status;
if(reg_ == 0xFF)
- status = HAL_I2C_Master_Receive(&i2cHandle[device].Handle,addr_ << 1,buf, len, I2C_DEFAULT_TIMEOUT);
+ status = HAL_I2C_Master_Receive(pHandle ,addr_ << 1, buf, len, I2C_DEFAULT_TIMEOUT);
else
- status = HAL_I2C_Mem_Read(&i2cHandle[device].Handle,addr_ << 1, reg_, I2C_MEMADD_SIZE_8BIT,buf, len, I2C_DEFAULT_TIMEOUT);
+ status = HAL_I2C_Mem_Read(pHandle, addr_ << 1, reg_, I2C_MEMADD_SIZE_8BIT,buf, len, I2C_DEFAULT_TIMEOUT);
if(status != HAL_OK)
return i2cHandleHardwareFailure(device);
@@ -182,87 +206,72 @@ bool i2cRead(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t len, uint8_t
void i2cInit(I2CDevice device)
{
- /*## Configure the I2C clock source. The clock is derived from the SYSCLK #*/
-// RCC_PeriphCLKInitTypeDef RCC_PeriphCLKInitStruct;
-// RCC_PeriphCLKInitStruct.PeriphClockSelection = i2cHardwareMap[device].clk;
-// RCC_PeriphCLKInitStruct.I2c1ClockSelection = i2cHardwareMap[device].clk_src;
-// HAL_RCCEx_PeriphCLKConfig(&RCC_PeriphCLKInitStruct);
-
- switch (device) {
- case I2CDEV_1:
- __HAL_RCC_I2C1_CLK_ENABLE();
- break;
- case I2CDEV_2:
- __HAL_RCC_I2C2_CLK_ENABLE();
- break;
- case I2CDEV_3:
- __HAL_RCC_I2C3_CLK_ENABLE();
- break;
-#ifdef USE_I2C_DEVICE_4
- case I2CDEV_4:
- __HAL_RCC_I2C4_CLK_ENABLE();
- break;
-#endif
- default:
- break;
+ if (device == I2CINVALID) {
+ return;
}
- if (device == I2CINVALID)
- return;
- i2cDevice_t *i2c;
- i2c = &(i2cHardwareMap[device]);
+ i2cDevice_t *pDev = &i2cDevice[device];
- //I2C_InitTypeDef i2cInit;
+ const i2cHardware_t *hardware = pDev->hardware;
- IO_t scl = IOGetByTag(i2c->scl);
- IO_t sda = IOGetByTag(i2c->sda);
+ if (!hardware) {
+ return;
+ }
- IOInit(scl, OWNER_I2C_SCL, RESOURCE_INDEX(device));
- IOInit(sda, OWNER_I2C_SDA, RESOURCE_INDEX(device));
+ IO_t scl = pDev->scl;
+ IO_t sda = pDev->sda;
- // Enable RCC
- RCC_ClockCmd(i2c->rcc, ENABLE);
+ IOInit(scl, OWNER_I2C_SCL, RESOURCE_INDEX(device));
+ IOInit(sda, OWNER_I2C_SDA, RESOURCE_INDEX(device));
+ // Enable RCC
+ RCC_ClockCmd(hardware->rcc, ENABLE);
- i2cUnstick(scl, sda);
+ i2cUnstick(scl, sda);
+
+ // Init pins
+#ifdef STM32F7
+ IOConfigGPIOAF(scl, pDev->pullUp ? IOCFG_I2C_PU : IOCFG_I2C, GPIO_AF4_I2C);
+ IOConfigGPIOAF(sda, pDev->pullUp ? IOCFG_I2C_PU : IOCFG_I2C, GPIO_AF4_I2C);
+#else
+ IOConfigGPIO(scl, IOCFG_AF_OD);
+ IOConfigGPIO(sda, IOCFG_AF_OD);
+#endif
- // Init pins
- #ifdef STM32F7
- IOConfigGPIOAF(scl, IOCFG_I2C, i2c->af);
- IOConfigGPIOAF(sda, IOCFG_I2C, i2c->af);
- #else
- IOConfigGPIO(scl, IOCFG_AF_OD);
- IOConfigGPIO(sda, IOCFG_AF_OD);
- #endif
// Init I2C peripheral
- i2cHandle[device].Handle.Instance = i2cHardwareMap[device].dev;
+ I2C_HandleTypeDef *pHandle = &pDev->handle;
+
+ memset(pHandle, 0, sizeof(*pHandle));
+
+ pHandle->Instance = pDev->hardware->reg;
+
/// TODO: HAL check if I2C timing is correct
- if (i2c->overClock) {
+ if (pDev->overClock) {
// 800khz Maximum speed tested on various boards without issues
- i2cHandle[device].Handle.Init.Timing = 0x00500D1D;
+ pHandle->Init.Timing = 0x00500D1D;
} else {
- //i2cHandle[device].Handle.Init.Timing = 0x00500B6A;
- i2cHandle[device].Handle.Init.Timing = 0x00500C6F;
+ pHandle->Init.Timing = 0x00500C6F;
}
- //i2cHandle[device].Handle.Init.Timing = 0x00D00E28; /* (Rise time = 120ns, Fall time = 25ns) */
- i2cHandle[device].Handle.Init.OwnAddress1 = 0x0;
- i2cHandle[device].Handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
- i2cHandle[device].Handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
- i2cHandle[device].Handle.Init.OwnAddress2 = 0x0;
- i2cHandle[device].Handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
- i2cHandle[device].Handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
+ pHandle->Init.OwnAddress1 = 0x0;
+ pHandle->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
+ pHandle->Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
+ pHandle->Init.OwnAddress2 = 0x0;
+ pHandle->Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
+ pHandle->Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
- HAL_I2C_Init(&i2cHandle[device].Handle);
- /* Enable the Analog I2C Filter */
- HAL_I2CEx_ConfigAnalogFilter(&i2cHandle[device].Handle,I2C_ANALOGFILTER_ENABLE);
+ HAL_I2C_Init(pHandle);
- HAL_NVIC_SetPriority(i2cHardwareMap[device].er_irq, NVIC_PRIORITY_BASE(NVIC_PRIO_I2C_ER), NVIC_PRIORITY_SUB(NVIC_PRIO_I2C_ER));
- HAL_NVIC_EnableIRQ(i2cHardwareMap[device].er_irq);
- HAL_NVIC_SetPriority(i2cHardwareMap[device].ev_irq, NVIC_PRIORITY_BASE(NVIC_PRIO_I2C_EV), NVIC_PRIORITY_SUB(NVIC_PRIO_I2C_EV));
- HAL_NVIC_EnableIRQ(i2cHardwareMap[device].ev_irq);
+ // Enable the Analog I2C Filter
+ HAL_I2CEx_ConfigAnalogFilter(pHandle, I2C_ANALOGFILTER_ENABLE);
+
+ // Setup interrupt handlers
+ HAL_NVIC_SetPriority(hardware->er_irq, NVIC_PRIORITY_BASE(NVIC_PRIO_I2C_ER), NVIC_PRIORITY_SUB(NVIC_PRIO_I2C_ER));
+ HAL_NVIC_EnableIRQ(hardware->er_irq);
+ HAL_NVIC_SetPriority(hardware->ev_irq, NVIC_PRIORITY_BASE(NVIC_PRIO_I2C_EV), NVIC_PRIORITY_SUB(NVIC_PRIO_I2C_EV));
+ HAL_NVIC_EnableIRQ(hardware->ev_irq);
}
uint16_t i2cGetErrorCounter(void)
diff --git a/src/main/drivers/bus_i2c_impl.h b/src/main/drivers/bus_i2c_impl.h
new file mode 100644
index 0000000000..8395845931
--- /dev/null
+++ b/src/main/drivers/bus_i2c_impl.h
@@ -0,0 +1,76 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cleanflight is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cleanflight. If not, see .
+ */
+
+#pragma once
+
+#include "platform.h"
+
+#include "drivers/io_types.h"
+#include "drivers/rcc_types.h"
+
+#define I2C_SHORT_TIMEOUT ((uint32_t)0x1000)
+#define I2C_LONG_TIMEOUT ((uint32_t)(10 * I2C_SHORT_TIMEOUT))
+#define I2C_DEFAULT_TIMEOUT I2C_SHORT_TIMEOUT
+
+#define I2C_PIN_SEL_MAX 3
+
+typedef struct i2cHardware_s {
+ I2CDevice device;
+ I2C_TypeDef *reg;
+ ioTag_t sclPins[I2C_PIN_SEL_MAX];
+ ioTag_t sdaPins[I2C_PIN_SEL_MAX];
+ rccPeriphTag_t rcc;
+#if !defined(STM32F303xC)
+ uint8_t ev_irq;
+ uint8_t er_irq;
+#endif
+} i2cHardware_t;
+
+extern const i2cHardware_t i2cHardware[];
+
+#if defined(STM32F1) || defined(STM32F4)
+typedef struct i2cState_s {
+ volatile bool error;
+ volatile bool busy;
+ volatile uint8_t addr;
+ volatile uint8_t reg;
+ volatile uint8_t bytes;
+ volatile uint8_t writing;
+ volatile uint8_t reading;
+ volatile uint8_t* write_p;
+ volatile uint8_t* read_p;
+} i2cState_t;
+#endif
+
+typedef struct i2cDevice_s {
+ const i2cHardware_t *hardware;
+ I2C_TypeDef *reg;
+ IO_t scl;
+ IO_t sda;
+ bool overClock;
+ bool pullUp;
+
+ // MCU/Driver dependent member follows
+#if defined(STM32F1) || defined(STM32F4)
+ i2cState_t state;
+#endif
+#ifdef USE_HAL_DRIVER
+ I2C_HandleTypeDef handle;
+#endif
+} i2cDevice_t;
+
+extern i2cDevice_t i2cDevice[];
diff --git a/src/main/drivers/bus_i2c_stm32f10x.c b/src/main/drivers/bus_i2c_stm32f10x.c
index 7cd909d542..0268642394 100644
--- a/src/main/drivers/bus_i2c_stm32f10x.c
+++ b/src/main/drivers/bus_i2c_stm32f10x.c
@@ -18,18 +18,19 @@
#include
#include
#include
+#include
#include
#include "drivers/io.h"
#include "drivers/time.h"
+#include "drivers/nvic.h"
+#include "drivers/rcc.h"
#include "drivers/bus_i2c.h"
-#include "drivers/nvic.h"
-#include "io_impl.h"
-#include "rcc.h"
+#include "drivers/bus_i2c_impl.h"
-#ifndef SOFT_I2C
+#if defined(USE_I2C) && !defined(SOFT_I2C)
#define CLOCKSPEED 800000 // i2c clockspeed 400kHz default (conform specs), 800kHz and 1200kHz (Betaflight default)
@@ -40,65 +41,52 @@ static void i2cUnstick(IO_t scl, IO_t sda);
#define GPIO_AF_I2C GPIO_AF_I2C1
#ifdef STM32F4
-
-#if defined(USE_I2C_PULLUP)
-#define IOCFG_I2C IO_CONFIG(GPIO_Mode_AF, 0, GPIO_OType_OD, GPIO_PuPd_UP)
-#else
-#define IOCFG_I2C IOCFG_AF_OD
-#endif
-
-#ifndef I2C1_SCL
-#define I2C1_SCL PB8
-#endif
-#ifndef I2C1_SDA
-#define I2C1_SDA PB9
-#endif
-
-#else
-
-#ifndef I2C1_SCL
-#define I2C1_SCL PB6
-#endif
-#ifndef I2C1_SDA
-#define I2C1_SDA PB7
-#endif
+#define IOCFG_I2C_PU IO_CONFIG(GPIO_Mode_AF, 0, GPIO_OType_OD, GPIO_PuPd_UP)
+#define IOCFG_I2C IO_CONFIG(GPIO_Mode_AF, 0, GPIO_OType_OD, GPIO_PuPd_NOPULL)
+#else // STM32F4
#define IOCFG_I2C IO_CONFIG(GPIO_Mode_AF_OD, GPIO_Speed_50MHz)
-
#endif
-#ifndef I2C2_SCL
-#define I2C2_SCL PB10
+const i2cHardware_t i2cHardware[I2CDEV_COUNT] = {
+#ifdef USE_I2C_DEVICE_1
+ {
+ .device = I2CDEV_1,
+ .reg = I2C1,
+ .sclPins = { DEFIO_TAG_E(PB6), DEFIO_TAG_E(PB8) },
+ .sdaPins = { DEFIO_TAG_E(PB7), DEFIO_TAG_E(PB9) },
+ .rcc = RCC_APB1(I2C1),
+ .ev_irq = I2C1_EV_IRQn,
+ .er_irq = I2C1_ER_IRQn,
+ },
#endif
-
-#ifndef I2C2_SDA
-#define I2C2_SDA PB11
+#ifdef USE_I2C_DEVICE_2
+ {
+ .device = I2CDEV_2,
+ .reg = I2C2,
+ .sclPins = { DEFIO_TAG_E(PB10), DEFIO_TAG_E(PF1) },
+ .sdaPins = { DEFIO_TAG_E(PB11), DEFIO_TAG_E(PF0) },
+ .rcc = RCC_APB1(I2C2),
+ .ev_irq = I2C2_EV_IRQn,
+ .er_irq = I2C2_ER_IRQn,
+ },
#endif
-
-#ifdef STM32F4
-#ifndef I2C3_SCL
-#define I2C3_SCL PA8
-#endif
-#ifndef I2C3_SDA
-#define I2C3_SDA PC9
-#endif
-#endif
-
-static i2cDevice_t i2cHardwareMap[] = {
- { .dev = I2C1, .scl = IO_TAG(I2C1_SCL), .sda = IO_TAG(I2C1_SDA), .rcc = RCC_APB1(I2C1), .overClock = I2C1_OVERCLOCK, .ev_irq = I2C1_EV_IRQn, .er_irq = I2C1_ER_IRQn },
- { .dev = I2C2, .scl = IO_TAG(I2C2_SCL), .sda = IO_TAG(I2C2_SDA), .rcc = RCC_APB1(I2C2), .overClock = I2C2_OVERCLOCK, .ev_irq = I2C2_EV_IRQn, .er_irq = I2C2_ER_IRQn },
-#ifdef STM32F4
- { .dev = I2C3, .scl = IO_TAG(I2C3_SCL), .sda = IO_TAG(I2C3_SDA), .rcc = RCC_APB1(I2C3), .overClock = I2C2_OVERCLOCK, .ev_irq = I2C3_EV_IRQn, .er_irq = I2C3_ER_IRQn }
+#ifdef USE_I2C_DEVICE_3
+ {
+ .device = I2CDEV_3,
+ .reg = I2C3,
+ .sclPins = { DEFIO_TAG_E(PA8) },
+ .sdaPins = { DEFIO_TAG_E(PC9) },
+ .rcc = RCC_APB1(I2C3),
+ .ev_irq = I2C3_EV_IRQn,
+ .er_irq = I2C3_ER_IRQn,
+ },
#endif
};
+i2cDevice_t i2cDevice[I2CDEV_COUNT];
+
static volatile uint16_t i2cErrorCount = 0;
-static i2cState_t i2cState[] = {
- { false, false, 0, 0, 0, 0, 0, 0, 0 },
- { false, false, 0, 0, 0, 0, 0, 0, 0 },
- { false, false, 0, 0, 0, 0, 0, 0, 0 }
-};
-
void I2C1_ER_IRQHandler(void) {
i2c_er_handler(I2CDEV_1);
}
@@ -135,18 +123,19 @@ static bool i2cHandleHardwareFailure(I2CDevice device)
bool i2cWriteBuffer(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t len_, uint8_t *data)
{
-
- if (device == I2CINVALID)
+ if (device == I2CINVALID || device > I2CDEV_COUNT) {
return false;
+ }
+ I2C_TypeDef *I2Cx = i2cDevice[device].reg;
+
+ if (!I2Cx) {
+ return false;
+ }
+
+ i2cState_t *state = &i2cDevice[device].state;
uint32_t timeout = I2C_DEFAULT_TIMEOUT;
- I2C_TypeDef *I2Cx;
- I2Cx = i2cHardwareMap[device].dev;
-
- i2cState_t *state;
- state = &(i2cState[device]);
-
state->addr = addr_ << 1;
state->reg = reg_;
state->writing = 1;
@@ -182,17 +171,19 @@ bool i2cWrite(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t data)
bool i2cRead(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t len, uint8_t* buf)
{
- if (device == I2CINVALID)
+ if (device == I2CINVALID || device > I2CDEV_COUNT) {
return false;
+ }
+ I2C_TypeDef *I2Cx = i2cDevice[device].reg;
+
+ if (!I2Cx) {
+ return false;
+ }
+
+ i2cState_t *state = &i2cDevice[device].state;
uint32_t timeout = I2C_DEFAULT_TIMEOUT;
- I2C_TypeDef *I2Cx;
- I2Cx = i2cHardwareMap[device].dev;
-
- i2cState_t *state;
- state = &(i2cState[device]);
-
state->addr = addr_ << 1;
state->reg = reg_;
state->writing = 0;
@@ -223,11 +214,9 @@ bool i2cRead(I2CDevice device, uint8_t addr_, uint8_t reg_, uint8_t len, uint8_t
static void i2c_er_handler(I2CDevice device) {
- I2C_TypeDef *I2Cx;
- I2Cx = i2cHardwareMap[device].dev;
+ I2C_TypeDef *I2Cx = i2cDevice[device].hardware->reg;
- i2cState_t *state;
- state = &(i2cState[device]);
+ i2cState_t *state = &i2cDevice[device].state;
// Read the I2C1 status register
volatile uint32_t SR1Register = I2Cx->SR1;
@@ -258,11 +247,9 @@ static void i2c_er_handler(I2CDevice device) {
void i2c_ev_handler(I2CDevice device) {
- I2C_TypeDef *I2Cx;
- I2Cx = i2cHardwareMap[device].dev;
+ I2C_TypeDef *I2Cx = i2cDevice[device].hardware->reg;
- i2cState_t *state;
- state = &(i2cState[device]);
+ i2cState_t *state = &i2cDevice[device].state;
static uint8_t subaddress_sent, final_stop; // flag to indicate if subaddess sent, flag to indicate final bus condition
static int8_t index; // index is signed -1 == send the subaddress
@@ -378,65 +365,72 @@ void i2cInit(I2CDevice device)
if (device == I2CINVALID)
return;
- i2cDevice_t *i2c;
- i2c = &(i2cHardwareMap[device]);
+ i2cDevice_t *pDev = &i2cDevice[device];
+ const i2cHardware_t *hw = pDev->hardware;
+
+ if (!hw) {
+ return;
+ }
+
+ I2C_TypeDef *I2Cx = hw->reg;
+
+ memset(&pDev->state, 0, sizeof(pDev->state));
NVIC_InitTypeDef nvic;
I2C_InitTypeDef i2cInit;
- IO_t scl = IOGetByTag(i2c->scl);
- IO_t sda = IOGetByTag(i2c->sda);
+ IO_t scl = pDev->scl;
+ IO_t sda = pDev->sda;
IOInit(scl, OWNER_I2C_SCL, RESOURCE_INDEX(device));
IOInit(sda, OWNER_I2C_SDA, RESOURCE_INDEX(device));
// Enable RCC
- RCC_ClockCmd(i2c->rcc, ENABLE);
+ RCC_ClockCmd(hw->rcc, ENABLE);
- I2C_ITConfig(i2c->dev, I2C_IT_EVT | I2C_IT_ERR, DISABLE);
+ I2C_ITConfig(I2Cx, I2C_IT_EVT | I2C_IT_ERR, DISABLE);
i2cUnstick(scl, sda);
// Init pins
#ifdef STM32F4
- IOConfigGPIOAF(scl, IOCFG_I2C, GPIO_AF_I2C);
- IOConfigGPIOAF(sda, IOCFG_I2C, GPIO_AF_I2C);
+ IOConfigGPIOAF(scl, pDev->pullUp ? IOCFG_I2C_PU : IOCFG_I2C, GPIO_AF_I2C);
+ IOConfigGPIOAF(sda, pDev->pullUp ? IOCFG_I2C_PU : IOCFG_I2C, GPIO_AF_I2C);
#else
IOConfigGPIO(scl, IOCFG_I2C);
IOConfigGPIO(sda, IOCFG_I2C);
#endif
- I2C_DeInit(i2c->dev);
+ I2C_DeInit(I2Cx);
I2C_StructInit(&i2cInit);
- I2C_ITConfig(i2c->dev, I2C_IT_EVT | I2C_IT_ERR, DISABLE); // Disable EVT and ERR interrupts - they are enabled by the first request
+ I2C_ITConfig(I2Cx, I2C_IT_EVT | I2C_IT_ERR, DISABLE); // Disable EVT and ERR interrupts - they are enabled by the first request
i2cInit.I2C_Mode = I2C_Mode_I2C;
i2cInit.I2C_DutyCycle = I2C_DutyCycle_2;
i2cInit.I2C_OwnAddress1 = 0;
i2cInit.I2C_Ack = I2C_Ack_Enable;
i2cInit.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
- if (i2c->overClock) {
+ if (pDev->overClock) {
i2cInit.I2C_ClockSpeed = 800000; // 800khz Maximum speed tested on various boards without issues
} else {
i2cInit.I2C_ClockSpeed = 400000; // 400khz Operation according specs
}
- I2C_Cmd(i2c->dev, ENABLE);
- I2C_Init(i2c->dev, &i2cInit);
-
- I2C_StretchClockCmd(i2c->dev, ENABLE);
+ I2C_Cmd(I2Cx, ENABLE);
+ I2C_Init(I2Cx, &i2cInit);
+ I2C_StretchClockCmd(I2Cx, ENABLE);
// I2C ER Interrupt
- nvic.NVIC_IRQChannel = i2c->er_irq;
+ nvic.NVIC_IRQChannel = hw->er_irq;
nvic.NVIC_IRQChannelPreemptionPriority = NVIC_PRIORITY_BASE(NVIC_PRIO_I2C_ER);
nvic.NVIC_IRQChannelSubPriority = NVIC_PRIORITY_SUB(NVIC_PRIO_I2C_ER);
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
// I2C EV Interrupt
- nvic.NVIC_IRQChannel = i2c->ev_irq;
+ nvic.NVIC_IRQChannel = hw->ev_irq;
nvic.NVIC_IRQChannelPreemptionPriority = NVIC_PRIORITY_BASE(NVIC_PRIO_I2C_EV);
nvic.NVIC_IRQChannelSubPriority = NVIC_PRIORITY_SUB(NVIC_PRIO_I2C_EV);
NVIC_Init(&nvic);
diff --git a/src/main/drivers/bus_i2c_stm32f30x.c b/src/main/drivers/bus_i2c_stm32f30x.c
index 3869c179ab..d1c5c97582 100644
--- a/src/main/drivers/bus_i2c_stm32f30x.c
+++ b/src/main/drivers/bus_i2c_stm32f30x.c
@@ -17,54 +17,57 @@
#include
#include
+#include
#include
+#include "build/debug.h"
+
#include "drivers/system.h"
#include "drivers/io.h"
-#include "io_impl.h"
-#include "rcc.h"
+#include "drivers/io_impl.h"
+#include "drivers/rcc.h"
#include "drivers/bus_i2c.h"
+#include "drivers/bus_i2c_impl.h"
-#ifndef SOFT_I2C
+#if defined(USE_I2C) && !defined(SOFT_I2C)
-#if defined(USE_I2C_PULLUP)
-#define IOCFG_I2C IO_CONFIG(GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_OD, GPIO_PuPd_UP)
-#else
-#define IOCFG_I2C IO_CONFIG(GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_OD, GPIO_PuPd_NOPULL)
-#endif
+#define IOCFG_I2C_PU IO_CONFIG(GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_OD, GPIO_PuPd_UP)
+#define IOCFG_I2C IO_CONFIG(GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_OD, GPIO_PuPd_NOPULL)
#define I2C_HIGHSPEED_TIMING 0x00500E30 // 1000 Khz, 72Mhz Clock, Analog Filter Delay ON, Setup 40, Hold 4.
#define I2C_STANDARD_TIMING 0x00E0257A // 400 Khz, 72Mhz Clock, Analog Filter Delay ON, Rise 100, Fall 10.
-#define I2C_SHORT_TIMEOUT ((uint32_t)0x1000)
-#define I2C_LONG_TIMEOUT ((uint32_t)(10 * I2C_SHORT_TIMEOUT))
#define I2C_GPIO_AF GPIO_AF_4
-#ifndef I2C1_SCL
-#define I2C1_SCL PB6
-#endif
-#ifndef I2C1_SDA
-#define I2C1_SDA PB7
-#endif
-#ifndef I2C2_SCL
-#define I2C2_SCL PF4
-#endif
-#ifndef I2C2_SDA
-#define I2C2_SDA PA10
-#endif
-
static uint32_t i2cTimeout;
static volatile uint16_t i2cErrorCount = 0;
-//static volatile uint16_t i2c2ErrorCount = 0;
-static i2cDevice_t i2cHardwareMap[] = {
- { .dev = I2C1, .scl = IO_TAG(I2C1_SCL), .sda = IO_TAG(I2C1_SDA), .rcc = RCC_APB1(I2C1), .overClock = I2C1_OVERCLOCK },
- { .dev = I2C2, .scl = IO_TAG(I2C2_SCL), .sda = IO_TAG(I2C2_SDA), .rcc = RCC_APB1(I2C2), .overClock = I2C2_OVERCLOCK }
+const i2cHardware_t i2cHardware[I2CDEV_COUNT] = {
+#ifdef USE_I2C_DEVICE_1
+ {
+ .device = I2CDEV_1,
+ .reg = I2C1,
+ .sclPins = { DEFIO_TAG_E(PA15), DEFIO_TAG_E(PB6), DEFIO_TAG_E(PB8) },
+ .sdaPins = { DEFIO_TAG_E(PA14), DEFIO_TAG_E(PB7), DEFIO_TAG_E(PB9) },
+ .rcc = RCC_APB1(I2C1),
+ },
+#endif
+#ifdef USE_I2C_DEVICE_2
+ {
+ .device = I2CDEV_2,
+ .reg = I2C2,
+ .sclPins = { DEFIO_TAG_E(PA9), DEFIO_TAG_E(PF6) },
+ .sdaPins = { DEFIO_TAG_E(PA10) },
+ .rcc = RCC_APB1(I2C2),
+ },
+#endif
};
+i2cDevice_t i2cDevice[I2CDEV_COUNT];
+
///////////////////////////////////////////////////////////////////////////////
// I2C TimeoutUserCallback
///////////////////////////////////////////////////////////////////////////////
@@ -77,24 +80,30 @@ uint32_t i2cTimeoutUserCallback(void)
void i2cInit(I2CDevice device)
{
+ if (device == I2CINVALID || device > I2CDEV_COUNT) {
+ return;
+ }
- i2cDevice_t *i2c;
- i2c = &(i2cHardwareMap[device]);
+ i2cDevice_t *pDev = &i2cDevice[device];
+ const i2cHardware_t *hw = pDev->hardware;
- I2C_TypeDef *I2Cx;
- I2Cx = i2c->dev;
+ if (!hw) {
+ return;
+ }
- IO_t scl = IOGetByTag(i2c->scl);
- IO_t sda = IOGetByTag(i2c->sda);
+ I2C_TypeDef *I2Cx = pDev->reg;
- RCC_ClockCmd(i2c->rcc, ENABLE);
+ IO_t scl = pDev->scl;
+ IO_t sda = pDev->sda;
+
+ RCC_ClockCmd(hw->rcc, ENABLE);
RCC_I2CCLKConfig(I2Cx == I2C2 ? RCC_I2C2CLK_SYSCLK : RCC_I2C1CLK_SYSCLK);
IOInit(scl, OWNER_I2C_SCL, RESOURCE_INDEX(device));
- IOConfigGPIOAF(scl, IOCFG_I2C, GPIO_AF_4);
+ IOConfigGPIOAF(scl, pDev->pullUp ? IOCFG_I2C_PU : IOCFG_I2C, GPIO_AF_4);
IOInit(sda, OWNER_I2C_SDA, RESOURCE_INDEX(device));
- IOConfigGPIOAF(sda, IOCFG_I2C, GPIO_AF_4);
+ IOConfigGPIOAF(sda, pDev->pullUp ? IOCFG_I2C_PU : IOCFG_I2C, GPIO_AF_4);
I2C_InitTypeDef i2cInit = {
.I2C_Mode = I2C_Mode_I2C,
@@ -103,7 +112,7 @@ void i2cInit(I2CDevice device)
.I2C_OwnAddress1 = 0x00,
.I2C_Ack = I2C_Ack_Enable,
.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit,
- .I2C_Timing = (i2c->overClock ? I2C_HIGHSPEED_TIMING : I2C_STANDARD_TIMING)
+ .I2C_Timing = (pDev->overClock ? I2C_HIGHSPEED_TIMING : I2C_STANDARD_TIMING)
};
I2C_Init(I2Cx, &i2cInit);
@@ -120,10 +129,17 @@ uint16_t i2cGetErrorCounter(void)
bool i2cWrite(I2CDevice device, uint8_t addr_, uint8_t reg, uint8_t data)
{
- addr_ <<= 1;
+ if (device == I2CINVALID || device > I2CDEV_COUNT) {
+ return false;
+ }
- I2C_TypeDef *I2Cx;
- I2Cx = i2cHardwareMap[device].dev;
+ I2C_TypeDef *I2Cx = i2cDevice[device].reg;
+
+ if (!I2Cx) {
+ return false;
+ }
+
+ addr_ <<= 1;
/* Test on BUSY Flag */
i2cTimeout = I2C_LONG_TIMEOUT;
@@ -186,10 +202,17 @@ bool i2cWrite(I2CDevice device, uint8_t addr_, uint8_t reg, uint8_t data)
bool i2cRead(I2CDevice device, uint8_t addr_, uint8_t reg, uint8_t len, uint8_t* buf)
{
- addr_ <<= 1;
+ if (device == I2CINVALID || device > I2CDEV_COUNT) {
+ return false;
+ }
- I2C_TypeDef *I2Cx;
- I2Cx = i2cHardwareMap[device].dev;
+ I2C_TypeDef *I2Cx = i2cDevice[device].reg;
+
+ if (!I2Cx) {
+ return false;
+ }
+
+ addr_ <<= 1;
/* Test on BUSY Flag */
i2cTimeout = I2C_LONG_TIMEOUT;
diff --git a/src/main/drivers/display_ug2864hsweg01.c b/src/main/drivers/display_ug2864hsweg01.c
index 9c14f15620..b9ac8528e5 100644
--- a/src/main/drivers/display_ug2864hsweg01.c
+++ b/src/main/drivers/display_ug2864hsweg01.c
@@ -24,6 +24,8 @@
#include "display_ug2864hsweg01.h"
+#ifdef USE_I2C_OLED_DISPLAY
+
#if !defined(OLED_I2C_INSTANCE)
#if defined(I2C_DEVICE)
#define OLED_I2C_INSTANCE I2C_DEVICE
@@ -288,3 +290,4 @@ bool ug2864hsweg01InitI2C(void)
return true;
}
+#endif // USE_I2C_OLED_DISPLAY
diff --git a/src/main/drivers/light_led.c b/src/main/drivers/light_led.c
index 858ee1ff08..a21672640a 100644
--- a/src/main/drivers/light_led.c
+++ b/src/main/drivers/light_led.c
@@ -17,24 +17,49 @@
#include "platform.h"
+#include "config/parameter_group_ids.h"
+
#include "drivers/io.h"
#include "io_impl.h"
#include "light_led.h"
-static IO_t leds[LED_NUMBER];
-static uint8_t ledPolarity = 0;
+PG_REGISTER_WITH_RESET_FN(statusLedConfig_t, statusLedConfig, PG_STATUS_LED_CONFIG, 0);
+
+static IO_t leds[STATUS_LED_NUMBER];
+static uint8_t ledInversion = 0;
+
+void pgResetFn_statusLedConfig(statusLedConfig_t *statusLedConfig)
+{
+#ifdef LED0_PIN
+ statusLedConfig->ioTags[0] = IO_TAG(LED0_PIN);
+#endif
+#ifdef LED1_PIN
+ statusLedConfig->ioTags[1] = IO_TAG(LED1_PIN);
+#endif
+#ifdef LED2_PIN
+ statusLedConfig->ioTags[2] = IO_TAG(LED2_PIN);
+#endif
+
+ statusLedConfig->inversion = 0
+#ifdef LED0_INVERTED
+ | BIT(0)
+#endif
+#ifdef LED1_INVERTED
+ | BIT(1)
+#endif
+#ifdef LED2_INVERTED
+ | BIT(2)
+#endif
+ ;
+}
void ledInit(const statusLedConfig_t *statusLedConfig)
{
- LED0_OFF;
- LED1_OFF;
- LED2_OFF;
-
- ledPolarity = statusLedConfig->polarity;
- for (int i = 0; i < LED_NUMBER; i++) {
- if (statusLedConfig->ledTags[i]) {
- leds[i] = IOGetByTag(statusLedConfig->ledTags[i]);
+ ledInversion = statusLedConfig->inversion;
+ for (int i = 0; i < STATUS_LED_NUMBER; i++) {
+ if (statusLedConfig->ioTags[i]) {
+ leds[i] = IOGetByTag(statusLedConfig->ioTags[i]);
IOInit(leds[i], OWNER_LED, RESOURCE_INDEX(i));
IOConfigGPIO(leds[i], IOCFG_OUT_PP);
} else {
@@ -54,6 +79,6 @@ void ledToggle(int led)
void ledSet(int led, bool on)
{
- const bool inverted = (1 << (led)) & ledPolarity;
+ const bool inverted = (1 << (led)) & ledInversion;
IOWrite(leds[led], on ? inverted : !inverted);
}
diff --git a/src/main/drivers/light_led.h b/src/main/drivers/light_led.h
index 341bbb81d9..198a3f3244 100644
--- a/src/main/drivers/light_led.h
+++ b/src/main/drivers/light_led.h
@@ -19,47 +19,48 @@
#include "config/parameter_group.h"
#include "drivers/io_types.h"
+#include "common/utils.h"
-#define LED_NUMBER 3
+#define STATUS_LED_NUMBER 3
typedef struct statusLedConfig_s {
- ioTag_t ledTags[LED_NUMBER];
- uint8_t polarity;
+ ioTag_t ioTags[STATUS_LED_NUMBER];
+ uint8_t inversion;
} statusLedConfig_t;
PG_DECLARE(statusLedConfig_t, statusLedConfig);
// Helpful macros
-#ifdef LED0
-# define LED0_TOGGLE ledToggle(0)
-# define LED0_OFF ledSet(0, false)
-# define LED0_ON ledSet(0, true)
-#else
-# define LED0_TOGGLE do {} while(0)
-# define LED0_OFF do {} while(0)
-# define LED0_ON do {} while(0)
-#endif
+#if defined(UNIT_TEST) || defined(USE_FAKE_LED)
-#ifdef LED1
-# define LED1_TOGGLE ledToggle(1)
-# define LED1_OFF ledSet(1, false)
-# define LED1_ON ledSet(1, true)
-#else
-# define LED1_TOGGLE do {} while(0)
-# define LED1_OFF do {} while(0)
-# define LED1_ON do {} while(0)
-#endif
+#define LED0_TOGGLE NOOP
+#define LED0_OFF NOOP
+#define LED0_ON NOOP
+
+#define LED1_TOGGLE NOOP
+#define LED1_OFF NOOP
+#define LED1_ON NOOP
+
+#define LED2_TOGGLE NOOP
+#define LED2_OFF NOOP
+#define LED2_ON NOOP
-#ifdef LED2
-# define LED2_TOGGLE ledToggle(2)
-# define LED2_OFF ledSet(2, false)
-# define LED2_ON ledSet(2, true)
#else
-# define LED2_TOGGLE do {} while(0)
-# define LED2_OFF do {} while(0)
-# define LED2_ON do {} while(0)
-#endif
+
+#define LED0_TOGGLE ledToggle(0)
+#define LED0_OFF ledSet(0, false)
+#define LED0_ON ledSet(0, true)
+
+#define LED1_TOGGLE ledToggle(1)
+#define LED1_OFF ledSet(1, false)
+#define LED1_ON ledSet(1, true)
+
+#define LED2_TOGGLE ledToggle(2)
+#define LED2_OFF ledSet(2, false)
+#define LED2_ON ledSet(2, true)
void ledInit(const statusLedConfig_t *statusLedConfig);
void ledToggle(int led);
void ledSet(int led, bool state);
+
+#endif
diff --git a/src/main/drivers/pwm_output.c b/src/main/drivers/pwm_output.c
index 1624598a56..a7da53c8b4 100644
--- a/src/main/drivers/pwm_output.c
+++ b/src/main/drivers/pwm_output.c
@@ -125,40 +125,42 @@ static void pwmOutConfig(pwmOutputPort_t *port, const timerHardware_t *timerHard
*port->ccr = 0;
}
-static void pwmWriteUnused(uint8_t index, uint16_t value)
+static void pwmWriteUnused(uint8_t index, float value)
{
UNUSED(index);
UNUSED(value);
}
-static void pwmWriteBrushed(uint8_t index, uint16_t value)
+static void pwmWriteBrushed(uint8_t index, float value)
{
- *motors[index].ccr = (value - 1000) * motors[index].period / 1000;
+ *motors[index].ccr = lrintf((value - 1000) * motors[index].period / 1000);
}
-static void pwmWriteStandard(uint8_t index, uint16_t value)
+static void pwmWriteStandard(uint8_t index, float value)
{
- *motors[index].ccr = value;
+ *motors[index].ccr = lrintf(value);
}
-static void pwmWriteOneShot125(uint8_t index, uint16_t value)
+static void pwmWriteOneShot125(uint8_t index, float value)
{
- *motors[index].ccr = lrintf((float)(value * ONESHOT125_TIMER_MHZ/8.0f));
+ *motors[index].ccr = lrintf(value * ONESHOT125_TIMER_MHZ/8.0f);
}
-static void pwmWriteOneShot42(uint8_t index, uint16_t value)
+static void pwmWriteOneShot42(uint8_t index, float value)
{
- *motors[index].ccr = lrintf((float)(value * ONESHOT42_TIMER_MHZ/24.0f));
+ *motors[index].ccr = lrintf(value * ONESHOT42_TIMER_MHZ/24.0f);
}
-static void pwmWriteMultiShot(uint8_t index, uint16_t value)
+static void pwmWriteMultiShot(uint8_t index, float value)
{
- *motors[index].ccr = lrintf(((float)(value-1000) * MULTISHOT_20US_MULT) + MULTISHOT_5US_PW);
+ *motors[index].ccr = lrintf(((value-1000) * MULTISHOT_20US_MULT) + MULTISHOT_5US_PW);
}
-void pwmWriteMotor(uint8_t index, uint16_t value)
+void pwmWriteMotor(uint8_t index, float value)
{
- pwmWritePtr(index, value);
+ if (pwmMotorsEnabled) {
+ pwmWritePtr(index, value);
+ }
}
void pwmShutdownPulsesForAllMotors(uint8_t motorCount)
@@ -245,11 +247,16 @@ void motorDevInit(const motorDevConfig_t *motorConfig, uint16_t idlePulse, uint8
idlePulse = 0;
break;
#ifdef USE_DSHOT
+ case PWM_TYPE_PROSHOT1000:
+ pwmWritePtr = pwmWriteProShot;
+ pwmCompleteWritePtr = pwmCompleteDigitalMotorUpdate;
+ isDigital = true;
+ break;
case PWM_TYPE_DSHOT1200:
case PWM_TYPE_DSHOT600:
case PWM_TYPE_DSHOT300:
case PWM_TYPE_DSHOT150:
- pwmWritePtr = pwmWriteDigital;
+ pwmWritePtr = pwmWriteDshot;
pwmCompleteWritePtr = pwmCompleteDigitalMotorUpdate;
isDigital = true;
break;
@@ -320,15 +327,17 @@ pwmOutputPort_t *pwmGetMotors(void)
uint32_t getDshotHz(motorPwmProtocolTypes_e pwmProtocolType)
{
switch (pwmProtocolType) {
- case(PWM_TYPE_DSHOT1200):
- return MOTOR_DSHOT1200_MHZ * 1000000;
- case(PWM_TYPE_DSHOT600):
- return MOTOR_DSHOT600_MHZ * 1000000;
- case(PWM_TYPE_DSHOT300):
- return MOTOR_DSHOT300_MHZ * 1000000;
- default:
- case(PWM_TYPE_DSHOT150):
- return MOTOR_DSHOT150_MHZ * 1000000;
+ case(PWM_TYPE_PROSHOT1000):
+ return MOTOR_PROSHOT1000_MHZ * 1000000;
+ case(PWM_TYPE_DSHOT1200):
+ return MOTOR_DSHOT1200_MHZ * 1000000;
+ case(PWM_TYPE_DSHOT600):
+ return MOTOR_DSHOT600_MHZ * 1000000;
+ case(PWM_TYPE_DSHOT300):
+ return MOTOR_DSHOT300_MHZ * 1000000;
+ default:
+ case(PWM_TYPE_DSHOT150):
+ return MOTOR_DSHOT150_MHZ * 1000000;
}
}
@@ -338,11 +347,21 @@ void pwmWriteDshotCommand(uint8_t index, uint8_t command)
motorDmaOutput_t *const motor = getMotorDmaOutput(index);
unsigned repeats;
- if ((command >= 7 && command <= 10) || command == 12) {
+ switch (command) {
+ case DSHOT_CMD_SPIN_DIRECTION_1:
+ case DSHOT_CMD_SPIN_DIRECTION_2:
+ case DSHOT_CMD_3D_MODE_OFF:
+ case DSHOT_CMD_3D_MODE_ON:
+ case DSHOT_CMD_SAVE_SETTINGS:
+ case DSHOT_CMD_SPIN_DIRECTION_NORMAL:
+ case DSHOT_CMD_SPIN_DIRECTION_REVERSED:
repeats = 10;
- } else {
+ break;
+ default:
repeats = 1;
+ break;
}
+
for (; repeats; repeats--) {
motor->requestTelemetry = true;
pwmWritePtr(index, command);
@@ -355,10 +374,10 @@ void pwmWriteDshotCommand(uint8_t index, uint8_t command)
#endif
#ifdef USE_SERVOS
-void pwmWriteServo(uint8_t index, uint16_t value)
+void pwmWriteServo(uint8_t index, float value)
{
if (index < MAX_SUPPORTED_SERVOS && servos[index].ccr) {
- *servos[index].ccr = value;
+ *servos[index].ccr = lrintf(value);
}
}
diff --git a/src/main/drivers/pwm_output.h b/src/main/drivers/pwm_output.h
index f4249f4adf..1c3e792a49 100644
--- a/src/main/drivers/pwm_output.h
+++ b/src/main/drivers/pwm_output.h
@@ -28,6 +28,26 @@
#define MAX_SUPPORTED_SERVOS 8
#endif
+typedef enum {
+ DSHOT_CMD_MOTOR_STOP = 0,
+ DSHOT_CMD_BEEP1,
+ DSHOT_CMD_BEEP2,
+ DSHOT_CMD_BEEP3,
+ DSHOT_CMD_BEEP4,
+ DSHOT_CMD_BEEP5,
+ DSHOT_CMD_ESC_INFO,
+ DSHOT_CMD_SPIN_DIRECTION_1,
+ DSHOT_CMD_SPIN_DIRECTION_2,
+ DSHOT_CMD_3D_MODE_OFF,
+ DSHOT_CMD_3D_MODE_ON,
+ DSHOT_CMD_SETTINGS_REQUEST,
+ DSHOT_CMD_SAVE_SETTINGS,
+ DSHOT_CMD_SPIN_DIRECTION_NORMAL = 20, //Blheli_S only command
+ DSHOT_CMD_SPIN_DIRECTION_REVERSED = 21, //Blheli_S only command
+ DSHOT_CMD_MAX = 47
+} dshotCommands_e;
+
+
typedef enum {
PWM_TYPE_STANDARD = 0,
PWM_TYPE_ONESHOT125,
@@ -39,6 +59,7 @@ typedef enum {
PWM_TYPE_DSHOT300,
PWM_TYPE_DSHOT600,
PWM_TYPE_DSHOT1200,
+ PWM_TYPE_PROSHOT1000,
#endif
PWM_TYPE_MAX
} motorPwmProtocolTypes_e;
@@ -56,6 +77,11 @@ typedef enum {
#define MOTOR_BIT_0 7
#define MOTOR_BIT_1 14
#define MOTOR_BITLENGTH 19
+
+#define MOTOR_PROSHOT1000_MHZ 24
+#define PROSHOT_BASE_SYMBOL 24 // 1uS
+#define PROSHOT_BIT_WIDTH 3
+#define MOTOR_NIBBLE_LENGTH_PROSHOT 96 // 4uS
#endif
#if defined(STM32F40_41xxx) // must be multiples of timer clock
@@ -75,7 +101,8 @@ typedef enum {
#define PWM_BRUSHED_TIMER_MHZ 24
#endif
-#define MOTOR_DMA_BUFFER_SIZE 18 /* resolution + frame reset (2us) */
+#define DSHOT_DMA_BUFFER_SIZE 18 /* resolution + frame reset (2us) */
+#define PROSHOT_DMA_BUFFER_SIZE 6/* resolution + frame reset (2us) */
typedef struct {
TIM_TypeDef *timer;
@@ -89,9 +116,9 @@ typedef struct {
uint16_t timerDmaSource;
volatile bool requestTelemetry;
#if defined(STM32F3) || defined(STM32F4) || defined(STM32F7)
- uint32_t dmaBuffer[MOTOR_DMA_BUFFER_SIZE];
+ uint32_t dmaBuffer[DSHOT_DMA_BUFFER_SIZE];
#else
- uint8_t dmaBuffer[MOTOR_DMA_BUFFER_SIZE];
+ uint8_t dmaBuffer[DSHOT_DMA_BUFFER_SIZE];
#endif
#if defined(STM32F7)
TIM_HandleTypeDef TimHandle;
@@ -104,7 +131,7 @@ motorDmaOutput_t *getMotorDmaOutput(uint8_t index);
extern bool pwmMotorsEnabled;
struct timerHardware_s;
-typedef void(*pwmWriteFuncPtr)(uint8_t index, uint16_t value); // function pointer used to write motors
+typedef void(*pwmWriteFuncPtr)(uint8_t index, float value); // function pointer used to write motors
typedef void(*pwmCompleteWriteFuncPtr)(uint8_t motorCount); // function pointer used after motors are written
typedef struct {
@@ -140,7 +167,8 @@ void pwmServoConfig(const struct timerHardware_s *timerHardware, uint8_t servoIn
#ifdef USE_DSHOT
uint32_t getDshotHz(motorPwmProtocolTypes_e pwmProtocolType);
void pwmWriteDshotCommand(uint8_t index, uint8_t command);
-void pwmWriteDigital(uint8_t index, uint16_t value);
+void pwmWriteProShot(uint8_t index, float value);
+void pwmWriteDshot(uint8_t index, float value);
void pwmDigitalMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t motorIndex, motorPwmProtocolTypes_e pwmProtocolType, uint8_t output);
void pwmCompleteDigitalMotorUpdate(uint8_t motorCount);
#endif
@@ -151,11 +179,11 @@ void pwmToggleBeeper(void);
void beeperPwmInit(IO_t io, uint16_t frequency);
#endif
-void pwmWriteMotor(uint8_t index, uint16_t value);
+void pwmWriteMotor(uint8_t index, float value);
void pwmShutdownPulsesForAllMotors(uint8_t motorCount);
void pwmCompleteMotorUpdate(uint8_t motorCount);
-void pwmWriteServo(uint8_t index, uint16_t value);
+void pwmWriteServo(uint8_t index, float value);
pwmOutputPort_t *pwmGetMotors(void);
bool pwmIsSynced(void);
diff --git a/src/main/drivers/pwm_output_dshot.c b/src/main/drivers/pwm_output_dshot.c
index 38a4c1ccd5..c75ac74a03 100644
--- a/src/main/drivers/pwm_output_dshot.c
+++ b/src/main/drivers/pwm_output_dshot.c
@@ -54,12 +54,9 @@ uint8_t getTimerIndex(TIM_TypeDef *timer)
return dmaMotorTimerCount-1;
}
-void pwmWriteDigital(uint8_t index, uint16_t value)
+void pwmWriteDshot(uint8_t index, float value)
{
-
- if (!pwmMotorsEnabled) {
- return;
- }
+ const uint16_t digitalValue = lrintf(value);
motorDmaOutput_t * const motor = &dmaMotors[index];
@@ -67,7 +64,7 @@ void pwmWriteDigital(uint8_t index, uint16_t value)
return;
}
- uint16_t packet = (value << 1) | (motor->requestTelemetry ? 1 : 0);
+ uint16_t packet = (digitalValue << 1) | (motor->requestTelemetry ? 1 : 0);
motor->requestTelemetry = false; // reset telemetry request to make sure it's triggered only once in a row
// compute checksum
@@ -86,7 +83,41 @@ void pwmWriteDigital(uint8_t index, uint16_t value)
packet <<= 1;
}
- DMA_SetCurrDataCounter(motor->timerHardware->dmaRef, MOTOR_DMA_BUFFER_SIZE);
+ DMA_SetCurrDataCounter(motor->timerHardware->dmaRef, DSHOT_DMA_BUFFER_SIZE);
+ DMA_Cmd(motor->timerHardware->dmaRef, ENABLE);
+}
+
+void pwmWriteProShot(uint8_t index, float value)
+{
+ const uint16_t digitalValue = lrintf(value);
+
+ motorDmaOutput_t * const motor = &dmaMotors[index];
+
+ if (!motor->timerHardware || !motor->timerHardware->dmaRef) {
+ return;
+ }
+
+ uint16_t packet = (digitalValue << 1) | (motor->requestTelemetry ? 1 : 0);
+ motor->requestTelemetry = false; // reset telemetry request to make sure it's triggered only once in a row
+
+ // compute checksum
+ int csum = 0;
+ int csum_data = packet;
+ for (int i = 0; i < 3; i++) {
+ csum ^= csum_data; // xor data by nibbles
+ csum_data >>= 4;
+ }
+ csum &= 0xf;
+ // append checksum
+ packet = (packet << 4) | csum;
+
+ // generate pulses for Proshot
+ for (int i = 0; i < 4; i++) {
+ motor->dmaBuffer[i] = PROSHOT_BASE_SYMBOL + ((packet & 0xF000) >> 12) * PROSHOT_BIT_WIDTH; // Most significant nibble first
+ packet <<= 4; // Shift 4 bits
+ }
+
+ DMA_SetCurrDataCounter(motor->timerHardware->dmaRef, PROSHOT_DMA_BUFFER_SIZE);
DMA_Cmd(motor->timerHardware->dmaRef, ENABLE);
}
@@ -139,7 +170,7 @@ void pwmDigitalMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t
TIM_Cmd(timer, DISABLE);
TIM_TimeBaseStructure.TIM_Prescaler = (uint16_t)((timerClock(timer) / getDshotHz(pwmProtocolType)) - 1);
- TIM_TimeBaseStructure.TIM_Period = MOTOR_BITLENGTH;
+ TIM_TimeBaseStructure.TIM_Period = pwmProtocolType == PWM_TYPE_PROSHOT1000 ? MOTOR_NIBBLE_LENGTH_PROSHOT : MOTOR_BITLENGTH;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
@@ -205,7 +236,7 @@ void pwmDigitalMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
#endif
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)timerChCCR(timerHardware);
- DMA_InitStructure.DMA_BufferSize = MOTOR_DMA_BUFFER_SIZE;
+ DMA_InitStructure.DMA_BufferSize = pwmProtocolType == PWM_TYPE_PROSHOT1000 ? PROSHOT_DMA_BUFFER_SIZE : DSHOT_DMA_BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
diff --git a/src/main/drivers/pwm_output_dshot_hal.c b/src/main/drivers/pwm_output_dshot_hal.c
index 01083add6c..c2ff4737e1 100644
--- a/src/main/drivers/pwm_output_dshot_hal.c
+++ b/src/main/drivers/pwm_output_dshot_hal.c
@@ -49,11 +49,9 @@ uint8_t getTimerIndex(TIM_TypeDef *timer)
return dmaMotorTimerCount - 1;
}
-void pwmWriteDigital(uint8_t index, uint16_t value)
+void pwmWriteDshot(uint8_t index, float value)
{
- if (!pwmMotorsEnabled) {
- return;
- }
+ const uint16_t digitalValue = lrintf(value);
motorDmaOutput_t * const motor = &dmaMotors[index];
@@ -61,7 +59,7 @@ void pwmWriteDigital(uint8_t index, uint16_t value)
return;
}
- uint16_t packet = (value << 1) | (motor->requestTelemetry ? 1 : 0);
+ uint16_t packet = (digitalValue << 1) | (motor->requestTelemetry ? 1 : 0);
motor->requestTelemetry = false; // reset telemetry request to make sure it's triggered only once in a row
// compute checksum
@@ -81,12 +79,55 @@ void pwmWriteDigital(uint8_t index, uint16_t value)
}
if (motor->timerHardware->output & TIMER_OUTPUT_N_CHANNEL) {
- if (HAL_TIMEx_PWMN_Start_DMA(&motor->TimHandle, motor->timerHardware->channel, motor->dmaBuffer, MOTOR_DMA_BUFFER_SIZE) != HAL_OK) {
+ if (HAL_TIMEx_PWMN_Start_DMA(&motor->TimHandle, motor->timerHardware->channel, motor->dmaBuffer, DSHOT_DMA_BUFFER_SIZE) != HAL_OK) {
/* Starting PWM generation Error */
return;
}
} else {
- if (HAL_TIM_PWM_Start_DMA(&motor->TimHandle, motor->timerHardware->channel, motor->dmaBuffer, MOTOR_DMA_BUFFER_SIZE) != HAL_OK) {
+ if (HAL_TIM_PWM_Start_DMA(&motor->TimHandle, motor->timerHardware->channel, motor->dmaBuffer, DSHOT_DMA_BUFFER_SIZE) != HAL_OK) {
+ /* Starting PWM generation Error */
+ return;
+ }
+ }
+}
+
+void pwmWriteProShot(uint8_t index, float value)
+{
+ const uint16_t digitalValue = lrintf(value);
+
+ motorDmaOutput_t * const motor = &dmaMotors[index];
+
+ if (!motor->timerHardware || !motor->timerHardware->dmaRef) {
+ return;
+ }
+
+ uint16_t packet = (digitalValue << 1) | (motor->requestTelemetry ? 1 : 0);
+ motor->requestTelemetry = false; // reset telemetry request to make sure it's triggered only once in a row
+
+ // compute checksum
+ int csum = 0;
+ int csum_data = packet;
+ for (int i = 0; i < 3; i++) {
+ csum ^= csum_data; // xor data by nibbles
+ csum_data >>= 4;
+ }
+ csum &= 0xf;
+ // append checksum
+ packet = (packet << 4) | csum;
+
+ // generate pulses for Proshot
+ for (int i = 0; i < 4; i++) {
+ motor->dmaBuffer[i] = PROSHOT_BASE_SYMBOL + ((packet & 0xF000) >> 12) * PROSHOT_BIT_WIDTH; // Most significant nibble first
+ packet <<= 4; // Shift 4 bits
+ }
+
+ if (motor->timerHardware->output & TIMER_OUTPUT_N_CHANNEL) {
+ if (HAL_TIMEx_PWMN_Start_DMA(&motor->TimHandle, motor->timerHardware->channel, motor->dmaBuffer, PROSHOT_DMA_BUFFER_SIZE) != HAL_OK) {
+ /* Starting PWM generation Error */
+ return;
+ }
+ } else {
+ if (HAL_TIM_PWM_Start_DMA(&motor->TimHandle, motor->timerHardware->channel, motor->dmaBuffer, PROSHOT_DMA_BUFFER_SIZE) != HAL_OK) {
/* Starting PWM generation Error */
return;
}
@@ -122,8 +163,8 @@ void pwmDigitalMotorHardwareConfig(const timerHardware_t *timerHardware, uint8_t
RCC_ClockCmd(timerRCC(timer), ENABLE);
motor->TimHandle.Instance = timerHardware->tim;
- motor->TimHandle.Init.Prescaler = (timerClock(timer) / getDshotHz(pwmProtocolType)) - 1;;
- motor->TimHandle.Init.Period = MOTOR_BITLENGTH;
+ motor->TimHandle.Init.Prescaler = (timerClock(timer) / getDshotHz(pwmProtocolType)) - 1;
+ motor->TimHandle.Init.Period = pwmProtocolType == PWM_TYPE_PROSHOT1000 ? MOTOR_NIBBLE_LENGTH_PROSHOT : MOTOR_BITLENGTH;
motor->TimHandle.Init.RepetitionCounter = 0;
motor->TimHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
motor->TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
diff --git a/src/main/drivers/serial_uart_hal.c b/src/main/drivers/serial_uart_hal.c
index a21ab96868..fa213eeab0 100644
--- a/src/main/drivers/serial_uart_hal.c
+++ b/src/main/drivers/serial_uart_hal.c
@@ -58,6 +58,8 @@ static void usartConfigurePinInversion(uartPort_t *uartPort) {
}
}
+// XXX uartReconfigure does not handle resource management properly.
+
void uartReconfigure(uartPort_t *uartPort)
{
/*RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit;
diff --git a/src/main/drivers/serial_uart_stm32f30x.c b/src/main/drivers/serial_uart_stm32f30x.c
index 8dd877c940..881d634286 100644
--- a/src/main/drivers/serial_uart_stm32f30x.c
+++ b/src/main/drivers/serial_uart_stm32f30x.c
@@ -184,7 +184,7 @@ void serialUARTInitIO(IO_t txIO, IO_t rxIO, portMode_t mode, portOptions_t optio
((options & SERIAL_INVERTED) || (options & SERIAL_BIDIR_PP)) ? GPIO_PuPd_DOWN : GPIO_PuPd_UP
);
- IOInit(txIO, OWNER_SERIAL_TX, index);
+ IOInit(txIO, OWNER_SERIAL_TX, RESOURCE_INDEX(index));
IOConfigGPIOAF(txIO, ioCfg, af);
if (!(options & SERIAL_INVERTED))
@@ -192,12 +192,12 @@ void serialUARTInitIO(IO_t txIO, IO_t rxIO, portMode_t mode, portOptions_t optio
} else {
ioConfig_t ioCfg = IO_CONFIG(GPIO_Mode_AF, GPIO_Speed_50MHz, GPIO_OType_PP, (options & SERIAL_INVERTED) ? GPIO_PuPd_DOWN : GPIO_PuPd_UP);
if ((mode & MODE_TX) && txIO) {
- IOInit(txIO, OWNER_SERIAL_TX, index);
+ IOInit(txIO, OWNER_SERIAL_TX, RESOURCE_INDEX(index));
IOConfigGPIOAF(txIO, ioCfg, af);
}
if ((mode & MODE_RX) && rxIO) {
- IOInit(rxIO, OWNER_SERIAL_RX, index);
+ IOInit(rxIO, OWNER_SERIAL_RX, RESOURCE_INDEX(index));
IOConfigGPIOAF(rxIO, ioCfg, af);
}
}
diff --git a/src/main/drivers/system_stm32f10x.c b/src/main/drivers/system_stm32f10x.c
index b6f2d423e0..536023fd27 100644
--- a/src/main/drivers/system_stm32f10x.c
+++ b/src/main/drivers/system_stm32f10x.c
@@ -83,7 +83,7 @@ void systemInit(void)
// Turn on clocks for stuff we use
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
- // cache RCC->CSR value to use it in isMPUSoftreset() and others
+ // cache RCC->CSR value to use it in isMPUSoftReset() and others
cachedRccCsrValue = RCC->CSR;
RCC_ClearFlag();
diff --git a/src/main/drivers/system_stm32f30x.c b/src/main/drivers/system_stm32f30x.c
index a00abdfba0..f3a349706a 100644
--- a/src/main/drivers/system_stm32f30x.c
+++ b/src/main/drivers/system_stm32f30x.c
@@ -90,7 +90,7 @@ void systemInit(void)
// Configure NVIC preempt/priority groups
NVIC_PriorityGroupConfig(NVIC_PRIORITY_GROUPING);
- // cache RCC->CSR value to use it in isMPUSoftreset() and others
+ // cache RCC->CSR value to use it in isMPUSoftReset() and others
cachedRccCsrValue = RCC->CSR;
RCC_ClearFlag();
diff --git a/src/main/drivers/system_stm32f4xx.c b/src/main/drivers/system_stm32f4xx.c
index de32333124..5b7e155640 100644
--- a/src/main/drivers/system_stm32f4xx.c
+++ b/src/main/drivers/system_stm32f4xx.c
@@ -152,7 +152,7 @@ void enableGPIOPowerUsageAndNoiseReductions(void)
bool isMPUSoftReset(void)
{
- if (RCC->CSR & RCC_CSR_SFTRSTF)
+ if (cachedRccCsrValue & RCC_CSR_SFTRSTF)
return true;
else
return false;
@@ -167,7 +167,7 @@ void systemInit(void)
// Configure NVIC preempt/priority groups
NVIC_PriorityGroupConfig(NVIC_PRIORITY_GROUPING);
- // cache RCC->CSR value to use it in isMPUSoftreset() and others
+ // cache RCC->CSR value to use it in isMPUSoftReset() and others
cachedRccCsrValue = RCC->CSR;
/* Accounts for OP Bootloader, set the Vector Table base address as specified in .ld file */
diff --git a/src/main/drivers/system_stm32f7xx.c b/src/main/drivers/system_stm32f7xx.c
index db4f9085ea..bfe8039dde 100644
--- a/src/main/drivers/system_stm32f7xx.c
+++ b/src/main/drivers/system_stm32f7xx.c
@@ -149,7 +149,7 @@ void enableGPIOPowerUsageAndNoiseReductions(void)
bool isMPUSoftReset(void)
{
- if (RCC->CSR & RCC_CSR_SFTRSTF)
+ if (cachedRccCsrValue & RCC_CSR_SFTRSTF)
return true;
else
return false;
@@ -164,7 +164,7 @@ void systemInit(void)
// Configure NVIC preempt/priority groups
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITY_GROUPING);
- // cache RCC->CSR value to use it in isMPUSoftreset() and others
+ // cache RCC->CSR value to use it in isMPUSoftReset() and others
cachedRccCsrValue = RCC->CSR;
/* Accounts for OP Bootloader, set the Vector Table base address as specified in .ld file */
diff --git a/src/main/fc/cli.c b/src/main/fc/cli.c
index 97a6d5f706..83bcf40051 100755
--- a/src/main/fc/cli.c
+++ b/src/main/fc/cli.c
@@ -75,6 +75,7 @@ extern uint8_t __config_end;
#include "drivers/time.h"
#include "drivers/timer.h"
#include "drivers/vcd.h"
+#include "drivers/light_led.h"
#include "fc/settings.h"
#include "fc/cli.h"
@@ -126,10 +127,20 @@ extern uint8_t __config_end;
static serialPort_t *cliPort;
-static bufWriter_t *cliWriter;
-static uint8_t cliWriteBuffer[sizeof(*cliWriter) + 128];
-static char cliBuffer[64];
+#ifdef STM32F1
+#define CLI_IN_BUFFER_SIZE 128
+#define CLI_OUT_BUFFER_SIZE 64
+#else
+// Space required to set array parameters
+#define CLI_IN_BUFFER_SIZE 256
+#define CLI_OUT_BUFFER_SIZE 256
+#endif
+
+static bufWriter_t *cliWriter;
+static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_IN_BUFFER_SIZE];
+
+static char cliBuffer[CLI_OUT_BUFFER_SIZE];
static uint32_t bufferIndex = 0;
static bool configIsInCopy = false;
@@ -295,33 +306,44 @@ static void cliPrintLinef(const char *format, ...)
static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
{
- int value = 0;
+ if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
+ for (int i = 0; i < var->config.array.length; i++) {
+ uint8_t value = ((uint8_t *)valuePointer)[i];
- switch (var->type & VALUE_TYPE_MASK) {
- case VAR_UINT8:
- value = *(uint8_t *)valuePointer;
- break;
-
- case VAR_INT8:
- value = *(int8_t *)valuePointer;
- break;
-
- case VAR_UINT16:
- case VAR_INT16:
- value = *(int16_t *)valuePointer;
- break;
- }
-
- switch(var->type & VALUE_MODE_MASK) {
- case MODE_DIRECT:
- cliPrintf("%d", value);
- if (full) {
- cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
+ cliPrintf("%d", value);
+ if (i < var->config.array.length - 1) {
+ cliPrint(",");
+ }
+ }
+ } else {
+ int value = 0;
+
+ switch (var->type & VALUE_TYPE_MASK) {
+ case VAR_UINT8:
+ value = *(uint8_t *)valuePointer;
+ break;
+
+ case VAR_INT8:
+ value = *(int8_t *)valuePointer;
+ break;
+
+ case VAR_UINT16:
+ case VAR_INT16:
+ value = *(int16_t *)valuePointer;
+ break;
+ }
+
+ switch(var->type & VALUE_MODE_MASK) {
+ case MODE_DIRECT:
+ cliPrintf("%d", value);
+ if (full) {
+ cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
+ }
+ break;
+ case MODE_LOOKUP:
+ cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
+ break;
}
- break;
- case MODE_LOOKUP:
- cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
- break;
}
}
@@ -379,14 +401,15 @@ static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
const char *defaultFormat = "#set %s = ";
const int valueOffset = getValueOffset(value);
const bool equalsDefault = valuePtrEqualsDefault(value->type, pg->copy + valueOffset, pg->address + valueOffset);
+
if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
cliPrintf(defaultFormat, value->name);
- printValuePointer(value, (uint8_t*)pg->address + valueOffset, 0);
+ printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
cliPrintLinefeed();
}
cliPrintf(format, value->name);
- printValuePointer(value, pg->copy + valueOffset, 0);
+ printValuePointer(value, pg->copy + valueOffset, false);
cliPrintLinefeed();
}
}
@@ -426,26 +449,31 @@ static void cliPrintVarRange(const clivalue_t *var)
}
cliPrintLinefeed();
}
+ break;
+ case (MODE_ARRAY): {
+ cliPrintLinef("Array length: %d", var->config.array.length);
+ }
+
break;
}
}
-static void cliSetVar(const clivalue_t *var, const cliVar_t value)
+static void cliSetVar(const clivalue_t *var, const int16_t value)
{
void *ptr = getValuePointer(var);
switch (var->type & VALUE_TYPE_MASK) {
case VAR_UINT8:
- *(uint8_t *)ptr = value.uint8;
+ *(uint8_t *)ptr = value;
break;
case VAR_INT8:
- *(int8_t *)ptr = value.int8;
+ *(int8_t *)ptr = value;
break;
case VAR_UINT16:
case VAR_INT16:
- *(int16_t *)ptr = value.int16;
+ *(int16_t *)ptr = value;
break;
}
}
@@ -2509,18 +2537,34 @@ static void cliGet(char *cmdline)
cliPrintLine("Invalid name");
}
+static char *skipSpace(char *buffer)
+{
+ while (*(buffer) == ' ') {
+ buffer++;
+ }
+
+ return buffer;
+}
+
+static uint8_t getWordLength(char *bufBegin, char *bufEnd)
+{
+ while (*(bufEnd - 1) == ' ') {
+ bufEnd--;
+ }
+
+ return bufEnd - bufBegin;
+}
+
static void cliSet(char *cmdline)
{
- uint32_t len;
- const clivalue_t *val;
- char *eqptr = NULL;
-
- len = strlen(cmdline);
+ const uint32_t len = strlen(cmdline);
+ char *eqptr;
if (len == 0 || (len == 1 && cmdline[0] == '*')) {
cliPrintLine("Current settings: ");
+
for (uint32_t i = 0; i < valueTableEntryCount; i++) {
- val = &valueTable[i];
+ const clivalue_t *val = &valueTable[i];
cliPrintf("%s = ", valueTable[i].name);
cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
cliPrintLinefeed();
@@ -2528,52 +2572,81 @@ static void cliSet(char *cmdline)
} else if ((eqptr = strstr(cmdline, "=")) != NULL) {
// has equals
- char *lastNonSpaceCharacter = eqptr;
- while (*(lastNonSpaceCharacter - 1) == ' ') {
- lastNonSpaceCharacter--;
- }
- uint8_t variableNameLength = lastNonSpaceCharacter - cmdline;
+ uint8_t variableNameLength = getWordLength(cmdline, eqptr);
// skip the '=' and any ' ' characters
eqptr++;
- while (*(eqptr) == ' ') {
- eqptr++;
- }
+ eqptr = skipSpace(eqptr);
for (uint32_t i = 0; i < valueTableEntryCount; i++) {
- val = &valueTable[i];
+ const clivalue_t *val = &valueTable[i];
// ensure exact match when setting to prevent setting variables with shorter names
if (strncasecmp(cmdline, valueTable[i].name, strlen(valueTable[i].name)) == 0 && variableNameLength == strlen(valueTable[i].name)) {
- bool changeValue = false;
- cliVar_t value = { .int16 = 0 };
+ bool valueChanged = false;
+ int16_t value = 0;
switch (valueTable[i].type & VALUE_MODE_MASK) {
case MODE_DIRECT: {
- value.int16 = atoi(eqptr);
+ int16_t value = atoi(eqptr);
- if (value.int16 >= valueTable[i].config.minmax.min && value.int16 <= valueTable[i].config.minmax.max) {
- changeValue = true;
- }
+ if (value >= valueTable[i].config.minmax.min && value <= valueTable[i].config.minmax.max) {
+ cliSetVar(val, value);
+ valueChanged = true;
}
- break;
+ }
+
+ break;
case MODE_LOOKUP: {
- const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex];
- bool matched = false;
- for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
- matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
+ const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex];
+ bool matched = false;
+ for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
+ matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
- if (matched) {
- value.int16 = tableValueIndex;
- changeValue = true;
- }
+ if (matched) {
+ value = tableValueIndex;
+
+ cliSetVar(val, value);
+ valueChanged = true;
}
}
- break;
+ }
+
+ break;
+ case MODE_ARRAY: {
+ const uint8_t arrayLength = valueTable[i].config.array.length;
+ char *valPtr = eqptr;
+ uint8_t array[256];
+ char curVal[4];
+ for (int i = 0; i < arrayLength; i++) {
+ valPtr = skipSpace(valPtr);
+ char *valEnd = strstr(valPtr, ",");
+ if ((valEnd != NULL) && (i < arrayLength - 1)) {
+ uint8_t varLength = getWordLength(valPtr, valEnd);
+ if (varLength <= 3) {
+ strncpy(curVal, valPtr, getWordLength(valPtr, valEnd));
+ curVal[varLength] = '\0';
+ array[i] = (uint8_t)atoi((const char *)curVal);
+ valPtr = valEnd + 1;
+ } else {
+ break;
+ }
+ } else if ((valEnd == NULL) && (i == arrayLength - 1)) {
+ array[i] = atoi(valPtr);
+
+ uint8_t *ptr = getValuePointer(val);
+ memcpy(ptr, array, arrayLength);
+ valueChanged = true;
+ } else {
+ break;
+ }
+ }
+ }
+
+ break;
+
}
- if (changeValue) {
- cliSetVar(val, value);
-
+ if (valueChanged) {
cliPrintf("%s set to ", valueTable[i].name);
cliPrintVar(val, 0);
} else {
@@ -2984,11 +3057,7 @@ static void backupConfigs(void)
{
// make copies of configs to do differencing
PG_FOREACH(pg) {
- if (pgIsProfile(pg)) {
- //memcpy(pg->copy, pg->address, pg->size * MAX_PROFILE_COUNT);
- } else {
- memcpy(pg->copy, pg->address, pg->size);
- }
+ memcpy(pg->copy, pg->address, pg->size);
}
configIsInCopy = true;
@@ -2997,11 +3066,7 @@ static void backupConfigs(void)
static void restoreConfigs(void)
{
PG_FOREACH(pg) {
- if (pgIsProfile(pg)) {
- //memcpy(pg->address, pg->copy, pg->size * MAX_PROFILE_COUNT);
- } else {
- memcpy(pg->address, pg->copy, pg->size);
- }
+ memcpy(pg->address, pg->copy, pg->size);
}
configIsInCopy = false;
diff --git a/src/main/fc/cli.c.orig b/src/main/fc/cli.c.orig
new file mode 100755
index 0000000000..665991e146
--- /dev/null
+++ b/src/main/fc/cli.c.orig
@@ -0,0 +1,3509 @@
+/*
+ * This file is part of Cleanflight.
+ *
+ * Cleanflight is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Cleanflight is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Cleanflight. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "platform.h"
+
+// FIXME remove this for targets that don't need a CLI. Perhaps use a no-op macro when USE_CLI is not enabled
+// signal that we're in cli mode
+uint8_t cliMode = 0;
+extern uint8_t __config_start; // configured via linker script when building binaries.
+extern uint8_t __config_end;
+
+#ifdef USE_CLI
+
+#include "blackbox/blackbox.h"
+
+#include "build/build_config.h"
+#include "build/debug.h"
+#include "build/version.h"
+
+#include "cms/cms.h"
+
+#include "common/axis.h"
+#include "common/color.h"
+#include "common/maths.h"
+#include "common/printf.h"
+#include "common/typeconversion.h"
+#include "common/utils.h"
+
+#include "config/config_eeprom.h"
+#include "config/feature.h"
+#include "config/parameter_group.h"
+#include "config/parameter_group_ids.h"
+
+#include "drivers/accgyro/accgyro.h"
+#include "drivers/buf_writer.h"
+#include "drivers/bus_i2c.h"
+#include "drivers/compass/compass.h"
+#include "drivers/display.h"
+#include "drivers/dma.h"
+#include "drivers/flash.h"
+#include "drivers/io.h"
+#include "drivers/io_impl.h"
+#include "drivers/inverter.h"
+#include "drivers/rx_pwm.h"
+#include "drivers/sdcard.h"
+#include "drivers/sensor.h"
+#include "drivers/serial.h"
+#include "drivers/serial_escserial.h"
+#include "drivers/sonar_hcsr04.h"
+#include "drivers/stack_check.h"
+#include "drivers/system.h"
+#include "drivers/transponder_ir.h"
+#include "drivers/time.h"
+#include "drivers/timer.h"
+#include "drivers/vcd.h"
+#include "drivers/light_led.h"
+
+#include "fc/settings.h"
+#include "fc/cli.h"
+#include "fc/config.h"
+#include "fc/controlrate_profile.h"
+#include "fc/fc_core.h"
+#include "fc/rc_adjustments.h"
+#include "fc/rc_controls.h"
+#include "fc/runtime_config.h"
+#include "fc/fc_msp.h"
+
+#include "flight/altitude.h"
+#include "flight/failsafe.h"
+#include "flight/imu.h"
+#include "flight/mixer.h"
+#include "flight/navigation.h"
+#include "flight/pid.h"
+#include "flight/servos.h"
+
+#include "io/asyncfatfs/asyncfatfs.h"
+#include "io/beeper.h"
+#include "io/flashfs.h"
+#include "io/displayport_max7456.h"
+#include "io/displayport_msp.h"
+#include "io/gimbal.h"
+#include "io/gps.h"
+#include "io/ledstrip.h"
+#include "io/osd.h"
+#include "io/serial.h"
+#include "io/transponder_ir.h"
+#include "io/vtx_rtc6705.h"
+#include "io/vtx_control.h"
+
+#include "rx/rx.h"
+#include "rx/spektrum.h"
+
+#include "scheduler/scheduler.h"
+
+#include "sensors/acceleration.h"
+#include "sensors/barometer.h"
+#include "sensors/battery.h"
+#include "sensors/boardalignment.h"
+#include "sensors/compass.h"
+#include "sensors/gyro.h"
+#include "sensors/sensors.h"
+
+#include "telemetry/frsky.h"
+#include "telemetry/telemetry.h"
+
+
+static serialPort_t *cliPort;
+
+#ifdef STM32F1
+#define CLI_IN_BUFFER_SIZE 128
+#define CLI_OUT_BUFFER_SIZE 64
+#else
+// Space required to set array parameters
+#define CLI_IN_BUFFER_SIZE 256
+#define CLI_OUT_BUFFER_SIZE 256
+#endif
+
+static bufWriter_t *cliWriter;
+static uint8_t cliWriteBuffer[sizeof(*cliWriter) + CLI_IN_BUFFER_SIZE];
+
+static char cliBuffer[CLI_OUT_BUFFER_SIZE];
+static uint32_t bufferIndex = 0;
+
+static bool configIsInCopy = false;
+
+static const char* const emptyName = "-";
+
+#ifndef USE_QUAD_MIXER_ONLY
+// sync this with mixerMode_e
+static const char * const mixerNames[] = {
+ "TRI", "QUADP", "QUADX", "BI",
+ "GIMBAL", "Y6", "HEX6",
+ "FLYING_WING", "Y4", "HEX6X", "OCTOX8", "OCTOFLATP", "OCTOFLATX",
+ "AIRPLANE", "HELI_120_CCPM", "HELI_90_DEG", "VTAIL4",
+ "HEX6H", "PPM_TO_SERVO", "DUALCOPTER", "SINGLECOPTER",
+ "ATAIL4", "CUSTOM", "CUSTOMAIRPLANE", "CUSTOMTRI", "QUADX1234", NULL
+};
+#endif
+
+// sync this with features_e
+static const char * const featureNames[] = {
+ "RX_PPM", "VBAT", "INFLIGHT_ACC_CAL", "RX_SERIAL", "MOTOR_STOP",
+ "SERVO_TILT", "SOFTSERIAL", "GPS", "FAILSAFE",
+ "SONAR", "TELEMETRY", "CURRENT_METER", "3D", "RX_PARALLEL_PWM",
+ "RX_MSP", "RSSI_ADC", "LED_STRIP", "DISPLAY", "OSD",
+ "UNUSED", "CHANNEL_FORWARDING", "TRANSPONDER", "AIRMODE",
+ "SDCARD", "VTX", "RX_SPI", "SOFTSPI", "ESC_SENSOR", "ANTI_GRAVITY", "DYNAMIC_FILTER", NULL
+};
+
+// sync this with rxFailsafeChannelMode_e
+static const char rxFailsafeModeCharacters[] = "ahs";
+
+static const rxFailsafeChannelMode_e rxFailsafeModesTable[RX_FAILSAFE_TYPE_COUNT][RX_FAILSAFE_MODE_COUNT] = {
+ { RX_FAILSAFE_MODE_AUTO, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_INVALID },
+ { RX_FAILSAFE_MODE_INVALID, RX_FAILSAFE_MODE_HOLD, RX_FAILSAFE_MODE_SET }
+};
+
+#if defined(USE_SENSOR_NAMES)
+// sync this with sensors_e
+static const char * const sensorTypeNames[] = {
+ "GYRO", "ACC", "BARO", "MAG", "SONAR", "GPS", "GPS+MAG", NULL
+};
+
+#define SENSOR_NAMES_MASK (SENSOR_GYRO | SENSOR_ACC | SENSOR_BARO | SENSOR_MAG)
+
+// sync with gyroSensor_e
+static const char * const gyroNames[] = {
+ "AUTO", "NONE", "MPU6050", "L3G4200D", "MPU3050", "L3GD20",
+ "MPU6000", "MPU6500", "MPU9250", "ICM20601", "ICM20602", "ICM20608G", "ICM20689", "BMI160", "FAKE"
+};
+
+static const char * const *sensorHardwareNames[] = {
+ gyroNames, lookupTableAccHardware, lookupTableBaroHardware, lookupTableMagHardware
+
+};
+#endif // USE_SENSOR_NAMES
+
+static void cliPrint(const char *str)
+{
+ while (*str) {
+ bufWriterAppend(cliWriter, *str++);
+ }
+ bufWriterFlush(cliWriter);
+}
+
+static void cliPrintLinefeed()
+{
+ cliPrint("\r\n");
+}
+
+static void cliPrintLine(const char *str)
+{
+ cliPrint(str);
+ cliPrintLinefeed();
+}
+
+#ifdef MINIMAL_CLI
+#define cliPrintHashLine(str)
+#else
+static void cliPrintHashLine(const char *str)
+{
+ cliPrint("\r\n# ");
+ cliPrintLine(str);
+}
+#endif
+
+static void cliPutp(void *p, char ch)
+{
+ bufWriterAppend(p, ch);
+}
+
+typedef enum {
+ DUMP_MASTER = (1 << 0),
+ DUMP_PROFILE = (1 << 1),
+ DUMP_RATES = (1 << 2),
+ DUMP_ALL = (1 << 3),
+ DO_DIFF = (1 << 4),
+ SHOW_DEFAULTS = (1 << 5),
+ HIDE_UNUSED = (1 << 6)
+} dumpFlags_e;
+
+static void cliPrintfva(const char *format, va_list va)
+{
+ tfp_format(cliWriter, cliPutp, format, va);
+ bufWriterFlush(cliWriter);
+}
+
+static void cliPrintLinefva(const char *format, va_list va)
+{
+ tfp_format(cliWriter, cliPutp, format, va);
+ bufWriterFlush(cliWriter);
+ cliPrintLinefeed();
+}
+
+static bool cliDumpPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
+{
+ if (!((dumpMask & DO_DIFF) && equalsDefault)) {
+ va_list va;
+ va_start(va, format);
+ cliPrintLinefva(format, va);
+ va_end(va);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void cliWrite(uint8_t ch)
+{
+ bufWriterAppend(cliWriter, ch);
+}
+
+static bool cliDefaultPrintLinef(uint8_t dumpMask, bool equalsDefault, const char *format, ...)
+{
+ if ((dumpMask & SHOW_DEFAULTS) && !equalsDefault) {
+ cliWrite('#');
+
+ va_list va;
+ va_start(va, format);
+ cliPrintLinefva(format, va);
+ va_end(va);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void cliPrintf(const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ cliPrintfva(format, va);
+ va_end(va);
+}
+
+
+static void cliPrintLinef(const char *format, ...)
+{
+ va_list va;
+ va_start(va, format);
+ cliPrintLinefva(format, va);
+ va_end(va);
+}
+
+static void printValuePointer(const clivalue_t *var, const void *valuePointer, bool full)
+{
+ if ((var->type & VALUE_MODE_MASK) == MODE_ARRAY) {
+ for (int i = 0; i < var->config.array.length; i++) {
+ uint8_t value = ((uint8_t *)valuePointer)[i];
+
+ cliPrintf("%d", value);
+ if (i < var->config.array.length - 1) {
+ cliPrint(",");
+ }
+ }
+ } else {
+ int value = 0;
+
+ switch (var->type & VALUE_TYPE_MASK) {
+ case VAR_UINT8:
+ value = *(uint8_t *)valuePointer;
+ break;
+
+ case VAR_INT8:
+ value = *(int8_t *)valuePointer;
+ break;
+
+ case VAR_UINT16:
+ case VAR_INT16:
+ value = *(int16_t *)valuePointer;
+ break;
+ }
+
+ switch(var->type & VALUE_MODE_MASK) {
+ case MODE_DIRECT:
+ cliPrintf("%d", value);
+ if (full) {
+ cliPrintf(" %d %d", var->config.minmax.min, var->config.minmax.max);
+ }
+ break;
+ case MODE_LOOKUP:
+ cliPrint(lookupTables[var->config.lookup.tableIndex].values[value]);
+ break;
+ }
+ }
+}
+
+static bool valuePtrEqualsDefault(uint8_t type, const void *ptr, const void *ptrDefault)
+{
+ bool result = false;
+ switch (type & VALUE_TYPE_MASK) {
+ case VAR_UINT8:
+ result = *(uint8_t *)ptr == *(uint8_t *)ptrDefault;
+ break;
+
+ case VAR_INT8:
+ result = *(int8_t *)ptr == *(int8_t *)ptrDefault;
+ break;
+
+ case VAR_UINT16:
+ case VAR_INT16:
+ result = *(int16_t *)ptr == *(int16_t *)ptrDefault;
+ break;
+ }
+
+ return result;
+}
+
+static uint16_t getValueOffset(const clivalue_t *value)
+{
+ switch (value->type & VALUE_SECTION_MASK) {
+ case MASTER_VALUE:
+ return value->offset;
+ case PROFILE_VALUE:
+ return value->offset + sizeof(pidProfile_t) * getCurrentPidProfileIndex();
+ case PROFILE_RATE_VALUE:
+ return value->offset + sizeof(controlRateConfig_t) * getCurrentControlRateProfileIndex();
+ }
+ return 0;
+}
+
+static void *getValuePointer(const clivalue_t *value)
+{
+ const pgRegistry_t* rec = pgFind(value->pgn);
+ return CONST_CAST(void *, rec->address + getValueOffset(value));
+}
+
+static void dumpPgValue(const clivalue_t *value, uint8_t dumpMask)
+{
+ const pgRegistry_t *pg = pgFind(value->pgn);
+#ifdef DEBUG
+ if (!pg) {
+ cliPrintLinef("VALUE %s ERROR", value->name);
+ return; // if it's not found, the pgn shouldn't be in the value table!
+ }
+#endif
+
+ const char *format = "set %s = ";
+ const char *defaultFormat = "#set %s = ";
+ const int valueOffset = getValueOffset(value);
+ const bool equalsDefault = valuePtrEqualsDefault(value->type, pg->copy + valueOffset, pg->address + valueOffset);
+
+ if (((dumpMask & DO_DIFF) == 0) || !equalsDefault) {
+ if (dumpMask & SHOW_DEFAULTS && !equalsDefault) {
+ cliPrintf(defaultFormat, value->name);
+ printValuePointer(value, (uint8_t*)pg->address + valueOffset, false);
+ cliPrintLinefeed();
+ }
+ cliPrintf(format, value->name);
+ printValuePointer(value, pg->copy + valueOffset, false);
+ cliPrintLinefeed();
+ }
+}
+
+static void dumpAllValues(uint16_t valueSection, uint8_t dumpMask)
+{
+ for (uint32_t i = 0; i < valueTableEntryCount; i++) {
+ const clivalue_t *value = &valueTable[i];
+ bufWriterFlush(cliWriter);
+ if ((value->type & VALUE_SECTION_MASK) == valueSection) {
+ dumpPgValue(value, dumpMask);
+ }
+ }
+}
+
+static void cliPrintVar(const clivalue_t *var, bool full)
+{
+ const void *ptr = getValuePointer(var);
+
+ printValuePointer(var, ptr, full);
+}
+
+static void cliPrintVarRange(const clivalue_t *var)
+{
+ switch (var->type & VALUE_MODE_MASK) {
+ case (MODE_DIRECT): {
+ cliPrintLinef("Allowed range: %d - %d", var->config.minmax.min, var->config.minmax.max);
+ }
+ break;
+ case (MODE_LOOKUP): {
+ const lookupTableEntry_t *tableEntry = &lookupTables[var->config.lookup.tableIndex];
+ cliPrint("Allowed values:");
+ for (uint32_t i = 0; i < tableEntry->valueCount ; i++) {
+ if (i > 0)
+ cliPrint(",");
+ cliPrintf(" %s", tableEntry->values[i]);
+ }
+ cliPrintLinefeed();
+ }
+ break;
+ case (MODE_ARRAY): {
+ cliPrintLinef("Array length: %d", var->config.array.length);
+ }
+
+ break;
+ }
+}
+
+static void cliSetVar(const clivalue_t *var, const int16_t value)
+{
+ void *ptr = getValuePointer(var);
+
+ switch (var->type & VALUE_TYPE_MASK) {
+ case VAR_UINT8:
+ *(uint8_t *)ptr = value;
+ break;
+
+ case VAR_INT8:
+ *(int8_t *)ptr = value;
+ break;
+
+ case VAR_UINT16:
+ case VAR_INT16:
+ *(int16_t *)ptr = value;
+ break;
+ }
+}
+
+#if defined(USE_RESOURCE_MGMT) && !defined(MINIMAL_CLI)
+static void cliRepeat(char ch, uint8_t len)
+{
+ for (int i = 0; i < len; i++) {
+ bufWriterAppend(cliWriter, ch);
+ }
+ cliPrintLinefeed();
+}
+#endif
+
+static void cliPrompt(void)
+{
+ cliPrint("\r\n# ");
+}
+
+static void cliShowParseError(void)
+{
+ cliPrintLine("Parse error");
+}
+
+static void cliShowArgumentRangeError(char *name, int min, int max)
+{
+ cliPrintLinef("%s not between %d and %d", name, min, max);
+}
+
+static const char *nextArg(const char *currentArg)
+{
+ const char *ptr = strchr(currentArg, ' ');
+ while (ptr && *ptr == ' ') {
+ ptr++;
+ }
+
+ return ptr;
+}
+
+static const char *processChannelRangeArgs(const char *ptr, channelRange_t *range, uint8_t *validArgumentCount)
+{
+ for (uint32_t argIndex = 0; argIndex < 2; argIndex++) {
+ ptr = nextArg(ptr);
+ if (ptr) {
+ int val = atoi(ptr);
+ val = CHANNEL_VALUE_TO_STEP(val);
+ if (val >= MIN_MODE_RANGE_STEP && val <= MAX_MODE_RANGE_STEP) {
+ if (argIndex == 0) {
+ range->startStep = val;
+ } else {
+ range->endStep = val;
+ }
+ (*validArgumentCount)++;
+ }
+ }
+ }
+
+ return ptr;
+}
+
+// Check if a string's length is zero
+static bool isEmpty(const char *string)
+{
+ return (string == NULL || *string == '\0') ? true : false;
+}
+
+static void printRxFailsafe(uint8_t dumpMask, const rxFailsafeChannelConfig_t *rxFailsafeChannelConfigs, const rxFailsafeChannelConfig_t *defaultRxFailsafeChannelConfigs)
+{
+ // print out rxConfig failsafe settings
+ for (uint32_t channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
+ const rxFailsafeChannelConfig_t *channelFailsafeConfig = &rxFailsafeChannelConfigs[channel];
+ const rxFailsafeChannelConfig_t *defaultChannelFailsafeConfig = &defaultRxFailsafeChannelConfigs[channel];
+ const bool equalsDefault = channelFailsafeConfig->mode == defaultChannelFailsafeConfig->mode
+ && channelFailsafeConfig->step == defaultChannelFailsafeConfig->step;
+ const bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
+ if (requireValue) {
+ const char *format = "rxfail %u %c %d";
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ channel,
+ rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode],
+ RXFAIL_STEP_TO_CHANNEL_VALUE(defaultChannelFailsafeConfig->step)
+ );
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ channel,
+ rxFailsafeModeCharacters[channelFailsafeConfig->mode],
+ RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
+ );
+ } else {
+ const char *format = "rxfail %u %c";
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ channel,
+ rxFailsafeModeCharacters[defaultChannelFailsafeConfig->mode]
+ );
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ channel,
+ rxFailsafeModeCharacters[channelFailsafeConfig->mode]
+ );
+ }
+ }
+}
+
+static void cliRxFailsafe(char *cmdline)
+{
+ uint8_t channel;
+ char buf[3];
+
+ if (isEmpty(cmdline)) {
+ // print out rxConfig failsafe settings
+ for (channel = 0; channel < MAX_SUPPORTED_RC_CHANNEL_COUNT; channel++) {
+ cliRxFailsafe(itoa(channel, buf, 10));
+ }
+ } else {
+ const char *ptr = cmdline;
+ channel = atoi(ptr++);
+ if ((channel < MAX_SUPPORTED_RC_CHANNEL_COUNT)) {
+
+ rxFailsafeChannelConfig_t *channelFailsafeConfig = rxFailsafeChannelConfigsMutable(channel);
+
+ const rxFailsafeChannelType_e type = (channel < NON_AUX_CHANNEL_COUNT) ? RX_FAILSAFE_TYPE_FLIGHT : RX_FAILSAFE_TYPE_AUX;
+ rxFailsafeChannelMode_e mode = channelFailsafeConfig->mode;
+ bool requireValue = channelFailsafeConfig->mode == RX_FAILSAFE_MODE_SET;
+
+ ptr = nextArg(ptr);
+ if (ptr) {
+ const char *p = strchr(rxFailsafeModeCharacters, *(ptr));
+ if (p) {
+ const uint8_t requestedMode = p - rxFailsafeModeCharacters;
+ mode = rxFailsafeModesTable[type][requestedMode];
+ } else {
+ mode = RX_FAILSAFE_MODE_INVALID;
+ }
+ if (mode == RX_FAILSAFE_MODE_INVALID) {
+ cliShowParseError();
+ return;
+ }
+
+ requireValue = mode == RX_FAILSAFE_MODE_SET;
+
+ ptr = nextArg(ptr);
+ if (ptr) {
+ if (!requireValue) {
+ cliShowParseError();
+ return;
+ }
+ uint16_t value = atoi(ptr);
+ value = CHANNEL_VALUE_TO_RXFAIL_STEP(value);
+ if (value > MAX_RXFAIL_RANGE_STEP) {
+ cliPrintLine("Value out of range");
+ return;
+ }
+
+ channelFailsafeConfig->step = value;
+ } else if (requireValue) {
+ cliShowParseError();
+ return;
+ }
+ channelFailsafeConfig->mode = mode;
+ }
+
+ char modeCharacter = rxFailsafeModeCharacters[channelFailsafeConfig->mode];
+
+ // double use of cliPrintf below
+ // 1. acknowledge interpretation on command,
+ // 2. query current setting on single item,
+
+ if (requireValue) {
+ cliPrintLinef("rxfail %u %c %d",
+ channel,
+ modeCharacter,
+ RXFAIL_STEP_TO_CHANNEL_VALUE(channelFailsafeConfig->step)
+ );
+ } else {
+ cliPrintLinef("rxfail %u %c",
+ channel,
+ modeCharacter
+ );
+ }
+ } else {
+ cliShowArgumentRangeError("channel", 0, MAX_SUPPORTED_RC_CHANNEL_COUNT - 1);
+ }
+ }
+}
+
+static void printAux(uint8_t dumpMask, const modeActivationCondition_t *modeActivationConditions, const modeActivationCondition_t *defaultModeActivationConditions)
+{
+ const char *format = "aux %u %u %u %u %u";
+ // print out aux channel settings
+ for (uint32_t i = 0; i < MAX_MODE_ACTIVATION_CONDITION_COUNT; i++) {
+ const modeActivationCondition_t *mac = &modeActivationConditions[i];
+ bool equalsDefault = false;
+ if (defaultModeActivationConditions) {
+ const modeActivationCondition_t *macDefault = &defaultModeActivationConditions[i];
+ equalsDefault = mac->modeId == macDefault->modeId
+ && mac->auxChannelIndex == macDefault->auxChannelIndex
+ && mac->range.startStep == macDefault->range.startStep
+ && mac->range.endStep == macDefault->range.endStep;
+ const box_t *box = findBoxByBoxId(macDefault->modeId);
+ if (box) {
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ box->permanentId,
+ macDefault->auxChannelIndex,
+ MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.startStep),
+ MODE_STEP_TO_CHANNEL_VALUE(macDefault->range.endStep)
+ );
+ }
+ }
+ const box_t *box = findBoxByBoxId(mac->modeId);
+ if (box) {
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ box->permanentId,
+ mac->auxChannelIndex,
+ MODE_STEP_TO_CHANNEL_VALUE(mac->range.startStep),
+ MODE_STEP_TO_CHANNEL_VALUE(mac->range.endStep)
+ );
+ }
+ }
+}
+
+static void cliAux(char *cmdline)
+{
+ int i, val = 0;
+ const char *ptr;
+
+ if (isEmpty(cmdline)) {
+ printAux(DUMP_MASTER, modeActivationConditions(0), NULL);
+ } else {
+ ptr = cmdline;
+ i = atoi(ptr++);
+ if (i < MAX_MODE_ACTIVATION_CONDITION_COUNT) {
+ modeActivationCondition_t *mac = modeActivationConditionsMutable(i);
+ uint8_t validArgumentCount = 0;
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ const box_t *box = findBoxByPermanentId(val);
+ if (box) {
+ mac->modeId = box->boxId;
+ validArgumentCount++;
+ }
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
+ mac->auxChannelIndex = val;
+ validArgumentCount++;
+ }
+ }
+ ptr = processChannelRangeArgs(ptr, &mac->range, &validArgumentCount);
+
+ if (validArgumentCount != 4) {
+ memset(mac, 0, sizeof(modeActivationCondition_t));
+ }
+ } else {
+ cliShowArgumentRangeError("index", 0, MAX_MODE_ACTIVATION_CONDITION_COUNT - 1);
+ }
+ }
+}
+
+static void printSerial(uint8_t dumpMask, const serialConfig_t *serialConfig, const serialConfig_t *serialConfigDefault)
+{
+ const char *format = "serial %d %d %ld %ld %ld %ld";
+ for (uint32_t i = 0; i < SERIAL_PORT_COUNT; i++) {
+ if (!serialIsPortAvailable(serialConfig->portConfigs[i].identifier)) {
+ continue;
+ };
+ bool equalsDefault = false;
+ if (serialConfigDefault) {
+ equalsDefault = serialConfig->portConfigs[i].identifier == serialConfigDefault->portConfigs[i].identifier
+ && serialConfig->portConfigs[i].functionMask == serialConfigDefault->portConfigs[i].functionMask
+ && serialConfig->portConfigs[i].msp_baudrateIndex == serialConfigDefault->portConfigs[i].msp_baudrateIndex
+ && serialConfig->portConfigs[i].gps_baudrateIndex == serialConfigDefault->portConfigs[i].gps_baudrateIndex
+ && serialConfig->portConfigs[i].telemetry_baudrateIndex == serialConfigDefault->portConfigs[i].telemetry_baudrateIndex
+ && serialConfig->portConfigs[i].blackbox_baudrateIndex == serialConfigDefault->portConfigs[i].blackbox_baudrateIndex;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ serialConfigDefault->portConfigs[i].identifier,
+ serialConfigDefault->portConfigs[i].functionMask,
+ baudRates[serialConfigDefault->portConfigs[i].msp_baudrateIndex],
+ baudRates[serialConfigDefault->portConfigs[i].gps_baudrateIndex],
+ baudRates[serialConfigDefault->portConfigs[i].telemetry_baudrateIndex],
+ baudRates[serialConfigDefault->portConfigs[i].blackbox_baudrateIndex]
+ );
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ serialConfig->portConfigs[i].identifier,
+ serialConfig->portConfigs[i].functionMask,
+ baudRates[serialConfig->portConfigs[i].msp_baudrateIndex],
+ baudRates[serialConfig->portConfigs[i].gps_baudrateIndex],
+ baudRates[serialConfig->portConfigs[i].telemetry_baudrateIndex],
+ baudRates[serialConfig->portConfigs[i].blackbox_baudrateIndex]
+ );
+ }
+}
+
+static void cliSerial(char *cmdline)
+{
+ if (isEmpty(cmdline)) {
+ printSerial(DUMP_MASTER, serialConfig(), NULL);
+ return;
+ }
+ serialPortConfig_t portConfig;
+ memset(&portConfig, 0 , sizeof(portConfig));
+
+ serialPortConfig_t *currentConfig;
+
+ uint8_t validArgumentCount = 0;
+
+ const char *ptr = cmdline;
+
+ int val = atoi(ptr++);
+ currentConfig = serialFindPortConfiguration(val);
+ if (currentConfig) {
+ portConfig.identifier = val;
+ validArgumentCount++;
+ }
+
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ portConfig.functionMask = val & 0xFFFF;
+ validArgumentCount++;
+ }
+
+ for (int i = 0; i < 4; i ++) {
+ ptr = nextArg(ptr);
+ if (!ptr) {
+ break;
+ }
+
+ val = atoi(ptr);
+
+ uint8_t baudRateIndex = lookupBaudRateIndex(val);
+ if (baudRates[baudRateIndex] != (uint32_t) val) {
+ break;
+ }
+
+ switch(i) {
+ case 0:
+ if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_1000000) {
+ continue;
+ }
+ portConfig.msp_baudrateIndex = baudRateIndex;
+ break;
+ case 1:
+ if (baudRateIndex < BAUD_9600 || baudRateIndex > BAUD_115200) {
+ continue;
+ }
+ portConfig.gps_baudrateIndex = baudRateIndex;
+ break;
+ case 2:
+ if (baudRateIndex != BAUD_AUTO && baudRateIndex > BAUD_115200) {
+ continue;
+ }
+ portConfig.telemetry_baudrateIndex = baudRateIndex;
+ break;
+ case 3:
+ if (baudRateIndex < BAUD_19200 || baudRateIndex > BAUD_2470000) {
+ continue;
+ }
+ portConfig.blackbox_baudrateIndex = baudRateIndex;
+ break;
+ }
+
+ validArgumentCount++;
+ }
+
+ if (validArgumentCount < 6) {
+ cliShowParseError();
+ return;
+ }
+
+ memcpy(currentConfig, &portConfig, sizeof(portConfig));
+}
+
+#ifndef SKIP_SERIAL_PASSTHROUGH
+static void cliSerialPassthrough(char *cmdline)
+{
+ if (isEmpty(cmdline)) {
+ cliShowParseError();
+ return;
+ }
+
+ int id = -1;
+ uint32_t baud = 0;
+ unsigned mode = 0;
+ char *saveptr;
+ char* tok = strtok_r(cmdline, " ", &saveptr);
+ int index = 0;
+
+ while (tok != NULL) {
+ switch(index) {
+ case 0:
+ id = atoi(tok);
+ break;
+ case 1:
+ baud = atoi(tok);
+ break;
+ case 2:
+ if (strstr(tok, "rx") || strstr(tok, "RX"))
+ mode |= MODE_RX;
+ if (strstr(tok, "tx") || strstr(tok, "TX"))
+ mode |= MODE_TX;
+ break;
+ }
+ index++;
+ tok = strtok_r(NULL, " ", &saveptr);
+ }
+
+ tfp_printf("Port %d ", id);
+ serialPort_t *passThroughPort;
+ serialPortUsage_t *passThroughPortUsage = findSerialPortUsageByIdentifier(id);
+ if (!passThroughPortUsage || passThroughPortUsage->serialPort == NULL) {
+ if (!baud) {
+ tfp_printf("closed, specify baud.\r\n");
+ return;
+ }
+ if (!mode)
+ mode = MODE_RXTX;
+
+ passThroughPort = openSerialPort(id, FUNCTION_NONE, NULL,
+ baud, mode,
+ SERIAL_NOT_INVERTED);
+ if (!passThroughPort) {
+ tfp_printf("could not be opened.\r\n");
+ return;
+ }
+ tfp_printf("opened, baud = %d.\r\n", baud);
+ } else {
+ passThroughPort = passThroughPortUsage->serialPort;
+ // If the user supplied a mode, override the port's mode, otherwise
+ // leave the mode unchanged. serialPassthrough() handles one-way ports.
+ tfp_printf("already open.\r\n");
+ if (mode && passThroughPort->mode != mode) {
+ tfp_printf("mode changed from %d to %d.\r\n",
+ passThroughPort->mode, mode);
+ serialSetMode(passThroughPort, mode);
+ }
+ // If this port has a rx callback associated we need to remove it now.
+ // Otherwise no data will be pushed in the serial port buffer!
+ if (passThroughPort->rxCallback) {
+ passThroughPort->rxCallback = 0;
+ }
+ }
+
+ tfp_printf("forwarding, power cycle to exit.\r\n");
+
+ serialPassthrough(cliPort, passThroughPort, NULL, NULL);
+}
+#endif
+
+static void printAdjustmentRange(uint8_t dumpMask, const adjustmentRange_t *adjustmentRanges, const adjustmentRange_t *defaultAdjustmentRanges)
+{
+ const char *format = "adjrange %u %u %u %u %u %u %u";
+ // print out adjustment ranges channel settings
+ for (uint32_t i = 0; i < MAX_ADJUSTMENT_RANGE_COUNT; i++) {
+ const adjustmentRange_t *ar = &adjustmentRanges[i];
+ bool equalsDefault = false;
+ if (defaultAdjustmentRanges) {
+ const adjustmentRange_t *arDefault = &defaultAdjustmentRanges[i];
+ equalsDefault = ar->auxChannelIndex == arDefault->auxChannelIndex
+ && ar->range.startStep == arDefault->range.startStep
+ && ar->range.endStep == arDefault->range.endStep
+ && ar->adjustmentFunction == arDefault->adjustmentFunction
+ && ar->auxSwitchChannelIndex == arDefault->auxSwitchChannelIndex
+ && ar->adjustmentIndex == arDefault->adjustmentIndex;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ arDefault->adjustmentIndex,
+ arDefault->auxChannelIndex,
+ MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.startStep),
+ MODE_STEP_TO_CHANNEL_VALUE(arDefault->range.endStep),
+ arDefault->adjustmentFunction,
+ arDefault->auxSwitchChannelIndex
+ );
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ ar->adjustmentIndex,
+ ar->auxChannelIndex,
+ MODE_STEP_TO_CHANNEL_VALUE(ar->range.startStep),
+ MODE_STEP_TO_CHANNEL_VALUE(ar->range.endStep),
+ ar->adjustmentFunction,
+ ar->auxSwitchChannelIndex
+ );
+ }
+}
+
+static void cliAdjustmentRange(char *cmdline)
+{
+ int i, val = 0;
+ const char *ptr;
+
+ if (isEmpty(cmdline)) {
+ printAdjustmentRange(DUMP_MASTER, adjustmentRanges(0), NULL);
+ } else {
+ ptr = cmdline;
+ i = atoi(ptr++);
+ if (i < MAX_ADJUSTMENT_RANGE_COUNT) {
+ adjustmentRange_t *ar = adjustmentRangesMutable(i);
+ uint8_t validArgumentCount = 0;
+
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ if (val >= 0 && val < MAX_SIMULTANEOUS_ADJUSTMENT_COUNT) {
+ ar->adjustmentIndex = val;
+ validArgumentCount++;
+ }
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
+ ar->auxChannelIndex = val;
+ validArgumentCount++;
+ }
+ }
+
+ ptr = processChannelRangeArgs(ptr, &ar->range, &validArgumentCount);
+
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ if (val >= 0 && val < ADJUSTMENT_FUNCTION_COUNT) {
+ ar->adjustmentFunction = val;
+ validArgumentCount++;
+ }
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
+ ar->auxSwitchChannelIndex = val;
+ validArgumentCount++;
+ }
+ }
+
+ if (validArgumentCount != 6) {
+ memset(ar, 0, sizeof(adjustmentRange_t));
+ cliShowParseError();
+ }
+ } else {
+ cliShowArgumentRangeError("index", 0, MAX_ADJUSTMENT_RANGE_COUNT - 1);
+ }
+ }
+}
+
+#ifndef USE_QUAD_MIXER_ONLY
+static void printMotorMix(uint8_t dumpMask, const motorMixer_t *customMotorMixer, const motorMixer_t *defaultCustomMotorMixer)
+{
+ const char *format = "mmix %d %s %s %s %s";
+ char buf0[FTOA_BUFFER_LENGTH];
+ char buf1[FTOA_BUFFER_LENGTH];
+ char buf2[FTOA_BUFFER_LENGTH];
+ char buf3[FTOA_BUFFER_LENGTH];
+ for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
+ if (customMotorMixer[i].throttle == 0.0f)
+ break;
+ const float thr = customMotorMixer[i].throttle;
+ const float roll = customMotorMixer[i].roll;
+ const float pitch = customMotorMixer[i].pitch;
+ const float yaw = customMotorMixer[i].yaw;
+ bool equalsDefault = false;
+ if (defaultCustomMotorMixer) {
+ const float thrDefault = defaultCustomMotorMixer[i].throttle;
+ const float rollDefault = defaultCustomMotorMixer[i].roll;
+ const float pitchDefault = defaultCustomMotorMixer[i].pitch;
+ const float yawDefault = defaultCustomMotorMixer[i].yaw;
+ const bool equalsDefault = thr == thrDefault && roll == rollDefault && pitch == pitchDefault && yaw == yawDefault;
+
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ ftoa(thrDefault, buf0),
+ ftoa(rollDefault, buf1),
+ ftoa(pitchDefault, buf2),
+ ftoa(yawDefault, buf3));
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ ftoa(thr, buf0),
+ ftoa(roll, buf1),
+ ftoa(pitch, buf2),
+ ftoa(yaw, buf3));
+ }
+}
+#endif // USE_QUAD_MIXER_ONLY
+
+static void cliMotorMix(char *cmdline)
+{
+#ifdef USE_QUAD_MIXER_ONLY
+ UNUSED(cmdline);
+#else
+ int check = 0;
+ uint8_t len;
+ const char *ptr;
+
+ if (isEmpty(cmdline)) {
+ printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
+ } else if (strncasecmp(cmdline, "reset", 5) == 0) {
+ // erase custom mixer
+ for (uint32_t i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
+ customMotorMixerMutable(i)->throttle = 0.0f;
+ }
+ } else if (strncasecmp(cmdline, "load", 4) == 0) {
+ ptr = nextArg(cmdline);
+ if (ptr) {
+ len = strlen(ptr);
+ for (uint32_t i = 0; ; i++) {
+ if (mixerNames[i] == NULL) {
+ cliPrintLine("Invalid name");
+ break;
+ }
+ if (strncasecmp(ptr, mixerNames[i], len) == 0) {
+ mixerLoadMix(i, customMotorMixerMutable(0));
+ cliPrintLinef("Loaded %s", mixerNames[i]);
+ cliMotorMix("");
+ break;
+ }
+ }
+ }
+ } else {
+ ptr = cmdline;
+ uint32_t i = atoi(ptr); // get motor number
+ if (i < MAX_SUPPORTED_MOTORS) {
+ ptr = nextArg(ptr);
+ if (ptr) {
+ customMotorMixerMutable(i)->throttle = fastA2F(ptr);
+ check++;
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ customMotorMixerMutable(i)->roll = fastA2F(ptr);
+ check++;
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ customMotorMixerMutable(i)->pitch = fastA2F(ptr);
+ check++;
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ customMotorMixerMutable(i)->yaw = fastA2F(ptr);
+ check++;
+ }
+ if (check != 4) {
+ cliShowParseError();
+ } else {
+ printMotorMix(DUMP_MASTER, customMotorMixer(0), NULL);
+ }
+ } else {
+ cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
+ }
+ }
+#endif
+}
+
+static void printRxRange(uint8_t dumpMask, const rxChannelRangeConfig_t *channelRangeConfigs, const rxChannelRangeConfig_t *defaultChannelRangeConfigs)
+{
+ const char *format = "rxrange %u %u %u";
+ for (uint32_t i = 0; i < NON_AUX_CHANNEL_COUNT; i++) {
+ bool equalsDefault = false;
+ if (defaultChannelRangeConfigs) {
+ equalsDefault = channelRangeConfigs[i].min == defaultChannelRangeConfigs[i].min
+ && channelRangeConfigs[i].max == defaultChannelRangeConfigs[i].max;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ defaultChannelRangeConfigs[i].min,
+ defaultChannelRangeConfigs[i].max
+ );
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ channelRangeConfigs[i].min,
+ channelRangeConfigs[i].max
+ );
+ }
+}
+
+static void cliRxRange(char *cmdline)
+{
+ int i, validArgumentCount = 0;
+ const char *ptr;
+
+ if (isEmpty(cmdline)) {
+ printRxRange(DUMP_MASTER, rxChannelRangeConfigs(0), NULL);
+ } else if (strcasecmp(cmdline, "reset") == 0) {
+ resetAllRxChannelRangeConfigurations(rxChannelRangeConfigsMutable(0));
+ } else {
+ ptr = cmdline;
+ i = atoi(ptr);
+ if (i >= 0 && i < NON_AUX_CHANNEL_COUNT) {
+ int rangeMin, rangeMax;
+
+ ptr = nextArg(ptr);
+ if (ptr) {
+ rangeMin = atoi(ptr);
+ validArgumentCount++;
+ }
+
+ ptr = nextArg(ptr);
+ if (ptr) {
+ rangeMax = atoi(ptr);
+ validArgumentCount++;
+ }
+
+ if (validArgumentCount != 2) {
+ cliShowParseError();
+ } else if (rangeMin < PWM_PULSE_MIN || rangeMin > PWM_PULSE_MAX || rangeMax < PWM_PULSE_MIN || rangeMax > PWM_PULSE_MAX) {
+ cliShowParseError();
+ } else {
+ rxChannelRangeConfig_t *channelRangeConfig = rxChannelRangeConfigsMutable(i);
+ channelRangeConfig->min = rangeMin;
+ channelRangeConfig->max = rangeMax;
+ }
+ } else {
+ cliShowArgumentRangeError("channel", 0, NON_AUX_CHANNEL_COUNT - 1);
+ }
+ }
+}
+
+#ifdef LED_STRIP
+static void printLed(uint8_t dumpMask, const ledConfig_t *ledConfigs, const ledConfig_t *defaultLedConfigs)
+{
+ const char *format = "led %u %s";
+ char ledConfigBuffer[20];
+ char ledConfigDefaultBuffer[20];
+ for (uint32_t i = 0; i < LED_MAX_STRIP_LENGTH; i++) {
+ ledConfig_t ledConfig = ledConfigs[i];
+ generateLedConfig(&ledConfig, ledConfigBuffer, sizeof(ledConfigBuffer));
+ bool equalsDefault = false;
+ if (defaultLedConfigs) {
+ ledConfig_t ledConfigDefault = defaultLedConfigs[i];
+ equalsDefault = ledConfig == ledConfigDefault;
+ generateLedConfig(&ledConfigDefault, ledConfigDefaultBuffer, sizeof(ledConfigDefaultBuffer));
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, ledConfigDefaultBuffer);
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format, i, ledConfigBuffer);
+ }
+}
+
+static void cliLed(char *cmdline)
+{
+ int i;
+ const char *ptr;
+
+ if (isEmpty(cmdline)) {
+ printLed(DUMP_MASTER, ledStripConfig()->ledConfigs, NULL);
+ } else {
+ ptr = cmdline;
+ i = atoi(ptr);
+ if (i < LED_MAX_STRIP_LENGTH) {
+ ptr = nextArg(cmdline);
+ if (!parseLedStripConfig(i, ptr)) {
+ cliShowParseError();
+ }
+ } else {
+ cliShowArgumentRangeError("index", 0, LED_MAX_STRIP_LENGTH - 1);
+ }
+ }
+}
+
+static void printColor(uint8_t dumpMask, const hsvColor_t *colors, const hsvColor_t *defaultColors)
+{
+ const char *format = "color %u %d,%u,%u";
+ for (uint32_t i = 0; i < LED_CONFIGURABLE_COLOR_COUNT; i++) {
+ const hsvColor_t *color = &colors[i];
+ bool equalsDefault = false;
+ if (defaultColors) {
+ const hsvColor_t *colorDefault = &defaultColors[i];
+ equalsDefault = color->h == colorDefault->h
+ && color->s == colorDefault->s
+ && color->v == colorDefault->v;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format, i,colorDefault->h, colorDefault->s, colorDefault->v);
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format, i, color->h, color->s, color->v);
+ }
+}
+
+static void cliColor(char *cmdline)
+{
+ if (isEmpty(cmdline)) {
+ printColor(DUMP_MASTER, ledStripConfig()->colors, NULL);
+ } else {
+ const char *ptr = cmdline;
+ const int i = atoi(ptr);
+ if (i < LED_CONFIGURABLE_COLOR_COUNT) {
+ ptr = nextArg(cmdline);
+ if (!parseColor(i, ptr)) {
+ cliShowParseError();
+ }
+ } else {
+ cliShowArgumentRangeError("index", 0, LED_CONFIGURABLE_COLOR_COUNT - 1);
+ }
+ }
+}
+
+static void printModeColor(uint8_t dumpMask, const ledStripConfig_t *ledStripConfig, const ledStripConfig_t *defaultLedStripConfig)
+{
+ const char *format = "mode_color %u %u %u";
+ for (uint32_t i = 0; i < LED_MODE_COUNT; i++) {
+ for (uint32_t j = 0; j < LED_DIRECTION_COUNT; j++) {
+ int colorIndex = ledStripConfig->modeColors[i].color[j];
+ bool equalsDefault = false;
+ if (defaultLedStripConfig) {
+ int colorIndexDefault = defaultLedStripConfig->modeColors[i].color[j];
+ equalsDefault = colorIndex == colorIndexDefault;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndexDefault);
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format, i, j, colorIndex);
+ }
+ }
+
+ for (uint32_t j = 0; j < LED_SPECIAL_COLOR_COUNT; j++) {
+ const int colorIndex = ledStripConfig->specialColors.color[j];
+ bool equalsDefault = false;
+ if (defaultLedStripConfig) {
+ const int colorIndexDefault = defaultLedStripConfig->specialColors.color[j];
+ equalsDefault = colorIndex == colorIndexDefault;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndexDefault);
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_SPECIAL, j, colorIndex);
+ }
+
+ const int ledStripAuxChannel = ledStripConfig->ledstrip_aux_channel;
+ bool equalsDefault = false;
+ if (defaultLedStripConfig) {
+ const int ledStripAuxChannelDefault = defaultLedStripConfig->ledstrip_aux_channel;
+ equalsDefault = ledStripAuxChannel == ledStripAuxChannelDefault;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannelDefault);
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format, LED_AUX_CHANNEL, 0, ledStripAuxChannel);
+}
+
+static void cliModeColor(char *cmdline)
+{
+ if (isEmpty(cmdline)) {
+ printModeColor(DUMP_MASTER, ledStripConfig(), NULL);
+ } else {
+ enum {MODE = 0, FUNCTION, COLOR, ARGS_COUNT};
+ int args[ARGS_COUNT];
+ int argNo = 0;
+ char *saveptr;
+ const char* ptr = strtok_r(cmdline, " ", &saveptr);
+ while (ptr && argNo < ARGS_COUNT) {
+ args[argNo++] = atoi(ptr);
+ ptr = strtok_r(NULL, " ", &saveptr);
+ }
+
+ if (ptr != NULL || argNo != ARGS_COUNT) {
+ cliShowParseError();
+ return;
+ }
+
+ int modeIdx = args[MODE];
+ int funIdx = args[FUNCTION];
+ int color = args[COLOR];
+ if(!setModeColor(modeIdx, funIdx, color)) {
+ cliShowParseError();
+ return;
+ }
+ // values are validated
+ cliPrintLinef("mode_color %u %u %u", modeIdx, funIdx, color);
+ }
+}
+#endif
+
+#ifdef USE_SERVOS
+static void printServo(uint8_t dumpMask, const servoParam_t *servoParams, const servoParam_t *defaultServoParams)
+{
+ // print out servo settings
+ const char *format = "servo %u %d %d %d %d %d";
+ for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
+ const servoParam_t *servoConf = &servoParams[i];
+ bool equalsDefault = false;
+ if (defaultServoParams) {
+ const servoParam_t *defaultServoConf = &defaultServoParams[i];
+ equalsDefault = servoConf->min == defaultServoConf->min
+ && servoConf->max == defaultServoConf->max
+ && servoConf->middle == defaultServoConf->middle
+ && servoConf->rate == defaultServoConf->rate
+ && servoConf->forwardFromChannel == defaultServoConf->forwardFromChannel;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ defaultServoConf->min,
+ defaultServoConf->max,
+ defaultServoConf->middle,
+ defaultServoConf->rate,
+ defaultServoConf->forwardFromChannel
+ );
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ servoConf->min,
+ servoConf->max,
+ servoConf->middle,
+ servoConf->rate,
+ servoConf->forwardFromChannel
+ );
+ }
+ // print servo directions
+ for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
+ const char *format = "smix reverse %d %d r";
+ const servoParam_t *servoConf = &servoParams[i];
+ const servoParam_t *servoConfDefault = &defaultServoParams[i];
+ if (defaultServoParams) {
+ bool equalsDefault = servoConf->reversedSources == servoConfDefault->reversedSources;
+ for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
+ equalsDefault = ~(servoConf->reversedSources ^ servoConfDefault->reversedSources) & (1 << channel);
+ if (servoConfDefault->reversedSources & (1 << channel)) {
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format, i , channel);
+ }
+ if (servoConf->reversedSources & (1 << channel)) {
+ cliDumpPrintLinef(dumpMask, equalsDefault, format, i , channel);
+ }
+ }
+ } else {
+ for (uint32_t channel = 0; channel < INPUT_SOURCE_COUNT; channel++) {
+ if (servoConf->reversedSources & (1 << channel)) {
+ cliDumpPrintLinef(dumpMask, true, format, i , channel);
+ }
+ }
+ }
+ }
+}
+
+static void cliServo(char *cmdline)
+{
+ enum { SERVO_ARGUMENT_COUNT = 6 };
+ int16_t arguments[SERVO_ARGUMENT_COUNT];
+
+ servoParam_t *servo;
+
+ int i;
+ char *ptr;
+
+ if (isEmpty(cmdline)) {
+ printServo(DUMP_MASTER, servoParams(0), NULL);
+ } else {
+ int validArgumentCount = 0;
+
+ ptr = cmdline;
+
+ // Command line is integers (possibly negative) separated by spaces, no other characters allowed.
+
+ // If command line doesn't fit the format, don't modify the config
+ while (*ptr) {
+ if (*ptr == '-' || (*ptr >= '0' && *ptr <= '9')) {
+ if (validArgumentCount >= SERVO_ARGUMENT_COUNT) {
+ cliShowParseError();
+ return;
+ }
+
+ arguments[validArgumentCount++] = atoi(ptr);
+
+ do {
+ ptr++;
+ } while (*ptr >= '0' && *ptr <= '9');
+ } else if (*ptr == ' ') {
+ ptr++;
+ } else {
+ cliShowParseError();
+ return;
+ }
+ }
+
+ enum {INDEX = 0, MIN, MAX, MIDDLE, RATE, FORWARD};
+
+ i = arguments[INDEX];
+
+ // Check we got the right number of args and the servo index is correct (don't validate the other values)
+ if (validArgumentCount != SERVO_ARGUMENT_COUNT || i < 0 || i >= MAX_SUPPORTED_SERVOS) {
+ cliShowParseError();
+ return;
+ }
+
+ servo = servoParamsMutable(i);
+
+ if (
+ arguments[MIN] < PWM_PULSE_MIN || arguments[MIN] > PWM_PULSE_MAX ||
+ arguments[MAX] < PWM_PULSE_MIN || arguments[MAX] > PWM_PULSE_MAX ||
+ arguments[MIDDLE] < arguments[MIN] || arguments[MIDDLE] > arguments[MAX] ||
+ arguments[MIN] > arguments[MAX] || arguments[MAX] < arguments[MIN] ||
+ arguments[RATE] < -100 || arguments[RATE] > 100 ||
+ arguments[FORWARD] >= MAX_SUPPORTED_RC_CHANNEL_COUNT
+ ) {
+ cliShowParseError();
+ return;
+ }
+
+ servo->min = arguments[MIN];
+ servo->max = arguments[MAX];
+ servo->middle = arguments[MIDDLE];
+ servo->rate = arguments[RATE];
+ servo->forwardFromChannel = arguments[FORWARD];
+ }
+}
+#endif
+
+#ifdef USE_SERVOS
+static void printServoMix(uint8_t dumpMask, const servoMixer_t *customServoMixers, const servoMixer_t *defaultCustomServoMixers)
+{
+ const char *format = "smix %d %d %d %d %d %d %d %d";
+ for (uint32_t i = 0; i < MAX_SERVO_RULES; i++) {
+ const servoMixer_t customServoMixer = customServoMixers[i];
+ if (customServoMixer.rate == 0) {
+ break;
+ }
+
+ bool equalsDefault = false;
+ if (defaultCustomServoMixers) {
+ servoMixer_t customServoMixerDefault = defaultCustomServoMixers[i];
+ equalsDefault = customServoMixer.targetChannel == customServoMixerDefault.targetChannel
+ && customServoMixer.inputSource == customServoMixerDefault.inputSource
+ && customServoMixer.rate == customServoMixerDefault.rate
+ && customServoMixer.speed == customServoMixerDefault.speed
+ && customServoMixer.min == customServoMixerDefault.min
+ && customServoMixer.max == customServoMixerDefault.max
+ && customServoMixer.box == customServoMixerDefault.box;
+
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ customServoMixerDefault.targetChannel,
+ customServoMixerDefault.inputSource,
+ customServoMixerDefault.rate,
+ customServoMixerDefault.speed,
+ customServoMixerDefault.min,
+ customServoMixerDefault.max,
+ customServoMixerDefault.box
+ );
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ customServoMixer.targetChannel,
+ customServoMixer.inputSource,
+ customServoMixer.rate,
+ customServoMixer.speed,
+ customServoMixer.min,
+ customServoMixer.max,
+ customServoMixer.box
+ );
+ }
+
+ cliPrintLinefeed();
+}
+
+static void cliServoMix(char *cmdline)
+{
+ int args[8], check = 0;
+ int len = strlen(cmdline);
+
+ if (len == 0) {
+ printServoMix(DUMP_MASTER, customServoMixers(0), NULL);
+ } else if (strncasecmp(cmdline, "reset", 5) == 0) {
+ // erase custom mixer
+ memset(customServoMixers_array(), 0, sizeof(*customServoMixers_array()));
+ for (uint32_t i = 0; i < MAX_SUPPORTED_SERVOS; i++) {
+ servoParamsMutable(i)->reversedSources = 0;
+ }
+ } else if (strncasecmp(cmdline, "load", 4) == 0) {
+ const char *ptr = nextArg(cmdline);
+ if (ptr) {
+ len = strlen(ptr);
+ for (uint32_t i = 0; ; i++) {
+ if (mixerNames[i] == NULL) {
+ cliPrintLine("Invalid name");
+ break;
+ }
+ if (strncasecmp(ptr, mixerNames[i], len) == 0) {
+ servoMixerLoadMix(i);
+ cliPrintLinef("Loaded %s", mixerNames[i]);
+ cliServoMix("");
+ break;
+ }
+ }
+ }
+ } else if (strncasecmp(cmdline, "reverse", 7) == 0) {
+ enum {SERVO = 0, INPUT, REVERSE, ARGS_COUNT};
+ char *ptr = strchr(cmdline, ' ');
+
+ len = strlen(ptr);
+ if (len == 0) {
+ cliPrintf("s");
+ for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
+ cliPrintf("\ti%d", inputSource);
+ cliPrintLinefeed();
+
+ for (uint32_t servoIndex = 0; servoIndex < MAX_SUPPORTED_SERVOS; servoIndex++) {
+ cliPrintf("%d", servoIndex);
+ for (uint32_t inputSource = 0; inputSource < INPUT_SOURCE_COUNT; inputSource++)
+ cliPrintf("\t%s ", (servoParams(servoIndex)->reversedSources & (1 << inputSource)) ? "r" : "n");
+ cliPrintLinefeed();
+ }
+ return;
+ }
+
+ char *saveptr;
+ ptr = strtok_r(ptr, " ", &saveptr);
+ while (ptr != NULL && check < ARGS_COUNT - 1) {
+ args[check++] = atoi(ptr);
+ ptr = strtok_r(NULL, " ", &saveptr);
+ }
+
+ if (ptr == NULL || check != ARGS_COUNT - 1) {
+ cliShowParseError();
+ return;
+ }
+
+ if (args[SERVO] >= 0 && args[SERVO] < MAX_SUPPORTED_SERVOS
+ && args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT
+ && (*ptr == 'r' || *ptr == 'n')) {
+ if (*ptr == 'r')
+ servoParamsMutable(args[SERVO])->reversedSources |= 1 << args[INPUT];
+ else
+ servoParamsMutable(args[SERVO])->reversedSources &= ~(1 << args[INPUT]);
+ } else
+ cliShowParseError();
+
+ cliServoMix("reverse");
+ } else {
+ enum {RULE = 0, TARGET, INPUT, RATE, SPEED, MIN, MAX, BOX, ARGS_COUNT};
+ char *saveptr;
+ char *ptr = strtok_r(cmdline, " ", &saveptr);
+ while (ptr != NULL && check < ARGS_COUNT) {
+ args[check++] = atoi(ptr);
+ ptr = strtok_r(NULL, " ", &saveptr);
+ }
+
+ if (ptr != NULL || check != ARGS_COUNT) {
+ cliShowParseError();
+ return;
+ }
+
+ int32_t i = args[RULE];
+ if (i >= 0 && i < MAX_SERVO_RULES &&
+ args[TARGET] >= 0 && args[TARGET] < MAX_SUPPORTED_SERVOS &&
+ args[INPUT] >= 0 && args[INPUT] < INPUT_SOURCE_COUNT &&
+ args[RATE] >= -100 && args[RATE] <= 100 &&
+ args[SPEED] >= 0 && args[SPEED] <= MAX_SERVO_SPEED &&
+ args[MIN] >= 0 && args[MIN] <= 100 &&
+ args[MAX] >= 0 && args[MAX] <= 100 && args[MIN] < args[MAX] &&
+ args[BOX] >= 0 && args[BOX] <= MAX_SERVO_BOXES) {
+ customServoMixersMutable(i)->targetChannel = args[TARGET];
+ customServoMixersMutable(i)->inputSource = args[INPUT];
+ customServoMixersMutable(i)->rate = args[RATE];
+ customServoMixersMutable(i)->speed = args[SPEED];
+ customServoMixersMutable(i)->min = args[MIN];
+ customServoMixersMutable(i)->max = args[MAX];
+ customServoMixersMutable(i)->box = args[BOX];
+ cliServoMix("");
+ } else {
+ cliShowParseError();
+ }
+ }
+}
+#endif
+
+#ifdef USE_SDCARD
+
+static void cliWriteBytes(const uint8_t *buffer, int count)
+{
+ while (count > 0) {
+ cliWrite(*buffer);
+ buffer++;
+ count--;
+ }
+}
+
+static void cliSdInfo(char *cmdline)
+{
+ UNUSED(cmdline);
+
+ cliPrint("SD card: ");
+
+ if (!sdcard_isInserted()) {
+ cliPrintLine("None inserted");
+ return;
+ }
+
+ if (!sdcard_isInitialized()) {
+ cliPrintLine("Startup failed");
+ return;
+ }
+
+ const sdcardMetadata_t *metadata = sdcard_getMetadata();
+
+ cliPrintf("Manufacturer 0x%x, %ukB, %02d/%04d, v%d.%d, '",
+ metadata->manufacturerID,
+ metadata->numBlocks / 2, /* One block is half a kB */
+ metadata->productionMonth,
+ metadata->productionYear,
+ metadata->productRevisionMajor,
+ metadata->productRevisionMinor
+ );
+
+ cliWriteBytes((uint8_t*)metadata->productName, sizeof(metadata->productName));
+
+ cliPrint("'\r\n" "Filesystem: ");
+
+ switch (afatfs_getFilesystemState()) {
+ case AFATFS_FILESYSTEM_STATE_READY:
+ cliPrint("Ready");
+ break;
+ case AFATFS_FILESYSTEM_STATE_INITIALIZATION:
+ cliPrint("Initializing");
+ break;
+ case AFATFS_FILESYSTEM_STATE_UNKNOWN:
+ case AFATFS_FILESYSTEM_STATE_FATAL:
+ cliPrint("Fatal");
+
+ switch (afatfs_getLastError()) {
+ case AFATFS_ERROR_BAD_MBR:
+ cliPrint(" - no FAT MBR partitions");
+ break;
+ case AFATFS_ERROR_BAD_FILESYSTEM_HEADER:
+ cliPrint(" - bad FAT header");
+ break;
+ case AFATFS_ERROR_GENERIC:
+ case AFATFS_ERROR_NONE:
+ ; // Nothing more detailed to print
+ break;
+ }
+ break;
+ }
+ cliPrintLinefeed();
+}
+
+#endif
+
+#ifdef USE_FLASHFS
+
+static void cliFlashInfo(char *cmdline)
+{
+ const flashGeometry_t *layout = flashfsGetGeometry();
+
+ UNUSED(cmdline);
+
+ cliPrintLinef("Flash sectors=%u, sectorSize=%u, pagesPerSector=%u, pageSize=%u, totalSize=%u, usedSize=%u",
+ layout->sectors, layout->sectorSize, layout->pagesPerSector, layout->pageSize, layout->totalSize, flashfsGetOffset());
+}
+
+
+static void cliFlashErase(char *cmdline)
+{
+ UNUSED(cmdline);
+
+#ifndef MINIMAL_CLI
+ uint32_t i = 0;
+ cliPrintLine("Erasing, please wait ... ");
+#else
+ cliPrintLine("Erasing,");
+#endif
+
+ bufWriterFlush(cliWriter);
+ flashfsEraseCompletely();
+
+ while (!flashfsIsReady()) {
+#ifndef MINIMAL_CLI
+ cliPrintf(".");
+ if (i++ > 120) {
+ i=0;
+ cliPrintLinefeed();
+ }
+
+ bufWriterFlush(cliWriter);
+#endif
+ delay(100);
+ }
+ beeper(BEEPER_BLACKBOX_ERASE);
+ cliPrintLinefeed();
+ cliPrintLine("Done.");
+}
+
+#ifdef USE_FLASH_TOOLS
+
+static void cliFlashWrite(char *cmdline)
+{
+ const uint32_t address = atoi(cmdline);
+ const char *text = strchr(cmdline, ' ');
+
+ if (!text) {
+ cliShowParseError();
+ } else {
+ flashfsSeekAbs(address);
+ flashfsWrite((uint8_t*)text, strlen(text), true);
+ flashfsFlushSync();
+
+ cliPrintLinef("Wrote %u bytes at %u.", strlen(text), address);
+ }
+}
+
+static void cliFlashRead(char *cmdline)
+{
+ uint32_t address = atoi(cmdline);
+
+ const char *nextArg = strchr(cmdline, ' ');
+
+ if (!nextArg) {
+ cliShowParseError();
+ } else {
+ uint32_t length = atoi(nextArg);
+
+ cliPrintLinef("Reading %u bytes at %u:", length, address);
+
+ uint8_t buffer[32];
+ while (length > 0) {
+ int bytesRead = flashfsReadAbs(address, buffer, length < sizeof(buffer) ? length : sizeof(buffer));
+
+ for (int i = 0; i < bytesRead; i++) {
+ cliWrite(buffer[i]);
+ }
+
+ length -= bytesRead;
+ address += bytesRead;
+
+ if (bytesRead == 0) {
+ //Assume we reached the end of the volume or something fatal happened
+ break;
+ }
+ }
+ cliPrintLinefeed();
+ }
+}
+
+#endif
+#endif
+
+#ifdef VTX_CONTROL
+static void printVtx(uint8_t dumpMask, const vtxConfig_t *vtxConfig, const vtxConfig_t *vtxConfigDefault)
+{
+ // print out vtx channel settings
+ const char *format = "vtx %u %u %u %u %u %u";
+ bool equalsDefault = false;
+ for (uint32_t i = 0; i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT; i++) {
+ const vtxChannelActivationCondition_t *cac = &vtxConfig->vtxChannelActivationConditions[i];
+ if (vtxConfigDefault) {
+ const vtxChannelActivationCondition_t *cacDefault = &vtxConfigDefault->vtxChannelActivationConditions[i];
+ equalsDefault = cac->auxChannelIndex == cacDefault->auxChannelIndex
+ && cac->band == cacDefault->band
+ && cac->channel == cacDefault->channel
+ && cac->range.startStep == cacDefault->range.startStep
+ && cac->range.endStep == cacDefault->range.endStep;
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ cacDefault->auxChannelIndex,
+ cacDefault->band,
+ cacDefault->channel,
+ MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.startStep),
+ MODE_STEP_TO_CHANNEL_VALUE(cacDefault->range.endStep)
+ );
+ }
+ cliDumpPrintLinef(dumpMask, equalsDefault, format,
+ i,
+ cac->auxChannelIndex,
+ cac->band,
+ cac->channel,
+ MODE_STEP_TO_CHANNEL_VALUE(cac->range.startStep),
+ MODE_STEP_TO_CHANNEL_VALUE(cac->range.endStep)
+ );
+ }
+}
+
+// FIXME remove these and use the VTX API
+#define VTX_BAND_MIN 1
+#define VTX_BAND_MAX 5
+#define VTX_CHANNEL_MIN 1
+#define VTX_CHANNEL_MAX 8
+
+static void cliVtx(char *cmdline)
+{
+ int i, val = 0;
+ const char *ptr;
+
+ if (isEmpty(cmdline)) {
+ printVtx(DUMP_MASTER, vtxConfig(), NULL);
+ } else {
+ ptr = cmdline;
+ i = atoi(ptr++);
+ if (i < MAX_CHANNEL_ACTIVATION_CONDITION_COUNT) {
+ vtxChannelActivationCondition_t *cac = &vtxConfigMutable()->vtxChannelActivationConditions[i];
+ uint8_t validArgumentCount = 0;
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ if (val >= 0 && val < MAX_AUX_CHANNEL_COUNT) {
+ cac->auxChannelIndex = val;
+ validArgumentCount++;
+ }
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ // FIXME Use VTX API to get min/max
+ if (val >= VTX_BAND_MIN && val <= VTX_BAND_MAX) {
+ cac->band = val;
+ validArgumentCount++;
+ }
+ }
+ ptr = nextArg(ptr);
+ if (ptr) {
+ val = atoi(ptr);
+ // FIXME Use VTX API to get min/max
+ if (val >= VTX_CHANNEL_MIN && val <= VTX_CHANNEL_MAX) {
+ cac->channel = val;
+ validArgumentCount++;
+ }
+ }
+ ptr = processChannelRangeArgs(ptr, &cac->range, &validArgumentCount);
+
+ if (validArgumentCount != 5) {
+ memset(cac, 0, sizeof(vtxChannelActivationCondition_t));
+ }
+ } else {
+ cliShowArgumentRangeError("index", 0, MAX_CHANNEL_ACTIVATION_CONDITION_COUNT - 1);
+ }
+ }
+}
+
+#endif // VTX_CONTROL
+
+static void printName(uint8_t dumpMask, const systemConfig_t *systemConfig)
+{
+ const bool equalsDefault = strlen(systemConfig->name) == 0;
+ cliDumpPrintLinef(dumpMask, equalsDefault, "name %s", equalsDefault ? emptyName : systemConfig->name);
+}
+
+static void cliName(char *cmdline)
+{
+ const uint32_t len = strlen(cmdline);
+ if (len > 0) {
+ memset(systemConfigMutable()->name, 0, ARRAYLEN(systemConfig()->name));
+ if (strncmp(cmdline, emptyName, len)) {
+ strncpy(systemConfigMutable()->name, cmdline, MIN(len, MAX_NAME_LENGTH));
+ }
+ }
+ printName(DUMP_MASTER, systemConfig());
+}
+
+static void printFeature(uint8_t dumpMask, const featureConfig_t *featureConfig, const featureConfig_t *featureConfigDefault)
+{
+ const uint32_t mask = featureConfig->enabledFeatures;
+ const uint32_t defaultMask = featureConfigDefault->enabledFeatures;
+ for (uint32_t i = 0; featureNames[i]; i++) { // disable all feature first
+ const char *format = "feature -%s";
+ cliDefaultPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
+ cliDumpPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
+ }
+ for (uint32_t i = 0; featureNames[i]; i++) { // reenable what we want.
+ const char *format = "feature %s";
+ if (defaultMask & (1 << i)) {
+ cliDefaultPrintLinef(dumpMask, (~defaultMask | mask) & (1 << i), format, featureNames[i]);
+ }
+ if (mask & (1 << i)) {
+ cliDumpPrintLinef(dumpMask, (defaultMask | ~mask) & (1 << i), format, featureNames[i]);
+ }
+ }
+}
+
+static void cliFeature(char *cmdline)
+{
+ uint32_t len = strlen(cmdline);
+ uint32_t mask = featureMask();
+
+ if (len == 0) {
+ cliPrint("Enabled: ");
+ for (uint32_t i = 0; ; i++) {
+ if (featureNames[i] == NULL)
+ break;
+ if (mask & (1 << i))
+ cliPrintf("%s ", featureNames[i]);
+ }
+ cliPrintLinefeed();
+ } else if (strncasecmp(cmdline, "list", len) == 0) {
+ cliPrint("Available:");
+ for (uint32_t i = 0; ; i++) {
+ if (featureNames[i] == NULL)
+ break;
+ cliPrintf(" %s", featureNames[i]);
+ }
+ cliPrintLinefeed();
+ return;
+ } else {
+ bool remove = false;
+ if (cmdline[0] == '-') {
+ // remove feature
+ remove = true;
+ cmdline++; // skip over -
+ len--;
+ }
+
+ for (uint32_t i = 0; ; i++) {
+ if (featureNames[i] == NULL) {
+ cliPrintLine("Invalid name");
+ break;
+ }
+
+ if (strncasecmp(cmdline, featureNames[i], len) == 0) {
+
+ mask = 1 << i;
+#ifndef GPS
+ if (mask & FEATURE_GPS) {
+ cliPrintLine("unavailable");
+ break;
+ }
+#endif
+#ifndef SONAR
+ if (mask & FEATURE_SONAR) {
+ cliPrintLine("unavailable");
+ break;
+ }
+#endif
+ if (remove) {
+ featureClear(mask);
+ cliPrint("Disabled");
+ } else {
+ featureSet(mask);
+ cliPrint("Enabled");
+ }
+ cliPrintLinef(" %s", featureNames[i]);
+ break;
+ }
+ }
+ }
+}
+
+#ifdef BEEPER
+static void printBeeper(uint8_t dumpMask, const beeperConfig_t *beeperConfig, const beeperConfig_t *beeperConfigDefault)
+{
+ const uint8_t beeperCount = beeperTableEntryCount();
+ const uint32_t mask = beeperConfig->beeper_off_flags;
+ const uint32_t defaultMask = beeperConfigDefault->beeper_off_flags;
+ for (int32_t i = 0; i < beeperCount - 2; i++) {
+ const char *formatOff = "beeper -%s";
+ const char *formatOn = "beeper %s";
+ cliDefaultPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOn : formatOff, beeperNameForTableIndex(i));
+ cliDumpPrintLinef(dumpMask, ~(mask ^ defaultMask) & (1 << i), mask & (1 << i) ? formatOff : formatOn, beeperNameForTableIndex(i));
+ }
+}
+
+static void cliBeeper(char *cmdline)
+{
+ uint32_t len = strlen(cmdline);
+ uint8_t beeperCount = beeperTableEntryCount();
+ uint32_t mask = getBeeperOffMask();
+
+ if (len == 0) {
+ cliPrintf("Disabled:");
+ for (int32_t i = 0; ; i++) {
+ if (i == beeperCount - 2){
+ if (mask == 0)
+ cliPrint(" none");
+ break;
+ }
+ if (mask & (1 << i))
+ cliPrintf(" %s", beeperNameForTableIndex(i));
+ }
+ cliPrintLinefeed();
+ } else if (strncasecmp(cmdline, "list", len) == 0) {
+ cliPrint("Available:");
+ for (uint32_t i = 0; i < beeperCount; i++)
+ cliPrintf(" %s", beeperNameForTableIndex(i));
+ cliPrintLinefeed();
+ return;
+ } else {
+ bool remove = false;
+ if (cmdline[0] == '-') {
+ remove = true; // this is for beeper OFF condition
+ cmdline++;
+ len--;
+ }
+
+ for (uint32_t i = 0; ; i++) {
+ if (i == beeperCount) {
+ cliPrintLine("Invalid name");
+ break;
+ }
+ if (strncasecmp(cmdline, beeperNameForTableIndex(i), len) == 0) {
+ if (remove) { // beeper off
+ if (i == BEEPER_ALL-1)
+ beeperOffSetAll(beeperCount-2);
+ else
+ if (i == BEEPER_PREFERENCE-1)
+ setBeeperOffMask(getPreferredBeeperOffMask());
+ else {
+ mask = 1 << i;
+ beeperOffSet(mask);
+ }
+ cliPrint("Disabled");
+ }
+ else { // beeper on
+ if (i == BEEPER_ALL-1)
+ beeperOffClearAll();
+ else
+ if (i == BEEPER_PREFERENCE-1)
+ setPreferredBeeperOffMask(getBeeperOffMask());
+ else {
+ mask = 1 << i;
+ beeperOffClear(mask);
+ }
+ cliPrint("Enabled");
+ }
+ cliPrintLinef(" %s", beeperNameForTableIndex(i));
+ break;
+ }
+ }
+ }
+}
+#endif
+
+static void printMap(uint8_t dumpMask, const rxConfig_t *rxConfig, const rxConfig_t *defaultRxConfig)
+{
+ bool equalsDefault = true;
+ char buf[16];
+ char bufDefault[16];
+ uint32_t i;
+ for (i = 0; i < MAX_MAPPABLE_RX_INPUTS; i++) {
+ buf[rxConfig->rcmap[i]] = rcChannelLetters[i];
+ if (defaultRxConfig) {
+ bufDefault[defaultRxConfig->rcmap[i]] = rcChannelLetters[i];
+ equalsDefault = equalsDefault && (rxConfig->rcmap[i] == defaultRxConfig->rcmap[i]);
+ }
+ }
+ buf[i] = '\0';
+
+ const char *formatMap = "map %s";
+ cliDefaultPrintLinef(dumpMask, equalsDefault, formatMap, bufDefault);
+ cliDumpPrintLinef(dumpMask, equalsDefault, formatMap, buf);
+}
+
+static void cliMap(char *cmdline)
+{
+ uint32_t len;
+ char out[9];
+
+ len = strlen(cmdline);
+
+ if (len == 8) {
+ // uppercase it
+ for (uint32_t i = 0; i < 8; i++)
+ cmdline[i] = toupper((unsigned char)cmdline[i]);
+ for (uint32_t i = 0; i < 8; i++) {
+ if (strchr(rcChannelLetters, cmdline[i]) && !strchr(cmdline + i + 1, cmdline[i]))
+ continue;
+ cliShowParseError();
+ return;
+ }
+ parseRcChannels(cmdline, rxConfigMutable());
+ }
+ cliPrint("Map: ");
+ uint32_t i;
+ for (i = 0; i < 8; i++)
+ out[rxConfig()->rcmap[i]] = rcChannelLetters[i];
+ out[i] = '\0';
+ cliPrintLine(out);
+}
+
+static char *checkCommand(char *cmdLine, const char *command)
+{
+ if(!strncasecmp(cmdLine, command, strlen(command)) // command names match
+ && (isspace((unsigned)cmdLine[strlen(command)]) || cmdLine[strlen(command)] == 0)) {
+ return cmdLine + strlen(command) + 1;
+ } else {
+ return 0;
+ }
+}
+
+static void cliRebootEx(bool bootLoader)
+{
+ cliPrint("\r\nRebooting");
+ bufWriterFlush(cliWriter);
+ waitForSerialPortToFinishTransmitting(cliPort);
+ stopPwmAllMotors();
+ if (bootLoader) {
+ systemResetToBootloader();
+ return;
+ }
+ systemReset();
+}
+
+static void cliReboot(void)
+{
+ cliRebootEx(false);
+}
+
+static void cliBootloader(char *cmdLine)
+{
+ UNUSED(cmdLine);
+
+ cliPrintHashLine("restarting in bootloader mode");
+ cliRebootEx(true);
+}
+
+static void cliExit(char *cmdline)
+{
+ UNUSED(cmdline);
+
+ cliPrintHashLine("leaving CLI mode, unsaved changes lost");
+ bufWriterFlush(cliWriter);
+
+ *cliBuffer = '\0';
+ bufferIndex = 0;
+ cliMode = 0;
+ // incase a motor was left running during motortest, clear it here
+ mixerResetDisarmedMotors();
+ cliReboot();
+
+ cliWriter = NULL;
+}
+
+#ifdef GPS
+static void cliGpsPassthrough(char *cmdline)
+{
+ UNUSED(cmdline);
+
+ gpsEnablePassthrough(cliPort);
+}
+#endif
+
+#if defined(USE_ESCSERIAL) || defined(USE_DSHOT)
+
+#ifndef ALL_ESCS
+#define ALL_ESCS 255
+#endif
+
+static int parseEscNumber(char *pch, bool allowAllEscs) {
+ int escNumber = atoi(pch);
+ if ((escNumber >= 0) && (escNumber < getMotorCount())) {
+ tfp_printf("Programming on ESC %d.\r\n", escNumber);
+ } else if (allowAllEscs && escNumber == ALL_ESCS) {
+ tfp_printf("Programming on all ESCs.\r\n");
+ } else {
+ tfp_printf("Invalid ESC number, range: 0 to %d.\r\n", getMotorCount() - 1);
+
+ return -1;
+ }
+
+ return escNumber;
+}
+#endif
+
+#ifdef USE_DSHOT
+static void cliDshotProg(char *cmdline)
+{
+ if (isEmpty(cmdline) || motorConfig()->dev.motorPwmProtocol < PWM_TYPE_DSHOT150) {
+ cliShowParseError();
+
+ return;
+ }
+
+ char *saveptr;
+ char *pch = strtok_r(cmdline, " ", &saveptr);
+ int pos = 0;
+ int escNumber = 0;
+ while (pch != NULL) {
+ switch (pos) {
+ case 0:
+ escNumber = parseEscNumber(pch, true);
+ if (escNumber == -1) {
+ return;
+ }
+
+ break;
+ default:
+ motorControlEnable = false;
+
+ int command = atoi(pch);
+ if (command >= 0 && command < DSHOT_MIN_THROTTLE) {
+ if (escNumber == ALL_ESCS) {
+ for (unsigned i = 0; i < getMotorCount(); i++) {
+ pwmWriteDshotCommand(i, command);
+ }
+ } else {
+ pwmWriteDshotCommand(escNumber, command);
+ }
+
+ if (command <= 5) {
+ delay(10); // wait for sound output to finish
+ }
+
+ tfp_printf("Command %d written.\r\n", command);
+ } else {
+ tfp_printf("Invalid command, range 1 to %d.\r\n", DSHOT_MIN_THROTTLE - 1);
+ }
+
+ break;
+ }
+
+ pos++;
+ pch = strtok_r(NULL, " ", &saveptr);
+ }
+
+ motorControlEnable = true;
+}
+#endif
+
+#ifdef USE_ESCSERIAL
+static void cliEscPassthrough(char *cmdline)
+{
+ if (isEmpty(cmdline)) {
+ cliShowParseError();
+
+ return;
+ }
+
+ char *saveptr;
+ char *pch = strtok_r(cmdline, " ", &saveptr);
+ int pos = 0;
+ uint8_t mode = 0;
+ int escNumber = 0;
+ while (pch != NULL) {
+ switch (pos) {
+ case 0:
+ if(strncasecmp(pch, "sk", strlen(pch)) == 0) {
+ mode = PROTOCOL_SIMONK;
+ } else if(strncasecmp(pch, "bl", strlen(pch)) == 0) {
+ mode = PROTOCOL_BLHELI;
+ } else if(strncasecmp(pch, "ki", strlen(pch)) == 0) {
+ mode = PROTOCOL_KISS;
+ } else if(strncasecmp(pch, "cc", strlen(pch)) == 0) {
+ mode = PROTOCOL_KISSALL;
+ } else {
+ cliShowParseError();
+
+ return;
+ }
+ break;
+ case 1:
+ escNumber = parseEscNumber(pch, mode == PROTOCOL_KISS);
+ if (escNumber == -1) {
+ return;
+ }
+
+ break;
+ default:
+ cliShowParseError();
+
+ return;
+
+ break;
+
+ }
+ pos++;
+ pch = strtok_r(NULL, " ", &saveptr);
+ }
+
+ escEnablePassthrough(cliPort, escNumber, mode);
+}
+#endif
+
+#ifndef USE_QUAD_MIXER_ONLY
+static void cliMixer(char *cmdline)
+{
+ int len;
+
+ len = strlen(cmdline);
+
+ if (len == 0) {
+ cliPrintLinef("Mixer: %s", mixerNames[mixerConfig()->mixerMode - 1]);
+ return;
+ } else if (strncasecmp(cmdline, "list", len) == 0) {
+ cliPrint("Available:");
+ for (uint32_t i = 0; ; i++) {
+ if (mixerNames[i] == NULL)
+ break;
+ cliPrintf(" %s", mixerNames[i]);
+ }
+ cliPrintLinefeed();
+ return;
+ }
+
+ for (uint32_t i = 0; ; i++) {
+ if (mixerNames[i] == NULL) {
+ cliPrintLine("Invalid name");
+ return;
+ }
+ if (strncasecmp(cmdline, mixerNames[i], len) == 0) {
+ mixerConfigMutable()->mixerMode = i + 1;
+ break;
+ }
+ }
+
+ cliMixer("");
+}
+#endif
+
+static void cliMotor(char *cmdline)
+{
+ int motor_index = 0;
+ int motor_value = 0;
+ int index = 0;
+ char *pch = NULL;
+ char *saveptr;
+
+ if (isEmpty(cmdline)) {
+ cliShowParseError();
+ return;
+ }
+
+ pch = strtok_r(cmdline, " ", &saveptr);
+ while (pch != NULL) {
+ switch (index) {
+ case 0:
+ motor_index = atoi(pch);
+ break;
+ case 1:
+ motor_value = atoi(pch);
+ break;
+ }
+ index++;
+ pch = strtok_r(NULL, " ", &saveptr);
+ }
+
+ if (motor_index < 0 || motor_index >= MAX_SUPPORTED_MOTORS) {
+ cliShowArgumentRangeError("index", 0, MAX_SUPPORTED_MOTORS - 1);
+ return;
+ }
+
+ if (index == 2) {
+ if (motor_value < PWM_RANGE_MIN || motor_value > PWM_RANGE_MAX) {
+ cliShowArgumentRangeError("value", 1000, 2000);
+ } else {
+ motor_disarmed[motor_index] = convertExternalToMotor(motor_value);
+
+ cliPrintLinef("motor %d: %d", motor_index, convertMotorToExternal(motor_disarmed[motor_index]));
+ }
+ }
+
+}
+
+#ifndef MINIMAL_CLI
+static void cliPlaySound(char *cmdline)
+{
+ int i;
+ const char *name;
+ static int lastSoundIdx = -1;
+
+ if (isEmpty(cmdline)) {
+ i = lastSoundIdx + 1; //next sound index
+ if ((name=beeperNameForTableIndex(i)) == NULL) {
+ while (true) { //no name for index; try next one
+ if (++i >= beeperTableEntryCount())
+ i = 0; //if end then wrap around to first entry
+ if ((name=beeperNameForTableIndex(i)) != NULL)
+ break; //if name OK then play sound below
+ if (i == lastSoundIdx + 1) { //prevent infinite loop
+ cliPrintLine("Error playing sound");
+ return;
+ }
+ }
+ }
+ } else { //index value was given
+ i = atoi(cmdline);
+ if ((name=beeperNameForTableIndex(i)) == NULL) {
+ cliPrintLinef("No sound for index %d", i);
+ return;
+ }
+ }
+ lastSoundIdx = i;
+ beeperSilence();
+ cliPrintLinef("Playing sound %d: %s", i, name);
+ beeper(beeperModeForTableIndex(i));
+}
+#endif
+
+static void cliProfile(char *cmdline)
+{
+ if (isEmpty(cmdline)) {
+ cliPrintLinef("profile %d", getCurrentPidProfileIndex());
+ return;
+ } else {
+ const int i = atoi(cmdline);
+ if (i >= 0 && i < MAX_PROFILE_COUNT) {
+ systemConfigMutable()->pidProfileIndex = i;
+ cliProfile("");
+ }
+ }
+}
+
+static void cliRateProfile(char *cmdline)
+{
+ if (isEmpty(cmdline)) {
+ cliPrintLinef("rateprofile %d", getCurrentControlRateProfileIndex());
+ return;
+ } else {
+ const int i = atoi(cmdline);
+ if (i >= 0 && i < CONTROL_RATE_PROFILE_COUNT) {
+ changeControlRateProfile(i);
+ cliRateProfile("");
+ }
+ }
+}
+
+static void cliDumpPidProfile(uint8_t pidProfileIndex, uint8_t dumpMask)
+{
+ if (pidProfileIndex >= MAX_PROFILE_COUNT) {
+ // Faulty values
+ return;
+ }
+ changePidProfile(pidProfileIndex);
+ cliPrintHashLine("profile");
+ cliProfile("");
+ cliPrintLinefeed();
+ dumpAllValues(PROFILE_VALUE, dumpMask);
+}
+
+static void cliDumpRateProfile(uint8_t rateProfileIndex, uint8_t dumpMask)
+{
+ if (rateProfileIndex >= CONTROL_RATE_PROFILE_COUNT) {
+ // Faulty values
+ return;
+ }
+ changeControlRateProfile(rateProfileIndex);
+ cliPrintHashLine("rateprofile");
+ cliRateProfile("");
+ cliPrintLinefeed();
+ dumpAllValues(PROFILE_RATE_VALUE, dumpMask);
+}
+
+static void cliSave(char *cmdline)
+{
+ UNUSED(cmdline);
+
+ cliPrintHashLine("saving");
+ writeEEPROM();
+ cliReboot();
+}
+
+static void cliDefaults(char *cmdline)
+{
+ UNUSED(cmdline);
+
+ cliPrintHashLine("resetting to defaults");
+ resetEEPROM();
+ cliReboot();
+}
+
+static void cliGet(char *cmdline)
+{
+ const clivalue_t *val;
+ int matchedCommands = 0;
+
+ for (uint32_t i = 0; i < valueTableEntryCount; i++) {
+ if (strstr(valueTable[i].name, cmdline)) {
+ val = &valueTable[i];
+ cliPrintf("%s = ", valueTable[i].name);
+ cliPrintVar(val, 0);
+ cliPrintLinefeed();
+ cliPrintVarRange(val);
+ cliPrintLinefeed();
+
+ matchedCommands++;
+ }
+ }
+
+
+ if (matchedCommands) {
+ return;
+ }
+
+ cliPrintLine("Invalid name");
+}
+
+static char *skipSpace(char *buffer)
+{
+ while (*(buffer) == ' ') {
+ buffer++;
+ }
+
+ return buffer;
+}
+
+static uint8_t getWordLength(char *bufBegin, char *bufEnd)
+{
+ while (*(bufEnd - 1) == ' ') {
+ bufEnd--;
+ }
+
+ return bufEnd - bufBegin;
+}
+
+static void cliSet(char *cmdline)
+{
+ const uint32_t len = strlen(cmdline);
+ char *eqptr;
+
+ if (len == 0 || (len == 1 && cmdline[0] == '*')) {
+ cliPrintLine("Current settings: ");
+
+ for (uint32_t i = 0; i < valueTableEntryCount; i++) {
+ const clivalue_t *val = &valueTable[i];
+ cliPrintf("%s = ", valueTable[i].name);
+ cliPrintVar(val, len); // when len is 1 (when * is passed as argument), it will print min/max values as well, for gui
+ cliPrintLinefeed();
+ }
+ } else if ((eqptr = strstr(cmdline, "=")) != NULL) {
+ // has equals
+
+ uint8_t variableNameLength = getWordLength(cmdline, eqptr);
+
+ // skip the '=' and any ' ' characters
+ eqptr++;
+ eqptr = skipSpace(eqptr);
+
+ for (uint32_t i = 0; i < valueTableEntryCount; i++) {
+ const clivalue_t *val = &valueTable[i];
+ // ensure exact match when setting to prevent setting variables with shorter names
+ if (strncasecmp(cmdline, valueTable[i].name, strlen(valueTable[i].name)) == 0 && variableNameLength == strlen(valueTable[i].name)) {
+
+ bool valueChanged = false;
+ int16_t value = 0;
+ switch (valueTable[i].type & VALUE_MODE_MASK) {
+ case MODE_DIRECT: {
+ int16_t value = atoi(eqptr);
+
+ if (value >= valueTable[i].config.minmax.min && value <= valueTable[i].config.minmax.max) {
+ cliSetVar(val, value);
+ valueChanged = true;
+ }
+ }
+
+ break;
+ case MODE_LOOKUP: {
+ const lookupTableEntry_t *tableEntry = &lookupTables[valueTable[i].config.lookup.tableIndex];
+ bool matched = false;
+ for (uint32_t tableValueIndex = 0; tableValueIndex < tableEntry->valueCount && !matched; tableValueIndex++) {
+ matched = strcasecmp(tableEntry->values[tableValueIndex], eqptr) == 0;
+
+ if (matched) {
+ value = tableValueIndex;
+
+ cliSetVar(val, value);
+ valueChanged = true;
+ }
+ }
+ }
+
+ break;
+ case MODE_ARRAY: {
+ const uint8_t arrayLength = valueTable[i].config.array.length;
+ char *valPtr = eqptr;
+ uint8_t array[256];
+ char curVal[4];
+ for (int i = 0; i < arrayLength; i++) {
+ valPtr = skipSpace(valPtr);
+ char *valEnd = strstr(valPtr, ",");
+ if ((valEnd != NULL) && (i < arrayLength - 1)) {
+ uint8_t varLength = getWordLength(valPtr, valEnd);
+ if (varLength <= 3) {
+ strncpy(curVal, valPtr, getWordLength(valPtr, valEnd));
+ curVal[varLength] = '\0';
+ array[i] = (uint8_t)atoi((const char *)curVal);
+ valPtr = valEnd + 1;
+ } else {
+ break;
+ }
+ } else if ((valEnd == NULL) && (i == arrayLength - 1)) {
+ array[i] = atoi(valPtr);
+
+ uint8_t *ptr = getValuePointer(val);
+ memcpy(ptr, array, arrayLength);
+ valueChanged = true;
+ } else {
+ break;
+ }
+ }
+ }
+
+ break;
+
+ }
+
+ if (valueChanged) {
+ cliPrintf("%s set to ", valueTable[i].name);
+ cliPrintVar(val, 0);
+ } else {
+ cliPrintLine("Invalid value");
+ cliPrintVarRange(val);
+ }
+
+ return;
+ }
+ }
+ cliPrintLine("Invalid name");
+ } else {
+ // no equals, check for matching variables.
+ cliGet(cmdline);
+ }
+}
+
+static void cliStatus(char *cmdline)
+{
+ UNUSED(cmdline);
+
+ cliPrintLinef("System Uptime: %d seconds", millis() / 1000);
+ cliPrintLinef("Voltage: %d * 0.1V (%dS battery - %s)", getBatteryVoltage(), getBatteryCellCount(), getBatteryStateString());
+
+ cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));
+
+#if defined(USE_SENSOR_NAMES)
+ const uint32_t detectedSensorsMask = sensorsMask();
+ for (uint32_t i = 0; ; i++) {
+ if (sensorTypeNames[i] == NULL) {
+ break;
+ }
+ const uint32_t mask = (1 << i);
+ if ((detectedSensorsMask & mask) && (mask & SENSOR_NAMES_MASK)) {
+ const uint8_t sensorHardwareIndex = detectedSensors[i];
+ const char *sensorHardware = sensorHardwareNames[i][sensorHardwareIndex];
+ cliPrintf(", %s=%s", sensorTypeNames[i], sensorHardware);
+ if (mask == SENSOR_ACC && acc.dev.revisionCode) {
+ cliPrintf(".%c", acc.dev.revisionCode);
+ }
+ }
+ }
+#endif /* USE_SENSOR_NAMES */
+ cliPrintLinefeed();
+
+#ifdef USE_SDCARD
+ cliSdInfo(NULL);
+#endif
+
+#ifdef USE_I2C
+ const uint16_t i2cErrorCounter = i2cGetErrorCounter();
+#else
+ const uint16_t i2cErrorCounter = 0;
+#endif
+
+#ifdef STACK_CHECK
+ cliPrintf("Stack used: %d, ", stackUsedSize());
+#endif
+ cliPrintLinef("Stack size: %d, Stack address: 0x%x", stackTotalSize(), stackHighMem());
+
+ cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
+
+ const int gyroRate = getTaskDeltaTime(TASK_GYROPID) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_GYROPID)));
+ const int rxRate = getTaskDeltaTime(TASK_RX) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_RX)));
+ const int systemRate = getTaskDeltaTime(TASK_SYSTEM) == 0 ? 0 : (int)(1000000.0f / ((float)getTaskDeltaTime(TASK_SYSTEM)));
+ cliPrintLinef("CPU:%d%%, cycle time: %d, GYRO rate: %d, RX rate: %d, System rate: %d",
+ constrain(averageSystemLoadPercent, 0, 100), getTaskDeltaTime(TASK_GYROPID), gyroRate, rxRate, systemRate);
+
+}
+
+#ifndef SKIP_TASK_STATISTICS
+static void cliTasks(char *cmdline)
+{
+ UNUSED(cmdline);
+ int maxLoadSum = 0;
+ int averageLoadSum = 0;
+
+#ifndef MINIMAL_CLI
+ if (systemConfig()->task_statistics) {
+ cliPrintLine("Task list rate/hz max/us avg/us maxload avgload total/ms");
+ } else {
+ cliPrintLine("Task list");
+ }
+#endif
+ for (cfTaskId_e taskId = 0; taskId < TASK_COUNT; taskId++) {
+ cfTaskInfo_t taskInfo;
+ getTaskInfo(taskId, &taskInfo);
+ if (taskInfo.isEnabled) {
+ int taskFrequency;
+ int subTaskFrequency = 0;
+ if (taskId == TASK_GYROPID) {
+ subTaskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
+ taskFrequency = subTaskFrequency / pidConfig()->pid_process_denom;
+ if (pidConfig()->pid_process_denom > 1) {
+ cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
+ } else {
+ taskFrequency = subTaskFrequency;
+ cliPrintf("%02d - (%11s/%3s) ", taskId, taskInfo.subTaskName, taskInfo.taskName);
+ }
+ } else {
+ taskFrequency = taskInfo.latestDeltaTime == 0 ? 0 : (int)(1000000.0f / ((float)taskInfo.latestDeltaTime));
+ cliPrintf("%02d - (%15s) ", taskId, taskInfo.taskName);
+ }
+ const int maxLoad = taskInfo.maxExecutionTime == 0 ? 0 :(taskInfo.maxExecutionTime * taskFrequency + 5000) / 1000;
+ const int averageLoad = taskInfo.averageExecutionTime == 0 ? 0 : (taskInfo.averageExecutionTime * taskFrequency + 5000) / 1000;
+ if (taskId != TASK_SERIAL) {
+ maxLoadSum += maxLoad;
+ averageLoadSum += averageLoad;
+ }
+ if (systemConfig()->task_statistics) {
+ cliPrintLinef("%6d %7d %7d %4d.%1d%% %4d.%1d%% %9d",
+ taskFrequency, taskInfo.maxExecutionTime, taskInfo.averageExecutionTime,
+ maxLoad/10, maxLoad%10, averageLoad/10, averageLoad%10, taskInfo.totalExecutionTime / 1000);
+ } else {
+ cliPrintLinef("%6d", taskFrequency);
+ }
+ if (taskId == TASK_GYROPID && pidConfig()->pid_process_denom > 1) {
+ cliPrintLinef(" - (%15s) %6d", taskInfo.subTaskName, subTaskFrequency);
+ }
+ }
+ }
+ if (systemConfig()->task_statistics) {
+ cfCheckFuncInfo_t checkFuncInfo;
+ getCheckFuncInfo(&checkFuncInfo);
+ cliPrintLinef("RX Check Function %19d %7d %25d", checkFuncInfo.maxExecutionTime, checkFuncInfo.averageExecutionTime, checkFuncInfo.totalExecutionTime / 1000);
+ cliPrintLinef("Total (excluding SERIAL) %25d.%1d%% %4d.%1d%%", maxLoadSum/10, maxLoadSum%10, averageLoadSum/10, averageLoadSum%10);
+ }
+}
+#endif
+
+static void cliVersion(char *cmdline)
+{
+ UNUSED(cmdline);
+
+ cliPrintLinef("# %s / %s %s %s / %s (%s)",
+ FC_FIRMWARE_NAME,
+ targetName,
+ FC_VERSION_STRING,
+ buildDate,
+ buildTime,
+ shortGitRevision
+ );
+}
+
+#if defined(USE_RESOURCE_MGMT)
+
+#define MAX_RESOURCE_INDEX(x) ((x) == 0 ? 1 : (x))
+
+typedef struct {
+ const uint8_t owner;
+ pgn_t pgn;
+ uint16_t offset;
+ const uint8_t maxIndex;
+} cliResourceValue_t;
+
+const cliResourceValue_t resourceTable[] = {
+#ifdef BEEPER
+ { OWNER_BEEPER, PG_BEEPER_DEV_CONFIG, offsetof(beeperDevConfig_t, ioTag), 0 },
+#endif
+ { OWNER_MOTOR, PG_MOTOR_CONFIG, offsetof(motorConfig_t, dev.ioTags[0]), MAX_SUPPORTED_MOTORS },
+#ifdef USE_SERVOS
+ { OWNER_SERVO, PG_SERVO_CONFIG, offsetof(servoConfig_t, dev.ioTags[0]), MAX_SUPPORTED_SERVOS },
+#endif
+#if defined(USE_PWM) || defined(USE_PPM)
+ { OWNER_PPMINPUT, PG_PPM_CONFIG, offsetof(ppmConfig_t, ioTag), 0 },
+ { OWNER_PWMINPUT, PG_PWM_CONFIG, offsetof(pwmConfig_t, ioTags[0]), PWM_INPUT_PORT_COUNT },
+#endif
+#ifdef SONAR
+ { OWNER_SONAR_TRIGGER, PG_SONAR_CONFIG, offsetof(sonarConfig_t, triggerTag), 0 },
+ { OWNER_SONAR_ECHO, PG_SONAR_CONFIG, offsetof(sonarConfig_t, echoTag), 0 },
+#endif
+#ifdef LED_STRIP
+ { OWNER_LED_STRIP, PG_LED_STRIP_CONFIG, offsetof(ledStripConfig_t, ioTag), 0 },
+#endif
+ { OWNER_SERIAL_TX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagTx[0]), SERIAL_PORT_MAX_INDEX },
+ { OWNER_SERIAL_RX, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagRx[0]), SERIAL_PORT_MAX_INDEX },
+#ifdef USE_INVERTER
+ { OWNER_INVERTER, PG_SERIAL_PIN_CONFIG, offsetof(serialPinConfig_t, ioTagInverter[0]), SERIAL_PORT_MAX_INDEX },
+#endif
+#ifdef USE_I2C
+ { OWNER_I2C_SCL, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagScl[0]), I2CDEV_COUNT },
+ { OWNER_I2C_SDA, PG_I2C_CONFIG, offsetof(i2cConfig_t, ioTagSda[0]), I2CDEV_COUNT },
+#endif
+ { OWNER_LED, PG_STATUS_LED_CONFIG, offsetof(statusLedConfig_t, ioTags[0]), STATUS_LED_NUMBER },
+<<<<<<< HEAD
+#ifdef TRANSPONDER
+ { OWNER_TRANSPONDER, PG_TRANSPONDER_CONFIG, offsetof(transponderConfig_t, ioTag), 0 },
+#endif
+=======
+>>>>>>> betaflight/master
+};
+
+static ioTag_t *getIoTag(const cliResourceValue_t value, uint8_t index)
+{
+ const pgRegistry_t* rec = pgFind(value.pgn);
+ return CONST_CAST(ioTag_t *, rec->address + value.offset + index);
+}
+
+static void printResource(uint8_t dumpMask)
+{
+ for (unsigned int i = 0; i < ARRAYLEN(resourceTable); i++) {
+ const char* owner = ownerNames[resourceTable[i].owner];
+ const pgRegistry_t* pg = pgFind(resourceTable[i].pgn);
+ const void *currentConfig;
+ const void *defaultConfig;
+ if (configIsInCopy) {
+ currentConfig = pg->copy;
+ defaultConfig = pg->address;
+ } else {
+ currentConfig = pg->address;
+ defaultConfig = NULL;
+ }
+
+ for (int index = 0; index < MAX_RESOURCE_INDEX(resourceTable[i].maxIndex); index++) {
+ const ioTag_t ioTag = *((const ioTag_t *)currentConfig + resourceTable[i].offset + index);
+ const ioTag_t ioTagDefault = *((const ioTag_t *)defaultConfig + resourceTable[i].offset + index);
+
+ bool equalsDefault = ioTag == ioTagDefault;
+ const char *format = "resource %s %d %c%02d";
+ const char *formatUnassigned = "resource %s %d NONE";
+ if (!ioTagDefault) {
+ cliDefaultPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
+ } else {
+ cliDefaultPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTagDefault) + 'A', IO_GPIOPinIdxByTag(ioTagDefault));
+ }
+ if (!ioTag) {
+ if (!(dumpMask & HIDE_UNUSED)) {
+ cliDumpPrintLinef(dumpMask, equalsDefault, formatUnassigned, owner, RESOURCE_INDEX(index));
+ }
+ } else {
+ cliDumpPrintLinef(dumpMask, equalsDefault, format, owner, RESOURCE_INDEX(index), IO_GPIOPortIdxByTag(ioTag) + 'A', IO_GPIOPinIdxByTag(ioTag));
+ }
+ }
+ }
+}
+
+static void printResourceOwner(uint8_t owner, uint8_t index)
+{
+ cliPrintf("%s", ownerNames[resourceTable[owner].owner]);
+
+ if (resourceTable[owner].maxIndex > 0) {
+ cliPrintf(" %d", RESOURCE_INDEX(index));
+ }
+}
+
+static void resourceCheck(uint8_t resourceIndex, uint8_t index, ioTag_t newTag)
+{
+ if (!newTag) {
+ return;
+ }
+
+ const char * format = "\r\nNOTE: %c%02d already assigned to ";
+ for (int r = 0; r < (int)ARRAYLEN(resourceTable); r++) {
+ for (int i = 0; i < MAX_RESOURCE_INDEX(resourceTable[r].maxIndex); i++) {
+ ioTag_t *tag = getIoTag(resourceTable[r], i);
+ if (*tag == newTag) {
+ bool cleared = false;
+ if (r == resourceIndex) {
+ if (i == index) {
+ continue;
+ }
+ *tag = IO_TAG_NONE;
+ cleared = true;
+ }
+
+ cliPrintf(format, DEFIO_TAG_GPIOID(newTag) + 'A', DEFIO_TAG_PIN(newTag));
+
+ printResourceOwner(r, i);
+
+ if (cleared) {
+ cliPrintf(". ");
+ printResourceOwner(r, i);
+ cliPrintf(" disabled");
+ }
+
+ cliPrintLine(".");
+ }
+ }
+ }
+}
+
+static void cliResource(char *cmdline)
+{
+ int len = strlen(cmdline);
+
+ if (len == 0) {
+ printResource(DUMP_MASTER | HIDE_UNUSED);
+
+ return;
+ } else if (strncasecmp(cmdline, "list", len) == 0) {
+#ifdef MINIMAL_CLI
+ cliPrintLine("IO");
+#else
+ cliPrintLine("Currently active IO resource assignments:\r\n(reboot to update)");
+ cliRepeat('-', 20);
+#endif
+ for (int i = 0; i < DEFIO_IO_USED_COUNT; i++) {
+ const char* owner;
+ owner = ownerNames[ioRecs[i].owner];
+
+ cliPrintf("%c%02d: %s", IO_GPIOPortIdx(ioRecs + i) + 'A', IO_GPIOPinIdx(ioRecs + i), owner);
+ if (ioRecs[i].index > 0) {
+ cliPrintf(" %d", ioRecs[i].index);
+ }
+ cliPrintLinefeed();
+ }
+
+ cliPrintLinefeed();
+
+#ifdef MINIMAL_CLI
+ cliPrintLine("DMA:");
+#else
+ cliPrintLine("Currently active DMA:");
+ cliRepeat('-', 20);
+#endif
+ for (int i = 0; i < DMA_MAX_DESCRIPTORS; i++) {
+ const char* owner;
+ owner = ownerNames[dmaGetOwner(i)];
+
+ cliPrintf(DMA_OUTPUT_STRING, i / DMA_MOD_VALUE + 1, (i % DMA_MOD_VALUE) + DMA_MOD_OFFSET);
+ uint8_t resourceIndex = dmaGetResourceIndex(i);
+ if (resourceIndex > 0) {
+ cliPrintLinef(" %s %d", owner, resourceIndex);
+ } else {
+ cliPrintLinef(" %s", owner);
+ }
+ }
+
+#ifndef MINIMAL_CLI
+ cliPrintLine("\r\nUse: 'resource' to see how to change resources.");
+#endif
+
+ return;
+ }
+
+ uint8_t resourceIndex = 0;
+ int index = 0;
+ char *pch = NULL;
+ char *saveptr;
+
+ pch = strtok_r(cmdline, " ", &saveptr);
+ for (resourceIndex = 0; ; resourceIndex++) {
+ if (resourceIndex >= ARRAYLEN(resourceTable)) {
+ cliPrintLine("Invalid");
+ return;
+ }
+
+ if (strncasecmp(pch, ownerNames[resourceTable[resourceIndex].owner], len) == 0) {
+ break;
+ }
+ }
+
+ pch = strtok_r(NULL, " ", &saveptr);
+ index = atoi(pch);
+
+ if (resourceTable[resourceIndex].maxIndex > 0 || index > 0) {
+ if (index <= 0 || index > MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex)) {
+ cliShowArgumentRangeError("index", 1, MAX_RESOURCE_INDEX(resourceTable[resourceIndex].maxIndex));
+ return;
+ }
+ index -= 1;
+
+ pch = strtok_r(NULL, " ", &saveptr);
+ }
+
+ ioTag_t *tag = getIoTag(resourceTable[resourceIndex], index);
+
+ uint8_t pin = 0;
+ if (strlen(pch) > 0) {
+ if (strcasecmp(pch, "NONE") == 0) {
+ *tag = IO_TAG_NONE;
+#ifdef MINIMAL_CLI
+ cliPrintLine("Freed");
+#else
+ cliPrintLine("Resource is freed");
+#endif
+ return;
+ } else {
+ uint8_t port = (*pch) - 'A';
+ if (port >= 8) {
+ port = (*pch) - 'a';
+ }
+
+ if (port < 8) {
+ pch++;
+ pin = atoi(pch);
+ if (pin < 16) {
+ ioRec_t *rec = IO_Rec(IOGetByTag(DEFIO_TAG_MAKE(port, pin)));
+ if (rec) {
+ resourceCheck(resourceIndex, index, DEFIO_TAG_MAKE(port, pin));
+#ifdef MINIMAL_CLI
+ cliPrintLinef(" %c%02d set", port + 'A', pin);
+#else
+ cliPrintLinef("\r\nResource is set to %c%02d", port + 'A', pin);
+#endif
+ *tag = DEFIO_TAG_MAKE(port, pin);
+ } else {
+ cliShowParseError();
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ cliShowParseError();
+}
+#endif /* USE_RESOURCE_MGMT */
+
+static void backupConfigs(void)
+{
+ // make copies of configs to do differencing
+ PG_FOREACH(pg) {
+ memcpy(pg->copy, pg->address, pg->size);
+ }
+
+ configIsInCopy = true;
+}
+
+static void restoreConfigs(void)
+{
+ PG_FOREACH(pg) {
+ memcpy(pg->address, pg->copy, pg->size);
+ }
+
+ configIsInCopy = false;
+}
+
+static void printConfig(char *cmdline, bool doDiff)
+{
+ uint8_t dumpMask = DUMP_MASTER;
+ char *options;
+ if ((options = checkCommand(cmdline, "master"))) {
+ dumpMask = DUMP_MASTER; // only
+ } else if ((options = checkCommand(cmdline, "profile"))) {
+ dumpMask = DUMP_PROFILE; // only
+ } else if ((options = checkCommand(cmdline, "rates"))) {
+ dumpMask = DUMP_RATES; // only
+ } else if ((options = checkCommand(cmdline, "all"))) {
+ dumpMask = DUMP_ALL; // all profiles and rates
+ } else {
+ options = cmdline;
+ }
+
+ if (doDiff) {
+ dumpMask = dumpMask | DO_DIFF;
+ }
+
+ backupConfigs();
+ // reset all configs to defaults to do differencing
+ resetConfigs();
+
+#if defined(TARGET_CONFIG)
+ targetConfiguration();
+#endif
+ if (checkCommand(options, "defaults")) {
+ dumpMask = dumpMask | SHOW_DEFAULTS; // add default values as comments for changed values
+ }
+
+ if ((dumpMask & DUMP_MASTER) || (dumpMask & DUMP_ALL)) {
+ cliPrintHashLine("version");
+ cliVersion(NULL);
+
+ if ((dumpMask & (DUMP_ALL | DO_DIFF)) == (DUMP_ALL | DO_DIFF)) {
+ cliPrintHashLine("reset configuration to default settings");
+ cliPrint("defaults");
+ cliPrintLinefeed();
+ }
+
+ cliPrintHashLine("name");
+ printName(dumpMask, &systemConfig_Copy);
+
+#ifdef USE_RESOURCE_MGMT
+ cliPrintHashLine("resources");
+ printResource(dumpMask);
+#endif
+
+#ifndef USE_QUAD_MIXER_ONLY
+ cliPrintHashLine("mixer");
+ const bool equalsDefault = mixerConfig_Copy.mixerMode == mixerConfig()->mixerMode;
+ const char *formatMixer = "mixer %s";
+ cliDefaultPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig()->mixerMode - 1]);
+ cliDumpPrintLinef(dumpMask, equalsDefault, formatMixer, mixerNames[mixerConfig_Copy.mixerMode - 1]);
+
+ cliDumpPrintLinef(dumpMask, customMotorMixer(0)->throttle == 0.0f, "\r\nmmix reset\r\n");
+
+ printMotorMix(dumpMask, customMotorMixer_CopyArray, customMotorMixer(0));
+
+#ifdef USE_SERVOS
+ cliPrintHashLine("servo");
+ printServo(dumpMask, servoParams_CopyArray, servoParams(0));
+
+ cliPrintHashLine("servo mix");
+ // print custom servo mixer if exists
+ cliDumpPrintLinef(dumpMask, customServoMixers(0)->rate == 0, "smix reset\r\n");
+ printServoMix(dumpMask, customServoMixers_CopyArray, customServoMixers(0));
+#endif
+#endif
+
+ cliPrintHashLine("feature");
+ printFeature(dumpMask, &featureConfig_Copy, featureConfig());
+
+#ifdef BEEPER
+ cliPrintHashLine("beeper");
+ printBeeper(dumpMask, &beeperConfig_Copy, beeperConfig());
+#endif
+
+ cliPrintHashLine("map");
+ printMap(dumpMask, &rxConfig_Copy, rxConfig());
+
+ cliPrintHashLine("serial");
+ printSerial(dumpMask, &serialConfig_Copy, serialConfig());
+
+#ifdef LED_STRIP
+ cliPrintHashLine("led");
+ printLed(dumpMask, ledStripConfig_Copy.ledConfigs, ledStripConfig()->ledConfigs);
+
+ cliPrintHashLine("color");
+ printColor(dumpMask, ledStripConfig_Copy.colors, ledStripConfig()->colors);
+
+ cliPrintHashLine("mode_color");
+ printModeColor(dumpMask, &ledStripConfig_Copy, ledStripConfig());
+#endif
+
+ cliPrintHashLine("aux");
+ printAux(dumpMask, modeActivationConditions_CopyArray, modeActivationConditions(0));
+
+ cliPrintHashLine("adjrange");
+ printAdjustmentRange(dumpMask, adjustmentRanges_CopyArray, adjustmentRanges(0));
+
+ cliPrintHashLine("rxrange");
+ printRxRange(dumpMask, rxChannelRangeConfigs_CopyArray, rxChannelRangeConfigs(0));
+
+#ifdef VTX_CONTROL
+ cliPrintHashLine("vtx");
+ printVtx(dumpMask, &vtxConfig_Copy, vtxConfig());
+#endif
+
+ cliPrintHashLine("rxfail");
+ printRxFailsafe(dumpMask, rxFailsafeChannelConfigs_CopyArray, rxFailsafeChannelConfigs(0));
+
+ cliPrintHashLine("master");
+ dumpAllValues(MASTER_VALUE, dumpMask);
+
+ if (dumpMask & DUMP_ALL) {
+ const uint8_t pidProfileIndexSave = systemConfig_Copy.pidProfileIndex;
+ for (uint32_t pidProfileIndex = 0; pidProfileIndex < MAX_PROFILE_COUNT; pidProfileIndex++) {
+ cliDumpPidProfile(pidProfileIndex, dumpMask);
+ }
+ changePidProfile(pidProfileIndexSave);
+ cliPrintHashLine("restore original profile selection");
+ cliProfile("");
+
+ const uint8_t controlRateProfileIndexSave = systemConfig_Copy.activeRateProfile;
+ for (uint32_t rateIndex = 0; rateIndex < CONTROL_RATE_PROFILE_COUNT; rateIndex++) {
+ cliDumpRateProfile(rateIndex, dumpMask);
+ }
+ changeControlRateProfile(controlRateProfileIndexSave);
+ cliPrintHashLine("restore original rateprofile selection");
+ cliRateProfile("");
+
+ cliPrintHashLine("save configuration");
+ cliPrint("save");
+ } else {
+ cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
+
+ cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
+ }
+ }
+
+ if (dumpMask & DUMP_PROFILE) {
+ cliDumpPidProfile(systemConfig_Copy.pidProfileIndex, dumpMask);
+ }
+
+ if (dumpMask & DUMP_RATES) {
+ cliDumpRateProfile(systemConfig_Copy.activeRateProfile, dumpMask);
+ }
+ // restore configs from copies
+ restoreConfigs();
+}
+
+static void cliDump(char *cmdline)
+{
+ printConfig(cmdline, false);
+}
+
+static void cliDiff(char *cmdline)
+{
+ printConfig(cmdline, true);
+}
+
+typedef struct {
+ const char *name;
+#ifndef MINIMAL_CLI
+ const char *description;
+ const char *args;
+#endif
+ void (*func)(char *cmdline);
+} clicmd_t;
+
+#ifndef MINIMAL_CLI
+#define CLI_COMMAND_DEF(name, description, args, method) \
+{ \
+ name , \
+ description , \
+ args , \
+ method \
+}
+#else
+#define CLI_COMMAND_DEF(name, description, args, method) \
+{ \
+ name, \
+ method \
+}
+#endif
+
+static void cliHelp(char *cmdline);
+
+// should be sorted a..z for bsearch()
+const clicmd_t cmdTable[] = {
+ CLI_COMMAND_DEF("adjrange", "configure adjustment ranges", NULL, cliAdjustmentRange),
+ CLI_COMMAND_DEF("aux", "configure modes", NULL, cliAux),
+#ifdef BEEPER
+ CLI_COMMAND_DEF("beeper", "turn on/off beeper", "list\r\n"
+ "\t<+|->[name]", cliBeeper),
+#endif
+#ifdef LED_STRIP
+ CLI_COMMAND_DEF("color", "configure colors", NULL, cliColor),
+#endif
+ CLI_COMMAND_DEF("defaults", "reset to defaults and reboot", NULL, cliDefaults),
+ CLI_COMMAND_DEF("bl", "reboot into bootloader", NULL, cliBootloader),
+ CLI_COMMAND_DEF("diff", "list configuration changes from default",
+ "[master|profile|rates|all] {showdefaults}", cliDiff),
+#ifdef USE_DSHOT
+ CLI_COMMAND_DEF("dshotprog", "program DShot ESC(s)", " +", cliDshotProg),
+#endif
+ CLI_COMMAND_DEF("dump", "dump configuration",
+ "[master|profile|rates|all] {showdefaults}", cliDump),
+#ifdef USE_ESCSERIAL
+ CLI_COMMAND_DEF("escprog", "passthrough esc to serial", " ", cliEscPassthrough),
+#endif
+ CLI_COMMAND_DEF("exit", NULL, NULL, cliExit),
+ CLI_COMMAND_DEF("feature", "configure features",
+ "list\r\n"
+ "\t<+|->[name]", cliFeature),
+#ifdef USE_FLASHFS
+ CLI_COMMAND_DEF("flash_erase", "erase flash chip", NULL, cliFlashErase),
+ CLI_COMMAND_DEF("flash_info", "show flash chip info", NULL, cliFlashInfo),
+#ifdef USE_FLASH_TOOLS
+ CLI_COMMAND_DEF("flash_read", NULL, " ", cliFlashRead),
+ CLI_COMMAND_DEF("flash_write", NULL, " ", cliFlashWrite),
+#endif
+#endif
+ CLI_COMMAND_DEF("get", "get variable value", "[name]", cliGet),
+#ifdef GPS
+ CLI_COMMAND_DEF("gpspassthrough", "passthrough gps to serial", NULL, cliGpsPassthrough),
+#endif
+ CLI_COMMAND_DEF("help", NULL, NULL, cliHelp),
+#ifdef LED_STRIP
+ CLI_COMMAND_DEF("led", "configure leds", NULL, cliLed),
+#endif
+ CLI_COMMAND_DEF("map", "configure rc channel order", "[