Перейти к содержимому

Аналого-цифровой преобразователь: напряжение и ток аккумуляторной батареи … ↩

Аналого-цифровой преобразователь: напряжение и ток аккумуляторной батареи mod. Четверг, 15 октября 2026 22:55 . См. 2

BeeFC · Урок S9 из 33 · Уровень «Старт»

Как микроконтроллер измеряет напряжение аккумуляторной батареи через делитель, как определить число элементов и состояние разряда, почему напряжение снижается под нагрузкой и как сгладить измерения цифровым фильтром первого порядка.

Фильтр первого порядка, написанный в этом уроке, ещё не раз понадобится в BeeFC: та же функция в уроке P3 будет фильтровать показания гироскопа.

Содержание

1. Цели урока

Продолжительность урока — четыре часа: 1,5 часа теории и 2,5 часа практики. После урока слушатель:

  • настраивает аналого-цифровой преобразователь и выбирает время выборки;
  • пересчитывает код преобразователя в напряжение батареи и калибрует делитель по мультиметру;
  • определяет число элементов батареи и знает ограничения такого определения;
  • реализует фильтр первого порядка и проверяет его переходную характеристику тестом;
  • реализует предупреждение о разряде с гистерезисом.
↑ К оглавлению

2. Термины и сокращения

ТерминОпределение
аналого-цифровой преобразователь (АЦП)блок, преобразующий напряжение на входе в число
разрядность АЦПчисло битов результата; у STM32F405 — 12 бит, коды 0–4095
опорное напряжениенапряжение, которому соответствует наибольший код АЦП
делитель напряжениядва резистора, уменьшающие напряжение батареи до допустимого для входа АЦП
время выборкивремя, за которое внутренний конденсатор АЦП заряжается до напряжения входа
фильтр нижних частот первого порядка (PT1)фильтр, который пропускает медленные изменения сигнала и ослабляет быстрые
частота срезачастота, на которой фильтр ослабляет сигнал примерно до 70,7 %
гистерезисразница между порогом перехода в состояние и порогом выхода из него
↑ К оглавлению

3. Аналого-цифровой преобразователь

В STM32F405 три 12-битных АЦП. Каналы 10–15 подключены к выводам PC0–PC5, поэтому на полётных контроллерах делитель напряжения батареи почти всегда подключён к одному из этих выводов. На плате Diatone Mamba F405 это вывод PC1, канал 11.

\( U_{вх} = \frac{N}{4095} \cdot U_{оп} \)

Опорное напряжение на полётных контроллерах равно напряжению питания аналоговой части, то есть 3,3 В. Один код соответствует примерно 0,8 мВ на входе АЦП.

Время выборки

Во время выборки внутренний конденсатор АЦП заряжается через сопротивление источника. Делитель напряжения батареи имеет сопротивление порядка килоом, и при коротком времени выборки конденсатор не успевает зарядиться: показания получаются заниженными. Для делителя выбирается наибольшее время выборки — 480 тактов. При частоте АЦП 21 МГц выборка вместе с преобразованием занимает около 23 мкс, что несущественно для задачи, которая выполняется 50 раз в секунду.

Файл hal/stm32f4/adc_stm32.c

void adc_init(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    (void)RCC->APB2ENR;
    GPIOC->MODER |= 3u << (1u * 2u);                 /* PC1: аналоговый режим */

    ADC->CCR = ADC_CCR_ADCPRE_0;                     /* 84 МГц / 4 = 21 МГц (не выше 36 МГц) */
    ADC1->SMPR1 = 7u << ADC_SMPR1_SMP11_Pos;         /* 480 тактов выборки: делитель высокоомный */
    ADC1->SQR3 = 11u;                                /* первым в очереди — канал 11 */
    ADC1->CR2 = ADC_CR2_ADON;
}

uint16_t adc_read_once(void)
{
    ADC1->CR2 |= ADC_CR2_SWSTART;
    while (!(ADC1->SR & ADC_SR_EOC)) { }
    return (uint16_t)ADC1->DR;                       /* чтение DR сбрасывает EOC */
}
Plain text
↑ К оглавлению

4. Делитель напряжения

