Наверное, каждый кто начинает свое знакомство с 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);
  }
}
//=====================================================================================

Комментарии (26)


  1. morber
    13.04.2016 11:54
    +4

    прикольно.


  1. avs24rus
    13.04.2016 12:07

    Вместо про мини, на макетной плате можно было разместить ATmega328 в DIP, 3-4 кондера и кварц.


    1. dmizh
      13.04.2016 12:22

      Согласен, но тогда уже разводить и травить всю плату целиком.


    1. ABy
      13.04.2016 13:58

      В ATmega328 есть же встроенный кварц. Правда он менее точен, чем внешний. Може кто подскажет, для каких задач его может не хватить? Скажем генерация ШИМ и подключение по UART или SIP?


      1. avs24rus
        13.04.2016 14:01
        +1

        Кварца там нет, есть задающая RC-цепочка.


        1. ABy
          13.04.2016 14:19

          Да, погорячился. Подчерпнул знания из этой статьи:
          http://mysku.ru/blog/aliexpress/22070.html
          Все-таки хотелось бы знать, когда можно применять встроенный генератор, а когда без внешнего кварца не обойтись. Или лучше экспериментально проверить?


          1. ElectricFromUfa
            13.04.2016 15:11
            +1

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


          1. Alexeyslav
            13.04.2016 15:13

            Когда нужны стабильные и точные временные интервалы в широком диапазоне температур — использовать только кварц. Встроенный генератор, он хоть и откалиброван но без температурной компенсации(большинство контроллеров) не буде обеспечивать стабильность частоты, и она порой будет гулять +-20% — если устроит такой разгул(для асинхронного UART максимальная ошибка должна быть не более +-2%) тогда МОЖНО использовать встроенный генератор.
            Но сейчас как-то даже микросхемы работающие с USB умудряются обходится без кварцевой стабилизации — в основном это происходит за счет постоянной следящей подстройки «по образцу» с хоста.


            1. ABy
              13.04.2016 15:36

              Спасибо за ответы. Вы меня разубедили использовать встроенный генератор. 20% разброс это как-то многовато, да и откалибровать все и настроить у меня опыта не хватит. Тем более про мини не намного больше места занимает чем голый контроллер в DIP корпусе (ну может раза в 2 шире).


              1. Nataly75
                15.04.2016 18:55

                Встроенный, безо всякой калибровки работает «как первый советский трактор», в т.ч. и с UART. Много страхов. Особенно у начинающих, наслушавшихся советов от таких же начинающих.


                1. Alexeyslav
                  15.04.2016 20:10

                  Не знаю, у меня даже с калибровкой последний бит искажается. и вообще работает нестабильно. А если взять расширенные условия эксплуатации не в комнатных условиях работы то где-то точно работать не будет.
                  Из 10 штук ATTINY13 из одной партии при питании от 5В разброс был -2% +5% это при том что они откалиброваны на заводе при 20 градусах и +3.3В. Если вот так взять и сделать на них 10-к устройств без дополнительной калибровки то минимум 3 из них не заработают правильно с UART, а 2 из них будут на грани. Изменится температура, и лажающих резко станет больше.


                  1. Nataly75
                    15.04.2016 20:25

                    Да, в широком диапазоне температур возможно, но при комнатной проблем не наблюдал. При том что постоянно использую int rc и UART. А если есть возможность калибровки, то какие вообще могут быть проблемы — путей возникновения нестабильности не видно.


                    1. Alexeyslav
                      16.04.2016 09:21

                      А их всего два… напряжение питания и температура, если в даташите не заявлено что генератор термокомпенсированный — например в ATTINY85.
                      К тому же калибровка очень грубая — один шаг соответствует примерно 1% и его величина сильно зависит от величины калибровки, к тому же меняется не монотонно а пилами. Калибровать можно, но есть проблема — новую калибровочную константу должна использовать прошивка, т.е. это должно быть предусмотрено специально. Для одного экземпляра это не сложно, десяток уже напрягает а когда речь пойдёт о сотне то всё больше посещает мысль о том что дешевле было использовать кварцевый резонатор чем решать эти проблемы. Можно не кварц использовать а пьезокерамику, дешевле но стабильность на уровне всего 3-х знаков.


                      1. Nataly75
                        16.04.2016 09:46

                        Ну естественно, тут уже самому нужно решать что проще, что менее затратно/хлопотно. И естественно нужно учитывать область применения. Но говорить однозначно о том что встроенный генератор для УАРТ использовать нельзя, я бы не стал. Ибо практика показывает обратное, моя в т.ч.


                        1. Alexeyslav
                          17.04.2016 12:01

                          Практика показывает, что работает скорей вопреки чем благодаря. Если в схеме есть асинхронный UART то обязательно должен быть кварц(или на худой конец керамический резонатор), одна деталька способна сэкономить кучу времени на поиск «неисправности» в будущем.


    1. sav13
      13.04.2016 16:06

      По цене DIP с кварцем и прочим дороже встанет, чем PRO MINI. Экономия в месте, если это не SMD монтаж, тоже особая не получится.


  1. ivaneska
    13.04.2016 12:32

    А как схемотехнически реализована цепь измерения напряжения на аккумуляторе? Сейчас занимаюсь именно данным моментом и не знаю как сделать так, чтобы через рез. делитель, на выходе которого напруга меньше 1.1В, не утекало больше 10мкА.


    1. dmizh
      13.04.2016 12:50
      +1

      Если правильно понимаю вашу задачу, то нужно идти другим путем.
      В исходнике эта функция readVcc(). Оригинальная статья недоступна, поэтому под спойлером подробнее.

      подробнее
      http://blog.unlimite.net/?p=25
      Секретный вольтметр в Arduino — измерение напряжения батареи средствами микроконтроллера
      Posted on 2014-11-03 by freeman
      В основном перевод статьи Secret Arduino Voltmeter – Measure Battery Voltage с некоторыми дополнениями.

      Мало известная фишка Ардуино и многих других AVR чипов это возможность измерить внутренний источник опорного напряжения 1.1 В. Эта функция может быть использована для повышения точности функции Arduino — analogRead () при использовании стандартного опорного напряжения 5 В (на платформах с напряжением питания 5 В) или 3.3 В (на платформах с напряжением питания 3.3 В). Она также может быть использована для измерения Vcc, поданного на чип, обеспечивая средство контроля напряжения батареи без использования драгоценных аналоговый выводов.

      Мотивация

      Есть, по крайней мере, две причины для измерения напряжения, питающего наш Arduino (Vcc). Одним из них является наш проект, питающийся от батареи, если мы хотим следить за уровнем напряжения батареи. Кроме того, когда питание от батареи (Vcc) не может быть 5,0 вольт(например питание от 3-х элементов 1.5 В), а мы хотим сделать аналоговые измерения более точными — мы должны использовать либо внутренний источник опорного напряжения 1,1 В либо внешний источник опорного напряжения. Почему?

      Обычно предполагают при использовании analogRead () то, что аналоговое напряжение питания контроллера составляет 5.0 вольт, когда в действительности это может быть совсем не так(например питание от 3-х элементов 1.5 В). Официальная документация Arduino даже может привести нас к этому неправильному предположению. Дело в том, что питание не обязательно 5,0 вольт, независимо от текущего уровня это питание подано на Vcc чипа. Если наше питание не стабилизировано или если мы работаем от аккумулятора, это напряжение может меняться совсем немного. Вот пример кода, иллюстрирующий эту проблему:

      double Vcc = 5.0; // не обязательно правда
      int value = analogRead(0); / читаем показания с А0
      double volt = (value / 1023.0) * Vcc; // верно только если Vcc = 5.0 вольт

      Для того чтобы измерить напряжение точно, необходимо точное опорное напряжение. Большинство чипов AVR обеспечивает три источника опорного напряжения:
      1,1 в от внутреннего источника, в документации он проходит как bandgap reference (некоторые из них 2,56 В, например ATMega 2560). Выбор осуществляется функцией analogReference() с параметром INTERNAL: analogReference(INTERNAL);
      внешний источник опорного наптяжения, на ардуинке подписан AREF. Выбор: analogReference(EXTERNAL);
      Vcc — источник питания самого контроллера. Выбор: analogReference(DEFAULT).
      В Arduino нельзя просто взять и подключить Vcc к аналоговому пину напрямую — по умолчанию AREF связан с Vcc и вы всегда будете получать максимальное значение 1023, от какого бы напряжения вы не питались. Спасает подключение к AREF источника напряжения с заранее известным, стабильным напряжением, но это — лишний элемент в схеме.

      Еще можно соединить Vcc с AREF через диод: падение напряжение на диоде заранее известно, поэтому вычислить Vcc не составит труда. Однако, при такой схеме через диод постоянно протекает ток, сокращая жизнь батареи, что тоже не очень удачно.

      Источник внешнего опорного напряжения является наиболее точным, но требует дополнительных аппаратных средств. Внутренний ИОН стабильным, но не точен + / — 10% отклонение. Vcc является абсолютно ненадежен в большинстве случаев. Выбор внутреннего источника опорного напряжения является недорогим и стабильным, но большую часть времени, мы хотели бы измеряет большее напряжение чем 1.1 В, так что использование Vcc является наиболее практичным, но потенциально наименее точным. В некоторых случаях оно может быть очень ненадежным!

      Как это сделать

      Многие чипы AVR включая серию ATmega и ATtiny обеспечивают средства для измерения внутреннего опорного напряжения. Зачем это нужно? Причина проста — путем измерения внутреннего напряжения, мы можем определить значение Vcc. Вот как:

      Установить источник опорного напряжения по умолчанию: analogReference(DEFAULT);. Используем как источник — Vcc.
      Снять показания АЦП для внутреннего источника 1.1 В.
      Расчитать значение Vcc основываясь на измерении 1.1 В по формуле:
      Vcc * (Показания АЦП) / 1023 = 1.1 В

      Из чего следует:

      Vcc = 1,1 В * 1023 / (Показания АЦП)

      Собираем все вместе и получаем код:

      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;

      result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
      return result; // Vcc in millivolts
      }
      Использование
      Проверка напряжения Vcc или батареи

      Вы можете назвать эту функцию – readVcc(), если Вы хотите мониторить Vcc. Примером может служить для проверка уровня заряда батареи. Вы также можете использовать её для определения подключены ли Вы к источнику питания или работаете от батареи.

      Измерение Vcc для опорного напряжения

      Вы также можете использовать её, чтобы получить правильное значение Vcc для использования с analogRead (), когда вы используете опорное напряжение (Vcc). Пока Вы не используете стабилизированный источник питания, Вы не можете быть уверенны, что Vcc = 5.0 вольт. Эта функция позволяет получить правильное значение. Хотя есть один нюанс….

      В одной из статей я сделал заявление, что эта функция может использоваться, чтобы улучшить точность аналоговых измерений в тех случаях, когда Vcc было не совсем 5.0 вольт. К сожалению, эта процедура не будет давать точный результат. Почему? Это зависит от точности внутреннего источника опорного напряжения. Спецификация дает номинальное напряжение 1.1 вольт, но говорится, что оно может отличаться до 10%. Такие измерения могут быть менее точными, чем наш источник питания Arduino!

      Повышаем точность

      Пока большие допуски внутреннего источника питания 1.1 В. значительно ограничивают точность измерений при использовании в серийном производстве, для индивидуальных проэктов мы можем добиться большей точности. Сделать это просто, просто измерив Vcc с помощью вольтметра и нашей функции readVcc(). Далее заменяем константу 1125300L новой переменной:

      scale_constant = internal1.1Ref * 1023 * 1000

      где

      internal1.1Ref = 1.1 * Vcc1 (показания_вольтметра) / Vcc2 (показания_функции_readVcc())

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

      Вывод<



      1. ivaneska
        13.04.2016 16:03

        Отлично! Благодарю.


  1. geher
    13.04.2016 19:57

    > на библиотеку TimeLord.h
    А с какой точностью она считает время восхода/захода?
    В свое время портировал на Atmega2560 библиотеку с диска-приложения к книге Оливера Монтенбрука и Томаса Пфлегера «Астрономия на персональном компьютере».
    Так из-за того, что для контроллеров Atmega double=float, погрешность получилась до двух минут.


    1. dmizh
      13.04.2016 23:32

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


  1. Nataly75
    15.04.2016 19:01

    Всё ничего, но провода у вас… Это что то. Прям, шланги какие то. Есть же МГТФ тонкий или ему подобные. Ну, просто порнография какая то, ей богу.


  1. tw1911
    15.04.2016 19:05

    Какое энергопотребление? Я не увидел какой либо функции отключения девайса. Было бы логично переводить его в power_down по таймауту, и потому выводить, или idle. А то мне кажется аккумулятора на долго не хватит так.


    1. dmizh
      19.04.2016 17:07

      Пока наблюдаю, 5 дней живет точно. Затем можно и с энергосбережением поиграться.


  1. tw1911
    22.04.2016 11:19

    А ток потребления какой при работе?