Alex

Подключение Кнопок К Микроконтроллерам

86 сообщений в этой теме

На вот такой метод меня подтолкнул Геннадий(спасибо ему огромное, многому научил меня), но он делал это немножечко мудрее, как-то с двумя флагами...

Viktor26, всегда пожалуйста. Обращайся, если что.

Применяются два флага. Назовем их key и key_dis.

Алгоритм выглядит примерно так:

- кнопки отжаты, флаги (оба) опущены. В таком положении разрешается обработка любого нажатия клавиш.

- клавиша нажата и при первом же прерывании (или в главном цикле, после прерывания таймера) определяется данное событие. Флаг key_dis опущен и разрешает поднятие флага key. Поднимаем флаг key и вместе с ним флаг key_dis. Такое состояние разрешит в функции обработки клавиш выполнить требуемую функцию, после которой флаг key должен быть опущен. Иначе при следующем проходе главного цикла будет непонятно, выполнялся ли обработчик нажатия клавиши.

- Теперь мы имеем состояние флагов такое: key - опущен (обработка выполнена и повторное выполнение функции, за одно нажатие клавиши, не требуется, а произойти оно может, т.к. пользователь не успеет отпустить клавишу за период одного прерывания таймера). Флаг key_dis остается поднятым (он опускается только при определении отпущенной клавиши в следующих прерываниях) и запрещает повторное поднятие флага key (в следующем прерывании) пока кнопка не будет отпущена. Этот момент можно использовать для подсчета времени нажатия в алгоритмах с длительным удержанием нажатой клавиши.

- клавиша отпущена. Флаг key_dis опускается, закрывая событие нажатия клавиши и разрешая в следующих прерываниях определить НОВОЕ нажатие клавиш и выполнить с ними определенные действия.

Вот как-то так. Описание сложноватое получилось, но сам алгоритм не сложный, полностью защищен от дребезга и не требует никаких задержек, тормозящих МК. Определение клавиш 100%-ное.

  • Одобряю 1

Поделиться сообщением


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

Быстрый заказ печатных плат

Полный цикл производства PCB по низким ценам!

  • x
    мм
Заказать Получить купон на $5.00
mail_robot    1 162

Значит далее. Как это я делаю сейчас, под АРМ.

В принципе все нижеизложенное можно адаптировать под любой камень, поняв в чем заключается суть. Если коротко - в основном цикле программы постоянно вызывается функция опроса клавиатуры. Как только она фиксирует событие, устанавливает соответствующие флаги. Далее события обрабатываются уже в других участках кода независимо от основной процедуры опроса клавиш. Код написан с использованием ООП, поэтому прошу сильно не пугаться. К сожалению без ООП под АРМ писать очень туго, поэтому писанину на процедурке я забросил уже давно.

Итак. Для начала обьявляем структурный тип кнопки, который содержит в себе информацию о том что у нас случается с этой кнопкой и обьявляем класс клавиатуры.

typedef struct // Key (собственно структура кнопки)
{
GPIO_TypeDef* GPIOx; (в это поле запишется номер порта кнопки, например PORTA etc)
uint16_t GPIO_Pin; (в это поле пин в порту на котором кнопка висит, пин 8 к примеру)
bool old; (это поле содержит предыдущее состояние кнопки. К примеру сейчас она отпущена и в порту 1 а было 0. Вот и записано ноль)
uint16_t Cnt; (это счетчик успешных считываний состояний кнопки, которые отличались от "предыдущего", см. поле выше)
bool Event; (это флаг, сообщающий, что на кнопке успешно зафиксировано событие)
bool Enable; (этот флаг говорит о том разрешено ли вообще считывание нажатий этой кнопки, а то вдруг надо ее запретить в процессе)
} Key;