Напряжение полностью заряженной батареи из шести элементов — 25,2 В, а на вход АЦП можно подавать не больше 3,3 В. Делитель из резисторов 10 кОм и 1 кОм уменьшает напряжение в одиннадцать раз: 25,2 В превращаются в 2,29 В. В Betaflight коэффициент делителя по умолчанию задан параметром vbat_scale со значением 110, что соответствует такому делителю.

Файл core/battery.h

typedef enum { BAT_OK, BAT_WARNING, BAT_CRITICAL } bat_state_t;

typedef struct {
    uint16_t warn_cell_mv;      /* порог предупреждения на элемент */
    uint16_t crit_cell_mv;      /* критический порог на элемент */
    uint16_t hysteresis_mv;     /* гистерезис возврата в лучшее состояние */
    uint8_t  cells;             /* число элементов, определяется при включении */
    bat_state_t state;
} battery_t;

uint32_t    adc_to_mv(uint16_t raw, uint32_t vref_mv, uint32_t divider_x100);
uint8_t     battery_detect_cells(uint32_t pack_mv);
bat_state_t battery_update(battery_t *b, uint32_t pack_mv);
Plain text

Файл core/battery.c

uint32_t adc_to_mv(uint16_t raw, uint32_t vref_mv, uint32_t divider_x100)
{
    /* 12-битный АЦП: полная шкала 4095 соответствует опорному напряжению. */
    uint64_t pin_mv = ((uint64_t)raw * vref_mv) / 4095u;
    return (uint32_t)((pin_mv * divider_x100) / 100u);
}

uint8_t battery_detect_cells(uint32_t pack_mv)
{
    /* Заряженный литий-полимерный элемент — не выше 4,35 В. */
    return (uint8_t)((pack_mv + 4349u) / 4350u);
}

bat_state_t battery_update(battery_t *b, uint32_t pack_mv)
{
    uint32_t cell = pack_mv / b->cells;
    uint32_t h = b->hysteresis_mv;

    switch (b->state) {
    case BAT_OK:
        if (cell <= b->warn_cell_mv)              b->state = BAT_WARNING;
        break;
    case BAT_WARNING:
        if (cell <= b->crit_cell_mv)            b->state = BAT_CRITICAL;
        else if (cell >= b->warn_cell_mv + h)   b->state = BAT_OK;
        break;
    case BAT_CRITICAL:
        if (cell >= b->crit_cell_mv + h)        b->state = BAT_WARNING;
        break;
    }
    return b->state;
}
Plain text

Точность резисторов делителя обычно составляет 1 %, поэтому коэффициент калибруется: напряжение батареи измеряется мультиметром и сравнивается с показанием BeeFC. Новое значение коэффициента равно старому, умноженному на отношение показания мультиметра к показанию BeeFC.

Делитель напряжения из резисторов 10 кОм и 1 кОм для батареи из шести элементов и датчик тока с шунтом и усилителем.
Делитель напряжения из резисторов 10 кОм и 1 кОм для батареи из шести элементов и датчик тока с шунтом и усилителем.

Определение числа элементов

Напряжение заряженного литий-полимерного элемента не превышает 4,35 В. Число элементов оценивается делением напряжения батареи на 4,35 В с округлением вверх. Для заряженной батареи из четырёх элементов 16,8 В дают ровно четыре элемента.

Ограничение автоматического определения: Сильно разряженная батарея из четырёх элементов с напряжением 13,0 В будет определена как батарея из трёх элементов, и предупреждение о разряде не сработает. Поэтому число элементов определяется один раз при подключении батареи, а для работы с батареями неизвестного заряда предусматривается параметр, задающий число элементов явно. В Betaflight это параметр force_battery_cell_count.
↑ К оглавлению

5. Датчик тока

Датчик тока на полётном контроллере или регуляторе состоит из резистора малого сопротивления и усилителя. Напряжение на выходе усилителя пропорционально току: \( I = (U - U_0) \cdot k \), где \( k \) — коэффициент в амперах на вольт, \( U_0 \) — напряжение при нулевом токе. Израсходованная ёмкость получается суммированием тока по времени.

\( Q_{мА·ч} = \sum I_{мА} \cdot \frac{\Delta t_{с}}{3600} \)

ПрошивкаКоэффициент напряженияКоэффициент тока
Betaflightvbat_scaleibata_scale, ibata_offset
ArduPilotBATT_VOLT_MULTBATT_AMP_PERVLT, BATT_AMP_OFFSET
PX4BAT1_V_DIVBAT1_A_PER_V
↑ К оглавлению

