В прошлой статье мы разбирали устройство семисегментного светодиодного индикатора и собрали из четырех таких приборов устройство:

led dyn5

 

Сегодня мы будем его оживлять, т.е. выводить на индикаторы какую-то осмысленную информацию.

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

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

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

Полный код скетча:

#define DIG1 9
#define DIG2 10
#define DIG3 11
#define DIG4 12
#define A 2
#define B 3
#define C 4
#define D 5
#define E 6
#define FF 7
#define G 8
#define TAKT 5
#define BRIGHT 5

int dig1 = 0;
int dig2 = 0;
int dig3 = 0;
int dig4 = 0;


void setup() {                
  pinMode(A, OUTPUT); pinMode(B, OUTPUT);
  pinMode(C, OUTPUT); pinMode(D, OUTPUT);
  pinMode(E, OUTPUT); pinMode(FF, OUTPUT);
  pinMode(G, OUTPUT); pinMode(DIG1, OUTPUT);
  pinMode(DIG2, OUTPUT); pinMode(DIG3, OUTPUT);
  pinMode(DIG4, OUTPUT);
  digitalWrite(A,HIGH); digitalWrite(B,HIGH);
  digitalWrite(C,HIGH); digitalWrite(D,HIGH);
  digitalWrite(E,HIGH); digitalWrite(FF,HIGH);
  digitalWrite(G,HIGH); digitalWrite(DIG1,HIGH);
  digitalWrite(DIG2,HIGH); digitalWrite(DIG3,HIGH);
  digitalWrite(DIG4,HIGH);
}

void loop() {
  DisplayMath((millis() / 1000));
  DisplayShow();
}

void DisplayMath(int data) {
  int DataTemp;
  dig1 = dig2 = dig3 = dig4 = 0;
  if (data < 100) {
    while (data >= 10) {
      data -= 10;
      dig2++;
    }
    dig1 = data;
 
  }
   
      if ((data >= 100)&& (data < 1000)){
        
         while (data >= 100)     {
         data -= 100;
         dig3++;    
         }
         while (data >= 10)     {
         data -= 10;
         dig2++;    
         }
         dig1 = data;
      }  
      
      if ((data >= 1000)&& (data < 10000)){
        
        while (data >= 1000)     {
         data -= 1000;
         dig4++;    
         }
        
         while (data >= 100)     {
         data -= 100;
         dig3++;    
         }
         while (data >= 10)     {
         data -= 10;
         dig2++;    
         }
         dig1 = data;
      }  
     
}


void DisplayShow() {
  digitalWrite(DIG1,HIGH);
  Show(dig1);
  delay(BRIGHT);
  Clean();
  digitalWrite(DIG1,LOW);
  delay(TAKT-BRIGHT);
  digitalWrite(DIG2,HIGH);
  Show(dig2);
  delay(BRIGHT);
  Clean();
  digitalWrite(DIG2,LOW);
  delay(TAKT-BRIGHT);
//===============================

 digitalWrite(DIG3,HIGH);
  Show(dig3);
  delay(BRIGHT);
  Clean();
  digitalWrite(DIG3,LOW);
  delay(TAKT-BRIGHT);

  digitalWrite(DIG4,HIGH);
  Show(dig4);
  delay(BRIGHT);
  Clean();
  digitalWrite(DIG4,LOW);
  delay(TAKT-BRIGHT);  
}

void Show(int digit) {
  switch(digit) {
    case 0: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW); digitalWrite(D,LOW);
      digitalWrite(E,LOW); digitalWrite(FF,LOW);
    }
    break;
    case 1: {
      digitalWrite(B,LOW); digitalWrite(C,LOW);
    }
    break;
    case 2: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(D,LOW); digitalWrite(E,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 3: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW); digitalWrite(D,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 4: {
      digitalWrite(B,LOW); digitalWrite(C,LOW);
      digitalWrite(FF,LOW); digitalWrite(G,LOW);
    }
    break;
    case 5: {
      digitalWrite(A,LOW); digitalWrite(C,LOW);
      digitalWrite(D,LOW); digitalWrite(FF,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 6: {
      digitalWrite(A,LOW); digitalWrite(C,LOW);
      digitalWrite(D,LOW); digitalWrite(E,LOW);
      digitalWrite(FF,LOW); digitalWrite(G,LOW);
    }
    break;
    case 7: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW);
    }
    break;
    case 8: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW); digitalWrite(D,LOW);
      digitalWrite(E,LOW); digitalWrite(FF,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 9: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW); digitalWrite(D,LOW);
      digitalWrite(FF,LOW); digitalWrite(G,LOW);
    }
    break;
  }
}

