Синхронизация ШИМ на STM32

  • Tutorial
Не буду особо вдаваться в теорию, в сети много ресурсов где все очень подробно описано. Но когда дело доходит до практики понимаешь, что все намного сложнее. Используется микроконтроллер stm32l152c-discovery. В статье будет описан процесс запуска ШИМ двух таймеров в одно и то же время (полная синхронизация):

image

А так же запуск с отставанием (на фото отставание в пол периода):

image

Создадим функцию для инициализации initPWM, объявим в ней переменные для удобства

void initPWM()
{
      const uint32_t Period = 20 - 1;//переменная периода
      const uint32_t prescaler = 1 - 1;//Предделитель
      //-1 потому что счет начинается с 0
      const uint32_t pulse = 15;//Длина импульса
}

Для начала необходимо провести инициализацию структур для конфигурирования таймеров:

     GPIO_InitTypeDef port;
     TIM_TimeBaseInitTypeDef timer;
     TIM_OCInitTypeDef timerPWM;

Далее активируем необходимую нам периферию. Будем использовать TIM3 и TIM4, просто потому что так захотелось:

     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

GPIOB активируем, т.к. выходы таймеров сидят на этой шине PB5 и PB9. Эту информацию можно найти в юзер мануале к микроконтроллеру в таблице MCU pin description versus board function.

image

Настраиваем выходы в режим альтернативной фунции:

     GPIO_StructInit(&port);
     port.GPIO_Mode = GPIO_Mode_AF;//Режим альтернативной функции, нужен для ШИМ
     port.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_9;
     port.GPIO_Speed = GPIO_Speed_2MHz;//Частота до 2Мгц
     port.GPIO_OType = GPIO_OType_PP;//режим работы "push-pull" "двухтактный выход"
     GPIO_Init(GPIOB, &port);
//активация альтернативной функции выхода PB5
     GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_TIM3);
//активация альтернативной функции выхода PB9
     GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_TIM4);

Настраиваем таймер в режим работы ШИМ:

     TIM_TimeBaseStructInit(&timer);
     timer.TIM_ClockDivision = TIM_CKD_DIV1;
     timer.TIM_CounterMode = TIM_CounterMode_Up;//Счет вверх
     timer.TIM_Prescaler = prescaler;//Предделитель
     timer.TIM_Period = Period;//Период
     TIM_TimeBaseInit(TIM4, &timer);//Записываем настройки в оба таймера
     TIM_TimeBaseInit(TIM3, &timer);

     TIM_OCStructInit(&timerPWM);
     timerPWM.TIM_Pulse = pulse;//Длина импульса
     timerPWM.TIM_OCMode = TIM_OCMode_PWM1;//Выравнивание по границе
     timerPWM.TIM_OutputState = TIM_OutputState_Enable;//активируем выход
     timerPWM.TIM_OCPolarity = TIM_OCPolarity_High;//Импульс это 3.3В

Инициализируем нужные каналы для таймеров, каналы ищем в юзер мануале:

      TIM_OC2Init(TIM3, &timerPWM);
      TIM_OC4Init(TIM4, &timerPWM);

А теперь самое важное, настройка синхронизации. Необходимо сделать один таймер мастером, другой слейвом:

//Настройка синхронизации
     TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_Enable);//Конфигурируем мастера
     TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable);//Задаем режим
     TIM_SelectInputTrigger(TIM3, TIM_TS_ITR3);//Конфигурируем подчиненного,
//используем триггер TIM4 канал 4, но нумерация с нуля, поэтому TIM_TS_ITR3
     TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Gated);//режим стробирования

Если нам нужно сделать отставание, допустим на пол периода, то необходимо задать начальное значение счета подчиненного таймера равное половине периода. Для полной синхронизации счет начинаем с 0:

      TIM_SetCounter(TIM3, 10);//Период 20, поэтому чтоб начать с половины пишем 10
//отставание в четверть это 5, в три четверти это 15

Активируем таймеры:

     TIM_Cmd(TIM3, ENABLE);//Обязательно сначала слейв
     TIM_Cmd(TIM4, ENABLE);//А потом уже мастер

Выкладываю весь код полностью:

#include "stm32l1xx.h"

//Для ШИМ
const uint32_t Period = 20 - 1;//переменная периода
const uint32_t prescaler = 1 - 1;
const uint32_t pulse = 15;

