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

Создадим функцию для инициализации 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.

Настраиваем выходы в режим альтернативной фунции:
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:
TIM3->CNT = 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);
TIM3->CNT = 5;
// TIM_Cmd(TIM4, ENABLE);
TIM_Cmd(TIM3, ENABLE);
TIM_Cmd(TIM4, ENABLE);
}
int main()
{
initPWM();
while(1)
{
}
}
Вот собственно и все. В моей задаче нужно было управлять двумя шаговыми двигателями. Дополнительно еще писался код для плавного разгона, начинал работу с более длинным периодом и постепенно его уменьшал функцией TIM_SetAutoreload.
Комментарии (42)
Dima_Sharihin
28.06.2018 15:18У вас в коде используется SPL? Разве он уже не Deprecated?
d_suslov
28.06.2018 16:16Аккуратней — сейчас набегут противники HAL'а.
Dima_Sharihin
28.06.2018 16:45Так я сам ненавижу это поделие индусов, но ведь есть STM32 Low Level Library
kovaltsov Автор
28.06.2018 16:31Начал изучать SPL поэтому в нем и сделал. Столько споров кругом что и не поймешь что лучше…
Dima_Sharihin
28.06.2018 16:46Не, тут просто: если производителю стало лень нянчиться со своей же библиотекой периферии, это значит, что для новых МК этой библиотеки не будет вовсе, а на исправление косяков в старой никто не будет тратить время и силы.
Поэтому рекомендуют использовать самое крайнее, что есть. Ну, за исключением HAL (см. выше), его не стоит использовать ни в каком виде, кроме случая "побыстрому написать на коленке за пять минут", потому что для всего остального он отвратителен
asakasinsky
28.06.2018 23:58Что же взять на вооружение начинающему, но пытливому embedded-программисту для освоения STM32?
Dima_Sharihin
29.06.2018 07:56+1Опять же, зависит от целей:
- Если вам надо нарисовать скетч "а-ля ардуино" за минимально возможное время и вам плевать на просадку по производительности/перерасход памяти/неэффективный и порой откровенно ужасный код библиотеки, то берете STM32 HAL
- Если вы полагаете, что эмбеддед — это что-то посерьезнее, чем "мигание лампочкой в цикле", то значит догадываетесь, что почти ни одна библиотека не спасет вас от необходимости изучать нижележащее железо, и лучше всего придумать синонимы для регистровых операций ближе к нативному языку. Это делает STM32 Low Level Library
- Если "проприетарные" поделки для вас — ничто, и подавай только хардкорный опенсорц, не взирая на возможные баги (впрочем они есть везде) — то для вас libopencm3. Upd: был полезен, пока не вышел пункт 2
- Если вы берете великомощный камень (типа STM32F429, STM32F7xx, i.MX RT 1020/1050), и не собираетесь заниматься на нем хард-реалтаймом, то можно взять какую-нибудь RTOS с собственной абстракцией от железа, к примеру NuttX.
Внимание — то, что чип заявлен в поддержке RTOS еще не гарантирует, что там реализовано буквально весь функционал камня, бывают и откровенно смешные недоработки. К примеру для TI Tiva C не было реализовано изменение baudrate для UART в рантайме. Патч занимал 40-50 строк, но пришлось писать и коммитить его в репозиторий)
NordicEnergy
29.06.2018 10:33У LL за который вы агитируете есть 2 минуса:
1) Данная библиотека есть далеко не на всю периферию. Если нужно понять что-то сложнее SPI, то опять вспоминаем или HAL или CMSIS.
2) LL такой треш, что и HAL. Да, в нем меньше мусора, он компактнее, быстрее и понятна его архитектура, но все равно это треш))
Все таки если человек хочет заниматься разработкой фирмваре достаточно серьезно, то ему стоит написать свою минималистичную библиотеку на С++, использую constexpr и обернуть каждую периферию в свой класс. На выходе получим высокопроизводительный low level с хорошей читаемость без необходимости обращаться постоянно к регистрам и документации.Dima_Sharihin
29.06.2018 10:43Ну я сейчас больше по С2000, нежели Cortex M, а там большая часть HAL — это просто Сишные структуры с битовыми полями, а компилятор застрял на С++03. Грустно, но что поделаешь, железо слишком крутое, чтобы из-за него перебираться на кортексы.
NordicEnergy
29.06.2018 10:49Сейчас С++11 начал нормально поддерживать и даже 14й, по крайней мере то, что я использую — работает. Правда эта поддержка появилась где-то год назад, до этого приходилось страдать.
У ST странная политика, они делают бесспорно очень крутое железо, но ни сделали свою ide (слава богу хоть Attolic купили) и ни одну библиотеку не довели до ума, вечно что-то новое «изобретают».
Радует, что tms-ы таки народ использует у нас! Я тоже в основном с С6674 и С2000 работаю, иногда создается впечатление, что на всю страну их человек 50 максимум используют)
gopotyr
29.06.2018 08:36Сходить на сайт st.com и посмотреть?
SPL поддерживается практически для всей линейки, ну разве кроме STM32F7xx
И для некоторых моделей МК, типа 303, SPL «зарыли» достаточно глубоко, и с размаху не видно. Но статус Active присутствует, и вроде как никто отменять не собирается…NordicEnergy
29.06.2018 10:35Еще H7 забыли)) Вообще SPL прекратили поддерживать, значит он уже мертв. Просто библиотеки и фреймворки не умирают мгновенно, они живут как раз за счет того, что многие предыдущие наработки завязаны на них. Насколько я понял именно это имели ввиду по «смертью».
gopotyr
29.06.2018 23:42Официально объявлено о прекращении поддержки?
Безусловно, ST «соорудили» конвертер SPL в LL. Но, есть один «маленький» затык. Работа с USB только HAL… Да и как то не очень активно народ переползает с SPL на LL, ибо всё равно остаётся какая то часть «ручной» работы. Тогда уж взад на CMSIS. И легче портировать например на TI и ли NXP. ( б-р-р )NordicEnergy
30.06.2018 11:09Много чего только на CMSIS и HAL, например, работа с графикой, сетью, тот же usb и ряд других сложных периферий. А вообще у меня нет знакомых разработчиков, кто использует SPL или HAL, все как-то на cmsis или на своих библиотеках. Библиотеки от st больше как-то любители и diy-щики используют активно.
Costic
28.06.2018 18:55На осциллографе вижу 104кГц, а сколько максимум?
Почему фронт сигнала неровный?
P.S. За статью спасибо (плюсов нет у меня).kovaltsov Автор
29.06.2018 08:23+1Все зависит от источника тактирования. Если перейти на шину HSI с 16мГц, то при таких же настройках предделителя и периода будет 798кГц. В примере шина MSI с частотой 2.09Мгц. На максимум не пытался выходить.
Сигнал не ровный, потому что цеплял 2 крокодильчика от осциллографа на 1 ножку GND микроконтроллера.
P.S. рад что статья оказалась полезной.
Reason89
28.06.2018 20:42У самого подобной задачи на STM32 ещё не было, но неужели там все на столько сложно!? У той же Xmega есть модуль Awex, который запускаешь и настраиваешь значение мертвого времени и частоту(для данного случая мертвое время было бы равно 0). Мне кажется либо есть вариант сделать проще, либо вы все усложняете.
NordicEnergy
29.06.2018 00:14+1В STM32 все так же понятно и прозрачно, если не изобретать велосипедов. Если интересует ШИМ, то советую посмотреть примеры для STM32F3348-Disco. Там весьма наглядно продемонстрировано построение и управление buck преобразователем и работа с ШИМ. Разницы глобальной между HRPWM и обычным PWM нет, суть такая же.
imperror
29.06.2018 11:46Не подскажите дельным советом? ШИМ, 3 моста (3-х фазный синус). Использую ДМА, пакетную передачу. Настроен сигнал BREAK, сигнал положительный. При подачи на ножку Брейка единицы происходить прерывание, но шим продолжает работать, что я сделал не так?
TIM1->DIER |= TIM_DIER_UDE | TIM_DIER_BIE;NordicEnergy
29.06.2018 11:52+1Залейте весь код по инициализации таймера сюда и скиньте в личку. Я дома вечером гляну, может и подскажу чего полезного.
BK вход еще нужно отдельно включить + для начала нормальной работы при включение на входе BK для начала должен быть лог. 0, после инициализации уже ошибка может приходить. То есть логика какая: стартуем МК -> ждем окончание всей инициализации -> ждем еще немного (0.1 с, например) -> стартуем работу таймера.
ryo_oh_ki
30.06.2018 08:34+1Этот осциллограф, судя по наличию USB-порта, умеет самостоятельно сохранять скриншоты экрана (и сами данные), в общем не нужно было мобилой его фоткать…
Ostrovv
… так а в чем сложность?
kovaltsov Автор
Для того чтобы это написать нужно пролистать 900 страниц reference manual'a и кучу профильных тем на форумах.
Ostrovv
так это не сложность синхронизации, а «особенности» камня и доков ))
FGV
что? описание таймеров на 900листах? незнаю что это за мануал но по содержанию rm0038 таймера 2-5 с 383 по 443 странички
kovaltsov Автор
Немного преувеличил. В любом случае для новичка это будет достаточно сложная задача.
NordicEnergy
Ни в чем. Самое интересное, что автор сам выдумал проблему. Дело в том, что все таймеры через шины тактируются от одного PLL, то есть у них единый источник, а значит оно априори синхронизировано.
Включение таймеров последовательно — служит для других целей, но ни как не для «синхронизации». Долго смеялся с вот этой фразы:
На деле достаточно просто понимать архитектуру железки и ее устройство + прочитать app note на систему тактирования (RCC). И то, последние стоит читать если действительно надо включить таймеры последовательно.
SergeyMax
Вот тут сейчас как-то сложно было. Источник тактовой частоты у таймеров конечно в каком-то смысле один, но если вы просто запустите сначала один таймер, а затем другой — то фронты у них разъедутся. И одновременно вы их не запустите, так как запуск двух таймеров требует записи в два разных регистра.
NordicEnergy
Фронты разъезжаются на китайских осциллографах до 100 тыс. руб., ибо синхронизация там просто убогая. Сравнивать синхронизацию, а тем более говорить о разнице, вносимой записью в регистр, погрешности, смотря в rigol/hantek/owon и прочий треш — это попросту глупо.
Кстати, какая там задержка будет если регистрами стартануть таймеры? Пару тактов? Уверены, что включая последовательно этой задержки нет? А чем вы ее измерили? Осциллограмму можно сразу для пруфа?
kovaltsov Автор
Хотелось бы увидеть ваш пруф на это:
В описанном мной алгоритме при включении слейва
Если в это время посмотреть на осциллограф не включая мастера на ножке ничего не будет. При включении мастера тактирование пойдет сразу на обе ножки без задержек.
NordicEnergy
Вы не умеете работать с документацией, ибо если действительно хотелось бы нормальным средством победить наличие задержки в пару тактов, то настроили бы событие триггера для таймера, а не изобретали бы велосипед…
SergeyMax
Это не велосипед, а пример синхронного запуска таймеров из reference manual, приложение A.8.19.
А автор-то молодец, утёр нос профессиональным инженерам))
NordicEnergy
1) Ох… Тяжело то как с людьми, которые не могут нормально работать с документацией. Данную задачу решить можно в разы проще, если нужна просто синхронизация. Все что избыточно — велосипед.
2) А кто такие «профессиональные инженеры» то? Обычно такие глупые звания себе и другим только любители выдумывают. Запуск таймера на SPL — это определенно заслуга :D
SergeyMax
Илья, я давно вам хотел сказать одну штуку, только вы не обижайтесь. Вы когда узнаёте для себя что-то новое, то если вы прилюдно скажете «ничего себе, а я и не знал» — то никто за вами не будет бегать с криками «фулох, не знал, не знал, ахаха». Что в этой теме, что в прошлой дискуссии про вход BKIN — вы почему-то постоянно пытаетесь доказать, что «это не нужно» только на основании того, что вы эту фичу не использовали. Мы тут все общаемся для взаимного обмена опытом, автор поделился своим решением, совершенно штатным для данного микроконтроллера, если можете предложить лучшее решение — запостите свой пример кода, и мы оценим все преимущества и недостатки. А постоянно повторять «не можете нормально работать с документацией, можно решить в разы проще» — это неконструктивно.
NordicEnergy
Уважаемый, вы путаете информацию из документации и что-то действительно новое. Вот так выглядит интересная и новая информация — так. Небольшое исследование за пределы документации с реальным практическим результатом. Вот это «вау!», а не настройка таймера на SPL и еще с пометкой «туториал».
И вот в этой пометке есть главная проблема. Туториал — это учебная информация, проверенная и построенная на весомом опыте, а не абстрактные мысли автора в стиле «я так думаю». Тем более еще и показан способ, который реализован через изобретение велосипеда.
P.S. Ссылка на лурк вообще верх интеллектуальности оппонента.
SergeyMax
Я уже участвовал в этой дискуссии парой постов выше.
andi123
А можно еще раз пояснить.
Автор предлагает для синхронного пуска использовать связку мастер-слейв.
А вы что предлагаете? Я пока увидел только:
1. Забить на пару тактов синхронизации.
2. Сделать старт таймеров по событию.
Так?
NordicEnergy
У любого таймера есть запуск по триггеру. Этот триггер настраивается прекрасно и на него можно много чего завести. Есть несколько вариантов развития событий:
1) Зачем ШИМ? Наверное чтобы сделать какой-то преобразователь, например, buck. Тогда мы после первого измерения АЦП мы может выставить флаг окончания преобразования и от него синхронно запустятся два предварительно настроенных таймера
2) ШИМ запускаем ради ШИМ, как в примере. При запуске одной таймера можно включить генерацию события по старту и его направить на триггер второго таймера (TRIGO вход или как-то так, могу ошибаться)
3) Самый прикольный вариант. В 99% случаев нам не страшная рассинхронизация в 2 такта, которая появляется из-за того, что сначала включаем один таймер, потом второй. Даже в dc/dc до 100 кГц это не страшно и не заметно. И что интересно осциллографом дешевым не измерить рассинхронизацию в десяток наносекунд, т.к. синхронизация самого осциллографа идет только по одному каналу, а второй соответственно отстает на собственную задержку осциллографа (обычно на время 1-го преобразования).
SergeyMax
Не пойму, почему вы постоянно пишете про синхронизацию осциллографа. Синхронизация не имеет никакого отношения к оцифровке сигнала, её вообще можно выключить, нажать кнопку «стоп», и спокойно увидеть сдвиг фронтов. При скорости оцифровки 1-2Gs/s сдвиг даже на один такт будет прекрасно виден. А если вы включаете таймеры не кодом на ассемблере, то сразу начинается игра в «угадай, за сколько тактов выполнится эта строчка, если оптимизатор настроен на минимизацию размера кода».
NordicEnergy
Ну вот мы и пришли к вашему узкому кругозору, а отсюда и к не понимаю. Зачем ассемблер? Он у вас ассоциируется с «крутостью»? Тогда открою секрет — есть такая штука как bit banding, установка бита это исполнение одной инструкции. И казалось бы при чем тут:
Что касается осциллографа… В том то и дело, что вы увидите на дешевом осциле сдвиг даже если таймеры идеально синхронизированы. Увы, но семплы не единственный параметр осциллографа и даже не самый важный. На 1 Гсемпл может быть какой-нибудь rigol, а может быть agilent/keysight и за счет разного уровня аналогового фронтенда, да и методов обработки — это два очень разных устройства.
Andy_Big
Вот тут я не понял. Всегда думал, что преобразования в цифровых осциллографах идут постоянно, независимо от синхронизации. Причем синхронно по всем каналам. А синхронизация просто задает позицию вывода на экран в в кольцевом буфере накопленных значений.