Обработка нажатия пользовательской кнопки, используя внешние прерывания

STM Урок 74. HAL. EXTI или внешние прерывания

Урок 74

HAL. EXTI или внешние прерывания

Вот наконец-то и настало время нам попробовать поработать с внешними прерываниям. Данный урок, во-первых, был очень востребован, хотя он и кажется на первый взгляд несложным. Очень много было просьб и я не мог не откликнуться. Во-вторых, мы сейчас работаем с модулем LAN ENC28J60, у которго имеется выход, по которому мы можем получить прерывания по окончании определённых операций, которые я и хотел обработать. Также, если у меня получится, я хотел этим поделиться и с вами, но без первоначального представления о внешних прерываниях в контроллере STM32, это понять, я считаю, будет, мягко говоря, нелегко. Вот поэтому и созрел данный урок.

В качестве микроконтроллера мы возьмём тот же самый STM32F103RCT6, расположенный на недорогой плате, с которой мы и занимаемся в процессе программирования модуля LAN, а в качестве программатора также недорогой маленький ST-Link V2.

Кратко о внешних прерываниях. Внешние прерывания – это такие прерывания, которые обрабатываются вследствие возникновения некоторых событий на определённой ножке порта микроконтроллера. Таких событий может быть несколько не смотря на всего 2 логических состояния. Разнообразие данных событий легко увидеть, раскрыв их список в Cube MX в настройке ножек портов

Можно разделить данные типы на 2 группы пополам. первая группа – External Interrupt – это обработка внешних прерываний. А второй – обработка событий. Разница здесь лишь в том, что в первом случае вызывается обработчик прерываний, а во втором только поднимается соответствующий флаг. Каждая группа уже делится на три вида событий:

1. Обнаружен восходящий фронт (изменение уровня 0 на 1),

2. Обнаружен нисходящий фронт (изменение уровня 1 на 0),

3. Обнаружен любой из вышеперечисленных фронтов.

Также ещё есть программные типы прерываний, но нам с вами они точно не понадобятся.

Вот и всё по типам прерываний.

Существует несколько регистров для обработки внешних прерываний:

EXTI_IMR: Регистр масок прерываний,

EXTI_EMR: Регистр масок событий,

EXTI_RTSR: Регистр срабатывания по восходящему фронту,

EXTI_FTSR:Регистр срабатывания по нисходящему фронту,

EXTI_SWIER: Регистр софтверного запуска прерывания,

EXTI_PR: регистр флагов событий, по которым происходят вызовы прерываний.

Подробно все эти регистры расписаны в технической документации Reference Manual ко всем контроллерам STM. Мы их подробно рассматривать не будем, оставим это на совести библиотеки HAL.

Вот логическая схема обработчика прерываний в контроллере

Здесь мы видим наши все регистры и какая между ними логическая связь.

Также можно посмотреть схему организации линии внешних прерываний

Здесь мы видим 16 линий, подключенных через мультиплексоры к одноимённым пинам всех портов. То есть одновременно мы можем обработать 16 ножек контроллера, но, как видно из мультиплексивной организации, что все они должны быть с разными номерами. То есть мы можем обработать одновременно ножки PA1 и PC2, но не можем обработать PA1 и PC1.

Также существуют ещё 4 линии, которые подключены не к портам общего назначения, а к определённым видам периферии

The four other EXTI lines are connected as follows:

• EXTI line 16 is connected to the PVD output

• EXTI line 17 is connected to the RTC Alarm event

• EXTI line 18 is connected to the USB Wakeup event

• EXTI line 19 is connected to the Ethernet Wakeup event (available only in connectivity line devices)

То есть мы можем ещё обработать внешние события от программируемого детектора напряжений, от будильника RTC, от «пробуждений» USB и Ethernet.

Вот сколько всего по прерываниям, и это ещё не всё. На данную тему в технической документации написано целых 18 страниц, так что кому интересно – обязательно почитайте.

Наша задача – грамотно эти прерывания обработать в нашем коде, используя функции библиотеки HAL.

Создадим в генераторе проектов Cube MX новый проект, выбрав наш контроллер

Включим кварцевый резонатор

Выберем программатор по интерфейсу SWD

Задействуем ножку, отвечающую за светодиод

Включим внешние прерывания на ножке PA1

Настроим делители и умножители на максимальную производительность в разделе «Clock Configuration» (нажмите на картинку для увеличения изображения)

