Complementary PWM и Push-Pull режимы на таймере 1 контроллеров STM32

Итак (орфография оригинала сохранена):
Да ладно. По фазе оно сколочно гвоздями краями или серединками и ничего с этим не сделаешь.
Попробуем что-нибудь сделать.
Для начала посмотрим, что нам вообще требуется. Я нарисовал картинку попонятнее, чтобы было яснее видно, в чем проблема.
В комплементарном режиме второй сигнал представляет собой инверсию первого, укороченную с учетом продолжительности защитного интервала (deadtime).
В двухтактном режиме сигналы выровнены друг относительно друга и изменяют коэффициент заполнения синхронно.
Теперь к реализации. Для проверки идей я использовал плату STM32-vldiscovery, базирующуюся на контроллере STM32F100RBT6B — datasheet, reference manual.
Комплементарный режим получается на одном канале. Чтобы получить такой сигнал, достаточно включить комплементарный канал (CC1NE в регистре CCER) и задать защитный интервал (константа DEADTIME, записываемая в младшие биты BDTR):
//CH1: PWM mode with complementary output & deadtime
TIM1->CCMR1=TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE;
TIM1->CCER=TIM_CCER_CC1E | TIM_CCER_CC1NE;
TIM1->BDTR=TIM_BDTR_MOE | DEADTIME;
TIM1->CCR1=PWM_VALUE;
TIM1->ARR=TMR_T;
TIM1->CR1=TIM_CR1_ARPE;
TIM1->CR1|=TIM_CR1_CEN;
TIM1->EGR=TIM_EGR_UG;
Результат наблюдаем на выводах PA8 (прямой канал) и PB13 (комплементарный канал):
D<50%:

D>50%:

Теперь самое интересное: двухтактный режим! Увы, на одном канале его, действительно, реализовать нельзя. Для этого нужно использовать два канала из четырех доступных в таймере 1. Таймер при этом должен работать в режиме, который
Первый канал сравнения надо инициализировать в режиме PWM 2, а второй — в режиме PWM 1 (или наоборот). Это и даст интересующий сдвиг сигналов по фазе. Ну а дальше включаем синхронное обновление значений регистров (OC1PE/OC2PE), сами выходы (CC1E/CC2E), и так далее. Самое главное — не забыть про MOE, без этого ничего не будет работать.

Простите, отвлекся.
Код инициализации таймера:
//CH1: PWM mode 2, CH2: PWM mode 1, preload enabled on all channels
TIM1->CCMR1=TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1PE | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE;
TIM1->CCER=TIM_CCER_CC1E | TIM_CCER_CC2E;
TIM1->BDTR=TIM_BDTR_MOE;
TIM1->CCR1=TMR_T - PWM_VALUE;
TIM1->CCR2=PWM_VALUE;
TIM1->ARR=TMR_T;
TIM1->CR1=TIM_CR1_ARPE | TIM_CR1_CMS_1 | TIM_CR1_CMS_0;
TIM1->CR1|=TIM_CR1_CEN;
TIM1->EGR=TIM_EGR_UG;
Результат виден на выводах PA8 (первый канал) и PA9 (второй канал).
D<25%:

D>25%:

