Хабрахабр

Генерация многофазного ШИМ сигнала на TMS320F28027

Данные контроллеры являются очень мощным инструментов разработки во многих задачах и хотелось написать про них что-то еще… простое и полезное. Давным давно в далекой далекой галактике я написал небольшую статью о специализированных контроллера Piccolo от Texas Instruments, которые предназначены для управления силовыми преобразователями и электроприводом.

Недавно меня озадачили разработать контроллер для управления двигателем и соответственно образовалась тема для статьи — сегодня я расскажу о процессе формирования трехфазного ШИМа для управления двигателем, а так же объясню в чем выгодные отличия TMS320F28 от других контроллеров типа STM32F334, STM32G484, XMC4200 и остальных.

Правда, если я скажу, что контроллер построен на базе связки TMS320F28027 + DRV8353RSRGZT, то вы можете посмотреть в даташит на драйвер и увидеть общий концепт схемотехники + на данном камне есть отладка и reference design на нее открыт. В качестве стенда я буду использовать разрабатываемый контроллер, увы, подробно про железную часть я рассказывать не могу.

Драйвер BLDC

Я покажу оба варианта, т.к. В принципе на однотипной схемотехнике можно управлять и BLDC двигателями, которые "потребляют" уровни напряжения и обычными трехфазниками, которые уже хотят синусоидальный выход. путь к синус лежит через формирование уровней напряжения.

Осциллограмма №1

Силовая часть драйвера идеологически представляет из себя 3 полумостовых преобразователя, подобным образом выполнены наверное все частотники и контроллеры для управления BLDC двигателями во всяких коптерах:

Трехфазный мост

контроллер изначальное питается от постоянного напряжения. Отличие одно — у меня нет выпрямителя входного, т.к. Используемый драйвер DRV8353RSRGZT умеет как раз управлять 3-мя силовыми полумостами, так же в применяемой версии камня еще имеются встроенные ОУ для работы с шунтами в роли датчиков тока, встроенный dc/dc, который умеет переваривать до 70... Источник питания в моем случае это сборка из li-ion АКБ в виде ячеек 18650. Например, очень удобно иметь возможность настроить максимальный импульсный ток управления транзисторами. 80В и все это очень гибко настраивается через SPI.

По цене они не особо отличаются и я взял самый "жирный" как вы уже наверное поняли. Так же в данной серии имеются драйверы с разным набором функций, например, есть с аналоговым управлением, а не SPI или без встроенного dc/dc и без ОУ. На самом деле проблема одна — это сильный перегрев: Все это дело выглядит очень красиво, но я довольно легкомысленно подошел к проектированию обвязки драйвера и у меня вылезло 2 существенные проблемы.

Тепловизор

Собственно суть проблемы заключается в перегреве самого драйвера. А вот вызвана эта проблема была уже 2-мя причинами. Транзисторов даже не видно, они имеют температуру печатной платы, при 5А там мизерные потери на тепло. На термограмме драйвер нагружен током 5А (для него это почти холостой ход) и не греется ничего кроме драйвера и чуть-чуть сам МК.

  • Ошибка №1
    Меня на нее натолкнул мой знакомый, я честно говоря на такое подумал бы в самую последнюю очередь — в драйвер встроен dc/dc, который принимает на вход 15...50В и выдает 3.3В для питания МК, логики, компараторов и операционных усилителей. Казалось бы у меня в проектах стоят микросхемы LM5008 и LM5017 в виде отдельных микросхем и я спокойно снижал 60В в 3.3В без заметного нагрева при токе 100-150 мА, но тут все оказалось хитрее — общий КПД преобразователя получался около 65-70% при токе 300 мА! Дело в том, что сам преобразователь то может выдать 3.3В, но КПД будет мизерный, оптимально надо задавать выходное напряжение 10-12-15В. При выходе 12В 100 мА у меня драйвер перестал греться практически, а КПД достиг приятных 88%. Решение проблемы — встроенным dc/dc опускаем вход 15...50В до 12В, а дальше из 12В снижать в 3.3В уже внешним дешевым dc/dc.
  • Ошибка №2
    Вторая ошибка более очевидна и первым делом я грешил на нее как мог. Дело в том, что у микросхем в корпусе QFN основной отвод тепла осуществляется через "пузо", обычно оно сидит на GND и через несколько переходных отверстий (via) цепляются на земляной полигон и все тепло спокойно туда уходит. Изначально я не учел мизерный КПД встроенного dc/dc при большой разнице напряжений, поэтому меня не смутило, что термопад ("пузо") цеплялось к сплошному полигону GND на внутреннем слое, на наружном слое у меня меди под пузом не было как собственно и полигона GND. В итоге оказалось, что на микросхеме выделяется ~0.5 Вт тепла, а у меня оно рассеивается во внутренний слой платы, то есть эффективность очень плохая. Решение проблемы — нужно земляной полигон так же делать на наружном слое (Bottom Layer) и не делать так:

Печатная плата

3В и на нижнем слое дополнительно залит полигон GND и пад микросхемы посажен на него + сохранился внутренний сплошной полигон земли. В итоге во второй ревизии железа были исправлены данные ошибки: добавился внешний dc/dc преобразователь 12-3. После таких доработок температура в продолжительном режиме работы снизилась с +82 до +43 oC:

Термограмма

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

Что вообще такое этот сдвига фазы на 120o? Особенностью трехфазной сети является то, что ток в фазах не синхронный, а сдвинут на 120o относительно соседней. Период сигнала с математической точки зрения равен , а значит двигать второй сигнал надо на 2π/3, а третий на 4π/3. Если говорить по простому, то это смещение точки старта генерации на 1/3 периода. Например, при тактирование 60 МГц мы хотим получить ШИМ с частотой 50 кГц, значит период отсчета таймера будет от 0 до 1200 (60 000 000 Гц / 50 000 Гц = 1200). С точки зрения электроники период задается периодом отсчета нашего таймера. Теперь чтобы получить 3 фазы со сдвигом 120o нам надо 1-ю фазу не трогать, для 2-й фазы к текущему значению добавлять +400, для 3-й фазы к текущему значению добавлять +800.

Для меня всегда было удивительно почему ST, NXP и прочие не сделали просто регистр куда записывалось бы значение сдвига. Если мы используем микроконтроллеры на ядре cortex, то сдвиг мы можем реализовать или записав математическую формулу или задействовав синхронизацию по событиям. Рассказывать почему софтверное решение не оптимально не буду, просто скажу, что считает формулы МК не очень быстро. К счастью так сделали TI в своих TMS320F28xxx, для установки сдвига достаточно просто записать один регистр! Является ли преимуществом возможность аппаратно управлять фазой? Про вариант с синхронизацией от событий уже более адекватен и на stm я бы делал именно так, но такой вариант не позволяет менять значение фазы "на лету", то есть для какого-нибудь фазосдвигового моста опять только софтварный вариант остается. Для меня же это очевидный плюс, когда мы говорим об управление электроприводом или инверторах напряжения с трехфазным выходом. Это решать уже вам, моя задача рассказать, что так можно.

Пока без синуса. Теперь давайте настроим генерацию ШИМ сигналов в виде 3-х комплементарный пар с dead time и сдвигом фазы. Именно эти сигналы идут у меня от микроконтроллера к драйверу. Я буду использовать следующие пары: EPWM1A + EPWM1B, EPWM2A + EPWM2B и EPWM4A + EPWM4B.

  • Шаг 1
    Необходимо настроить мультиплексор GPIO с помощью регистра GPAMUX на работу с PWM и отключить подтяжки выхода к питанию, чтобы в момент включения на всех ногах не оказалось лог.1 и не открылись ключи. Защита по току конечно спасет, но лучше так не делать. Так же стоит помнить, что для доступа к регистрам настройки его нужно получить командой EALLOW и затем обратно включить защиту от перезаписи командой EDIS.