Перейдём в «Configuration» и в разделе GPIO настроим ножку PC13 на среднюю скорость

Для ножки PA1 применим подтягивающий регистр к общей шине, чтобы она не болталась в воздухе, так как прерывания мы будем ловить от кнопки, а сама кнопка никуда у нас не притянута, иначе состояние будет неопределённое. Тип внешнего прерывания оставим по умолчанию – восходящий фронт

Применим настройки портов и перейдём в настройки глобальных прерываний по кнопке «NVIC», включим там прерывания EXTI1

Также применим данные настройки.

Также в силу того, что у меня тормозит видеозапись при работе с Keil, я решил воспользоваться средой разработки System Workbench, с которой мы уже неплохо знакомы. Вы можете также работать с Keil или с любой другой средой разработки, разницы нет.

Настроим проект, присвоив имя EXTI01 и выбрав среду разработки SW4STM32

Сгенерируем проект, откроем его в System Workbench, как всегда и удалим в настройках отладчика файл отладки.

Соберём проект, попытаемся его запустить, но скорей всего у нас ничего не получится и мы увидим в консоли вот такое сообщение, что прошить контроллер не удалось из-за того, что его не удалось перезагрузить

Конечно, можно вовремя держать и отпускать на плате кнопку RESEТ. Но мы пойдём другим путём. Я почитал некоторое количество форумов и ничего там не нашёл, за исключением правки конфигурационных файлов. Откроем наш файл отладки в свойствах проекта

В открывшемся диалоге зайдём в закладку «Debugger» и нажмём там кнопку «Show generator options»

В открывшемся внизу поле «Reset mode» выберем пункт «Software System reset«

Сохраним настройки, теперь должно всё прошиться.

Откроем main.c и отключим наш светодиод в функции main(), так как мы знаем, что он подключен инверсно (можно конечно это сделать и в Cube MX, но так нагляднее)

/* USER CODE BEGIN 2 */

HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);

/* USER CODE END 2 */

Далее в main.c добавим обработчик внешних прерываний

/* USER CODE BEGIN 4 */

void HAL_GPIO_EXTI_Callback( uint16_t GPIO_Pin)

/* USER CODE END 4 */

Затем мы здесь отследим прерывание именно от 1 линии и зажжем светодиод

void HAL_GPIO_EXTI_Callback( uint16_t GPIO_Pin)

if (GPIO_Pin== GPIO_PIN_1) <

HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET );

__NOP();

/* USER CODE END 4 */

Подключим кнопку между PA1 и проводом питания (нажмите на картинку для увеличения изображения)

Соберём проект, прошьём контроллер. Затем нажмём на кнопку – светодиод должен будет засветиться (нажмите на картинку для увеличения изображения)

Давайте разнообразим немного наш проект, чтобы он не был таким скучным. Для этого зайдём в Cube MX, и, так как у нас не Keil, проект закрывать не надо, и включим внешние прерывания ещё и на ножке PA2

Причем включим мы уже другой тип – срабатывание прерывания по низходящему фронту, а резистор мы уже наоборот подтянем к источнику напряжения. Для этого внесём следующие настройки в «Configuration» в GPIO для ножки PA2

Применим настройки, сгенерируем проект, вернёмся в System Workbench и сделаем проекту Refresh.

Допишем следующим образом наш обработчик, чтобы по прерыванию на ножке PA1 наш светодиод зажигался, а в случае PA2 – потухал

void HAL_GPIO_EXTI_Callback( uint16_t GPIO_Pin)

if (GPIO_Pin== GPIO_PIN_1) <

HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET );

> else if (GPIO_Pin== GPIO_PIN_2) <

HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET );

Подключем ещё одну кнопку, но уже к PA2, причём вторым выводом уже к общему проводу (нажмите на картинку для увеличения изображения)

Теперь, если мы соберём код и прошьём контроллер, то первая кнопка будет у нас светодиод зажигать, а вторая тушить.

Казалось бы, что тут такого? А то, что происходит это уже независимо от хода самой программы, не в бесконечном цикле, когда во время там написанного обработчика по нажатию кнопки мы можем вообще не находиться и будет уже не ясно, обработается ли наше событие. Поэтому, изучив внешние прерывания, мы сделали большой шаг вперёд к независимости обработки тех или иных событий на ножках портов микроконтроллера. Конечно, учитывая наш уже теперь очень богатый опыт, нам было это проделать не очень тяжело.

Читайте также  Скрытая проводка в кирпичном доме

Всем спасибо за внимание!

Отладочную плату можно приобрести здесь STM32F103C8T6

Смотреть ВИДЕОУРОК (нажмите на картинку)

STM32F3 и EXTI. Использование внешних прерываний.

Доброго всем дня! Продолжаем работать с отладочной платой STM32F3Discovery, и сегодня мы разберемся как настроить и использовать внешние прерывания в микроконтроллерах серии STM32F3.

Что такое вообще внешнее прерывание? Ну тут особо нечего рассказывать – это просто такое прерывание, которое возникает при изменении состояния определенного входа микроконтроллера.

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

У STM32F3 имеется в наличии целых 36(!) внешних прерываний. Прерывания EXTI0…EXTI15 висят на обработке изменения уровня сигнала на обычных ножках нашего контроллера. То есть прерывание EXTI0 мы можем настроить на работу с ножками PA0, PB0, PC0, PD0, PE0 или PF0, аналогично для EXTI3 – выводы PA3, PB3, PC3, PD3, PE3 или PF3. Вот как это все выглядит:

Возникает вопрос – а остальные 20 прерываний? А вот они:

Вот, например, в статье про будильник (вот она) мы использовали EXTI line 17, для пробуждения по будильнику.

Итак, с теорией мы разобрались немножко, давайте напишем тестовую программку для работы с внешними прерываниями. А заодно, пожалуй, поэкспериментируем-ка с пользовательской кнопкой (синего цвета), установленной на плате STM32F3Discovery Пусть будет такая задачка:

  • опрашиваем состояние кнопки (ножка PA0)
  • если кнопка нажата, то выставляем вывод PE2 в 1 (высокий уровень сигнала)
  • замыкаем на плате ногу PE2 на вывод PE1
  • настраиваем внешнее прерывание так, чтобы оно возникало при изменении сигнала на PE1 с низкого уровня на высокий
  • и в обработчике прерывания меняем состояние какого-нибудь светодиода из установленных на плате – пусть будет синий (PE8)

Что же мы ожидаем увидеть в результате всего этого? Сейчас разберемся…

При нажатии на кнопку сигнал на выводе PE2 меняется с 0 на 1, а, соответственно, аналогичным образом меняется сигнал и на выводе PE1, что приводит к возникновению внешнего прерывания. А в обработчике, как вы помните, мы должны менять состояние светодиода. Таким образом, при нажатиях на кнопку синий светодиод должен то зажигаться, то гаснуть. Немного запутанно, конечно, но для примера подойдет

Переходим к делу..Создаем новый проект, подключаем все нужные файлы и объявляем переменные:

Пора переходить к инициализации! Для того, чтобы настроить правильную работу внешних прерываний нужно сделать следующее:

  • настраиваем нужный вывод на работу в режиме входа
  • используя функцию SYSCFG_EXTILineConfig() выбираем, какой вывод и какого порта будем использовать в качестве источника внешнего прерывания
  • настраиваем поля структуры EXTI_InitTypeDef и, как и при работе с любой другой периферией, вызываем функцию инициализации – в данном случае это EXTI_Init()
  • аналогично производим настройки прерывания при помощи структуры NVIC_InitTypeDef

Теперь осталось реализовать все эти шаги в коде:

Почти все готово, осталось написать функцию main() и, собственно, обработчик прерывания:

В функции main() опрашиваем нашу кнопку и в зависимости от ее состояния меняем уровень сигнала на выводе PE2, который, как вы помните, у нас замкнут на ножку PE1. При нажатии кнопки должно возникать прерывание EXTI1_IRQ, в обработчике которого меняем состояние вывода PE8, на котором у нас висит светодиод.

Компилируем, прошиваем микроконтроллер, жмем на кнопку и видим результат – на каждое нажатие кнопки синий светодиод меняет свое состояние. Соответственно, программа работает именно так, как и было задумано 🙂

Обработка нажатия пользовательской кнопки, используя внешние прерывания

В статье пользователя Rough был описан способ обработки нажатия пользовательской кнопки с использованием чтения состояния портов ввода/вывода, а так же таймера. В случае, когда разрабатываемое устройство должно незамедлительно реагировать на какое — либо событие (например, нажатие кнопки, или срабатывание датчика), более уместно использовать внешние прерывания.