class TKeyboard
{
private:

... (там еще функции служебки для конкретных задач, нам пока не интересные)
void KeyScan (Key *Key); (собственно ф-я сканирования определенной кнопки, в качестве параметра передается структура идентификатор кнопки, та что описана выше)

bool LongPressFlag; (флаг индицирующий доооолгое нажатие на кнопку. То есть удержание. Надо в некоторых меню)
bool OldLongKeyPress; (служебный флаг, для обработки долгих нажатий)

public:

Key OnOff;	 (собственно обьявление кнопок, столько сколько душеньке угодно со своими именами)
Key Encoder; (я их обьявляю отдельно, для читабельности. На размер кода не влияет)
Key Menu;
Key Mode;
Key Status;
Key Select;
int8_t Step;	 (это для энкодера, нам пока не надо. Считает шаги и нужно для сервиса)
bool EncoderLongEvent; (флаг долгого нажатия на энкодер, тоже пока не надо, ибо та же кнопка по сути)

uint8_t BeepLength; (поле нужное для работы бипера. Если в него записано значение отличное от нуля, функция бипера читает его и воспроизводит звук в зависимости от потребности нужной длинны. Короткий пип, длинный пип итп)

TKeyboard(); (конструктор класса)
void Scan (void); (метод выполняющий сканирование клавиатуры, инкапсулирован в класс)
void Beep (uint8_t *BL); (метод выводящий звучок на бипер, также инкапсулирован)
void delay_us(unsigned int delay); (эти функции я нигде не использую, но на всякий случай храню их в классе клавиатуры)
void delay_ms(unsigned int delay); (пригождаются они обычно в сочетании с работой именно клавиш)
};

теперь по методам. Самый главный метод - сканирование клавиатуры

void TKeyboard::Scan ()
{
... (тут впереди про энкодер, удалил)
if (OnOff.Enable || OnOff.Cnt) KeyScan (&OnOff); (если кнопка разрешена или было выполнено хоть одно успешное чтение состояния, сканируем эту кнопку дальше)
if (Status.Enable || Status.Cnt) KeyScan (&Status);
if (Menu.Enable || Menu.Cnt)	 KeyScan (&Menu);
if (Mode.Enable || Mode.Cnt)	 KeyScan (&Mode);
if (Select.Enable || Select.Cnt) KeyScan (&Select);
if (Encoder.Enable || Encoder.Cnt) KeyScan (&Encoder);

if (OnOff.Event) (это обработчик немедленной реакции на кнопку вкл/выкл. Так нужно для безопасности)
{
OnOff.Event = false;
LoadEnable();
};

if (BeepLength !=0) Beep (&BeepLength); (а это собственно функция звучка, если вдруг его значение оказалось не нулевым)
(звучок как и клавиатура сканируется каждый раз, дабы не тормозить программу)
}

как видим пока ни одной задержки, все исполняется на вылет

Есть один нюанс - условие if (OnOff.Enable || OnOff.Cnt). Для чего оно двойное? А для того, что юзер может в процессе нажатия на кнопку передумать или кнопка неисправна и бедет отпущена ДО того как зафиксируется устойчивое событие. В этом случае обработка кнопки будет все равно завершена и событие зафиксируется как несостоявшееся. Дело в том, что если не завершить обработку, то счетчик успешных попыток останется с каким то значением и при следующем нажатии может быть обработан с накоплением ошибки, а это будет не красиво. Код всегда должен выполняться однозначно и устойчиво независимо от действий/бездействий пользователя, а также технического состояния аппаратной части.

Далее собственно код сканирования непосредственно атомарной кнопки

