
منذ عام ونصف العام ، اشتريت زوجًا من شاشات E-Ink مع eBay استنادًا إلى برنامج تشغيل SSD1606 ، فقط لمحطة الطقس. وقبل 4 أشهر ، قبل العام الجديد ، ظهر.
سأقول على الفور أنه لا توجد ساعة فيه ، حيث توجد ساعات في المنزل حرفيا في كل مكان! لكنه يعرف كيف يظهر ما يلي:
- درجة الحرارة الحالية في مئوية.
- الرطوبة الحالية في المئة ؛
- الضغط الحالي بالملليمتر زئبقي ؛
- تاريخ الضغط لآخر 15 ساعة في الرسم البياني ؛
- جهد البطارية.
في الواقع هذا كل شيء. الحد الأدنى الضروري والبساطة النهائية!
حتى لا يوجد مثل واجهة المستخدم الرسومية هذه مبدأ العمل
يجب على وحدة التحكم ، بلمسة زر واحدة ، عرض المعلومات ذات الصلة على الشاشة. في معظم الأحيان ينام جهاز التحكم ، كما هو الحال مع الشاشة ، وهي في حالة نوم عميق.
تستيقظ وحدة التحكم بشكل دوري مع watchDog وتتخذ قياس الضغط كل 5 دقائق لإنشاء رسم بياني لتغييرات الضغط.
اتضح أنه مثير للاهتمام للغاية مع الجدول الزمني ، حيث يمكن أن يتغير الضغط بسرعة كبيرة وبقوة (الطقس في المدينة الشمالية بشكل عام لا يمكن التنبؤ به) ، ثم في مرحلة ما قد يخرج الرسم البياني عن النطاق. للقيام بذلك ، مرة كل بضع ساعات ، يتم إعادة معايرة نقطة الوسط للقياسات (يمكن أن يزداد الضغط لأعلى ولأسفل). ومع ذلك ، وبسبب هذا ، فإن الاختلاف الواضح بين القيم السابقة يبسط قراءة الرسم البياني (مثال على CPDV).
حديد
الدماغ الرئيسي هو متحكم ATMega328P ،
ويستخدم BME280 كمقياس البارومتر بالكامل ،
وللشاشة هو الحبر الإلكتروني للمراجعة الثانية استنادًا إلى SSD1606 من Smart-Prototyping ، والتي تم وصفها سابقًا.
هذه هي تقريبًا نفس شاشة WaveShare epaper 2.7 "، أقدم فقط (أوراق البيانات متشابهة جدًا معها).
كل هذا يعمل على بطارية من طائرة هليكوبتر
لعبة 120 مللي أمبير. يتم شحن البطارية باستخدام وحدة مع حماية ضد التفريغ العميق والشحن الزائد بناءً على TP4056 مع المقاوم 47 كيلو أوم المركب للشحن بتيار يبلغ حوالي 20 مللي أمبير.
تحسين الطاقة
النوم السليم والصحي هو كل شيء لدينا! لذلك ، تحتاج إلى النوم إلى أقصى حد!
نظرًا لعدم وجود برنامج للعمل مع الشاشة ، فقط مثال أساسي للتعليمات البرمجية مع التعليقات باللغة السماوية وورقة البيانات (ظهرت الشاشة قبل عام ونصف فقط) ، كان علي القيام بكل شيء بنفسي ، حيث كان لدي بالفعل تجربة مع شاشات مختلفة.
تم العثور على وضع DeepSleep في ورقة البيانات ، حيث لا تستهلك الشاشة أي شيء على الإطلاق - 1.6mkA!
يحتوي البارومتر على وضع قياس عند الطلب (ويعرف أيضًا باسم وضع الاستعداد) ، حيث يستهلك المستشعر الحد الأدنى من الطاقة ، مع توفير الدقة الكافية للإشارة البسيطة إلى التغييرات (تشير ورقة البيانات إلى أنه مخصص لمحطات الطقس فقط). أعطى إدراج هذا الوضع استهلاك 6.2 μA. بالإضافة إلى ذلك ، في الوحدة ، تم لحام منظم LDO من LM6206N3 (أو ربما XC6206 ، كلاهما متخفين في شكل 662k) على MCP1700.

