В уроке 165 мы настраивали ножки порта на выход. Также мы хорошо знаем, что порт — это такая шина, которая умеет работать также хорошо как на выход, так и на вход. Поэтому сегодня мы попробуем настроить ножку порта на вход. В этом случае контроллер не управляет состоянием тех или иных ножек портов, а, наоборот, следит за их состоянием, которое управляется извне. Мы также будем следить за состоянием ножки, менять которое будем с помощью кнопки, которая на нашей схеме, которая со времён последних уроков не изменилась, уже присутствует на нашей схеме и одним контактом притянута к общему проводу, а другим — к ножке порта микроконтроллера PA1
В связи с этим у нас ножка PA1 при ненажатом состоянии кнопки будет находиться в высоком состоянии и мы будем следить за тем, когда на ней установится низкий уровень.
Какие регистры и какие их биты отвечают за те или иные настройки GPIO, мы также знаем из того же урока, поэтому можно уже приступать и к проекту.
Проект был сделан из проекта прошлого урока с именем CMSIS_TIM2 и назван был CMSIS_BUTTON01.
Откроем наш проект в Keil и в файле main.c из обработчика таймера мы пока удалим вот этот код, оставив только сброс флага прерывания, так как таймер этот нам ещё пригодится в данном уроке
switch(tim2_count)
{
case 0: LED10_OFF(); LED1_ON(); break;
case 1: LED1_OFF(); LED2_ON(); break;
case 2: LED2_OFF(); LED3_ON(); break;
case 3: LED3_OFF(); LED4_ON(); break;
case 4: LED4_OFF(); LED5_ON(); break;
case 5: LED5_OFF(); LED6_ON(); break;
case 6: LED6_OFF(); LED7_ON(); break;
case 7: LED7_OFF(); LED8_ON(); break;
case 8: LED8_OFF(); LED9_ON(); break;
case 9: LED9_OFF(); LED10_ON(); break;
}
tim2_count++;
if(tim2_count>9) tim2_count=0;
Прежде чем следить за состоянием ножки, её нужно настроить.
Порт A у нас уже тактируется, поэтому включать тактирование нам не надо.
Давайте для начала решим какие биты и где нам надо включить для нашей ножки PA1, чтобы она работала на вход и чтобы на ней установился сразу высокий уровень.
Для этого в битовом поле CNF1[1:0], отвечающего за настройку именно ножки 1 порта A, регистра GPIOA_CRL мы установим вот такую комбинацию битов
Таким образом мы настроим подтягивание резистора. А куда именно он подтянется, — к общему проводу или к питанию, будет зависеть уже от настройки соответствующего бита регистра ODR.
А в битовом поле MODE1[1:0] мы выберем самую первую комбинацию битов
Проделаем всё это в нашем проекте в функции main(). Для этого нам не нужно будет писать новую команду, добавим данные настройки в уже существующую
MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF7 | GPIO_CRL_CNF6 | GPIO_CRL_CNF5 |\
GPIO_CRL_CNF4 | GPIO_CRL_CNF3 | GPIO_CRL_CNF2 | GPIO_CRL_CNF1_0 |\
GPIO_CRL_MODE1,\
GPIO_CRL_CNF1_1 | GPIO_CRL_MODE7_0 | GPIO_CRL_MODE6_0 | GPIO_CRL_MODE5_0 |\
GPIO_CRL_MODE4_0 | GPIO_CRL_MODE3_0 | GPIO_CRL_MODE2_0);
Соберём и прошьём наш код и посмотрим в отладке, что у нас всё правильно установилось
Настроим бит 1 регистра ODR, чтобы резистор подтянулся именно к питанию
1 2 |
GPIO_CRL_MODE4_0 | GPIO_CRL_MODE3_0 | GPIO_CRL_MODE2_0); SET_BIT(GPIOA->ODR,GPIO_ODR_ODR1); |
Удалим старт нашего таймера
TIM_EnableCounter(TIM2);
В бесконечный цикл функции main() добавим условие, в тело которого мы будем попадать при условии, когда на ножке PA1 установится уровень 0, то есть будет нажата кнопка. В данном теле мы зажжём первый светодиод
1 2 3 4 5 6 |
while(1) { if(!READ_BIT(GPIOA->IDR, GPIO_IDR_IDR1)) { LED1_ON(); } |
Проверим работу нашего кода, собрав и прошив код. Пока мы не нажмём кнопку светодиод светиться не будет, а как только мы её нажмём, он засветится
По идее, цель урока уже достигнута, мы научились настраивать ножку порта на вход, но так как порой имеет место ситуация с дребезгом кнопок, давайте как-то с ней поборемся, иначе урок будет как-то неполный. Также мы задействуем все светодиоды в планке, чтобы не простаивали и при каждом новом нажатии у нас будет светодиод, светящийся в данный момент, гаснуть, а зажигаться будет следующий. Думаю, так будет интересней.
Способов борьбы с дребезгом (непроизвольным кратковременным включением) кнопки существует очень много, в том числе есть схематические, например, посредством подключенного конденсатора параллельно кнопке, но существует также ряд способов борьбы с этим явлением и программных. Мы в прошлых уроках по другим контроллерам применяли некоторые из них. Сегодня мы проведём борьбу с дребезгом кнопки с помощью таймера, ведь он у нас все равно уже работает. Мы будем считать тики таймера и убеждаться, что в некотором проценте из них у нас кнопка остаётся нажатой. Если контакт был случайным, то он будет кратковременным.
Во-первых, мы уменьшим период срабатывания таймера, а то 100 милисекунд слишком долгий, и нам придётся долго держать кнопку нажатой, чтобы набрать наших тиков.
Для этого в функции TIM2_Init внесём изменения в коде
WRITE_REG(TIM2->ARR, 50);
Данный период срабатывания подбирается опытным путём. Теперь таймер будет у нас срабатывать каждые 2.5 милисекунды. Думаю, нормально.
Добавим ещё один глобальный счётчик. Это будет счётчик тиков таймера, но именно при нажатой кнопке
1 2 |
__IO uint8_t tim2_count = 0; __IO uint8_t but1_count = 0; |
В функции обработчика прерываний TIM2_IRQHandler от таймера добавим код, который будет инкрементировать количество тиков при условии нажатой кнопки, а также в другой переменной — общее количество тиков
1 2 3 |
CLEAR_BIT(TIM2->SR, TIM_SR_UIF); if(!READ_BIT(GPIOA->IDR, GPIO_IDR_IDR1)) but1_count++; tim2_count++; |
Добавим функцию, в которой мы будем решать, — случайное нажатие произошло у кнопки или намеренное
1 2 3 4 5 6 |
//----------------------------------------------------------- uint8_t IsBut01On(void) { uint8_t res; } //----------------------------------------------------------- |
Если кнопка нажата, запустим наш таймер
1 2 |
uint8_t res; if(!READ_BIT(GPIOA->IDR, GPIO_IDR_IDR1)) TIM_EnableCounter(TIM2); |
А если кнопка не нажата, то покинем нашу функцию совсем
1 2 |
if(!READ_BIT(GPIOA->IDR, GPIO_IDR_IDR1)) TIM_EnableCounter(TIM2); else return 0; |
Подождём, пока таймер насчитает некоторое количество срабатываний (подбирается также опытным путём)
1 2 |
else return 0; while (tim2_count<20) {} |
Остановим таймер
1 2 |
while (tim2_count<20) {} TIM_DisableCounter(TIM2); |
Прежде чем исследовать количество срабатываний именно при нажатой кнопке, добавим ещё одну глобальную переменную, в которой будет храниться состояние блокировки и разблокировки доступа к кнопке. Блокировка нужна, чтобы наша кнопка не порождала бесконечное количество событий нажатого состояния
1 2 |
__IO uint8_t but1_count = 0; __IO uint8_t but1_lock = 0; |
Вернёмся в функцию IsBut01On и, если таймер насчитал более 10 тиков с нажатым состоянием кнопки, запишем в результат 1 и заблокируем доступ к кнопке. Мы его разблокируем потом там, где будем вызывать данную функцию
1 2 |
TIM_DisableCounter(TIM2); if(but1_count>10) {res=1; but1_lock=1;} |
В противном случае запишем в результат 0
1 2 |
if(but1_count>10) {res=1; but1_lock=1;} else res=0; |
Обнулим наши счётчики и вернём результат
1 2 3 4 |
else res=0; tim2_count=0; but1_count=0; return res; |
В бесконечном цикле удалим весь код и добавим новый, в котором для начала узнаем, есть ли у нас доступ к кнопке
1 2 3 4 5 6 7 8 |
while(1) { if(!but1_lock) { } else { } |
Если кнопка не заблокирована, то узнаем, было ли реальное нажатие, с помощью нашей функции
1 2 3 4 5 |
if(!but1_lock) { if(IsBut01On()) { } |
Добавим ещё одну глобальную переменную, в котором будем хранить номер светящегося в данный момент светодиода
1 2 |
__IO uint8_t but1_lock = 0; __IO uint8_t led_count = 0; |
Вернёмся в бесконечный цикл функции main() и добавим вот такую конструкцию, подобную той, которая ранее была у нас в обработчике прерываний от таймера
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if(IsBut01On()) { switch(led_count) { case 0: LED10_OFF(); LED1_ON(); break; case 1: LED1_OFF(); LED2_ON(); break; case 2: LED2_OFF(); LED3_ON(); break; case 3: LED3_OFF(); LED4_ON(); break; case 4: LED4_OFF(); LED5_ON(); break; case 5: LED5_OFF(); LED6_ON(); break; case 6: LED6_OFF(); LED7_ON(); break; case 7: LED7_OFF(); LED8_ON(); break; case 8: LED8_OFF(); LED9_ON(); break; case 9: LED9_OFF(); LED10_ON(); break; } |
Перейдём теперь в обработку противного случая блокировки кнопки (то есть когда доступа к кнопке у нас нет) и добавим там код, который в случае, если кнопка уже отжата, будет инкрементировать счётчик номера светящегося светодиода, затем в случае достижения порога будет его обнулять. Также затем мы предоставим снова доступ к нашей кнопке, сняв блокировку
1 2 3 4 5 6 7 8 9 10 11 12 |
case 9: LED9_OFF(); LED10_ON(); break; } } } else { if(READ_BIT(GPIOA->IDR, GPIO_IDR_IDR1)) { led_count++; if(led_count>9) led_count=0; but1_lock=0; } |
Вот и весь код. Проверим его на практике
Теперь по каждому новому нажатию на кнопку у нас будет зажигаться следующий светодиод, а текущий будет погасать. Получился у нас такой вот бегущий огонь, управляемый нажатиями на кнопку (теперь реальными). Вернее, мы отследили теоретически не нажатие кнопки, а её пребывание в какое-то время в нажатом состоянии. Нажатие кнопки — это переход её состояния из отжатого в нажатое. Конечно, классический вариант должен работать по-другому. Как правило в различных устройствах отслеживается не нажатие на кнопку, а обратный процесс — процесс её отжатия, то есть такое явление в нашем случае когда уровень на ножке порта будет меняться из низкого состояния в высокое. Данный процесс мы попробуем отследить, когда будем работать с внешними прерываниями.
Итак, сегодня мы научились работать с ножками порта, настроенными на вход, также поработали с состоянием подключенной кнопки, проведя также некоторую борьбу с явлением дребезга её контактов.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Смотреть ВИДЕОУРОК (нажмите на картинку)
В других стм F4 другие регистры
Отличные уроки. Большое спасибо!