Разработка buck-преобразователя на STM32F334: принцип работы, расчеты, макетирование

В двух своих последних статьях я рассказал о силовом модуле и плате управления на базе микроконтроллера STM32F334R8T6, которые созданы специально для реализации систем управления силовыми преобразователями и электроприводом. Так же был рассмотрен пример DC/AC преобразователя, который являлся демонстрацией, а не завершенной конструкцией. Теперь пришло время сделать что-то простое, но полезное, а главное завершенное.

Большинство вопросов, касающихся проекта и силовой электроники, связаны с конкретными топологиями: кому-то интересно узнать алгоритм управления PFC, кому-то хочется научиться строить LLC полумост, но наиболее популярная топология — это несомненно buck. Ведь buck-преобразователь (он же buck converter) является основной для большинства интересных проектов: это и драйвер для LED светильников, и основа MPPT контроллера для солнечных панелей, и зарядные устройства и вообще много чего еще.

В сети достаточно много информации по buck, в том числе и даташиты, но она разрозненна и мне лично не встречался материал, где подробно описан процесс создания buck-преобразователя с цифровым управлением. Пора это исправить. Математики практически нет, объяснения «на пальцах», поэтому будет интересно всем, кто хоть как-то связан с электроникой.



Введение
Для начала нужно понять, что мы хотим получить в итоге и какие вводные у нас есть. Топология buck является понижающей, то есть позволяет построить понижающий преобразователь напряжения. Как вы увидите далее напряжение на выходе buck-преобразователя практически линейно зависит от напряжения на входе, поэтому необходимо добавить обратную связь. Сегодня я расскажу о простой обратной связи по напряжению (voltage mode), которая является наиболее наглядной и позволит вам понять принцип работы, при этом такой обратной связи вам хватит для реализации большинства задач.

В конце статьи мы получим работающий стабилизированный источник напряжения по топологии «синхронный buck», работающий на достаточно высокой частоте с цифровым управлением, реализованным на STM32F334R8T6 с применение High Resolution PWM (HRPWM). Диапазон входного напряжения — 15…60В, выходное напряжение — 12В, максимальный выходной ток — 2А.

Глава 1. Принцип работы топологии buck
Начну я рассказывать начиная с самых основ и постепенно будем улучшать наш преобразователь, т.к. «синхронный buck» это версия улучшенный вариант с повышенными КПД и сложностью управления. Базовый вариант топологии, который вы наверняка использовали, выглядит следующим образом:

Данная топология используется в маломощных преобразователях напряжения, например, для питания цифровых схем и прочих маломощных приборов. Понижающие dc/dc, которые вы применяете в своих устройствах наверняка реализованы на микросхемах по данной топологии. Пример такой микросхемы LMR16006.

Принцип работы у данной схемы очень простой, на транзистор VT1 подается ШИМ сигнал, сама работа разделяется на 2 этапа, которые чередуются друг за другом:

  • Стадия накопления энергии в LC-контуре. На данном этапе транзистор VT1 открыт и ток протекает через транзистор в нагрузку, попутно накапливая энергию в катушке индуктивности и выходной емкости:

  • Стадия разряда. На данном этапе транзистор VT1 закрывается и тут начинается самое интересное. Дроссель — эта такая штука, которая накапливает энергию если к нему приложить потенциал (открыть VT1) и отдает ее, если потенциал пропал (VT1 закрыли). При этом он стремится не просто отдать энергию, а сохранить значение тока и его направление, поэтому чтобы использовать данное свойство нужно добавить диод VD1, чтобы замкнуть цепь, ведь ток протекает только в замкнутой цепи:

Когда классе в 6-7м я познакомился с данной топологией, то не понял сразу почему диод не проводит ток на 1-й стадии, сейчас это кажется банальным, но думаю стоит упомянуть. Когда VT1 открыт, то на катод диода VD1 прикладывается потенциал +VIN, например, +20В, а на аноде диода соответственно потенциал земли. Чтобы ток через диод протекал должно быть ровно наоборот: потенциал на аноде должен быть больше потенциала на катоде, поэтому в buck-е на стадии накопления энергии диод «закрыт». На стадии разряда диод уже замыкает цепь, на его катод не действует потенциал +VIN и не «запирает» его. Надеюсь понятно объяснил.

Тут у вас должен был возникнуть вопрос: «А какое напряжение будет на выходе, если мы подали на вход 20В?». Как всегда все просто:

Как видно из формулы напряжение на выходе линейно зависит от коэффициента заполнения (duty) ШИМ сигнала, который мы подаем на транзистор VT1. Если кто-то не знает или забыл «коэффициент заполнение (duty)» — это отношение времени, которое транзистор находится в открытом состояние к длительности периода. Данный коэффициент может принимать значение от 0 до 1 или от 0 до 100%. Дальше мы будет оперировать именно этой цифрой при управление преобразователем, но для понимая сути давайте подставим это отношение в формулу:

Частота работы buck-преобразователя величина постоянная и выбирается при проектирование, в процессе работы она не меняется, а значит и период (T) величина постоянная. Получается, что выходное напряжение напрямую зависит двух физических величин:

  • от времени на которое мы открывает верхний транзистор (VT1) — чем дольше он открыт, тем больше энергии успевает накопиться в LC-фильтре и соответственно выше напряжение на выходе;
  • от входного напряжения, например, если мы зафиксировали заполнение на 50% и меняем Vin от 20 до 40В, то на выходе напряжение будет так же меняться от 10 до 20В.

Я думаю у вас начала прорисовываться общая картина и принцип работы, давайте теперь ее закрепим и посмотрим на реальные осциллограммы и проверим данное соотношение на практике. У меня собран макет buck-а, который нагружен светодиодом на 10 Вт. Я задействовал 3 канала осциллографа, которые включены в следующие точки:

Опыт №1 — Входное напряжение (Vin) постоянное 20В, изменяется коэффициент заполнения

  • Vin = 20V, D = 25%, Vout = D * Vin = 0,25 * 20V = 5V

  • Vin = 20V, D = 50%, Vout = D * Vin = 0,5 * 20V = 10V

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

Опыт №2 — Входное напряжение (Vin) изменяется, коэффициент заполнения постоянный и равен 50%

  • Vin = 15V, D = 50%, Vout = D * Vin = 0.5 * 15V = 7.5V

  • Vin = 20V, D = 50%, Vout = D * Vin = 0.5 * 20V = 10V

  • Vin = 30V, D = 50%, Vout = D * Vin = 0.5 * 30V = 15V