void Clean() {
    digitalWrite(A,HIGH); digitalWrite(B,HIGH);
    digitalWrite(C,HIGH); digitalWrite(D,HIGH);
    digitalWrite(E,HIGH); digitalWrite(FF,HIGH);
    digitalWrite(G,HIGH);
}

Приступаем разбирать важные куски скетча:

Сначала задаем выводы Ардуины, к которым будут подключены наши индикаторы, а также - другие параметры и переменные:

#define DIG1 9                // выводы Ардуины
#define DIG2 10             // для подключения анодов
#define DIG3 11             // индикаторов
#define DIG4 12
#define A 2                     // выводы Ардуины
#define B 3                     // для подключения сегментов
#define C 4                    // индикаторов
#define D 5
#define E 6
#define FF 7
#define G 8
#define TAKT 5              // параметры, определяющие
#define BRIGHT 5          // время свечения индикатора (яркость)

int dig1 = 0;                     // четыре целочисленных переменных
int dig2 = 0;                    // для каждого разряда (индикатора)
int dig3 = 0;
int dig4 = 0;


void setup() {                
  pinMode(A, OUTPUT); pinMode(B, OUTPUT);      // первичная настройка всех выводов для 
  pinMode(C, OUTPUT); pinMode(D, OUTPUT);     // работы на выход
  pinMode(E, OUTPUT); pinMode(FF, OUTPUT);
  pinMode(G, OUTPUT); pinMode(DIG1, OUTPUT);
  pinMode(DIG2, OUTPUT); pinMode(DIG3, OUTPUT);
  pinMode(DIG4, OUTPUT);
  digitalWrite(A,HIGH); digitalWrite(B,HIGH);           // гашение всех сегментов
  digitalWrite(C,HIGH); digitalWrite(D,HIGH);
  digitalWrite(E,HIGH); digitalWrite(FF,HIGH);
  digitalWrite(G,HIGH); digitalWrite(DIG1,HIGH);
  digitalWrite(DIG2,HIGH); digitalWrite(DIG3,HIGH);
  digitalWrite(DIG4,HIGH);

//++++++++++++++++++++++++++++++++++++++++

Вроде бы, с установками все просто и вопросов быть не должно. Поэтому перейдем сразу к функциям.

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

 void Clean() {
    digitalWrite(A,HIGH); digitalWrite(B,HIGH);
    digitalWrite(C,HIGH); digitalWrite(D,HIGH);
    digitalWrite(E,HIGH); digitalWrite(FF,HIGH);
    digitalWrite(G,HIGH);
}

//++++++++++++++++++++++++++++++++++++++++

Функция Show(int digit) - предназначена непосредственно для зажигания определенных сегментов, соответствующих переданному в нее целому числу digit. Работает она так - в функцию передается целое число. При помощи оператора switch() - case, она сравнивает его с предопределенными значениями, и , если натыкается на совпадение этих значений, то зажигает определенные сегменты индикатора, соответствующие переданному в функцию числу

void Show(int digit) {
  switch(digit) {                             
    case 0: {                                                                    //если переданное в функцию число - 0
      digitalWrite(A,LOW); digitalWrite(B,LOW);        // то зажигаются сегменты индикатора,
      digitalWrite(C,LOW); digitalWrite(D,LOW);        // образующие рисунок цифры 0
      digitalWrite(E,LOW); digitalWrite(FF,LOW);
    }
    break;
    case 1: {
      digitalWrite(B,LOW); digitalWrite(C,LOW);
    }
    break;
    case 2: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(D,LOW); digitalWrite(E,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 3: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW); digitalWrite(D,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 4: {
      digitalWrite(B,LOW); digitalWrite(C,LOW);
      digitalWrite(FF,LOW); digitalWrite(G,LOW);
    }
    break;
    case 5: {
      digitalWrite(A,LOW); digitalWrite(C,LOW);
      digitalWrite(D,LOW); digitalWrite(FF,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 6: {
      digitalWrite(A,LOW); digitalWrite(C,LOW);
      digitalWrite(D,LOW); digitalWrite(E,LOW);
      digitalWrite(FF,LOW); digitalWrite(G,LOW);
    }
    break;
    case 7: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW);
    }
    break;
    case 8: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW); digitalWrite(D,LOW);
      digitalWrite(E,LOW); digitalWrite(FF,LOW);
      digitalWrite(G,LOW);
    }
    break;
    case 9: {
      digitalWrite(A,LOW); digitalWrite(B,LOW);
      digitalWrite(C,LOW); digitalWrite(D,LOW);
      digitalWrite(FF,LOW); digitalWrite(G,LOW);
    }
    break;
  }
}

 //++++++++++++++++++++++++++++++++++++++

