Наверное, каждый кто начинает свое знакомство с Arduino, поморгав светодиодом и подключив кнопку, переходит к созданию своей метеостанции. Не исключением стал и я.
Ознакомившись с различными проектами в интернете, были составлены определенные требования. Устройство должно отображать время, температуру, давление и график его изменения. Также очень хотелось рассчитывать фазы луны и время восхода-захода солнца. А самым важным требованием было – мобильность. Создаваемая портативная метеостанция предполагалась использоваться в помощь для рыбалки, поэтому должна была иметь компактный размер и обладать определенной автономностью.
Создание корпуса для любой самоделки является довольно сложной задачей. В закромах был найден мобильный телефон Motorola T192.
Оказалось, что в него отлично становится экран от Nokia 3310 (конечно использовать саму Nokia 3310, возможно, было бы удобнее).
Управлять всем было поручено Arduino Pro Mini.
Чтобы не проваливались кнопки, была вставлена макетная плата. При переклеивании клавиатуры, было замечено, что шаг отверстий макетки почти совпал с кнопками. В дальнейшем это позволило использовать кнопки.
Для измерения давления применен датчик bmp180. В качестве часов был взят модуль ds1302, но для экономии места с него была взята только микросхема. Кварц был выпаян из материнской платы, а батарейку вынул с ноутбука (конечно же, можно и обычную cr2032 в термокембрике).
Так как имелись свободные выходы, то подсветку дисплея повесил на один из свободных и кнопку на второй.
Для зарядки литиевого аккумулятора был использован модуль заряда Li-ion на TP4056.
Итоговый вид устройства (провода, торчащие справа, для прошивки, поэтому вскоре будут убраны.)
При написании программы использовались обычные средства. Единственное хотелось бы обратить внимание на библиотеку TimeLord.h (https://github.com/probonopd/TimeLord). С помощью ее функций, указав дату, координаты и часовой пояс можно определить время восхода-захода солна, фазу луны. Описание можно скачать по ссылке: TimeLord.pdf.
Касаемо времени автономной работы, то непродолжительное тестирование показало, что неделю устройство проработает точно. Этот результат вполне устраивает, поэтому дальнейшие эксперименты по энергосбережению пока отложены.
Скетч на данный момент использует 81% памяти устройства, поэтому есть возможность добавить что-нибудь еще.
На экране отображается антенна (индикатор уровня сигнала). Это оставлен задел для тестирования радио модуля на 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