void TKeyboard::KeyScan (Key *Key)
{
// опацэ! Заметили, что кнопку ктото нажал, или пытается
// по крайней мере был перепад и текущее состояние кнопки не совпало со старым (для читабельности флаги расписаны полностью со сравнениями, хотя можно обходиться и без них)
if(GPIO_ReadInputDataBit(Key->GPIOx,Key->GPIO_Pin) == 0 && (Key->old == true))
{
if (!Key->Cnt) // если счетчик успешных попыток чтения кнопки до этого был нулевым, то надо включить бипер и пикнуть по нажатию
{
BeepLength = BEEP_L;
Key->Event = true;		 // ну и зафиксировать событие нажатия на кнопку. Оно зафиксируется только один раз за цикл нажатия, ибо дальше все условия пропустят этот иф, потому как счетчик уже не будет нулевым
}
Key->Cnt++;			 // считаем устойчивые состояния кнопки (успешным считается достижения числа циклов определенное константой DEBOUNCE_CNT)
if ((Key->Cnt) > DEBOUNCE_CNT) // если досчитали
{
Key->Cnt = 0;		 // сбрасываем счетчик циклов
Key->old = false;		 // изменяем значение флага предыдущего события, так как оно изменилось. Мы же теперь считаем кнопку нажатой
}
}
(для примера приведу еще один способ на примере долгого нажатия на энкодер. Данный метод использует таймер на лету)
//если долго держим кнопку энкодера
if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_15) == 0 && OldLongKeyPress == true)
{
if (!LongPressFlag) // если таймер 17 еще не запускался, входим в модуль запуска
{ // вошли
RCC->APB2ENR |= RCC_APB2ENR_TIM17EN;
TIM17->PSC = F_APB1/1000+1; //устанавливаем предделитель в значение 1 мс независимо от частоты шины (48 МГц в моем случае)
TIM17->ARR = 800; //ставим через сколько нам надо сгенерировать событие, так как считаем в обратную сторону. 0,8 сек в данном случае
TIM17->EGR |= TIM_EGR_UG; //генерируем событие обновления для записи значений в регистры PSC и ARR (защелка)
TIM17->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; //запускаем таймер флагом CEN и говорим что нам нужен всего один проход до остановки флагом OPM. Таймер досчитает до нуля, упрется в дно и сгенерирует событие, после чего остановится
LongPressFlag = true; // фиксируем событие от нажатия на энкодер
}
if(!bit_test(TIM17->CR1,0))			 // если таймер досчитал, значит кнопку энкодера так и не отпустили за 0,8 сек ни разу
{
EncoderLongEvent = true;			 // значит фиксируем долгое нажатие
BeepLength = BEEP_L_LONG;			 // делаем длинный бип записью в соответствующее поле класса сканирования клавиатуры
OldLongKeyPress = false;	 // меняем старое состояние кнопки энкодера на новое
}
}
else						 // иначе кнопку успели отпустить и нажатие уже не длинное, а короткое
{
TIM17->CR1 &= ~TIM_CR1_CEN;			 // останавливаем таймер
LongPressFlag = false;			 // сбрасываем флаг события принудительно
}

// а в этом месте мы заметили, что кнопку отпустили
if(GPIO_ReadInputDataBit(Key->GPIOx,Key->GPIO_Pin) == 1 && (Key->old == false))
{
Key->Cnt++; // считаем число успешных состояний кнопки типа "отпущено"
if ((Key->Cnt) > DEBOUNCE_CNT) // а когда досчитаем, то значит кнопка реально отпущена
{
Key->Cnt = 0;		 // сбрасываем счетчик
Key->old = true;		 // фиксируем новое предыдущее (!) состояние как нажатое
OldLongKeyPress = true;	 // также восстанавливаем флаг предыдущего состояния долгого нажатия, если вдруг таковое было. Мы же не знаем точно какое было нажатие до отпускания, долгое или короткое
}
}
}

Как видим опять программа нигде не тормозит. То есть - при вызове функции опроса клавиатуры в основном цикле программы (main), она в свою очередь запускает сканирование каждой кнопки без каких либо пауз и остановок. Каждый проход main вызывает один единственный скан клавы. При этом производится подсчет устойчивых состояний каждой кнопки (хоть все одновременно нажмите) до тех пор, пока их количество не превысит некоторую константу. У меня это 30 проходов. То есть если в течение некоторого времени контроллер зафиксировал как минимум 30 раз что кнопка нажата, значит она реально нажата, даже если во время нажатия она дребезжала вовсю и кое какие проходы и не фиксировали нажатия, то есть не увеличивали счетчик устойчивых состояний.

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

Зафиксировав состояние, устанавливается флаг события и далее обрабатывается программой уже в соответствии с логикой обработки независимо от сканирования самой клавиатуры. Хоть 10 раз нажми на кнопку, а пока событие не обработается и флаг не сбросится (в другой части программы другой процедурой) будет считаться что кнопка была нажата всего только раз. Это нужно для исключения многократных срабатываний кнопок.

Метод прост. Позволяет отрабатывать нажатия кнопок в любых сочетаниях. Хоть рояль. И делать это исключительно безошибочно. Конечно кто-то скажет - фууу, все это привязано к длине цикла основной программы. По сути да, привязано. К минимальной его длине, путем подбора той самой константы безошибочных считываний. Но это единственная настройка, которая требуется. И да - если вы используете такой метод, то в программе не должно быть ни единой остановки нигде. То есть про функцию delay() можно смело забыть, ибо нам надо постоянно появляться в основном цикле. Это есть хороший тон программирования, равномерно распределяющий ресурсы между процессами и расходующий их только по мере появления спроса.

А теперь на закуску функция генерации звучка. В качестве бипера использован однотональный излучатель со встроенным генератором (так проще и надежнее)

