Продолжение полёта

В прошлых статьях я рассказывал о портировании AM32 на отечественный микроконтроллер К1946ВК035 и о том, как принимать DShot, когда нужный нам модуль захвата сигнала не соединён с DMA. С шестишаговой коммутацией тогда разобрались, мотор закрутился. Но на этом, как вы уже понимаете, останавливаться было бы неинтересно.
На этот раз попробуем векторное управление, оно же FOC. Микроконтроллер оставим тот же, а за математикой отправимся в открытый проект https://github.com/vedderb/bldc Бенджамина Веддера.
Для тех, кому интереснее сразу посмотреть результат, — https://github.com/RRegulo/K1946VK035_VESC

Ну а для всех остальных — прошу под кат. Здесь будут три отдельных ШИМ-блока, немного математики и АЦП, который успел сообщить о готовности раньше, чем положил в буфер все результаты.
Что именно переносим
Сначала немного о разнице с прошлым проектом.
В AM32 мы использовали шестишаговую коммутацию. В каждый момент две фазы участвовали в управлении, а по свободной фазе можно было следить за обратной ЭДС и определять момент следующего переключения. Для этого я использовал внешние компараторы. Подробности остались в https://habr.com/ru/articles/938128/
Теперь нам нужно управлять током относительно положения ротора. Измеряем фазные токи, переводим их в систему координат, связанную с ротором, и получаем две составляющие: Id и Iq. В обычном режиме управления моментом для нашего мотора задаём Id = 0, а момент регулируем через Iq. На выходе токового регулятора получаем вектор напряжения, который ещё предстоит превратить в переключения транзисторов.
Вот здесь и пригодится VESC. Из него перенесены наблюдатель положения ротора, PLL, токовый регулятор и пространственно-векторная ШИМ — SVPWM. Последняя рассчитывает время включения фаз так, чтобы за период ШИМ получить нужный средний вектор напряжения.
Не всё здесь взято из VESC. Преобразования Clarke/Park и обратное Park, фильтр, PID и задатчик плавного изменения задания адаптированы из MotorControlDemo, разработанного ООО «НПФ Вектор» по заказу НИИЭТ. В моём порте эти блоки переведены на float и собраны в FOC_Math.c. Так что НПФ Вектор тоже спасибо: базовую математику для управления мотором писать с нуля не пришлось.
При этом мы не переносим на ВК035 всю прошивку VESC вместе с её операционной системой и всеми интерфейсами. Настройку периферии, запуск, хранение параметров и обмен с конфигуратором пришлось делать под нашу плату.
На чём будем запускать
В этот раз я использую плату BLDC на К1946ВК035 от TDMLab https://github.com/TDMLab/K1946VK035_BLDC_BOARD. Сразу отмечу: разработчик платы — TDMLab. Схему и материалы по ней смотрим в исходном репозитории, авторство железа себе не приписываем.
Сам микроконтроллер нам уже знаком по AM32: Cortex-M4F, аппаратный FPU одинарной точности, три блока ШИМ и АЦП с секвенсорами. В этой прошивке ядро работает на 100 МГц.
Четыре канала АЦП теперь используются для трёх фазных токов и напряжения питания. Прежде чем разрешать силовые выходы, снимаем нули токовых каналов: усредняем 256 измерений на закрытых ключах. Иначе небольшое смещение усилителя очень быстро превратится в ток, который существует только в представлении программы.
Три блока ШИМ вместо одного таймера
В прошлом проекте мы уже сталкивались с тем, что привычный по STM32 продвинутый таймер здесь заменён тремя отдельными ШИМ-модулями. Возможности есть, но настраивать их нужно согласованно.
Задаём счёт вверх-вниз, период 2000 тактов и синхронизацию между блоками. При частоте TBCLK 100 МГц получаем:
100 000 000 / (2 × 2000) = 25 000 Гц
Мёртвое время задаём отдельно. В текущей настройке это 40 тактов, то есть 0,4 мкс, на соответствующем фронте:
PWM0->TBPRD = PWM_TIM1_AUTORELOAD;
PWM1->TBPRD = PWM_TIM1_AUTORELOAD;
PWM2->TBPRD = PWM_TIM1_AUTORELOAD;
PWM0->DBRED = PWM_DEAD_TIME;
PWM0->DBFED = PWM_DEAD_TIME;Настройка комплементарных выходов и задержек повторяется для всех трёх фаз. Полный код находится в PWM_Control.c.