Теперь мы на практике убедились, что выходное напряжение так же линейно зависит от входного при фиксированном коэффициенте заполнения. Поняли уже как будет стабилизировать выход? Принцип стабилизации простой как сама формула — Vout равно 12В и константа, коэффициент заполнения мы можем менять с помощью микроконтроллера, значит если Vin увеличивается, то увеличивается и напряжение выхода и в этот момент мы уменьшаем коэффициент заполнения (duty) пока опять не станет 12В. Соответственно при уменьшение Vin мы начинаем увеличивать коэффициент заполнения пока опять же напряжение Vout не станет 12В.

На что еще хотелось бы обратить внимание в теоретическом разделе… Ах, да! Наверняка вам интересно, как ШИМ с амплитудой 20В после транзистора в итоге превратился в постоянное напряжение с мизерными пульсациями? Действительно, если мы поставим красный щуп осциллографа в исток транзистора VT1, зеленый щуп после LC-фильтра, то увидим такую картину:

Вы можете видеть, как LC-фильтр «смазывает» напряжение переменное в постоянное, а дело все в том, что энергия запасенная в индуктивности и емкости не может мгновенно израсходоваться, следовательно и напряжение не может мгновенно измениться. Получаем, что в момент, когда ШИМ перед дросселем стал 0В напряжение на выходе обеспечивается запасенной в фильтре энергией, которая не рассасывается мгновенно и ее хватает чтобы поддерживать напряжение в момент закрытия VT1. Это все на пальцах разумеется, если интересно углубиться, то как всегда советую для начал книгу Б.Ю. Семенова «Силовая электроника: от простого к сложному», там целая глава по buck (чопперу).

Борьба за КПД
Как я чуть ранее писал — это был базовый вариант топологии. Основной ее минус — высокие потери на запирающем диоде. Какой ток в несложных системах работающих на МК и CPLD? Обычно в пределах 1А, иногда 2А, если имеется какой-то TFT дисплей. В таком случае потери даже при использовании диода Шоттки составят 0,4В * 2А = 0,8 Вт. В принципе терпимо, рассеивать столько на корпусе SMA/SMB можно без проблем, хотя при напряжении 3.3В и 2А потери 0.8В — это все таки 12% КПД!

Теперь представим себе случай, когда ток у нас 20А. Это может быть и MPPT контроллер, и система питания большой FPGA и много чего еще. В таком случае потери составят 0,4В * 20А = 8 Вт! Что это значит? Например, в случае MPPT у вам будет меньше запасаться энергии в АКБ, в случае питания FPGA это будут дополнительные 8 Вт тепла, которые надо куда-то рассеивать и в обоих случаях это несомненно потеря общего КПД. Что можно сделать? А давайте заменим диод VD1 на еще один N-канальный Mosfet и получим вот такую схему:

Теперь транзистор VT2 выполняет роль диода, то есть проводит ток, когда VT1 закрыт. Диод, который был в базовой версии не требовал управления, сейчас же за улучшение характеристик мы вынуждены заплатить дополнительным каналом управления с ШИМ сигналом.

Для начала давайте посчитаем насколько мы уменьшили потери. Сопротивление канала современного mosfet-а составляет несколько мОм. В качестве примера давайте возьмем транзистор из моего силового модуля о котором я рассказывал в прошлых статьях — IPP083N10N5AKSA1 с сопротивление канала 8.3 мОм. Получаем статические потери равные 0,0083 * 20А * 20А = 3,32 Вт. Еще разумеется будут динамические потери, которые с адекватно спроектированным драйвером составят не более 20%, то есть суммарные потери у нас составят 4 Вт. Получаем, что переход от обычного buck-а к синхронному позволяет вдвое уменьшить потери на диоде.

Давайте теперь разберем усложнившееся управление. Как мы уже поняли запирающий диод проводил ток когда был закрыт VT1. Из этого следует, что VT2 должен быть закрыт когда открыт VT1 и соответственно VT2 открыт когда закрыт VT1. Если проще — транзисторы работают попеременно: или один открыт иди другой, если оба транзистора откроются, то возникнет сквозной ток, т.к. они замкнут между собой VIN и GND. Давайте посмотрим какой должен быть сигнал, где «желтый канал» — транзистор VT1 и «зеленый канал» — транзистор VT2:

Как видите, если в желтой канале (на VT1) установлена логическая «1», то в это время обязательно должен быть установлен логический «0» в зеленом канале (на VT2). Получаем, что VT1 накачивает энергию в LC-фильтр, а VT2 замыкает контур на стадии разрядки.

Есть еще один момент о которым вы уже ранее слышали или читали выше — сквозной ток. Дело в том, что реальный, а не идеальный транзистор (mosfet) имеет на затворе определенную емкость, то есть в реальности он не мгновенно переходит из лог.0 в лог.1, да и энергия в транзисторе не рассасывается мгновенно, в результате чего транзисторы на короткое время в момент переключения могут оказаться оба открыты. Это может привести в лучшем случае к повышенным потерям, а значит нагреву и в худшем случае в бабаху, т.к. сквозной ток это обычное короткое замыкание (КЗ). Чтобы этого избежать между выключением одного транзистора и включением другого вводят задержку или так называемый dead-time. Выглядит это следующим образом:

Думаю вы заметили, что на границе переключения сигнала есть небольшой пробел. Я установил его заведомо большой (около 3%), чтобы вы могли его увидеть, в реальности он значительно меньше. Вообще dead-time (далее dt) устанавливается как можно короче, но при этом достаточный чтобы транзисторы успели закрыться. Его можно рассчитать, а можно подобрать опытным путем, лично я считаю и тот и тот вариант нормальным, но бородатые джедаи наверняка скажут вам: «Нужно считать обязательно, а лучше моделировать!». Это конечно правильно, но решайте сами — если не лень моделируйте в LTspice с учетом паразитных индуктивностей и емкостей проводников и компонентов.

Для стенда в данной статья я установил dt равный ~100 нс (на самом деле 104). Мой модуль позволяет установить его значительно меньше, т.к. драйвер очень суровый применен, но наверняка многие из вас будут собирать свой макет без моего модуля, а значит там с большой долей вероятностью будут сопли. Вот чтобы не бахнуло из-за соплей оставлю dt с запасом и если у вас нормальная разводка на плате, то вы сами можете его уменьшить — дальше в главе посвященной коду вы увидите как, а пока смотрим действительно ли есть dt:

Тут видно, что dt длится 2,5 деления и каждое деление 40 нс — значит длительность составляет ~100 нс как и было задумано. Надеюсь вы поняли зачем нужен dt, сколько он должен быть по длительности и как вообще работает преобразователь по топологии buck. Если не поняли, то как обычно вопросы в комментариях, лс и на почту принимаются, пока вроде отвечаю всем.

Глава 2. Расчет основных компонентов
В этом части статьи я покажу как быстро и просто рассчитать основные силовые компоненты для синхронного buck-преобразователя, а именно: дроссель, входной и выходной конденсаторы, транзисторы.

Напомню вводные данные:

  • Входное напряжение: 15…30В
  • Выходное напряжение: 12В
  • Номинальный выходной ток: 2А
  • Частота коммутации: 100 кГц

Выходное напряжение 12В выбрано, т.к. в качестве нагрузки планирую использовать светодиод 12В 20Вт, который оказался под рукой и является весьма наглядной нагрузкой. Предвидя вопросы «экспертов» в комментариях — да, я в курсе, что светодиоду нужна стабилизация тока, но мы то делаем стабилизатор напряжения и светодиод просто нагрузка.

Входное напряжение выбрано от балды, можно и 15…60В сделать, поэтому если вам интересен другой диапазон, то можете посчитать для него сами значение компонентов. Номинальный ток 2А выбран чтобы получить мощность на выходе 12В * 2А = 24 Вт, то есть чуть больше, чем нужно для светодиода. Сам светодиод при 12В потребляет около 1,82…1,9А.

Остался самый интересный параметр — рабочая частота преобразователя. Какая она должна быть? Ответить тут придется вам самим, в моем случае это 100 кГц. Выбор основывается на двух тезисах:

  • Увеличение частоты приводит к уменьшению необходимой индуктивности дросселя, входного и выходного конденсатора. Если говорить проще — с увеличением частоты уменьшаются габариты устройства. С уменьшением частоты габариты увеличиваются.
  • Уменьшение частоты приводит к увеличению КПД, т.к. динамические потери при переключение транзисторов уменьшаются. Увеличение частоты увеличивает динамическую составляющую транзисторов и соответственно уменьшает КПД.

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

Шаг 1. Выбор транзисторов

Нас в основном будут интересовать 3 параметра: максимальное напряжение «сток-исток», сопротивление канала в открытом состоянии и емкость затвора. К транзистору прикладывается весь потенциал источника напряжения (Vin), а так же присутствуют выбросы в момент переключения. У вас есть 2 варианта: взять транзистор VT1 и VT2 с запасом по напряжению или повесть RC-снаббер на VT2. В моем случае на силовом модуле стоят транзисторы на 100В, а при входном 30В это огромный запас по напряжению, даже 60В хватило чтобы обойтись без снабберов и обезопасить транзистор от пробоя.

Сопротивление канала — тут чем меньше, тем лучше, но есть одно НО. При уменьшение сопротивления канала мы уменьшаем статические потери (I2*R), но технология такова, что увеличивается емкость затвора, а это приводит к увеличению динамических потерь. Вам предстоит найти золотую середину между «сопротивление канала» и «емкость затвора». Для напряжение до 100В советую обратить внимание на транзисторы серии OptiMOS от Infineon, на большие напряжения уже смотрите сами по параметрическому поиску или даже в сторону IGBT-транзисторов. Последние так же поддерживаются моим силовым модулем и не требуют никаких изменений в драйвере.

Шаг 2. Расчет индуктивности дросселя

Необходимо посчитать минимальное значение индуктивности, которая позволит работать нашему dc/dc преобразователю в режиме неразрывных токов (Lmin):

По переменным я думаю понятно все, кроме — kind. Это допустимые пульсации тока в дросселе, обычно выбирают значение 20…50%, я же практически всегда задаю 30%. Чем меньше пульсация тока, тем дальше мы будем от границы насыщения сердечника на котором намотан дроссель, но как видно из формулы — понадобится большая индуктивность дросселя.

Теперь посчитаем минимальное значение индуктивности, которая понадобится для моих вводных данных, пульсации я заложу 30% как уже выше писал:

Стоит понимать, что это минимальная индуктивность, требующаяся для работы buck-преобразователя в режиме неразрывных токов, но опять есть нюанс. В процессе увеличения тока, действующего в обмотке, несколько уменьшается проницаемость сердечника и индуктивность дросселя БЕЗ тока и С током несколько отличается, у разных материалов это зависимость разная. Чтобы не возникла ситуация, когда при увеличение тока в дросселе индуктивность уменьшилась ниже Lmin и dc/dc не ушел в режим разрывного тока, необходимо несколько увеличить индуктивность, то есть добавить парочку лишних витков при намотке. Увеличения индуктивности на 10-15% будет достаточно для материала Kool Mu, а мой дроссель будет именно на нем.

Шаг 3. Расчет и изготовление дросселя

Я хотел данную процедуру описать в разделе «макетирование», но тогда этап расчета индуктивности остался бы для вас менее понятным, да и по интересным картинкам наверное соскучились, поэтому опишу все здесь. Для изготовления дросселя я возьму дроссель R26/14/11 (R — это кольцо, а цифры — габариты) из материала Kool Mu с проницаемостью 60, скачать документацию на него и купить можно тут — Лэпкос.

Теперь нужно посчитать сколько витков и каким проводом надо намотать. Начнем с количества витков пожалуй. В документации на сердечник есть такой удобный параметр — AL, который равен 75 нГн/виток2. Тут внимательно — витки в квадрате! Чтобы найти индуктивность сердечника необходимо умножить AL на количество витков в квадрате. Отсюда формула для нахождения количества витков выглядит так:

Чтобы получить минимальную необходимую индуктивность необходимо намотать 40 витков, но как мы уже обсудили — необходимо немного увеличить индуктивность, допустим накинем +3 витка. Берем кольцо и наматываем 43 витка, получаем вот такой дроссель:

Теперь ради интереса посчитаем какая индуктивность должна получиться:

И для надежности проверяем индуктивность дросселя пинцетом:

137 мкГн, прекрасно! Результаты сошлись, погрешность в пределах ±8% для AL. Тут стоит лишь заметить — если у вас нет возможности измерить индуктивность, то не покупайте сердечники на алиэкспресс, в ЧиДе, компэле, электронщике и прочих «забегаловках» — там есть вероятность получить сердечник из другого материала или не той проницаемостью, но при правильной маркировке — проверено. Без возможности измерить индуктивность вы не сможете проверить AL и сможете сильно намучиться в поисках причины «бабаха» вашего преобразователя.