void TKeyboard::Beep (uint8_t *BL)
{
// Beep RB1 = 1 (тут коротенькая памятка самому себе)
// No Beep RB1 = 0;
if(GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_1) == 0) // если бип был выключен (сейчас не включен)
{						 // выполняем запуск таймера длины звучка
GPIO_SetBits (GPIOB,GPIO_Pin_1); // сразу включаем бип, пускай пока пищит
// а мы пока настроим и запустим таймер
RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // на всякий случай каждый раз запускаем тактирование модуля таймера, ибо оно не вредно никогда
TIM6->PSC = F_APB1/1000+1; //инициализируем предделитель на частоту 1 мс
TIM6->ARR = *BL; //загружаем в таймер значение длинны звучка (передается как параметр метода, см выше)
TIM6->EGR |= TIM_EGR_UG; //генерируем событие обновления для записи в регистры PSC и ARR (защелка)
TIM6->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM; //запускаем таймер флагом CEN и заказываем один проход флагом OPM
};
if(!bit_test(TIM6->CR1,0))			 // если таймер досчитал до дна
{
GPIO_ResetBits(GPIOB,GPIO_Pin_1);		 // выключаем звучок
*BL = 0;					 // сбрасываем значение длинны бипа, которое передалось по ссылке и обрабатывается как глобальное независимо от места вызова метода
};
/* а если ничего не сосчиталось и не поменялось, то вываливаемся нафик до следующего прохода чтобы не тормозить основной код */
}

как видим и здесь функция не требует постоянного присмотра, зашла, проверила - ага, ничего нового, пущай пищит дальше, займусь пока своими делами. Фрикам могу дополнительно посоветовать вообще привязать бип к флагу OPM таймера, тогда вообще почти ничего проверять не придется. Как только таймер досчитает бипер автоматом погаснет. Метод и так весьма короткий, потому как инициализация и запуск таймера выполнится только один раз и потом будет всегда пропускаться и по сути будет выполняться только один оператор if в конце, который также сможет выполниться только один раз. Итого ресурсов на выполнение - 0,000000000... в периоде. Точно так же как и на сканирование кнопок, если вы внимательно посмотрите на то куда вообще в процессе заходит программа.

Изменено пользователем mail_robot
  • Одобряю 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
mail_robot    1 162

методика описанная для АРМов выглядит конечно жутковато для новичка. Да и для бывалого в принципе тоже... Однако на практике реализовалось все достаточно просто. Учитывая тот факт, что это была моя вторая прога под арм после перехода на него из 8-биток. Там развитое меню и довольно много кнопок с нелинейной логикой работы. Если кому надо все исходники целиком, могу выложить проект под Keil.

Первым делом обычно всегда приходится решать вопрос с интерфейсом. А это в первую очередь конечно кнопки и энкодер. Код на вид довольно громоздкий, но компилируется всего в 16К (это со всем всем всем остальным кодом, которого еще 200К с хвостиком и плюс библиотеки). Для камня даже самого простого типа типа ARM 32F051 это семечки. Там еще 4 раза по столько же можно уложить, да нечем. Код под АРМ получается очень читабельным если использовать предложенную производителем стандартную библиотеку, так что боятся армов совершенно не стоит. Они гораздо гибче и во многом проще 8-биток.

Вот взять к примеру таймер (коих там гора на все случаи жизни). Я использовал самые простые таймера общего назначения. Но и эти таймера настолько гибкие, что использовать их одно удовольствие. Вот к примеру я использовал режим одного прохода. Очень удобно, когда надо реализовать некое одиночное событие. Загружаем таймер задержкой, включаем и через время он генерирует флаг, что остановился и досчитал. Прерываться вовсе не обязательно, можно гдето в программе потом проверить - все или нет. И если готово, то работать как то там дальше. Можно этот флаг привязать к запуску другого события (причем участия процессора при этом не потребуется). Например запуск АЦП. То есть я могу вот от этого времени запустить таймер на 20 мс, а через 20 мс получу готовое значение в регистре АЦП. А дальше больше. Я могу указать контроллеру ДМА, что как только АЦП закончит преобразование, мне надо значение из его регистра переправить воооон в ту ячеечку памяти, которая как раз занята какой то переменной, которая задействована в моей программе. И не прокрутив ни строчки кода я буду в этой переменной спустя 20 мс иметь готовое значение АЦП. Такие вот чудеса дает делать АРМ. И так можно строить самые разные цепочки взаимодействия аппаратных блоков.

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

