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

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

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

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

Драйвер BLDC

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

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

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

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

Отличие одно — у меня нет выпрямителя входного, т.к. контроллер изначальное питается от постоянного напряжения. Источник питания в моем случае это сборка из li-ion АКБ в виде ячеек 18650. Используемый драйвер DRV8353RSRGZT умеет как раз управлять 3-мя силовыми полумостами, так же в применяемой версии камня еще имеются встроенные ОУ для работы с шунтами в роли датчиков тока, встроенный dc/dc, который умеет переваривать до 70…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) и не делать так:

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

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

Термограмма

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

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

Если мы используем микроконтроллеры на ядре cortex, то сдвиг мы можем реализовать или записав математическую формулу или задействовав синхронизацию по событиям. Для меня всегда было удивительно почему ST, NXP и прочие не сделали просто регистр куда записывалось бы значение сдвига. К счастью так сделали 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) {

    // EPWM Module 1 config
    EPwm1Regs.TBPRD = 600; // Set priod
    EPwm1Regs.TBPHS.half.TBPHS = 0; // Set phase
    EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; // Symmetrical mode
    EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE; // Master enable
    EPwm1Regs.TBCTL.bit.PRDLD = TB_SHADOW;
    EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_CTR_ZERO; // Sync down-stream module
    EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
    EPwm1Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
    EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; 
    EPwm1Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; 
    EPwm1Regs.AQCTLA.bit.CAU = AQ_SET;
    EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR;
    EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; // enable dead-time module
    EPwm1Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; // Active Hi complementary
    EPwm1Regs.DBFED = 20; // dead-time on 20 tick
    EPwm1Regs.DBRED = 20; // dead-time off 20 tick

    // EPWM Module 2 config
    EPwm2Regs.TBPRD = 600; 
    EPwm2Regs.TBPHS.half.TBPHS = 400; // Set phase = 400/1200 * 360 = 120 deg
    EPwm2Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; 
    EPwm2Regs.TBCTL.bit.PHSEN = TB_ENABLE; // Slave enable
    EPwm2Regs.TBCTL.bit.PHSDIR = TB_DOWN; // Count DOWN on sync (=120 deg)
    EPwm2Regs.TBCTL.bit.PRDLD = TB_SHADOW;
    EPwm2Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN; // sync flow-through
    EPwm2Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
    EPwm2Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
    EPwm2Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; 
    EPwm2Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; 
    EPwm2Regs.AQCTLA.bit.CAU = AQ_SET; 
    EPwm2Regs.AQCTLA.bit.CAD = AQ_CLEAR;
    EPwm2Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; 
    EPwm2Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; 
    EPwm2Regs.DBFED = 20; 
    EPwm2Regs.DBRED = 20;

    // EPWM Module 4 config
    EPwm4Regs.TBPRD = 600; 
    EPwm4Regs.TBPHS.half.TBPHS = 400; 
    EPwm4Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; 
    EPwm4Regs.TBCTL.bit.PHSEN = TB_ENABLE; 
    EPwm4Regs.TBCTL.bit.PHSDIR = TB_UP; 
    EPwm4Regs.TBCTL.bit.PRDLD = TB_SHADOW;
    EPwm4Regs.TBCTL.bit.SYNCOSEL = TB_SYNC_IN; 
    EPwm4Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
    EPwm4Regs.CMPCTL.bit.SHDWBMODE = CC_SHADOW;
    EPwm4Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; 
    EPwm4Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; 
    EPwm4Regs.AQCTLA.bit.CAU = AQ_SET; 
    EPwm4Regs.AQCTLA.bit.CAD = AQ_CLEAR;
    EPwm4Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; 
    EPwm4Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC; 
    EPwm4Regs.DBFED = 20; 
    EPwm4Regs.DBRED = 20; 

}

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

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

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

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

Вуаля! Щупы осциллографа подключены на выход драйвера. Желтый канал — это наш EPWM1, то есть мастер. Синий канал это EPWM2 и он сдвинут на 2π/3 (или на 400 отсчетов) относительно желтого канала, а зеленый канал сдвинут еще на 400 отсчетов. Таким образом мы получаем 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 кГц и сдвиг фазы устанавливается между периодами данного сигнала. Соответственно, когда мы будем модулировать синусоиду частотой 50 Гц, то аппаратный сдвиг фазы «потеряется», он все еще будет присутствовать между ШИМами, но не внутри синусоиды, поэтому его придется делать софтварно. Тригонометрия штука тяжеловесная для TMS320F28027, но у меня он не особо загружен, поэтому пусть считает. Если же у вас задача, требующая большого количества вычислений, то вам нужен контроллер с TMU и FPU, например, TMS320F280049, который сможет ворочать математику гораздо быстрее.

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

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

y=(sin(x)/2)

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

y = 0.5 + (sin(x)/2)

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

3 фазы

Неплохо! Картинка похожа на то, что обычно рисуют в учебниках электротехники, когда говорят про трехфазную сеть или асинхронные двигатели. Кстати, 2.0943 это 2π/3, а 4,1866 соответственно 4π/3, я их просто сразу посчитал и они у меня фигурируют в коде. Итого у нас есть 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 вычислений.

Для начала определимся, что подставлять вместо x. Период нашей синусоиды , а значит шаг дискретизации будет равен 2π/20. Соответственно синусоида будет состоять из 20 точек, мы как бы строим график по точкам, а между ними аппроксимируем. В итоге значение на первом шаге будет sin (2π * (1/20), на втором шаге sin (2π * (2/20)), на третьем шаге sin (2π * (3/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, но в реальности мы оперируем не с абстрактной амплитудой. Амплитуда в нашем случае зависит от коэффициента заполнения, т.к. duty меняется от 0 до 600 (из настроек ШИМа), то 0 это 0, а 1 эквивалентна 600. Исходя из этого давайте пересчитаем в реальную формулу для получения значения, которое будет загружаться в регистр ШИМа CMPA:


  • Фаза А — 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))

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

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 и потом сбрасывается. Получается, что за один период мы выполняет 20 шагов и 20 вычислений для каждой фазы. Чтобы не считать в формуле постоянно 2π/3 и 4π/3 я посчитал их сразу, чтобы использоваться как константы.

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

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

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

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

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

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

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

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

© Habrahabr.ru