[sape_tizer]
Ср. Май 31st, 2023

Что и как строить на земельном участке или обо всем понемногу

Все о инструментах, строительстве, работах, АРДУИНО, электрика, электроника и многое другое

Урок 7.4- Пишем скетч на часы (ч.1)

Ну наконец дошли до собственно написания скетча. Модуль RTC DS1307 программно разбирать не стал, опишу методику работы с ним в процессе написания скетча. В данном уроке мы напишем программы подключения LCD 16х2 и подключение кнопок к Ардуино, сохранение этих данных в ЕЕПРОМ Ардуино и проконтроллируем контрольные значения. Приступаем.

Прежде всего скажу что я поменял интерфейс разработчика с 1.8.5 на 1.6.8 т.к. новая версия вышла какая то глючная, выдает кучу ошибок при компилировании. Поэтому поменял на более старую и надежную версию. Сразу скажу что ошибся в применяемом мной RTC. Думал у меня 1302 а оказалось 1307. Ничего страшного. Качаем эту библиотЭку и устанавливаем ее. Она является универсальной для 3 видов RTC- 1302,1307 и 3231. Затем, дабы не терять время, собираем любую из описанных выше схем по управлению на кнопках. Я собрал по первой схеме, в данном случае совершенно неважно по какой схеме вы соберете, учитывайте лишь данные рекомендации. Ну а теперь- точно начинаем.

Открываем оболочку АРДУИНО и приступаем к программированию. Если у вас отобразился не новый файл то создаем новый Файл-Новый. После этого сохраните его в какую нибудь понятную вам папку Файл-Сохранить. Обзовите его как нибудь, чтоб вам было самим понятно. Вот теперь начинаем накидывать код. Прежде всего давайте определимся какие устройства мы будем использовать и, соответственно, какие библиотеки будем подключать. Прежде всего нам нужно видеть что-то, значит подключаем библиотеку LCD для прямого подключения или для подключения по I2C. В моем случае я использую LCD с модулем I2C и поэтому я делаю следующее: Скетч- Подключить библиотеку- LiquidCrystal_I2C. Вверху страницы появится #include <LiquidCrystal_I2C.h>, означающее что вы подключили к проекту выбранную библиотеку. Таким же образом подключаем RTC и EEPROM. Обратите внимание что при подключении RTC вам надо оставить тот модуль который вы будете использовать. Т.е. в моем случае при использовании RTC DS1307 осталась только строка #include <iarduino_RTC_DS1307.h> без двойного слэша // впереди. Теперь все необходимые библиотеки мы подключили, сохраняем проект, объявляем дисплей LiquidCrystal_I2C lcd(0x27, 16, 2); // устанавливаем адрес LCD 0x27 для 16 х 2  и переходим к секции setup. Как известно, в данной секции происходит инициализация Ардуино и написанный в данной секции код выполняется один раз при загрузке контроллера. Давайте подумаем логически что нам надо вызвать только один раз. Нам надо в данной секции присвоить значения нашим кнопках (их у нас 3 шт) и если эти значения отсутствуют то нужно определить и опять же присвоить полученные данные к переменным. Какой тип данных должен быть у переменных отвечающих за значения кнопок? Попробуйте ответить самостоятельно. О типах данных можно почитать здесь.  Давайте опять думать. Будем исходить из значений порта А0. Порт 10 битный, 1024 значения. Значит тип byte (256 значений или 1 байт) так же как и тип unsigned char (тот же byte) нам не подходят т.к. слишком маленькие значения они принимают. Можно конечно применить и их но потом придется каждый раз преобразовывать в 10 битный формат или вводить еще одну переменную. Берем следующий тип данных- integer (32768 значения).  Этот тип данных вмещает в 32768/1024=32 раза больше информации и он нам подходит. Поэтому все 3 кнопки объявляем следующим образом перед секцией setup:

int iButMinus = 0; // значение при нажатии кнопки —
int iButMenu = 0; // значение при нажатии кнопки меню
int iButPlus = 0; // значение при нажатии кнопки +

Теперь переменные iButMinus, iButMenu, iButPlus  имеют  тип integer и все имеют значение 0. Почему я так странно назвал переменные кнопок? Возьмем например iButPlus. i-тип данных (integer), But- button (кнопка с английского), Plus-плюс с того же английского. Если говорить по аналогии то получается что эта переменная принадлежит кнопке плюс и имеет тип integer. Заметьте что But и Minus я написал с большой буквы- так быстрее находятся нужные вхождения в названиях переменных. Сразу видно какая переменная и за что отвечает. Вы можете назвать совершенно произвольно эти переменные но вам стоит избегать зарезервированных слов в оболочке Ардуино и помнить для чего нужны объявленные переменные. Например вы можете записать iButPlus как knopkaplus или plus, это совершенно неважно но вы должны помнить о всем что написано выше. Следующий момент- кнопки не всегда будут иметь одинаковые значения при нажатии. Т.е., предположим, кнопка iButPlus при первом нажатии имеет значение 347, при втором- 340, при третьем-362 и т.д. Спросите меня почему так происходит? Отвечаю. Во первых контроллер работает на диких скоростях, порядка 1 миллиона операций в секунду и попасть в 1 миллионную секунды с надежным нажатием вы просто физически не сможете. Как только контакт хоть чуть- чуть соприкасается с другим контактом то сразу же начинает течь ток который и улавливается контроллером. Можно конечно описать функцией что при первом появлении хоть какого то сигнала ждать 50 миллисекунд (подобрать экспериментально) и повторно снять показания… Какие еще факторы влияют на такие разные данные. По порядку: старые кнопки (читай окисленные контакты), б/у кнопки (контакты подгорели, протерлись), сила нажатия (чем сильнее тем надежнее контакт но и быстрее износ кнопок) и т.д.  Как же с этим бороться. Один способ я уже описал чуть выше, но можно сделать немного проще- ввести отклонение от нормального измеренного значения и при попадании значения нажатой кнопки в данный предел считать что кнопка нажата! Поэтому после  (или до) объявленных переменных дописываем #define bDelta 20 // отклонение при нажатии кнопок. Что означает такое написание переменной. b- тип данных byte, Delta читается как отклонение в данном случае и оно равняется 20. b можно и не писать, это не является определением типа данных а просто подсказка для меня. Можно было присвоить совершенно любое значение, и обозвать переменную как вам угодно, это не является критичным. Данная запись указывает Ардуино что везде где в коде находится указанная переменная при компиляции записывается не переменная а ее значение и таким образом экономится занимаемое программой место. Например написание bDelta займет 6 байт (по количеству букв) а 20- только 1.  Объясню подробнее принцип будущей работы кнопок.  Например, как написано выше, значение кнопки iButPlus -347, значит ее предел срабатывания будет находится в диапазоне от iButPlus —  bDelta (347-20)=327 до iButPlus +  bDelta (347+20)=367, и если, например, при нажатии на кнопку появится значение 330- это является признаком что нажата кнопка iButPlus, а что будет если значение например 320? Да ничего не будет, данный опрос пролетит мимо данной обработки и не затронет другие. Т.к. скорость работы контроллера дикая то за одно ваше нажатие происходит опрос кнопки несколько сотен а то и тысяч раз и при достижении  вхождения в указанный предел все равно произойдет срабатывание данной кнопки и, соответственно, условия. Еще раз повторюсь что значение отклонения должно не задевать границы соседних кнопок иначе может произойти ложное срабатывание. Хорошо. С этим надеюсь все понятно? Переходим далее к программированию. Как было написано в предыдущем уроке, мы можем присвоить значения несколькими способами. Я решил использовать способ №2 т.к. он является более простым и  понятным. Повторюсь по этому способу. Мы получаем значение нажатой кнопки с порта А0 (максимально 1024 значения) и записываем полученное в ЕЕПРОМ (максимально 256) предварительно разделив пришедшее значение на 4. При последующем присваивании переменным  iButMinus, iButMenu, iButPlus значении мы наоборот умножим считанные из ЕЕПРОМ  данные на 4. Они могут отличаться от первоначально полученных но все равно будут входить в предел благодаря использованию отклонения bDelta. Объясню еще раз на примере.Значение кнопки iButPlus -347, т.е. мы не можем записать эти данные напрямую в ЕЕПРОМ т.к. максимальное значение ячейки ЕЕПРООМ- 256. Поэтому 347/4=93,5. Ардуино округлит значение до 93 и запишет в указанную ячейку (теперь значение 93< 256). Следующий момент- считываем данные из ячейки- 93 и производим нехитрое действие iButPlus =93 * 4=372. Вроде больше всего на единицу но благодаря переменной  bDelta мы практически полностью вписываемся в указанный предел!