На осциллограмме смотрим на взаимное положение фронтов: один сигнал переключается раньше, чем включается комплементарный. Между ними должна оставаться пауза, чтобы ключи одного плеча не оказались открыты одновременно. В прошивке задано 0,4 мкс; для точного измерения этой паузы нужен увеличенный участок фронта и временные курсоры, а не линейка, приложенная к картинке. Сквозной ток, к сожалению, не интересуется тем, насколько аккуратно мы написали константу PWM_DEAD_TIME.
Наличие правильной частоты на осциллографе ещё не означает, что всё готово. Важно проверить соответствие каналов фазам, полярность выходов и то, что действительно означает рассчитанное заполнение для нашей настройки обработчика событий.
В этом проекте времена из SVPWM переводятся в CMPA с инверсией:
g_pwm.GammaA = g_pwm.k_pwm - tA;
g_pwm.GammaB = g_pwm.k_pwm - tB;
g_pwm.GammaC = g_pwm.k_pwm - tC;Если забыть про эту особенность, сама математика продолжит считать совершенно правильные числа. Только силовая часть будет получать уже другую команду. Транзисторы объяснительную записку о том, что мы имели в виду, читать не будут.

Здесь видны импульсы напряжения на фазах при вращении. Между повторяющимися фронтами около двух делений, то есть 40 мкс — наш период ШИМ 25 кГц. Уровень импульсов соответствует примерно напряжению питания стенда. Небольшое различие положения фронтов каналов связано с разным временем включения фаз.
Красивую гладкую синусоиду фазного напряжения на таком масштабе ждать не стоит: мост переключает напряжение импульсами, а рассчитанный вектор получается в среднем за период. Фазные токи после измерительных усилителей посмотрим отдельно ниже.
25 кГц ШИМ — это ещё не 25 кГц управления
Тут стоит сразу разделить две частоты, чтобы дальше не путаться.
ШИМ работает на 25 кГц, один период занимает 40 мкс. А обычный контур FOC в открытом проекте выполняется раз в четыре периода — на 6,25 кГц. На один расчёт у нас есть 160 мкс.
Запуск АЦП привязан к событию SOCA от PWM0:
PWM0->ETSEL_bit.SOCAEN = 1;
PWM0->ETSEL_bit.SOCASEL = ET_CTR_ZERO;
ADC->EMUX_bit.EM0 = ADC_EMUX_EM0_PWM012A;
PWM0->ETPS_bit.SOCAPRD = ET_3RD;В используемой настройке значение поля SOCAPRD равно 3, событие получается раз в четыре периода. Соответственно, шаг алгоритмов задаём так:
#define FOC_TS (1.0f / 6250.0f)
Это шаг интеграторов, наблюдателя и токового контура. Просто оставить здесь 25 кГц, а вызывать код в четыре раза реже нельзя: формулы станут описывать другое течение времени.
При проверке на плате частота вызовов получилась около 6,25 кГц. Считаем сами вызовы и сверяемся со счётчиком тактов DWT, а не делаем вывод только по тому, какую константу записали в заголовке.
Сейчас обычный контур работает на 6,25 кГц. Работа над повышением частоты управления до 25 кГц продолжается, но в текущей версии она ещё не завершена.
Токи, напряжения и наблюдатель должны договориться
В бессенсорном FOC отдельного датчика положения ротора у нас нет. Наблюдатель получает токи, напряжения и параметры мотора, по ним оценивает магнитное состояние и угол. PLL помогает отслеживать угол и оценивать скорость.
На словах всё выглядит вполне понятно. Но у измерительного усилителя есть своя полярность, у фаз — своё подключение, а у программы — свои представления о направлении вращения. Если эти трое не договорились, мотор очень быстро объяснит это механическим способом.
В параметрах прошивки есть флаг инверсии измеренного тока и отдельный флаг OBS_V_RAW. Последний определяет, нужно ли менять знак напряжения на входе наблюдателя вслед за током. Важно не только хранить эти настройки, но и применять их в соответствующем расчёте.
Применение параметра выглядит так:
g_foc_obs_vraw = (g_params.s.flags & FOC_PARAM_FLAG_OBS_V_RAW) ? 1u : 0u;А при передаче напряжения наблюдателю учитываются оба флага:
float v_inv = (g_foc_inv_i && !g_foc_obs_vraw) ? -1.0f : 1.0f;
float v_alpha = g_vesc_state.v_alpha * v_inv;
float v_beta = g_vesc_state.v_beta * v_inv;
Тут, думаю, понятно, почему мотор может дёргаться даже тогда, когда преобразования координат написаны без ошибок. Если ток и напряжение относятся к разным системам координат, наблюдатель будет добросовестно оценивать совсем не то, что происходит на валу.