void InitGPIOforPWM (void) { EALLOW; GpioCtrlRegs.GPAPUD.bit.GPIO0 = 1; // Disable pull-up on GPIO0 (EPWM1A) GpioCtrlRegs.GPAPUD.bit.GPIO1 = 1; // Disable pull-up on GPIO1 (EPWM1B) GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1; // Configure GPIO0 as EPWM1A GpioCtrlRegs.GPAMUX1.bit.GPIO1 = 1; // Configure GPIO1 as EPWM1B GpioCtrlRegs.GPAPUD.bit.GPIO2 = 1; // Disable pull-up on GPIO2 (EPWM2A) GpioCtrlRegs.GPAPUD.bit.GPIO3 = 1; // Disable pull-up on GPIO3 (EPWM2B) GpioCtrlRegs.GPAMUX1.bit.GPIO2 = 1; // Configure GPIO2 as EPWM2A GpioCtrlRegs.GPAMUX1.bit.GPIO3 = 1; // Configure GPIO3 as EPWM2B GpioCtrlRegs.GPAPUD.bit.GPIO6 = 1; // Disable pull-up on GPIO6 (EPWM4A) GpioCtrlRegs.GPAPUD.bit.GPIO7 = 1; // Disable pull-up on GPIO7 (EPWM4B) GpioCtrlRegs.GPAMUX1.bit.GPIO6 = 1; // Configure GPIO6 as EPWM4A GpioCtrlRegs.GPAMUX1.bit.GPIO7 = 1; // Configure GPIO7 as EPWM4B EDIS;
}

  • Шаг 2
    Настраиваем генерацию ШИМ сигнала. Необходимо получить частоту 50 кГц и сдвиг фазы 120o. В данном случае я использую обычный PWM, ведь в данном контроллере имеется и HRPWM, это важно помнить. Тактируется модуль PWM на частоте ядра, то есть 60 МГц, как настроить частоту PLL я показывал в первой статье по TMS320, повторяться не буду, но в конце статьи будет архив с кодом и можно будет подсмотреть там.

void InitPWM (void)

таймер считаем в обе стороны, получается что период 600 соответствует частоте выходного ШИМ-сигнала в 50 кГц в режиме комплементарной пары. Теперь чуть подробнее… в регистр TBPRD записываем период, вернее "период / 2", т.к. Тут стоит отметить, что 1-ю фазу мы не двигаем, поэтому для нее сдвиг равен 0, для 2-й фазы соответственно сдвиг равен 400, а вот для 3-й фазы логичным покажется вариант записать 800, но 800 из 600 как-то не очень… поэтому пишут сдвиг не относительно 1-й фазы, а относительной предыдущей, то есть 2-й. В регистр TBPHS пишем значение фазы на которую нужно сдвинуть, в данном случае 400 из 600, что соответствует 2π/3. В итоге получаем, что в 3-ю фазу пишем 400 и это соответствует 2π/3 между фазой 2 и 3, а так как 2-я уже сдвинута, то между фазами 1 и 3 будет "2π/3 + 2π/3 = 4π/3" и с точки зрения электроники все выглядит логичным.

Так же с помощью битов SYNCOSEL настраивается "точка" синхронизации, то есть откуда до куда считать сдвиг. Чтобы фазы понимали кто относительно кого двигается — нужен начальник, поэтому EPWM1 настраивается с помощью бита PHSEN в режим master (ведущий), а EPWM2 и EPWM4 соответственно как slave (ведомые). EPWM1 синхронизируется с началом отсчета таймера, то есть с нулем периода, а EPWM2 и EPWM4 уже синхронизируются относительно фронта сигнала предыдущего канала: предыдущий канал для EPWM2 это EPWM1, а для EPWM4 это EPWM2.

