Основы работы с прерываниями stm23. Таймеры stm32.
Добрый день.
Сегодня будет довольно простое занятие - будем разбираться что такое прерывания, как с ними работать.
Параллельно я буду наводить порядок с подключением файлов, которые приходится постоянно копировать из проекта в проект.
Это неправильно, и очень неудобно.
Как обычно - поставим задачу на это занятие:
К выводу PA0 подключен светодиод, необходимо что бы он моргал с частотой от 1 до 100 герц, с шагом 10 герц. Управление частотой реализовать по помощи 2х кнопок. Так же заданную частоту - вывести на блок семисегментных индикаторов.
Первая же мысль - реализовать по помощи HAL_Delay(). Но тогда будет очень неудобно, что нажатие на кнопку будет регистрировать только в очень короткие промежутки времени, когда светодиод меняет своё состояние. Надо придумать что-то ещё.
Для решения подобных задач разработчики микроконтроллеров дали нам блок прерываний. И неразрывно с ним идёт второй блок - событий.
 
Приступим, пока правда только к теории.
Прерывание - это внутренняя команда для микроконтроллера приостановить выполнение кода, и перейти к выполнению так называемого обработчика прерывания. После выполнения этого обработчика - выполнение кода продолжится с места, где оно было прервано.
Давайте посмотрим какие у нас прерывания существуют.
Для этого откроем CubeMX, и создадим новый проект.
После выбора микроконтроллера во вкладке SystemCore выбираем пункт NVIC.
Если просто навести курсор на этот пункт, то мы увидим расшифровку этой аббревиатуры.
Вложенный векторный контроллер прерываний.
В правой части окна отобразились доступные прерывания. Но так как мы идём смотреть все прерывания, выберем пункт - отображать все.
Теперь мы видим все прерывания, которые нам доступны.
Для решения поставленной задачи идеально подходит прерывание от таймера.
Более подробно с таймерами мы познакомимся позднее, а пока что будем считать, что таймер - это "черная коробка", которая умеет считать от нуля до какого то значения, и дойдя до этого значения - генерировать событие переполнения счетчика. Что в дальнейшем можно использовать для вызова прерывания от этого таймера.
Глядя на список прерываний - мы видим что таймер1 использовать не стоит, он какой то сложный, содержит 2 прерывания, и они пока не очень понятны от какого события возникают.
Идём искать таймер попроще. Четырнадцатый визуально нас устраивает.
Активируем его.
В нижней части появился список параметров для настройки.
Prescaler - на какое число необходимо поделить тактовую частоту. Значение шестнадцати битное.
У нас в задаче необходимо мигать светодиодом самое быстрое - 100 герц.
Частоту приходящую на таймеры можно посмотреть на вкладке Clock Configuration. Нас интересует APB1 Timer Clock.
Получается у нас входящая частота 8 мегагерц. Её требуется поделить на 80 000. Идём проверять на калькуляторе это число, и видим что выходим за рамки 16 бит. Хорошо, пишем максимально удобные и доступные нам 8 000.
Далее идёт Counter mode. - тут у нас доступен только один вариант - считать в верх. То есть от нуля и дальше.
Counter Period - До скольки необходимо досчитать таймеру, что бы отправить событие о переполнении. Значение так же шестнадцати битное. Так как после деления частоты у нас от неё осталось всего 1 000 герц, значит нам надо на каждое изменение состояния светодиода 10 импульсов.
А так как отсчет начинается с нуля - то укажем тут 9.
Internal Clock Division - ещё одно деление входящей частоты, если не удалось добиться необходимого прескейлером и счетчиком. Но нам уже не надо.
И последний пункт - auto-reload preload. Если эта настройка включена, то изменив в программе Counter Period (а мы будем его менять) эти изменения вступят в силу не сразу же, а по достижении окончания счета. Для текущей задачи любой вариант приемлем.
Кроме вкладки "параметры" у нас еще есть одна вкладка - NVIC Settings.
Там у нас всего 1 пункт - разрешить глобальные прерывания от этого таймера. Разрешаем.
Далее - как обычно, включаем отладку, включаем SPI в режиме Transmit Only Master, и настраиваем его для работы с MAX7219. Вывод PA0 - светодиод. PF0 и PA4 - кнопки, включим для них подтяжку. Пин PA6 - вывод ChipSelect так же не стоит забывать.
Осталось только задать имя проекту, тулчейн, и можно генерировать код.
Но я сейчас хочу начать проект, который будет развиваться на протяжении нескольких занятий.
И позволит удобнее организовать рабочее пространство.
По этому идём в гит, и создаём новый проект - VoltAmperMeter. Как понятно из названия - глобальная задача - создать законченное устройство, которое будет измерять напряжение и силу тока, потребляемого схемой. Так же это устройство будет являться неким подобием источника питания, для моей макетной платы.
Импортируем проект.
За кадром я создал ещё пару репозиториев, в которые сложил написанные на предыдущих занятиях программы работы с кнопкой и MAX7219.
Они опубликованы от имени пользователя Library, что бы не смешиваться с остальными проектами, и содержат только необходимое для одной, конкретной задачи. http://domstudent.ru:3000/Library
Таким образом, что бы в нашем новом проекте подключить работу с кнопкой, достаточно в корне создать папку Library, и в командной строке написать git submodule add, и далее путь к репозиторию.
Подключим кнопку и MAX7219.
Как видно - в наш проект действительно добавились требуемые файлы и самый большой плюс - при обновлении этих подмодулей в репозитории - в нашем проекте они обновятся автоматически.
Осталось только добавить пару строк в список сборки Cmake.
Для начала - давайте просто разберёмся как мы можем использовать прерывания, которые генерируются таймером. Все прерывания, которые сгенерировал CubeMX аккумулируются в файле stm32f0xx_it.c. Открываем его, и в самом низу видим - TIM14_IRQHandler. Это и есть прерывание нашего таймера. Пока для начала просто напишем сюда переключение состояния на нашем выводе, и посмотрим что произойдёт. Запускаем...и ничего не произошло. Всё верно, у нас хоть таймер и настроен, но не запущен. Для его запуска необходимо в файле main.c вызвать функцию HAL_TIM_Base_Start_IT(), которая в качестве своего единственного аргумента ожидает указатель на структуру таймера. Её наименование мы можем найти пролистав main.c чуть ниже. htim14. Пишем это вот сюда, запускаем, и светодиод загорелся.
Так как у нас сейчас получилась частота 100 герц - глаз не успевает увидеть такое быстрое изменение. Давайте при помощи логического анализатора убедимся что светодиод не просто светит, а действительно очень быстро моргает. Подключаем логический анализатор, запускаем сбор данных, и видим что у нас частота 50 герц. А напомню - мы хотели получить 100. Просчет в том, что таймер нам даёт прерывание 100 раз в секунду, но светодиод же включается и отключается в два раза реже, чем происходит прерывание. Для исправления этого недоразумения - в CubeMX изменим Counter Period на 4. Перегенерируем проект, сразу запускаем, и смотрим что теперь получилось. А вот и наши 100 герц. Теперь надо понять как мы можем менять установленные в CubeMX значения.
Тут путей целых три, и мы пойдём по самому неправильному, зато наглядному.
Установим точку остановки, что бы выполнение программы приостановилось в бесконечном цикле, и посмотрим в список глобальных переменных.
Тут можно увидеть наш четырнадцатый таймер. Развернём, и в Instance видим все регистры, которые у данного таймера доступны. Названия конечно неудобные, но самое главное мы видим сразу. Наш счетчик, который мы установили в CubeMX.
Для проверки - давайте в функции main изменим его на 49, что по расчетам должно дать частоту 10 герц. Прошиваем и проверяем. Что хотели - то и получилось.
Сразу сделаю важное замечание. В связи с тем, что нам необходимо будет не только увеличивать значение порога счетчика, но и уменьшать, может произойти ситуация, когда счетчик досчитал до какого то значения, а мы в этот момент решим уменьшить этот порог. В этом случае - счетчик продолжит считать до переполнения своего значения в 16 бит. По этому либо необходимо в кубе активировать auto-reload preload. Что это такое я объяснял ранее, либо при изменении порога счета - контролировать сам счетчик, и сбрасывать его в случае, если его значение получилось выше порога.
Мы пойдём по простому пути - изменим настройку в CubeMX.
По сути - всё необходимое для решения поставленной задачи мы разобрали.
Осталось написать это в коде.
Приступаем. Создаём и инициализируем кнопки и индикатор.
Создаём переменную, в которой будем хранить заданную частоту.
И при нажатии кнопок - изменяем эту переменную, и в соответствии с ней - Counter Period.
 