А в это время проц будет курить бамбук и сканировать кнопки ожидая юзера. Ни единого прерывания, ни единой остановки, не тратя ни одного процента ресурсов ядра. Нука сделайте мне то же самое на АВР?

сказка!

Изменено пользователем mail_robot

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Viktor26    300

из вашего кода понятно пока-что одно(это я про себя)

чтобы помигать светодиодом, нужно написать 16к для кнопки :crazy:

это я так подшучиваю))

Полезная тема однако получилась

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
mail_robot    1 162

боюсь что из этих 16К едва ли 200 байт наберется на обработчик клавы. Если бы вы видели весь проект, то было бы понятно о чем я

даже если все методы определить как inline все равно будет немного, потому как классы очень компактно компилируются

будут вопросы по коду задавайте, разверну

Изменено пользователем mail_robot

Поделиться сообщением


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

Вам бы с AtxMega пообщаться. Чудес и в ней хватает. Кроме DMA она содержит еще и систему событий, что так же очень удобно.

  • Одобряю 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
mail_robot    1 162

не могу спорить, не вникал. Потому как не люблю терять время на то, что априори тупиковое на сегодняшний день. АРМ кругом, а Хмега только в прайсах и воспаленных умах поклонников АВР. А STM32 кругом, куда ни плюнь. Да и цена не конкурирует совсем. В итоге поддался естественному отбору. Весь мир же не может быть дураками

Изменено пользователем mail_robot

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Alex    484

Народ, Вам не кажется, что вы выбрали не то место для обсуждения "что лучше ?" ?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
ST_A    5

Применяются два флага. Назовем их key и key_dis.

Алгоритм выглядит примерно так:

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

В данном примере описана в общем виде совмещенная процедура для опроса клавиатуры и динамической индикации на 7. сегментных индикаторов, при желании ее можно приспособить для ЖКИ и/или большего числа кнопок (например матрица 4х5)

Исходные данные:

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

Ресурсы микроконтроллера:

P2 - Порт для считывания линий возврата кнопок (обозначение условное)

Ct_scan - счетчик шагов сканирования (совпадает с числом разрядов 7 сегм. индикатора)

Rg_buff - Буфер для запоминания состояния линий возврата кнопок

Ct_Dspl - Указатель адреса в массиве данных для индикации

F_keyb – Флаг нажатия кнопки на протяжении цикла сканирования

F_press – Флаг признака для выполнения программы-процедуры для нажатой кнопки

1. Вход в процедуру опроса и индикации.

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

3. Считывание с порта P2 состояние линий возврата кнопок, при необходимости накладывается битовая маска, делается инверсия и сдвиги влево/вправо и т. д.

4. Считывается и проверяется состояние P2=0:

- P2=0 (кнопка не нажата), переход к п.11

- P2#0 (одна из кнопок нажаты), перход к п.5 (активация флажка F_press=1)

5. Активация флажка F_press (устанавливают F_press=1)

6. Опрос состояния флажка обслуживания кнопок F_keyb:

- F_keyb=1, переход к п.11.

- F_keyb=0, переход к п. 7.

7. Проверка ячейки памяти Rg_buff:

- Rg_buff=1, переход к п. 11.

- Rg_buff=0, переход к п. 8.

8. Считывание с порта P2 состояние линий возврата кнопок, при необходимости накладывается битовая маска, делается инверсия и сдвиги влево/вправо и т. д. (фактически повторяется п.3)

9. Обработанный код запоминается в Rg_buff, он должен отличаться от значения «0»

10. Активация F_press, т.е. устанавливают F_press=1.

11. Наращивают счетчик сканирования +1 (Ct_scan= Ct_scan+1)

12. Проверка Ct_scan на переполнение (Ct_scan=Последняя позиция?):

- Ct_scan<Последняя позиция, переход к п.19. (выход из процедуры)

- Ct_scan=Последняя позиция, переход к п.13.

13. Заносится начальное значение в Ct_scan

14. Заносится начальное значение в Ct_Dspl

15. Проверка состояния флажка F_press:

- F_press=1, переход к п.18

- F_press=0, перход к п.16

16. Проверка состояния F_keyb:

- F_keyb=1, переход к п.18

- F_keyb=0, переход к п. 17

17. Очистка Rg_buff, т.е. в него запиывается «0»

18. Очистка флажка F_press=0

19. Выход из процедуры

Изменено пользователем Alex

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
DmitryS    23

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

