Если лампы зажигают, значит это кому-то нужно.



Плавное возгорание лампочки

В сети можно встретить кучу статей и видео на эту тему, но на мой взгляд очень много интересной теоретической и практической части остаются без внимания. В этой статье вы узнаете о: 
  • таймерах; 
  • широтно-импульсной модуляции (ШИМ/PWM); 
  • прерываниях; 
  • начальной инициализации в CubeMX. 
Вся теоретическая и практическая части относится к микроконтроллерам STM32 в моем случае это STM32F407VG, но ничего страшного, если есть другой микроконтроллер STM с наличием светодиодов и таймеров общего назначения.

Перывания

Прерывания представляют собой некоторый запрос, который нужно срочно обработать. Для этого выполнение основной программы останавливается и вызывается код прерывания (обработчик прерывания), после выполнения которого, управление возвращается к основной программе. Соответственно, чтобы надолго не задерживать выполнение основной программы, выполнение кода обработчика должно занимать мало времени.

В ARM процессорах управлением прерываний занимается Nested vectored interrupt controller (NVIC). Он позволяет назначать приоритет прерываниям. И в случае если во время выполнения прерывания случиться более приоритетное прерывание, то выполнения текущего прерывания будет остановлено, и начнет выполняться более приоритетное (рисунок ниже).
Временная диаграмма прерываний

Таймеры


Микроконтроллеры серии STM32 имеют около 10 таймеров. Первым таймером, которым вы уже вероятно пользовались в функции void HAL_Delay(uint32_t Delay) является system timer, который присутствует практически во всех ARM Cortex-M процессорах. По умолчанию CubeMX настраивает данный таймер на генерацию прерываний с частотой 1 кГц. А код обработчика прерывания просто выполняет инкремент переменной uwTick библиотеки HAL.


Кроме данного таймера у STM32F3 так же имеются таймеры TIM1-TIM14. Данные таймеры облают большими возможностями чем system timer и имеют следующие возможности: 
  • генерация прерываний с заданной частотой 
  • измерение длительности сигналов 
  • генерация сигнала с широтно-импульсной модуляцией (PWM), которая используется для управления большим количеством периферии. Например, c помощью широтно-импульсной модуляции управляются сервоприводы и бесколлекторные двигатели. Мы будем использовать PWM для плавного возгорания и затухания светодиодов. Пример таких сигналов приведен на рисунке ниже. 
Пример сигналов с широтно-импульсной модуляцией

По функциональности таймеры в STM32 делятся на следующие группы:
  1. базовые таймеры. Умеют только генерировать прерывания с заданной частотой; 
  2. таймеры общего назначения. Имеют весь функционал базовых таймеров, но также могут измерять длительность импульсов и генерировать сигналы с широтно-импульсной модуляцией;
  3. расширенные таймеры. Умеют делать тоже самое, что и таймер общего назначения, но имеют дополнительные возможности по генерации сигналов для управления двигателями и периферией.
Рассмотрим общую структуру таймера на примере таймера общего назначения, представленного на рисунке ниже. Не пугайтесь ее, все проще, чем кажется, тем более далеко не все элементы нужно понимать для осознания работы таймеров ;)

Схема таймера общего назначения

Из основных элементов таймера выделим счетчик (CNT counter) и каналы (TIMx_CH1 — TIMx_CH4).


Рассмотрим сначала работу счетчика. На рисунке ниже изображена линия CK_PSC, от которой работает таймер. Обычно она подключена к системной шине и соответственно работает на частоте процессора. Прежде чем попасть на счетчик частота тактового сигнала уменьшается в PSC + 1 раз (данной значение можно настроить), и потом попадает на счетчик. Счетчик представляет собой обычный регистр. Каждый такт с CK_CNT, увеличивает, либо уменьшает его значение на 1. В случае увеличения, в какой-то момент счётчик достигает значения, которое равно значению в auto-reload регистре. В этот момент генерируется событие, например прерывание, и потом счетчик сбрасывается в 0. После чего весь процесс повторяется снова.
Счетчик таймера

Так например, допустим что частота процессора 48 МГц, счетчик тактируется от системной шины, значение PSC равно 479, а значение autorelaod register равно 1999. В этом случае счетчик инкрементируется с частотой 100 КГц (48000000 / (479+1) = 100000), и с частотой 50 Гц генерирует прерывания (100000 / (1999+1) = 50).

Второй важной частью таймера является канал, обеспечивающий возможности изменения длительности входных сигналов, и генерации ШИМ. Центральной частью канала является capture/compare регистр (рисунок ниже).
Схема первого канала таймера

В режиме сравнения capture/compare регистр заранее записывается некоторое значение, которое постоянно сравнивается со значением счетчика. В случае если это значение меньше или равно значению счетчика, то канал на выходе выдает логическую 1, и если больше логический 0. Например, если значение счетчика изменяется от 0 до 500, то записав в capture/compare 400 или 200, то можно получить на выходе канала сигналы, как показано на рисунке ниже. Так же в момент, когда значение capture/compare равно значению счетчика, можно генерировать прерывания.



