output compare что это
Продолжаем тему таймеров в STM32. В прошлый раз мы рассмотрели базовые таймеры, которые довольно-таки просты. Но сегодня мы поиграемся с более крутой игрушкой — таймерами общего назначения, которые на голову выше предыдущих.
Вот как вы думаете, если у таймеров общего назначения так много функций, чем тогда продвинутые (advanced-control) таймеры отличаются от них? o_O
Правильный ответ — почти ничем, это по факту просто таймеры общего назначения, которые не имеют никаких ограничений: в них напихано по 4 канала (с комплементарными) и есть все возможности сразу, без какого-то ни было разброса. Так что остальная часть статьи будет относиться ко всем таймерам выше базовых, а продвинутые таймеры я отдельно упоминать не буду.
В даташите на STM32F100xx ( ещё ссылка ) есть сводная таблица возможностей таймеров, в которую тоже удобно поглядывать для справки:
Кстати, обращайте внимание на сноски. Например, там написано, что у МК семейства Low density Value line нет таймера TIM4.
Захват сигнала
В этом режиме с выбранного канала захватываются импульсы, на каждый из них текущее значение счётчика таймера кладётся в регистр TIM_CCRx, где x — номер канала. Таким образом, период следования импульсов равен разнице между текущим значением TIM_CCRx и предыдущим. Ну а для того, чтобы получить период в каких-то внятных единицах измерения, нужно настроить предделитель через функции базового таймера.
При этом можно настроить генерацию прерывания и запроса DMA на приход очередного импульса, и если в это время предыдущее значение TIM_CCRx не было считано, будет сгенерировано так называемое прерывание over-capture, т.е. сигнал о том, что предыдущее значение потерялось.
Ловить можно фронты, спады или и то, и другое вместе. Есть настройка так называемого фильтра — числа выборок, после которого переход уровня будет считаться состоявшимся (полезно для устранения дребезга). Значение фильтра может принимать значения от 0 (фильтр выключен) до 15 (0xF). Также настраивается делитель входной частоты — 2, 4 или 8: будет ловиться каждый 2й, 4й или 8й импульс соответственно.
Примера ради подёргаем вывод PB15 и замерим таймером TIM3 период, подключив PB15 к его каналу 1 (PA6):
Также существует режим захвата ШИМ. На самом деле, это не отдельный режим, а просто особое сочетание настроек с таким эффектом. Таймер настраивается так, чтобы один канал ловил фронты и сбрасывал счётчик таймера, а второй ловил спады — тогда первый будет захватывать период ШИМ, а второй — заполнение. При этом оба канала подключаются к одному и тому же физическому входу. Суть работы этого «режима» показана в даташите следующим образом:
Изменим предыдущий пример, используя захват ШИМ (прокомментированы только изменения):
Режим чтения энкодера
Так как мне захотелось ещё и прерывание заиметь, я сделал период равным 4 и разрешил счёт в обе стороны, так что теперь прерывание будет возникать при каждом щелчке энкодера. Использовал я каналы 1 и 2 таймера TIM3 (PA6 и PA7):
Сравнение вывода (output compare)
В этом режиме выбранный канал таймера будет подключен к соответствующему выводу и будет изменять его (вывода) состояние каждый раз, когда счётчик таймера досчитает до значения регистра TIM_CCRx. Состояние вывода, в зависимости от настройки, будет меняться на ноль, на единицу или на противоположное текущему. У многих таймеров у каналов есть комплементарные выводы, которые по умолчанию являются инверсными: на такой выход подаётся тот же сигнал, что и на обычный, но с противоположным уровнем.
Смотрим в сводную таблицу по таймерам в даташите и видим, что комплементарных выводов у TIM3 нет, но вот у единственного канала таймера TIM16 есть такой вывод — этот таймер я и использую для примера. Вообще, комплементарные выводы есть и у нескольких других таймеров, но вот TIM15 — особенный: у него есть два канала, но комплементарный вывод имеет только 1й канал. Будьте бдительны!
В таблице пинаутов находим выводы канала 1 таймера TIM16 — PB8 (основной) и PB6 (комплементарный). Для иллюстрации работы таймера подключим эти выводы к светодиодам на плате STM32VLDiscovery — PC8 и PC9, которые в коде мы отключим от греха подальше. Таким образом, выводы канала таймера будут напрямую мигать светодиодами:
В это примере я выбрал режим переключения вывода в противоположное состояние (TIM_OCMode_Toggle), а остальные настройки оставил по умолчанию. Кстати, не забывайте вызывать функции типа TIM_OCStructInit() для инициализации соответствующих структур, даже если заполняете все поля структур вручную: copy&paste-ориентированное программирование никто не отменял, но при нём легко забыть заполнить какое-нибудь поле и ловить потом баги.
Для обоих выводов канала можно настроить (поля TIM_OCPolarity и TIM_OCNPolarity структуры) так называемую «полярность» — состояние вывода в промежуток времени от начала отсчёта и до TIM_Pulse. По умолчанию для выводов выставляются значения TIM_OCPolarity_High и TIM_OCNPolarity_High, но комплементарный вывод является инверсным — поэтому, если на нём нужен обычный (не инверсный) сигнал, нужно ему выставить TIM_OCNPolarity_Low.
В режиме сравнения генерируется то же самое прерывание, что и в режиме захвата сигнала — TIM_IT_CCx, но здесь оно было не нужно, поэтому я его не разрешал.
Генерация ШИМ
Настройка этого режима не слишком отличается от настройки output compare: вместо режима TIM_OCMode_Toggle нужно выбрать один из режимов ШИМ, тогда TIM_Period будет трактоваться как период ШИМ, а поле TIM_Pulse — как заполнение (duty cycle). Режимов ШИМ имеется два — выровненный по границе и по центру (edge-aligned и center-aligned). У микроконтроллеров AVR они называются Fast PWM и Phase Correct PWM, соответственно.
Отличной иллюстрацией крутизны таймеров STM32 для генерации ШИМ будет типичная прикладная задача — управление сервомашинкой : Как известно, сервы управляются импульсами переменной ширины, которые шлются с частотой примерно 50 Гц (каждые 20 мс). У сервы, которая оказалась под рукой (Robbe 4.3 g), ширина управляющего импульса от 500 мкс (0°) до 2250 мкс (175°), судя по замерам — то есть, по 10 мкс на каждый градус поворота:
ΔT = T₂ — T₁ = 2250 — 500 = 1750 мкс
∠A = 175°
ΔT/A = 10 мкс/°
Dead-time
Если кто не знает, это задержка фронтов сигналов на основном и комплементарном выводах канала таймера. Эта функция есть у некоторых таймеров (смотрите руководство), и нужна она бывает для исключения сквозных токов при управлении силовыми ключами [полу]мостовых схем. Даже не спрашивайте меня, что это такое — это вне моей компетенции.
Настраивается этот самый dead-time в поле TIM_DeadTime структуры TIM_BDTRInitTypeDef и имеет диапазон значений с 0 по 255 (0xFF). Но смысл этого числа не так уж прямолинеен:
Ага, вот так оно и рассчитывается. Здесь Tdts — это длительность такта генератора dead-time (DTG), зависящая от Tdts — текущей частоты тактирования таймера. Обычно таймеры тактируются системной частотой, и TIM_Prescaler на это никак не влияет, а влияет поле TIM_ClockDivision структуры TIM_TimeBaseInitTypeDef — делитель частоты таймера.
Для примера положим, что таймер затактирован без деления частоты (делитель равен 1, TIM_CKD_DIV1), системная частота F равна 24 МГц, а значение DTG = 150. Тогда:
Tdts = 1/F = 1/24 мкс
DTG = 150 = 100101102 ⇒ DTG[7:5] = 1002
Tdtg = 2⋅Tdts = 1/12 мкс
DT = (64 + DTG[5:0])Tdtg = (64 + 6)/12 = 5.8(3) мкс
«Just like that» ☯ ChosunNinja
Я тут для примера набросал код с dead-time попроще для расчёта: DTG=96 ⇒ DT=96.
Для того, чтобы узреть этот самый dead-time на одноканальном осциллографе, нужно подключить PB8 и PB6 через резисторы 1 кОм к его щупу:
Т.к. на эти выводы идут взаимно инверсные сигналы, на экране будут прекрасно видны места, где во время dead-time уровень на обоих входах одинаков из-за задержки фронтов:
Ну, и напоследок — имейте ввиду, что если длительность dead-time превышает длительность импульса на выводе, то соответствующий импульс не будет сгенерирован вообще.
Счётчик повторений
Этот счётчик имеется у нескольких таймеров (TIM15, TIM16 и TIM17) и выполняет он очень простую функцию: генерировать событие (прерывание или запрос DMA) update не на каждое переполнение счётчика, а на каждые N переполнений. То есть, вы задаёте счётчик повторений, таймер его копирует в скрытый регистр и при каждом переполнении уменьшает значение копии на 1. Когда значение достигает нуля, генерируется событие update, таймер снова копирует счётчик повторений и т.д. На самом деле, перечисленные таймеры и так задействуют этот счётчик, просто по умолчанию его значение равно нулю, и событие генерируется на каждое переполнение.
Счётчик может принимать значения от 0 до 255 (0xFF). Описывать тут особо нечего, потому что для использования этой функции достаточно при инициализации таймера написать что-то вроде:
и всё. В этом случае событие update будет генерироваться каждые 8 переполнений (7 повторений).
Вход BRK
Если вам вдруг понадобится резко перевести выводы каналов таймера в заранее определённое состояние (например, выключить), то эта функция — то, что нужно. Включить её проще пареной репы — нужно сконфигурировать пин TIMx_BKIN на вход, и при инициализации BDTR включить вход BRK:
По умолчанию для активации функции break нужно на вход BRK подать логический ноль, но это можно настроить в поле TIM_BreakPolarity. Как только break активирован, все выводы каналов переходят в состояние, которое задаётся при их инициализации полями TIM_OCIdleState и TIM_OCNIdleState в структуре TIM_OCInitTypeDef (по умолчанию на выводах будет низкий уровень). Dead-time при этом учитывается.
Синхронизация таймеров
Таймеры можно соединять друг с другом для синхронизации или образования цепочек таймеров. В первом случае появляется возможность синхронного старта нескольких таймеров по внешнему триггеру, а во втором — возможность одному таймеру управлять счётчиком другого — запускать, разрешать, сбрасывать.
Второй случай (цепочка таймеров) больше подойдёт для иллюстрации, ибо интереснее он. Сделаем-ка для примера 32-битный таймер из двух обычных 16-битных. Для примера я возьму таймеры TIM2 и TIM3. Задача состоит в том, таймер TIM3 тактировал таймер TIM2 по переполнению своего счётчика: то есть, счётчик таймера TIM2 будет увеличиваться при переполнении счётчика TIM3 — получаем 32-битный счётчик, «состоящий» из TIM2_CNT (старшие биты) и TIM3_CNT (младшие).
Для этого нужно настроить выходной триггер таймера TIM3 на переполнение (update), а входной триггер таймера TIM2 — на вход с триггера TIM3. Смотрим в таблицу соединения триггеров для таймеров TIM2-TIM4 (таких таблиц несколько — для разных групп таймеров):
Здесь мы видим, что TIM3 соединён с входом ITR2 таймера TIM2. И тут выясняется, что в Reference manual рассматриваемый случай описан в разделе «Using one timer as prescaler for another», но там допущена ошибка: вместо ITR2 там указан ITR1. Я джва года час искал ошибку в коде!
STM32. TIMx OUTPUT COMPARE MODE.
В своей прошлой статье я как-то не полностью раскрыл вопросы по использованию таймера. Теперь восполняю этот пробел. Дело в том, что при обилии информации по STM32, конкретных примеров использования таймера в указанном режиме я не встретил в сети. Были подобные статьи, однако описано всё как-то слишком общими фразами.
В своём макете генератора меандра я использовал таймер 2, но это не суть важно, просто пояснять материал я буду именно на нём, и это ни с чем не сязано, просто так легли карты. �?так, управление таймером (да и всем контроллером) заключается в записи в управляющие регистры определённых значений, и контроль определённых разрядов (флагов) в тех же регистрах, если это необходимо. Так вот для правильного управления просто необходимо знать назначение каждого разряда каждого регистра и иметь перед глазами структурную схему таймера.
Сводная таблица регистров таймеров 2-5 микроконтроллера STM32F100xx представлена в документе под названием «RM0041 Reference manual», в Table 73. «TIMx register map and reset values», а структурная схема — Figure 88 на стр.276 того же документа. Я для себя распечатал описание всех регистров контроллера, структурные схемы узлов и сшил листы в виде этакой книжицы.Рассмотрим, что же делает код, представленный в прошлой статье:
AFIO->MAPR = 0x02000300;
Группе бит 26-24, которые управляют JTAG/SWD — интерфейсами, установили значение = 010: (JTAG-DP Disabled and SW-DP Enabled), т.е отключили JTAG при включенном SWD. Группе бит 9-8, которые ремапят таймер 2 установили значение = 11: Full remap (CH1/ETR/PA15, CH2/PB3, CH3/PB10, CH4/PB11), т.е выход его канала CH1 подключили на PA15. Я долго не мог понять, почему же при правильных настройках таймера на его выходе CH1 (PA15) я тупо наблюдаю лог. «1». Переключался на CH2 (PB3), а там вообще какая-то постоянная байтовая посылка! �? именно эта байтовая посылка толкнула меня посмотреть ещё раз на распиновку контроллера. Естественно, я увидел там, что по умолчанию PA15 — это вход JTDI (вот почему там лог. «1»), а PB3 — это выход JTDO (вот откуда байтовая посылка). Надеюсь действия с битами 26-24 (SWJ_CFG) теперь не требуют пояснения
GPIOA->CRH = 0xB0000000; Ну, этот регистр разжёван во многих статьях. Вывод PA15 настраиваем как выход MODE15[1:0] = 11: Output mode, max speed 50 MHz. с максимальной частотой 50 МГц. При этом CNF15[1:0] = 10: Alternate function output Push-pull, он управляется таймером в режиме «тяни-толкай».
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; Для работы таймера на него нужно подать тактирование, что мы и делаем устанавливая бит 0 (TIM2EN), не изменяя остальных, возможно уже установленных битов.
TIM2->CR1 = 0x00000000; Многие настройки таймера необходимо делать при выключенном таймере, поэтому мы его останавливаем, так, на всякий случай, а то вдруг он где-то в программе запущен был. Для этого сбрасываем бит 0 CEN. Заодно сбрасываем и все остальные биты, хотя для нас здесь значение имеет, пожалуй, бит 4 DIR — он задаёт направление счёта. При нуле — счётчик инкементирует значение. Хотя, в принципе это не важно — можно и декрементировать, импульсы всё равно будут.
TIM2->ARR = 1;
Это шестнадцатиразрядный регистр переполнения, до значения которого (0xFFFF — max) счётчик будет считать, а потом обнуляться и считать заново. По нашим условиям он будет считать до 1, т.е. 0,1…0,1…0,1 и т.д.
TIM2->CCMR1 = 0x00000038; Для бит 6-4 OC1M: Output compare 1 mode устанавливаем значение = 011. Оно определяет поведение вывода CH1 (PA15) при равенстве счётного и TIMx_CCR1 — регистра. В нашем случае значение вывода будет меняться на противоположное, ведь мы хотим формировать меандр! Бит 3 OC1PE определяет порядок изменения значения регистра сравнения TIMx_CCR1. Если он сброшен — изменение будет проведено немедленно, даже во время счёта, если установлен — изменение значения произойдет после обновления события. Я решил его установить, хоть это и не важно.
TIM2->CCR1 = 1;
Шестнадцатиразрядный регистр сравнения. Значение должно быть не нулевое и не больше значения регистра переполнения TIM2->ARR, а то сравнение никогда не произойдет. При совпадении значения счётного регистра со значением регистра сравнения произойдёт событие — переключение значения вывода CH1 (PA15). �?менно такое событие мы установили в регистре TIM2->CCMR1.
TIM2->CCER = 0x00000001; Бит 0 CC1E, если канал блока сравнения 1 сконфигурирован как выход, подключает его на соответствующий выходной контакт CH1 (PA15).
Вот таким образом инициализируется таймер для работы в режиме сравнения 1 канала. Вообще-то сюда ещё необходимо добавить оператор, устанавливающий значение делителя счётных импульсов (регистр TIMx prescaler (TIMx_PSC)), однако по умолчанию он нулевой, и я зашарился одной строчкой ))) После инициализации таймера логику его работы можно визуально отследить по структурной схеме ниже:
Как я уже упоминал выше, имея перед глазами структурную схему и описания регистров довольно легко разобраться в работе таймера во всех режимах. На ней видны все настройки, флаги, когда происходят события и пр.
ОБОРУДОВАНИЕ
ТЕХНОЛОГИИ
РАЗРАБОТКИ
Блог технической поддержки моих разработок
Урок 16. Таймеры STM32 в режиме счетчиков. Генерация циклических прерываний от таймеров.
В уроке познакомимся с таймерами микроконтроллера, научимся конфигурировать их в режиме счетчика и организовывать циклические прерывания.
В предыдущем уроке я рассказывал о выполнении задач параллельными процессами. Для реализации такого способа необходимо формировать циклические прерывания с заданным периодом. Опять же в предыдущем уроке было сказано, что логичнее и проще это делать с помощью аппаратных таймеров.
В этом уроке мы будем рассматривать таймеры в качестве именно такого функционального применения. Т.е. будем использовать их для генерации циклических прерываний.
Таймеры STM32.
У нашего микроконтроллера STM32F103C8T6 есть 4 таймера:
Все таймеры имеют одинаковую архитектуру. Расширенный таймер отличается наличием дополнительных аппаратных узлов для формирования противофазных сигналов ШИМ. В результате его можно конфигурировать на работу в режиме 6-канального ШИМ и управлять им тремя полумостовыми усилителями мощности.
У таймеров большой выбор источников тактирования, есть предделители, обеспечивается цифровая фильтрация входных сигналов, возможна синхронизация между собой и много еще чего.
Но сейчас нас интересует исключительно режим счетчика с перезагрузкой. Именно в этом режиме удобнее всего формировать циклические прерывания.
Функциональная схема таймера достаточно сложная. Я выделил только необходимую нам часть.
Собственно отсчет импульсов или времени происходит на 16-ти разрядном счетчике CNT. Когда код счетчика достигает значения регистра перезагрузки, счетчик сбрасывается в 0. Таким образом, счетчик считает по циклу от 0 до значения регистра перезагрузки.
Частота сигнала тактирования таймера может быть уменьшена с помощью 16-ти разрядного предделителя PSC.
Перезагрузка счетчика формирует событие (прерывание). Частота его появления также может быть уменьшена счетчиком повторов (8 разрядов). Коэффициент деления задается в регистре повторов.
Код счетчика используется другими узлами таймера, например, для формирования ШИМ. Но об этом в других уроках.
В качестве источника тактирования могут быть выбраны:
Сейчас мы будем использовать только внутренний источник тактирования.
Режимы счета таймера.
При использовании таймера в качестве счетчика импульсов можно выбрать один из режимов:
При прямом счете содержимое счетчика с каждым импульсом тактирования увеличивается на 1. Когда оно достигает значения регистра перезагрузки, то счетчик сбрасывается. Таким образом,таймер считает по циклу от 0 до значения перезагрузки. В момент перезагрузки формируется прерывание.
В режиме обратного (реверсивного) счета с каждым входным импульсом содержимое счетчика уменьшается на 1. При достижении 0 в счетчик загружается значение регистра перезагрузки и реверсивный счет продолжается. Таймер считает по циклу от значения перезагрузки до 0. В момент перезагрузки формируется прерывание.
Двунаправленный режим означает, что счетчик считает в прямом направлении от 0 до значения перезагрузки, а затем переходит в реверсивный режим и счет ведется до 0. При изменении направления счета и сбросе генерируется прерывание.
Установка конфигурации таймера с помощью STM32CubeMX.
Давайте научимся конфигурировать таймеры через STM32CubeMX. Заодно в строгой форме перечислим регистры, задающие режимы таймера и выясним, что конкретно в них загружать.
Создадим проект Lesson16_1. Настроим конфигурацию системы тактирования. Обратим внимание на то, что частота тактирования таймеров на шинах APB1 и APB2 задана 72 мГц.
Теперь будем конфигурировать таймер 1. В нашем микроконтроллере он самый многофункциональный.
Ниже появилось поле Parameter Settings.
Давайте подробно разберем, что в нем.
Prescaler (PSC).
Это регистр предделителя. Предделитель делит частоту тактирования таймера, поступающую на основной счетчик. По сути, он, вместе с входной частотой, определяет разрешающую способность таймера.
Счетчик предделителя считает входные импульсы от 0 до значения этого регистра. При равенстве кода счетчика и регистра счетчик сбрасывается и начинает считать заново. В момент сброса формируется импульс тактирования основного счетчика таймера. Таким образом, значение регистра предделителя определяет коэффициент деления частоты входного сигнала.
Счетчик и регистр предделителя 16-ти разрядные. Т.е. максимальный коэффициент деления 65536.
Надо помнить, что реальный коэффициент деления на 1 больше, чем значение регистра предделителя. Например:
Значение регистра предделителя | Коэффициент деления |
0 | 1 |
999 | 1000 |
65535 (максимальное значение) | 65536 |
Регистр предделителя имеет буферный регистр. Поэтому его значение можно устанавливать в любой момент. Реальное изменение коэффициента деления произойдет при перезагрузке буферного регистра в момент перезагрузки основного счетчика таймера.
Counter mode.
Режим счетчика, определяет в какую сторону считать.
Counter Period (Auto Reload Register).
Регистр перезагрузки. Его значение задает период работы таймера. Конечно, на время периода влияет еще режим счета.
Счетчик 16-ти разрядный. Значит, для однонаправленного счета период может длиться от 1 до 65536 длительностей импульсов предделителя. Реальная длительность периода на 1 больше значения регистра перезагрузки. Все как для регистра предделителя.
Internal Clock Division (CKD).
Делитель входной частоты для внутренних нужд таймера.
Частота используется при фильтрации внешних сигналов, формировании “мертвого времени” ШИМ и т.п. Сейчас это нам не интересно.
Repetition Counter (RCR).
Регистр счетчика повторов. Присутствует не во всех таймерах. Счетчик повторов считает импульсы событий на выходе таймера и при достижении значения регистра повторов сбрасывается и формирует реальное событие. Т.е. он делит частоту генерации событий (прерываний) таймера.
Auto-reload preload.
Управляет режимом перезагрузки специальный бит, а в STM32CubeMX выбор делается в выпадающем меню.
Вкладка NVIC Settings позволяет выбрать нужный тип прерывания, связанного с таймером.
Пример конфигурации таймера и реализации программы.
Сделаем практическую задачу. Установим конфигурацию таймера 1, обеспечивающую циклические прерывания с периодом 0,5 секунд. В обработчике прерывания будем инвертировать состояние светодиода. В результате получим мигающий светодиод, но с использованием таймера и прерывания.
Частота тактирования у нас 72 мГц. Превратим ее с помощью предделителя в круглое значение.
Например, если задать 720 – 1 = 719, то частота после предделителя будет 72 000 000 / 720 = 100 000 Гц, или период 10 мкс.
Если в регистр перезагрузки задать значение 50 000, то получим требуемый период 0,5 секунд.
Во вкладке NVIC Settings выберем прерывание по перезагрузке счетчика.
Создаем проект и открываем его в Atollic TrueStudio.
В папке Src проекта создан файл stm32f1xx_it.c. Он существовал и во всех предыдущих проектах. Просто мы на него до времени не обращали внимания.
Это файл обработчиков прерываний. Хороший стиль размещать функции обработки прерываний в нем.
В самом конце файла появилась функция:
void TIM1_UP_IRQHandler(void) <
/* USER CODE BEGIN TIM1_UP_IRQn 0 */
/* USER CODE END TIM1_UP_IRQn 0 */
/* USER CODE BEGIN TIM1_UP_IRQn 1 */
/* USER CODE END TIM1_UP_IRQn 1 */
>
Это и есть обработчик прерывания таймера 1. Код, который мы поместим в функцию, будет вызываться с периодом 0,5 секунд.
Вызовем в обработчике прерывания функции инверсии состояния для обоих светодиодов.
void TIM1_UP_IRQHandler(void) <
/* USER CODE BEGIN TIM1_UP_IRQn 0 */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_13);
Мы установили конфигурацию таймера, но не запустили его. Сделаем это HAL-функцией в файле main.c.
/* Initialize all configured peripherals */
HAL_TIM_Base_Start_IT(&htim1); // запуск таймера
Функция запускает таймер в режиме генерации прерываний.
Все. Компилируем, загружаем, проверяем. Оба светодиода мигают раз в секунду.
Полностью проект можно загрузить по ссылке:
Зарегистрируйтесь и оплатите. Всего 60 руб. в месяц за доступ ко всем ресурсам сайта!
Основной цикл у нас пустой. Программа просто крутится в нем.
while (1) <
/* USER CODE END WHILE */
По отношению к нему светодиоды мигают в фоновом режиме, сами по себе. Если мы будем выполнять в цикле какие-либо действия, это никак не скажется на равномерном мигании светодиодов. Единственное условие – надолго не запрещать прерывания.
В следующем уроке будем разрабатывать программу, обрабатывающую сигнал кнопки параллельным процессом. Научимся связывать переменные разных файлов одной программы. Разберемся в специфике использования функций обработки прерываний.