Давайте еще сразу сделаем красивую загрузку!

Пишем в секции setup:

с модулем I2C без модуля I2C
lcd.init(); // инициализируем lcd
lcd.backlight(); // включаем подсветку
// экран загрузки
lcd.setCursor(0, 0);
lcd.print(«www.samosdel.ru»);
lcd.setCursor(3, 1);
lcd.print(«Loading»);
delay(600);
byte count = 0;
while (count != 3)
{
lcd.print(«.»);
delay(600);
count++;
}
lcd.clear();
lcd.begin(16, 2);// инициализируем lcd
// экран загрузки
lcd.setCursor(0, 0);
lcd.print(«www.samosdel.ru»);
lcd.setCursor(3, 1);
lcd.print(«Loading»);
delay(600);
byte count = 0;
while (count != 3)
{
lcd.print(«.»);
delay(600);
count++;
}
lcd.clear();

Данный код выведет экран загрузки. Просьба не убирать его чтоб люди видели с какого сайта взят данный код. Своего рода реклама. Объясню по порядку принцип работы. С LCD все понятно (повторюсь что первые 2 строки относятся к LCD с модулем I2C, без I2C  нужно подключить библиотеку LCD, объявить LCD LiquidCrystal lcd(12, 11, 5, 4, 3, 2);  после подключенных библиотек вместо LiquidCrystal_I2C lcd(0x27, 16, 2); // устанавливаем адрес LCD 0x27 для 16 х 2 ). Затем в верхней строке высветится «www.samosdel.ru«, в нижней посредине «Loading» и три появляющиеся после слова точки. Команда setCursor устанавливает курсор в указанную позицию (0,0). Запомните что первое число- позиция по горизонтали, т.е. отступ слева на указанное количество знаков. Начинается с нуля. Второе число- номер строки. Тоже начинается с нуля. Print— печатает то что указано в скобках. Можно выводить значения переменных но об этом чуть ниже. Затем объявляется переменная count типа byte и равна она 0. В цикле While указывается что пока переменная count не равна 3 (количеству точек после слова «Loading«) то продолжаем выполнение цикла до закрывающей цикловой скобки. В самом цикле печатаем точку, делаем небольшую задержку в 600 миллисекунд delay(600); прибавляем к переменной count  единицу оператором count++; что равносильно записи count=count+1; и снова повторяем цикл пока не достигнем значения count = 3. И только после этого выходим из цикла и очищаем экран оператором lcd.clear(); Уже сейчас можно залить скетч в Ардуино и посмотреть заставку!

1602 и ARDUINO
Подключение ЖК индикатора 1602 к ARDUINO напрямую

Сохраняемся. Подключаем дисплей и кнопки. Теперь пришло время присваивания значений кнопкам.

Подключение LCD 16х2 с модулем I2C
Подключение LCD 16х2 с модулем I2C

Давайте условимся что кнопки будут стоять по порядку. Слева- минус, центр- меню, справа- плюс. Вы можете расположить как вам будет угодно лишь бы потом не запутались. Давайте снова подумаем. Т.к. мы будем хранить значения кнопок в ЕЕПРОМ то давайте условимся что они будут храниться с 1 по 3 ячейку включительно: минус- 1 ячейка, меню- 2, плюс- 3. Теперь нам нужно проверить что находится в этих ячейках. Добавляем после экрана загрузки в секцию setup.

// проверяем ячейки памяти на наличие данных о кнопках, 1-минус, 2- меню, 3- плюс

if (EEPROM.read(1) == 0 || EEPROM.read(2) == 0 || EEPROM.read(3) == 0) //если в памяти в 3 ячейках ничего не записано то записываем данные кнопок
{
checkButtons(); //записываем значения в епром
}
iButMinus = EEPROM.read(1) * 4;
iButMenu = EEPROM.read(2) * 4;
iButPlus = EEPROM.read(3) * 4;
lcd.clear();

Разберем что написано. Проверяем все 3 ячейки на наличие в них нуля. Знак || указывает на логическую операцию ИЛИ, т.е. если хотя бы в одной из трех ячеек находится ноль то мы должны перейти к функции присваивания и эта функция у меня выведена отдельно и называется checkButtons().  Можно конечно все описать здесь но в итоге код получается слишком длинный и неудобный для чтения. Если же все 3 ячейки имеют значения отличные от 0 то считываем эти ячейки поочередно, умножаем на 4 (что я описывал выше) и присваиваем соответствующим переменным.  Делаем новую вкладку как написано в уроке раньше, обзываем её тоже как и функцию- checkButtons чтобы было легче ориентироваться. Её код:

void checkButtons() // функция проверки нажатия кнопок
{
while (ButOut != 0) //цикл чтобы не выходить из меню программно
{
lcd.clear();

switch (ButOut)
{
case 1:
//выполняется, когда var равно 1
lcd.setCursor(2, 0);
lcd.print(«Press MINUS:»);
writeButtons(1);
break;
case 2:
//выполняется когда var равно 2
lcd.setCursor(2, 0);
lcd.print(«Press MENU:»);
writeButtons(2);
break;
case 3:
//выполняется когда var равно 3
lcd.setCursor(2, 0);
lcd.print(«Press PLUS:»);
writeButtons(3);
break;
}
}
}

В данной функции используется еще одна переменная ButOut и по условию изначально она не должна равняться 0. Поэтому в основной вкладке в секцию с переменными вносим: int ButOut = 1; // для выхода из меню. Сразу присваиваем переменной значение отличное от нуля. Об этой переменной еще поговорим но чуть позже. Загоняем функцию в бесконечный цикл while (ButOut != 0) до того момента пока переменная ButOut не будет равна нулю. Очищаем экран lcd.clear(); Подходим к оператору switch (ButOut)  который начинает выбирать секции case по указанным значениям ButOut. Немного запутано, объясню проще. При объявлении ButOut мы присвоили ей значение 1. Теперь выбираем оператором case значение 1 и выполняем все что написано в этой секции до оператора break;. Все три секции практически идентичны и просто выводят приглашения нажать соответствующие кнопки + каждая запускает функцию writeButtons() с аргументом указанном в скобках. Данная функция собственно и будет опрашивать кнопки и производить запись в ЕЕПРОМ. Поэтому снова делаем новую вкладку, обзываем её writeButtons и вставляем следующий код:

void writeButtons(int n) // функция записи данных в епром
{
delay(40);
if (analogRead(A0) >= 10) // пока никакая кнопка не нажата продолжаем опрос порта А2
{
EEPROM.write(n, analogRead(A0) / 4);
if (n == 3)
{
ButOut = 0;
}
else
{
ButOut++;
}
lcd.setCursor(6, 1);
lcd.print(«OK»);
delay(2000);
}
}

Разберем и этот код. Как видно аргумент функции имеет тип integer хотя было бы достаточно и byte. Исправьте если вам нужно. Это мой косяк (подумайте почему можно присвоить тип byte). Давайте разберем и её. Итак, сначала делаем небольшую задержу в 40 миллисекунд delay(40); чтобы снизить мерцание LCD при такой высокой скорости. Затем считываем данные с порта А0, который притянут к земляной шине через резистор 1кОм во избежание наводок (об этом тоже будет чуть ниже) и, если значение больше 10, т.е. считаем что нажата какая то кнопка, переходим к оператору записи в ЕЕПРОМ который и записывает в ячейку указанную аргументом функции n значение порта А0 деленное на 4 дабы втиснуть 1024 в 256. Надеюсь понятно. Далее стоит условие на аргумент функции которое говорит что при n=3 признак выхода из функции ButOut должен быть равном 0, иначе просто прибавляем к ButOut единицу. Не путайте оператор присваивания = (равно) с оператором сравнивания == (двойное равно). В первом случае вы просто присвоите значение переменной а во второй будете сравнивать переменную со значением. Затем во второй строке посредине пишем OK, делаем задержку в 2 секунды, чтобы было видно что данные занесены в память и возвращаемся в функцию checkButtons  но уже ButOut будет больше на единицу, т.е. переходим к следующему оператору case. И так до тех пор пока функция writeButtons не присвоит переменной ButOut ноль, функция checkButtons прекратит работу по условию цикла while (ButOut != 0) и выйдет в основную программу. Можно конечно в функцию checkButtons  вписать напрямую функцию writeButtons, но её придется написать 3 раза и это сразу скажется на размере программы.

В секции setup наконец считываем данные из ЕЕПРОМ и, соответственно преобразовав, присваиваем их нужным переменным. На этом подготовка данных закончена. Переходим к основной секции loop.

Теперь давайте подумаем о ситуации выше, где я говорил что возможно перепутаете кнопки, номиналы сопротивлений или просто решите поменять назначение и порядок кнопок. Для этой операции в секцию loop вставьте следующий код:

if (analogRead(A1) >= 10) // сброс памяти перемычкой или кнопкой
{
lcd.clear();
for (int i = 0; i <= 1023; i++)
{
EEPROM.write(i, 0);
}
lcd.setCursor(1, 0);
lcd.print(«Reset Complete»);
delay(3000);
resetFunc(); //вызываем reset
}

Как известно в Ардуино УНО ЕЕПРОМ имеет 1024 ячейки по 256 бит каждая начиная с 0 по 1023. Поэтому данный код обнуляет ВЕСЬ ЕЕПРОМ если на входе А1 сигнал выше 10. Т.е., всего вероятнее, если  вы оставите вход не притянутым к земле через резистор 1кОм, то Ардуино из-за наводок будет очищать память и перезагружать Ардуино. Об этом я писал выше на этой странице дабы вы смогли увидеть и почувствовать объемы наводок. Чтобы предотвратить стирание достаточно замкнуть вход А1 на GND Ардуино.  Здесь вы встретите функцию resetFunc() которая перезагружает Ардуино и таким образом опять переходит в функцию присваивания кнопок. Функцию resetFunc() следует объявить, лучше всего прямо перед секцией setup:

void(* resetFunc) (void) = 0; // объявляем функцию reset.

Кроме того вы можете уменьшить количество стираемых ячеек чтобы уменьшить время  работы Ардуино.

Данные ЕЕПРОМ и кнопок в моем варианте
Данные ЕЕПРОМ и кнопок в моем варианте

Ну и наконец давайте хоть выведем на дисплей значения ячеек ЕЕПРОМ и реальные значения кнопок управления. Пишем ниже в секции loop:

lcd.setCursor(0, 0);
lcd.print(EEPROM.read(1));
lcd.setCursor(6, 0);
lcd.print(EEPROM.read(2));
lcd.setCursor(12, 0);
lcd.print(EEPROM.read(3));
lcd.setCursor(0, 1);
lcd.print(iButMinus);
lcd.setCursor(6, 1);
lcd.print(iButMenu);
lcd.setCursor(12, 1);
lcd.print(iButPlus);

Данный код выведет в верхней строке значения ЕЕПРОМ для всех кнопок а в нижней строке под значениями ЕЕПРОМа значения соответствующих кнопок. Вот вам пример вывода переменных на LCD.

Ну и наконец код данного урока можно скачать Lesson Clock 1. Надеюсь Вам понравился данный урок, что непонятно- пишите в комментариях. Буду очень рад отзывам как положительным так и отрицательным. Готовлю следующий урок. Всем спасибо. Пока.

PS. После постройки курятника возникла необходимость в включении/ отключении света по таймеру. Устройство было собрано и работает. Это практически законченная версия часов на Ардуино Нано и индикаторе TM1637.  Если вы доработаете скетч под свои нужды- то заходите на ЭТУ СТРАНИЦУ. Если нет, то в ближайшем будущем выложу уже готовый скетч часов Ардуино Нано и индикаторе TM1637.

<-Урок 7.3 Урок 7.5->

 

Добавить комментарий