أعطى هذا مكاسب أكثر من 2 μA.
نظرًا لأنه من الضروري تحقيق الحد الأدنى من استهلاك الطاقة ، تم استخدام مكتبة LowPower. لديها عمل ملائم مع WatchDog ، على أساسه حلم Atmega. ومع ذلك ، في حد ذاته يستهلك حوالي 4 μA. أرى حلاً لهذه المشكلة باستخدام جهاز توقيت خارجي يعتمد على Texas Instruments TPL5010 أو ما شابه.
أيضًا ، لتقليل استهلاك الطاقة ، كان من الضروري وميض atme بتات FUSE الأخرى ومحمل إقلاع ، والذي تم بنجاح باستخدام USBasp ، وتمت إضافته إلى ملف boards.txt
النص التالي:## Arduino Pro or Pro Mini (1.8V, 1 MHz Int.) w/ ATmega328p
## internal osc div8, also now watchdog, no LED on boot
## bootloader size: 402 bytes
## http://homes-smart.ru/index.php/oborudovanie/arduino/avr-zagruzchik
## http://homes-smart.ru/fusecalc/?prog=avrstudio&part=ATmega328P
## http://www.engbedded.com/fusecalc
## -------------------------------------------------
pro.menu.cpu.1MHzIntatmega328=ATmega328 (1.8V, 1 MHz Int., BOD off)
pro.menu.cpu.1MHzIntatmega328.upload.maximum_size=32256
pro.menu.cpu.1MHzIntatmega328.upload.maximum_data_size=2048
pro.menu.cpu.1MHzIntatmega328.upload.speed=9600
pro.menu.cpu.1MHzIntatmega328.bootloader.low_fuses=0x62
pro.menu.cpu.1MHzIntatmega328.bootloader.high_fuses=0xD6
pro.menu.cpu.1MHzIntatmega328.bootloader.extended_fuses=0x07
pro.menu.cpu.1MHzIntatmega328.bootloader.file=atmega/a328p_1MHz_62_d6_5.hex
pro.menu.cpu.1MHzIntatmega328.build.mcu=atmega328p
pro.menu.cpu.1MHzIntatmega328.build.f_cpu=1000000L
ضع أيضًا أداة تحميل التشغيل المترجمة من optiboot في مجلد "bootloaders / atmega /":
a328p_1MHz_62_d6_5.hex:107E0000F894112484B714BE81FFDDD082E0809302
:107E1000C00088E18093C10086E08093C2008CE0BE
:107E20008093C4008EE0B9D0CC24DD2488248394D0
:107E3000B5E0AB2EA1E19A2EF3E0BF2EA2D08134A3
:107E400061F49FD0082FAFD0023811F0013811F43F
:107E500084E001C083E08DD089C0823411F484E1D4
:107E600003C0853419F485E0A6D080C0853579F447
:107E700088D0E82EFF2485D0082F10E0102F00278F
:107E80000E291F29000F111F8ED068016FC0863583
:107E900021F484E090D080E0DECF843609F040C049
:107EA00070D06FD0082F6DD080E0C81680E7D8065C
:107EB00018F4F601B7BEE895C0E0D1E062D089932E
:107EC0000C17E1F7F0E0CF16F0E7DF0618F0F60147
:107ED000B7BEE89568D007B600FCFDCFA601A0E0CC
:107EE000B1E02C9130E011968C91119790E0982F91
:107EF0008827822B932B1296FA010C0187BEE895F6
:107F000011244E5F5F4FF1E0A038BF0751F7F60133
:107F1000A7BEE89507B600FCFDCF97BEE89526C042
:107F20008437B1F42ED02DD0F82E2BD03CD0F601D2
:107F3000EF2C8F010F5F1F4F84911BD0EA94F80143
:107F4000C1F70894C11CD11CFA94CF0CD11C0EC0EF
:107F5000853739F428D08EE10CD085E90AD08FE03E
:107F60007ACF813511F488E018D01DD080E101D09E
:107F700065CF982F8091C00085FFFCCF9093C600FD
:107F800008958091C00087FFFCCF8091C00084FDE0
:107F900001C0A8958091C6000895E0E6F0E098E160
:107FA000908380830895EDDF803219F088E0F5DF5B
:107FB000FFCF84E1DECF1F93182FE3DF1150E9F7E5
:107FC000F2DF1F91089580E0E8DFEE27FF27099494
:0400000300007E007B
:00000001FF
في الواقع ، كما توقعت على الأرجح ، تم إجراء كل هذا على أساس Arduino ، أي pro mini عند 8MHz 3.3V. تم لحام منظم mic5203 LDO من هذه اللوحة (شره للغاية في التيارات المنخفضة) وتم لحام المقاوم LED للإشارة إلى الطاقة.
ونتيجة لذلك ، كان من الممكن تحقيق استهلاك طاقة يبلغ 10 μAh في وضع السكون ، مما يمنح حوالي 462.96 يومًا من التشغيل. من هذا الرقم ، يمكنك طرح الثلث بأمان ، وبالتالي الحصول على حوالي 10 أشهر ، والتي تتوافق حتى الآن مع الواقع.
لقد اختبرت الإصدار على ionistors ، بسعة نهائية 3 mAh ، ولا تدوم أكثر من 6 أيام (تفريغ ذاتي عالي). تم حساب سعة السعة الأيونية وفقًا للصيغة C * V / 3.6 = X mAh. أعتقد أن الإصدار مع البطارية الشمسية و MSP430 سيكون أبديًا بشكل عام.
إعلانات:#include <SPI.h>
#include <Wire.h>
#include <ssd1606.h>
#include <Adafruit_BME280.h>
//#include <BME280_2.h> // local optimisation
#include <LowPower.h>
#include <avr/sleep.h>
#include <avr/power.h>
#define TIME_X_POS 0
#define TIME_Y_POS 12
#define DATE_X_POS 2
#define DATE_Y_POS 9
#define WEECK_X_POS 65
#define WEECK_Y_POS 9
// ====================================== //
#define TEMP_X_POS 105
#define TEMP_Y_POS 15
#define PRESURE_X_POS 105
#define PRESURE_Y_POS 12
#define HUMIDITY_X_POS 105
#define HUMIDITY_Y_POS 9
// ====================================== //
#define BATT_X_POS 65
#define BATT_Y_POS 15
#define ONE_PASCAL 133.322
// ==== for presure history in graph ==== //
#define MAX_MESURES 171
#define BAR_GRAPH_X_POS 0
#define BAR_GRAPH_Y_POS 0
#define PRESURE_PRECISION_RANGE 4.0 // -/+ 4 mm
#define PRESURE_GRAPH_MIN 30 // vertical line graph for every N minutes
#define PRESURE_PRECISION_VAL 10 // max val 100
#define PRESURE_CONST_VALUE 700.0 // const val what unneed in graph calculations
#define PRESURE_ERROR -1000 // calibrated value
// ====================================== //
#define VCC_CALIBRATED_VAL 0.027085714285714 // == 3.792 V / 140 (real / mesured)
//#define VCC_CALIBRATED_VAL 0.024975369458128 // == 5.070 V / 203 (real / mesured)
#define VCC_MIN_VALUE 2.95 // min value to refresh screen
#define CALIBRATE_VCC 1 // need for battery mesure calibration
// 37 ~296 sec or 5 min * MAX_MESURES = 14,33(3) hours for full screen
#define SLEEP_SIZE 37
#ifdef BME280_ADDRESS
#undef BME280_ADDRESS
#define BME280_ADDRESS 0x76
#endif
#define ISR_PIN 3 // other mega328-based 2, 3
#define POWER_OFF_PIN 4 // also DONEPIN
#define E_CS 6 // CS ~ D6
#define E_DC 5 // D/C ~ D5
#define E_BSY 7 // BUSY ~ D7
#define E_RST 2 // RST ~ D2
#define E_BS 8 // BS ~ D8
/*
MOSI ~ D11
MISO ~ D12
CLK ~ D13
*/
EPD_SSD1606 Eink(E_CS, E_DC, E_BSY, E_RST);
Adafruit_BME280 bme;
volatile bool adcDone;
bool updateSreen = true;
bool normalWakeup = false;
float battVal =0;
uint8_t battValcV =0;
uint8_t timeToSleep = 0;
float presure =0;
float temperature =0;
float humidity =0;
float presure_mmHg =0;
unsigned long presureMin =0;
unsigned long presureMax =0;
uint8_t currentMesure = MAX_MESURES;
uint8_t presureValHistoryArr[MAX_MESURES] = {0};
typedef struct {
uint8_t *pData;
uint8_t pos;
uint8_t size;
unsigned long valMax;
unsigned long valMin;
} history_t;
التهيئة:void setup()
{
saveExtraPower();
Eink.begin();
initBME();
// https://www.arduino.cc/en/Reference/attachInterrupt
pinMode(ISR_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ISR_PIN), ISRwakeupPin, RISING);
//drawDefaultGUI();
drawDefaultScreen();
// tiiiiny fix....
checkBME280();
updatePresureHistory();
}
void saveExtraPower(void)
{
power_timer1_disable();
power_timer2_disable();
// Disable digital input buffers:
DIDR0 = 0x3F; // on ADC0-ADC5 pins
DIDR1 = (1 << AIN1D) | (1 << AIN0D); // on AIN1/0
}
void initBME(void)
{
bme.begin(BME280_ADDRESS); // I2C addr
LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF); // wait for chip to wake up.
while(bme.isReadingCalibration()) { // if chip is still reading calibration, delay
LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF);
}
bme.readCoefficients();
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperature
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF);
}
الكود الرئيسي:void loop()
{
for(;;) { // i hate func jumps when it's unneed!
checkVCC();
if(normalWakeup) {
checkBME280();
updatePresureHistory();
} else {
normalWakeup = true;
}
updateEinkData();
enterSleep();
}
}
// func to exec in pin ISR
void ISRwakeupPin(void)
{
// Keep this as short as possible. Possibly avoid using function calls
normalWakeup = false;
updateSreen = true;
timeToSleep = 1;
}
ISR(ADC_vect)
{
adcDone = true;
}
void debounceFix(void)
{
normalWakeup = true;
updateSreen = false;
}
//https://github.com/jcw/jeelib/blob/master/examples/Ports/bandgap/bandgap.ino
uint8_t vccRead(void)
{
uint8_t count = 4;
set_sleep_mode(SLEEP_MODE_ADC);
ADMUX = bit(REFS0) | 14; // use VCC and internal bandgap
bitSet(ADCSRA, ADIE);
do {
adcDone = false;
while(!adcDone) sleep_mode();
} while (--count);
bitClear(ADCSRA, ADIE);
// convert ADC readings to fit in one byte, ie 20 mV steps:
// 1.0V = 0, 1.8V = 40, 3.3V = 115, 5.0V = 200, 6.0V = 250
return (55U * 1023U) / (ADC + 1) - 50;
}
unsigned long getHiPrecision(double number)
{
// what if presure will be more 800 or less 700? ...
number -= PRESURE_CONST_VALUE; // remove constant value
number *= PRESURE_PRECISION_VAL; // increase precision by PRESURE_PRECISION_VAL
return (unsigned long)number; // Extract the integer part of the number
}
void checkVCC(void)
{
// reconstruct human readable value
battValcV = vccRead();
battVal = battValcV * VCC_CALIBRATED_VAL;
if(battVal <= VCC_MIN_VALUE) { // not enought power to drive E-Ink or work propetly
detachInterrupt(digitalPinToInterrupt(ISR_PIN));
// to prevent full discharge: just sleep
bme.setSampling(Adafruit_BME280::MODE_SLEEP);
LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
Eink.sleep(true);
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}
}
void checkBME280(void)
{
bme.takeForcedMeasurement(); // wakeup, make new mesure and sleep
temperature = bme.readTemperature();
humidity = bme.readHumidity();
presure = bme.readPressure();
}
void updatePresureHistory(void)
{
// convert Pa to mmHg; 1 mmHg == 133.322 Pa
presure_mmHg = (presure + PRESURE_ERROR)/ONE_PASCAL;
// === calc presure history in graph === //
if((++currentMesure) >= (MAX_MESURES/3)) { // each 4,75 hours
currentMesure =0;
presureMin = getHiPrecision(presure_mmHg - PRESURE_PRECISION_RANGE);
presureMax = getHiPrecision(presure_mmHg + PRESURE_PRECISION_RANGE);
}
// 36 == 4 pixels in sector * 9 sectors
presureValHistoryArr[MAX_MESURES-1] = map(getHiPrecision(presure_mmHg), presureMin, presureMax, 0, 35);
for(uint8_t i=0; i < MAX_MESURES; i++) {
presureValHistoryArr[i] = presureValHistoryArr[i+1];
}
}
void updateEinkData(void)
{
if(updateSreen) {
updateSreen = false;
Eink.sleep(false);
// bar history
Eink.fillRect(BAR_GRAPH_X_POS, BAR_GRAPH_Y_POS, MAX_MESURES, 9, COLOR_WHITE);
for(uint8_t i=1; i <= (MAX_MESURES/PRESURE_GRAPH_MIN); i++) {
Eink.drawVLine(BAR_GRAPH_X_POS+i*PRESURE_GRAPH_MIN, BAR_GRAPH_Y_POS, 35, COLOR_DARKGREY);
}
for(uint8_t i=0; i <= MAX_MESURES; i++) {
Eink.drawPixel(i, BAR_GRAPH_Y_POS+presureValHistoryArr[i], COLOR_BLACK);
}
#if CALIBRATE_VCC
Eink.setCursor(BATT_X_POS, BATT_Y_POS);
Eink.print(battVal);
Eink.setCursor(BATT_X_POS, BATT_Y_POS-3);
Eink.print(battValcV);
#endif
Eink.setCursor(TEMP_X_POS, TEMP_Y_POS);
Eink.print(temperature);
Eink.setCursor(PRESURE_X_POS, PRESURE_Y_POS);
Eink.print(presure_mmHg);
Eink.setCursor(HUMIDITY_X_POS, HUMIDITY_Y_POS);
Eink.print(humidity);
updateEinkSreen();
Eink.sleep(true);
}
}
void updateEinkSreen(void)
{
Eink.display(); // update Eink RAM to screen
LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF);
Eink.closeChargePump();
// as Eink display acts not like in DS, then just sleep for 2 seconds
LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
}
void effectiveIdle(void)
{
LowPower.idle(SLEEP_30MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF);
}
void drawDefaultScreen(void)
{
Eink.fillScreen(COLOR_WHITE);
Eink.printAt(TEMP_X_POS, TEMP_Y_POS, F("00.00 C"));
Eink.printAt(PRESURE_X_POS, PRESURE_Y_POS, F("000.00 mm"));
Eink.printAt(HUMIDITY_X_POS, HUMIDITY_Y_POS, F("00.00 %"));
#if CALIBRATE_VCC
Eink.printAt(BATT_X_POS, BATT_Y_POS, F("0.00V"));
// just show speed in some kart racing game in mushr... kingdom \(^_^ )/
Eink.printAt(BATT_X_POS, BATT_Y_POS-3, F("000cc"));
#endif
}
void drawDefaultGUI(void)
{
Eink.drawHLine(0, 60, 171, COLOR_BLACK); // split 2 areas
// draw window
Eink.drawRect(0, 0, 171, 71, COLOR_BLACK);
// frame for text
Eink.drawRect(BATT_X_POS, BATT_Y_POS, 102, 32, COLOR_BLACK);
}
void snooze(void)
{
do {
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
} while(--timeToSleep);
}
void disablePower(void)
{
digitalWrite(POWER_OFF_PIN, HIGH);
delay(1);
digitalWrite(POWER_OFF_PIN, LOW);
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
}
void enterSleep(void)
{
// wakeup after ISR signal;
timeToSleep = SLEEP_SIZE;
debounceFix();
snooze();
}
الإسكان
نظرًا لعدم وجود طابعة ثلاثية الأبعاد ، ولكن لدي قلم 3D MyRiwell RP800A. اتضح أنه لم يكن من السهل إنشاء هياكل مستوية وحتى. تم رسم كل شيء باستخدام بلاستيك PLA ، والذي كان في ذلك الوقت ، لذلك ظهرت العلبة متعددة الألوان ، والتي بالإضافة إلى ذلك تعطي سحرًا معينًا (ثم سأعيد صنعه تحت الشجرة عندما يصل البلاستيك مع رقائق الخشب).
تم رسم الأجزاء الأولى مباشرة على الورق ، ثم تم قطعها. هذا يترك علامات على البلاستيك. علاوة على ذلك ، كانت التفاصيل ملتوية ويجب تعديلها بطريقة أو بأخرى!