6. Фильтр первого порядка

Показания АЦП содержат шум от регуляторов и двигателей. Фильтр первого порядка на каждом шаге приближает выход к входу на долю \( k \).

\( y_n = y_{n-1} + k\,(x_n - y_{n-1}), \qquad k = \frac{\omega\,\Delta t}{\omega\,\Delta t + 1}, \qquad \omega = 2\pi f_c \)

Здесь \( \Delta t \) — период вызова фильтра, \( f_c \) — частота среза. Постоянная времени фильтра \( \tau = 1/(2\pi f_c) \): за это время выход проходит 63,2 % пути к новому значению входа. Для частоты среза 10 Гц постоянная времени составляет около 15,9 мс.

Файл core/pt1.h

typedef struct {
    float k;      /* коэффициент усиления фильтра, 0..1 */
    float state;  /* текущее отфильтрованное значение */
} pt1_t;

float pt1_gain(float cutoff_hz, float dt_s);
void  pt1_init(pt1_t *f, float cutoff_hz, float dt_s, float initial);
float pt1_apply(pt1_t *f, float input);
Plain text

Файл core/pt1.c

#define BEE_PI 3.14159265f

float pt1_gain(float cutoff_hz, float dt_s)
{
    float omega_dt = 2.0f * BEE_PI * cutoff_hz * dt_s;   /* суффикс f: одинарная точность */
    return omega_dt / (omega_dt + 1.0f);
}

void pt1_init(pt1_t *f, float cutoff_hz, float dt_s, float initial)
{
    f->k = pt1_gain(cutoff_hz, dt_s);
    f->state = initial;
}

float pt1_apply(pt1_t *f, float input)
{
    f->state += f->k * (input - f->state);
    return f->state;
}
Plain text

Файл tests/unit/test_level1.c

void test_pt1_reaches_63_percent_after_time_constant(void)
{
    const float fc = 10.0f, dt = 0.001f;
    const float tau = 1.0f / (2.0f * 3.14159265f * fc);     /* около 15,9 мс */
    pt1_t f;
    pt1_init(&f, fc, dt, 0.0f);
    int steps = (int)(tau / dt + 0.5f);
    for (int i = 0; i != steps; i++) pt1_apply(&f, 1.0f);
    TEST_ASSERT_FLOAT_WITHIN(0.02f, 0.632f, f.state);
}
Plain text
Выбор частоты среза: Чем ниже частота среза, тем лучше подавляется шум и тем больше запаздывание. Для показаний напряжения батареи запаздывание в доли секунды допустимо, поэтому частота среза выбирается около 1 Гц. Для гироскопа в уроке P3 частота среза будет в сотни раз выше, потому что запаздывание в цикле управления ухудшает устойчивость.
↑ К оглавлению

7. Падение напряжения под нагрузкой

У батареи есть внутреннее сопротивление, и под нагрузкой её напряжение падает: \( U = E - I \cdot R \). Например, при внутреннем сопротивлении батареи 0,08 Ом и токе 30 А напряжение снижается на 2,4 В. Эти числа — учебный ориентир, а не данные конкретной батареи. После снятия нагрузки напряжение восстанавливается.

Если сравнивать с порогом мгновенное напряжение, предупреждение будет срабатывать при каждом резком увеличении газа. Поэтому решение о разряде принимается по отфильтрованному напряжению и с гистерезисом: состояние «предупреждение» сменяется на «норму», только когда напряжение элемента превысит порог на величину гистерезиса.

СостояниеПереход внизПереход вверх
норманапряжение элемента не выше 3,50 В—
предупреждениенапряжение элемента не выше 3,30 Внапряжение элемента не ниже 3,60 В
критический разряд—напряжение элемента не ниже 3,40 В

Пороги совпадают со значениями по умолчанию параметров vbat_warning_cell_voltage и vbat_min_cell_voltage в Betaflight, гистерезис BeeFC — 0,1 В на элемент.

↑ К оглавлению

8. Практика

Задание S9.1. Напряжение и состояние батареи

Продолжительность — 40 минут, выполняется на компьютере.

Реализовать функции adc_to_mv, battery_detect_cells и battery_update.