Сигналы с выходов усилителей измерения фазных токов. Это напряжения измерительной цепи, соответствующие токам, а не напряжения на силовых фазах.
Здесь уже видны изменения огибающих трёх сигналов на фоне высокочастотной пульсации от ШИМ. По ним удобно сравнивать форму и взаимное положение фазных токов. Чтобы получить из показаний вольтметра амперы, нужно учитывать нулевое смещение и коэффициент усиления цепи. Само по себе число в вольтах на экране осциллографа током ещё не является.
Коэффициенты под мотор, а не под прошлый эксперимент
Коэффициенты токового регулятора должны соответствовать мотору. Если сопротивление, индуктивность и постоянную времени контура меняем, а Kp и Ki оставляем от предыдущего эксперимента, рассчитывать на такой регулятор было бы слишком оптимистично.
Для одного мотора это могло выглядеть приемлемо. При подключении другого, особенно с небольшой индуктивностью, результат уже гораздо менее приятный: резкий толчок, просадка питания и UV fault.
Коэффициенты рассчитываем так же, как в VESC. Сначала записываем cc_tc_us, затем применяем R/L мотора и пересчитываем регулятор:
float bw = 1.0f / (g_foc_cc_tc_us * 1e-6f);
g_vesc_cfg.foc_current_kp = p->L * bw;
g_vesc_cfg.foc_current_ki = p->R * bw;Здесь R — в омах, L — в генри, постоянная времени — в микросекундах. Для наблюдателя усиление тоже рассчитывается из параметров мотора, а не остаётся одной константой на все случаи.
В текущей версии мотор вращается, задание оборотов через конфигуратор работает. Но правильные формулы не отменяют необходимости проверить введённые параметры и ограничения тока под конкретную силовую часть.
А теперь измерим R и L
В прошивке есть отдельная процедура определения параметров двигателя. Для сопротивления задаём два уровня тока при фиксированном положении вектора и усредняем полученные токи и напряжения:
R = (Vd2 − Vd1) / (Id2 − Id1)
Два уровня нужны, чтобы вычесть общее смещение напряжения. Это полезно, когда падение на силовых ключах и влияние dead-time уже сравнимы с напряжением, которое подаём на обмотку.
Для измерения индуктивности используем переходный процесс. Подаём короткий вектор напряжения и смотрим, как растёт ток. Для простой RL-модели он стремится к установившемуся значению по экспоненте. Если взять три равноотстоящие точки, получим:
(i3 − i2) / (i2 − i1) = exp(−dt / tau)
L = R × tau
В коде расчёт достаточно короткий:
float dt = (float)n1 * DETECT_L_SAMPLE_DT;
float tau = -dt / logf(d2 / d1);
g_detect_l = s_l_rr * tau;Для этого на время импульса выполняется короткая серия из четырёх выборок АЦП с шагом 40 мкс. Усреднение отключается, а нужные точки сохраняются непосредственно в прерывании. По окончании импульса силовые выходы закрываются и восстанавливается обычный режим измерений с шагом 160 мкс.
При чтении FIFO нужно дождаться всех четырёх результатов. Прерывание секвенсора может прийти раньше последней записи, поэтому в обработчике предусмотрено ограниченное ожидание:
uint32_t guard = 2000u;
while(ADC->SEQ[0].SFLOAD_bit.VAL < 4u && guard != 0u)
guard--;Если полный пакет не появился, остаток FIFO очищается, а расчёт по этой выборке пропускается. Неполный буфер не должен превращаться в четыре вполне убедительных, но неправильных измерения.
Настройки во Flash
Про работу с Flash я уже рассказывал в статье про AM32: стираем страницу целиком, читаем и записываем данные группами по 8 байт.
Здесь блок параметров занимает 128 байт, содержит версию формата и CRC16. Страница настроек начинается по адресу 0xF000, а линкер ограничивает программу первыми 60 КиБ. Так при росте прошивки код не попадёт в область, которую мы потом будем стирать ради настроек.
На время стирания и записи соответствующие функции выполняются из RAM. Это нужно для самой операции с Flash. Контур FOC в RAM ради ускорения не переносится.
Когда запускается АЦП

