Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/Battery.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ set vbat_warning_cell_voltage = 340
set vbat_min_cell_voltage = 330
```

## BEC voltage

The BEC supplies the servo rail, which on these boards also powers the flight controller. When it sags under a servo load, the board can reset or the servos glitch, so it is worth watching apart from the pack.

The NEXUS, the NEXUS X/XR, the Vantac RF007 and the FlyDragon Pro measure the servo rail on their own ADC, on PC1, as Rotorflight does for the same boards. On the FlyDragon Pro that is the servo plug bank; PC2, the output of its built-in regulator, is not read. `bec_adc_channel` selects the channel, PC1's by default, and `vbec_scale` calibrates it as `vbat_scale` does the pack.

`status` in the CLI shows it. The OSD element "BEC voltage" shows it, and blinks below `vbec_warning_voltage` (0.01 V units, 0 for no warning).

# Current Monitoring

Current monitoring (amperage) is supported by connecting a current meter to the appropriate current meter ADC input (see the documentation for your particular board).
Expand Down
30 changes: 30 additions & 0 deletions docs/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,16 @@ If the remaining battery capacity goes below this threshold the beeper will emit

---

### bec_adc_channel

ADC channel measuring the BEC voltage, on the servo rail the BEC supplies. Defaults to the board's input for it. 0 = none

| Default | Min | Max |
| --- | --- | --- |
| _target default_ | ADC_CHN_NONE | ADC_CHN_MAX |

---

### beeper_pwm_mode

Allows disabling PWM mode for beeper on some targets. Switch from ON to OFF if the external beeper sound is weak. Do not switch from OFF to ON without checking if the board supports PWM beeper mode
Expand Down Expand Up @@ -7472,6 +7482,26 @@ Warning voltage per cell, this triggers battery-warning alarms, in 0.01V units,

---

### vbec_scale

BEC voltage calibration value, as vbat_scale: 1100 = 11:1 voltage divider (10k:1k) x 100. Adjust it if the BEC voltage shown by "status" in the CLI differs from a multimeter's reading

| Default | Min | Max |
| --- | --- | --- |
| _target default_ | 0 | 5000 |

---

### vbec_warning_voltage

Below this BEC voltage the OSD's BEC element blinks [0.01V]. 0 = no warning

| Default | Min | Max |
| --- | --- | --- |
| 0 | 0 | 1500 |

---

### vtol_autotransition_always

When enabled, an armed manual VTOL profile switch between a multicopter-like profile and a fixed-wing profile starts the auto-transition controller instead of switching profiles directly. This allows a two-position desired-mode switch: selecting FW starts MC->FW, selecting MC starts FW->MC. Disarmed direct profile switching remains available for bench and preflight checks. OFF preserves direct manual profile switching when `MIXER TRANSITION` is not active. Available only on targets with more than 512 KB flash.
Expand Down
3 changes: 2 additions & 1 deletion docs/development/msp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3215,8 +3215,9 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
| `remainingCapacity` | `uint32_t` | 4 | Capacity unit (`batteryMetersConfig()->capacity_unit`) | Estimated remaining capacity (`getBatteryRemainingCapacity()`) |
| `percentageRemaining` | `uint8_t` | 1 | % | Estimated remaining capacity percentage (`calculateBatteryPercentage()`) |
| `rssi` | `uint16_t` | 2 | Raw (0-1023) | RSSI value (`getRSSI()`) |
| `becVoltage` | `uint16_t` | 2 | 0.01V | BEC voltage (`becGetVoltage()`) on boards with a BEC input; 0xFFFF on the others, and with `bec_adc_channel` = 0 |

**Notes:** Requires `USE_CURRENT_METER`/`USE_ADC` for current-related fields; values fall back to zero when unavailable. Capacity fields are reported in the units configured by `batteryMetersConfig()->capacity_unit` (mAh or mWh).
**Notes:** Requires `USE_CURRENT_METER`/`USE_ADC` for current-related fields; values fall back to zero when unavailable. Capacity fields are reported in the units configured by `batteryMetersConfig()->capacity_unit` (mAh or mWh). Older firmware ends after `rssi`.

## <a id="msp2_inav_misc"></a>`MSP2_INAV_MISC (8195 / 0x2003)`
**Description:** Retrieves miscellaneous configuration settings, superseding `MSP_MISC` with higher precision and capacity fields.
Expand Down
8 changes: 7 additions & 1 deletion docs/development/msp/msp_messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -6995,10 +6995,16 @@
"ctype": "uint16_t",
"desc": "RSSI value (`getRSSI()`)",
"units": "Raw (0-1023)"
},
{
"name": "becVoltage",
"ctype": "uint16_t",
"desc": "BEC voltage (`becGetVoltage()`) on boards with a BEC input; 0xFFFF on the others, and with `bec_adc_channel` = 0",
"units": "0.01V"
}
]
},
"notes": "Requires `USE_CURRENT_METER`/`USE_ADC` for current-related fields; values fall back to zero when unavailable. Capacity fields are reported in the units configured by `batteryMetersConfig()->capacity_unit` (mAh or mWh).",
"notes": "Requires `USE_CURRENT_METER`/`USE_ADC` for current-related fields; values fall back to zero when unavailable. Capacity fields are reported in the units configured by `batteryMetersConfig()->capacity_unit` (mAh or mWh). Older firmware ends after `rssi`.",
"description": "Provides detailed analog sensor readings, superseding `MSP_ANALOG` with higher precision and additional fields."
},
"MSP2_INAV_MISC": {
Expand Down
2 changes: 2 additions & 0 deletions src/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,8 @@ main_sources(COMMON_SRC
sensors/battery_config_structs.h
sensors/battery_sensor_dronecan.c
sensors/battery_sensor_dronecan.h
sensors/bec.c
sensors/bec.h
sensors/boardalignment.c
sensors/boardalignment.h
sensors/compass.c
Expand Down
3 changes: 2 additions & 1 deletion src/main/config/parameter_group_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@
#define PG_DRONECAN_CONFIG 1045
#define PG_TERRAIN_NAV_CONFIG 1046
#define PG_DRONECAN_DNA_SERVER 1047 // Separate PG so we don't wipe user settings if the allocation table changes
#define PG_INAV_END PG_DRONECAN_DNA_SERVER
#define PG_BEC_CONFIG 1049
#define PG_INAV_END PG_BEC_CONFIG

// OSD configuration (subject to change)
//#define PG_OSD_FONT_CONFIG 2047
Expand Down
8 changes: 8 additions & 0 deletions src/main/drivers/adc.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@

#pragma once

// ADC_FUNCTION_COUNT depends on the target's USE_BEC_VOLTAGE
#include "platform.h"

#include "drivers/io_types.h"

typedef enum {
ADC_BATTERY = 0,
ADC_RSSI = 1,
ADC_CURRENT = 2,
ADC_AIRSPEED = 3,
#ifdef USE_BEC_VOLTAGE
ADC_BEC = 4,
#endif
ADC_FUNCTION_COUNT
} adcFunction_e;

#define ADCVREF 3300 // mV

typedef enum {
ADC_CHN_NONE = 0,
ADC_CHN_1 = 1,
Expand Down
14 changes: 13 additions & 1 deletion src/main/fc/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ bool cliMode = false;
#include "sensors/acceleration.h"
#include "sensors/barometer.h"
#include "sensors/battery.h"
#include "sensors/bec.h"
#include "sensors/boardalignment.h"
#include "sensors/compass.h"
#include "sensors/diagnostics.h"
Expand Down Expand Up @@ -4155,6 +4156,11 @@ static void cliStatus(char *cmdline)
dateTimeFormatLocal(buf, &dt);
cliPrintLinef("Current Time: %s", buf);
cliPrintLinef("Voltage: %d.%02dV (%dS battery - %s)", getBatteryVoltage() / 100, getBatteryVoltage() % 100, getBatteryCellCount(), getBatteryStateString());
#ifdef USE_BEC_VOLTAGE
if (becIsConfigured()) {
cliPrintLinef("BEC: %d.%02dV", becGetVoltage() / 100, becGetVoltage() % 100);
}
#endif
cliPrintf("CPU Clock=%dMHz", (SystemCoreClock / 1000000));

const uint32_t detectedSensorsMask = sensorsMask();
Expand Down Expand Up @@ -4251,7 +4257,13 @@ static void cliStatus(char *cmdline)
cliPrintLinef("I2C Errors: %d, config size: %d, max available config: %d", i2cErrorCounter, getEEPROMConfigSize(), &__config_end - &__config_start);
#endif
#if defined(USE_ADC) && !defined(SITL_BUILD)
static char * adcFunctions[] = { "BATTERY", "RSSI", "CURRENT", "AIRSPEED" };
static char * adcFunctions[] = {
"BATTERY", "RSSI", "CURRENT", "AIRSPEED",
#ifdef USE_BEC_VOLTAGE
"BEC",
#endif
};
STATIC_ASSERT(ARRAYLEN(adcFunctions) == ADC_FUNCTION_COUNT, adc_function_names);
cliPrintLine("ADC channel usage:");
for (int i = 0; i < ADC_FUNCTION_COUNT; i++) {
cliPrintf(" %8s :", adcFunctions[i]);
Expand Down
8 changes: 8 additions & 0 deletions src/main/fc/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,22 @@ PG_RESET_TEMPLATE(beeperConfig_t, beeperConfig,
.pwmMode = SETTING_BEEPER_PWM_MODE_DEFAULT,
);

#ifdef USE_BEC_VOLTAGE
// The BEC input grows the group: a board gaining it starts again from the target's channels
PG_REGISTER_WITH_RESET_TEMPLATE(adcChannelConfig_t, adcChannelConfig, PG_ADC_CHANNEL_CONFIG, 1);
#else
PG_REGISTER_WITH_RESET_TEMPLATE(adcChannelConfig_t, adcChannelConfig, PG_ADC_CHANNEL_CONFIG, 0);
#endif

PG_RESET_TEMPLATE(adcChannelConfig_t, adcChannelConfig,
.adcFunctionChannel = {
[ADC_BATTERY] = VBAT_ADC_CHANNEL,
[ADC_RSSI] = RSSI_ADC_CHANNEL,
[ADC_CURRENT] = CURRENT_METER_ADC_CHANNEL,
[ADC_AIRSPEED] = AIRSPEED_ADC_CHANNEL,
#ifdef USE_BEC_VOLTAGE
[ADC_BEC] = BEC_ADC_CHANNEL,
#endif
}
);

Expand Down
4 changes: 4 additions & 0 deletions src/main/fc/fc_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,10 @@ void init(void)
}
#endif

#ifdef USE_BEC_VOLTAGE
adc_params.adcFunctionChannel[ADC_BEC] = adcChannelConfig()->adcFunctionChannel[ADC_BEC];
#endif

adcInit(&adc_params);
#endif

Expand Down
6 changes: 6 additions & 0 deletions src/main/fc/fc_msp.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
#include "sensors/sensors.h"
#include "sensors/diagnostics.h"
#include "sensors/battery.h"
#include "sensors/bec.h"
#include "sensors/rangefinder.h"
#include "sensors/acceleration.h"
#include "sensors/barometer.h"
Expand Down Expand Up @@ -812,6 +813,11 @@ static bool mspFcProcessOutCommand(uint16_t cmdMSP, sbuf_t *dst, mspPostProcessF
sbufWriteU32(dst, getBatteryRemainingCapacity());
sbufWriteU8(dst, calculateBatteryPercentage());
sbufWriteU16(dst, getRSSI());
#ifdef USE_BEC_VOLTAGE
sbufWriteU16(dst, becIsConfigured() ? becGetVoltage() : 0xFFFF);
#else
sbufWriteU16(dst, 0xFFFF);
#endif
break;

case MSP2_INAV_GET_LINK_STATS:
Expand Down
11 changes: 10 additions & 1 deletion src/main/fc/fc_tasks.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
#include "sensors/temperature.h"
#include "sensors/barometer.h"
#include "sensors/battery.h"
#include "sensors/bec.h"
#include "sensors/compass.h"
#include "sensors/gyro.h"
#include "sensors/irlock.h"
Expand Down Expand Up @@ -155,6 +156,10 @@ void taskUpdateBattery(timeUs_t currentTimeUs)
}
#endif

#ifdef USE_BEC_VOLTAGE
becUpdate(BatMonitoringTimeSinceLastServiced);
#endif

batMonitoringLastServiced = currentTimeUs;
}

Expand Down Expand Up @@ -398,7 +403,11 @@ void fcTasksInit(void)
#ifdef USE_LIGHTS
setTaskEnabled(TASK_LIGHTS, true);
#endif
setTaskEnabled(TASK_BATTERY, feature(FEATURE_VBAT) || isAmperageConfigured());
bool batteryTask = feature(FEATURE_VBAT) || isAmperageConfigured();
#ifdef USE_BEC_VOLTAGE
batteryTask = batteryTask || becIsConfigured();
#endif
setTaskEnabled(TASK_BATTERY, batteryTask);
setTaskEnabled(TASK_TEMPERATURE, true);
setTaskEnabled(TASK_RX, true);
#ifdef USE_GPS
Expand Down
25 changes: 25 additions & 0 deletions src/main/fc/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,31 @@ groups:
field: adcFunctionChannel[ADC_AIRSPEED]
min: ADC_CHN_NONE
max: ADC_CHN_MAX
- name: bec_adc_channel
description: "ADC channel measuring the BEC voltage, on the servo rail the BEC supplies. Defaults to the board's input for it. 0 = none"
default_value: :target
field: adcFunctionChannel[ADC_BEC]
condition: USE_BEC_VOLTAGE
min: ADC_CHN_NONE
max: ADC_CHN_MAX

- name: PG_BEC_CONFIG
type: becConfig_t
headers: ["sensors/bec.h"]
condition: USE_BEC_VOLTAGE
members:
- name: vbec_scale
description: "BEC voltage calibration value, as vbat_scale: 1100 = 11:1 voltage divider (10k:1k) x 100. Adjust it if the BEC voltage shown by \"status\" in the CLI differs from a multimeter's reading"
default_value: :target
field: scale
min: 0
max: 5000
- name: vbec_warning_voltage
description: "Below this BEC voltage the OSD's BEC element blinks [0.01V]. 0 = no warning"
default_value: 0
field: warningVoltage
min: 0
max: 1500

- name: PG_ACCELEROMETER_CONFIG
type: accelerometerConfig_t
Expand Down
19 changes: 18 additions & 1 deletion src/main/io/osd.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@

#include "sensors/acceleration.h"
#include "sensors/battery.h"
#include "sensors/bec.h"
#include "sensors/boardalignment.h"
#include "sensors/compass.h"
#include "sensors/diagnostics.h"
Expand Down Expand Up @@ -238,7 +239,7 @@ static bool osdDisplayHasCanvas;
#define AH_MAX_PITCH_DEFAULT 20 // Specify default maximum AHI pitch value displayed (degrees)

PG_REGISTER_WITH_RESET_TEMPLATE(osdConfig_t, osdConfig, PG_OSD_CONFIG, 0);
PG_REGISTER_WITH_RESET_FN(osdLayoutsConfig_t, osdLayoutsConfig, PG_OSD_LAYOUTS_CONFIG, 4);
PG_REGISTER_WITH_RESET_FN(osdLayoutsConfig_t, osdLayoutsConfig, PG_OSD_LAYOUTS_CONFIG, 5);

/* OSD formatting helpers replacing common tfp_sprintf patterns
* for reduced code size and CPU overhead. */
Expand Down Expand Up @@ -1864,6 +1865,22 @@ static bool osdDrawSingleElement(uint8_t item)
2 + osdConfig()->main_voltage_decimals, osdConfig()->main_voltage_decimals);
return true;

#ifdef USE_BEC_VOLTAGE
case OSD_BEC_VOLTAGE:
strcpy(buff, "BEC");
if (!becIsConfigured()) {
strcpy(buff + 3, " ---");
} else {
osdFormatCentiNumber(buff + 3, becGetVoltage(), 0, 2, 0, 4, false);
if (becIsVoltageLow()) {
TEXT_ATTRIBUTES_ADD_BLINK(elemAttr);
}
}
buff[7] = SYM_VOLT;
buff[8] = '\0';
break;
#endif

case OSD_CURRENT_DRAW: {
osdFormatCentiNumber(buff, getAmperage(), 0, 2, 0, 3, false);
buff[3] = SYM_AMP;
Expand Down
1 change: 1 addition & 0 deletions src/main/io/osd.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ typedef enum {
OSD_GPS_EXTRA_STATS,
OSD_AUTO_SPEED, // 170
OSD_TERRAIN_AGL, // 171,
OSD_BEC_VOLTAGE = 173, // 172 is the MZTC camera's (#11837), which the Configurator already has

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Camera layouts waste osd refresh cycles 🐞 Bug ➹ Performance

OSD_BEC_VOLTAGE = 173 makes slot 172 a valid layout index, but osdIncElementIndex() counts a
visible slot 172 while osdDrawSingleElement() has no renderer for it. If a user enables the
Configurator’s existing camera item at 172, the firmware accepts and schedules an element it can
never display.
Agent Prompt
## Issue description
OSD slot 172 is now addressable but has no renderer, so enabling it adds an undrawable item to refresh scheduling.
## Fix Focus Areas
- src/main/io/osd.h[381-385]
- src/main/io/osd.c[4145-4158]
- src/main/io/osd.c[4265-4276]
## Recommended Fix
Keep 172 reserved for the planned camera element, but skip it when iterating and counting drawable items until its renderer exists; reject attempts to configure unsupported slots if appropriate.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this firmware only a manual osd_layout can enable 172: the Configurator offers the MZTC element only when a port has the MZTC_CAMERA function, which this firmware does not have (#11837 adds it, together with the element's renderer). Even then, osdDrawNextElement() skips an element that draws nothing within the same cycle, so the cost is one count in activeElements.

Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
OSD_ITEM_COUNT // MUST BE LAST
} osd_items_e;

Expand Down
2 changes: 0 additions & 2 deletions src/main/sensors/battery.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ static ina226Dev_t ina226Dev;
static bool ina226Detected = false;
#endif

#define ADCVREF 3300 // in mV (3300 = 3.3V)

#define VBATT_CELL_FULL_MAX_DIFF 10 // Max difference with cell max voltage for the battery to be considered full (10mV steps)
#define VBATT_PRESENT_THRESHOLD 220 // Minimum voltage to consider battery present
#define VBATT_STABLE_DELAY 40 // Delay after connecting battery to begin monitoring
Expand Down
Loading
Loading