Как всегда в Протеусе , модифицированная с вашей помощью программа , заработала сразу , но в реальности задержки в 10мс для преодоления дребезга контактов оказалось мало , увеличил до 50 , сейчас весь блок управления воротами лежит дома на столе , вроде как работает без сбоев , завтра полезу устанавливать в гараже ))) Вот что у меня получилось пока :

/*
* Knopka_SekcyonnyeVorota.c
*
* Created: 2.09.2015 1:03:16
*  Author: Dmitry S
*/

#define F_CPU 1200000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define DIRECT_CLOSE PORTB &= ~(1<<0)
#define DIRECT_OPEN  PORTB &= ~(1<<1)
#define STOP_CLOSE PORTB |= (1<<0)
#define STOP_OPEN PORTB |= (1<<1)
#define STOP PORTB |= (1<<0) | (1<<1)

volatile unsigned char a=1;

ISR(PCINT0_vect) // обработка прерывания
{
   GIMSK &=~(1<<PCIE); GIFR |= (1<<PCIF);// Запрет прерывания , для устранения дребезга контактов

   if (~PINB &(1<<PINB3)) // Только если концевик замкнут
   {
       STOP;
       _delay_ms(50);
       a++;		    // определить направление вращения
       if(a==5)
       a=1;
   }
   if (~PINB &(1<<PINB4))  // Защита заклинивания ворот по току двигателя
   {
       _delay_ms(300); // отфильтровываем пусковой ток
       if (~PINB &(1<<PINB4))
       {
           STOP;	  // остановить двигатель

           a++;		    // определить направление вращения
           if(a==5)
           a=1;
       }
   }
   GIMSK |= (1<<PCIE);    GIFR |= (1<<PCIF);//Разрешение прерывания
}

int main(void)
{
    // Настрока порта
     DDRB = 0b000011;
    PORTB = 0b011111;
    // Настройка и разрешение прерывания PCINT3 PCINT4
    PCMSK = (0<<PCINT5) | (1<<PCINT4) | (1<<PCINT3) | (0<<PCINT2) | (0<<PCINT1) | (0<<PCINT0);
    GIMSK |= (1<<PCIE);    GIFR |= (1<<PCIF);
    sei();

   while(1)
   {
  static char butt_cur, butt_prev; //[Alex] Не знаю, есть ли булевые переменные в вашем компиляторе. По этому - char
  //.....................
  //.....................
  _delay_ms(50);
  butt_cur=((PINB &(1<<PINB2))==0); //[Alex] 0- активный уровень кнопки
  if(butt_cur && !butt_prev)
     { //[Alex] Если нажали кнопку
   	  //[Alex]..................... // Тут код, который нужно выполнить после нажатия
   	 switch(a)//[Alex]..................... // Т.б. , как я понял, switch.
   	 {
       	 case 1:
       	 //_delay_ms(50);
       	 DIRECT_OPEN; // Вращение на открытие
       	 a=2;
       	 break;

       	 case 2: // Stop
       	 //_delay_ms(50);
       	 STOP_OPEN;
       	 a=3;
       	 break;

       	 case 3:
       	 //_delay_ms(50);
       	 DIRECT_CLOSE; // Вращение на закрытие
       	 a=4;
       	 break;

       	 case 4: //Stop
       	 //_delay_ms(50);
       	 STOP_CLOSE;
       	 a=1;
       	 break;
   	 }
  }
  butt_prev=butt_cur;
  //.....................
  //[Alex] Тут выполняем что-то полезное. Но не задерживаем надолго программу.
  //.....................
     }
   }

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
UMTS    9

Отмечусь, чувствую у темы долгая, интересная жизнь. И для меня тема очень актуальная.

Изменено пользователем UMTS

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
солар    116

По идее можно опрашивать так, как опрашивает вход RxD микроконтроллер. Опрашиваем через равные промежутки времени вход. Анализируем три подряд идущие состояния. Чего больше - нулей или единиц - значит такое состояние входа. Например, если 010, то 0, если 110, то 1.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Alex    484

А для чего, если можно опрашивать всего 1 раз, через те же равные промежутки времени ?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
mail_robot    1 162

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Illusi0ns    19

Здравствуйте, уважаемые.

Хочу предложить на обсуждение алгоритм обработки кнопки. Я не претендую на оригинальность, потому что опрос кнопок через промежутки времени, использование флагов, это известный подход.. Но, претендую на авторство и не-содранность:)) хотя, какая разница))

