Arduino STM32 - таймеры
Это третья часть посвящённая плате Blue Pill (Arduino STM32).
Первая часть — прошивка
Вторая часть — внешние прерывания
Таймер — это
Микроконтроллер STM32F103х имеет на борту четыре 16-ти битных таймера
• три таймера — TIM2, TIM3, TIM4, общего назначения (general-purpose timers).
• один продвинутый таймер — TIM1, с расширенными возможностями (advanced-control timers).
А так же…
• два WDT (WatchDog Timer).
• один SysTick Timer.
16-ти битный таймер умеет считать в диапазоне от 0 до 65535
Таймеры независимы друг от друга.
Таймеры тактируются (то есть «тикают») от системной частоты и соответственно не потребляют ресурсов.
Если не использовать дополнительных настроек, то таймер будет считать со скоростью 72Мгц
Настройки таймера (режимы)
Так как настроек и режимов достаточно много, то я буду описывать их «от простого к сложному». Каким образом и при каких обстоятельствах применять те или иные режимы решать Вам.
мс — миллисекунда (1000мс = 1сек).
мкс — микросекунда (1000000мкс = 1сек).
нс — наносекунда (1000000000нс = 1сек)
Предделитель
Как написано выше, если запустить таймер без настроек, то он будет считать очень быстро, что конечно же не всегда целесообразно. Для того чтобы понизить скорость отсчёта, нужно использовать предделитель.
Предделитель делит системную частоту на любое число от 1 до 65536. Например, если установить значение предделителя 3600, то таймер будет «тикать» со скоростью 20000 раз в секунду (72МГц / 3600 = 20КГц). То есть он будет доходить до максимального значения (65536) примерно за 3.22 секунды.
Для примера используем таймер №3, установим делитель 720
volatile bool LEDOn13 = 0;
void setup()
{
pinMode(LED_BUILTIN, OUTPUT); // PC13
Timer3.pause(); // останавливаем таймер перед настройкой
Timer3.setPrescaleFactor(720); // устанавливаем делитель
Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
Timer3.refresh(); // обнулить таймер
Timer3.resume(); // запускаем таймер
}
void loop()
{}
void func_tim_3() // обработчик прерывания
{
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}
TIMER_UPDATE_INTERRUPT — режим таймера (автоматическое обновление и вызов прерывания).
Функции pause(), refresh() и resume() не обязательны, я их просто обозначил. Эти функции можно использовать в любом месте программы, например, если где-то в основном цикле нужно приостановить, запустить или обнулить таймер.
Переполнение
Переполнением называется то самое значение (65535), до которого считает таймер. Его можно менять по своему усмотрению.
Если мы укажем в нашем коде переполнение равное 16000, тогда таймер будет обнуляться и начинать отсчёт заново достигнув этой цифры. Таким образом светодиод будет моргать в четыре раза чаще…
volatile bool LEDOn13 = 0;
void setup()
{
pinMode(LED_BUILTIN, OUTPUT); // PC13
Timer3.pause(); // останавливаем таймер перед настройкой
Timer3.setPrescaleFactor(720); // устанавливаем делитель
Timer3.setOverflow(16000); // переполнение
Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
Timer3.refresh(); // обнулить таймер
Timer3.resume(); // запускаем таймер
}
void loop()
{}
void func_tim_3() // обработчик прерывания
{
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}
Оперируя предделителем и переполнением можно получать различные интервалы времени. Однако если нет желания заморачиваться с подсчётами, то можно функции setPrescaleFactor() и setOverflow() заменить одной функцией — setPeriod(), которая сделает всё сама, ей нужно только указать время в микросекундах…
volatile bool LEDOn13 = 0;
void setup()
{
pinMode(LED_BUILTIN, OUTPUT); // PC13
Timer3.pause(); // останавливаем таймер перед настройкой
Timer3.setPeriod(500000); // время в микросекундах (500мс)
Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
Timer3.refresh(); // обнулить таймер
Timer3.resume(); // запускаем таймер
}
void loop()
{}
void func_tim_3() // обработчик прерывания
{
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}
Таймер будет срабатывать каждые 500мс.
Максимальная задержка ~59сек — setPeriod(59000000).
Вернёмся ко второму примеру и добавим функцию — setCount(). Эта функция в некотором роде противоположность setOverflow(), она указывает таймеру с какого места нужно начинать отсчёт.
Допустим что переполнение у нас равно 60000, то есть счётчик считает от 0 до 60000. Теперь если в обработчике прерывания дописать — Timer3.setCount(59000), то отсчёт будет вестись не от нуля, а от 59000. Соответственно таймер переполнится очень быстро.
Функция setCount() устанавливает новое значение только один раз, при следующей итерации таймер снова будет отсчитывать от нуля, поэтому её нужно вызывать каждый раз…
volatile int i = 0;
volatile bool LEDOn13 = 0;
void setup()
{
pinMode(LED_BUILTIN, OUTPUT); // PC13
Timer3.pause(); // останавливаем таймер перед настройкой
Timer3.setPrescaleFactor(1720); // устанавливаем делитель
Timer3.setOverflow(60000); // переполнение
Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_tim_3); // активируем прерывание
Timer3.refresh(); // обнулить таймер
Timer3.resume(); // запускаем таймер
}
void loop()
{}
void func_tim_3() // обработчик прерывания
{
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
i++;
if(i > 6 && i < 91) Timer3.setCount(59000);
if(i > 90 && i < 101) Timer3.setCount(35000);
if(i > 100) Timer3.setCount(0);
}
Сначала светик мигнёт три раза с частотой заданой в
Теперь пришло время разобраться с каналами таймеров…
У таймеров TIM1, TIM2, TIM3, TIM4 имеется в наличии по четыре канала ввода/вывода (TIMER_CH1...TIMER_CH4). Каналы могут работать в режимах — захват, сравнение, генерировать ШИМ и одиночные импульсы.
Сравнение
Работа каналов в режиме сравнения, это когда в специальную ячейку (для каждого канала своя ячейка) памяти помещается число от 0 до 65535, а таймер ведя отсчёт постоянно сравнивает своё значение со значением в ячейке. Как только значения совпадают, то тут же вызывается какое-либо событие, например прерывание. Таймер при этом продолжает считать пока не переполнится, после чего цикл повторяется.
Таким образом, один таймер может выполнить несколько действий с разными интервалами времени в рамках одного цикла переполнения.
В примере ниже происходит следующее: таймер №3 будет считать от 0 до 14000 (переполнение), и после каждого «тика» сравнивать своё значение со значениями сравнений каналов
пример
volatile bool LEDOn10 = 0;
volatile bool LEDOn11 = 0;
volatile bool LEDOn12 = 0;
volatile bool LEDOn13 = 0;
void setup()
{
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PB10, OUTPUT);
pinMode(PB11, OUTPUT);
pinMode(PB12, OUTPUT);
Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH2, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH3, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
Timer3.pause();
Timer3.setPrescaleFactor(7200);
Timer3.setOverflow(14000); // счетчик считает от 0 до 14000
Timer3.setCompare(TIMER_CH1, 3000); // сравнение
Timer3.attachInterrupt(TIMER_CH1, func_1);
Timer3.setCompare(TIMER_CH2, 6000);
Timer3.attachInterrupt(TIMER_CH2, func_2);
Timer3.setCompare(TIMER_CH3, 9000);
Timer3.attachInterrupt(TIMER_CH3, func_3);
Timer3.setCompare(TIMER_CH4, 11000);
Timer3.attachInterrupt(TIMER_CH4, func_4);
Timer3.resume();
}
void loop()
{}
void func_1(void)
{
Serial.println("Compare CH1");
digitalWrite(PB10, (LEDOn10 = !LEDOn10));
}
void func_2(void)
{
Serial.println("Compare CH2");
digitalWrite(PB11, (LEDOn11 = !LEDOn11));
}
void func_3(void)
{
Serial.println("Compare CH3");
digitalWrite(PB12, (LEDOn12 = !LEDOn12));
}
void func_4(void)
{
Serial.println("Compare CH4");
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}
… при этом ничто не мешает использовать ещё и прерывание по переполнению.
пример
volatile bool LEDOn10 = 0;
volatile bool LEDOn11 = 0;
volatile bool LEDOn12 = 0;
volatile bool LEDOn13 = 0;
volatile bool LEDOn15 = 0;
void setup()
{
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PB10, OUTPUT);
pinMode(PB11, OUTPUT);
pinMode(PB12, OUTPUT);
pinMode(PB15, OUTPUT);
Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH2, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH3, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
Timer3.pause();
Timer3.setPrescaleFactor(7200);
Timer3.setOverflow(14000); // счетчик считает от 0 до 14000
Timer3.setCompare(TIMER_CH1, 3000); // сравнение
Timer3.attachInterrupt(TIMER_CH1, func_1);
Timer3.setCompare(TIMER_CH2, 6000);
Timer3.attachInterrupt(TIMER_CH2, func_2);
Timer3.setCompare(TIMER_CH3, 9000);
Timer3.attachInterrupt(TIMER_CH3, func_3);
Timer3.setCompare(TIMER_CH4, 11000);
Timer3.attachInterrupt(TIMER_CH4, func_4);
Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_5); // прерывание по переполнению
Timer3.resume();
}
void loop()
{}
void func_1(void)
{
Serial.println("Compare CH1");
digitalWrite(PB10, (LEDOn10 = !LEDOn10));
}
void func_2(void)
{
Serial.println("Compare CH2");
digitalWrite(PB11, (LEDOn11 = !LEDOn11));
}
void func_3(void)
{
Serial.println("Compare CH3");
digitalWrite(PB12, (LEDOn12 = !LEDOn12));
}
void func_4(void)
{
Serial.println("Compare CH4");
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}
void func_5(void)
{
Serial.println("Overflow");
digitalWrite(PB15, (LEDOn15 = !LEDOn15));
}
Поскольку в рамках одного цикла интервал получается очень маленьким, то можно просто добавить переменную, которая будет увеличиваться при каждом прерывании и событие произойдёт по достижении нужного значения…
пример
volatile bool LEDOn10 = 0;
volatile bool LEDOn11 = 0;
volatile bool LEDOn12 = 0;
volatile bool LEDOn13 = 0;
volatile bool LEDOn15 = 0;
volatile int count = 0;
void setup()
{
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PB10, OUTPUT);
pinMode(PB11, OUTPUT);
pinMode(PB12, OUTPUT);
pinMode(PB15, OUTPUT);
Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH2, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH3, TIMER_OUTPUT_COMPARE);
Timer3.setMode(TIMER_CH4, TIMER_OUTPUT_COMPARE);
Timer3.pause();
Timer3.setPrescaleFactor(7200);
Timer3.setOverflow(14000); // счетчик считает от 0 до 14000
Timer3.setCompare(TIMER_CH1, 3000); // сравнение
Timer3.attachInterrupt(TIMER_CH1, func_1);
Timer3.setCompare(TIMER_CH2, 6000);
Timer3.attachInterrupt(TIMER_CH2, func_2);
Timer3.setCompare(TIMER_CH3, 9000);
Timer3.attachInterrupt(TIMER_CH3, func_3);
Timer3.setCompare(TIMER_CH4, 11000);
Timer3.attachInterrupt(TIMER_CH4, func_4);
Timer3.attachInterrupt(TIMER_UPDATE_INTERRUPT, func_5); // прерывание по переполнению
Timer3.resume();
}
void loop()
{}
void func_1(void)
{
count++;
if(count == 5)
{
Serial.println("Канал_1 - моргаю при каждом пятом сравнении");
digitalWrite(PB10, (LEDOn10 = !LEDOn10));
count = 0;
}
}
void func_2(void)
{
Serial.println("Compare CH2");
digitalWrite(PB11, (LEDOn11 = !LEDOn11));
}
void func_3(void)
{
Serial.println("Compare CH3");
digitalWrite(PB12, (LEDOn12 = !LEDOn12));
}
void func_4(void)
{
Serial.println("Compare CH4");
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}
void func_5(void)
{
Serial.println("Overflow");
digitalWrite(PB15, (LEDOn15 = !LEDOn15));
}
TIMER_CH1...TIMER_CH4 — номера каналов.
TIMER_OUTPUT_COMPARE — режим канала.
ШИМ
К каждому каналу таймера привязана конкретная «ножка» МК, которую можно использовать в различных целях, например генерировать сигнал ШИМ…
каналы
TIM1_CH1 — PA8
TIM1_CH2 — PA9
TIM1_CH3 — PA10
TIM1_CH4 — PA11 (это usb — не надо использовать)
TIM2_CH1 — PA0
TIM2_CH2 — PA1
TIM2_CH3 — PA2
TIM2_CH4 — PA3
TIM3_CH1 — PA6
TIM3_CH2 — PA7
TIM3_CH3 — PB0
TIM3_CH4 — PB1
TIM4_CH1 — PB6
TIM4_CH2 — PB7
TIM4_CH3 — PB8
TIM4_CH4 — PB9
TIM1_CH2 — PA9
TIM1_CH3 — PA10
TIM1_CH4 — PA11 (это usb — не надо использовать)
TIM2_CH1 — PA0
TIM2_CH2 — PA1
TIM2_CH3 — PA2
TIM2_CH4 — PA3
TIM3_CH1 — PA6
TIM3_CH2 — PA7
TIM3_CH3 — PB0
TIM3_CH4 — PB1
TIM4_CH1 — PB6
TIM4_CH2 — PB7
TIM4_CH3 — PB8
TIM4_CH4 — PB9
Для примера воспользуемся первым и вторым каналом третьего таймера. Переполнением
t — длина импульса.
T — период.
void setup()
{
pinMode(PA6, PWM); // выход ШИМ канал 1
pinMode(PA7, PWM); // выход ШИМ канал 2
Timer3.setPrescaleFactor(72); // 1мкс
Timer3.setOverflow(5000); // период 5 мс
Timer3.setCompare(TIMER_CH1, 4000); // импульс 4 мс
Timer3.setCompare(TIMER_CH2, 100); // импульс 0.1 мс
Timer3.refresh(); // обнулить счётчик
Timer3.resume();
}
void loop()
{
Timer3.setCompare(TIMER_CH1, 4000);
Timer3.setCompare(TIMER_CH2, 100);
Timer3.refresh();
delay(2000);
Timer3.setCompare(TIMER_CH1, 2000);
Timer3.setCompare(TIMER_CH2, 700);
Timer3.refresh();
delay(2000);
Timer3.setCompare(TIMER_CH1, 700);
Timer3.setCompare(TIMER_CH2, 2000);
Timer3.refresh();
delay(2000);
Timer3.setCompare(TIMER_CH1, 100);
Timer3.setCompare(TIMER_CH2, 4000);
Timer3.refresh();
delay(2000);
}
Добавив ещё один канал можно рулить RGB-лентой…
RGB
Отправляя в «Монитор порта» R+ или R-, G+ или G-, B+ или B- можно управлять яркостью светиков.
int compare_R = 2000;
int compare_G = 2000;
int compare_B = 2000;
void setup()
{
Serial.begin(115200);
pinMode(PA6, PWM); // выход ШИМ канал 1
pinMode(PA7, PWM); // выход ШИМ канал 2
pinMode(PB0, PWM); // выход ШИМ канал 3
Timer3.setPrescaleFactor(72); // 1мкс
Timer3.setOverflow(5000); // период 5 мс
Timer3.setCompare(TIMER_CH1, compare_R); // R
Timer3.setCompare(TIMER_CH2, compare_G); // G
Timer3.setCompare(TIMER_CH3, compare_B); // B
Timer3.refresh(); // обнулить счётчик
Timer3.resume();
}
void loop()
{
if(Serial.available() > 1)
{
char first_sim = Serial.read();
char second_sim = Serial.read();
for(int i=0; i < 3; i++)
{
Serial.read();
delay(1);
}
switch(first_sim)
{
case 'R':
if(second_sim == '+')
{
compare_R += 1000;
if(compare_R > 5000) compare_R = 5000;
Timer3.setCompare(TIMER_CH1, compare_R);
Serial.print("Compare_CH1 ");
Serial.println(Timer3.getCompare(TIMER_CH1));
}
else if(second_sim == '-')
{
compare_R -= 1000;
if(compare_R < 0) compare_R = 0;
Timer3.setCompare(TIMER_CH1, compare_R);
Serial.print("Compare_CH1 ");
Serial.println(Timer3.getCompare(TIMER_CH1));
}
break;
case 'G':
if(second_sim == '+')
{
compare_G += 1000;
if(compare_G > 5000) compare_G = 5000;
Timer3.setCompare(TIMER_CH2, compare_G);
Serial.print("Compare_CH2 ");
Serial.println(Timer3.getCompare(TIMER_CH2));
}
else if(second_sim == '-')
{
compare_G -= 1000;
if(compare_G < 0) compare_G = 0;
Timer3.setCompare(TIMER_CH2, compare_G);
Serial.print("Compare_CH2 ");
Serial.println(Timer3.getCompare(TIMER_CH2));
}
break;
case 'B':
if(second_sim == '+')
{
compare_B += 1000;
if(compare_B > 5000) compare_B = 5000;
Timer3.setCompare(TIMER_CH3, compare_B);
Serial.print("Compare_CH3 ");
Serial.println(Timer3.getCompare(TIMER_CH3));
}
else if(second_sim == '-')
{
compare_B -= 1000;
if(compare_B < 0) compare_B = 0;
Timer3.setCompare(TIMER_CH3, compare_B);
Serial.print("Compare_CH3 ");
Serial.println(Timer3.getCompare(TIMER_CH3));
}
break;
default:
break;
}
}
}
Отправляя в «Монитор порта» R+ или R-, G+ или G-, B+ или B- можно управлять яркостью светиков.
Задействовав все четыре таймера можно подключить четыре ленты, и при этом выполнять ещё какие-либо действия без ущерба для всей этой иллюминации.
В примере под спойлером появилась новая функция — getCompare(). Если у некоторых функций заменить приставку — set на get, то можно посмотреть текущие значения…
volatile bool LEDOn13 = 0;
void setup()
{
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
Timer3.setMode(TIMER_CH1, TIMER_OUTPUT_COMPARE);
Timer3.pause();
Timer3.setPrescaleFactor(7200);
Timer3.setOverflow(14000);
Timer3.setCompare(TIMER_CH1, 3000);
Timer3.attachInterrupt(TIMER_CH1, func_1);
Timer3.resume();
}
void loop()
{
Serial.println(Timer3.getPrescaleFactor());
Serial.println(Timer3.getOverflow());
Serial.println(Timer3.getCount()); // текущее значение счётчика
Serial.println(Timer3.getCompare(TIMER_CH1));
Serial.println("--------");
delay(1000);
}
void func_1(void)
{
digitalWrite(LED_BUILTIN, (LEDOn13 = !LEDOn13));
}
Захват
Предположим что у вас есть некий источник сигналов, пускай это будет чередующиеся HIGH и LOW, генерируемые обычной Ардуиной. Нам нужно измерить длину импульса и период…
t — длина импульса.
T — период.
… вот как раз для решения этой задачи, лучше всего и подойдёт режим захвата.
Коротко говоря, выглядит это так: источник сигнала подключается к «ножке» соединённой (внутри МК) с двумя каналами таймера, таймер тикает с заданной частотой
Важно понимать, что таймер должен быть настроен так, чтоб все три импульса
Итак, источником сигналов послужит обычная ардуина
void setup()
{
pinMode(5, OUTPUT);
}
void loop()
{
digitalWrite(5, HIGH);
delay(20); // длина импульса 20 миллисекунд
digitalWrite(5, LOW);
delay(30); // длина периода получится 20 + 30 = 50 миллисекунд
}
… и соединим пин 5 ардуины с пином PA6
В stm загрузим такую программу…
void setup()
{
Serial.begin(115200);
pinMode(PA6, INPUT_PULLDOWN);
Timer3.pause();
Timer3.setPrescaleFactor(72); // один "тик" равен одной микросекунде
Timer3.setInputCaptureMode(TIMER_CH1, TIMER_IC_INPUT_DEFAULT);
Timer3.setInputCaptureMode(TIMER_CH2, TIMER_IC_INPUT_SWITCH);
Timer3.setPolarity(TIMER_CH2, 1);
Timer3.setSlaveFlags(TIMER_SMCR_TS_TI1FP1 | TIMER_SMCR_SMS_RESET);
Timer3.refresh();
Timer3.resume();
}
void loop()
{
if(Timer3.getInputCaptureFlag(TIMER_CH2))
{
Serial.print("Длина импульса ");
Serial.println(Timer3.getCompare(TIMER_CH2));
}
if(Timer3.getInputCaptureFlag(TIMER_CH1))
{
Serial.print("Период ");
Serial.println(Timer3.getCompare(TIMER_CH1));
}
}
В этом режиме таймер работает следующим образом: один канал ловил фронты, а второй ловил спады. При этом оба канала могут обмениваться сигналами внутри микроконтроллера.
Открываем
… и видим то, что и должны были увидеть. Длина импульса 20000 мкс = 20 мс и период 50000 мкс = 50 мс
Теперь в коде для ардуины увеличим длительность LOW с 30-ти до 60-ти миллисекунд…
digitalWrite(5, LOW);
delay(60); // длина периода 20 + 60 = 80 миллисекунд
В терминале вы увидите, что длина импульса будет верной, а период, вместо ожидаемых 80мс, будет какой-то странный. Это происходит потому, что наш счётчик обнуляется раньше чем ардуина поменяет состояние с LOW на HIGH. То есть, сейчас счётчик «тикает» с интервалом в одну микросекунду, соответственно он переполняется через 65535 микросекунд = 65.5 миллисекунд, и конечно же 80 мс сюда никак не влезают.
Для измерения сигналов различной длины, нужно изменять значение предделителя — setPrescaleFactor(). Если мы поменяем 72 на 720, то каждый «тик» таймера будет равен 10 микросекундам, и соответственно диапазон измерений вырастет с 65 мс до 655 мс (65535 «тиков» по 10 мкс = 655350 мкс = 655 мс).
Измените значение и загрузите в плату…
Timer3.setPrescaleFactor(720); // один "тик" равен 10-ти микросекундам
Вот теперь всё в порядке, период равен 8000 (80мс = 80000мкс / 10 = 8000).
На этом наверно пора закругляться. В статье затронута лишь крохотная часть возможностей таймеров, однако на первое время этого должно хватить для использования в рамках
Полезные ресурсы…
Таймеры stm32 (англ.)
Руководство (рус.) по stm32
Основной форум (англ.) — www.stm32duino.com/
Человека, который пилит ядро, зовут Roger Clark — github.com/rogerclarkmelbourne/Arduino_STM32
Ещё разработчик — github.com/stevstrong/Arduino_STM32
HardwareTimer — librambutan.readthedocs.io/en/latest/lang/api/hardwaretimer.html#_CPPv210timer_mode
timer.h — librambutan.readthedocs.io/en/latest/libmaple/api/timer.html#capture-compare-mode-register-2-ccmr2
Телеграм-чат istarik
Телеграм-чат STM32
Купить Arduino STM32
- 0
- stD
67791
Поддержать автора
Комментарии (0)