В режиме захвата, в результате некоторого события, например, изменения напряжения на пине, в capture/compare регистр записывается текущее значение счетчика, и может вызваться прерывание. Из последнего отметим что таймер имеет один счетчик, но может иметь несколько каналов (таймеры STM32 обычно имеют 4 канала).
Реализация плавного возгорания

Выше описанной теоретической базы достаточно, чтобы реализовать поставленную задачу. Если время для подачи напряжения на светодиод очень мало, то светодиод не успеет полностью возгореть из-за этого он будет выглядеть неярко. И наоборот, если время для подачи напряжения на светодиод велико, то он будет выглядеть ярко. Регулировать время подачи напряжения позволит нам широтно-импульсная модуляция.

Для начала попытаемся сделать неяркое свечения светодиода. Для этого нужно понять с какой частотой должен обновляться счетчик и на какую долю его периода нужно подавать напряжение. Допустим счетчик обновляется с частотой 1Гц, то есть он за 1 секунду достигает какого-то значения записанного в auto-reload регистре и обнуляется. Допустим мы на 1*10^-3 секунды подадим напряжение на диод, то остальные 999*10^-3 секунды напряжение в светодиоде будет равно нулю. Очевидно, что простой в 0.999 секунды будет заметен для глаза, а 0.001 хватает для полного возгорания диода (проверено на практике).

Увеличим частоту обновления счетчика, теперь она будет равна 1кГц, соответственно период будет равен 1*10^-3с, который будет состоять из 1мкс подачи напряжения и 999мкс из нулевого напряжения на светодиоде. Получается диод должен возгорать на 1 микросекунду за каждую миллисекунду. Этого должно хватать…

Давайте теперь запрограммируем это. Для того, чтобы вручную не писать инициализацию воспользуемся средством CubeMX. Для начала необходимо выбрать плату, затем найти пины, которые отвечают за диоды (в STM32F407 это PD12-PD15), нажать на них и в выпадающем списке нужно выбрать TIMx_CHx.
Инициализация светодиодов
Затем найти в правой части окна выбрать привязанный к светодиодам таймер и настроить каналы на ШИМ.
Инициализация каналов таймера на ШИМ
Затем посмотрим на конфигурацию тактирования. Здесь я ничего не менял. Из рисунка видно, что частота процессора является 16МГц.
Clock configuration

Далее «идем» во вкладку Configuration и нажимаем на наш таймер.
Configuration
Теперь преступим к инициализации таймера в соответствии с рассчитанными значениями. В вкладке «Parameter Settings» настраиваем значения Prescaler. Я выставил его на 15, значит частота, с которой будет инкрементироваться счетчик будет 16МГц(частота процессора)/(15+1)=1МГц.

Там же инициируем значение Auto-reload регистра я выставил его в 1000. Следовательно частота обновления счетчика будет 1000/1МГц=1кГц. Именно такие значения мы получили в расчете выше. Далее установим поле Pulse в каждом канале нашего таймера в 1. Этот параметр синоним Capture/Compare регистру и в данном случае соответствует времени (1мкс) подачи напряжения в светодиод (ШИМ).
Инициализация таймера
Далее перейдем во вкладку NVIC Settings и включим там прерывание. Это прерывание возникает тогда, когда значение счетчика равно значению в Auto-reload регистре. (Расскажу чуть позже зачем оно нам нужно).
Включение прерывания
Теперь генерируем код и открываем проект в среде разработки! В main’e после инициализации необходимо стартовать ШИМ на каждом инициализированном канале при помощи функции HAL_TIM_PWM_Start(tim4, TIM_CHANNEL_1).






Перепрошьем контроллер, и если вы все сделали правильно, то диод должен гореть неярко.

Как заставить диод гореть неярко мы разобрались — очень быстро включаем и тут же выключаем не давая ему полностью загореться, а глаз в свою очередь не видит простоя. Для того, чтобы он горел ярче необходимо увеличить время подачи напряжения на диод, то есть, чтобы он постепенно возгорал необходимо постепенно увеличивать значения счетчика capture/compare регистра.

Так как мы включили прерывание, которое будет вызывать обработчик каждый раз, когда значение счетчика будет равно значению Auto-reload регистра, то можно с частотой 1кГц (частота обновления счетчика) увеличивать это значение на 1 или уменьшать в случае затухания.

Перейдем в файл stm32f4xx_it.c и найдем созданную CubeMX’ом функциюvoid TIM4_IRQHandler(void). Это обработчик прерывания, в котором поместим алгоритм программы.



Из листинга программы видно что CurrentPulse увеличивается до тех пор пока не достигнет 1000, что соответствует максимальному значению Auto-reload регистра. При такой конфигурации время возгорания как и время затухания равно 1мс(период обновления счетчика)*1000=1с.

Для того, чтобы работа прерывания по таймеру началась, необходимо в main’е его стартовать и убрать старт ШИМа из предыдущего примера, так как он находится в каждом обработчике.
Старт таймера и его прерывания
После перепрошивки диоды должны плавно возгорать и затухать.

Надеюсь статья была написана доступным языком.
Удачи!

(c) Elijah Shall https://vk.com/@gener_it-plavnoe-vozgoranie-lampochki

Популярные сообщения из этого блога

Надежды юношей питают.

Слово о сложности

Опять про QTcpSocket и disconnected