Вот такой алгоритм, а код вечером тоже приложу)

Вкратце: проверка проводится раз в 20мс (в коде я сделал чаще, но может это и избыточные меры..). Распознает 3 типа нажатия. Функция сканирования кнопоки принимает две переменные (счётчик и uint8_t, 5 битов которой использованы под флаги).

В железе работает, результатом доволен, буду использовать в своих проектах

post-130756-0-16447600-1448533863_thumb.jpg

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
mail_robot    1 162

как вариант пойдет, но можно проще сделать такой же функционал

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Illusi0ns    19

как вариант пойдет, но можно проще сделать такой же функционал

Да, возможно так и есть, как то можно оптимизировать.. Но по скорости алгоритм должен быть хорошим, и в режиме простоя делает 3 if'а и все. Если запускать раз в 20-30мс, то норм)

Я там с макросами не разобрался, ну мало опыта у меня еще, попрошу немного помочь, когда код скину..

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Illusi0ns    19

Вот, выкладываю код.

1. переменные и макросы

volatile uint8_t bt_flags = 0; //ну тут таких переменных тоже будет три, как и счетчика. для каждой кнопки - своя пара.
uint16_t counter1 = 0, counter2 = 0, counter3 = 0;
#define PROCESSING_FLAG (bt_flags&1) //флаг запущенного обработчика FO
#define BUTTON_WAS_PRESSED ((bt_flags&2)>>1) //флаг, кнопка была нажата fp
#define PRESSED	 ((bt_flags&4)>>2) //флаг, кнопка зажата pr
#define LONG_PRESS ((bt_flags&8)>>3) //флаг, длинное нажатие lg
#define SHORT_PRESS ((bt_flags&16)>>4) //флаг, короткое нажатие sh
#define PRESSED_TIME 3000	 //длительность зажима
#define LONG_PRESS_TIME 2000	 //длительность длинного нажатия
#define SHORT_PRESS_TIME 150	 //длительность короткого нажатия
#define PAUSE_TIME 1200	 //длительность паузы между нажатиями
#define MENU	 ((bt_flags&4)>>2) //кнопка вызова меню
#define MENU_SAVE_CANCEL ((bt_flags&16)>>4) //кнопка выхода из меню

2. Функция сканирования

void bt_scanning(volatile uint8_t *flags, uint16_t *counter)
{
if (PROCESSING_FLAG) //FO флаг
{
if (!(PIND&(1<<PD7))) //кнопка нажата? (0 - нажата, 1 - нет)
{
(*counter)++;

if ((*counter) > PRESSED_TIME) //значение для зажима
{
(*flags) |= (1<<2); //pr = 1
}
else
{
(*flags) &=~ (1<<2); //pr = 0
}
}
else
{
(*flags) |= (1<<1);	 //fp = 1

if (PRESSED)	 //pr = 1
{
(*flags) &=~ (1<<2); //pr = 0
(*counter) = 0;
}

if ((*counter) > LONG_PRESS_TIME) //значение для длинного нажатия
{
(*flags) |= (1<<3); //lg = 1
}
else
{
if ((*counter) > SHORT_PRESS_TIME) //значение для короткого нажатия
{
 (*flags) |= (1<<4); //sh = 1
}
}
(*counter) = 0;
(*flags) &=~ (1<<0); //FO = 0
}
}
else
{
if (BUTTON_WAS_PRESSED)	 //fp флаг
{
(*counter)++;

if ((*counter) > PAUSE_TIME) //значение паузы между нажатиями.
{
(*counter) = 0;
(*flags) &=~ (1<<1); //fp = 0
}
}
else
{
if (!(PIND&(1<<PD7))) //если кнопка нажата
{
(*flags) |= (1<<0); //FO = 1
(*counter) = 0;
}
}
}
}

3. вызов функции в обработчике прерывания по переполнению таймера

ISR (TIMER2_COMP_vect)
{
/*.....*/
bt_scanning(&bt_flags, &counter1); //вызов раз в ? сек, TCCR2 = ((1<<CS01)|(1<<WGM21));, 8МГц
}

4. проверка функции в основном цикле:

if (PRESSED)
{
a++;
_delay_ms(500);
}
else
{
if (LONG_PRESS)
{
bt_flags &=~ (1<<3); //lg = 0
a++;	 //1,2 разряд индикатора
}
else
{
if (SHORT_PRESS)
{
 bt_flags &=~ (1<<4); //sh = 0
 b++;	 //3,4 разряд индикатора
}
}
}

