Ознакомившись с различными проектами в интернете, были составлены определенные требования. Устройство должно отображать время, температуру, давление и график его изменения. Также очень хотелось рассчитывать фазы луны и время восхода-захода солнца. А самым важным требованием было – мобильность. Создаваемая портативная метеостанция предполагалась использоваться в помощь для рыбалки, поэтому должна была иметь компактный размер и обладать определенной автономностью.
Создание корпуса для любой самоделки является довольно сложной задачей. В закромах был найден мобильный телефон 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);
}
}
//=====================================================================================
Комментарии (26)
avs24rus
13.04.2016 12:07Вместо про мини, на макетной плате можно было разместить ATmega328 в DIP, 3-4 кондера и кварц.
ABy
13.04.2016 13:58В ATmega328 есть же встроенный кварц. Правда он менее точен, чем внешний. Може кто подскажет, для каких задач его может не хватить? Скажем генерация ШИМ и подключение по UART или SIP?
avs24rus
13.04.2016 14:01+1Кварца там нет, есть задающая RC-цепочка.
ABy
13.04.2016 14:19Да, погорячился. Подчерпнул знания из этой статьи:
http://mysku.ru/blog/aliexpress/22070.html
Все-таки хотелось бы знать, когда можно применять встроенный генератор, а когда без внешнего кварца не обойтись. Или лучше экспериментально проверить?ElectricFromUfa
13.04.2016 15:11+1Применять встроенный генератор можно всегда, когда вас устраивает его НЕ стабильность в условиях работы вашего устройства (в документации есть графики зависимости его частоты от температуры и напряжения питания). А ещё есть разбег от экземпляра к экземпляру и есть процедура калибровки этого генератора.
Но есть и плюсы. Например, от встроенного генератора микроконтроллер стартует быстрее, чем от внешнего кварца (его ещё надо раскачать)
Alexeyslav
13.04.2016 15:13Когда нужны стабильные и точные временные интервалы в широком диапазоне температур — использовать только кварц. Встроенный генератор, он хоть и откалиброван но без температурной компенсации(большинство контроллеров) не буде обеспечивать стабильность частоты, и она порой будет гулять +-20% — если устроит такой разгул(для асинхронного UART максимальная ошибка должна быть не более +-2%) тогда МОЖНО использовать встроенный генератор.
Но сейчас как-то даже микросхемы работающие с USB умудряются обходится без кварцевой стабилизации — в основном это происходит за счет постоянной следящей подстройки «по образцу» с хоста.ABy
13.04.2016 15:36Спасибо за ответы. Вы меня разубедили использовать встроенный генератор. 20% разброс это как-то многовато, да и откалибровать все и настроить у меня опыта не хватит. Тем более про мини не намного больше места занимает чем голый контроллер в DIP корпусе (ну может раза в 2 шире).
Nataly75
15.04.2016 18:55Встроенный, безо всякой калибровки работает «как первый советский трактор», в т.ч. и с UART. Много страхов. Особенно у начинающих, наслушавшихся советов от таких же начинающих.
Alexeyslav
15.04.2016 20:10Не знаю, у меня даже с калибровкой последний бит искажается. и вообще работает нестабильно. А если взять расширенные условия эксплуатации не в комнатных условиях работы то где-то точно работать не будет.
Из 10 штук ATTINY13 из одной партии при питании от 5В разброс был -2% +5% это при том что они откалиброваны на заводе при 20 градусах и +3.3В. Если вот так взять и сделать на них 10-к устройств без дополнительной калибровки то минимум 3 из них не заработают правильно с UART, а 2 из них будут на грани. Изменится температура, и лажающих резко станет больше.Nataly75
15.04.2016 20:25Да, в широком диапазоне температур возможно, но при комнатной проблем не наблюдал. При том что постоянно использую int rc и UART. А если есть возможность калибровки, то какие вообще могут быть проблемы — путей возникновения нестабильности не видно.
Alexeyslav
16.04.2016 09:21А их всего два… напряжение питания и температура, если в даташите не заявлено что генератор термокомпенсированный — например в ATTINY85.
К тому же калибровка очень грубая — один шаг соответствует примерно 1% и его величина сильно зависит от величины калибровки, к тому же меняется не монотонно а пилами. Калибровать можно, но есть проблема — новую калибровочную константу должна использовать прошивка, т.е. это должно быть предусмотрено специально. Для одного экземпляра это не сложно, десяток уже напрягает а когда речь пойдёт о сотне то всё больше посещает мысль о том что дешевле было использовать кварцевый резонатор чем решать эти проблемы. Можно не кварц использовать а пьезокерамику, дешевле но стабильность на уровне всего 3-х знаков.Nataly75
16.04.2016 09:46Ну естественно, тут уже самому нужно решать что проще, что менее затратно/хлопотно. И естественно нужно учитывать область применения. Но говорить однозначно о том что встроенный генератор для УАРТ использовать нельзя, я бы не стал. Ибо практика показывает обратное, моя в т.ч.
Alexeyslav
17.04.2016 12:01Практика показывает, что работает скорей вопреки чем благодаря. Если в схеме есть асинхронный UART то обязательно должен быть кварц(или на худой конец керамический резонатор), одна деталька способна сэкономить кучу времени на поиск «неисправности» в будущем.
sav13
13.04.2016 16:06По цене DIP с кварцем и прочим дороже встанет, чем PRO MINI. Экономия в месте, если это не SMD монтаж, тоже особая не получится.
ivaneska
13.04.2016 12:32А как схемотехнически реализована цепь измерения напряжения на аккумуляторе? Сейчас занимаюсь именно данным моментом и не знаю как сделать так, чтобы через рез. делитель, на выходе которого напруга меньше 1.1В, не утекало больше 10мкА.
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 чипом, но может зависеть от изменений температуры. Не стесняйтесь экспериментировать с вашим собственным измерениям.
Вывод<geher
13.04.2016 19:57> на библиотеку TimeLord.h
А с какой точностью она считает время восхода/захода?
В свое время портировал на Atmega2560 библиотеку с диска-приложения к книге Оливера Монтенбрука и Томаса Пфлегера «Астрономия на персональном компьютере».
Так из-за того, что для контроллеров Atmega double=float, погрешность получилась до двух минут.dmizh
13.04.2016 23:32Я не знаю с какой точностью она считает. В тоже время я не знаю где вообще взять эталонные значения и есть ли они. Все равно для каждой местности все индивидуально, любая возвышенность легко изменит время. Поэтому минута-две вообще роли не играет.
Сравнил значения для моей местности с трех источников
Nataly75
15.04.2016 19:01Всё ничего, но провода у вас… Это что то. Прям, шланги какие то. Есть же МГТФ тонкий или ему подобные. Ну, просто порнография какая то, ей богу.
tw1911
15.04.2016 19:05Какое энергопотребление? Я не увидел какой либо функции отключения девайса. Было бы логично переводить его в power_down по таймауту, и потому выводить, или idle. А то мне кажется аккумулятора на долго не хватит так.
dmizh
19.04.2016 17:07Пока наблюдаю, 5 дней живет точно. Затем можно и с энергосбережением поиграться.
morber
прикольно.