Внешние прерывания, как уже говорилось выше, используются, когда нецелесообразно постоянно опрашивать состояние датчика, или в случаях, когда возникает дилемма между частотой опроса кнопки/датчика и временем реакции на интересующее нас событие.

Микроконтроллеры семейства STM32F4xx имеют 23 линии внешних прерываний. Первые 16 линий используются для формирования запросов на прерывание от любых из пинов микроконтролерра для портов от A до H, к оставшимся же линиям «привязаны» события от периферии, например USB, Ethernet, часов реального времени, а так же PVD (Programmable voltage detector), модуля, позволяющего контролировать изменение напряжения питания микроконтроллера[1].

Использование альтернативных функций, которые совмещены с портами ввода/вывода, позволяют обрабатывать до 83 различных событий, полный перечень которых можно найти в Reference manual [2]. Но здесь есть одно ограничение, если процедура прерывания сконфигурирована таким образом, что к линии EXTI_Line0 «привязан» пин PA0, то мы не имеем права добавлять к этой линии любой другой источник прерывания от выводов PB0 – PH0. Зато у нас есть возможность выставить до 16 уровней приоритета на обработку прерываний, а так же настроить вывод на регистрацию фронта, спада, или же любого изменения уровня сигнала.

При работе с внешними прерываниями мы будем использовать дополнительные библиотеки stm32f4xx_exti.h, stm32f4xx_syscfg.h и misc.h. Первые две необходимы для конфигурации процедуры прерывания, а последняя, для настройки обработчика запроса на прерывание. Среда разработки CooCox v1.7.6.

Чтобы можно было визуально подтвердить то, что микроконтроллер обрабатывает внешнее прерывание сформулируем такое техническое задание: при нажатии пользовательской кнопки светодиоды, расположенные на плате начинают загораться с некоторым интервалом времени, при повторном нажатии светодиоды гаснут.

Для реализации этого задания нам необходимо выполнить следующие основные пункты:

  1. Сконфигурировать порты ввода/вывода для светодиодов;
  2. Сконфигурировать процедуру прерывания и обработчик запроса на прерывание;
  3. Написать код обработчика прерывания.

Итак, подключаем библиотеки, объявляем переменные, создаем функцию init_led_port, которая будет отвечать за инициализацию выводов, к которым подключены светодиоды. Каждая строка кода имеет подробное описание, поэтому будем двигаться дальше.

Теперь перейдем к функции, в которой будем задавать конфигурацию нашей процедуры прерывания.

Для начала проинициализируем порт, которому подключена пользовательская кнопка и контроллер конфигурации системы, который управляет ремапингом выводов микроконтроллера. Настройка пина, к которому подключена пользовательская кнопка полностью аналогична настройке порта со светодиодами.

Строка является очень важной, так как именно здесь мы конкретизируем, что к нулевой лини прерывания мы хотим подключить пин PA0, а не какой либо другой.

Далее следует настройка остальных параметров задающих приоритет, вид изменения сигнала, для генерации запроса на прерывание, а так же конфигурация обработчика запроса на прерывания (структура nvic)

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

Осталось написать последний элемент — обработчик прерывания. Имя функции-обработчика должно обязательно соответствовать номеру линии, на которой мы ожидаем прерывание, то есть для нулевой линии это будет EXTI0_IRQHandler, для первой EXTI1_IR QHandler и так далее.

Здесь мы сталкиваемся с небольшой проблемой — дребезг контактов. Существует два основных метода программной борьбы с этим явлением — временная задержка и подсчет числа совпадающих значений сигнала. Мы воспользуемся вторым методом. Наш цикл в течении 140 итераций проверяет остается ли нажатой кнопка, и в случае появления низкого потенциала (кнопка оказалось отпущенной или дребезг еще не закончился) счетчик сбрасывается. В нашем случае необходимо достаточно большое количество итераций цикла. Это связано с видом кнопок, которые мы используем, применение мембранных кнопок позволяет сократить время дребезга примерно в 10 раз. Наличие оператора return может показаться избыточным, но по субъективным ощущениям это повышает надежность функционирования обработчика прерывания.

Формируем основное тело программы:

Список используемой литературы и источников:

Читайте также  Жучки, передатчики и приемники: основные термины