Критерий автоматической проверки: тест test_battery пройден; скрытые тесты проверяют пересчёт без переполнения при наибольшем коде и коэффициенте, батареи из одного–шести элементов и многократное колебание напряжения около порога.

Задание S9.2. Фильтр первого порядка

Продолжительность — 30 минут, выполняется на компьютере.

Реализовать модуль core/pt1.

Критерий автоматической проверки: тест test_pt1_reaches_63_percent_after_time_constant пройден; скрытый тест проверяет ослабление синусоиды на частоте среза до 0,707 ± 0,03.

Задание S9.3. Измерение на плате и калибровка

Продолжительность — 50 минут, требуется мультиметр.

Добавить задачу батареи с частотой 50 Гц и вывод напряжения в команду консоли status. Откалибровать коэффициент делителя по мультиметру при трёх разных напряжениях лабораторного источника. Слушатели с пакетом Proteus могут дополнительно проверить модуль на модели STM32F401 с потенциометром вместо батареи.

Критерий: после калибровки расхождение с мультиметром не больше 1 % во всех трёх точках; таблица измерений приложена к отчёту docs/s9_report.md.

Задание S9.4. Ток и израсходованная ёмкость

Продолжительность — 30 минут, выполняется на компьютере.

Реализовать модуль core/current_meter, который по коду АЦП, коэффициенту и смещению вычисляет ток и суммирует израсходованную ёмкость в миллиампер-часах.

Критерий автоматической проверки: скрытый тест: постоянный ток 10 А в течение 360 секунд модельного времени даёт 1000 ± 1 мА·ч.

↑ К оглавлению

9. Как это сделано в больших прошивках

  • В Betaflight АЦП работает непрерывно и через DMA записывает результаты всех каналов в массив: файл adc_stm32f4xx.c. Модуль voltage.c фильтрует напряжение фильтрами первого порядка displayFilter и sagFilter, второй из них служит для компенсации падения напряжения.
  • Модуль battery.c Betaflight определяет число элементов как целую часть от деления напряжения на vbat_max_cell_voltage плюс единица, если число элементов не задано параметром force_battery_cell_count. Порог «батарея не подключена» задан параметром vbat_detect_cell_voltage.
  • В INAV модуль battery.c идёт дальше: он оценивает внутреннее сопротивление батареи (функция getPowerSupplyImpedance), оставшуюся ёмкость (getBatteryRemainingCapacity) и средний расход энергии на метр пути (calculateAverageEfficiency). Эти оценки нужны для решения о возврате самолёта или мультиротора.
  • В ArduPilot класс AP_BattMonitor_Analog читает напряжение и ток через AnalogIn.cpp; коэффициенты задаются параметрами BATT_VOLT_MULT и BATT_AMP_PERVLT.
  • В PX4 модуль analog_battery.cpp использует параметры BAT1_V_DIV и BAT1_A_PER_V; номер батареи в имени параметра позволяет подключить несколько батарей.
↑ К оглавлению

10. Проверьте себя

  1. Почему для входа делителя напряжения выбирается наибольшее время выборки АЦП?
  2. Какое напряжение попадёт на вход АЦП от заряженной батареи из шести элементов через делитель 1:11?
  3. Почему автоматическое определение числа элементов ошибается на разряженной батарее?
  4. Чему равна постоянная времени фильтра первого порядка с частотой среза 10 Гц и что она означает?
  5. Зачем предупреждению о разряде гистерезис?
↑ К оглавлению

11. Чек-лист самопроверки

Результат: Все пункты выполнены. Можно переходить к итоговой работе уровня, уроку S10.
↑ К оглавлению

12. Источники

  1. Справочное руководство RM0090, раздел 13, АЦП.
  2. Указания по применению AN2834: точность АЦП в микроконтроллерах STM32.
  3. Модуль батареи Betaflight.
  4. Документация ArduPilot.
  5. Документация PX4.
↑ К оглавлению

13. Что дальше

  • Следующий урок: S10. Итоговая работа уровня «Старт»: тяговый стенд, версия 1.
  • Полный перечень уроков приведён в статье «Курс BeeFC: содержание уровня „Старт“».

Проверено: 13.10.2026

↑ К оглавлению
  • мод. : Четверг, 15 октября 2026