Вот, как то так.

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

UPD2: код для сканирования привязан к определенной кнопке. надо исправить. только сейчас заметил (потому что до этого использовал только одну кнопку)

Изменено пользователем Illusi0ns

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Alex    484

volatile uint8_t bt_flags = 0; //ну тут таких переменных тоже будет три, как и счетчика. для каждой кнопки - своя пара.
uint16_t counter1 = 0, counter2 = 0, counter3 = 0;
#define PROCESSING_FLAG (bt_flags&1) //флаг запущенного обработчика FO
#define BUTTON_WAS_PRESSED ((bt_flags&2)>>1) //флаг, кнопка была нажата fp
#define PRESSED  ((bt_flags&4)>>2) //флаг, кнопка зажата pr
#define LONG_PRESS ((bt_flags&8)>>3) //флаг, длинное нажатие lg
#define SHORT_PRESS ((bt_flags&16)>>4) //флаг, короткое нажатие sh

void bt_scanning(volatile uint8_t *flags, uint16_t *counter)
{

Так будет проще и нагляднее:

typedef struct{
 unsigned  processing_flag:1;
 unsigned  was_pressed:1;
 unsigned  pressed:1;
 unsigned  long_pressed:1;
 unsigned  short_pressed:1;

uint16_t   counter;
}t_btn;



void bt_scanning(volatile t_btn* p_btn){

if (p_btn->processing_flag) //FO флаг
{
..................
..................

Но не забывайте, что обращение к переменным через указатели, намного тяжелее прямых обращений.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Illusi0ns    19

Так будет проще и нагляднее:

typedef struct{
 unsigned  processing_flag:1;
 unsigned  was_pressed:1;
 unsigned  pressed:1;
 unsigned  long_pressed:1;
 unsigned  short_pressed:1;

uint16_t   counter;
}t_btn;



void bt_scanning(volatile t_btn* p_btn){

if (p_btn->processing_flag) //FO флаг
{
..................
..................

Но не забывайте, что обращение к переменным через указатели, намного тяжелее прямых обращений.

Спасибо. Но не могли бы вы объяснить, что означает typedef, и поля в структуре, я не знаю что это, не сталкивался.. Ну или где об этом почитать.

И на счёт, почему через указатель тяжелее, можно тоже пару слов?..

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
mail_robot    1 162

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

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

Изменено пользователем mail_robot

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Illusi0ns    19

unsigned was_pressed:1;

А что означает запись типа такой? Я понял на счёт структуры. Но :1 - не понятно..

А так, я уже поправил код, для 3х кнопок, внедрил в свои часы, и он вполне себе функционирует:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
3d_killer    3

Пересмотрел все уроки, перечитал кучу материала, искал что либо подобное в интернете, но ничего не нарыл, помогите решить вопрос, как пример

есть бегущий огонь, светодиоды 6 штук мигают с задержкой 1с, и допустим 4 кнопки (неважно что выполняют), как сканировать кнопки не дожидаясь начала нового цикла? (кнопки - сенсоры)

Изменено пользователем 3d_killer

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Alex    484

В обработчике прерываний по таймеру.

Как пример - http://radiokot.ru/f...470598#p1470598 В примере, как раз, основной цикл загружен делеями, а кнопки сканируются в обработчике таймера, потом нажатия спокойно обрабатываются в основном цикле.

На правах автора дублирую сообщение сюда :

--------------------------------------------------------------

Ну чтож, коли пошла такая пьянка, подкину и я своих дровишек в костёрчик )

Раз тут собрались одни АВРщики :)), пришлось поставить CV и черкнуть на нём примерчик обработки кнопок. CV взял из-за генератора кода, т.к. не хочется лезть в даташит на совершенно незнакомые мне МК. В общем, за им генерируемый код меня не пинать :)) . Остальное, что касается моей писанины - обсуждаем, критикуем, закидываем помидорами, .... :)))

В архиве проект + файл всеми любимого протеуса:

butt.rar

Программа хорошо комментирована, так что, думаю, вопросов не возникнет.

Если кому будет интересно, могу добавить обработку длинного нажатия.

Изменено пользователем Alex
Скопировал сообщение сюда

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
3d_killer    3

то есть в литературе искать прерывания информацию об этом правильно?

Поделиться сообщением


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

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас