Светодиодные часы 2.0: температура

В прошлой статье «Arduino: светодиодные часы с синхронизацией времени» многие просили добавить отображение температуры с термодатчика DS18B20. Вот, наконец-то выкладываю код.

Всё основное подключается также, как и в предыдущей статье по ссылке, поэтому повторять не буду. Дополнительно нам понадобится цифровой термодатчик DS18B20 и резистор на 4.7 кОм. Схема его подключения такая:

DS18B20:

Распиновка:

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

Ну и собственно сам код:

// Скетч для Arduino. Светодиодные часы с синхронизацией времени и отображением температуры
// Версия 2.0 (15.11.2015)
//
// Автор: Гладышев Дмитрий (2014-2015)
// http://student-proger.ru/2015/11/svetodiodnye-chasy-2-0-temperatura

#include <SPI.h>         //для Ethernet-shield
#include <Ethernet.h>    //   --//--
#include <OneWire.h>     //для температурного датчика
#include <EthernetUdp.h>
#include <Wire.h>        //I2C
#include <DS1307RTC.h>   //RTC
#include <Time.h>
#include <Metro.h>       //для задания интервалов времени (аля Cron)

#define Debug false      //режим отладки

//****************************************************************************************
byte mac[] = { 0xDE, 0xAD, 0xBE, 0x00, 0x00, 0x00 }; //MAC-адрес Arduino
byte clock_addr[] = {0x39, 0x3a, 0x3b, 0x3d}; //адреса PCF8574 часов

#define DS_PIN 2                          //пин подключения термодатчика
#define DS_UPDATE_INTERVAL 150000         //частота чтения данных с датчика (2.5 минуты)

#define CLOCK_SHOW 15000                  //продолжительность отображения часов
#define TEMP_SHOW 5000                    //продолжительность отображения температуры

IPAddress timeServer(132, 163, 4, 101);   // IP-адрес NTP сервера
#define timeZone  7                       // Временная зона

//****************************************************************************************
unsigned int localPort = 53;

Metro ClockModeSwitch = Metro(15000); //Первое переключение показаний через 15 секунд
byte ClockMode = 0; // 0 - часы, 1 - температура

Metro TimeSync = Metro(86400000); //Частота синхронизации времени
Metro TempRead = Metro(5000); //Первое чтение с датчика через 5 секунд после запуска

char clockbuf[4];

OneWire ds(DS_PIN);

EthernetClient client2;
EthernetUDP Udp;

bool DotTimeState = false;
byte CountSensors;
unsigned long lastTimeUpdate = 0;
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract; //для нужд преобразования данных от температурного датчика
byte CurTemp=100;
int CurSign;

void setup() 
{
  if (Debug)
  {
    Serial.begin(9600);
  }
  
  for (int i=0; i<4; i++)
  {
    clockbuf[i]='-';
  }
  UpdateClock();

  // Ethernet connection:
  do 
  {
    delay(1000);
  } while (Ethernet.begin(mac) == 0);
  
  Wire.begin();
  
  //Узнаём количество термодатчиков
  CountSensors = DsCount();
  Udp.begin(localPort);
  //Запрашиваем время с NTP сервера
  setSyncProvider(getNtpTime);
  //Если время получили успешно, то записываем данные в RTC
  if (timeStatus() != timeNotSet)
  {
    RTC.set(now());
  }
}

//==============================

void loop()
{
  tmElements_t tm;

  if (ClockModeSwitch.check() == 1) //Переключение режима показа
  {
    ClockMode++;
    if (ClockMode >= 2)
    {
      ClockMode = 0;
    }
    
    switch (ClockMode)
    {
      case 0:
        ClockModeSwitch.interval(15000);
        break;
      case 1:
        ClockModeSwitch.interval(5000);
        break;
    }
  }
  
  if (TimeSync.check() == 1) //Синхронизация по времени
  {
    //Запрашиваем время с NTP сервера
    setSyncProvider(getNtpTime);
    //Если время получили успешно, то записываем данные в RTC
    if (timeStatus() != timeNotSet)
    {
      RTC.set(now());
    }
  }
    
  if (client2.available())
  {
    char c = client2.read();
  }

  if (TempRead.check() == 1) //Чтение с датчика
  {
    TempRead.interval(DS_UPDATE_INTERVAL);
    //Сбрасываем поиск датчиков (кол-во нам уже известно)
    ds.reset_search();
    //Теперь в цикле опрашиваем все датчики сразу
    for (int j=0; j<CountSensors; j++)
    {

      byte i;
      byte present = 0;
      byte data[12];
      byte addr[8];

      if ( !ds.search(addr))
      {
        ds.reset_search();
        return;
      }

      ds.reset();
      ds.select(addr);
      ds.write(0x44,1);

      delay(1000); //Время на конвертацию

      present = ds.reset();
      ds.select(addr);
      ds.write(0xBE);

      for ( i = 0; i < 9; i++) // we need 9 bytes
      {
        data[i] = ds.read();
      }

      LowByte = data[0];
      HighByte = data[1];
      TReading = (HighByte << 8) + LowByte;
      SignBit = TReading & 0x8000;  // test most sig bit
      if (SignBit) // negative
      {
        TReading = (TReading ^ 0xffff) + 1; // 2's comp
      }
      Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25

      Whole = Tc_100 / 100;  // separate off the whole and fractional portions
      Fract = Tc_100 % 100;

      CurTemp = round(Tc_100 / 100);
      CurSign = SignBit;
    }
  }

  if (millis() - lastTimeUpdate > 500)
  {
    lastTimeUpdate = millis();
    
    if (((ClockMode==0)||(CurTemp==100))&&(RTC.read(tm)))
    {
      DotTimeState=!DotTimeState;
      
      clockbuf[0] = (char)(tm.Hour/10)+'0';
      if (tm.Hour<10)
      {
        clockbuf[0] = ' ';
      }
      clockbuf[1] = (char)(tm.Hour%10)+'0';
      clockbuf[2] = (char)(tm.Minute/10)+'0';
      clockbuf[3] = (char)(tm.Minute%10)+'0';
      UpdateClock();
    }
    else
    {
      DotTimeState=false;
      if (CurSign)
      {
        clockbuf[0] = '-';
      }
      else
      {
        clockbuf[0] = ' ';
      }
      clockbuf[1] = (char)(abs(CurTemp)/10)+'0';
      if (abs(CurTemp)<10)
      {
        clockbuf[1] = ' ';
      }
      clockbuf[2] = (char)(abs(CurTemp)%10)+'0';
      clockbuf[3] = '°';
      UpdateClock();
    }
  }

}