Тут появится разумный вопрос — «а хватит ли нам сердечника и его габаритов? Может надо было больше?». Для материала Kool Mu предел магнитной индукции составляет 0.5 Тл, на практике лучше не вылезать за порог выше 0.45 Тл без явно необходимости. Получается, что обмотка намотанная на сердечник не должная создавать индукцию в каждой точке сердечника больше 0.45 Тл, так что проверим:

Как видим значение магнитной индукции 0,06 Тл сильно ниже, чем предельные 0,5 Тл. Из этого можно сделать 2 вывода: во-первых, дроссель не уйдет в насыщение, а во-вторых, сердечник сильно большой и мощно взять кольцо существенно меньшего размера. Я взял кольцо R26 просто потому, что у меня их целая коробка, никакого другого тайного смысла тут нет.

Осталось определить какое сечение провода взять для дросселя. Во-первых, провод с диаметром более 1…1,2 мм я вам брать настоятельно не советую при таких высоких частотах, т.к. скин-эффект уже оказывает существенное влияние и уменьшает эффективное сечение. Во-вторых, плотность тока в проводе нужно выбирать исходя из условий охлаждения и мощности. На малых мощностях (до 10-20 Вт) можно смело закладывать плотность тока 8..10 А/мм2 даже без обдува воздушным потоком. На мощностях до нескольких киловатт лучше закладывать плотность тока в интервале 5…6 А/мм2, а при мощностях от 10 кВт и далее разумно будет снизить плотность тока до 3…4 А/мм2.

У меня под рукой оказался лакированный провод с диаметром 0,8 мм. Его сечение соответственно составляет ~0,5 мм2. При токе в 2А получаем плотность тока в обмотке около 4 А/мм2. Я мог бы использовать провод и вдвое меньшего сечения, но у меня сердечник достаточно большой, поэтому провод большего сечения без проблем влез. Когда же вы оптимизируете свое устройство, то вам придется сначала считать, а потом закупать провод нужного сечения, тогда вы сможете получить оптимальные габариты дросселя.

Шаг 4. Расчет выходного конденсатора

На данном этапе, как и в случае с индуктивностью, мы будем считать минимальное значение емкости, которую необходимо установить в LC-фильтр на выходе buck-преобразователя. Соответственно если установите больше, то будет лучше и дальше увидите почему. Посчитаем емкость:

Разумеется емкость необходимо так же ставить с некоторым запасом, особенно если вы используете только керамику на выходе, т.к. ее емкость сильно уменьшается в зависимости от приложенного к ней напряжения. Еще стоит обратить на зависимость от пульсаций — переменная Vpulse. Это максимальное значение пульсаций на выходе, то есть в идеале при емкости 147,8 мкФ амплитуда пульсаций будет 0,2В, то есть напряжение на выходе будет плавать в диапазоне 11,9…12,1В. Хотите уменьшить пульсации? Тогда уменьшайте их в формуле и значение полученной емкости соответственно увеличится, разумеется лабораторный блок питания вы не получите просто увеличивая выходную емкость. Так же необходимо учесть необходимость низкого ESR, для этого обычно ставят 1-2 электролита параллельно и параллельно им навешивают керамику на несколько мкФ с диэлектриком X7R желательно. Если позволяет бюджет, то можно заменить электролитический конденсатор на полимерный тантал (как в GPU) и так керамика не нужна, ESR у них и так мизерный.

Рассуждения о рабочей частоте

Теперь, как и говорил, вернемся к вопросу выбора рабочей частоты преобразователя. Давайте я разделю выводы на несколько мыслей:

  • Как видите в формуле фигурирует частота, чем выше рабочая частота, тем меньшее значение индуктивности дросселя будет требоваться и меньшее количество витков придется мотать — экономим на меди и упрощаем изготовление моточных изделий
  • В формуле вычисления магнитной индукции присутствует индуктивность и количество витков, правда как помните индуктивность имеет квадратичную зависимость от витков, а значит при снижение количества витков в 2 раза индуктивность уменьшится в 4 раза. Из этого следует, что при повышение частоты уменьшается индуктивность и значение магнитной индукции, а значит можно применить сердечник меньшего размера, то есть уменьшаем габариты
  • Теперь посмотрим на формулу выходной емкости, тут тоже все очевидно — она линейно зависит от индуктивности, то есть чем меньше индуктивность, тем меньшая емкость нужна. Габариты уменьшаются!
  • Теперь о плохом… Частота растет, а значит увеличиваются динамические потери транзисторов и это очевидно ведет к снижению общего КПД. На сколько нибудь значительной мощности разумный предел частоты для buck-преобразователя на mosfet-ах составляет 200 кГц и ниже. Хотите большую мощность (сотни ватт) и огромную частоту? Добро пожаловать в мир GaN транзисторов или резонансных топологий

Думаю из этих тезисов вам стало понятно на что влияет частота коммутации, теперь вам необходимо самостоятельно научиться находить «золотую середину» между потерями на транзисторах и габаритом устройства. В одной из следующих статей я научу вас оптимизировать рабочую частоту по максимальному КПД, главное не забыть, что собирался это сделать.

Глава 3. Собираем макет buck-преобразователя
Так, самая нудная, но важная, часть закончилась, теперь пойдет железо и код. Давайте соберем макет на котором будем реализовывать теоретические выкладки. Для этого понадобятся два модуля про которые я рассказывал в предыдущих статьях: силовой модуль и модуль управления на STM32F334. Так же вы можете сами на макетной плате собрать полумост из любого подручного мусора типа IR2110, а в качестве управления применить любой МК: STM32-Discovery, LaunchPad, Arduino и просто адаптировать логику работы и код под свой любимый МК, сложного ничего не будет, если вы поняли из первых двух глав как работает buck-преобразователь.

Теперь давайте сделаем нашу структурную схему buck-а более «реалистичной», добавив на нее номиналы всех компонентов и верно отразим количество конденсаторов, а так же отметим какую часть может реализовать мой силовой модуль:

Как видно по схеме модуль уже содержит в себе полумост (два транзистора) для реализации синхронного buck-а и входной конденсатор, он кстати в модуле стоит с огромным запасом — там 3 электролита по 1000 мкФ и 100В, этого хватит чтобы спокойно собрать buck на 500-800 Вт. Нам остается добавить дроссель, который мы уже изготовили и выходные конденсаторы, последние тоже кстати с запасом, т.к. на низкое напряжение я нашел только 4700 мкФ 25В, но они китайские какие-то поэтому еще и решил парочку запараллелить. На самом деле там хватит и 470 мкФ одного, но у меня такой мелочи в выводном исполнение просто не оказалось. Получается вот такая конструкция:

Как ранее было сказано, в качестве нагрузки используется мощный светодиод на 20 Вт. На сколько он светит не знаю, да и не очень интересно, но потребляет он при 12В как раз 21…22 Вт на которые и рассчитан мой buck-преобразователь. Сам светодиод обмазал КПТ-8 и прикрутил к радиатору, его конечно мало, но на 5-7 минут работы его хватает без проблем (греется до +40…50 oC), а большего мне и не нужно. Подключаем от модуля управления 2 сигнала HRPWM, GND и через делитель цепляем выход buck-а на АЦП, в итоге имеем такой стенд:

Глава 4. Пишем ПО и запускаем преобразователь
Теперь у нас есть все необходимое, чтобы начать писать код и оживить наш buck-преобразователь. Для начала давайте посмотрим на pin-out для микроконтроллера STM32F334R8T6, который стоит в модуле управления:

Теперь нам понятно, какие будут использоваться выводы микроконтроллера. На самом модуле мне понадобится всего 1 из 5 каналов для управления силовой части, использовать будем канал «А». У данного канала, как в прочем и у остальных, есть 2 выхода высокоточного ШИМа (HRPWM), 1 вход ошибки (его не используем), GND для объединения земли плат и 2 канала АЦП (будем использовать только один для напряжения).

Немного о HRPWM

В просторах русскоязычного сегмента интернета я практически не встречал каких-то обучающих материалов по HRPWM и не встречал совсем материалов о работе с HRPWM на базе микроконтроллеров STM32, а ведь это очень полезная периферия.

Я не буду в рамках данной статьи углубляться в теорию данной периферии, поэтому опишу суть. HRPWM или High Resolution PWM — это привычный нам ШИМ модуль, который обладает повышенным разрешением установки коэффициента заполнения (duty) и в дополнение обычно обладает более гибкими настройками.

  • У микроконтроллера STM32F334R8T6 имеется 10 каналов HRPWM, которые объединены в 5 групп по 2 канала. Эти 2 канала внутри группы могут работать как независимо, так образовывать комплементарную пару — последняя нам и нужна;
  • Внутри комплементарной пары между 2-мя сигналами ШИМ можно устанавливать аппаратный dead-time для защиты от сквозного тока;
  • Все 10 каналов тактируются от одного таймера — Master timer, благодаря этому они все синхронизируются между собой и вам не придется вручную настраивать цепочку из таймеров. Достаточно включить мастер и таймеры Timer A…E за тактируются от него;
  • Частота на HRPWM идет удвоенная, то есть при частоте ядра в 72 МГц на HRPWM идет 144 МГц после дополнительного множителя (х2) с PLL. Это дает возможность управлять преобразователями на частоте в сотни кГц;
  • Множество настроек для управления ШИМом, например, по мимо возможности привязать геренерацию ШИМа к началу и концу периода, есть еще 4 настраиваемых события (comp), которые позволяю перевести ШИМ в 0 или 1 в любой момент периода отличный от начала/конец периода;
  • Есть режимы для конкретных топологий, например, режим push-pull, который позволяет реализовать множество двухтактный топологий.

И это лишь малая часть особенностей, на схеме устройства HRPWM вы видите еще возможности синхронизации с кучей событий, ЦАПом, встроенными в МК компараторами, а по мимо данной блок-схемы есть еще множество задокументированных возможностей.

Остался последний вопрос, который нужно осветить — «почему этот ШИМ высокоразрядный?». Для этого рассмотрим простой пример. Представим, что решили использовать МК без HRPWM, допустим STM32F103C8T6, который так же работает на частоте 72 МГц. Нам нужно управлять полумостом на частоте 70 кГц, считаем какой шаг регулирования мы можем получить: 72 000 000 / 1025 шагов = 70 243 Гц. Ага, у нас 1025 шагов и мы можем при регулировке менять напряжение на выходе с теоретическим шагом 1/1025 = ~0,1%. Теперь берем STM32F334, при частоте тактирования 144 МГц и разрядности сдвига таймера в 32 бита мы получаем эквивалентную частоту 144 МГц * 32 = 4,608 ГГц. Для тех, кто испугался и усомнился в цифре:

Нет, это не рабочая частота, это частота эквивалентная. Что нам это дает? Берем эквивалентную частоту 4 608 000 000 Гц / 70 300 Гц = 65 535 шагов. Теперь мы можем регулировать напряжение (или ток) на выходе с шагом 1 / 65 535 = ~0,001%, то есть в 100 раз точнее!

А теперь сделаем так — частота у нас 700 кГц, что является нормально для многофазного buck-а, например. У F103 получится 72 000 000 Гц / 700 000 Гц = 102 шага, что позволяет в лучшем случае получить регулирование 1%, но это 1% для duty, то есть в реальности с таким количество шагов у вас будет напряжение плавать на выходе как будто стабилизации и нет особо. Тогда как для F334 количество шагов будет примерно 6500, что все еще позволяет строить очень точный регулятор напряжения или тока. Получаем, что разрешение (шаг) установки скважности значительно выше/чаще, чем у обычного МК с стандартным ШИМ-модулем внутри.

Настройка системы тактирования

В качестве среды разработки в данной статье я использовал TrueSTUDIO, ибо бесплатная, не такая убогая как Keil или IAR да да, расскажите мне о его чудесном отладчике, кросс-платформенная и пожалуй лучшее решение для новичков и не только. В конце статьи будет архив с проектом именно для этой IDE. Как создавать и настраивать проект я рассказывать не буду, просто оставлю ссылку на видео, где все подробно показано — смотреть.

После того как создали проект и помигали светодиодом, необходимо настроить систему тактирования, а именно с 8 МГц поднять частоту то 72 МГц и подать на ядро, а потом настроить делитель чтобы уменьшить частоту, подаваемую на АЦП:

void StartInitClock (void) {

RCC->CR |= RCC_CR_HSEON; // Enable HSE
while (!(RCC->CR & RCC_CR_HSERDY));

FLASH->ACR |= FLASH_ACR_LATENCY_1;

RCC->CFGR |= RCC_CFGR_PLLMUL9; // PLL mult x9
RCC->CFGR |= RCC_CFGR_PLLSRC; // Source HSE
RCC->CFGR2 |= RCC_CFGR2_ADCPRE12_DIV10; // ADC source AHB/10

RCC->CR |= RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY) == 0){}

RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_PLL; // Select source SYSCLK = PLL
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_1) {} // Wait PLL

}

Тут все просто думаю, алгоритм настройки следующий: переходит на внешний кварц (HSE) -> ждем когда переход завершится и выставится флаг готовности -> подаем на вход PLL сигнал с кварца -> умножаем 8 МГц на 9 -> частоту 72 МГц делим на 10 для тактирования АЦП -> включаем PLL -> ждем пока он включится и выставит флаг готовности -> подаем на системную шину и ядро сигнал с PLL -> ждем пока переключение завершится -> готово.

Настройка HRPWM

Вот тут все несколько сложнее, т.к. функционала у данного модуля очень много, куча всяких настроек и объем документации очень большой, но это и минус и одновременно плюс — за гибкость надо платить.

RCC->CFGR3 |= RCC_CFGR3_HRTIM1SW_PLL;
RCC->APB2ENR |= RCC_APB2ENR_HRTIM1EN;

Нужно указать, что HRTIM тактируется от PLL, множитель х2 по умолчанию уже включен. Дальше просто включаем тактирование для HRTIM, вот тут первая фича — как мы понимаем таймер тактируется от PLL, но включаем для APB2. Это не совсем логично, но в файлике с CMSIS легко ищется или в документации.

RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

GPIOA->MODER &= ~GPIO_MODER_MODER8;
GPIOA->MODER |= GPIO_MODER_MODER8_1; // Alternative PP
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8; // Very high speed

GPIOA->MODER &= ~GPIO_MODER_MODER9;
GPIOA->MODER |= GPIO_MODER_MODER9_1;
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9;

GPIOA->AFR[1] |= 0xDD; // PA8 and PA9 — AF13

PA8 и PA9 — это выход Timer A, который на моем модуле идет на канал №1, что вы можете видеть на схеме и pin-out. Ноги настраиваются как push-pull с альтернативной функцией, номер самой функции для обеих ног 13-й. Важно так же настроить на максимальную частоту GPIO, а то будет непонятный завал фронта и спада сигнала, что для силовой электроники крайне критично.

HRTIM1->sCommonRegs.DLLCR |= HRTIM_DLLCR_CAL | HRTIM_DLLCR_CALEN;
while ((HRTIM1->sCommonRegs.ISR & HRTIM_ISR_DLLRDY) == RESET);

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

HRTIM1->sTimerxRegs[0].PERxR = PeriodTimerA; // Period for timer A
HRTIM1->sTimerxRegs[0].CMP1xR = 0; // Duty for timer A

Вот и пошла гибкость. Во-первых, мы можем задать свою частоту для каждого таймера A…E, тут просто записываем период нашего ШИМа. Во-вторых, по умолчанию у нас выравнивание ШИМа по началу периода, то есть сигнал переходит в лог.1 при начале нового периода, а теперь нам надо выбрать когда он вернутся в лог.0, в данном случае по компаратору №1, то есть я задаю в нем по суть коэффициент заполнения (duty).

Для примера можно переводить ШИМ не по началу периода, а по компаратору №1, а возвращать в лог.0 по компаратору №2 и таким образом аппаратно двигать фазу.

// Deadtime enable
HRTIM1->sTimerxRegs[0].OUTxR |= HRTIM_OUTR_DTEN;

// Tdtg = 6.94 ns
HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTPRSC_0 | HRTIM_DTR_DTPRSC_1;

// Deadtime rising = 15*Ttg = 104 ns
HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTR_0 | HRTIM_DTR_DTR_1 | HRTIM_DTR_DTR_2 | HRTIM_DTR_DTR_3;

// Deadtime falling = 15*Ttg = 104 ns
HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTF_0 | HRTIM_DTR_DTF_1 | HRTIM_DTR_DTF_2 | HRTIM_DTR_DTF_3;
HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTFSLK | HRTIM_DTR_DTRSLK;

На этом этапе мы включаем dead-time и настраиваем его, в принципе в комментариях есть все формулы, их можно так же найти в reference manual. DT длительностью ~100 нс вы уже видели на осциллограмме в теоретической главе данной статьи. Мертвое время можно задать отдельно по фронту и по спаду сигнала. Кстати, [0] — это Timer A, соответственно [1] — это Timer B и так далее.

// Samples in middle of ON time
HRTIM1->sTimerxRegs[0].CMP2xR = PeriodTimerA / 10;

// ADC trigger 1 update: Timer A
HRTIM1->sCommonRegs.CR1 |= HRTIM_CR1_ADC1USRC_0;

// ADC trigger 1 event: Timer A compare 2
HRTIM1->sCommonRegs.ADC1R |= HRTIM_ADC1R_AD1TAC2;

Для меня это был самый не очевидный момент. Суть какая — я хочу сделать так, чтобы на 10% длительности периода Timer A генерировалось событие, которое бы запускал преобразование АЦП и измеряло сигнал с обратной связи. Почему 10%? Просто в идеале измерение не должно приходиться на момент перехода ШИМа из 0 в 1 или обратно, т.к. в этот момент в силовой части идут переходные процессы и помехи, а нам их измерять не надо. Поэтому 10% в моем случае оптимально, т.к. при 12В выходном и 30В входном напряжение коэффициента заполнения (duty) не упадет до 10% и момент переключения транзистора точно не совпадет с измерением АЦП.

Теперь нужно посмотреть на систему связи событий между HRTIM и АЦП:

В первой строке мы выбираем когда именно будет срабатывать компаратор, в моем случае это 10% от периода таймера А. Далее выбираем конкретный триггер в АЦП, который свяжется МК, нам доступен 1 или 3-й. Теперь просто указывает какое событие будет отправлять сигнал на АЦП, в моей случае это компаратор №2.

// Enable output PWM for TA1 and TA2
HRTIM1->sCommonRegs.OENR |= HRTIM_OENR_TA1OEN | HRTIM_OENR_TA2OEN;

// Continuous mode
HRTIM1->sTimerxRegs[0].TIMxCR |= HRTIM_TIMCR_CONT;