Функция DisplayShow() - обеспечивает поочередную индикацию всех четырех разрядов нашего устройства. Причем, как говорилось выше - с такой скоростью, чтобы для человеческого глаза казалось, что они горят одновременно. Глобальные переменные dig1 - dig4 должны содержать поразрядно цифры, которые нам необходимо индицировать.

void DisplayShow() {
  digitalWrite(DIG1,HIGH);          // зажигаем первый индикатор подачей на анод высокого уровня (1).
  Show(dig1);                                // вызываем функцию Show(dig1) и устанавливаем на сегментах уровни для индикации соответствующей цифры.
  delay(BRIGHT);                          // делаем короткую паузу, чем она больше, тем больше яркость горения индикатора.
  Clean();                                        // гасим индикатор
  digitalWrite(DIG1,LOW);           // подаем на анод низкий уровень (0)
  delay(TAKT-BRIGHT);              // чисто для экспериментов - поиграться со значениями времени горения индикатора:-) 

//_----------------------
  digitalWrite(DIG2,HIGH);
  Show(dig2);
  delay(BRIGHT);
  Clean();
  digitalWrite(DIG2,LOW);
  delay(TAKT-BRIGHT);

//+++++++++++++++++++++++++++++++++++++++++

Самая важная функция DisplayMath(int data). В нее передается целое число (data), которое для нашего 4-х разрядного устройства может быть в интервале от 0 до 9999. Эта функция разбивает это число по разрядам для индикации соответствующих цифр в каждом разряде нашего устройства. 

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

void DisplayMath(int data) {
  dig1 = dig2 = dig3 = dig4 = 0;      // в переменные, соответстующие разрядам индикатора записывается 0
  if (data < 100) {                               // если число, переданное для индикации меньше 100,
    while (data >= 10) {                     // пока число больше 10, в цикле отнимаем от него
      data -= 10;                                  //десятки, одновременно
      dig2++;                                       // инкрементируя на каждом проходе переменную dig2, соответствующую цифре второго разряда.
    }
    dig1 = data;                                 // остаток, когда число станет меньше 10, записываем в переменную первого разряда.
 
  }
   
      if ((data >= 100)&& (data < 1000)){              // если переданное число в интервале от 100 до 1000
        
         while (data >= 100)     {                               // аналогично вышеизложенному, вычитаем из него
         data -= 100;                                                 // сотни, одновременно накапливая третий разряд (сотни)
         dig3++;    
         }
         while (data >= 10)     {                                 // когда в результате вычитаний сотен, число станет <100
         data -= 10;                                                   // но >=10, начинаем вычитать десятки,
         dig2++;                                                         // накапливая второй разряд (десятки)
         }
         dig1 = data;                                                 // остаток записываем в первый разряд
      }  
      
      if ((data >= 1000)&& (data < 10000)){       // аналогично поступаем, если переданное число находится
                                                                              // в интервале от 1000 до 9999.
        while (data >= 1000)     {
         data -= 1000;
         dig4++;    
         }
        
         while (data >= 100)     {
         data -= 100;
         dig3++;    
         }
         while (data >= 10)     {
         data -= 10;
         dig2++;    
         }
         dig1 = data;
      }  
     
}

//++++++++++++++++++++++++++++++++++++++++

Для проверки работоспособности также использована уже знакомая функция millis(), которая отсчитывает миллисекунды, прошедшие с момента включения устройства. В данном случае, она передает для индикации количество секунд с момента включения, которые мы и можем наблюдать на индикаторах:

led dyn7

 

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

 

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

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

led dyn

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

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

Для удобства подключения, эти черточки, из которых составляются знаки, обозначают буквами латинского алфавита:

led dyn2

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

Соответственно, все остальные выводы такого индикатора, кроме общего анода (катода), выведены наружу и соответствуют своим сегментам на "экране". Схема соединения индикатора:

led dyn3

Общее правило: если при подаче на общий проводник индикатора "плюса" питания, а на любой сегмент "минуса", этот самый сегмент загорается, то имеем индикатор с "общим анодом". Ну и, соответственно, наоборот.

Вот эти мои индикаторы, которые на фото выше (те что поменьше), были когда-то выпаяны из какой-то аппаратуры и не имели никакой маркировки. Причем, можно заметить, что у них в одном корпусе содержится по 2 индикатора. Тем не менее, взяв 2 проводка, соединенные с источником питания (лучше стандартные 5 вольт) и (обязательно!) сопротивление на 100 - 200 ом, можно методом научного тыка определить все выводы. То есть, начинаем подавать напряжение наугад на разные выводы индикатора. В какой-то момент мы найдем такую пару выводов, когда загорится какой-то сегмент. Значит, один из выводов, на которые мы наткнулись - это общий провод для данного индикатора (анод или катод). Ну, а вычисление расположения выводов других сегментов - дело техники и здравого смысла. Рекомендую нарисовать на бумажке расположение выводов индикатора и соответствующих им сегментов.

Разобравшись с выводами и нарисовав схему соответствия выводов конкретного индикатора с его сегментными светодиодами, мы обнаружим, что для подключения КАЖДОГО индикатора (разряда), нам нужно управлять ДЕВЯТЬЮ выводами (7 сегментов, точка и анод). То есть, так выходит, что, если мы хотим одновременно использовать, например, 4-хразрядный экран, то нам понадобится куда-то подключать 4 * 9 = 36 его выводов! И куда мы будем их подключать, когда во всей Ардуине их меньше?

Придется как-то выкручиваться. Если мы физически не можем управлять одновременно всеми 4-мя индикаторами, то будем управлять ими..... ПО ОЧЕРЕДИ! Воспользуемся недостатком человеческого глаза.. Ну, или достоинством, раз в данном случае оно нам поможет. 

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

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

Алгоритм работы: исходное положение - на анодах (буду для простоты про свои индикаторы) нулевой потенциал, ни один индикатор не горит. Все выводы одноименных сегментов всех индикаторов соединены между собой согласно рисунку. Набираем на них нужную комбинацию "0" и "1", соответствующую какой-нибудь цифре. Затем подаем на анод "1". Индикатор загорается первым разрядом, показывая нам набранную цифру. Через какое-то время подаем на анод "0", первый разряд тухнет. Набираем на сегментных выводах комбинацию, соответствующую второму разряду, подаем "1" на анод второго разряда, он загорается цифрой, выдержка - и тушим его. И так далее по кругу. Если проделывать это с достаточно большой скоростью, то нашему глазу будет казаться, что все разряды зажжены одновременно. При этом мы использовали всего 7 сегментных выводов и 1 анодный для каждого индикатора, в моем случае, для четырех индикаторов - 4. Всего - 11, если не считая точки. Уже стало гораздо легче.

Вот это и есть принцип динамической индикации, на котором работает подавляющее количество приборов с цифровой индикацией.

Ну, а пока займемся размещением наших индикаторов на макетной плате:

led dyn1

 

На этом рисунке показаны установленные на макетке индикаторы и зажжен один сегмент. ВАЖНО! Напряжение на сегменты необходимо подавать через гасящие сопротивления 50-150 ом!

led dyn4

 

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

led dyn5

 

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

 Теперь еще такой нюанс. Как мы помним из предыдущих статей, ток одного включенного светодиода - примерно 20 миллиампер. А их, в одном разряде - целых 7, а с точкой - так и 8. То есть, через общий анодный вывод, при одновременном свечении всех сегментов разряда может протекать суммарный ток около 150 миллиампер. А это очень много, если учесть, что максимальный выходной ток вывода Ардуины - не более 40 миллиампер. Поэтому, для соединения анодов с Ардуиной будем использовать дополнительно транзисторные ключи:

led dyn6

 

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

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

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

 

 

 

В прошлых статьях мы успешно измеряли температуру с помощью датчика 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. Но это уже - в следующей стать.

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

 

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

В этом месте у меня для вас, мальчики и девочки, есть новости как хорошие, так и плохие.

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

rus

 

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

rus1

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

Теперь вернемся к первому, русифицированному дисплею. Обратите внимание, что все символы расположены в ячейках, которые имеют свой адрес (или код). Когда мы пишем в скетче что-то типа lcd.print("Temper");, то среда программирования Arduino IDE воспринимает (вернее, ретранслирует Ардуине) выводимую строку не в виде строки символов, а в виде набора адресов (кодов) ячеек индикатора, в которых лежат нужные нам символы.

То есть, присутствует цепочка: мы пишем на экране строку символов, среда программирования переводит эту строку в набор адресов ячеек памяти индикатора, индикатор, читая эти коды, роется у себя в памяти и выводит себе на экран то, что мы ему передали. Все прекрасно!

Прекрасно, если только мы пишем по-английски. Не буду вдаваться в рассуждения про стандартные таблицы кодировок символов, только скажу, что Arduino IDE, сама по себе, воспринимает (и передает в кодах индикатору) только английские символы. И передаваемые коды символов точно соответствуют адресам индикатора, в которых эти символы хранятся. То есть - написал в компьютере строку - точно такая же строка появилась в индикаторе.

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

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

Как же опрелелить нужный нам адрес? Смотрим верхнюю таблицу, самую левую колонку. Каждая строка зашифрована кодом типа HHLH. Таким же способом обозначен и каждый столбец. Давайте выберем русскую букву, которую нам нужно показать. Допустим, это будет буква "П" для слова "АПР" (апрель, значит). Обозначение строки, на которой лежит буква "П" - HLLL,  а столбец - HLHL. Теперь, вместо "Н" подставляем "1", а вместо "L" - "0" и получаем адрес в двоичной записи 10101000 (сначала - адрес столбца, потом - строки).

Теперь откроем "Калькулятор" Windows, переключим его в режим "Bin" (двоичные числа) и вставим в окошко полученный адрес.

rus2

Затем, тут же переключим в режим восьмеричного отображения "Oct".

rus3

Вот этот полученный код и будем использовать для отображения буквы "П". Например, вот в этом кусочке кода из прошлой статьи:

 switch(tm.Month) {
    ...........................
      case 4:
      lcd.print("A\250P");
      break;

 .............................

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

Вот что получилось в итоге:

 rus5

 

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

На этом - все про дисплеи на основе HD44780. Вернее, все необходимое для первоначального знакомства. На самом деле там еще есть что покопать, но это - отдельная большая тема. 

Продолжаем играть в наш конструктор, современные "Электронные кубики". Уже знакомая нам по датчику температуры DS18B20, буржуинская фирма "Dallas Semiconductor", в свое время обрадовала любителей электроники еще одним своим творением - микросхемой часов "реального времени" DS1307.

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

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

rtc 02

Для остальных - схематический рисунок микросхемы часов:

rtc 03

Значит, включать будем так:

 1 - 2 ножка - кварцевый резонатор 32768 Гц (берется их старой материнской платы компьютера)

 3 ножка       - плюсовой вывод батарейки ( вместе с держателем выпаивается из той же материнки)

 4 ножка       - общий провод и минус питания микросхемы (сюда же подключаем и минус батарейки)

 5 -6 ножки  - управление часами, будем подключать к Ардуине.

 7 ножка       - не подключаем.

 8 ножка       - плюс источника питания.

 Еще нам понадобятся 2 "подтягивающих" резистора по 3-5 килоом, через которые необходимо соединить ножки 5 и 6 с плюсом питания ( 8-я ножка).

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

 В результате должно получиться что-то вроде этого:

rtc1

 

Не сильно красиво, главное, чтобы работало, правда? В слелующий раз сделаем красивее.:-) 

Теперь установим наш "модуль времени" на ту же макетку, где у нас остался собранный в прошлый раз термометр:

rtc4

 Как видите, мы пока что соединили только выводы питания нашего модуля.

Теперь нужно подключить ножки 5 и 6 DS1307. Это так называемый - "последовательный интерфейс". Он состоит из 2-х проводников: SDA (вывод 5 DS1307) - это "шина данных", подключаем к выводу А4 Ардуины. Второй проводник интерфейса - SCL (вывод 6 DS1307) - выход "синхронизации" или "тактовый выход", подключаем к выводу А5 Ардуины. Вот и все с подключениями!

 rtc4 1

Тем временем, перейдем к программной части нашей системы. Как уже кто-то догадался, управлять DS1307 мы будем с помощью специализированной библиотеки "DS1307RTC" для Arduino IDE, которую можно взять, например, здесь. Установка библиотеки тривиальна - просто копируем папку в директорию /libraries.

 После установки библиотеки откроем Arduino IDE и посмотрим, что у нас получилось:

rtc5

 

Библиотека содержит 2 скетча в виде примеров использования - ReadTest и SetTime. Нужно сказать, что после сборки нашего "модуля времени", он еще не работает, как ни странно. В самом деле - откуда ему знать, сколько сейчас времени и какое число? Значит, надо в него эти данные предварительно записать. И здесь Ардуино снова показывает свой класс. Не нужно разбираться во внутреннем устройстве, принципах работы, форматах записи даты-времени в DS1307. Не нужно даже разбираться, как работает скетч SetTime, который мы сейчас открыли (открыли уже, да?). Просто, загружаем его в Ардуину и, если мы все правильно соединили, в мониторе последовательного порта получаем такую картинку:

rtc6

 

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

 Теперь откроем второй скетч, ReadTest, и загрузим его в Ардуину. После загрузки, открыв "Монитор последовательного порта", увидим следующее:

rtc7

 

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

Ну, а пока что - загрузим такой скетч:

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

LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // Указываем к каким пинам Ардуины подключен наш индикатор
void setup() {
  lcd.begin(8, 2);      // указываем тип индикатора - сколько столбцов и строк.
  lcd.clear();
}

void loop() {
  tmElements_t tm;      // объявляем набор данных, считываемый из модудя времени
  lcd.setCursor(0, 0);    // устанавливаем курсор в начало дисплея. 

  if (RTC.read(tm)) {   

    print2digits(tm.Hour);
    lcd.print(":");
    print2digits(tm.Minute);
    lcd.print(":");
    print2digits(tm.Second);    lcd.setCursor(0, 1);    
    lcd.print(tm.Day);
    lcd.print("/");
    lcd.print(tm.Month);
    lcd.print("/");
    lcd.print(tmYearToCalendar(tm.Year));  }
  delay(500);
}

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

 Коротко прокомментирую скетч. В начале его подключаются все необходимые библиотеки, затем инициализируется наш LCD дисплей HD44780, про подключение которого я писал в этой статье. Объявляется набор данных tm, как результат чтения текущего времени-даты из DS1307. Далее, выделяем из этого набора часы-минуты-секунды и выводим их на ЖК-дисплей. Из интересного - использовании отдельной функции для обработки данных:

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

Дело в том, что мы получаем данные из часов в виде целых чисел - типа 1, 3, 4 и т.д.Когда число двузначное, индикация часов вроде привычная, а когда, например, индицируем 3 часа и 5 минут, то нам, все-таки, привычней видеть 03:05. Функция работает просто - при вызове ей передается целое число - часы или минуты. Она определяет - если число меньше 10, то допечатывает перед числом "0", если 10 или больше - печатает, как есть. Ничего сложного.

Результат работы скетча:

 rtc8

 

Как видите, поскольку индикатор у меня укороченный, всего на 8 символов в ширину, то одна цифра года не поместилась. Но это сейчас неважно - ведь скетч-то учебный. Чтобы не усложнять, не стал думать про то, как выделить из года 2 последних цифры.

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

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