Сегодня мы соберем секундомер, взяв за основу схему 4-битного подключения ЖК-дисплея к Arduino из прошлого занятия.





      В Tinkercad это будет выглядеть так:



Для работы с дисплеем, программа будет использовать библиотеку LiquidCrystal.h
#include 
LiquidCrystal lcd(3, 5, 10, 11, 12, 13);
long Seconds;
                       // переменная Seconds будет содержать количество секунд
void setup() {
  lcd.begin(16, 2);
  lcd.clear();
}

void loop() {
  lcd.setCursor(0, 0); // устанавливаем курсор в вехний левый угол дисплея
  lcd.print(Seconds);  // выводим результат на дисплей
  delay(1000);         // ждём 1 секунду
  Seconds += 1;        // увеличиваем значение переменной Seconds на 1
}
Программа получилась очень простая, правда и умеет она не слишком много, да и точность нашего секундомера оставляет желать лучшего. Секундомер запускается сразу при включении и нет возможности запускать и останавливать секундомер когда это потребуется. Конечно, можно воспользоваться кнопкой "Сброс" контроллера, но это не очень удобно, поэтому добавим кнопку, которая позволит запускать и останавливать секундомер. Добавить кнопку и запрограммировать её можно так, как мы это делали на уроке "Зажигаем огни". Кнопки и светодиоды., в котором мы рассмотрели различные варианты программирования кнопки, в том числи и с учетом "дребезга контактов". Кнопку подключаем к выводу D2.



      В Tinkercad полностью схема будет выглядеть так:



И программа, с учетом подключенной кнопки:

#include < LiquidCrystal.h>
LiquidCrystal lcd(3, 5, 10, 11, 12, 13);
long  Seconds;
boolean button;		  // в эту переменную быдум записывать состояние кнопки.
boolean btnUp = true;     // была ли кнопка отпущена?

void setup() {
  lcd.begin(16, 2);
  lcd.clear();

  pinMode(2, INPUT);     // Программируем вывод D2 как вход, и подключаем к нему кнопку.
}

void loop() 
{
  boolean btn = digitalRead(2);    // Присваиваем переменной btn значение вывода D8
  if (!btn && btnUp)               // Если кнопка нажата, но до этого была отпущена
    {
     delay(1);
     boolean btn = digitalRead(2); // Присваиваем переменной btn значение вывода D8
     if (!btn) button=!button;     // если кнопка нажата, переменной button присвоить противоположенное значение  
     delay(1);                     // Ждем 1 милисекунду.
    }
  btnUp = btn;                     // запоминаем последнее состояние кнопки для нового цикла


  lcd.setCursor(0, 0);// выводим результат на дисплей
  lcd.print(Seconds);
  delay(1000); //ждём 1 секунду
  if (button == 1){ Seconds += 1; } // увеличиваем Seconds на 1, только если кнопка была нажата
}
И в принципе все работает, в нужный момент мы можем кнопкой запустить секундомер, или остановить его, а потом продолжить счет, а сбросить показания секндомера можно кнопкой "Сброс" платы Arduino.

Но код получился какой-то громоздкий и на самом деле все можно сделать еще проще, смотрите:
#include < LiquidCrystal.h>
LiquidCrystal lcd(3, 5, 10, 11, 12, 13);
long  Seconds;

int button;				// кнопка "Старт/Стоп"

void setup() {
  lcd.begin(16, 2);
  lcd.clear();
  pinMode(2, INPUT);     		// Программируем вывод D2 как вход
  attachInterrupt(0, btn, FALLING);  	// "Ловим" кнопку при переходе с высокого сигнала на низкий и переходим в цикл batton
}

void btn () {
  button = !button;         		//"переворачиваем" значение при нажатии кнопки
}

void loop() {

  lcd.setCursor(0, 0);		// выводим результат на дисплей
  lcd.print(Seconds);
  delay(1000); 			//ждём 1 секунду
  Seconds +=  button; 	        // прибовляем к переменной Seconds значение переменной button 
}



      Вместо программы обработки "дребезга" здесь появилась строка

attachInterrupt(0, btn, FALLING);


и подпрограмма btn(), которая присваивает единицу переменной button, если button была равна нулю, и ноль, если button была равна единице. Давайте с этим разбираться...

