7-и зонное освещение:вкл\выкл с разных выключателей

Alexxmart
Offline
Зарегистрирован: 08.10.2017

     MK UNO плюс 7 зон освещения от 7 кнопок с вариативным управлением по количеству нажатий.

Здравствуйте, уважаемые! В процессе осмысления схема управления 7 группами нагрузки, подключеные  через оптосимисторы. Вопрос- как описать в скетче логику для управления одной кнопкой разными зонами освещения? Описание: LED1 - лампа над лестницей с выключателем S1,  LED2;LED5/S2;S3 - верхний свет, LED3;LED4/S3;S4 настенные светильники, LED6/S6 -туалет, LED7/S7 - мастерская. Связь элементов схемы через витую пару, расстояние не более 8 метров на ветвь.  Предполагается, что по кнопке S1 должны управляться LED1;2;5;7 и общее выключение, S2;S3 совместно управляют LED2;LED3,  S4;S5 для LED4;LED5, S6 индивидуально для LED6,  S7 для LED7;LED1;LED2;LED5.  

             П.С.  Это моя первая схема, идеально совпавшая во времени с осенним обострением и ожидаемым периодом долгих зимних вечеров) - почему бы и нет? в дальнейшем есть идеи по расширению функционала-  подключение часов для привязки интенсивности освещения от времени суток, имитация присутствия, датчики движения и охранные функции, обесточивание по срабатыванию датчиков, управление климатом и вентиляцией.  


 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Вот я смотрю на схему и понимаю, что ТС "нулевой". Видно к обострению.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Alexxmart пишет:

Вопрос- как описать в скетче логику для управления одной кнопкой разными зонами освещения? 

Сначала нужно описать её словами так, чтобы, кроме Вас, ещё кто-то понял. Пока Вы этого не сделаете, к скетчу даже не прикасайтесь.

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

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

Если вместо выключателей использовать кнопку, то все упрощается:

Alexxmart
Offline
Зарегистрирован: 08.10.2017

Логика для кнопки S1- по первому нажатию включает LED1, при повторном нажатии до истечения одной секунды дополнительно включает LED2;LED5;LED7. По истечению первой секунды от первого нажатия S1 выключает включенные ею нагрузки, при повторном нажатии отключает все освещение LED1-7. ( клик S1 включает LED1, контроллер ожидает повторное нажатие до 1сек для включения дополнительных трех зон; переход в ожидание следущего нажатия кнопки уже для отключения с выдержкой интервала в 1сек.для возможного второго нажатия; переход в начало цикла.)  Похожая логика будет и для остальных кнопок, кроме S6/LED6 но с разными зонами освещения

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Значит так,

1 первое нажатие - включение Led1
2 следующее, если в пределах 1 сек - включение LED2;LED5;LED7
3. если поступит ещё одно в течение той же секунды, то ХЗ
4. после секунды, по нажатию выключаем, всё, что включали
5. по следующему нажатию. выключаем всё вообще
6. если будет ещё нажатие, то ХЗ

Я всё правильно понял?

А теперь приведите в божеский вид, чтобы никаких ХЗ не было.

Кстати, надеюсь, Вы повесите инструкцию для пользователя возле каждой кнопки? Ну или поставите бутылку, т.к. без бутылки в этом точно никто не разберётся :)

Alexxmart
Offline
Зарегистрирован: 08.10.2017

Пункты 3 и 6 - игнорирование, как мне кажется т.е. максимум на включение 1 или 2 нажатия, на  выключение 1 или 2 нажатия. Таким образом любое второе нажатие заканчивает период вкл/выкл.

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Вы это, голову-то включайте. Вам же сказали, подумать надо, а не ляпать что придётся. Если игнорировать п.6, то один раз включив и выключив, Вы больше уже не сможете ничего включить. Как Вы включите, если все нажатия после выключения игнорируются?

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

Alexxmart
Offline
Зарегистрирован: 08.10.2017

Евгений, сейчас подробнее подумаю

Alexxmart
Offline
Зарегистрирован: 08.10.2017

Дано: кнопка без фиксации S1, нагрузка в виде ламп LED1 и LED2.

