Таймеры и программный антидребезг входов.
- Войдите на сайт для отправки комментариев
16 лет занимаюсь программированием промышленной автоматики на PLC Omron, Siemens. С ардуино столкнулся месяца 2 назад. И сразу же стали бесить delay(), когда для того, чтобы правильно отработал какой-то один алгоритм, приходится тормозить весть проц и все другие аглоритмы.
Для того чтобы использовать фронты дискретных входов, нужно избавиться от дребезга контактов нужно собирать схемы с триггером Шмитта. А если таких входов штук 20???
В итоге написал библиотеку из 2-х классов Timer_P и DI.
Оттестировал. Вроде работает.
Описание:
Класс Timer_P имеет 3 публичных метода, публичные свойства отсутствуют.
Timer_P() - конструктор класса, вызывается при декларации объекта класса Timer_P, аргументы отсутствуют.
boolean Timer(boolean Condition, boolean Reset,int Mode, long Duration) - основной метод работы таймера
Аргументы:
- boolean Condition - условие запуска таймера
- boolean Reset - условие принудительного сброса таймера
- int Mode - режим работы таймера (0...4) см. временную диаграмму Timer_P.png
- long Duration - длительность работы таймера в миллисекундах
возвращает признак срабатывания таймера
long GetRemains() - оставшееся время до срабатывания таймера в миллисекундах.
Timer_P работает с фронтами сигналов, поэтому очень чувствителен к дребезгу входных сигналов.
Для того, чтобы использовать Timer_P для входов Arduino рекомендуется "пропускать" их через класс DI.
Класс DI используется для программой фильтрации дребезга контакта.
Класс DI имеет 3 публичных метода, публичные свойства отсутствуют.
DI(int PIN, long Duration) конструктор класса, вызывается при декларации объекта класса DI.
Аргументы:
- int PIN - номер пина Arduino
- long Duration - длительность времени фильтрации.
boolean DI_Read() - считывание фильтрованного входа, возвращает фильтрованное значени входа.
void DI_Refresh() - обновление входа, связанного с объектом класса.
Делать pinMode для входов используемых для DI не нужно. Они инициализируются при создании объекта класса DI.
При работе со входами используется INPUT_PULLUP-режим. Значения входов нормализовываются во время обработки класса.
Замкнутая кнопка - логическая 1 в DI.
Пример использования классов в скетче Arduino
-------------------------------------------------
#include <DI.h> //подключаем библиотеку DI - обработка дискретного входа из набора библиотек от X-Dron
#include <Timer_P.h> //подключаем библиотеку Timer_P - работа с таямерами из набора библиотек от X-Dron
#define Condition_PIN 2 //PIN кнопки условия работы таймера
#define Reset_PIN 3 //PIN кнопки сброса таймера
#define QTimer_PIN 7 //PIN светодиода работы таймера
#define QReset_PIN 8 //PIN светодиода сброса таймера
Timer_P Timer_Test; //создание и инициализация объекта Timer_Test класса Timer_P
// Создание объектов типа "Дистретный вход" они посажены описанные выше пины. Фильтр антидребезга 30мс.
DI Condition_IN(Condition_PIN, 30);
DI Reset_IN(Reset_PIN, 30);
void setup() {
//Режимы выходов
pinMode(QTimer_PIN, OUTPUT);
pinMode(QReset_PIN, OUTPUT);
//делать pinMode для входов не нужно. Они инициализируются при создании объекта класса DI см. DI.cpp
//при работе со входами используется INPUT_PULLUP-режим. Значения входов нормализовываются во время обработки класса.
//замкнутая кнопка - логическая 1.
}
void loop() {
// Обновляем значение дискретных входов
// Производится их считывание с пинов и фильтрация через внутреннюю переменную класса.
Condition_IN.DI_Refresh();
Reset_IN.DI_Refresh();
//Считываем состояние фильтрованной кнопки сброса таймера и выводим его на лампочку.
digitalWrite(QReset_PIN, Reset_IN.DI_Read());
//Запускаем таймер по фильтрованной кнопке запуска таймера.
//Сброс выполнения таймера по фильтрованной кнопке сброса таймера.
//Режим работы таймера - Mode = 1 extended pulse
//Время работы таймера - 1900мс.
//Результат работы таймера выводим на светодиод работы таймера
digitalWrite(QTimer_PIN, Timer_Test.Timer(Condition_IN.DI_Read(), Reset_IN.DI_Read(), 1, 1900));
//Получаем значение отсрочки срабатывания таймера
long T_Remains = Timer_Test.GetRemains();
}
-------------------------------------------------
Библиотека находится по адресу
https://github.com/X-Dron/X-Dron_lib
Ссылка для скачивания https://github.com/X-Dron/X-Dron_lib/archive/master.zip
В примерах есть видео для разных режимов работы таймера. Временную диаграмму см. в Timer_P.png
https://raw.githubusercontent.com/X-Dron/X-Dron_lib/master/Timer_P.png
Есть в планах написать еще один класс, который будет имитировать на Arduino входы-выходы как на промышленных контроллерах. Будет:
разбиение адресного поля на отдельные разделы (дискретные входы, дискретные выходы, аналоговые входы, аналоговые выходы).
- Считывание входов в начале цикла loop.
- Вывод выходов в конце цикла loop.
- Возможность настройки антидребезга по любому входному дискретному каналу.
Для мелких контроллеров это практически не актуально, а вот для меги может пригодиться.
Когда дойдут руки - не знаю. Наиболее интересный для себя проект - это все-таки беспроводной ввод-вывод.
X-Dron, было бы любопытно узнать, в чём отличие ваших библиотек от уже существующих.
Хе. Библиотека DigitalTube с классом TM1637, например, работает с 4-х разрядным семисегментным индикатором, а класс Timer_P - создает разные импульсы. Библиотека DHT с классом DHT работает с датчиками температуры и влажности. Каков вопрос таков и ответ. Вы не указали, с какими библиотеками сравнивать, я других аналогичных не встречал. А если серьезно, то Безусловно, везде используются одни и те же принципы работы с основными базовыми наборами функций и атрибутов. Но можно всю жизнь писать на базовом уровне, а можно использовать библиотеки. Это как писать на ассемблере или на объектно-ориентированном языке. По поводу работы с кнопками есть закрепленная тема на форуме. Видел такой вариант фильтрации антидребезга
if (buttonWasUp && !digitalRead(BUTTON_PIN)) { delay(10); if (!digitalRead(BUTTON_PIN)) ; //типа сделан антидребезг, что-то можно сделать } buttonWasUp = digitalRead(BUTTON_PIN);Т.е. ловим первый первый фронт нажатия, делаем паузу 10мс, снова считываем кнопку. В первых строках поста я описал, что delay() бесит. В системе регулирования паровой турбины за 10мс нужно обработать датчики положения сервоприводов, обработать регуляторы положения, выдать задания на позиционирование. А здесь 10мс фильтруем кнопку и все другое подождет. Я практически не сомневаюсь, что многие будут плеваться, т.к. если полностью отказаться от delay(), то методы программирования будут сильно отличаться. Но они гораздо ближе к тем, что используются в промышленных PLC. Простой пример. http://wiki.amperka.ru/%D0%BA%D0%BE%D0%BD%D1%81%D0%BF%D0%B5%D0%BA%D1%82-... Скетч сделан с использованием delay()
#define BUZZER_PIN 12 // пин с пищалкой #define PLAYER_COUNT 2 // количество игроков-ковбоев // вместо перечисления всех пинов по-одному, мы объявляем пару // списков: один с номерами пинов с кнопками, другой — со // светодиодами. Списки также называют массивами (англ. array) int buttonPins[PLAYER_COUNT] = {3, 13}; int ledPins[PLAYER_COUNT] = {9, 11}; void setup() { pinMode(BUZZER_PIN, OUTPUT); for (int player = 0; player < PLAYER_COUNT; ++player) { // при помощи квадратных скобок получают значение в массиве // под указанным в них номером. Нумерация начинается с нуля pinMode(ledPins[player], OUTPUT); pinMode(buttonPins[player], INPUT_PULLUP); } } void loop() { // даём сигнал «пли!», выждав случайное время от 2 до 7 сек delay(random(2000, 7000)); tone(BUZZER_PIN, 3000, 250); // 3 килогерца, 250 миллисекунд for (int player = 0; ; player = (player+1) % PLAYER_COUNT) { // если игрок номер «player» нажал кнопку... if (!digitalRead(buttonPins[player])) { // ...включаем его светодиод и сигнал победы на 1 сек digitalWrite(ledPins[player], HIGH); tone(BUZZER_PIN, 4000, 1000); delay(1000); digitalWrite(ledPins[player], LOW); break; // Есть победитель! Выходим (англ. break) из цикла } } }Как задание для самостоятельного решения написано: В игре есть лазейка: кнопку можно зажать до сигнала «пли!» и таким образом сразу же выиграть. Дополните программу так, чтобы так выиграть было нельзя. Начинаешь ловить фронты - ловишь дребезг. Либо используй внешнюю обвязку в виде триггера Шмитта, или делай программно. Решение на моей библиотеке выглядит так, лазейки нет.
#include <DI.h> #include <Timer_P.h> #define BUZZER_PIN 11 // пин с пищалкой #define PLAYER_COUNT 2 // количество игроков-ковбоев int buttonPins[PLAYER_COUNT] = {2, 3}; int ledPins[PLAYER_COUNT] = {7, 8}; boolean Tour, Win[PLAYER_COUNT]; DI Keys[PLAYER_COUNT]; //создание массива из класса DI - дискретный вход Timer_P KeyTimers[PLAYER_COUNT], LedTimers[PLAYER_COUNT], StartDelayTimer; //создание экземпляров таймеров void setup() { pinMode(BUZZER_PIN, OUTPUT); Tour = false; // инициализация цикла игры for (int player = 0; player < PLAYER_COUNT; player++) { pinMode(ledPins[player], OUTPUT); //инициализация выходов на светодиоды Keys[player].Init(buttonPins[player], 10); //инициализация дискретных входов класса DI, антидребезг 10мс } } void loop() { if (StartDelayTimer.Timer((!Tour), false, 2, random(3000, 8000))) // если тур игры не запущен, то через промежуток 3..8 секунд { Tour = true; //запускаем тур tone(BUZZER_PIN, 3000, 250); //даем сигнал } for (int player = 0; player < PLAYER_COUNT; player++) { Keys[player].DI_Refresh(); //Обновляем входа Win[player] = KeyTimers[player].Timer(Keys[player].DI_Read(), !Tour, 0, 1); //Определяем победителя //Формируется импульс длительностью до 1мс по нажатию клавиш. if (Win[player]) //Если победитель есть { Tour = false; //то тур завершился tone(BUZZER_PIN, 4000, 1500); //даем сигнал } digitalWrite(ledPins[player], LedTimers[player].Timer(Win[player], false, 1, 1500)); //обновляем светодиоды } }Здесь используется обновленная библиотека, в репозитории еще вчерашняя. Обновлю через час-два. Добавился еще один конструктор класса DI и метод инициализации. Это дает возможность создавать массивы класса DI.
с помощью этой библиотеки написан скетч на подсветку с двумя датчиками, ты помогал,кстати, спасибо
сейчас что то глюкануло и не работает, не мог бы еще раз помоч?
Несколько некоректно проводить паралели с тригером Шмидта, с дребезгом эти тригеры не справляются, да они не для того и придуманы. У ардуино Шмидты кстати есть, на каждом порте с гистерезисом около 0.5В, т.е. в "1" при 2.7В, а обратно в "0" при 2.2В
16 лет занимаюсь программированием промышленной автоматики на PLC Omron, Siemens. С ардуино столкнулся месяца 2 назад. И сразу же стали бесить delay(), когда для того, чтобы правильно отработал какой-то один алгоритм, приходится тормозить весть проц и все другие аглоритмы.
Для того чтобы использовать фронты дискретных входов, нужно избавиться от дребезга контактов нужно собирать схемы с триггером Шмитта. А если таких входов штук 20???
Если вы так охрененно озабочены дребезгом, юзайте Кортекс от Nuvoton. Там на портах обработка дребезга хардварная и всё настраивается в регистрах порта при ините.
Для подавления дребезга я использую следующую конструкцию
if (k1cnt > 3){ if ((PINB & dataPin)==0) k1cnt++; else {if (k1cnt > 40) key=2; else key=1;} k1cnt=0;} else if ((PINB & dataPin)==0) k1cnt++;Висит либо в цикле на конструкции
tm=millis(); if(tm-tio > 20) { tio=tm; if (k1cnt > 3){ if ((PINB & dataPin)==0) k1cnt++; else {if (k1cnt > 40) key=2; else key=1;} k1cnt=0;} else if ((PINB & dataPin)==0) k1cnt++; }либо в прерывании таймера, если цикл работает больше 20 мс. Но это как то редко случается.
Выдаёт короткоё и длинное нажатие. Легко позволяет расширить до быстрого повтора короткого нажатия при удержании больше определённого времени - часто использую при настройке параметров. Это конечно не титановый велосипед, но меня полностью удволетворяет. Занимает мало места и времени. Не имеет задержек при подавлении дребезга. Из минусов - для каждой кнопки надо прописать свои параметры и писать строки.