وتبين أن الحل بسيط عادي - رسم على الزجاج ، ووضع "رسومات" للعناصر الضرورية للحالة تحته.
وإليك ما حدث:

يجب أن يكون زر تحديث الشاشة أحمر على خلفية بيضاء!

يتكون الجدار الخلفي من أبسط نمط ، وبالتالي خلق فتحات تهوية.

تم تثبيت الزر على دعامة أفقية في الداخل (باللون الأصفر) بنفس المقبض.

يتم أخذ الزر نفسه من علبة كمبيوتر قديمة (لديه صوت جميل).

في الداخل ، يتم إصلاح كل شيء بمادة لاصقة تذوب الساخنة والبلاستيك ، بحيث لا يكون من السهل تفكيكه.

بالطبع ، يتم ترك موصل شحن وتحديث البرامج الثابتة. لسوء الحظ ، كان يجب جعل القضية متجانسة لمزيد من القوة.
الخلاصة
مرت 4 أشهر ، وبعد عدم الشحن الكامل (حتى 4 فولت) ، انخفض الجهد على البطارية إلى 3.58 فولت فقط ، مما يضمن عمر خدمة أطول حتى الشحن التالي.
اعتاد العاملون في المنزل على هذه الوسيلة في حالة الصداع أو إذا كنت بحاجة إلى معرفة توقعات الطقس الدقيقة للساعة أو ساعتين التاليتين ، فانتقل إليها على الفور وشاهد ما حدث مع الضغط. على KPDV ، على سبيل المثال ، يظهر انخفاض ضغط قوي ، ونتيجة لذلك ، بدأ تساقط الثلوج والرياح الغزيرة في الانخفاض.
روابط للمستودعات:
→
مكتبة الشاشة→
مكتبة ل LowPower→
مكتبة BME280محدث:
بسبب الاهتمام المتزايد بالجسم ، نشر المزيد من الصور. شاشة النماذج الأولية الذكية للمراجعة الثانية. التناظرية له على علي
هنا .
ملحوظة: تم صنع CPDV في المساء ، نتيجة تساقط ثلوج كبيرة جدًا في سانت بطرسبرغ الليلة.
لم يُعرف الشريط الأزرق PPS بإضافته إلى المسح.