В прошлых статьях мы успешно измеряли температуру с помощью датчика DS18B20 и получали текущую дату-время из "часов реального времени (RTC)" DS1307. Все эти данные по-отдельности выводили на жидкокристаллический дисплей типа HD44780

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

То есть - из двух скетчей, которые мы рассматривали раньше, будем собирать один. Ну, типа, как в конструкторе. Прежде, чем опубликовать полный скетч устройства "часы + термометр", хочу рассмотреть, для понимания принципа работы, важные и основные части нашей будущей программы.

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

То есть - мы должны заставить Ардуину переключаться с одной задачи на другую с такой скоростью, чтобы казалось, что они выполняются одновременно. В качестве жизненного примера можно привести циркового жонглера, который одновременно подбрасывает штук 6-8 шариков. Чтобы шарики не падали - он должен постоянно выполнять некую работу - подбрасывать их. Но рукИ-то у него только 2? Поэтому, он распределяет свое время (руки) поочередно между шарами, уделяя каждому внимание строго не позже того момента, когда необходимо. А если, во время жонглирования, например, он захочет налить себе стакан воды? Возможно это? Ну, если он сможет выкроить время между подбрасываниями шариков - почему бы и нет?

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

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

Ну, и для удобства использования и восприятия - оформить их в виде отдельных функций, чтобы не захламлять лишними буквами основной программный цикл loop().

 К примеру, функцию измерения и индикации температуры предлагаю разбить на 3 части:

1. Запрос на измерение температуры

void Izm_Temp(){
  ds.reset();             // инициализируем датчик
  ds.write(0xCC);         // пропускаем адресацию к конкретному датчику (у нас он один)
  ds.write(0x44);         // даем команду измерять температуру
}

2. Получение температуры из датчика DS18B20

void Read_Temp() {
   ds.reset();            // снова инициализируем датчик
  ds.write(0xCC);        // снова пропускаем адресацию
  ds.write(0xBE);         // даем команду готовности считывать температуру
  data[0] = ds.read();    //считываем  младший
  data[1] = ds.read();    // и старщий байты
  if bitRead(data[1],7)   // если старший бит старшего байта - 1, это отрицательная температура.
     {znak = "";}
     else
     {znak = "+";}
  Temp = (data[1] << 8) + data[0];  // преобразуем считанную информацию

  Temp = Temp >> 4;                     // к нужному виду.
}

3. Индикация полученной температуры

void Print_Temp() {
  lcd.clear();                            //очищаем экран индикатора
lcd.print("Temper:");                   // печатаем сообщение

 /* Устанавливаем курсор в 1 столбец 2й строки. Нумерация идёт с нуля,
  * первым аргументом идёт номер столбца.
  */
 lcd.setCursor(1, 1);

      if (Temp < 0)            // если температура отрицательная, то выводим только ее.
         lcd.print(Temp + " C");
         else
         lcd.print(znak + Temp + " C"); // если положительная - дополнительно выводим знак "+".
}

 Для чего мы это сделали? Ну, именно в данном случае, в первую очередь из-за того, что в рекомендуемом цикле измерения температуры датчиком DS18B20 присутствует задержка, примерно 1 с, во время которой происходит измерение и конвертация температуры. И только после этой паузы - мы можем начинать чтение результата. В учебном скетче эта пауза формировалась функцией delay(1000). Дойдя до этого места, Ардуина бросала все остальное и на целую секунду останавливалась, выжидая, когда же там термодатчик, наконец, вычислит свою температуру. Хотя, при правильной организации процесса, за эту секунду можно было сделать десятки и сотни полезных дел.

То есть понимаете, что начинает вырисовываться: "рабочая точка" скетча с огромной скоростью крутится внутри цикла loop(), выполняя подряд все инструкции, которые попадаются на пути. При этом, она может быть направлена в ту или иную сторону, для выполнения тех или других действий, в зависимости от результатов каких-нибудь вычислений или выполнения каких-то условий.

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

Теперь, вырисовывается последовательность действий: даем команду на измерение, а затем НЕ ЖДЕМ, А ПРОВЕРЯЕМ, сколько прошло времени. И если прошла 1 с или более, тогда выполняем серию команд для чтения результата. Ну, вот, примерно так:

void loop() {
   unsigned long currentMillis = millis(); // текущее время в миллисекундах
   if((Read_Temp_State == LOW) && (currentMillis - previousMillis >= 1000))
  {
    previousMillis = currentMillis; // запоминаем момент времени
    Izm_Temp();             // запрашиваем измерение температуры.
    Read_Temp_State = HIGH; // включаем сигнал, что измерение было
    
  }
  else if ((Read_Temp_State == HIGH)&& (currentMillis - previousMillis >= 1000)) // если на предыдущем этапе измерение уже было
  {
    previousMillis = currentMillis ; // запоминаем момент времени
    Read_Temp();             // читаем температуру из датчика DS18B20
    Read_Temp_State = LOW; // включаем сигнал, что чтение было 
  }

}

Первый вопрос - кто будет измерять время? Для этого в Ардуине существует встроенная функция millis(); которая отсчитывает в миллисекундах время, прошедшее с момента включения Ардуины. Ею и будем пользоваться. Значит, алгоритм данного кусочка скетча такой - через 1 секунду подавать команду на измерение, потом, еще через 1 секунду - считывать преобразованную температуру. 

Чтобы измерить прошедший промежуток времени, мы должны ввести переменную currentMillis = millis(); и, при каждом проходе приравнивать ее к текущему значению времени. Также, предварительно зададим еще одну переменную previousMillis, которая при первом проходе равна 0.

Что же происходит? При каждом проходе цикла loop() значение millis() возрастает и, на каком-то обороте, превысит 1000, то есть выполнится условие currentMillis - previousMillis >= 1000. Замечательно, значит, прошла 1 секунда после включения, первая часть нашего условия выполнилась, теперь можно было бы направить "рабочую точку" на исполнение чего-нибудь полезного, но чего сначала - запрос температуры или считывание? Тем более, что эти действия должны чередоваться чере 1 секунду. Для определения наших действий, введем еще одну вспомогательную переменную Read_Temp_State , которая будет принимать всего два значения: 0 и 1, или, для красоты - LOW и HIGH. 

Значит, примем , что для выдачи команд измерения температуры должно выполниться два условия: пройти более 1 секунды и значение переменной Read_Temp_State == LOW:

if((Read_Temp_State == LOW) && (currentMillis - previousMillis >= 1000))

А для считывания температуры: пройти еще более 1 секунды и значение переменной Read_Temp_State == HIGH:

if ((Read_Temp_State == HIGH)&& (currentMillis - previousMillis >= 1000))

Вот и вся история. Остается только, после каждого срабатывания условий, не забыть изменить значение переменной Read_Temp_State на противоположное, чтобы через 1с сработало другое условие, да еще приравнять previousMillis = currentMillis ; чтобы начать отсчет следующей секунды.

Заметьте, пока условия не выполняются, "рабочая точка" скетча пробегает мимо этих задач, не останавливаясь. Поэтому, без задержек может выполнять другие задачи, например, считывание даты-времени из DS1307. Но это уже сами разберетесь в полном листинге скетча:

 

 

#include <OneWire.h>
#include <LiquidCrystal.h> //подключаем библиотеку LiquidCrystal.
#include <DS1307RTC.h>
#include <Time.h>
#include <Wire.h>

OneWire  ds(10);  //
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // Указываем к каким пинам Ардуины подключен наш индикатор

byte data[2];           // объявляем массив из 2-х байт
String znak;            // переменная, знак температуры.
int Temp;               // переменная, измеренная температура
tmElements_t tm;      // объявляем набор данных, считываемый из модудя времени
unsigned long previousMillis = 0; //предыдущее состояние счетчика millis
unsigned long previousMillis1 = 0; //предыдущее состояние второго счетчика millis
int Read_Temp_State = LOW;        // состояние функции измерения (на предыдущем этапе измерения не было)
int Ind_State = LOW;
void setup(void) {
//  Serial.begin(9600);     //настраиваем последовательный порт для вывода результатов
  lcd.begin(8, 2);   // указываем тип индикатора - сколько столбцов и строк.
  lcd.clear();       // очищаем экран
}