STM32 Удобные внешние прерывания

STM32 Удобные внешние прерывания

При изучении STM32 меня приятно удивило большое количество внешних прерываний (всего 16), да и еще возможность настроить их на любой пин микроконтроллера. Такой расклад после того, как долгое время сидел на AVR — кажется фантастичным. Однако при детальном изучении радость моя несколько поуменьшилась. Оказывается, есть и ограничения:
1) Настроить прерывания можно только на один из входов, где совпадает нумерация порта.
Например, настроить два прерывания на GPIOA.0 и GPIOB.0 — не получится. Только одно из них. Это необходимо учитывать при разводке схемы.
2) Отсутствует способ вызова прерываний по нижнему уровню (иногда требуется именно этот способ, например при сочленении с WIZnet). Хотя этот недочет, в принципе, — программно решаемый.
3) Всего 7 векторов в прерываниях. А событий — 16. А это значит, что обработка нескольких событий производится в одном и том же месте.

Но даже такое положение вещей все равно гораздо более привлекательное, чем в старом-добром AVR-e.

Цель написания этой статьи — разбавить 3-е по списку ограничение — недостаток векторов, упрощение инициализации внешних прерываний. И напоследок, разберемся, что такое «слабосвязанные функции» и с чем их едят (С) Умка :). Итак, начнем:

У нас есть 16 внешних событий и 7 обработчиков. Так каким же образом все организовано? А очень просто:
Первые пять событий имеют свои собственные вектора (от 0 по 4 включительно), а вот остальные события делят оставшиеся два вектора приблизительно поровну:
Внешние события с 5 по 9 — группируются в один вектор, и с 10 по 15 — в другой.
Чтобы узнать, какое именно событие возникло, необходимо опросить регистр внешних прерываний EXTI->PR.
Пример:

где Number_of_event — это номер события

Все бы хорошо, да вот одна незадача: Внешние прерывания, обрабатывающиеся в одном векторе, используются в разных сочлененных устройствах, которые в свою очередь описаны в разных модулях. И как быть?
Одно из решений я предлагаю рассмотреть здесь.
Создадим отдельный модуль, в котором поместим шаблоны («рыбы») этих векторов и разместим в них вызов слабосвязанных функций. Преимущество таких функций в следующем: если тело вызываемой функции будет отсутствовать — не страшно. Она попросту не будет вызываться. Причем тело функции может располагаться в любой части программы, и обеспечивать видимость для нашего модуля совсем необязательно.
Итак, названия этих функций сделаем фиксированными по типу EXTIx_IRQHandler, где x — это номер прерывания. А уже в модулях периферии можно будет разместить тела тех самых вызываемых функций.

external.h

Данный модуль использует другие модули, такие как bitbanding.h
Рассмотрим подробнее, что и для чего здесь написано:
Макрофункция GPIO_TO_INT преобразует имя структуры порта GPIO в ее порядковый номер.
Например, GPIO_TO_INT(GPIOA) — вернет , GPIO_TO_INT(GPIOB)1, и т.д. Напрямую использовать это нет нужды, однако эта макрофункция вызывается из основной макрофункции EXTI_INIT, описанной ниже. Она нам пригодится для удобной настройки прерывания.

Макрофункция PIN_TO_EXTI_IRQn(PIN) возвращает имя прерывания, для последующей инициализации в NVIC.
Пример: PIN_TO_EXTI_IRQn(9) преобразится к виду EXTI9_5_IRQn
Этот макрос так же будет использован основным макросом инициализации внешнего прерывания.

Макрос PIN_TO_EXTI_HANDLER(PIN) преобразуется к виду вызываемой функции нашими «рыбами».
Таким образом описав следующее:

получим, что ENCODER_IRQ_HANDLER преобразуется к виду EXTI10_IRQHandler, а ведь именно функцию с таким именем будет вызывать наш модуль.

Ну, и напоследок, о главном:
Макрофункция EXTI_INIT(GPIO, PIN, EXTI_MODE, NVIC_PRIORITY), где
GPIO — порт пина,
PIN — номер пина,
EXTI_MODE — режим прерывания. их 4:
EXTI_MODE_DISABLE — обработка события выключена
EXTI_MODE_RISE — событие настроено на подъем фронта
EXTI_MODE_FALL — по спаду
EXTI_MODE_BOTH — по подъему и спаду.

NVIC_PRIORITY — приоритет прерывания.

Следующий листинг — исполняемый файл external.c

Разберем все по порядку: вначале идут инклюды: Здесь ничего особенного.
Ниже у нас описания вызываемых функций. Они описаны как слабосвязанные. Подробную информацию по этой теме можно почерпнуть с официального сайта KEIL здесь

далее — пошли наши «рыбы». Здесь тоже ничего необычного. Видно, что «рыба» представляет собой простые вызовы слабосвязанных функций по условию. Поэтому заботиться о сбросе флагов нужно в самих функциях, впрочем, это обычная практика.

Здесь за ненадобностью отсутствуют описания векторов с 0 по 4. Понятное дело, они работают и без того красиво.

Вот и получается, что появился дополнительный ряд функций, по виду похожих на стандартные
EXTI5_IRQHandler, EXTI6_IRQHandler и т.д.

Итак, приведу пример, как должно выглядеть оформление в заголовке модуля сочлененного устройства:

Здесь указаны настройки пинов для OP01 и OP02 (так названы связи в принципиальной схеме).

В самом модуле устройства можно написать следующее:

Здесь задефайнены имена вызываемых функций, которые будут преобразованы к стандарту EXTIx_IRQHandler нашего модуля. Этот этап необязателен, если вы будете использовать жестко назначенные имена функций и номера прерываний.

Здесь используются макросы из модуля gpio_init.h.
Функции Enc_OP01_IRQHandler и Enc_OP02_IRQHandler будут преобразованы к виду EXTI9_IRQHandler и EXTI8_IRQHandler препроцессором соответственно, что нам и требуется.

Итак, в модулях теперь у нас становится все красиво и лаконично. Теперь можно не думать, какие еще события в тех же векторах обрабатываются в других модулях. Каждому модулю принадлежит лишь та часть кода, которая относится непосредственно к этому модулю

Обработка нажатия пользовательской кнопки, используя внешние прерывания

_________________
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется.

JLCPCB, всего $2 за прототип печатной платы! Цвет — любой!

Зарегистрируйтесь и получите два купона по 5$ каждый:https://jlcpcb.com/cwc

Сборка печатных плат от $30 + БЕСПЛАТНАЯ доставка по всему миру + трафарет

если вы начинающий — зачем вам сложности с таймером? я уже так много раз рассказывал про кнопки, что скоро тошнить будет. но все-таки повторюсь.

алгоритм опроса кнопки должен быть следующим:
1. считываем состояние кнопки (или кнопок, если их несколько) в какую-то переменную, пусть это будет key
2. делаем задержку на 10-15 миллисекунд, за которые дребезг обычно должен кончиться
3. снова считываем состояние кнопки и сравниваем его с key
4. если оба значения одинаковы — то key и будет соответствовать текущему состоянию кнопки (кнопок), можно его обрабатывать
5. если значения разные — то мы считаем, что дребезг еще не кончился и тогда вместо key выдаем 0, что должно означать отсутствие нажатия кнопок.

на языке Си это может выглядеть так:

// макрос для задержки подалвения дребезга в миллисекундах
#define DEBOUNCE 15

uint8_t get_key_code(void); // эта функция вернет состояние кнопок

// функия опроса кнопок, вернет 0, если нет нажатий, не ноль — если есть
uint8_t get_key(void) <
uint8_t key;

key = get_key_code();
delay_ms(DEBOUNCE);
if(key == get_key_code())
return key;
else
return 0;
>

так как обычно кнопки с порта вешают на землю, то нажатому состоянию соответствует 0 в соответствующем пине. чтобы не нарушать логику, я инвертирую это значение, чтобы не нажатому состоянию соответствовал 0, а нажатому 1.

если вы повесили 4 кнопки на PORTB, то поступаете вот так:

// определяем константы для каждой кнопки
#define KEY1 _BV(PB0)
#define KEY2 _BV(PB1)
#define KEY3 _BV(PB2)
#define KEY4 _BV(PB3)
// а вот так мы обозначаем сразу все наши кнопки
#define ALL_KEY (KEY1 | KEY2 | KEY3 | KEY4)

uint8_t get_key_code(void) <
return

// где-то в глубине своей программы

switch(get_key()) <
case KEY1: // тут обработка кнопки 1
break;
case KEY2: // тут обработка кнопки 1
break;
case KEY3: // тут обработка кнопки 1
break;
case KEY4: // тут обработка кнопки 1
break;
case KEY1 | KEY3: // тут обработка одновременного нажатия сразу 2 кнопок
break;
default:
// а тут обработка всех прочих вариантов, в том числе отсутствия нажатия
>

как видите, все элементарно просто, и никакие таймеры не нужны. когда заматереете, как программист, тогда, возможно, начнете обрабатывать кнопки и с прерываниями и таймерами, но лично мне в огромном количестве поделок практически никогда это не требовалось.

P.S. да, примеры я привел с ориентацией на avr-gcc, если вы любитель CodeVision, вам нужно перед всем этим добавиь пару строчек:

typedef unsigned char uint8_t;
#define _BV(x) (1
_________________
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется.

Читайте также  Штробление стен под проводку без пыли

При замене в современном автомобиле электромеханических реле на интеллектуальные силовые ключи PROFET производства Infineon необходимо учитывать особенности их коммутации по сравнению с «сухими контактами» реле, а также особенности управления с их помощью различными типами нагрузок.

понимать как должна работать программа этого очень мало — это лишь техническое задание.
Алгоритм — это решение техзадания и не зависит от языка его можно написать хоть на русском(блок-схемы, графическая форма, карта состояний), а уже алгоритм преобразуется в программу практически чисто механически с учетом возможностей конкретного языка.

Берешь указательный палец правой руки и водишь им по программе предсказывая её поведение в каждый момент времени. Это возможно, поскольку программа работает строго определенным образом и не зависит от нашего желания — обычно когда за строгость принимаешь собственное желание возникает неожиданное поведение программы.

Про обход дребезга я уже написал(ниже). Единственное что отмечу — тебе надо считать именно количество моментов отпускания(или нажатия?) кнопки.
Значит в кратце обрисуем алгоритм.
У нас есть бесконечный цикл, внутри него первым шагом стоит задержка 10мс, потом считывание кнопки +сохранение его в переменную, и CASE на 4 состояния кнопки(текущее и предыдущее)
из всех 4-х состояний нам интересно лишь одно — текущее:кнопка нажата прошлое:кнопка отпущена. Как обнаруживаем это состояние — прибавляем +1 к количеству нажатий. И делаем все что нам надо еще — проверяем какое нажатие, если 2-е то что-то делаем и т.д.
Перед переходом к началу цикла копируем текущее состояние кнопки в предыдущее. Замечу еще раз: чтобы не нарваться на дребезг считывать кнопку надо только один раз за итерацию, а внутри цикла работать только с сохраненным значением.

Чрезмерно усложненный алгоритм. Чтобы победить дребезг АБСОЛЮТНО ДОСТАТОЧНО считывать состояние кнопки ОДИН РАЗ на интервале минимум 10мс. Т.е. можно больше и даже немного меньше — зависит от качества кнопки(герконам, говорят, хватает 2мс). Но больший интервал уже может приводить к заметной человеком задержку реакции на нажатие.

Если алгоритм не критичен к скорости выполнения, то можно вставить такую задержку в главный цикл и в начале его считывать состояние кнопки и уже им пользоваться внутри цикла вполне безопасно. Тогда каждая итерация основного цикла будет занимать примерно 10мс, этот факт надо учитывать и использовать(для отсчета времени, например).
Иногда нужно знать предыдущее состояние для отлова моментов нажатия/отпускания — тогда достаточно в конце цикла, сразу перед переходом на следующую итерацию сохранить текущее состояние в переменную прошлого состояния кнопки. Внутри цикла если сравнивать текущее значение и прошлое можно выделить 4 состояния: 1) момент нажатия, 2) кнопка нажата и удерживается 3) момент отпускания 4) кнопка отпущена.

Вебинар посвящен проектированию и интеграции встроенных и внешних антенн Quectel для сотовых модемов, устройств навигации и передачи данных 2,4 ГГц. На вебинаре вы познакомитесь с продуктовой линейкой и способами решения проблем проектирования. В программе: выбор типа антенны; ключевые проблемы, влияющие на эффективность работы антенны; требования к сертификации ОТА; практическое измерение параметров антенн.

_________________
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется.

ну, это тоже не совсем верно: одного минимального интервала совсем не достаточно

если вы будете опрашивать по 1 разу кнопку каждые 5 секунд, вы пропустите минимум 10 моих нажатий — я страшно быстро на кнопки нажимаю

_________________
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется.

и чем же это будет лучше (проще?) обычного периодического опроса кнопки, предлагаемому мной? использованием лишнего ресурса — таймера?

в сущнсти, все вами сказанное отторжения у меня не вызывает — варианты известные и применяемые по ситуации. мне приходилось вешать кнопки на линии вывода на ЖКИ — вот там пришлось поизвращаться с опросом, и то обошелся без таймеров и прерываний
но надо ли так все усложнять начинающему?!

_________________
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется.

как изменится ваш код для 16 кнопок матрицей? для 3 кнопок на 3 разных портах? если таймер занят чем-то? если пинов с аппартными запросами прерываний нет свободных?

вы каждый раз будете изобретать новый костыль? я — нет

_________________
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется.

volatile unsigned char per=0;

while (1)
<
unsigned char i, dr;
if (PINC.0==0) // если кнопка нажата
<
dr=0;
for (i=0;i 5)
<
per++; // увеличиваем переменную

if(per==4) // если переменная = 4
<
PORTB.0=1;
per=0; //обнулить переменную
>

Фигня. У вас каждые N мс пока кнопка нажата будут считаться как отдельные нажатия кнопок.(похоже что N = 500).

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

Дальше, берем и сравниваем это значение с сохраненным в предыдущей итерации — если одинаковое ничего не делаем, если значения отличаются — значит произошло нажатие или отпускание а что именно определить можно из текущего состояния.

HAL: работа с пользовательской кнопкой

Краткое описание: Начиная с этой статьи все настройки периферии будут производиться в версии STM32 Cube Mx 4.1.0.0. Принцип работы с программой не изменился, хотя дизайн стал немного другим. Для работы с кнопкой напишем собственную функцию которая будет принимать в качестве аргумента номер кнопки (на нашей плате пользовательская кнопка одна), проверять ее состояние и возвращать 1 если кнопка нажата или 0 в обратном случае.

Напомню что пользовательская кнопка на плате CodeIN alfa подключена к ножке PC0. Для индикации состояния кнопки будем использовать пользовательский светодиод и написанную в прошлой статье функцию для работы с ним.

Задача: Требуется создать новый проект в новой версии программы CubeMX, сконфигурировать периферию GPIO, сгенерировать проект для IDE KeiluVision5. Также необходимо добавить файлы «button.h«, «button.c» и написать функцию для удобной работы с пользовательской кнопкой.

Запускаем CubeMX и создаем новый проект рисунок 1.

Выбираем ядро нашего микроконтроллера и в представленном списке находим нашу модель рисунок 2.

В появившемся графическом редакторе, видим графическое изображение нашего микроконтроллера и список доступной периферии. Настройки начинаем с тактирования рисунок 3.

Теперь нужно настроить как выход ножки для светодиодов и как вход ножку для кнопки рисунок 4.

Переходим во вкладку Clock Configuration и выставляем галочки как на рисунке 5.

Теперь необходимо придумать имя проекту, указать место его расположения и выбрать IDE для которой будет генерироваться проект. После этого генерируем проект рисунок 6.

После генерации проекта нам предложат открыть папку с проектом, или сразу открыть сам проект. Открываем проект рисунок 7.

Добавьте файл led.h и led.c созданные в предыдущей статье к нашему проекту и новый файл button.h рисунок 8, рисунок 9, рисунок 10.

По аналогии добавьте файл button.c.

Сразу переместим эти файлы в папки «inc» и «src» рисунок 11, рисунок 12.

Cодержимое файла button.h.

Cодержимое файла button.c.

Добавляем новую функцию BUTTON_STATE в цикл while. Ниже содержимое файла main.c с нашими функциями.

Скомпилируйте программу нажав кнопку Build, прошейте микроконтроллер, нажмите RESET. При запуске программы вы увидите что включен красный светодиод, нажмите на пользовательскую кнопку красный выключится, а зеленый включится, когда кнопка не нажата результат обратный.

ВНИМАНИЕ: Приведенный листинг кода не является единственной и оптимальной реализацией поставленной задачи. Пример опубликован с демонстрационной целью. Также автор во избежание переписывания чужих статей, пропускает теоретические основы необходимые для понимания примера, связывая это с тем что всю необходимую информацию можно найти в интернете.