16 лет занимаюсь программированием промышленной автоматики на PLC Omron, Siemens. С ардуино столкнулся месяца 2 назад. И сразу же стали бесить delay(), когда для того, чтобы правильно отработал какой-то один алгоритм, приходится тормозить весть проц и все другие аглоритмы. Для того чтобы использовать фронты дискретных входов, нужно избавиться от дребезга контактов нужно собирать схемы с триггером Шмитта. А если таких входов штук 20??? В итоге написал библиотеку из 2-х классов Timer_P и DI. Оттестировал. Вроде работает. Описание: Код (Text): Timer_P - аналогия блока Timer_P(FB5) промышленных логических контроллеров (PLC) Siematic Siemens Класс Timer_P имеет 5 публичных методов, публичные свойства отсутствуют. Timer_P() - конструктор класса, вызывается при декларации объекта класса Timer_P, аргументы отсутствуют. void TimerV(boolean Condition, boolean Reset,int Mode, long Duration) - метод запуска и контроля таймера Аргументы: - boolean Condition - условие запуска таймера - boolean Reset - условие принудительного сброса таймера - int Mode - режим работы таймера (0...4) см. временную диаграмму Timer_P.png - long Duration - длительность работы таймера в миллисекундах boolean Timer(boolean Condition, boolean Reset,int Mode, long Duration) - метод запуска и контроля таймера с возвратом значения Аргументы аналогично TimerV. возвращает признак срабатывания таймера boolean Q0() - возвращает признак срабатывания таймера. long GetRemains() - оставшееся время до срабатывания таймера в миллисекундах. Timer_P работает с фронтами сигналов, поэтому очень чувствителен к дребезгу входных сигналов. Методы TimerV и Q0 добавлены, чтобы можно было делать таймеры с самосбросом и для удобного испоьзования признака срабатывания таймера см. Blink_TimerP в примерах Для того, чтобы использовать Timer_P для входов Arduino рекомендуется "пропускать" их через класс DI. Класс DI используется для программой фильтрации дребезга контакта. Класс DI имеет 3 публичных метода, публичные свойства отсутствуют. DI() конструктор класса без аргументов. Может быть использован для создания массива объектов класса DI. см. cowboys_X-Dron в примерах DI(int PIN, long Duration) конструктор класса, вызывается при декларации объекта класса DI. Аргументы: - int PIN - номер пина Arduino - long Duration - длительность времени фильтрации. boolean DI_Read() - считывание фильтрованного входа, возвращает фильтрованное значени входа. void Init(int PIN, long Duration) - метод инициализации объектов класса DI. Используется при инициализации массива DI. см.cowboys_X-Dron в примерах 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 Есть в планах написать еще один класс, который будет имитировать на Arduino входы-выходы как на промышленных контроллерах. Будет: разбиение адресного поля на отдельные разделы (дискретные входы, дискретные выходы, аналоговые входы, аналоговые выходы). - Считывание входов в начале цикла loop. - Вывод выходов в конце цикла loop. - Возможность настройки антидребезга по любому входному дискретному каналу. Для мелких контроллеров это практически не актуально, а вот для меги может пригодиться. Когда дойдут руки - не знаю. Наиболее интересный для себя проект - это все-таки беспроводной ввод-вывод.
Интересное решение. Сам ярый противник delay, пользуюсь простой функцией таймера на основании mills(), такой же принцип как у Вас в библиотеке. А для подавления дребезга подтягивающими резисторами. Как я понял разницу режимов, можно увидеть на видео?
Подробная временнАя диаграмма режимов описана во вложенном файле поста. Этот же файл присутствует в библиотеке. Timer_P.png. Только сейчас заметил небольшую ошибку в диаграмме 4-го режима, сейчас подправлю. Подтягивающие резисторы помогают от наводок, а вот от дребезга не спасают. Пока не работаешь с фронтами это не заметно. В Эксперементе 14 вики, там где считаются нажатия кнопок, антидребезг сделан так. Код (Text): if (buttonWasUp && !digitalRead(BUTTON_PIN)) { delay(10); if (!digitalRead(BUTTON_PIN)) clicks = (clicks + 1) % 10; } buttonWasUp = digitalRead(BUTTON_PIN); Т.е. ловим первый первый фронт нажатия, делаем паузу 10мс, снова считываем кнопку. Если она нажата, то увеличиваем количество нажатий на 1. А представим на миг, что все-таки дребезг продолжается и мы поймали отпущенное состояние, от нажатие не засчитается. delay() опять же бесит.
Спасибо за пояснение, надеюсь у Вас хватит времени воплотить все задумки. Очень плохо разбираюсь в электронике (главное, что усвоил электроника - тонкий кабель, электрика - толстый), пока не было необходимости в ее изучении. Надеюсь закончу текущий проект и сяду за парту.
Обновил библиотеку. Добавил в классе DI: DI() конструктор класса без аргументов. Может быть использован для создания массива объектов класса DI. см. cowboys_X-Dron в примерах void Init(int PIN, long Duration) - метод инициализации объектов класса DI. Используется при инициализации массива DI. см.cowboys_X-Dron в примерах. http://wiki.amperka.ru/конспект-arduino:кнопочные-ковбои Как задание для самостоятельного решения написано: Начинаешь ловить фронты - ловишь дребезг. Либо используй внешнюю обвязку в виде триггера Шмитта, или делай программно. Решение на моей библиотеке выглядит так, лазейки нет. и без delay() Код (Text): #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)); //обновляем светодиоды } } В этом коде, на самом деле есть чит за первого игрока. Если, чисто случайно, нажали одновременно, то предпочтение отдается первому. Более справедливый void loop должен быть таким: Код (Text): 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[0] || Win[1]) //Если победитель есть { Tour = false; //то тур завершился tone(BUZZER_PIN, 4000, 1500); //даем сигнал } for (int player = 0; player < PLAYER_COUNT; player++) digitalWrite(ledPins[player], LedTimers[player].Timer(Win[player], false, 1, 1500)); //обновляем светодиоды } При одновременном нажатии дается ничья, загораются оба светодиода
Раз Вы так серьезно подошли к вопросу, возможно Вам будет полезна библиотека работы с ножками на прямую. Данный код был взят из проекта Marlin, оригинал тут. Мой обрезанный код(большая часть чипов мне не нужна) с измененной стилистикой, небольшими пометками в прищепке (сюда в виде листинга не поместится). Кроме этого отказался от отключения прерываний при записи в порт. Пока разобрался с цифровыми выходами. Но возможностей у нее больше. В чем его плюсы, более быстрое выполнение, пример(код, отрывки ниже) используется Mega 2560 и примерный подсчет на основании micros (на глаз, хотите точности используйте таймеры). Код с FastIO, тут использовался оригинальная библиотека. Код (Text): WRITE(pin11, HIGH); // +1 digitalWrite +4 // +0 WRITE WRITE(pin12, HIGH); // +1 digitalWrite +4 // +0 WRITE WRITE(pin13, HIGH); // +1 digitalWrite +8 // +0 WRITE WRITE(pin14, HIGH); WRITE(pin15, HIGH); WRITE(pin16, HIGH); WRITE(pin17, HIGH); WRITE(pin18, HIGH); WRITE(pin19, HIGH); WRITE(pin20, HIGH); WRITE(pin21, HIGH); WRITE(pin22, HIGH); WRITE(pin23, HIGH); WRITE(pin24, HIGH); WRITE(pin25, HIGH); // 15 digitalWrite + 72 // 15 WRITE + 4 bpin0 = READ(pin1); // +1 digitalRead +8 // +4 READ bpin1 = READ(pin2); // +1 digitalRead +4 // +0 READ bpin2 = READ(pin3); // +1 digitalRead +4 // +0 READ bpin3 = READ(pin4); bpin4 = READ(pin5); bpin5 = READ(pin6); bpin6 = READ(pin7); bpin7 = READ(pin8); bpin8 = READ(pin9); bpin9 = READ(pin10); // 10 digitalRead + 48 // 10 READ + 8 Так же она позволяет уменьшить код, пример Blink without Delay, будет весить 630 байт, вместо 1 028 байт. Очень удобно, когда работаешь с большим кол-вом пинов. Удобна, тем, что используется более понятное наименования пина, просто 13 а не PINB7 (к примеру). Появится время детально разберу Вашу библиотеку и если понравится уволоку себе, надеюсь не будете против. UPD. Прямой доступ к цифровым пинам для Arduino Due, вдруг кому-то будет любопытно Код (Text): inline void digitalWriteDirect(int pin, boolean val){ if(val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin; else g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin; } inline int digitalReadDirect(int pin){ return !!(g_APinDescription[pin].pPort -> PIO_PDSR & g_APinDescription[pin].ulPin); } И тесты скорости выполнения Код (Text): /*digitalWriteDirect(pin11, HIGH); // +1 digitalWrite +3 // +1 digitalWriteDirect +1 digitalWriteDirect(pin12, HIGH); // +1 digitalWrite +2 // +1 digitalWriteDirect +0 digitalWriteDirect(pin13, HIGH); // +1 digitalWrite +3 // +1 digitalWriteDirect +0 digitalWriteDirect(pin14, HIGH); digitalWriteDirect(pin15, HIGH); digitalWriteDirect(pin16, HIGH); digitalWriteDirect(pin17, HIGH); digitalWriteDirect(pin18, HIGH); digitalWriteDirect(pin19, HIGH); digitalWriteDirect(pin20, HIGH); digitalWriteDirect(pin21, HIGH); digitalWriteDirect(pin22, HIGH); digitalWriteDirect(pin23, HIGH); digitalWriteDirect(pin24, HIGH); digitalWriteDirect(pin25, HIGH);*/ // 15 digitalWrite + 38 // 15 digitalWriteDirect + 5 /*bpin0 = digitalReadDirect(pin1); // +1 digitalRead +1 // +1 digitalReadDirect +1 bpin1 = digitalReadDirect(pin2); // +1 digitalRead +2 // +1 digitalReadDirect +0 bpin2 = digitalReadDirect(pin3); // +1 digitalRead +0 // +1 digitalReadDirect +1 bpin3 = digitalReadDirect(pin4); bpin4 = digitalReadDirect(pin5); bpin5 = digitalReadDirect(pin6); bpin6 = digitalReadDirect(pin7); bpin7 = digitalReadDirect(pin8); bpin8 = digitalReadDirect(pin9); bpin9 = digitalReadDirect(pin10);*/ // 10 digitalRead + 11 // 10 digitalReadDirect + 7 Было взято с arduino.cc уже не вспомню где.
Спасиб, за инфу. Если дойду до симуляции PLC на Mega, то попробую разобраться. Сейчас уже моя беспроводка стала приходить.
Не за что, все помогаем друг другу по мере сил. Он подходит не только для Mega, так же для UNO, Nano и т.д. вообщем на все платы с чипами ATmega168, ATmega328, ATmega328P, ATmega1280, ATmega2560 (в оригинале больше, но они очень редкие к примеру серия AT90USB, не нашел только ATmega32u4, его можно дописать изучив пин мапинг). Узнать какой чип, на конкретной ардуине, можно тут.
Обновил библиотеку. В класс Timer_P добавил 2 метода. Код (Text): void TimerV(boolean Condition, boolean Reset,int Mode, long Duration) - метод запуска и контроля таймера Аргументы: - boolean Condition - условие запуска таймера - boolean Reset - условие принудительного сброса таймера - int Mode - режим работы таймера (0...4) см. временную диаграмму Timer_P.png - long Duration - длительность работы таймера в миллисекундах boolean Q0() - возвращает признак срабатывания таймера. Методы TimerV и Q0 добавлены, чтобы можно было делать таймеры с самосбросом и для удобного использования признака срабатывания таймера. Меандр с выводом на лампочку с использованием библиотеки пишется так. Код (Text): #include <Timer_P.h> //подключаем библиотеку Timer_P - работа с таямерами из набора библиотек от X-Dron #define LED_PIN 7 //PIN светодиода #define Period 2000 //Период меандра #define Duration 500 //Импульс меандра Timer_P Period_T, Pulse_T; //создание и инициализация объекта Timer_Test класса Timer_P void setup() { //Режимы выходов pinMode(LED_PIN, OUTPUT); } void loop() { Period_T.TimerV(!Period_T.Q0(), 0, 2, Period); // Самосбрасывающийся таймер периода Pulse_T.TimerV(!Period_T.Q0(), 0, 1, Duration);// Импульс длительности Duration по началу периода digitalWrite(LED_PIN, Pulse_T.Q0()); //Выводим меандр на лампочку. }