Полный код тестового проекта:
#include "stm32f10x.h"
#include "stm32_ports.h"
#define TIM1_CH1N_PB 13
#define TIM1_CH1_PA 8
#define TIM1_CH2_PA 9
#define LED1_G_PC 9
#define LED2_B_PC 8
#define PWM_VALUE 20
#define TMR_T 200
#define DEADTIME 20
#define PP_MODE
//#define COMPL_MODE
void main(void)
{
RCC->APB2ENR|=RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_TIM1EN;
GPIOA->CRH=SET_CRH(TIM1_CH1_PA,M_OUT_50M,OUT_AF_PP) | SET_CRH(TIM1_CH2_PA,M_OUT_50M,OUT_AF_PP);
GPIOB->CRH=SET_CRH(TIM1_CH1N_PB,M_OUT_50M,OUT_AF_PP);
GPIOC->CRH=SET_CRH(LED1_G_PC,M_OUT_50M,OUT_GP_PP) | SET_CRH(LED2_B_PC,M_OUT_50M,OUT_GP_PP);
#ifdef PP_MODE
//CH1: PWM mode 2, CH2: PWM mode 1, preload enabled on all channels
TIM1->CCMR1=TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1PE | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE;
TIM1->CCER=TIM_CCER_CC1E | TIM_CCER_CC2E;
TIM1->BDTR=TIM_BDTR_MOE;
TIM1->CCR1=TMR_T - PWM_VALUE;
TIM1->CCR2=PWM_VALUE;
TIM1->ARR=TMR_T;
TIM1->CR1=TIM_CR1_ARPE | TIM_CR1_CMS_1 | TIM_CR1_CMS_0;
TIM1->CR1|=TIM_CR1_CEN;
TIM1->EGR=TIM_EGR_UG;
#endif
#ifdef COMPL_MODE
//CH1: PWM mode with complementary output & deadtime
TIM1->CCMR1=TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE;
TIM1->CCER=TIM_CCER_CC1E | TIM_CCER_CC1NE;
TIM1->BDTR=TIM_BDTR_MOE | DEADTIME;
TIM1->CCR1=PWM_VALUE;
TIM1->ARR=TMR_T;
TIM1->CR1=TIM_CR1_ARPE;
TIM1->CR1|=TIM_CR1_CEN;
TIM1->EGR=TIM_EGR_UG;
#endif
while (1)
{
}
}
- +5
- 07 января 2018, 16:13
- _YS_
У меня есть ощущение, что Lifelover имел ввиду ровно то, что вы показали. Сколочено гвоздями или краями (комплементарный режим) или серединками (пуш-пулл) и ничего с этим не сделать.
Вы знаете, я еще раз подумал над вашими и Lifelover'а словами про края и серединки. Тут, конечно, надо рисовать, чтобы всем все стало понятно. Но если имеется в виду получение совсем произвольного сдвига между каналами (зачем?), то для этого можно сделать следующее (даже не трогая остальные аппаратные возможности):
— настроить прерывание по обнулению (при счете вниз) и переполнению (при счете вверх) таймера;
— в этом прерывании при достижении максимального значения таймера прибавлять значение сдвига к основному значению CCR канала, а при счете вниз и достижении нуля — вычитать.
Таким образом можно получить любой сдвиг.
— настроить прерывание по обнулению (при счете вниз) и переполнению (при счете вверх) таймера;
— в этом прерывании при достижении максимального значения таймера прибавлять значение сдвига к основному значению CCR канала, а при счете вниз и достижении нуля — вычитать.
Таким образом можно получить любой сдвиг.
Т.е. сделать программно… По сути таймер будет выполнять действия «включи по счёту M тактов» и «выключи по счёту N тактов». Для таймера это простые задачи. Хороший пример, который показывает, что когда задача хоть на миллиметр (а такие задачи встречаются гораздо чаще чем некоторые малюют) отходит от стандартного набора, и продвинутая (которая далеко не такая продвинутая, как некоторые малюют) периферия стм проигрывает и начинают выигрывать быстрые прерывания как в AVR и эффективные команды типа sbi/cbi как в AVR.
Ради интереса, когда (и, если не секрет) для чего вам было необходимо получать произвольный сдвиг между каналами ШИМ? :)
Но это ладно. Я выше писал в предположении, что мы используем один таймер. Если разрешить себе использовать два таймера, то все сводится к конфигурации ШИМ-каналов от разных таймеров и задании разных начальных значений для этих двух таймеров. Дальше все будет происходить само по себе.
Надо сказать, что в STM32 это делается гораздо спокойнее, потому что таймеров там, как правило, больше, и потратить два не так жалко. :)
Вместо SBI/CBI в STM32 есть механизм bit-banding (отображение битов на индивидуальные адреса памяти). Для портов там вообще есть специальные регистры атомарной установки/снятия битов.
Не поймите меня правильно, я не продвигаю STM32 (или какую-то другую) архитектуру как абсолют. Если бы какая-то архитектура была абсолютом, то уже давно осталась бы она одна. Просто мы с вами разговариваем именно о STM32 и AVR, потому и примеры на STM32. С тем же успехом я мог бы приводить примеры на MSP430. Просто мы о них пока не говорим.
Кстати, у MSP430 мне как раз таки очень нравится ассемблер. Писать на нем — одно удовольствие! Вот это супер-архитектура для ассемблерного кода. Попробуйте, не пожалеете. Из них можно многое выжать.
А так, контроллер надо выбирать под задачу. В разных задачах разные архитектуры имеют преимущество. Скажем, в AVR мне нравится АЦП, а в STM32 — таймеры, особенно поддержка инкрементального энкодера.
Но это ладно. Я выше писал в предположении, что мы используем один таймер. Если разрешить себе использовать два таймера, то все сводится к конфигурации ШИМ-каналов от разных таймеров и задании разных начальных значений для этих двух таймеров. Дальше все будет происходить само по себе.
Надо сказать, что в STM32 это делается гораздо спокойнее, потому что таймеров там, как правило, больше, и потратить два не так жалко. :)
Вместо SBI/CBI в STM32 есть механизм bit-banding (отображение битов на индивидуальные адреса памяти). Для портов там вообще есть специальные регистры атомарной установки/снятия битов.
Не поймите меня правильно, я не продвигаю STM32 (или какую-то другую) архитектуру как абсолют. Если бы какая-то архитектура была абсолютом, то уже давно осталась бы она одна. Просто мы с вами разговариваем именно о STM32 и AVR, потому и примеры на STM32. С тем же успехом я мог бы приводить примеры на MSP430. Просто мы о них пока не говорим.
Кстати, у MSP430 мне как раз таки очень нравится ассемблер. Писать на нем — одно удовольствие! Вот это супер-архитектура для ассемблерного кода. Попробуйте, не пожалеете. Из них можно многое выжать.
А так, контроллер надо выбирать под задачу. В разных задачах разные архитектуры имеют преимущество. Скажем, в AVR мне нравится АЦП, а в STM32 — таймеры, особенно поддержка инкрементального энкодера.
Пару раз мне хотелось иметь 3-4 фазы ШИМ (между которыми, соответственно, 120 и 90 градусов). Хотя да, не столь уж часто это нужно…
Вместо SBI/CBI в STM32 есть механизм bit-banding (отображение битов на индивидуальные адреса памяти).Уродский механизм, именно после того как я прочитал о нём, моё отношение к стм сменилось на разочарование (впрочем, потом это подтвердилось раз 100). Неудобный (компилятор сам не делает) и костыльный (магические числа). Главное — неэффективынй. Установка бита через RMW — 5 команд (9 тактов ЕМНИП), через битбанд — 3 команды (5 тактов). Быстрый способ, нда… После AVR плеваться хочется.
Для портов там вообще есть специальные регистры атомарной установки/снятия битов.В PIC32 на каждый регистр (SFR) есть по 3 регистра для установки, сброса и инверсии. Мне это решение нравится куда больше битбэнда.
Кстати, у MSP430 мне как раз таки очень нравится ассемблер. Писать на нем — одно удовольствие! Вот это супер-архитектура для ассемблерного кода. Попробуйте, не пожалеете. Из них можно многое выжать.Попробую. Подробно пока не смотрел, но в целом отношение положительное. Мне нравится выбор разрядности (32 бита — очень тяжело для набора команд фиксированной длины, а 16 — гораздо лучше для кодирования команд и весьма неплохо для вычислений и для адресации).
В PIC32 на каждый регистр (SFR) есть по 3 регистра для установки, сброса и инверсии. Мне это решение нравится куда больше битбэнда.У STM32 ровно так же 3 регистра для этих целей.
Главное — неэффективынй. Установка бита через RMW — 5 команд (9 тактов ЕМНИП), через битбанд — 3 команды (5 тактов).Учитывая частоты это вообще не проблема.
Быстрый способ, нда… После AVR плеваться хочется.Особенно учитывая, что у авр ничего похожего даже близко нет.
Большое спасибо за статью, в своей практике с ШИМ-ом сталкиваюсь нечасто, так что знания по различным режимам лишними не будут.
P.S. За MOE отдельный плюс :D
P.S. За MOE отдельный плюс :D
- Misaka10032
- 07 января 2018, 17:06
- ↓
. Я им не пользуюсь, и вам не советую.И напрасно. Куб штука удобная, другой вопрос, что то, что он генерит никуда не годится. Либо HAL, либо, еще хуже, портянки low level типа того, что в топике. Ни то, ни другое не пригодно для использования.
другой вопрос, что то, что он генерит никуда не годится.
Это как раз основной вопрос. :)
Не надо никаких HAL'ов. Если человек может освоить документацию и имеет небольшие навыки программирования, то ему не составит труда писать с использованием регистров.
Например, для FMC я писал небольшой кодогенератор на Lua, который генерировал мне макросы для портов.
Если человек может освоить документацию и имеет небольшие навыки программирования, то ему не составит труда писать с использованием регистров.То есть вместо одних вонючих портянок писать другие вонючие портянки. Выбор, как бы, понятен, но это вовсе не значит, что он правильный или удобный.
Добро пожаловать в низкоуровневое программирование — некоторых прикладников оно шокирует, действительно. Особенно тех, которые не догадываются о том, как все работает под капотом привычных им конструкций на Питоне. :)
Я писал на низком уровне тогда, когда папки и мамки большинства местных обиталей еще только начали проявлять интерес к противоположному полу. А вот эти вот ваши вонючие портянки это не низкоуровневое программирование, а его профанация, попытка превратить непроизводительный труд в некую «илитную» «фишку». На самом деле это всего лишь бессмысленная трата ресурсов мозга программиста. Справедливости ради должен заметить, что С не дает достаточно инструментов, что бы сделать что-нибудь внятное с этими портянками, а изучение С++ для «илиты» выше их возможностей. Ну а те, кто таки его освоил, могут наслаждаться настоящим низкоуровневым программированием.
Куда уж мне там до элиты, я скромный инженер, так что «непроизводительный труд» я не стесняюсь автоматизировать скриптованием. Но, конечно, для того, чтобы написать собственный кодогенератор «на случай», надо понимать, как устроена периферия. Это сложнее, чем копировать примеры HAL или тыкать мышкой в Cube. Потому те, кто пишут на HAL, регулярно приходят ко мне (это на работе, из других отделов), чтобы я им объяснил, почему у них что-то не работает.
Полагаю, вы один из тех немногих людей, которые пишут на Ada (если начинали в такие далекие времена, времена богов и героев). Между прочим да, по показателям надежности (главное для встроенных систем) — это лучший язык. По сравнению с ним что C, что C++ ничего не стоят.
Ну и кстати, расскажите же нам, как сделать то, что я описывал в статье, в Cube? Мне самому интересно, а заново ставить Cube лень.
Полагаю, вы один из тех немногих людей, которые пишут на Ada (если начинали в такие далекие времена, времена богов и героев). Между прочим да, по показателям надежности (главное для встроенных систем) — это лучший язык. По сравнению с ним что C, что C++ ничего не стоят.
Ну и кстати, расскажите же нам, как сделать то, что я описывал в статье, в Cube? Мне самому интересно, а заново ставить Cube лень.
Куда уж мне там до элиты, я скромный инженерЧто не мешает вам вести себя как та самая «илита». Вот характерный пример:
Ну и кстати, расскажите же нам, как сделать то, что я описывал в статье, в Cube?
Во-первых: кто такие «вы» и почему «вам» я что-то должен рассказывать? С чего вдруг, вы решили, что я «вписался» за куб, если выше открытым текстом написал, что то, что он генерит неюзабельно? Наконец, где я писал, что конкретно эту задачу можно сделать в кубе?
Во-первых: кто такие «вы» и почему «вам» я что-то должен рассказывать?
«Мы» — это уважаемая аудитория сообщества, которая, уверен, как и я, не откажется получить крупицу бесценного знания от умудренного опытом старца, годящегося их родителям в деды или, на худой конец, в отцы.
Я не решил, что вы прозелит Cube. Просто я предположил, что годы плодотворной практики расширили ваш кругозор в числе прочего и до блестящего знания возможностей Cube, которым я не обладаю; а я всегда стремлюсь поучиться у знаюшего человека, потому и задал вопрос. Если в Cube это невозможно — то и такую новость я приму со смирением.
Более того, в вашем понимании, если не портянки, то HAL и никаких других вариантов вы не видите.
Вариантов распихивать битовые маски по регистрам существует множество. Можно и до шаблонов дойти. Это смотрится круто…
Выше я писал только о том, что ко мне приходят люди, запутавшиеся в HAL. Люди, запутавшиеся в шаблонах, ко мне не приходили, вот и все.
Учитывая куда ST занесло в плане написания либ, ждать прийдётся очень долго.
- coredumped
- 30 октября 2018, 17:33
- ↑
- ↓
И в один прекрасный день его научат генерировать отличный код.Его самого — врядли. В принципе, задача решаемая, но слишком геморная. Куда проще написать свою генерилку прекрасного кода из кубовского файла проекта. Там примитивный текстовый файл в стиле =и вся необходимая информация, что бы сгенерировать инициализацию, там есть.
RCC->APB2ENR|=RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_TIM1EN;
GPIOA->CRH=SET_CRH(TIM1_CH1_PA,M_OUT_50M,OUT_AF_PP) | SET_CRH(TIM1_CH2_PA,M_OUT_50M,OUT_AF_PP);
GPIOB->CRH=SET_CRH(TIM1_CH1N_PB,M_OUT_50M,OUT_AF_PP);
GPIOC->CRH=SET_CRH(LED1_G_PC,M_OUT_50M,OUT_GP_PP) | SET_CRH(LED2_B_PC,M_OUT_50M,OUT_GP_PP);
У меня почему-то эту часть не хочет компилировать.
А-а-а. Ну так тут используются мои самописные макросы SET_CRH/SET_CRL. Забыл об этом написать в статье, т.к. давно их использую.
Эти макросы не делают ничего волшебного — просто скрывают битовые сдвиги.
Посмотрите в докуменации, как устроен регистр GPIOx->CRH, я наизусть не помню, где там какие битовые поля.
Эти макросы не делают ничего волшебного — просто скрывают битовые сдвиги.
Посмотрите в докуменации, как устроен регистр GPIOx->CRH, я наизусть не помню, где там какие битовые поля.
#ifndef STM32_PORTS_H_INCLUDED
#define STM32_PORTS_H_INCLUDED
#define M_IN 0x00
#define M_OUT_2M 0x02
#define M_OUT_10M 0x01
#define M_OUT_50M 0x03
#define IN_ANALOG 0x00
#define IN_FLOATING 0x01
#define IN_PULLED 0x02
#define OUT_GP_PP 0x00
#define OUT_GP_OD 0x01
#define OUT_AF_PP 0x02
#define OUT_AF_OD 0x03
#define SET_CRL(pin_no,mode,conf) (((mode) | ((conf) << 2)) << (4*(pin_no)))
#define SET_CRH(pin_no,mode,conf) (((mode) | ((conf) << 2)) << (4*((pin_no)-8)))
#define PIN_MASK(pin_no) (1<<(pin_no))
#define PIN_SETMASK(pin_no) (1<<(pin_no))
#define PIN_RSTMASK(pin_no) (1<<((pin_no)+16))
#endif /* STM32_PORTS_H_INCLUDED */
Комментарии (44)
RSS свернуть / развернуть