//================================================

//Количество термодатчиков на шине
int DsCount()
{
  int count=0;
  bool thatsall = false;
  byte addr[8];
  do
  {
    if ( !ds.search(addr)) {
      ds.reset_search();
      thatsall = true;
    }
    count++;
  } while(!thatsall);
  return (count-1);
}

void UpdateClock()
{
  for (int i=0; i<4; i++)
  {
    Wire.beginTransmission(clock_addr[i]);
    Wire.write(SegmentValue(clockbuf[i]));
    Wire.endTransmission();
  }
}

byte SegmentValue(char k)
{
  byte u;
  switch (k)
  {
    case '0':
      u=B1111110;
      break;
    case '1':
      u=B0110000;
      break;
    case '2':
      u=B1101101;
      break;
    case '3':
      u=B1111001;
      break;
    case '4':
      u=B0110011;
      break;
    case '5':
      u=B1011011;
      break;
    case '6':
      u=B1011111;
      break;
    case '7':
      u=B1110000;
      break;
    case '8':
      u=B1111111;
      break;
    case '9':
      u=B1111011;
      break;
    case '-':
      u=B0000001;
      break;
    case ' ':
      u=B0000000;
      break;
    case 'E':
      u=B1001111;
      break;
    case 'U':
      u=B0111110;
      break;
    case 'u':
      u=B0011100;
      break;
    case 'I':
      u=B0110000;
      break;
    case 'i':
      u=B0010000;
      break;
    case 'O':
      u=B1111110;
      break;
    case 'o':
      u=B0011101;
      break;
    case 'P':
      u=B1100111;
      break;
    case 'A':
      u=B1110111;
      break;
    case 'S':
      u=B1011011;
      break;
    case 'F':
      u=B1000111;
      break;
    case 'H':
      u=B0110111;
      break;
    case 'J':
      u=B0111100;
      break;
    case 'L':
      u=B0001110;
      break;
    case 'C':
      u=B1001110;
      break;
    case 'B':
      u=B1111111;
      break;
    case 'b':
      u=B0011111;
      break;
    case 'd':
      u=B0111101;
      break;
    case '°':
      u=B1100011;
      break;
    default:
      u=B0000000;
  }
  
  byte t=0;
  
  //переворачиваем биты
  for (byte i=0; i<7; i++)
  {
    if ((u&(1<<i))!=0)
    {
      t=t|(1<<(6-i));
    }
  }
  u=t;
  
  u=u<<1;
  if (DotTimeState)
  {
    u=u+1;
  }
    
  return u;
}


/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  if (Debug)
  { 
    Serial.println("Transmit NTP Request");
  }    
  sendNTPpacket(timeServer);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) 
  {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) 
    {
      if (Debug)
      {
        Serial.println("Receive NTP Response");
      }
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  if (Debug)
  {
    Serial.println("No NTP Response :-(");
  }    
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

/*-------- NTP code END ----------*/

Смотрим строки 19-29:

byte mac[] = { 0xDE, 0xAD, 0xBE, 0x00, 0x00, 0x00 }; //MAC-адрес Arduino
byte clock_addr[] = {0x39, 0x3a, 0x3b, 0x3d}; //адреса PCF8574 часов
 
#define DS_PIN 2                          //пин подключения термодатчика
#define DS_UPDATE_INTERVAL 150000         //частота чтения данных с датчика (2.5 минуты)
 
#define CLOCK_SHOW 15000                  //продолжительность отображения часов
#define TEMP_SHOW 5000                    //продолжительность отображения температуры
 
IPAddress timeServer(132, 163, 4, 101);   // IP-адрес NTP сервера
#define timeZone  7                       // Временная зона

mac — MAC-адрес Ethernet-shield. В принципе, можете оставить таким же.

clock_addr — адреса микросхем PCD8574 на шине I2C. Если не меняли, то должны быть такими же. Определить адреса можно с помощью скетча i2c_scanner, который есть в конце статьи.

timeServer — IP-адрес NTP-сервера.

timeZone — номер часового пояса.

DS_PIN — номер порта, к которому подключен датчик DS18B20.

DS_UPDATE_INTERVAL — частота обновления данных с датчика (в миллисекундах).

CLOCK_SHOW — время отображения часов (в миллисекундах).

TEMP_SHOW — время отображения температуры (в миллисекундах).

Файлы

Скетч
Библиотеки:
DS1307RTC
Time
Metro
OneWire

Работа проверялась с Arduino IDE v1.0.5-r2.
Библиотеки являются собственностью их авторов!

Предупреждение!
Автор не несёт ответственности за возможную порчу оборудования. Всё, что вы делаете — вы делаете на свой страх и риск!