1.1. Период1 "ВКЛ"  - ожидание до действия на S1 "нажатие1"(первое)

    1.1.2. действие"нажатие1" вызывает включение LED1-->запуск "таймер 1секунда"

    1.1.3. Окончание отсчета "таймер 1секунда" приводит к переводу работы программы в период2 "ВЫКЛ".

        1.2.1 действие "нажатие2"(второе), выполнимо, если "таймер 1секунда от нажатия1" еще не вызвал  переход в период2"ВЫКЛ", вызывает включение LED2-->отмену отсчета "таймер 1секунда"-->переход в период2 "ВЫКЛ".

2.1. Период2 "ВЫКЛ" -ожидание до действия на S1 "нажатие1"

     2.1.2. действие"нажатие1" вызывает выключение LED1-->запуск "таймер 1секунда"

     2.1.3. Окончание отсчета "таймер 1секунда" приводит к переводу работы программы в период1 "ВКЛ".

        2.2.1 действие "нажатие2"(второе), выполнимо, если "таймер 1секунда от нажатия1" еще не вызвал  переход в период1"ВКЛ", вызывает выключение LED2-->отмену отсчета "таймер 1секунда"-->переход в период1 "ВКЛ".

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

     

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

Знаете что такое автомат Мили? И как он строится? А то, если знаете, то чего мне пальцы бить-то. Не знаете расскажу.

Alexxmart
Offline
Зарегистрирован: 08.10.2017

Про автомат Мили погуглил, суть понятна только в общих чертах, вроде это алгоритм позволяющий проделать всю работу по шагам. Если будет возможность опишите применение автомата Мили на примере моего будущего скетча, это облегчит мне задачу) Пока курю форум, там есть отличный пост про "титановый велосипед для тактовой кнопки"

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Alexxmart пишет:

там есть отличный пост про "титановый велосипед для тактовой кнопки"

Велосипед Вам не пригодится, он плохо ложится на автоматный поход.

Автомат строится очень просто.

Сначала выписываются все возможные состояния в которых он может находится. В Вашем случае они примерно таковы (проверяйте. может я чего пропустил).

1. Ожидаем нажатия кнопки включения
2. Ожидаем второго нажатия кнопки включения
3. Ожидаем кнопки выключения
4. Ожидаем второго нажатия кнопки выключения

Вроде всё.

Дальше назначаем начальное состояние - у нас это состояние №1

теперь выписываются все возможные сигналы (воздействия). У нас они вроде такие

1. Нажата кнопка
2. Истёк таймаут

Вроде, других нет.

Теперь строится матрица (таблица переходов и выходов). Какжда строка соответсвует состоянию, каждый столбец воздействию. В клетках на пересечении состояния и воздействия пишутся ровно две вещи А) что сделать и Б) в какое состояние перейти. И интепретируется это так: если автомат находится в данном состоянии и поступило данное воздействие, то сделать (А) и перейти в состояние (Б). Примерно так:

  Нажата кнопка Истёк таймаут
1. Ожидаем нажатия кнопки включения
    (начальное состояние)
А) включить Led1
Б) №2
 
2. Ожидаем второго нажатия кнопки включения А) включить Led2
Б) №3
А) ---
Б) №3
3. Ожидаем кнопки выключения А) выключить Led1
Б) №4
 
4. Ожидаем второго нажатия кнопки выключения А) выключить Led2
Б) №1
А) ---
Б) №1

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

Это и есть таблица переходов и выходов. Она полностью задаёт автомат.  Всё с нею понятно?

Программирование такой таблицы дело чисто техническое. Более того, программы получаются всегда одинаковые, потому есть куча готовых, в них только таблицу подставить. Но если Вы этого никогда не делали. то лучше сделайте "ручками".

Знаете как делать? Я больше не нужен?

Alexxmart
Offline
Зарегистрирован: 08.10.2017

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

А если Вы подумали, что его надо как-то специально останвливать, таки нет. В том-то и прелесть автомата. Посмотрите в таблицу, прямо пальцем водите.

Если автомат в состоянии 1 то ему плевать срабатывает там таймер или нет, он на это просто не смотрит.

Если автомат в состоянии 2 и сработал таймер, он 9автомат тут же перешёл в состояние 3, в еотором ему опять же плавать на таймер.

Смотрите - автомат - очень простая и мощная вещь.

Если хотите. мы это дожмём до конца.

Alexxmart
Offline
Зарегистрирован: 08.10.2017

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Да, не надо вознограждений - это больно дорого выйдет.

А по теме, пожалуй Вы правы - для "чистоты" давайте добавим завод таймера в действия. некрасиво делать что-то, чего нет в таблице. Несистемно. Итак таблица с заводом таймера выглядит так

&

  Нажата кнопка Истёк таймаут
1. Ожидаем нажатия кнопки включения
    (начальное состояние)
А) включить Led1;
    завести таймер на 1 сек
Б) №2
 
2. Ожидаем второго нажатия кнопки включения А) включить Led2
Б) №3
А) ---
Б) №3
3. Ожидаем кнопки выключения А) выключить Led1
    завести таймер на 1 сек
Б) №4
 
4. Ожидаем второго нажатия кнопки выключения А) выключить Led2
Б) №1
А) ---
Б) №1

 

А теперь, "следите за руками. То, что я сейчас буду писать - касасается любого автомата. Они все одинаковые и различаются только таблицей. Когда таблица готова, далее всё всегда одинаково и одна программа не отличается от другой.

Итак, у Вас есть два события: "нажата кнопка" и "истёк таймер".

Значит, прежде чем писать программу, Вам надо написать две функции, которые возвращают true, если событие произошло и false - в противном случае. Т.е. их заголовки:

inline bool isButtonPressed(void) {
   .....
}

inline bool isTimerElapsed(void) {
    ....
}

Сами функции мы напишем позже. пока же считаем. что они у нас есть.

Также, у Вас есть пять действий: "включить Led1", "включить Led2", "выключить Led1", "выключить Led2" и "завести таймер на 1 сек". Опять же пишем соответсвенно пять функций:

// включить Led1
inline void led1ON(void) {
    ....
}

// включить Led2
inline void led2ON(void) {
    ....
}

// выключить Led1
inline void led1OFF(void) {
    ....
}

// выключить Led2
inline void led2OFF(void) {
    ....
}

// завести таймер на 1 сек
inline void startTimer(void) {
    ....
}

Функции эти тривиальны и мы их напишем позже, пока же опять считаем, что они у нас есть.

Тогда оснновная программа выглядит совсем просто (я сейачс пиша для пояснения и не проверяю на опечатки и т.п., завтра откомпилируем и сделаем как надо). так вот, основная программа выглядит предельно просто. Ведём пальцеп по таблице и пишем точно то, что написано там:

enum AUTOMATA_STATES {
	ON_FIRST_PRESS,
	ON_SECOND_PRESS,
	OFF_FIRST_PRESS,
	OFF_SECOND_PRESS
};

void runAutomata(void) {
	static AUTOMATA_STATES currentState = ON_FIRST_PRESS;
	switch (currentState) {
		case ON_FIRST_PRESS:
			if (isButtonPressed()) {
				led1ON();
				startTimer();
				currentState = ON_SECOND_PRESS;
			}
			break;
		case ON_SECOND_PRESS:
			if (isButtonPressed()) {
				led2ON();
				currentState = OFF_FIRST_PRESS;
			}
			else if (isTimerElapsed()) {
				currentState = OFF_FIRST_PRESS;
			}
			break;
		case OFF_FIRST_PRESS:
			if (isButtonPressed()) {
				led1OFF();
				startTimer();
				currentState = OFF_SECOND_PRESS;
			}
			break;
		case OFF_SECOND_PRESS:
			if (isButtonPressed()) {
				led2OFF();
				currentState = ON_FIRST_PRESS;
			}
			else if (isTimerElapsed()) {
				currentState = ON_FIRST_PRESS;
			}
			break;
	}
}

Сравните с таблицей и убедитесь, что это просто слово в слово переписанная таблица и не более того.

осталось только вызывать функцию runAutomata постоянно в loop (ну и дописать недописанные простые функции).

Заметьте, если Вы сейчас поймёте эту идею, Вы сможете сделать почти любую автоматику - главное построить таблицу, а дальше всё и всегда одинаково.

Alexxmart
Offline
Зарегистрирован: 08.10.2017

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, давайте продолжать.

Вы сказали, что с таблицей всё в порядке. Странно. Значит, с логикой что-то не то. Давайте посмотрим на примерах:

Пример №1.
Рассмотрим последовательность действий

1.Нажали кнопку (led1 горит, мы в состоянии №2)
2. Выждали больше секунды (led1 горит, мы в состоянии №3)
3. Нажали кнопку (ничего не горит, мы в состоянии №4)
4. Не дожидаясь таймаута ещё раз нажали кнопку (выключили led2, который итак был выключен и перешли в состояние №1)

Получается, что при нажатии кнопки в п.4 внешне вообще ничего не происходит. Это явно будет сбивать с панталыку пользователя.

Пример №2
Рассмотрим последовательность действий

1.Нажали кнопку (led1 горит, мы в состоянии №2)
2.Не выжидая нажали ещё раз (led1 и led2 горят, мы в состоянии №3)
3. Нажали кнопку (led1 горит, мы в состоянии №4)
4. Выждали больше секунды (led1 горит, мы ушли в состояние №1)

Приплыли. Теперь, для того, чтобы погасить led1 нам надо сначал зажечь led2 (другого выхода из состояния №1 нет). Это к примеру со спящим ребёнком – чтобы погасить свет, нам надо сначала зажечь его на полную.

Понравились примеры?

Это я к тому, что Вы должны твёрдо усвоить – любая, даже самая простая задача нуждается в тщательно проектировании. Иначе она будет работать через пень-колоду, как в моих примерах.

Проектирование это серьёзная работа, а вовсе не «изложить хотелку». Теперь Вы сами убедились, что нормальный проект всего-то для одной кнопки и двух лампочек у Вас уже со второго раза не получается :(

Никогда нельзя опускать стадию проектирования и программировать непосредственно хотелку. Получится, скорее всего, криво и потребует огромных усилий и очень высокой квалификации программиста. Хотелка – она ведь не проект, а «чувственная идея», а как говаривал граф Калиостро: «Материализация чувственных идей суть сложнейшая задача научной магии».

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Итак, вчера мы остановились на том, что осталось написать функции для управляющих сигналов и действий, а именно

bool isButtonPressed(void);
bool isTimerElapsed(void);
void startTimer(void);
void led1ON(void);
void led2ON(void);
void led1OFF(void);
void led2OFF(void);

Последние 4 функции тривиальны, один digitalWrite Вы, думаю, сумеете написать. Потому. остановимся на первых трёх.

Сначала о кнопке.

Делать её в нашем случае проще всего так:

1.  Заводим переменную, в которой будем всегда хранить текущее состояние кнопки (нажат или отпущена)

2. Заводим функцию, которую будем вызывать постоянно из loop. Эта функция один раз в 10 миллисекунд (например) узнаёт новое состояние кнопки. Если новое состояние, отличающееся от текущего (которое в переменной) повторилось два раза подряд, то считаем, что состояние кнопки изменилось. Тогда делаем следующее:

a. Изменяем переменную «текущее состояние» на новое.

b. Если новое значение «текущего состояния» - «нажата», то взводим флажок «кнопка нажата».

3. Наконец наша функция isButtonPressed просто возвращает этот флажок, попутно сбрасывая его (чтобы при следующем обращении не отработать  одно нажатие дважды)

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

Ну, а у нас получается как-то так:

typedef uint32_t TMillisType;	// тип для хранения времени от millis

///////////////////////////////////////////////////////////////////
//
// Собственно, работа с кнопкой
//
// Постоянно храним текущее состояние кнопки и каждые 10мс читаем новое.
// Если дважды подряд новое отличается от текущего, то изменяем текущее
// на новое.
// Если только что установленное текущее состояние - "нажата", взводим флаг,
// что "имеем нажатие"
// Если только что установленное текущее состояние - "отпущена", ничего не делаем,
// (т.е. не сбрасываем флаг "имеем нажатие")
// При опросе флага, возвращаем его текущее значение и тут же сбрасываем его,
// чтобы не обработать одно нажати дважды
// При таком подходе:
// 1) любое нажатие будет обработано, даже если кнопку успели отпустить до того,
//    как программа опросила флаг нажатия
// 2) никакое нажатие не будет обработано дважды 
//    (даже если нажать кнопку и не отпускать час)
// 3) дребезг до 10мс не страшен. Если надо больше, увеличиваем соотв. константу.
// 
enum BUTTON_STATES {
	BTN_PRESSED = LOW, // значение пина, когда нажата (если кнопка притянута к питанию, то LOW, а если к земле, то HIGH)
	BTN_RELEASED = HIGH // значение пина, когда отпущена (если кнопка притянута к питанию, то HIGH, а если к земле, то LOW)
};

static BUTTON_STATES currentButtonState = BTN_RELEASED;
static BUTTON_STATES stateCandidate = BTN_RELEASED;

static bool pressedFlag = false;

static TMillisType buttonCheckTime = 0;

static const TMillisType BUTTON_CHECK_INTERVAL = 10; // Интервал проверки кнопки (если кнопка раздолбана, то надо увеличить)

//
// Собственно раббота с физической кнпокой
// Эту функцию нужно всегда вызывать из loop()
//
static void runButton(const TMillisType currentMillis) {
	if (currentMillis - buttonCheckTime >= BUTTON_CHECK_INTERVAL) {	// Пора проверять
		buttonCheckTime = currentMillis; // на будущее
		const BUTTON_STATES newState = (BUTTON_STATES) digitalRead(BUTTON_PIN);
		if (newState == stateCandidate) {
				if (stateCandidate != currentButtonState) { // Состояние кнопки изменилось
					currentButtonState = newState;
					if (newState == BTN_PRESSED) pressedFlag = true; // причём изменилось на "нажата" 
				}
		} else stateCandidate = newState;
	}
}

inline bool isButtonPressed(void) {
	const bool res = pressedFlag;
	pressedFlag = false;
	return res;
}

Разбирайтесь.

Кстати, функцию isButtonPressed можно писать так нагло только потому, что мы не работаем с прерываниями. Если бы работали, то нам бы надо было иметь в виду, что переменная pressedFlag может имениться между строками 55 и 56 и это нам всё сломает.

typedef uint32_t TMillisType;	// тип для хранения времени от millis

///////////////////////////////////////////////////////////////////
//
// Одноразовый таймер
//
static TMillisType startMillis = 0;	// 0 - если таймер выключен и время запуска, если запущен
static bool timeoutElapsed = false;

static const TMillisType TIMER_TIMEOUT = 1000;	// 1 сек

//
// Собственно таймер.
// Эту функцию нужно всегда вызывать из loop()
//
static void runTimer(const TMillisType currentMillis) {
	if (! startMillis) return; // если таймер не запущен, то делать нечего
	if (currentMillis - startMillis >= TIMER_TIMEOUT) {	// Если время вышло
		startMillis = 0;	// выключаем таймер
		timeoutElapsed = true;	// взводим флаг, что таймер сработал
	}
}

//
//	завести таймер на TIMER_TIMEOUT миллисекунд
//
static inline void startTimer(void) {
	startMillis = millis();
	timeoutElapsed = false;
}

//
//	Вернуть флаг срабатывания таймера
//	Заодно сбросить этот флаг, чтобы второй раз не возвращать
//
inline bool isTimerElapsed(void) {
	const bool result = timeoutElapsed;
	timeoutElapsed = false;
	return result;
}

Ну, смотрите, если там непонятно чего. ...

Ну, вот, собственно и всё. Основную функцию, мы вчера писали. Дополнительные написали сейчас. Осталось правильно сконфигурировать пины в setup и всё время вызывать в loop три функции: runTimer, runButton и runAutomata. Больше ничего не нужно.

Могу собрать весь код в кучу:

typedef uint32_t TMillisType;	// тип для хранения времени от millis

#define	BUTTON_PIN	8

#define	LED_1_PIN	13
#define	LED_2_PIN	10


///////////////////////////////////////////////////////////////////
//
// Одноразовый таймер
//
static TMillisType startMillis = 0;	// 0 - если таймер выключен и время запуска, если запущен
static bool timeoutElapsed = false;

static const TMillisType TIMER_TIMEOUT = 1000;	// 1 сек

//
// Собственно таймер.
// Эту функцию нужно всегда вызывать из loop()
//
static void runTimer(const TMillisType currentMillis) {
	if (! startMillis) return; // если таймер не запущен, то делать нечего
	if (currentMillis - startMillis >= TIMER_TIMEOUT) {	// Если время вышло
		startMillis = 0;	// выключаем таймер
		timeoutElapsed = true;	// взводим флаг, что таймер сработал
	}
}

//
//	завести таймер на TIMER_TIMEOUT миллисекунд
//
static inline void startTimer(void) {
	startMillis = millis();
	timeoutElapsed = false;
}

//
//	Вернуть флаг срабатывания таймера
//	Заодно сбросить этот флаг, чтобы второй раз не возвращать
//
inline bool isTimerElapsed(void) {
	const bool result = timeoutElapsed;
	timeoutElapsed = false;
	return result;
}

//////////////////////////////

///////////////////////////////////////////////////////////////////
//
// Собственно, работа с кнопкой
//
// Постоянно храним текущее состояние кнопки и каждые 10мс читаем новое.
// Если дважды подряд новое отличается от текущего, то изменяем текущее
// на новое.
// Если только что установленное текущее состояние - "нажата", взводим флаг,
// что "имеем нажатие"
// Если только что установленное текущее состояние - "отпущена", ничего не делаем,
// (т.е. не сбрасываем флаг "имеем нажатие")
// При опросе флага, возвращаем его текущее значение и тут же сбрасываем его,
// чтобы не обработать одно нажати дважды
// При таком подходе:
// 1) любое нажатие будет обработано, даже если кнопку успели отпустить до того,
//    как программа опросила флаг нажатия
// 2) никакое нажатие не будет обработано дважды 
//    (даже если нажать кнопку и не отпускать час)
// 3) дребезг до 10мс не страшен. Если надо больше, увеличиваем соотв. константу.
// 
enum BUTTON_STATES {
	BTN_PRESSED = LOW, // значение пина, когда нажата (если кнопка притянута к питанию, то LOW, а если к земле, то HIGH)
	BTN_RELEASED = HIGH // значение пина, когда отпущена (если кнопка притянута к питанию, то HIGH, а если к земле, то LOW)
};

static BUTTON_STATES currentButtonState = BTN_RELEASED;
static BUTTON_STATES stateCandidate = BTN_RELEASED;

static bool pressedFlag = false;

static TMillisType buttonCheckTime = 0;

static const TMillisType BUTTON_CHECK_INTERVAL = 10; // Интервал проверки кнопки (если кнопка раздолбана, то надо увеличить)

//
// Собственно раббота с физической кнпокой
// Эту функцию нужно всегда вызывать из loop()
//
static void runButton(const TMillisType currentMillis) {
	if (currentMillis - buttonCheckTime >= BUTTON_CHECK_INTERVAL) {	// Пора проверять
		buttonCheckTime = currentMillis; // на будущее
		const BUTTON_STATES newState = (BUTTON_STATES) digitalRead(BUTTON_PIN);
		if (newState == stateCandidate) {
				if (stateCandidate != currentButtonState) { // Состояние кнопки изменилось
					currentButtonState = newState;
					if (newState == BTN_PRESSED) pressedFlag = true; // причём изменилось на "нажата" 
				}
		} else stateCandidate = newState;
	}
}

inline bool isButtonPressed(void) {
	const bool res = pressedFlag;
	pressedFlag = false;
	return res;
}



// включить Led1
inline void led1ON(void) {
	digitalWrite(LED_1_PIN, HIGH);
}

// включить Led2
inline void led2ON(void) {
	digitalWrite(LED_2_PIN, HIGH);
}

// выключить Led1
inline void led1OFF(void) {
	digitalWrite(LED_1_PIN, LOW);
}

// выключить Led2
inline void led2OFF(void) {
	digitalWrite(LED_2_PIN, LOW);
}


//
//	Все состояния
//
enum AUTOMATA_STATES {
	ON_FIRST_PRESS,	// Ожидаем нажатия кнопки включения (начальное состояние)
	ON_SECOND_PRESS,	// Ожидаем второго нажатия кнопки включения
	OFF_FIRST_PRESS,	// Ожидаем кнопки выключения
	OFF_SECOND_PRESS	// Ожидаем второго нажатия кнопки выключения
};
static const AUTOMATA_STATES InitialState = ON_FIRST_PRESS;

static void runAutomata(void) {
	static AUTOMATA_STATES currentState = InitialState;
	switch (currentState) {
		case ON_FIRST_PRESS:
			if (isButtonPressed()) {
				led1ON();
				startTimer();
				currentState = ON_SECOND_PRESS;
			}
			break;
		case ON_SECOND_PRESS:
			if (isButtonPressed()) {
				led2ON();
				currentState = OFF_FIRST_PRESS;
			}
			else if (isTimerElapsed()) {
				currentState = OFF_FIRST_PRESS;
			}
			break;
		case OFF_FIRST_PRESS:
			if (isButtonPressed()) {
				led1OFF();
				startTimer();
				currentState = OFF_SECOND_PRESS;
			}
			break;
		case OFF_SECOND_PRESS:
			if (isButtonPressed()) {
				led2OFF();
				currentState = ON_FIRST_PRESS;
			}
			else if (isTimerElapsed()) {
				currentState = ON_FIRST_PRESS;
			}
			break;
	}
}
void setup(void) {
	pinMode(BUTTON_PIN, INPUT_PULLUP);
	pinMode(LED_1_PIN, OUTPUT);
	pinMode(LED_2_PIN, OUTPUT);
}

void loop(void) {
	const uint32_t currentMillis = millis();
	runTimer(currentMillis);
	runButton(currentMillis);
	runAutomata();
}

теперь, если Вы поставите свои пины для кнопки и светодиодов в строках 3-6 и внимательно отнесётесь к строкам 71-72 (возможно, Вам надо поменять там наоборот), то всё по идее должно нормально заработать по логике той таблицы, что у нас была вчера.

Разбирайтесь, меняйте таблицу (я утром писал про ошибки) и делайте по уму сами.

Alexxmart
Offline
Зарегистрирован: 08.10.2017

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

Пример2 пункт 3 не был мною озвучен именно так, я считал что второе нажатие из второго состояния отключает все включенное этой кнопкой и для моего частного случая на кнопку S1 я бы навесил отключение всего освещения на этаже по второму нажатию.

Так что логика верна))) просто немного осталось за пределами таблицы

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Alexxmart пишет:

Пример2 пункт 3 не был мною озвучен именно так, я считал что второе нажатие из второго состояния отключает все включенное этой кнопкой 

Возможно, Вы так подумали, а написали в посте №9 буквально следующее

Alexxmart пишет:

        2.2.1 действие "нажатие2"(второе), выполнимо, если "таймер 1секунда от нажатия1" еще не вызвал  переход в период1"ВКЛ", вызывает выключение LED2-->отмену отсчета "таймер 1секунда"-->переход в период1 "ВКЛ".

Как видите про led2 есть, а про "всё" - нету :(

Но это не важно. Мы же занимаемся не поиском виновных, а разработкой. Думайте. если надо меняйте таблицу, работайте.

Alexxmart
Offline
Зарегистрирован: 08.10.2017

Комментарии сами по себе достойны уважения, а код тем более) Я и не ожидал такого участия в своем вопросе, посему- огромное человеческое спасибо, про конфигурацию пинов все понятно(из примера blink).

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

а inline зачем?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Да, тут скорее всего не надо, осталось от чего-то прежнего. inline имеет смысл, когда функция из единственного места вызывается, а здесь это не так. Так что, можно смело убирать.

ua6em
ua6em аватар
Онлайн
Зарегистрирован: 17.08.2016

73