void initPWM()
{
     GPIO_InitTypeDef port;
     TIM_TimeBaseInitTypeDef timer;
     TIM_OCInitTypeDef timerPWM;
  
//активация переферии
     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

//   настройка выхода PB5, PB9
     GPIO_StructInit(&port);
     port.GPIO_Mode = GPIO_Mode_AF;
     port.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_9;
     port.GPIO_Speed = GPIO_Speed_2MHz;
     port.GPIO_OType = GPIO_OType_PP;
     GPIO_Init(GPIOB, &port);
    
//активация альтернативной функции выхода PB5
     GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_TIM3);
//активация альтернативной функции выхода PB9
     GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_TIM4);
     
//настройка таймера     
     TIM_TimeBaseStructInit(&timer);
     timer.TIM_ClockDivision = TIM_CKD_DIV1;
     timer.TIM_CounterMode = TIM_CounterMode_Up;
     timer.TIM_Prescaler = prescaler;
     timer.TIM_Period = Period;
     TIM_TimeBaseInit(TIM4, &timer);
     TIM_TimeBaseInit(TIM3, &timer);
 
     TIM_OCStructInit(&timerPWM);
     timerPWM.TIM_Pulse = pulse;
     timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
     timerPWM.TIM_OutputState = TIM_OutputState_Enable;
     timerPWM.TIM_OCPolarity = TIM_OCPolarity_High; 
     TIM_OC2Init(TIM3, &timerPWM);
     TIM_OC4Init(TIM4, &timerPWM);
//Настройка синхронизации
     TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_Enable);//Конфигурируем мастера
     TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable);
     TIM_SelectInputTrigger(TIM3, TIM_TS_ITR3);//Конфигурируем подчиненного
     TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Gated);
     
     TIM_SetCounter(TIM3, 10);
//     TIM_Cmd(TIM4, ENABLE);
     TIM_Cmd(TIM3, ENABLE);
     TIM_Cmd(TIM4, ENABLE);
}

int main()
{
    initPWM();
    while(1)
    {
         
    }
}

Вот собственно и все. В моей задаче нужно было управлять двумя шаговыми двигателями. Дополнительно еще писался код для плавного разгона, начинал работу с более длинным периодом и постепенно его уменьшал функцией TIM_SetAutoreload.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 44

    0
    … так а в чем сложность?
      –1
      Для того чтобы это написать нужно пролистать 900 страниц reference manual'a и кучу профильных тем на форумах.
        +2
        так это не сложность синхронизации, а «особенности» камня и доков ))
          +1

          что? описание таймеров на 900листах? незнаю что это за мануал но по содержанию rm0038 таймера 2-5 с 383 по 443 странички

            –1
            Немного преувеличил. В любом случае для новичка это будет достаточно сложная задача.
          0
          Ни в чем. Самое интересное, что автор сам выдумал проблему. Дело в том, что все таймеры через шины тактируются от одного PLL, то есть у них единый источник, а значит оно априори синхронизировано.
          Включение таймеров последовательно — служит для других целей, но ни как не для «синхронизации». Долго смеялся с вот этой фразы:
          Для того чтобы это написать нужно пролистать 900 страниц reference manual'a и кучу профильных тем на форумах.

          На деле достаточно просто понимать архитектуру железки и ее устройство + прочитать app note на систему тактирования (RCC). И то, последние стоит читать если действительно надо включить таймеры последовательно.
            +1
            Включение таймеров последовательно — служит для других целей, но ни как не для «синхронизации»

            Вот тут сейчас как-то сложно было. Источник тактовой частоты у таймеров конечно в каком-то смысле один, но если вы просто запустите сначала один таймер, а затем другой — то фронты у них разъедутся. И одновременно вы их не запустите, так как запуск двух таймеров требует записи в два разных регистра.
              –4
              Фронты разъезжаются на китайских осциллографах до 100 тыс. руб., ибо синхронизация там просто убогая. Сравнивать синхронизацию, а тем более говорить о разнице, вносимой записью в регистр, погрешности, смотря в rigol/hantek/owon и прочий треш — это попросту глупо.

              Кстати, какая там задержка будет если регистрами стартануть таймеры? Пару тактов? Уверены, что включая последовательно этой задержки нет? А чем вы ее измерили? Осциллограмму можно сразу для пруфа?
                –1
                Хотелось бы увидеть ваш пруф на это:
                Фронты разъезжаются на китайских осциллографах до 100 тыс. руб., ибо синхронизация там просто убогая.

                В описанном мной алгоритме при включении слейва
                TIM_Cmd(TIM3, ENABLE);

                Если в это время посмотреть на осциллограф не включая мастера на ножке ничего не будет. При включении мастера тактирование пойдет сразу на обе ножки без задержек.
                  –1
                  Вы не умеете работать с документацией, ибо если действительно хотелось бы нормальным средством победить наличие задержки в пару тактов, то настроили бы событие триггера для таймера, а не изобретали бы велосипед…
                    0
                    Это не велосипед, а пример синхронного запуска таймеров из reference manual, приложение A.8.19.

                    А автор-то молодец, утёр нос профессиональным инженерам))
                      0
                      1) Ох… Тяжело то как с людьми, которые не могут нормально работать с документацией. Данную задачу решить можно в разы проще, если нужна просто синхронизация. Все что избыточно — велосипед.

                      2) А кто такие «профессиональные инженеры» то? Обычно такие глупые звания себе и другим только любители выдумывают. Запуск таймера на SPL — это определенно заслуга :D
                        +2
                        Илья, я давно вам хотел сказать одну штуку, только вы не обижайтесь. Вы когда узнаёте для себя что-то новое, то если вы прилюдно скажете «ничего себе, а я и не знал» — то никто за вами не будет бегать с криками «фулох, не знал, не знал, ахаха». Что в этой теме, что в прошлой дискуссии про вход BKIN — вы почему-то постоянно пытаетесь доказать, что «это не нужно» только на основании того, что вы эту фичу не использовали. Мы тут все общаемся для взаимного обмена опытом, автор поделился своим решением, совершенно штатным для данного микроконтроллера, если можете предложить лучшее решение — запостите свой пример кода, и мы оценим все преимущества и недостатки. А постоянно повторять «не можете нормально работать с документацией, можно решить в разы проще» — это неконструктивно.
                          0
                          Уважаемый, вы путаете информацию из документации и что-то действительно новое. Вот так выглядит интересная и новая информация — так. Небольшое исследование за пределы документации с реальным практическим результатом. Вот это «вау!», а не настройка таймера на SPL и еще с пометкой «туториал».

                          И вот в этой пометке есть главная проблема. Туториал — это учебная информация, проверенная и построенная на весомом опыте, а не абстрактные мысли автора в стиле «я так думаю». Тем более еще и показан способ, который реализован через изобретение велосипеда.

                          P.S. Ссылка на лурк вообще верх интеллектуальности оппонента.
                            0
                            Тем более еще и показан способ, который реализован через изобретение велосипеда.

                            Я уже участвовал в этой дискуссии парой постов выше.
                      +2
                      А можно еще раз пояснить.
                      Автор предлагает для синхронного пуска использовать связку мастер-слейв.
                      А вы что предлагаете? Я пока увидел только:
                      1. Забить на пару тактов синхронизации.
                      2. Сделать старт таймеров по событию.
                      Так?
                        0
                        У любого таймера есть запуск по триггеру. Этот триггер настраивается прекрасно и на него можно много чего завести. Есть несколько вариантов развития событий:
                        1) Зачем ШИМ? Наверное чтобы сделать какой-то преобразователь, например, buck. Тогда мы после первого измерения АЦП мы может выставить флаг окончания преобразования и от него синхронно запустятся два предварительно настроенных таймера
                        2) ШИМ запускаем ради ШИМ, как в примере. При запуске одной таймера можно включить генерацию события по старту и его направить на триггер второго таймера (TRIGO вход или как-то так, могу ошибаться)
                        3) Самый прикольный вариант. В 99% случаев нам не страшная рассинхронизация в 2 такта, которая появляется из-за того, что сначала включаем один таймер, потом второй. Даже в dc/dc до 100 кГц это не страшно и не заметно. И что интересно осциллографом дешевым не измерить рассинхронизацию в десяток наносекунд, т.к. синхронизация самого осциллографа идет только по одному каналу, а второй соответственно отстает на собственную задержку осциллографа (обычно на время 1-го преобразования).
                          –1
                          Не пойму, почему вы постоянно пишете про синхронизацию осциллографа. Синхронизация не имеет никакого отношения к оцифровке сигнала, её вообще можно выключить, нажать кнопку «стоп», и спокойно увидеть сдвиг фронтов. При скорости оцифровки 1-2Gs/s сдвиг даже на один такт будет прекрасно виден. А если вы включаете таймеры не кодом на ассемблере, то сразу начинается игра в «угадай, за сколько тактов выполнится эта строчка, если оптимизатор настроен на минимизацию размера кода».
                            –1
                            Ну вот мы и пришли к вашему узкому кругозору, а отсюда и к не понимаю. Зачем ассемблер? Он у вас ассоциируется с «крутостью»? Тогда открою секрет — есть такая штука как bit banding, установка бита это исполнение одной инструкции. И казалось бы при чем тут:
                            «угадай, за сколько тактов выполнится эта строчка, если оптимизатор настроен на минимизацию размера кода»

                            Что касается осциллографа… В том то и дело, что вы увидите на дешевом осциле сдвиг даже если таймеры идеально синхронизированы. Увы, но семплы не единственный параметр осциллографа и даже не самый важный. На 1 Гсемпл может быть какой-нибудь rigol, а может быть agilent/keysight и за счет разного уровня аналогового фронтенда, да и методов обработки — это два очень разных устройства.
                            0
                            синхронизация самого осциллографа идет только по одному каналу, а второй соответственно отстает на собственную задержку осциллографа (обычно на время 1-го преобразования).

                            Вот тут я не понял. Всегда думал, что преобразования в цифровых осциллографах идут постоянно, независимо от синхронизации. Причем синхронно по всем каналам. А синхронизация просто задает позицию вывода на экран в в кольцевом буфере накопленных значений.
              0
              del
                +1

                У вас в коде используется SPL? Разве он уже не Deprecated?

                  0
                  Аккуратней — сейчас набегут противники HAL'а.
                    0

                    Так я сам ненавижу это поделие индусов, но ведь есть STM32 Low Level Library

                    0
                    Начал изучать SPL поэтому в нем и сделал. Столько споров кругом что и не поймешь что лучше…
                      0

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


                      Поэтому рекомендуют использовать самое крайнее, что есть. Ну, за исключением HAL (см. выше), его не стоит использовать ни в каком виде, кроме случая "побыстрому написать на коленке за пять минут", потому что для всего остального он отвратителен

                        0
                        Что же взять на вооружение начинающему, но пытливому embedded-программисту для освоения STM32?
                          +1

                          Опять же, зависит от целей:


                          1. Если вам надо нарисовать скетч "а-ля ардуино" за минимально возможное время и вам плевать на просадку по производительности/перерасход памяти/неэффективный и порой откровенно ужасный код библиотеки, то берете STM32 HAL
                          2. Если вы полагаете, что эмбеддед — это что-то посерьезнее, чем "мигание лампочкой в цикле", то значит догадываетесь, что почти ни одна библиотека не спасет вас от необходимости изучать нижележащее железо, и лучше всего придумать синонимы для регистровых операций ближе к нативному языку. Это делает STM32 Low Level Library
                          3. Если "проприетарные" поделки для вас — ничто, и подавай только хардкорный опенсорц, не взирая на возможные баги (впрочем они есть везде) — то для вас libopencm3. Upd: был полезен, пока не вышел пункт 2
                          4. Если вы берете великомощный камень (типа STM32F429, STM32F7xx, i.MX RT 1020/1050), и не собираетесь заниматься на нем хард-реалтаймом, то можно взять какую-нибудь RTOS с собственной абстракцией от железа, к примеру NuttX.

                          Внимание — то, что чип заявлен в поддержке RTOS еще не гарантирует, что там реализовано буквально весь функционал камня, бывают и откровенно смешные недоработки. К примеру для TI Tiva C не было реализовано изменение baudrate для UART в рантайме. Патч занимал 40-50 строк, но пришлось писать и коммитить его в репозиторий)

                            0
                            У LL за который вы агитируете есть 2 минуса:
                            1) Данная библиотека есть далеко не на всю периферию. Если нужно понять что-то сложнее SPI, то опять вспоминаем или HAL или CMSIS.
                            2) LL такой треш, что и HAL. Да, в нем меньше мусора, он компактнее, быстрее и понятна его архитектура, но все равно это треш))

                            Все таки если человек хочет заниматься разработкой фирмваре достаточно серьезно, то ему стоит написать свою минималистичную библиотеку на С++, использую constexpr и обернуть каждую периферию в свой класс. На выходе получим высокопроизводительный low level с хорошей читаемость без необходимости обращаться постоянно к регистрам и документации.
                              0

                              Ну я сейчас больше по С2000, нежели Cortex M, а там большая часть HAL — это просто Сишные структуры с битовыми полями, а компилятор застрял на С++03. Грустно, но что поделаешь, железо слишком крутое, чтобы из-за него перебираться на кортексы.

                                0
                                Сейчас С++11 начал нормально поддерживать и даже 14й, по крайней мере то, что я использую — работает. Правда эта поддержка появилась где-то год назад, до этого приходилось страдать.
                                У ST странная политика, они делают бесспорно очень крутое железо, но ни сделали свою ide (слава богу хоть Attolic купили) и ни одну библиотеку не довели до ума, вечно что-то новое «изобретают».
                                Радует, что tms-ы таки народ использует у нас! Я тоже в основном с С6674 и С2000 работаю, иногда создается впечатление, что на всю страну их человек 50 максимум используют)
                      0
                      Сходить на сайт st.com и посмотреть?
                      SPL поддерживается практически для всей линейки, ну разве кроме STM32F7xx
                      И для некоторых моделей МК, типа 303, SPL «зарыли» достаточно глубоко, и с размаху не видно. Но статус Active присутствует, и вроде как никто отменять не собирается…
                        0
                        Еще H7 забыли)) Вообще SPL прекратили поддерживать, значит он уже мертв. Просто библиотеки и фреймворки не умирают мгновенно, они живут как раз за счет того, что многие предыдущие наработки завязаны на них. Насколько я понял именно это имели ввиду по «смертью».
                          0
                          Официально объявлено о прекращении поддержки?
                          Безусловно, ST «соорудили» конвертер SPL в LL. Но, есть один «маленький» затык. Работа с USB только HAL… Да и как то не очень активно народ переползает с SPL на LL, ибо всё равно остаётся какая то часть «ручной» работы. Тогда уж взад на CMSIS. И легче портировать например на TI и ли NXP. ( б-р-р )
                            0
                            Много чего только на CMSIS и HAL, например, работа с графикой, сетью, тот же usb и ряд других сложных периферий. А вообще у меня нет знакомых разработчиков, кто использует SPL или HAL, все как-то на cmsis или на своих библиотеках. Библиотеки от st больше как-то любители и diy-щики используют активно.
                      0
                      На осциллографе вижу 104кГц, а сколько максимум?
                      Почему фронт сигнала неровный?
                      P.S. За статью спасибо (плюсов нет у меня).
                        +1
                        Все зависит от источника тактирования. Если перейти на шину HSI с 16мГц, то при таких же настройках предделителя и периода будет 798кГц. В примере шина MSI с частотой 2.09Мгц. На максимум не пытался выходить.
                        Сигнал не ровный, потому что цеплял 2 крокодильчика от осциллографа на 1 ножку GND микроконтроллера.
                        P.S. рад что статья оказалась полезной.
                        +2
                        У самого подобной задачи на STM32 ещё не было, но неужели там все на столько сложно!? У той же Xmega есть модуль Awex, который запускаешь и настраиваешь значение мертвого времени и частоту(для данного случая мертвое время было бы равно 0). Мне кажется либо есть вариант сделать проще, либо вы все усложняете.
                          +2
                          В STM32 все так же понятно и прозрачно, если не изобретать велосипедов. Если интересует ШИМ, то советую посмотреть примеры для STM32F3348-Disco. Там весьма наглядно продемонстрировано построение и управление buck преобразователем и работа с ШИМ. Разницы глобальной между HRPWM и обычным PWM нет, суть такая же.
                            0
                            Не подскажите дельным советом? ШИМ, 3 моста (3-х фазный синус). Использую ДМА, пакетную передачу. Настроен сигнал BREAK, сигнал положительный. При подачи на ножку Брейка единицы происходить прерывание, но шим продолжает работать, что я сделал не так?
                            TIM1->DIER |= TIM_DIER_UDE | TIM_DIER_BIE;
                              +1
                              Залейте весь код по инициализации таймера сюда и скиньте в личку. Я дома вечером гляну, может и подскажу чего полезного.
                              BK вход еще нужно отдельно включить + для начала нормальной работы при включение на входе BK для начала должен быть лог. 0, после инициализации уже ошибка может приходить. То есть логика какая: стартуем МК -> ждем окончание всей инициализации -> ждем еще немного (0.1 с, например) -> стартуем работу таймера.
                          +1
                          Этот осциллограф, судя по наличию USB-порта, умеет самостоятельно сохранять скриншоты экрана (и сами данные), в общем не нужно было мобилой его фоткать…
                            0
                            А неужели одного меня смущает соседство SPL и вот этого:
                            TIM3->CNT = 10;
                            Если уж взяли SPL, то почему не использовать TIM_SetCounter?
                              0
                              Спасибо за замечание, поправил

                            Only users with full accounts can post comments. Log in, please.