Мобильная метеостанция на Arduino

в 8:42, , рубрики: arduino, diy или сделай сам, метеостанция

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

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

Создание корпуса для любой самоделки является довольно сложной задачей. В закромах был найден мобильный телефон Motorola T192.

image

Оказалось, что в него отлично становится экран от Nokia 3310 (конечно использовать саму Nokia 3310, возможно, было бы удобнее).
Управлять всем было поручено Arduino Pro Mini.

image

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

image

Для измерения давления применен датчик bmp180. В качестве часов был взят модуль ds1302, но для экономии места с него была взята только микросхема. Кварц был выпаян из материнской платы, а батарейку вынул с ноутбука (конечно же, можно и обычную cr2032 в термокембрике).

image

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

image

Для зарядки литиевого аккумулятора был использован модуль заряда Li-ion на TP4056.

image

Итоговый вид устройства (провода, торчащие справа, для прошивки, поэтому вскоре будут убраны.)

image

При написании программы использовались обычные средства. Единственное хотелось бы обратить внимание на библиотеку TimeLord.h (https://github.com/probonopd/TimeLord). С помощью ее функций, указав дату, координаты и часовой пояс можно определить время восхода-захода солна, фазу луны. Описание можно скачать по ссылке: TimeLord.pdf.

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

Скетч на данный момент использует 81% памяти устройства, поэтому есть возможность добавить что-нибудь еще.

image

На экране отображается антенна (индикатор уровня сигнала). Это оставлен задел для тестирования радио модуля на 433 МГц, для получения правдивой температуры и влажности от внешнего модуля.

Исходник

#include <BMP085.h>
#include <EEPROM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <DS1302.h>
#include <TimeLord.h>
#include <Wire.h>

//84x48
Adafruit_PCD8544 display = Adafruit_PCD8544(3, 4, 5, 6, 7);
// Create a DS1302 object.
DS1302 rtc(12, 11, 10);// Chip Enable, Input/Output, Serial Clock
Time t = rtc.time();
String dayAsString(const Time::Day day) {
  switch (day) {
    case Time::kSunday: return "Sunday";
    case Time::kMonday: return "Monday";
    case Time::kTuesday: return "Tuesday";
    case Time::kWednesday: return "Wednesday";
    case Time::kThursday: return "Thursday";
    case Time::kFriday: return "Friday";
    case Time::kSaturday: return "Saturday";
  }
  return "(unknown day)";
}
//-------------
TimeLord tardis;
float const LATITUDE = 52.70;
float const LONGITUDE = 25.40;
//byte today[] = {0,0,12,22,03,16};
byte today[6];
//-------------
//long previousMillis = 0, previousMillis2 = 0;      // храним время последней команды
//long interval = 1800000;           // интервал между командами
//long interval = 3600000;           // интервал между командами
//long interval2 = 1000;           // интервал между командами
byte prevSecond = 99, prevHour = 99;
//-------------
BMP085 dps = BMP085();
long Temperature = 0, Pressure = 0;
float t_f = 0;

//-------------buttons--------------------
int buttonPushCounter = 0;
int buttonState = 0;
int lastButtonState = 0;

//-------------moon----------------------
//0.0 New Moon 0.99 - Almost new
const unsigned char PROGMEM moon0[] =
{ B00000111, B11100000,
  B00001000, B00010000,
  B00010000, B00001000,
  B00100000, B00000100,
  B01000000, B00000010,
  B10000000, B00000001,
  B10000000, B00000001,
  B10000000, B00000001,
  B10000000, B00000001,
  B10000000, B00000001,
  B01000000, B00000010,
  B00100000, B00000100,
  B00010000, B00001000,
  B00001000, B00010000,
  B00000111, B11100000
};

//Waxing Crescent
const unsigned char PROGMEM moon1[] =
{ B00000111, B11100000,
  B00001000, B01110000,
  B00010000, B00111000,
  B00100000, B00011100,
  B01000000, B00001110,
  B10000000, B00001111,
  B10000000, B00001111,
  B10000000, B00001111,
  B10000000, B00001111,
  B10000000, B00001111,
  B01000000, B00001110,
  B00100000, B00011100,
  B00010000, B00111000,
  B00001000, B01110000,
  B00000111, B11100000
};

//0.25 First Quarter
const unsigned char PROGMEM moon2[] =
{ B00000111, B11100000,
  B00001000, B11110000,
  B00010000, B11111000,
  B00100000, B11111100,
  B01000000, B11111110,
  B10000000, B11111111,
  B10000000, B11111111,
  B10000000, B11111111,
  B10000000, B11111111,
  B10000000, B11111111,
  B01000000, B11111110,
  B00100000, B11111100,
  B00010000, B11111000,
  B00001000, B11110000,
  B00000111, B11100000
};

//Waxing Gibbous
const unsigned char PROGMEM moon3[] =
{ B00000111, B11100000,
  B00001011, B11110000,
  B00010111, B11111000,
  B00101111, B11111100,
  B01001111, B11111110,
  B10001111, B11111111,
  B10001111, B11111111,
  B10001111, B11111111,
  B10001111, B11111111,
  B10001111, B11111111,
  B01001111, B11111110,
  B00101111, B11111100,
  B00010111, B11111000,
  B00001011, B11110000,
  B00000111, B11100000
};

//0.5 Full Moon
const unsigned char PROGMEM moon4[] =
{ B00000111, B11100000,
  B00001111, B11110000,
  B00011111, B11111000,
  B00111111, B11111100,
  B01111111, B11111110,
  B11111111, B11111111,
  B11111111, B11111111,
  B11111111, B11111111,
  B11111111, B11111111,
  B11111111, B11111111,
  B01111111, B11111110,
  B00111111, B11111100,
  B00011111, B11111000,
  B00001111, B11110000,
  B00000111, B11100000
};

//Waning Gibbous
const unsigned char PROGMEM moon5[] =
{ B00000111, B11100000,
  B00001111, B11010000,
  B00011111, B11101000,
  B00111111, B11110100,
  B01111111, B11110010,
  B11111111, B11110001,
  B11111111, B11110001,
  B11111111, B11110001,
  B11111111, B11110001,
  B11111111, B11110001,
  B01111111, B11110010,
  B00111111, B11110100,
  B00011111, B11101000,
  B00001111, B11010000,
  B00000111, B11100000
};

//0.75 Third Quarter  (Last Quarter)
const unsigned char PROGMEM moon6[] =
{ B00000111, B11100000,
  B00001111, B00010000,
  B00011111, B00001000,
  B00111111, B00000100,
  B01111111, B00000010,
  B11111111, B00000001,
  B11111111, B00000001,
  B11111111, B00000001,
  B11111111, B00000001,
  B11111111, B00000001,
  B01111111, B00000010,
  B00111111, B00000100,
  B00011111, B00001000,
  B00001111, B00010000,
  B00000111, B11100000
};


//Waning Crescent
const unsigned char PROGMEM moon7[] =
{ B00000111, B11100000,
  B00001110, B00010000,
  B00011100, B00001000,
  B00111000, B00000100,
  B01110000, B00000010,
  B11110000, B00000001,
  B11110000, B00000001,
  B11110000, B00000001,
  B11110000, B00000001,
  B11110000, B00000001,
  B01110000, B00000010,
  B00111000, B00000100,
  B00011100, B00001000,
  B00001110, B00010000,
  B00000111, B11100000
};
//=====================================================================================
void drawMoon(int moon_x, int moon_y, int phase) {
  display.fillRect(moon_x, moon_y, 16, 15, WHITE);
  display.drawBitmap(moon_x, moon_y, moon4,  16, 15, WHITE);
  switch (phase) {
    case 0:
      display.drawBitmap(moon_x, moon_y, moon0,  16, 15, BLACK);
      break;
    case 1:
      display.drawBitmap(moon_x, moon_y, moon1,  16, 15, BLACK);
      break;
    case 2:
      display.drawBitmap(moon_x, moon_y, moon2,  16, 15, BLACK);
      break;
    case 3:
      display.drawBitmap(moon_x, moon_y, moon3,  16, 15, BLACK);
      break;
    case 4:
      display.drawBitmap(moon_x, moon_y, moon4,  16, 15, BLACK);
      break;
    case 5:
      display.drawBitmap(moon_x, moon_y, moon5,  16, 15, BLACK);
      break;
    case 6:
      display.drawBitmap(moon_x, moon_y, moon6,  16, 15, BLACK);
      break;
    case 7:
      display.drawBitmap(moon_x, moon_y, moon7,  16, 15, BLACK);
      break;
    default:
      display.drawBitmap(moon_x, moon_y, moon4,  16, 15, WHITE);
  }
}
//===========================================================================================
void drawMoonDate(int moon_x, int moon_y, uint8_t * datetoday) {
  float phase;
  phase = tardis.MoonPhase(datetoday);
  if (phase >= 0.0   && phase <= 0.0625)  {
    drawMoon(moon_x, moon_y, 0);
  }; //0.000  New moon
  if (phase > 0.0625 && phase <= 0.1875)  {
    drawMoon(moon_x, moon_y, 1);
  }; //0,125
  if (phase > 0.1875 && phase <= 0.3125 ) {
    drawMoon(moon_x, moon_y, 2);
  }; //0.250  First Quarter
  if (phase > 0.3125 && phase <= 0.4375)  {
    drawMoon(moon_x, moon_y, 3);
  }; //0,375
  if (phase > 0.4375 && phase <= 0.5625)  {
    drawMoon(moon_x, moon_y, 4);
  }; //0.500  Full
  if (phase > 0.5625 && phase <= 0.6875)  {
    drawMoon(moon_x, moon_y, 5);
  }; //0,625
  if (phase > 0.6875 && phase <= 0.8125)  {
    drawMoon(moon_x, moon_y, 6);
  }; //0.750  Last Quarter
  if (phase > 0.8125 && phase <= 0.9375)  {
    drawMoon(moon_x, moon_y, 7);
  }; //0.875
  if (phase > 0.9375 && phase <= 1)       {
    drawMoon(moon_x, moon_y, 0);
  }; //0.990  Almost new
}
//=====================================================================================
void drawSignal(float streng) {
  display.fillRect(0, 0, 12, 6, WHITE);
  display.drawTriangle(0, 0, 8, 0, 4, 4, BLACK);
  display.drawLine(4, 0, 4, 6, BLACK);

  display.drawLine(6, 5, 6, 6, BLACK);
  display.drawLine(8, 4, 8, 6, BLACK);
  display.drawLine(10, 2, 10, 6, BLACK);
  display.drawLine(12, 0, 12, 6, BLACK);

}
//=====================================================================================
void drawBatteryState(float v_bat) {
  display.fillRect(68, 0, 16, 7, WHITE);
  display.drawRect(83, 2, 1, 3, BLACK);
  display.drawRoundRect(68, 0, 15, 7, 2, BLACK);
  // 3, 44  4, 2 0, 76  0, 152
  //4,200 full  4
  //4,048       3
  //3,896       2
  //3,744       1
  //3,592       0
  //3,440 zero  -

  if (v_bat > 4500)   {
    display.fillRect(70, 2, 10, 3, BLACK);
  }
  if (v_bat > 4048)   {
    display.drawRect(79, 2, 2, 3, BLACK);
  }
  if (v_bat > 3896)   {
    display.drawRect(76, 2, 2, 3, BLACK);
  }
  if (v_bat > 3744)    {
    display.drawRect(73, 2, 2, 3, BLACK);
  }
  if (v_bat > 3592)    {
    display.drawRect(70, 2, 2, 3, BLACK);
  }
}
//=====================================================================================
void drawTime(byte x, byte y) {
  display.fillRect(0 + x, 0 + y, 48, 7, WHITE);
  //display.fillRect(0+x, 0+y, 30, 7, WHITE);
  Time t = rtc.time();
  display.setTextColor(BLACK);
  display.setTextSize(1);
  display.setCursor(x, y);
  display.print(t.hr);
  display.print(":");
  display.print(t.min);
  display.print(":");
  display.print(t.sec);
}
//===========================================================================================
void updateMoonSunDate()
{
  Time t = rtc.time();
  today[0] = 0;
  today[1] = 0;
  today[2] = 12;
  today[3] = t.date;
  today[4] = t.mon;
  today[5] = t.yr - 2000;
}
//=====================================================================================
void drawSunRiseSet(byte x, byte y) {
  updateMoonSunDate();
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.fillRect(x, y, 33, y + 8, WHITE);

  if (tardis.SunRise(today)) // if the sun will rise today (it might not, in the [ant]arctic)
  {
    display.setCursor(x, y);
    display.print((int) today[tl_hour]);
    display.print(":");
    display.println((int) today[tl_minute]);
  }
  if (tardis.SunSet(today)) // if the sun will set today (it might not, in the [ant]arctic)
  {
    display.setCursor(x, y + 8);
    display.print((int) today[tl_hour]);
    display.print(":");
    display.println((int) today[tl_minute]);
  }

}
//=====================================================================================
void drawPressure(byte x, byte y) {
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.fillRect(x, y, 33, y + 8, WHITE);
  display.setCursor(x, y);
  t_f = Temperature;
  display.println( t_f / 10, 1);
  display.setCursor(x, y + 8);
  //display.println(ceil(Pressure / 133.3), 0);
  display.println(Pressure / 133.3, 1);
}
//=====================================================================================
long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif

  delay(75); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both

  long result = (high << 8) | low;

  //scale_constant = internal1.1Ref * 1023 * 1000
  //где
  //internal1.1Ref = 1.1 * Vcc1 (показания_вольтметра) / Vcc2 (показания_функции_readVcc())
  //4.967/4600-------1215079.369565217
  // result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*100000
  result = 1132060 / result;

  return result; // Vcc in millivolts
  //http://blog.unlimite.net/?p=25
}
//===========================================================================================
void drawVcc(byte x, byte y, word vcc) {
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.fillRect(x, y, 33, y + 8, WHITE);
  display.setCursor(x, y);
  display.println(vcc);
}
//=====================================================================================
void pressure_drawLine(byte x, byte h)
{
  display.drawLine(x, 47 - h, x, 47, BLACK);
}
void pressure_drawGraph()
{
  display.fillRect(0, 25, 83, 47, WHITE);
  for (int i = 0; i <= 83; i++) {
    pressure_drawLine(i, EEPROM.read(i));
  }
}

//===========================================================================================
void setup()
{
  Serial.begin(9600);
 
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(2, OUTPUT);
  //-----------------------------------------------------------------------------
  //rtc.writeProtect(false);
  //rtc.halt(false);
  String day = dayAsString(t.day);
  //-----------------------------------------------------------------------------
  tardis.TimeZone(3 * 55);
  tardis.Position(LATITUDE, LONGITUDE);
  //-----------------------------------------------------------------------------
  Wire.begin();
  display.begin();
  display.setContrast(55);
  display.clearDisplay();

  display.drawLine(0, display.height() / 2, display.width(), display.height() / 2, BLACK);
  dps.init();

  updateMoonSunDate();
  drawMoonDate(34, 8, today);
  pressure_drawGraph();
  display.display();

  prevHour = rtc.time().hr;
  //EEPROM.write(0, 9);
  // for (int i=0; i <= 83; i++){
  // EEPROM.write(i, random(1, 23));
  //   pressure_drawLine(i,EEPROM.read(i));
  //     Serial.println(EEPROM.read(i));
  // }

}
//=====================================================================================
void loop()
{
  unsigned long currentMillis = millis();
  unsigned long currentMillis2 = millis();
  byte temp;

  //timer---------------------- 1 hour
  //  if (currentMillis - previousMillis > interval) {
  //  previousMillis = currentMillis;
  if (rtc.time().hr != prevHour) {
    prevHour = rtc.time().hr;

    dps.getPressure(&Pressure);
    dps.getTemperature(&Temperature);

    for (int i = 0; i <= 82; i++) {
      temp = EEPROM.read(i + 1);
      EEPROM.write(i, temp);
    }
    EEPROM.write(83, ceil(Pressure / 133.3) - 740);
    pressure_drawGraph();
    display.display();


  }
  //timer---------------------- 1 sec
  // if (currentMillis2 - previousMillis2 > interval2) {
  //   previousMillis2 = currentMillis2;

  if (rtc.time().sec != prevSecond) {
    prevSecond = rtc.time().sec;

    dps.getPressure(&Pressure);
    dps.getTemperature(&Temperature);
    updateMoonSunDate();

    drawPressure(0, 8);
    drawTime(17, 0);
    drawSunRiseSet(53, 8);
    drawMoonDate(34, 8, today);
    drawBatteryState(readVcc());
    drawSignal(5);
    //    drawVcc(0, 16, readVcc());
    display.display();
  }
  //timer----------------------

  buttonState = digitalRead(8);
  // Serial.println(buttonState);
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      buttonPushCounter++;
    }
  }
  lastButtonState = buttonState;
  if (buttonPushCounter % 2 == 0) {
    digitalWrite(2, HIGH);
  } else {
    digitalWrite(2, LOW);
  }
}
//=====================================================================================

Автор: dmizh

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js