void loop() {
   unsigned long currentMillis = millis(); // текущее время в миллисекундах
   if((Read_Temp_State == LOW) && (currentMillis - previousMillis >= 1000))
  {
    previousMillis = currentMillis; // запоминаем момент времени
    Izm_Temp();             // запрашиваем измерение температуры.
    Read_Temp_State = HIGH; // включаем сигнал, что измерение было
    
  }
  else if ((Read_Temp_State == HIGH)&& (currentMillis - previousMillis >= 1000)) // если на предыдущем этапе измерение уже было
  {
    previousMillis = currentMillis ; // запоминаем момент времени
    Read_Temp();             // читаем температуру из датчика DS18B20
    Read_Temp_State = LOW; // включаем сигнал, что чтение было 
  }
//=========================================   
 if((Ind_State == HIGH) && (currentMillis - previousMillis1 >= 3000)) {
    previousMillis1 = currentMillis; // запоминаем момент времени
    Print_Temp();
    Ind_State = LOW; 
}
  else if ((Ind_State == LOW) && (currentMillis - previousMillis1 >= 3000)){
   previousMillis1 = currentMillis;
   RTC_READ();
  Ind_State = HIGH;
} 
}

void Izm_Temp(){
  ds.reset();             // инициализируем датчик
  ds.write(0xCC);         // пропускаем адресацию к конкретному датчику (у нас он один)
  ds.write(0x44);         // даем команду измерять температуру
}

void Read_Temp() {
   ds.reset();            // снова инициализируем датчик
  ds.write(0xCC);        // снова пропускаем адресацию
  ds.write(0xBE);         // даем команду готовности считывать температуру
  data[0] = ds.read();    //считываем  младший
  data[1] = ds.read();    // и старщий байты
  if bitRead(data[1],7)   // если старший бит старшего байта - 1, это отрицательная температура.
     {znak = "";}
     else
     {znak = "+";}
  Temp = (data[1] << 8) + data[0];  // преобразуем считанную информацию  Temp = Temp >> 4;                     // к нужному виду. 
}

void print2digits(int number) {     //функция принимает целое число и если
  if (number >= 0 && number < 10) { //оно меньше 10, то добавляется еще "0".
    lcd.print("0");
  }
  lcd.print(number);
}

void RTC_READ() {
  if (RTC.read(tm)) {
    lcd.clear();
    lcd.setCursor(1, 0);
    print2digits(tm.Hour);
    lcd.print(":");
    print2digits(tm.Minute);
       lcd.setCursor(1, 1);    
    lcd.print(tm.Day);
    lcd.print(" ");
    
    switch(tm.Month) {
      case 1:
      lcd.print("jan");
      break;
      case 2:
      lcd.print("feb");
      break;
      case 3:
      lcd.print("mar");
      break;
      case 4:
      lcd.print("apr");
      break;
      case 5:
      lcd.print("may");
      break;
      case 6:
      lcd.print("jun");
      break;
      case 7:
      lcd.print("jul");
      break;
      case 8:
      lcd.print("aug");
      break;
      case 9:
      lcd.print("sep");
      break;
      case 10:
      lcd.print("okt");
      break;
      case 11:
      lcd.print("nov");
      break;
      case 12:
      lcd.print("dec");
      break;
    } 

  }
}

void Print_Temp() {
  lcd.clear();                            //очищаем экран индикатора
lcd.print("Temper:");                   // печатаем сообщение

 /* Устанавливаем курсор в 1 столбец 2й строки. Нумерация идёт с нуля,
  * первым аргументом идёт номер столбца.
  */
 lcd.setCursor(1, 1);

      if (Temp < 0)            // если температура отрицательная, то выводим только ее.
         lcd.print(Temp + " C");
         else
         lcd.print(znak + Temp + " C"); // если положительная - дополнительно выводим знак "+".
}

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

Отдельного упоминания, наверное, требует вот эта полезная конструкция:

switch(tm.Month) {
      case 1:
      lcd.print("jan");
      break;

................... и так далее. 

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

Результат работы скетча - на индикаторе значения температуры и даты-времени, меняющие друг-друга через каждые 3 секунды:

datatemp

 

И через 3 секунды :

datatemp1

 

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

Открытым остается вопрос русских букв в HD44780. Но это уже - в следующей стать.

Продолжение следует...

Комментарии  

0 #2 админ 13.06.2016 18:28
Сейчас поправил. Строка OneWire ds(10); прилипла к предыдущей при копировании и получилось, что переменная ds не была объявлена.
Цитировать
0 #1 Asafei48 13.06.2016 17:57
При компиляции скетча происходит ошибка 'ds' was not declared in this scope
Цитировать

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