// Period for master timer
HRTIM1->sMasterRegs.MPER = 65000;

// Enable counter for Master and timer A
HRTIM1->sMasterRegs.MCR |= HRTIM_MCR_MCEN | HRTIM_MCR_TACEN;

И финальный аккорд! Разрешаем HRTIM выводить сигналы от Timer A на наши GPIO. Теперь выбираем режим, бывает бесконечный (у меня он), а бывает что таймер включился на 1 период и после этого его опять надо запускать. Далее устанавливаем период для Master timer и последним шагом включаем его, он начинает тактировать таймеры каналов и на выходе появляется ШИМ сигнал.

Это была функция настройки, осталось сделать функцию, которая будет задавать коэффициент заполнения (duty), именно с ней мы и будем работать при создании регулятора:

void SetDutyTimerA (uint16_t duty) {
HRTIM1->sTimerxRegs[0].CMP1xR = duty;
}

Листинг функции настройки и установки коэффициента заполнения// f = 102,4 kHz
#define PeriodTimerA ((uint16_t)45000)

void InitHRPWM (void) {

RCC->CFGR3 |= RCC_CFGR3_HRTIM1SW_PLL;
RCC->APB2ENR |= RCC_APB2ENR_HRTIM1EN;

/************************************************
* Setting GPIO
***********************************************/

RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

// Alternative PP
GPIOA->MODER &= ~GPIO_MODER_MODER8;
GPIOA->MODER |= GPIO_MODER_MODER8_1;

// Very high speed
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8;

GPIOA->MODER &= ~GPIO_MODER_MODER9;
GPIOA->MODER |= GPIO_MODER_MODER9_1;
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9;

// PA8 and PA9 — AF13
GPIOA->AFR[1] |= 0xDD;

/************************************************
* Setting timer A
***********************************************/

HRTIM1->sCommonRegs.DLLCR |= HRTIM_DLLCR_CAL | HRTIM_DLLCR_CALEN;
while ((HRTIM1->sCommonRegs.ISR & HRTIM_ISR_DLLRDY) == RESET);

// Period for timer A
HRTIM1->sTimerxRegs[0].PERxR = PeriodTimerA;

// Duty for timer A
HRTIM1->sTimerxRegs[0].CMP1xR = 0;

// Deadtime enable
HRTIM1->sTimerxRegs[0].OUTxR |= HRTIM_OUTR_DTEN;

// Tdtg = 6.94 ns
HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTPRSC_0 | HRTIM_DTR_DTPRSC_1;

// Deadtime rising = 15*Ttg = 104 ns
HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTR_0 | HRTIM_DTR_DTR_1 | HRTIM_DTR_DTR_2 | HRTIM_DTR_DTR_3;

// Deadtime falling = 15*Ttg = 104 ns
HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTF_0 | HRTIM_DTR_DTF_1 | HRTIM_DTR_DTF_2 | HRTIM_DTR_DTF_3;

HRTIM1->sTimerxRegs[0].DTxR |= HRTIM_DTR_DTFSLK | HRTIM_DTR_DTRSLK;

// Event forces the output to active state for TA1
HRTIM1->sTimerxRegs[0].SETx1R |= HRTIM_SET1R_PER;

// Event forces the output to inactive state for TA1
HRTIM1->sTimerxRegs[0].RSTx1R |= HRTIM_RST1R_CMP1;

/************************************************
* ADC trigger intialization (with CMP2 event)
************************************************/

// Samples in middle of ON time
HRTIM1->sTimerxRegs[0].CMP2xR = PeriodTimerA / 10;

// ADC trigger 1 update: Timer A
HRTIM1->sCommonRegs.CR1 |= HRTIM_CR1_ADC1USRC_0;

// ADC trigger 1 event: Timer A compare 2
HRTIM1->sCommonRegs.ADC1R |= HRTIM_ADC1R_AD1TAC2;

/************************************************
* HRTIM start
***********************************************/

// Enable output PWM for TA1 and TA2
HRTIM1->sCommonRegs.OENR |= HRTIM_OENR_TA1OEN | HRTIM_OENR_TA2OEN;

// Continuous mode
HRTIM1->sTimerxRegs[0].TIMxCR |= HRTIM_TIMCR_CONT;

// Period for master timer
HRTIM1->sMasterRegs.MPER = 65000;

// Enable counter for Master and timer A
HRTIM1->sMasterRegs.MCR |= HRTIM_MCR_MCEN | HRTIM_MCR_TACEN;

}

void SetDutyTimerA (uint16_t duty) {

HRTIM1->sTimerxRegs[0].CMP1xR = duty;

}

Теперь давайте выясним правильным ли путем мы движемся. В функции main инициализируем настройку HRTIM и устанавливаем скважность, допустим 22500. При входном напряжение 20В и периоде 45000 наш коэффициент заполнения будет 50% и на выходе будет около 10В. Этого не хватит чтобы раскачегарить светодиод на полную, но загореться он должен и мы поймем работает ли силовая часть, все хорошо ли с dt и прочее. У меня все стартануло с первого раза:

Вы можете видеть, что все предыдущие теоретические выкладки подтвердились. При фиксированном коэффициенте заполнения (duty) в 50% напряжение на выходе просто делилось на 2: 20В -> 10В, 22В -> 11В, 18В -> 9В. Теперь давайте сделаем так, чтобы напряжение на выходе было стабильным и не зависело от входного, то есть добавим обратную связь.

Настройка АЦП и регулятора

Про АЦП в STM32 очень много уже написано до меня, я подробно остановился лишь на настройке триггера привязанного к компаратору HRTIM. Об остальных настройках АЦП я расскажу кратко. Смотрим функцию инициализации:

void InitBasicADC (void) {

RCC->AHBENR |= RCC_AHBENR_ADC12EN;
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;

/************************************************
* Calibration
***********************************************/

ADC2->CR &= ~ADC_CR_ADVREGEN;
ADC2->CR |= ADC_CR_ADVREGEN_0; // Vref enable
Delay(10);
ADC2->CR &= ~ADC_CR_ADCALDIF;

ADC2->CR |= ADC_CR_ADCAL; // Start calibration
while (ADC2->CR & ADC_CR_ADCAL); // Wait end calibration

/************************************************
* Select event trigger and channel
***********************************************/

// Enable start conversion external trigger
ADC2->CFGR |= ADC_CFGR_EXTEN_0;

// Event 7 — HRTIM
ADC2->CFGR |= ADC_CFGR_EXTSEL_0 | ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_2;

// Select ADC2 channel IN5
ADC2->SQR1 |= ADC_SQR1_SQ1_0 | ADC_SQR1_SQ1_2;

// Length regular ADC channel = 1
ADC2->SQR1 &= ~ADC_SQR1_L;

ADC2->IER |= ADC_IER_EOCIE; // Interrupt enable
NVIC_EnableIRQ(ADC1_2_IRQn); // enable interrupt ADC1 and ADC2

/************************************************
* Start ADC
***********************************************/

ADC2->CR |= ADC_CR_ADEN; // Enable ADC2
Delay(10);
ADC2->CR |= ADC_CR_ADSTART;
}

Я использую режим регулярных каналов, канал у меня всего один и он выбирается в регистре SQR1. Задействовал АЦП №2, а именно его вход IN5, он является быстрым и может работать на максимальной частоте семплирования, но не в этот раз. Частота выборки равна частоте ШИМа, т.к. 1 период = 1 выборка, в принципе этого более чем достаточно.

Так же нам в регистре CFGR нужно выбрать событие по которому будет запускать преобразование, то есть Event 7, почему именно он? Смотрим в RM:

Trigger 1 от модуля HRPWM приходит на Event 7 для нашего АЦП №2, который в данном случае работает в роли slave, то он управляется от модуля HRPWM. Думаю теперь понятно как связать 2 модуля, в принципе алгоритм аналогичный для любой периферии и любого таймера, отличаться будет разве что название регистра.

При достижения счетчика периода Master timer будет запускаться преобразования, которое спустя примерно 15 циклов (сколько точно смотрите в RM-е) вызовет прерывание и в нем можно будет забрать результат. Именно в этом прерывание мы и организуем алгоритм управления. Да, внутри прерывания что-то массивно лучше не делать, лучше выставлять флаг и передавать выполнение дальше, но я позволю себе такое упрощение, ибо в данном случае мой контроллер ничем особо не загружен и он с вероятностью 146% успеет посчитать и выйти из прерывания до появления нового.

Немного об управление

Представьте, что вы вошли в ванную комнату и решили помыть руки в раковине. Вы чуть приоткрываете воду, трогаете рукой, холодная? Добавляете еще горячей воды, теплее? Хорошо! Добавляете еще горячей воды? Почти то, что надо? Хорошо! Добавим еще горячей воды, пробуем рукой, обжегся? Давай теперь чуть убавим горячую. Хорошо? И так до бесконечности вы будете крутить кран пока температура воды не станет идеальной. Это самый простой регулятор!

Только у нас регулируется не количество горячей воды, а коэффициент заполнения ШИМа. Вместо руки у нас АЦП с измеренным результатом. Остается лишь реализовать логику. Мы посчитаем что должен выдавать АЦП при 12В на выходе, а потом с помощью условия if заставим наш контроллер поддерживать это значение с помощью изменения коэффициента заполнения (duty).

Для начала давайте повесим на выход делитель напряжения, чтобы 12В уменьшить до 2-2,5В к примеру, т.к. АЦП умеет измерять от 0 до +3,3В и если подать 12В, то микроконтроллер сгорит просто. Поэтому я поставлю делитель с номиналами 10 кОм и 2 кОм, который даст коэффициент деления 6 и соответственно наши +12В превратятся в +2В. Наш АЦП будет выдавать результат: adcResult = (Vout / k) / Vref * 212 = (12V / 6) / 3.3 * 4095 = 2481. Теперь пишем код для обработчика прерывания:

void ADC1_2_IRQHandler (void) {

ADC2->ISR |= ADC_ISR_EOC;

adcResult = ADC2->DR;

if (adcResult > 2480) {

dutyControl = dutyControl — 10;
}
else
{
dutyControl = dutyControl + 10;
}

SetDutyTimerA(dutyControl);
}

Первым делом после попадания в обработчик прерывания нужно сбросить флаг этого самого прерывания, иначе второй раз вы в него уже не попадете. Затем считываем результат и сохраняем его в виде переменной adcResult. Теперь, зная напряжение на выходе, нужно внести корректировку значения коэффициента заполнения для ШИМ, я это реализовал просто через условие if. В каждом периоде ШИМ мы делаем измерение, увеличиваем или уменьшаем коэффициент заполнения и устанавливаем полученный результат для следующего периода. Все просто, быстро и видна суть. Смотрим на результат работы:

Как видите все работает и при изменение входного напряжения сам выход остается стабильным в 12В. Сильно внимательные могут заметить небольшие иголки проскакивающие, тут нужно просто повесить керамику X7R на выход на 1-10 мкФ и они уйдут, мне просто лень уже искать ее и напаивать. Теперь сама осциллограмма, чтобы не портить глаза:

Тут вы можете увидеть как нарастает напряжение на выходе. Дело в том, что из-за алгоритма управления, чтобы заполнение достигло нужно значения от 0 до 10000, например, необходима тысяча периодов или примерно 10 мс. Меня это устраивает ибо софт-старт, если вы хотите сократить время нарастания, то чуть усложните алгоритм и прибавляйте +1000, а не +10, и чем ближе вы будете подбираться к заданным 12В, тем меньше шаг регулирования делайте пока не дойдете до +10. Вообще в плане управления можно сделать много чего, так что поле для экспериментов у вас есть.

Еще интересный момент это колебания в момент выключения, такая «гармошка». Дело в том, что после выключения питания моя цифровая часть продолжает работать от другого БП и она пытается удержать нужное значение на выходе. Откуда берется энергия? Да из входных конденсатор, это те что по 1000 мкФ аж 3 штуки, вот такое интересное явление.

Заключение
Статья вышла не маленькая, но вы же хотели всё и сразу мол давай готовую железку — получите. Надеюсь статья вам понравится, постарался сделать ее скорее не научную, а научно-популярную, чтобы материал был доступен людям с разным уровнем знаний и опыта. Возможно в дальнейшем я разберу аналогично и другие топологии типа boost, full bridge и прочие.

Кстати, данная статья и код послужат для нового MPPT контроллера на 20А, который проектирую. Сейчас жду платы от PCBway, которые собственно и вызвались про спонсировать печатными платами мои открытые проекты, исходники на MPPT так же будут открыты как и на все мои модули.

Самое главное забыл! Держите проект с кодом для TrueSTDIO — RAR.

Оставить комментарий