Вот собственно и вся программа, для сегодняшней задачи. Заливаем в микроконтроллер, и проверяем.
 
Эх, сломали что то в кубе. при перегенерации проекта удалились строки в CMakeList, которые мы туда добавляли. Вернём их на место, но на будущее - придётся использовать другой механизм добавления библиотек к проекту.
 
Ещё раз запускаем отладку, и опять ничего не работает.
Внимательно смотрим что же мы тут напрограммировали.
Оставлю за кадром поиск причины того, что не заработало как планировалось, Но как оказалось - всё очень просто, я забыл включить подтяжку на кнопках, и ониа начали жить своей жизнью.
Исправляем это в кубе, предварительно скопировав строку из CMakeList.
Ещё раз запускаем, и наконец то видим что при нажатии кнопок мерцание светодиода меняется, и на индикаторе отображается установленная частота.
 
На самом деле таймеры в микроконтроллерах основанных на ядре Cortex умеют очень много чего ещё, помимо просто генерации прерываний по переполнению, но это мы будем разбирать по мере появления задач, требующих большего функционала.
А в рамках задачи вольт-амперметр мы можем в прерывание вынести например обновление состояния кнопок и обновление дисплея, но этом будет уже в рамках следующих занятий.
 
А на этом - всё, спасибо за внимание.