Жёлтый сигнал повторяется примерно через 40 мкс, голубые импульсы — через 160 мкс. Между соседними маркерами укладываются четыре периода ШИМ: на этой осциллограмме хорошо видно различие между частотой силовых переключений 25 кГц и частотой обычного запуска измерений 6,25 кГц.
Что получилось
На К1946ВК035 работает бессенсорное управление на основе алгоритмов VESC: мотор запускается, обороты задаются через конфигуратор, R и L измеряются. Есть UART-телеметрия и сохранение настроек во Flash. Несущая ШИМ — 25 кГц, обычный контур FOC — 6,25 кГц.
Проект открытый. Исходники прошивки доступны, их можно изучать и дорабатывать на условиях GPLv3. Авторство VESC, MESC, TDMLab и остальных исходных компонентов сохранено. Конфигуратор — отдельный проект, его исходники в этот репозиторий не входят.
Прошивка и описание распространяются без гарантий. Ограничения ответственности действуют в пределах применимого законодательства и условий лицензий. Это экспериментальный стенд, а не сертифицированный регулятор с завершённым набором защит.
По итогу микроконтроллер с задачей справляется, но правильная математика начинается с правильных измерений. Остаётся настроить регулятор под конкретный мотор — для этого и нужен конфигуратор.
Как пользоваться конфигуратором
Теперь о том, как всё это запустить, не пересобирая прошивку под каждый мотор.
Открываем https://regulo.ru/foc-configurator/ в браузере с поддержкой Web Serial. Плата подключается к компьютеру через USB–UART; нажимаем «Подключить (UART)» и выбираем порт платы. Скорость обмена — 115200 бод. Режим «Демо без платы» нужен только для знакомства с интерфейсом: настоящим мотором он не управляет.
Перед измерениями закрепляем мотор, снимаем пропеллер и проверяем ограничение тока источника. При измерении потокосцепления ротор будет вращаться. Компьютер уже знает, что мы сейчас нажмём кнопку, а лежащей рядом отвёртке это пока никто не объяснил.
1. Сначала параметры и лимиты. На вкладке «Параметры» задаём число пар полюсов, допустимые напряжения и «I cmd — потолок команды» под свой мотор и питание. Число пар полюсов — именно пар: для ротора с 14 полюсами вводим 7. От этого зависит пересчёт электрических оборотов в механические. Заданный в интерфейсе порог тока не заменяет аппаратную защиту и ограничение источника.
2. Измеряем R. В состоянии STOP переходим на вкладку «Измерение», выбираем подходящий ток замера и нажимаем «Измерить R». После успешного завершения нажимаем «→ параметры» в строке R. В таблице результат показан в мОм.
3. Измеряем L. Нажимаем «Измерить L», дожидаемся завершения и переносим результат кнопкой «→ параметры» в строке L. Здесь единицы — мкГн. В этой прошивке токовый уровень импульса L подбирает сама процедура; поле тока замера используется для R и λ.
4. Измеряем λ. После применения R и L нажимаем «Измерить λ (мотор вращается)». Прошивка выполняет разгон в разомкнутом режиме и оценивает потокосцепление. После завершения нажимаем «→ параметры (и KV)». Значение λ показано в мкВб; конфигуратор также пересчитывает KV с учётом числа пар полюсов.
5. Сохраняем настройки. Кнопки переноса результатов применяют значения в RAM платы. Чтобы они сохранились после отключения питания, на вкладке «Параметры» нажимаем «Сохранить во flash», пока мотор остановлен. Число в таблице результатов само по себе ещё не означает, что оно уже стало параметром регулятора.
6. Теперь можно крутить. Переходим на вкладку «Управление», в блоке «Скорость» задаём небольшие обороты в поле «Обороты, об/мин (мех.)» и нажимаем «Задать». Смотрим на токи, напряжение и поведение мотора. Для остановки — красная кнопка STOP.
Если измерение завершилось ошибкой, результат не применяем и не заменяем случайным числом. Проверяем питание, подключение фаз и токовых каналов, выбранные ограничения. После смены двигателя его параметры измеряем заново.
Коротко последовательность такая: R → применить → L → применить → λ → применить → сохранить → задать обороты. Не наоборот. Мотор, конечно, может попробовать покрутиться и с параметрами от соседа, но хорошую работу ему тогда никто не обещал.
Полезные материалы и источники
Предыдущие работы
Реализации VESC и MESC FOC
VESC Firmware — Benjamin Vedder — в частности,
motor/mcpwm_foc.cиmotor/foc_math.c.MESC Firmware — David Molony — источник происхождения наблюдателя MXLEMMING. Специальное уведомление об авторстве сохранено в исходном коде.
Аппаратная платформа и документация
К1946ВК035. Руководство пользователя — НИИЭТ (PDF) — разделы, посвящённые PWM, АЦП и контроллеру Flash.
Математические блоки и лицензирование
MotorControlDemo — НПФ «Вектор» — исходный проект математических блоков.
Материалы курса K1921ВК01Т (PDF) — на слайде 3 указана лицензия Apache License 2.0.
Ссылки на проект
Исходный код: K1946VK035_VESC
Сайт проекта: regulo.ru
Telegram: Regulo R&D
Конфигуратор FOC: regulo.ru/foc-configurator
Конфигуратор позволяет подключаться к плате через USB–UART с параметрами 115200 бод.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.