С помощью битов POLSEL устанавливаем не инверсный ШИМ, то есть по достижению заданного значения компаратора (отсчета) на выходе генерируется лог. Теперь осталось включить комплементарные пары и установить длительность dead-time. В OUT_MODE устанавливаем генерацию dead-time и по фронту и по спаду сигнала. 1. Соответственно в регистры DBFED и DBRED записываем длительность мертвого времени в тактах.

  • Шаг 3
    Теперь остается записать значение коэффициента заполнения (duty) в соответствующие каждому каналу регистр CMPA и можно наблюдать результат.

EPwm1Regs.CMPA.half.CMPA = 300; // duty for output EPWM1A EPwm2Regs.CMPA.half.CMPA = 300; // duty for output EPWM2A EPwm4Regs.CMPA.half.CMPA = 300; // duty for output EPWM4A

Трехфазный ШИМ

Щупы осциллографа подключены на выход драйвера. Вуаля! Синий канал это EPWM2 и он сдвинут на 2π/3 (или на 400 отсчетов) относительно желтого канала, а зеленый канал сдвинут еще на 400 отсчетов. Желтый канал — это наш EPWM1, то есть мастер. Таким образом мы получаем 3 фазы, где каждая фаза сдвинута на 120o.

Давайте теперь перекинем щупы осциллографа с выхода силового моста на управляющие сигналы, которые выходят с микроконтроллера и проверим наличие dead-time внутри комплементарной пары:

Осциллограмма №2

Длительность одного отсчета 1 / 60 000 000 Гц = 16,6 нс и у нас получается 20 отсчетов, что равносильно длительности мертвого времени 20 16,6 нс = 332 нс,* что примерно и наблюдается на осциллограмме. Как видите установленное мертвое время соответствует реальному.

Самый очевидный вариант — многофазные dc/dc преобразователи, для заинтересованных гуглить interleaved dc/dc converter. Собственно где такое может пригодиться, именно в том виде, что сейчас. На простеньком TMS320F28027 можно реализовать 4-х фазный преобразователь и все это будет очень просто реализовано в коде и только аппаратно. Это крайне интересное техническое решение, которое позволяет значительно уменьшить размеры силовых индуктивностей, уменьшить выходную емкость конденсаторов, а так же повысить КПД.

У меня есть статья, где рассказывается про формирование однофазного переменного напряжения и там используется "табличный" метод, то есть значения для синусоиды изначально посчитаны. Во многих задачах будет недостаточно получить на выходе дискретные значения 0 или VCC, нужна синусоида. В принципе так можно сделать и для трехфазки, но хочется показать альтернативный вариант, а именно расчет значения заполнения (duty) в реальном времени или "на лету".

Частота ШИМа в данном случае так же 50 кГц и сдвиг фазы устанавливается между периодами данного сигнала. Тут есть одна особенность. Тригонометрия штука тяжеловесная для TMS320F28027, но у меня он не особо загружен, поэтому пусть считает. Соответственно, когда мы будем модулировать синусоиду частотой 50 Гц, то аппаратный сдвиг фазы "потеряется", он все еще будет присутствовать между ШИМами, но не внутри синусоиды, поэтому его придется делать софтварно. Если же у вас задача, требующая большого количества вычислений, то вам нужен контроллер с TMU и FPU, например, TMS320F280049, который сможет ворочать математику гораздо быстрее.

Мне нужен период 20 мс (1/50Гц = 20 мс) и количество шагов в синусоиде возьму допустим 20, в итоге с частотой 0,02 с / 20 = 0,001 мс = 1 кГц должно генерироваться прерывание и в этом прерывании буду записывать значение в ШИМ. Для загрузки значений заполнения (duty) в ШИМ нам потребуется таймер, период которого задаст частоту дискретизации. Для простоты возьму обычный таймер CPU0 и настрою его:

void InitTimer0ForGenerator (void) { EALLOW; PieVectTable.TINT0 = &cpu_timer0_isr; EDIS; InitCpuTimers(); ConfigCpuTimer(&CpuTimer0, 60, 1000); CpuTimer0Regs.TCR.bit.TIE = 1; CpuTimer0Regs.TCR.bit.TSS = 0; IER |= M_INT1; PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // Enable TINT0 in the PIE: Group 1 interrupt 7 EINT; // Enable Global interrupt INTM ERTM; // Enable Global real-time interrupt DBGM } __interrupt void cpu_timer0_isr (void) { CpuTimer0.InterruptCount++; /* * Кадило крутится - синус мутится. Потом... */ PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // Acknowledge this interrupt to receive more interrupts from group 1 }

Так где в функции настройки разрешаем прерывания и передаем адрес обработчика нашего прерывания, где все будет происходить. Функции InitCpuTimers и ConfigCpuTimer стандартные, вся настройка именно в них, нам нужно просто передать частоту ядра (60 МГц) и период счета в микросекундах (1000 мкс = 1 мс), что эквивалентно частоте 1 кГц, а нам так и надо было.

И так… у нас есть функция y = sin(x) давайте построим график данной функции: Теперь необходимо заново "изобрести" формулу синуса, для этого потребуется знание школьной тригонометрии и все.

y=sin(x)

при минимальной амплитуде у нас 0В, а при максимальной (эквивалент 1) у нас +VCC. Как видно на графике амплитуда у изменяется от -1 до 1, мы же хотим от 0 до 1, т.к. Нужно сместить график в положительную сторону. Чтобы "нарисовать" -1...+1 нам нужно двухполярное питание, а его нет. Значит нужно поделить на 2 и всего-то! Если мы его просто поднимем вверх, то он станет от 0 до +2, а мы может только до +1. Давайте для начала просто поделим и построим график для y = (sin(x)/2):

y=(sin(x)/2)

Теперь график имеет размах от -0. Ага! 5, то есть амплитуда составляет 1. 5 до +0. 5, для этого нужно просто прибавить данное значение к результату и получим формулу y = 0. Уже лучше, но мы еще не избавились от отрицательных значений, так давайте просто сместим график вверх на 0. 5 + (sin(x)/2) и построим график для данной функции:

5 + (sin(x)/2)"/> <img src="https://habrastorage.org/webt/df/vu/ez/dfvuez1lks2uhg0cp3tkhie9cdq.png" alt="y = 0.

Формула y = 0. Вот теперь стало все совсем прекрасно: синусоида имеет амплитуду от 0 до 1, отрицательные значения отсутствуют. Для этого из x отнимаем 2π/3 и 4π/3 соответственно и получаем формулы для оставшихся фаз y = 0. 5 + (sin(x)/2) описывает 1-ю фазу, теперь необходимо добавить сдвиг фазы, чтобы получить фазы 2 и 3. 5 + (sin(x-4π/3)/2). Строим 3 графика и смотрим похоже ли на правду: 5 + (sin(x-2π/3)/2) и y = 0.

3 фазы

Картинка похожа на то, что обычно рисуют в учебниках электротехники, когда говорят про трехфазную сеть или асинхронные двигатели. Неплохо! 0943 это 2π/3, а 4,1866 соответственно 4π/3, я их просто сразу посчитал и они у меня фигурируют в коде. Кстати, 2. Итого у нас есть 3 уравнения:

  • Фаза А — y = 0.5 + (sin(x)/2)
  • Фаза В — y = 0.5 + (sin(x-2π/3)/2)
  • Фаза С — y = 0.5 + (sin(x-4π/3)/2)

Синусоида у нас не аналоговая, а имеет "ступеньки", то есть дискретна, ведь мы умеет задавать только напряжение или 0В или +15В (VCC) в моем случае. Со стороны математики вроде все просто и понятно, но теперь нужно адаптировать для микроконтроллерных реалий. Ранее я писал, что шагов у меня будет 20, значит на 1 период у меня будет 20 вычислений.

Период нашей синусоиды , а значит шаг дискретизации будет равен 2π/20. Для начала определимся, что подставлять вместо x. В итоге значение на первом шаге будет sin(2π * (1/20), на втором шаге sin (2π * (2/20)), на третьем шаге *sin (2π (3/20)) и так далее, когда мы дойдет до 20/20, то это будет означать окончание периода и надо будет начинать считать по новой. Соответственно синусоида будет состоять из 20 точек, мы как бы строим график по точкам, а между ними аппроксимируем. На основании полученных данных давай поправим формулы:

  • Фаза А — y = 0.5 + (sin(2π * (n/N))/2)
  • Фаза В — y = 0.5 + (sin(2π * (n/N)-2π/3)/2)
  • Фаза С — y = 0.5 + (sin(2π * (n/N)-4π/3)/2)

Соответственно n — текущий шаг, N — всего шагов (20). Вот, теперь мы считаем значение синуса в каждой конкретной точки на графике. Амплитуда в нашем случае зависит от коэффициента заполнения, т.к. После этих формул мы получим значение от 0 до 1, но в реальности мы оперируем не с абстрактной амплитудой. Исходя из этого давайте пересчитаем в реальную формулу для получения значения, которое будет загружаться в регистр ШИМа CMPA: duty меняется от 0 до 600 (из настроек ШИМа), то 0 это 0, а 1 эквивалентна 600.

  • Фаза А — duty1 = A (0.5 + (sin(2π (n/N))/2))
  • Фаза В — duty2 = A (0.5 + (sin(2π (n/N)-2π/3)/2))
  • Фаза С — duty4 = A (0.5 + (sin(2π (n/N)-4π/3)/2))

Значения duty1, duty2, duty4 — это пересчитанное реальное значения коэффициента заполнения (duty), которое загружается в CMPA. Теперь давайте напишем код для обновленного обработчика прерываний и объявим все необходимые переменные: Соответственно А — это максимальное значение амплитуды, то есть 600, n — текущий шаг, N — всего шагов (20).

float activeStep = 0.0;
float amplitude = 600.0;
float allStep = 20.0; const float pi = 3.1415; // π
const float piTwo = 6.2831; // 2π
const float phaseShifted120deg = 2.0943; // 2π/3
const float phaseShifted240deg = 4.1866; // 4π/3 __interrupt void cpu_timer0_isr (void) { if (activeStep >= allStep) {activeStep = 0;} activeStep++; EPwm1Regs.CMPA.half.CMPA = ((uint16_t)(amplitude * (0.5 + (sinf(piTwo * (activeStep / allStep)) / 2)))); EPwm2Regs.CMPA.half.CMPA = ((uint16_t)(amplitude * (0.5 + (sinf(piTwo * (activeStep / allStep) - phaseShifted120deg) / 2)))); EPwm4Regs.CMPA.half.CMPA = ((uint16_t)(amplitude * (0.5 + (sinf(piTwo * (activeStep / allStep) - phaseShifted240deg) / 2)))); PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // Acknowledge this interrupt to receive more interrupts from group 1 }

При каждом вызове прерывания мы инкрементирует переменную activeStep, которая содержит в себе номер шага, она изменяется от 0 до 20 и потом сбрасывается. Код как видите простейший, если понимать что требовалось сделать и простую математику в решаемой задаче. Чтобы не считать в формуле постоянно 2π/3 и 4π/3 я посчитал их сразу, чтобы использоваться как константы. Получается, что за один период мы выполняет 20 шагов и 20 вычислений для каждой фазы.

При желании количество точек можно значительно увеличить, например, до 200. Вычислений получилось минимум, для данного МК это совсем ни о чем. Изменение частоты ШИМа происходит с помощью изменения частоты вызова прерывания и количества шагов. Все зависит от задачи. Так же вы можете изменять переменную amplitude и изменять напряжение на выходе силового преобразователя.

После загрузки кода в микроконтроллер вы получите соответствующую картинку:

Осциллограмма №1

Это следствие малого количество шагов дискретизации, тут действует условное правило: чем больше точек, чем красивее сигнал. Если растянуть по Y график, то станет лучше видно дефекты сигнала.

Осциллограмма №3

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

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

Архив с проектом для Code Composer Studio

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть