Чуть ранее (в этой статье) мы рассмотрели в общих чертах таймеры в STM32 и написали простенькую программку. Теперь, как и обещал, поподробнее покопаем генерацию ШИМ при помощи все того же таймера TIM4. Итак, начинаем!
Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также небольшая подборка статей с ШИМ и таймерами:
- ПИД-регулятор. Пример ПИД-регулятора температуры на STM32.
- STM32 и Timer Input Capture. Режим захвата сигнала.
Честно говоря, писать-то особо нечего. Думаю многие знают что такое ШИМ и с чем его едят, а если нет то об этом можно прочитать, например, в Википедии, так что нет, наверное, смысла отдельно описывать то, что уже многократно и хорошо описано...
Давайте создадим пример для генерации ШИМ. Просто сгенерировать такой сигнал не так интересно, так что давайте хоть немного усложним себе задачу. Будем генерировать ШИМ с разной длительностью импульса в зависимости от состояния кнопки. Если кнопка нажата – генерируем сигнал с периодом 2.5 мс и длительностью импульса 1.5 мс, а если кнопка не нажата – то 2.5 мс и 0.5 мс соответственно.
Так что создаем новый проект и пишем код. Для начала набор includ’ов:
#include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "stm32f10x_gpio.h" #include "stm32f10x_tim.h"
Добавили все файлы, необходимые нам для работы. Объявим переменные:
#define TIM_CCER_CC3NE ((uint16_t)0x0400) #define TIMER_PRESCALER 720 #define EXT_TIM_PULSE 150 #define TIM_PULSE 50
- TIMER_PRESCALER мы уже упоминали - это предделитель частоты.
- EXT_TIM_PULSE – увеличенная длительность импульса (то есть при нажатой кнопке), аналогично TIM_PULSE – обычная длительность (кнопка не нажата).
Продолжаем:
uint16_t previousState; GPIO_InitTypeDef port; TIM_TimeBaseInitTypeDef timer; TIM_OCInitTypeDef timerPWM; uint16_t buttonPreviousState;
Просто переменные, которые нам понадобятся в проекте. Пока все просто! И вот наконец-то кое-что поинтереснее, а именно наша разросшаяся функция инициализации:
void initAll() { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); GPIO_StructInit(&port); port.GPIO_Mode = GPIO_Mode_AF_PP; port.GPIO_Pin = GPIO_Pin_6; port.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, &port); port.GPIO_Mode = GPIO_Mode_IPD; port.GPIO_Pin = GPIO_Pin_3; port.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOA, &port); TIM_TimeBaseStructInit(&timer); timer.TIM_Prescaler = TIMER_PRESCALER; timer.TIM_Period = 250; TIM_TimeBaseInit(TIM4, &timer); TIM_OCStructInit(&timerPWM); timerPWM.TIM_Pulse = 50; timerPWM.TIM_OCMode = TIM_OCMode_PWM1; timerPWM.TIM_OutputState = TIM_OutputState_Enable; TIM_OC1Init(TIM4, &timerPWM); }
Давайте прямо по строчкам смотреть, что тут происходит.
Вначале уже привычное включение тактирования необходимой периферии. Шестую ножку порта GPIOB настраиваем на работу в режиме альтернативной функции(!). Лезем в даташит на контроллер и видим, что альтернативной функцией у этого вывода является первый канал таймера TIM4. То что надо:
Вывод PA3 настраиваем на вход – там будет наша воображаемая кнопка. Итак, с инициализацией портов закончили, идем настраивать таймер. Поначалу все конфигурируем, как и в предыдущем проекте. А вот дальше кое-что новенькое:
TIM_OCStructInit(&timerPWM); timerPWM.TIM_Pulse = 50; timerPWM.TIM_OCMode = TIM_OCMode_PWM1; timerPWM.TIM_OutputState = TIM_OutputState_Enable; TIM_OC1Init(TIM4, &timerPWM);
Для использования режима генерации ШИМ нам понадобилась структура TIM_OCInitTypeDef. Поле TIM_Pulse – длительность заполнения, пусть будет сначала 50 тиков (0.5 мс). Далее задаем режим - TIM_OCMode_PWM1. Помимо PWM1 есть еще PWM2. Это всего лишь разные режимы ШИМ – с выравниванием по границе и по центру. В поле TIM_OutputState забиваем – Enable и инициализируем таймер. Готово!
Осталось совсем немного... Функция main() – как же без нее:
int main() { __enable_irq(); initAll(); buttonPreviousState = 0; TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); TIM_Cmd(TIM4, ENABLE); NVIC_EnableIRQ(TIM4_IRQn); while(1) { __NOP(); } }
Все как и раньше – включаем прерывание по переполнению. Оно нам нужно, чтобы изменять параметры генерируемого ШИМ-сигнала именно в момент окончания периода. Вот и сам код обработчика:
void TIM4_IRQHandler() { uint16_t button = 0; button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3); TIM_ClearITPendingBit(TIM4, TIM_IT_Update); if ((button == 1) && (buttonPreviousState == 0)) { TIM4->CCR1 = EXT_TIM_PULSE; buttonPreviousState = 1; } if ((button == 0) && (buttonPreviousState == 1)) { TIM4->CCR1 = TIM_PULSE; buttonPreviousState = 0; } }
Опрашиваем кнопку, ничего нового, знакомая функция 🙂 Возможно возникнет вопрос – зачем так усложнять:
(button == 1) && (buttonPreviousState == 0)
А чтобы работа с регистром таймера шла не на каждое прерывание, а только когда состояние кнопки изменяется. Упомянутая работа с регистром заключается в прямой записи длительности заполнения в регистр таймера TIM_CCR.
Компилируем, идем в отладчик, запускаем программу и эмулируем нажатие кнопки на PA3. Если кто забыл, открываем окно General Purpose I/O A (GPIOA) и вручную выставляем бит на входе PA3. В окошке логического анализатора видим:
При изменении состояния кнопки меняется и скважность импульсов, как и задумывалось. И на сегодня это все, до скорого 🤝
А можно поподробней строчку:
timerPWM.TIM_Pulse = 50;
если туда вбить ноль?
или 5000?
Это время, в течение которого, на выходе будет высокий уровень
извиняюсь за бестолковый вопрос, но где в главной функции вызывается обработчик TIM4_IRQHandler?
Это обработчик прерывания, он явно нигде не вызывается, в этом и фишка ) Он вызывается аппаратно при выполнении определенных условий, в этом примере когда таймер досчитал до значения - вызывается обработчик прерывания.
а какие еще функции, связанные с таймерами, могут вызываться аппаратно и где про них можно прочитать?
просто в stm32f4xx_tim.h про него упоминания нет, в reference manual тоже
разобрался - в stm32f4xx.h
А на установленных на плате светодиодах ШИМ не пробовали? На зарубежных форумах ппадалось, но не компилируется выдает ошибки.
#include "stm32F10x.h"
#include "STM32vldiscovery.h"
int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
volatile int i;
int n = 1;
int brightness = 0;
RCC_APB2PeriphClockCmd(
RCC_APB2Periph_GPIOC |
RCC_APB2Periph_AFIO, ENABLE );
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3, ENABLE );
GPIO_StructInit(&GPIO_InitStructure); // Reset init structure
// Setup Blue & Green LED on STM32-Discovery Board to use PWM.
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Alt Function - Push Pull
GPIO_Init( GPIOC, &GPIO_InitStructure );
GPIO_PinRemapConfig( GPIO_FullRemap_TIM3, ENABLE ); // Map TIM3_CH3 to GPIOC.Pin8, TIM3_CH4 to GPIOC.Pin9
// Let PWM frequency equal 100Hz.
// Let period equal 1000. Therefore, timer runs from zero to 1000. Gives 0.1Hz resolution.
// Solving for prescaler gives 240.
TIM_TimeBaseStructInit( &TIM_TimeBaseInitStruct );
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV4;
TIM_TimeBaseInitStruct.TIM_Period = 1000 - 1; // 0..999
TIM_TimeBaseInitStruct.TIM_Prescaler = 240 - 1; // Div 240
TIM_TimeBaseInit( TIM3, &TIM_TimeBaseInitStruct );
TIM_OCStructInit( &TIM_OCInitStruct );
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
// Initial duty cycle equals 0%. Value can range from zero to 1000.
TIM_OCInitStruct.TIM_Pulse = 0; // 0 .. 1000 (0=Always Off, 1000=Always On)
TIM_OC3Init( TIM3, &TIM_OCInitStruct ); // Channel 3 Blue LED
TIM_OC4Init( TIM3, &TIM_OCInitStruct ); // Channel 4 Green LED
TIM_Cmd( TIM3, ENABLE );
while(1) // Do not exit
{
if (((brightness + n) >= 1000) || ((brightness + n) CCR3 = brightness; // set brightness
TIM3->CCR4 = 1000 - brightness; // set brightness
for(i=0;i<10000;i++); // delay
}
return(0); // System will implode
}
Не, на светодиодах платы не пробовал. Можно в принципе прям на ту ногу, которая в примере используется напаять все что угодно
Если есть на плате светодиоды, чего изголяться... уже получилось, код ниже.
#include "stm32f10x.h" /* "stm32f10x.h"
int main(void)
{
volatile int i; // Переменная отсчета паузы
int n = 1; // Шаг увеличения яркости
int brightness = 0; // Яркость
RCC->APB2ENR |= (RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN); //Тактирование порта C и альтернативных функций
RCC->APB1ENR | =RCC_APB1ENR_TIM3EN;//Тактирование таймера TIM3
GPIOC->CRH &= ~(GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF8 | GPIO_CRH_CNF9); //очистить разряды MODE, CNF (поскольку после сброса мк задан режим "Input Floating")
GPIOC->CRH |= GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF8_1 | GPIO_CRH_CNF9_1;//Максимальная скорость порта PC8,9 = 50 MHz - выход Push-Pull в режиме альтернативной функции
AFIO->MAPR |=AFIO_MAPR_TIM3_REMAP_FULLREMAP; //Full remap (CH1/PC6, CH2/PC7, CH3/PC8, CH4/PC9) TIM3->PSC = 240-1; // Настройка предделителя таймера (24МГц/240Кгц=100 Кгц)
TIM3->ARR = 1000-1; // задаем период ШИМ 100000/1000=100 Гц
TIM3->CCMR2 |= (TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1); // OCxM = 110 - ШИМ режим 1(прямой ШИМ)на каналы 3 & 4
TIM3->CCR3 = 0; // Начальная длительность импульса.
/* разрешаем выходной сигнал */
TIM3->CCER |= (TIM_CCER_CC3E | TIM_CCER_CC4E);
TIM3->CR1 |= TIM_CR1_CEN; //Запускаем таймер!
while(1)
{
if (((brightness + n) >= 1000) || ((brightness + n) CCR3 = brightness; // установим яркость
TIM3->CCR4 = 1000 - brightness; // установим яркость
for(i=0;i<10000;i++); // пауза
}
}
1) Если в прерывании я буду менять CCR1 по синусоидальному закону (брать значения из таблицы синуса), а выходной сигнал пропущу через фнч, то получится синусоидальное напряжение?
2) Если использовать режим ШИМ выравнивание по центру, то как настроить прерывание?
С прерыванием все без изменений.
По первому пункту - да, какое-то подобие синуса можно так получить)
Всем привет. Не пойму как считать значения prescaler , TIM_Period и TIM_Pulse. Хочу попробовать управлять сервоприводом. Ему нужно 50 Гц. Как мне расчитать параметры, чтобы получилась нужная частота ШИМа?
Здравствуйте, коллеги!
Очень простой вопрос. Я хочу, чтобы мой процессор работал на частоте 168 МГц (очень надо). Еще я хочу, чтобы один из таймеров делал ШИМ на одной из ног с частотой порядка 2 МГц с предельно высокой точностью. Но получается, что точность ШИМа моего таймера будет определяться не точностью и стабильностью кварца, а тем, как работает модуль PLL???
Может это конечно и тупой вопрос, но подскажите как высчитывать PRESCALER? Точнее от какой частоты? Вот не могу найти эту информацию в гугле, везде попадается только такие же примеры, как и тут. Спасибо.
От частоты тактирования шины, на которой висит таймер.
Я предполагал что с 8 битных AVRок будет тяжеловато переходить на 32 битные но все же.
Переделал чуть код для Stm32F3discovery:
#include "stm32f30x_gpio.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_tim.h"
#include "stm32f30x.h"
#define TIMER_PRESCALER 720
#define EXT_TIM_PULSE 150 //
#define TIM_PULSE 50
uint16_t previousState;
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
uint16_t buttonPreviousState;
void initAll()
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // для PA0 ( User кнопка ) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // тактирую TIM2 на шине APB1
GPIO_StructInit(&port);
port.GPIO_Mode = GPIO_Mode_AF; // альтерн. ф-ция
port.GPIO_Pin = GPIO_Pin_2; // PA2 I/O TTa -
USART2_TX, TIM2_CH3, ( из даташит )
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);
port.GPIO_Mode = GPIO_Mode_IN;
port.GPIO_Pin = GPIO_Pin_0; // вход PA0 User кнопка
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);
TIM_TimeBaseStructInit(&timer);
timer.TIM_Prescaler = TIMER_PRESCALER;
timer.TIM_Period = 250;
TIM_TimeBaseInit(TIM2, &timer);
TIM_OCStructInit(&timerPWM);
timerPWM.TIM_Pulse = 50; // длительность заполнения
timerPWM.TIM_OCMode = TIM_OCMode_PWM1; // режим
timerPWM.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM2, &timerPWM);
}
int main()
{
__enable_irq ();
initAll();
//buttonPreviousState = 0;
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
NVIC_EnableIRQ(TIM2_IRQn);
while(1)
{
__NOP();
}
}
void TIM2_IRQHandler()
{
uint16_t button = 0;
button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
if ((button == 1) && (buttonPreviousState == 0))
{
TIM2->CCR1 = EXT_TIM_PULSE;
buttonPreviousState = 1;
}
if ((button == 0) && (buttonPreviousState == 1))
{
TIM2->CCR1 = TIM_PULSE;
buttonPreviousState = 0;
}
}
Все вроде перепроверил нету на выходе ШИМа (((
Aveal что я не так делаю? Заранее спасибо.
P.S. канал для таймера указывать не надо? ( TIM2->CCER | = TIM_CCER_CC3E; )
Надо пошагово все проверить - работает ли вообще таймер, уходит ли на обработчик прерывания итд
Разобрался! Необходимо было добавить
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1); это для контроллеров серии STM32f3xx - f4xx.
Теперь пытаюсь разобраться дальше с частотой тактирования и надеюсь на вашу помощь.
Шина от которой тактирую таймер 2 имеет максимальную частоту 36 мгц ( по даташиту ).
здесь gpio.GPIO_Speed = GPIO_Speed_50MHz; выставляется 50 мгц так какая частота будет по факту?
Такая, какая будет на шине APB1.
Спасибо за быстрый ответ !
Добрый день! я тоже разбираюсь с stm32 на плате F3Discovery. Подскажите, GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1); где инициализировать эту функцию?
Че то у меня нету шима по-прежнему
После инициализации вывода:
GPIO_StructInit(&port);
port.GPIO_Mode = GPIO_Mode_AF; // альтерн. ф-ция
port.GPIO_Pin = GPIO_Pin_2; // PA2 I/O TTa -
USART2_TX, TIM2_CH3, ( из даташит )
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1);
Вот мой код, и всеравно не работает, Кнопка на РА0, таймер 4, альтернативный пин PORTD12, по даташиту канал 1 таймера. Че еще не так?
#include "stm32f30x.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_gpio.h"
#include "stm32f30x_tim.h"
#define TIMER_PRESCALER 711
#define EXT_TIM_PULSE 150
#define TIM_PULSE 50
uint16_t previousState;
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
uint16_t buttonPreviousState;
void initAll()
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
GPIO_StructInit(&port);
port.GPIO_Mode = GPIO_Mode_OUT;
port.GPIO_OType = GPIO_OType_PP;
port.GPIO_Pin = GPIO_Pin_12;
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOD, &port);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_2);
port.GPIO_Mode = GPIO_Mode_IN;
port.GPIO_Pin = GPIO_Pin_0;
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);
TIM_TimeBaseStructInit(&timer);
timer.TIM_Prescaler = TIMER_PRESCALER;
timer.TIM_Period = 250;
TIM_TimeBaseInit(TIM4, &timer);
TIM_OCStructInit(&timerPWM);
timerPWM.TIM_Pulse = 50;
timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
timerPWM.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM4, &timerPWM);
}
int main()
{
__enable_irq ();
initAll();
buttonPreviousState = 0;
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM4, ENABLE);
NVIC_EnableIRQ(TIM4_IRQn);
while(1)
{
__NOP();
}
}
void TIM4_IRQHandler()
{
uint16_t button = 0;
button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
if ((button == 1) && (buttonPreviousState == 0))
{
TIM4->CCR1 = EXT_TIM_PULSE;
buttonPreviousState = 1;
}
if ((button == 0) && (buttonPreviousState == 1))
{
TIM4->CCR1 = TIM_PULSE;
buttonPreviousState = 0;
}
}
Там ошибка, вместо GPIO_PinSource15 должно быть GPIO_PinSource12, но всеравно не работает
Из того, что сразу бросается в глаза - вывод надо настроить в режиме альтернативной функции, а не обычного вывода
ШИМ осциллографом смотришь? Надо проверить все поэтапно - работает ли сам таймер, переходит ли программа на обработчик прерывания.....
GPIO_Mode_AF точно. проглядел. Спасибо. теперь вроде работает. А частота импульсов теоретически должна быть около 333Гц, я правильно понимаю?
А какая частота шины APB1?
Я даже не знаю.. А где можно посмотреть или как выставить?
Можно опытным путем вычислить например - измерить период ШИМА и рассчитать из нее частоту шины) Вообще в настройках тактирования
В проекте есть файл типа такого:
startup_stm32f10x_md.s
В нём все названия и описаны. По сути - это просто указатель на функцию, записаный в нужный адрес. Так же, как и прерывания в MS-DOS для PC.
Здравствуйте. Вы не подскажите, как пользоваться логическим анализатором? Хотя бы, как увидеть такую же картинку, как в примере.
Я им только в ознакомительных целях пользовался...В новой версии Keil'а его может уже и нет, точно не знаю. Лучше в железе все-таки отлаживать.
День добрый. Использую F407 проц и Cube. Прошу подсказать, как правильно запустить трехфазый ШИМ (то есть нужно три канала), с использованием DMA. Пытался вызывать функцию HAL_TIM_PWM_Start_DMA для всех трех каналов, но работает в итоге только один, который первым вызвали. Как это делается правильно?
Добрый день, я в таком режиме не пробовал. Все три канала работают по одному (если очередность вызовов менять) ?
Все три канала работают по своим данным. Ладно, ну его DMA. Выяснилось что у меня и обычная функция HAL_TIM_PWM_Start_IT не хочет работать. Как на ней запустить три канала одновременно и для каждого канала иметь возможность задавать свои данные?
Пример запуска:
HAL_TIM_PWM_Start_IT(&htim4, TIM_CHANNEL_2);
HAL_TIM_PWM_Start_IT(&htim4, TIM_CHANNEL_3);
HAL_TIM_PWM_Start_IT(&htim4, TIM_CHANNEL_4);
Значения задаем в прерывании:
void TIM4_IRQHandler(void)
{
/* USER CODE BEGIN TIM4_IRQn 0 */
/* USER CODE END TIM4_IRQn 0 */
HAL_TIM_IRQHandler(&htim4);
/* USER CODE BEGIN TIM4_IRQn 1 */
TIM4->CCR2 = data1;
TIM4->CCR3 = data2;
TIM4->CCR4 = data3;
/* USER CODE END TIM4_IRQn 1 */
}
Инициализация целиком на Cube, там ничего не меняем.
Да, спасибо. Уже сообразил. Не мог долго понять, что у TIM1 прерывание надо вручную врубать, а активное прерывание, запускаемое от ШИМ, не подходит для подпихивания данных
GPIO_StructInit(&port);
в предыдущих статьях в функции StructInit не было &.
Для чего здесь в примере добавлено?
Добрый день!
Приношу свои извинения, это плагин для оформления кода поставил эти amp... Исправил.
Здравствуйте! А через define-ны мы получается можем настраивать интервалы и длительность импульсов?
Добрый день!
В этом примере - да, а в целом, таймер руководствуется значениями в своих регистрах.