суббота, 15 октября 2016 г.

Таймер обратного отсчета СЕКУНДА-2 на ARDUINO с памятью на 20 значений, от 1с до 99 часов. Для: УФ - сушилки, Фоторезиста, Шеллака, Печатей

Таймер обратного отсчета СЕКУНДА-2
На ARDUINO с памятью на 20 значений, от 1с до 99 часов.
Для: УФ - сушилки, Фоторезиста, Шеллака, Печатей 





Нажми что бы открыть СКЕТЧ:
#include <EEPROM.h>
#include <LiquidCrystal.h>

// пины ЖК индикатора, rs, enable, d4, d5, d6, d7
LiquidCrystal lcd(4, 5, 6, 7, 8, 9);

// пины энкодера. Для atmega 168/328 PIN_A должен быть на 2 или 3 пине (для работы прерывания)
#define ENC_PIN_A 2
#define ENC_PIN_B 3
#define ENC_PIN_BTN 12
// пин кнопки старт/стоп
#define START_PIN 11
// пин, управляющий реле нагрузки
#define RELAY 13
// пин, управляющий пищалкой
#define BEEPER 10

// номер прерывания для пина А энкодера. 
//#define ENC_PIN_A_INTERRUPT digitalPinToInterrupt(ENC_PIN_A)
// если строка выше не пашет, можно задать вручную. Для пина 2 - номер 0
#define ENC_PIN_A_INTERRUPT 0

// интервал мигания в миллисекундах
#define BLINK_MS 600

// длительность длинного нажатия в миллисекундах
#define LONG_PRESS_MS 1000

uint8_t h, m, s; // hours, minutes, seconds
uint8_t prg; // номер текущей программы (ячейки памяти)
unsigned long start_time; // время начала отсчёта текущей секунды
uint8_t working; // флаг работы, 1 - идёт отсчёт
uint8_t selection; // текущее выбраное для редактирования поле, одно из:
#define HOURS 1
#define MINS 2
#define SECS 3
#define PROG 4
#define LAST 4

// количество программ
#define MAX_PROG 20

// Частота пищания в Гц
#define BEEPER_FREQ 1000
// Длительность бипа в секундах
#define BEEP_LENGTH 1

// адреса сохранённых программ в EEPROM
#define EEPROM_PRG_H(_prg) (_prg*3+0)
#define EEPROM_PRG_M(_prg) (_prg*3+1)
#define EEPROM_PRG_S(_prg) (_prg*3+2)

// чтение ч, м, с текущей программы из EEPROM
#define PRG_H (EEPROM.read(EEPROM_PRG_H(prg)))
#define PRG_M (EEPROM.read(EEPROM_PRG_M(prg)))
#define PRG_S (EEPROM.read(EEPROM_PRG_S(prg)))

typedef struct Button
{
	uint8_t last_state; // прошлое состояние кнопки
	unsigned long last_change; // время последнего изменения состояния кнопки
	uint8_t press; // флаг нажатия на кнопку
	uint8_t long_press; // флаг длинного нажатия на кнопку
} Button;

typedef struct Encoder
{
	uint8_t last_state_A, last_state_B; // прошлое состояние входов
	int8_t pos; // относительное значение энкодера, без очистки от дребезга
	volatile int8_t value; // относительное значение энкодера, отфильтрованное
} Encoder;

Button btn_enc;
Button btn_start;
Encoder encoder;

bool ProgramValid()
// проверяет текущую программу на корректность. По умолчанию в EEPROMе лежат сплошные 0xFF (255).
{
	return (PRG_H<100) && (PRG_M<60) && (PRG_S<60);
}
	
void DisplayAll()
{
  uint8_t blink;
  
  blink=(millis() % BLINK_MS) < BLINK_MS/2; // Определяем, какая сейчас половина интервала мигания
	if (working) blink=1; // если работаем, то нефиг мигать
  
  //lcd.clear();
  lcd.setCursor(0, 0);
  if (blink || (selection!=HOURS))
  {
    lcd.write((h / 10)+'0');
    lcd.write((h % 10)+'0');
    lcd.write('h');
  } else lcd.print("   ");
  if (blink || (selection!=MINS))
  {
    lcd.write((m / 10)+'0');
    lcd.write((m % 10)+'0');
    lcd.write('m');
  } else lcd.print("   ");
  if (blink || (selection!=SECS))
  {
    lcd.write((s / 10)+'0');
    lcd.write((s % 10)+'0');
    lcd.write('s');
  } else lcd.print("   ");
  
  uint16_t sm, i;
  char buf[]="     0s"; // "  0000s"
  
  // далее отображаем время в секундах, для справки
  // меньше двух часов дублируем в секундах, больше - в минутах
  if (h>=2)
  {
    sm=(uint16_t)h*60+m; 
    buf[6]='m';
  }
  else 
  {
    sm=(uint16_t)h*3600 + m*60 + s;
    buf[6]='s';
  }
  i=5;
  do
  {
    buf[i--]=((sm % 10)+'0');
    sm=sm/10;
  } while (sm>0 && i>0);
  lcd.print(&buf[0]);
	
	// вторая строка
  lcd.setCursor(0, 1);
	
	// номер программы
	lcd.write('[');
  if (blink || (selection!=PROG))
  {
    lcd.write((prg / 10)+'0');
    lcd.write((prg % 10)+'0');
  } else lcd.print("  ");
  lcd.write(']');
  lcd.write(' ');
	
	// это для отладки энкодера
	//if (digitalRead(ENC_PIN_A)) lcd.write('A'); else lcd.write(' ');
	//if (digitalRead(ENC_PIN_B)) lcd.write('B'); else lcd.write(' ');

	// длительность текущей программы
	if (ProgramValid())
	{
		lcd.write((PRG_H / 10)+'0');
		lcd.write((PRG_H % 10)+'0');
		lcd.write('h');
		lcd.write((PRG_M / 10)+'0');
		lcd.write((PRG_M % 10)+'0');
		lcd.write('m');
		lcd.write((PRG_S / 10)+'0');
		lcd.write((PRG_S % 10)+'0');
		lcd.write('s');
	} else
		lcd.print("--h--m--s");
}

// третье состояние кнопки (первые два LOW и HIGH), означает длинное нажатие, на которое мы уже отреагировали
#define STATE_WAIT 3

void ProcessButton(struct Button& btn, uint8_t state)
{
	// проверяем, если кнопка всё ещё нажата, а длинное нажатие уже отработано, то ничего не делаем
	if (state==LOW && btn.last_state==STATE_WAIT) return;
	
	// проверяем, изменилось ли состояние кнопки
	if (btn.last_state != state)
	{ // изменилось
		// если кнопку отменили, а длинного нажатия ещё не было, значит ставим флаг простого клика
		if (state==HIGH && btn.last_state!=STATE_WAIT) btn.press=1;
		btn.last_change=millis();
		btn.last_state=state;
	}
	else
	{ // ничего не изменилось. Проверяем, не удерживают ли кнопку дольще определенного порога?
		if (state==LOW && btn.last_state!=STATE_WAIT && (millis()-btn.last_change) > LONG_PRESS_MS)
		{ // да, это длинный клик. Ставим флаг длинного клика и отмечаем что клик уже отработан в состоянии кнопки
			btn.long_press=1;
			btn.last_state=STATE_WAIT;
		}
	}			
}

void ProcessEncoder(struct Encoder& enc, uint8_t state_A, uint8_t state_B)
{
	if (enc.last_state_A == state_A /*&& enc.last_state_B==state_B*/) return; // ничего не изменилось, уходим
	
	if (state_A == state_B) enc.pos--; else enc.pos++;
	
	if (enc.pos>=2)
	{
		enc.pos=0;
		enc.value++;
	}
	if (enc.pos<=-2)
	{
		enc.pos=0;
		enc.value--;
	}
	
	enc.last_state_A=state_A;
	enc.last_state_B=state_B;
}

void CheckButtons()
{
	ProcessButton(btn_enc, digitalRead(ENC_PIN_BTN));
	ProcessButton(btn_start, digitalRead(START_PIN));
}

void EncoderRotated()
// обработчик прерывания от энкодера
{
	ProcessEncoder(encoder, digitalRead(ENC_PIN_A), digitalRead(ENC_PIN_B));
}

void SaveProg()
// сохраняет текущее время в текущую программу
{
	EEPROM.write(EEPROM_PRG_H(prg), h);
	EEPROM.write(EEPROM_PRG_M(prg), m);
	EEPROM.write(EEPROM_PRG_S(prg), s);
}

void LoadProgram()
// загружает текущую программу
{
  if (ProgramValid()) 
	{ 
		h=PRG_H; 
		m=PRG_M; 
		s=PRG_S; 
	}	
}

void PowerOn()
{
	digitalWrite(RELAY, HIGH);
}

void PowerOff()
{
	digitalWrite(RELAY, LOW);
}

void Beeeeeeeep()
{
	// мерзко бибипкнуть подольше
	int i;
	
	for (i=0; i<BEEPER_FREQ*BEEP_LENGTH; i++)
	{
		digitalWrite(BEEPER, HIGH);
		delayMicroseconds(500000/BEEPER_FREQ);
		digitalWrite(BEEPER, LOW);
		delayMicroseconds(500000/BEEPER_FREQ);
	}
}

void Beep()
{
	// короткий бип
	int i;
	
	for (i=0; i<BEEPER_FREQ/20; i++)
	{
		digitalWrite(BEEPER, HIGH);
		delayMicroseconds(500000/BEEPER_FREQ);
		digitalWrite(BEEPER, LOW);
		delayMicroseconds(500000/BEEPER_FREQ);
	}
}

void Start()
// запускаем отсчёт, включаем нагрузку
{
	if (h==0 && m==0 && s==0) return; // если отсчитывать нечего, тогда и не запускаемся
	working=1;
	Beep();
	start_time=millis(); // запоминаем время начала отсчёта
	PowerOn();
}

void Stop()
// останавливаем отсчёт, выключаем нагрузку
{
	working=0;
	PowerOff();
	// если дошли до нуля (счёт окончен, а не просто пауза кнопкой), то перезапускаем таймер
	if (h==0 && m==0 && s==0)
	{
		Beeeeeeeep();
		LoadProgram();
	} else Beep();
}

void setup()
{
	lcd.begin(16, 2);
  selection=PROG;
  h=0;
  m=0;
  s=0;
	working=0;
	// устанавливаем пины от кнопок в режим входов
	pinMode(ENC_PIN_A, INPUT);
	pinMode(ENC_PIN_B, INPUT);
	pinMode(ENC_PIN_BTN, INPUT);
	pinMode(START_PIN, INPUT);
	// включаем подтяжку к питанию
	digitalWrite(ENC_PIN_A, HIGH);
	digitalWrite(ENC_PIN_B, HIGH);
	digitalWrite(ENC_PIN_BTN, HIGH);
	digitalWrite(START_PIN, HIGH);

	// пин управления реле нагрузки
	PowerOff();
	pinMode(RELAY, OUTPUT);

	// пин управления пищалкой
	pinMode(BEEPER, OUTPUT);
	digitalWrite(BEEPER, LOW);
	
  // начальное состояние кнопок - не нажаты
	btn_enc.last_state=HIGH;
	btn_start.last_state=HIGH;
	encoder.last_state_A=HIGH;
	encoder.last_state_B=HIGH;
	
	LoadProgram();
	
	// вешаем прерывание на пин А энкодера. Обработчик - EncoderRotated()
	attachInterrupt(ENC_PIN_A_INTERRUPT, EncoderRotated, CHANGE);
}

void loop()
{
	if (working && (millis()-start_time >= 1000))
	{ // прошло больше секунды, уменьшаем
		// сдвигаем время начала на секунду вперёд, а время уменьшаем на секунду
		start_time=start_time+1000;
		if (s>0) s--;
		else
		{
			s=59;
			if (m>0) m--;
			else
			{
				m=59;
				if (h>0) h--;
				else h=m=s=0;
			}
		}
	}

	DisplayAll();

	if (working && h==0 && m==0 && s==0) Stop(); // дошли до нуля
	
	CheckButtons();
	
	// кнопка старт/стоп
	if (btn_start.press)
	{
		if (working) Stop(); else Start();
		btn_start.press=0;
	}
	if (btn_start.long_press)
	{
		// в режиме паузы перезагружаем время из программы
		if (!working)
		{
			LoadProgram();
			Beep();
		}
		btn_start.long_press=0;
	}
	
	// кнопка энкодера
	if (btn_enc.press && !working)
	{
		selection++;
		if (selection>LAST) selection=1;
	}
	btn_enc.press=0;
	if (btn_enc.long_press && !working)
	{
		if (selection==PROG) { h=m=s=0; /*SaveProg();*/ }
		if (selection==HOURS || selection==MINS || selection==SECS) { SaveProg(); }
		Beep();
	}
	btn_enc.long_press=0;
	
	// энкодер
	if (encoder.value != 0 && !working)
	{
		if (selection==HOURS) h=(h+100+encoder.value) % 100;
		if (selection== MINS) m=(m+ 60+encoder.value) %  60;
		if (selection== SECS) s=(s+ 60+encoder.value) %  60;
		if (selection== PROG) 
		{
			prg=(prg+MAX_PROG+encoder.value) %  MAX_PROG;
			LoadProgram();
		}
	}
	encoder.value=0;
  delay(10);
}



30 комментариев :

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

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. А архив чем не устраивает? https://yadi.sk/d/yP6Qu2_MwuQgE

      Удалить
    2. Я сколько не пробовал не открывает ссылку https://yadi.sk/d/yP6Qu2_MwuQgE

      Удалить
    3. Я живу в Ирландии и может провайдеры чего мутят, но по ссылке страница тупо не открывается

      Удалить
    4. Так всё получилось, большое спасибо.

      Удалить
  2. Здравствуйте. Как добавить сенсор температуры и вывести даные на экран?. Спасибо.

    ОтветитьУдалить
    Ответы
    1. Здравствуйте. Без понятия как добавить, программисту ставилась задача написать только таймер.

      Удалить
  3. Здравствуйте. А какое реле для управления нагрузки должно стоять, низкого уровня или высокого? Спасибо.

    ОтветитьУдалить
    Ответы
    1. Обычное реле.
      Загуглите "Подключение нагрузки к Arduino" или "управление нагрузкой Arduino".
      P.S. Нагрузку всегда подключать через транзистор, что бы не спалить порт Arduino

      Удалить
  4. Печатка с секунды 1 подходит к секунде 2?

    ОтветитьУдалить
    Ответы
    1. Что за печатка?
      Если имеется в виду печатная плата, так ее нет в архивах обоих проектов, просто не разводил её.

      Удалить
  5. Здравствуйте. Подключил все по схеме, но не заработало. Я исользовал энкодер-модуль (KY-040), может по этому не заработал потому что надо только энкодер не как модуль. И с бузером не понятно, у меня модуль, так что надо еще одно сопротивление подключать как на схеме? Помогите разобраться. Зарание спасибо.

    ОтветитьУдалить
  6. P.S. Экран не выдает значений, просто светится и на нем ничего не показывает, не пойму где причина.

    ОтветитьУдалить
    Ответы
    1. Добрый.
      При условии, что все собрано верно и исправно, но нет изображения, тогда крутите продстроечный/переменный резистор R2 (контраст изображения) до тех пор пока не появиться изображение на экране.

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

      2. Тогда собираем опять схему таймера но без энкодера и пищалки и проверяем не забыв покрутить резистор R2 отрегулировав контраст дисплея тем самым.
      -------------------------------------------------------------------
      Допустим что все работает и изображение на дисплее присутствует.
      -------------------------------------------------------------------

      3. Подключаем энкодер.
      Тут возможны два варианта:
      а) Если энкодер как у меня (не модуль), а просто как радиодеталь, тогда подключаем его как в схеме, в этом варианты подтягивающие резисторы используются встроенные в саму Ардуино и соответственно активированы в скетче.

      б) Если энкодер в виде готового модуля, то тут наверное нужно повозиться.
      У данного модуля подтягивающие резисторы как я понимаю присутствуют физически на самой плате модуля, о чем свидетельствует наличие контактов питания (+) и (GND) соответственно.
      По моему разумения для этого модуля надобно отключить в скетче программные подтягивающие ризисторы, я не сильно разбираюсь в скетчах, но судя по описанию данного скетча там есть прям кусок кода отвечающий за это, я бы его выключил.
      Выключить можно поставив перед строчкой двойной слеш "//"
      Находим в нижней части скетча кусок кода:
      -----------------------------------------
      // включаем подтяжку к питанию
      digitalWrite(ENC_PIN_A, HIGH);
      digitalWrite(ENC_PIN_B, HIGH);
      digitalWrite(ENC_PIN_BTN, HIGH);
      digitalWrite(START_PIN, HIGH);
      -----------------------------------------
      И добавляем двойной слеш в строки для пинов энкодера, тем самым выключив встроенные резисторы подтяжки, за исключением резистора для кнопки энкодера, судя по даташиту модуля KY-040 на там нет физического резистора на кнопке, только на крутилке:
      -----------------------------------------
      // включаем подтяжку к питанию
      //digitalWrite(ENC_PIN_A, HIGH);
      //digitalWrite(ENC_PIN_B, HIGH);
      digitalWrite(ENC_PIN_BTN, HIGH);
      digitalWrite(START_PIN, HIGH);
      -----------------------------------------

      Ну и не забываем тогда подать питание на контакт энкодера (+).
      http://i91.fastpic.ru/big/2017/0215/04/34da23b27810888cc36d1b317ef02904.jpg

      (P.S. ВКЛЮЧЕННЫЕ в скетче цифровые пины НЕ ДОЛЖНЫ "висеть в воздухе", на них ВСЕГДА должны быть подтягивающие резисторы физические или встроенные.!!!)

      -------------------------------------------------------------------
      Допустим что энкодер работает.
      -------------------------------------------------------------------

      4.Подключаем пищалку.
      (В данной схеме применяется пищалка БЕЗ ВСТРОЕННОГО ГЕНЕРАТОРА!)
      а) Если пищалка в виде модуля, то на нем как правило уже есть транзисторный ключ (R1 и Т1), тогда просто подключаем сигнальный вход на pin D10, контакт VCC к питанию (+5V), контакт GND соответственно к земле.

      б) Если пищалка просто как радиодеталь, тогда желательно подключить ее через транзисторный ключ R1 / T1, да бы не перегружать цифровой порт Ардуино который максимум может выдержать 20мА нагрузки, хотя многие подключают и без транзисторного ключа, но я не любитель этого.

      Удалить
    2. Спасибо огромное за столь разшириный ответ, буду пробовать по пунктам искать проблему.

      Удалить
  7. Здравствуйте. Все получилось по вашей методике. Энкодер подключил модульный, сделав правку в скетче, а вот з пищалкой вышло интересно: подключив модуль пищалки соответственно маркировке, экран начал затухать и пищалка не работала, посмотрев на плату модуля и сравнив со схемой оказалось что надо соединять плюс на минус, и минус на плюс и таким образом все совпадало со схемой, и заработало. А вот с реле нагрузки не получилось. Использовал модуль реле для ардуино "низкого уровня", как было указано продавцом. Реле сразу при подключение замкнуло контакты, и задав время отчета, то при отчоте времени реле размыкалось, но както оно погружало ардуино, экран при этом менял яркость и затухал. Вот как то так. Решить бы мне проблему с подключением реле. Ну спасибо за помощь. Буду рад если поможете с реле.

    ОтветитьУдалить
    Ответы
    1. 1. Не понял по поводу пищалки, китайцы перепутали маркировку на плате модуля что ли?

      2. Экран затухает, это скорее всего значит что не хватает мощности блока питания.
      P.S. От Ардуино исполнительные устройства желательно не питать, а питать отдельно напрямую от БП, если напряжение соответствует и мощности достаточно или например сделать отдельный стабилизатор стабилизатор на кренке в корпусе ТО220 для лучшего охлаждения.

      3. В конкретно данном скетче на контакте управления реле (D13) в режиме ожидания таймера находиться (0) или (-), то есть "низкий уровень", когда таймер стартует (начинает отсчет) на контакт (D13) подается (1) или (+), то есть "высокий уровень".
      Из всего выше написанного следует, что для данного скетча требуется реле "высокого уровня", которое включается при подачи на его сигнальный контакт (+).
      На плате реле высокого уровня отсутствует чип оптической развязки (оптопара), он выглядит как микросхема с 4 ножками. У Вас на плате модуля реле скорее всего есть оптопара, значит реле у вас низкого уровня, если так то возможно несколько вариантов:

      Вариант 1. (Самый правильный)
      Инвертировать в скетче сигнал на pin D13 с 1 на 0
      Тогда Ваше реле заработает без проблем. Проблема в том что я не программист и фиг знает как это сделать, думаю не сложно, просто надо почитать про это дело или посмотреть видеоролики с примерами скетчей подключения модулей реле низкого уровня.

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

      Удалить
  8. По поводу пищалки: на схеме таймера с плюса пищалки контакт идет на плюс на прямую, а у моей пищалки-модуля, если посмотреть на плату, то видно что плюс пищалки идет через транзистор на контакт vcc, тоесть как на схеме таймера идет минус, и после транзистора через сопротивление на контакт сигнала.VCC я подключал на плюс а gnd на минус, логично, но так не работало, поменяв наоборот, и заработало.

    Вопрос: сопротивление R3 на схеме обязательно?

    По поводу реле, у меня реле без оптопары, на нем пишет "Low Level Trigger". Я не могу найти где в скетче прописать вместо 1, ноль.

    ОтветитьУдалить
    Ответы
    1. 1) R3 - желательно, дабы не спалить подсветку дисплея.

      2) Если на модуле реле нет оптопары, то по идее это реле высокого уровня, должно управляться (+) как по схеме.
      Если на выходе модуля стоит колодка из 3х контактов, попробуйте подключиться к другому крайнему контакту модуля реле.

      Удалить
  9. а все таки где заменить в скетче один на ноль?, не могу найти.

    ОтветитьУдалить
    Ответы
    1. 20 минут погуглить.....
      -------------
      Было:
      -------------

      void PowerOn()
      {
      digitalWrite(RELAY, HIGH);
      }

      void PowerOff()
      {
      digitalWrite(RELAY, LOW);
      }


      ----------------------
      Стало:
      ----------------------

      void PowerOn()
      {
      digitalWrite(RELAY, LOW);
      }

      void PowerOff()
      {
      digitalWrite(RELAY, HIGH);
      }
      -----------------------------

      Удалить
    2. Кстати с изменением управления реле с (1) на (0), встает другая проблема, пока Ардуина при включение не загрузиться, на ее портах находиться (0), соответственно до момента загрузки Ардуино, реле будет включено.

      Удалить
  10. Да действительно проблема...., я так понял что проще всего ее решить, это поставить реле высокого уровня?
    А подойдет ли твердотельное реле?, к примеру это:

    https://ru.aliexpress.com/item/5V-DC-1-Channel-Solid-State-Relay-Board-module-High-Level-fuse-for-arduino/32419488549.html?spm=2114.14010208.99999999.268.V0raNr

    ОтветитьУдалить
    Ответы
    1. Если оно "высокого уровня" то да, подойдет конечно, это самый простой и правильный вариант.
      P.S. Только не забыть использовать первоначальный скетч под реле высокого уровня.

      Удалить
  11. Отличный таймер. Спасибо! )

    ОтветитьУдалить
  12. Привет, возможно собрать такой же таймер. Только вместо энкодера будут кнопки управления. Будет 5 кнопок, установка часы, мин, стоп, старт и сброс?

    ОтветитьУдалить
    Ответы
    1. https://lehih80.blogspot.com/2016/03/arduino-10.html

      Удалить
    2. Спасибо, но не то. в этой версии можно установить только секунды. А мне нужно: кнопка 1-установка часов. кнопка 2- установка минут. кнопка 3 старт. кнопка 4 сброс и кнопка 5 стоп.
      если это возможно, то могу заплатить за данный скетч и flpro.

      Удалить
    3. К сожалению разработками не занимаюсь.

      Удалить