attachInterrupt()

      Функция attachInterrupt() - это функция прерывания. Прерывание - это сигнал, который сообщает процессору о наступлении какого-либо события, которое требует незамедлительного внимания. Процессор должен отреагировать на этот сигнал, прервав выполнение текущих инструкций и передав управление обработчику прерывания (ISR, Interrupt Service Routine). Обработчик - это обычная функция, которую мы пишем сами и помещаем туда тот код, который должен отреагировать на событие. В нашем примере это обработчиком прерывания является подпрограмма btn() - она выполняется один раз, когда прерывание произошло.

После обслуживания прерывания ISR функция завершает свою работу и процессор с удовольствием возвращается к прерванным занятиям - продолжает выполнять код с того места, в котором остановился.

Синтаксис вызова: attachInterrupt(interrupt, function, mode)

Аргументы функции:

interrupt - номер вызываемого прерывания (стандартно 0 - для 2-го пина, 1 - для 3-го пина),

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

mode - условие срабатывания прерывания.

Прерывания в Ардуино можно разделить на несколько видов:

Аппаратные прерывания.

Аппаратные прерывания возникают в ответ на внешнее событие и исходят от внешнего аппаратного устройства. В зависимости от аппаратной реализации конкретной модели микроконтроллера есть несколько прерываний.



Плата Arduino Uno имеет 2 прерывания на втором и третьем пине. На других платах число прерываний выше. Например, плата Ардуино Мега 2560 имеет 6 пинов, которые могут обрабатывать внешние прерывания.

В Ардуино представлены 4 типа аппаратных прерываний, или условий срабатывания прерывания. Все они различаются сигналом на контакте прерывания:




Итак, наше прерывание:
attachInterrupt(0, btn, FALLING);
работает следующим образом, во первых отслеживается пин D2, именно он отвечает за прерывание int_0. Выполняется прерывание один раз при изменении сигнала от HIGH к LOW, т.е. сразу как будет нажата кнопка. И если кнопка была нажата, то управление передается подпрограмме btn(), которое изменит значение переменной button, и если значение переменной button = 1 - секундомер будет считать, так как переменная Seconds будет увеличивать на значение button каждую секунду, а если button=0, то Seconds изменяться не будет.

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



Кнопку подключим также как и первую кнопку, но к выводу D7, так как вывод D3 уже занят, а значит еще раз использовать прерывание мы не сможем. Но мы сделаем хитрее :)
#include < LiquidCrystal.h>
LiquidCrystal lcd(3, 5, 10, 11, 12, 13);
long  Seconds;

int button;				// кнопка "Старт/Стоп"

void setup() {
  lcd.begin(16, 2);
  lcd.clear();
  pinMode(2, INPUT);     		// Программируем вывод D2 как вход
  pinMode(7, INPUT);     		// Программируем вывод D7 как вход
  attachInterrupt(0, btn, FALLING);  	// "Ловим" кнопку при переходе с высокого сигнала на низкий и переходим в цикл batton
}

void btn () {
  button = !button;         		//"переворачиваем" значение при нажатии кнопки
}

void loop() {

  lcd.setCursor(0, 0);		// выводим результат на дисплей
  lcd.print(Seconds);
  delay(1000); 			//ждём 1 секунду
  Seconds +=  button;      // прибовляем к переменной Seconds значение переменной button 
  Seconds = Seconds * digitalRead(7); //если ножали на кнопку 2 то счотчик обнулится
}
Просто умножим переменную Seconds на считанное с вывода 7 значение! И если кнопка не нажата, то ничего и не произойдет, так как вывод подтянут через резистор к плюсу питания и на нем всегда высокий уровень, или единица, а умножение на 1 никак не изменит значение переменной Second. Но если кнопку нажать, но на выводе 7 будет низкий уровень или ноль, а при умножении не 0 переменная Second станет равной нулю.

Точность секундомера можно повысить скорректировав значение функции delay(). Строго говоря оно должно быть чуть меньше чем 1000, но, забегая вперед, скажу, что для этого лучше использовать внешний или внутренний таймер.

И, наконец, можно сделать более красивый вывод на дисплей, выводя не только значение секунд, но, например, минут и часов, а также режима работы или какие-нибудь надписи. Попробуйте усовершенствовать этот секундомер самостоятельно!
Далее...


Copyright © R9AL 2020 Все права защищены

Рейтинг@Mail.ru Яндекс цитирования