Dumme Wetterbox auf E-Ink



Bereits vor anderthalb Jahren habe ich bei eBay ein Paar E-Ink-Bildschirme gekauft, die auf dem SSD1606-Treiber basieren, nur für die Wetterstation. Und vor 4 Monaten, vor dem neuen Jahr, erschien er.

Ich werde gleich sagen, dass es keine Uhr gibt, da es buchstäblich überall Uhren zu Hause gibt! Aber er weiß folgendes zu zeigen:

  • aktuelle Temperatur in Celsius;
  • aktuelle Luftfeuchtigkeit in Prozent;
  • aktueller Druck in mmHg;
  • Druckverlauf für die letzten 15 Stunden in einem Diagramm;
  • Batteriespannung.

Eigentlich ist das alles. Notwendiges Minimum und ultimative Einfachheit!

Auch gibt es keine solche GUI


Arbeitsprinzip


Der Controller sollte auf Knopfdruck relevante Informationen auf dem Bildschirm anzeigen. Die meiste Zeit schläft der Controller, ebenso wie das Display, das sich im Tiefschlaf befindet.

Der Regler wacht regelmäßig mit watchDog auf und führt alle 5 Minuten eine Druckmessung durch, um ein Diagramm der Druckänderungen zu erstellen.

Es stellte sich als sehr interessant mit dem Zeitplan heraus, da sich der Druck sehr schnell und stark ändern kann (das Wetter in der nördlichen Stadt ist im Allgemeinen unvorhersehbar), und dann kann die Grafik irgendwann vom Maßstab abweichen. Zu diesem Zweck wird alle paar Stunden der Mittelpunkt der Messungen neu kalibriert (der Druck kann sowohl nach oben als auch nach unten gehen). Aus diesem Grund vereinfacht ein deutlicher Unterschied zwischen den vorherigen Werten das Lesen des Diagramms (ein Beispiel für CPDV).

Eisen


Das Haupthirn ist der ATMega328P-Mikrocontroller, der BME280 wird als ganzes Messgerät des Barometers verwendet, und für den Bildschirm ist die E-Ink der zweiten Revision basierend auf SSD1606 von Smart-Prototyping, die bereits zuvor beschrieben wurde.

Dies ist fast der gleiche Bildschirm wie beim WaveShare-Epaper 2.7 ", nur älter (die Datenblätter sind ihnen sehr ähnlich).

All dies funktioniert mit einer Batterie eines 120-mAh- Spielzeughubschraubers . Der Akku wird über ein Modul mit Schutz gegen Tiefenentladung und Überladung auf Basis von TP4056 mit einem 47-kΩ-Widerstand zum Laden mit einem Strom von ca. 20 mA aufgeladen.

Leistungsoptimierung


Ein gesunder und gesunder Schlaf ist unser Alles! Deshalb müssen Sie maximal schlafen!

Da es keine Software für die Arbeit mit dem Bildschirm gab, sondern nur ein einfaches Beispiel für Code mit Kommentaren in der Himmelssprache und im Datenblatt (der Bildschirm erschien erst vor anderthalb Jahren), musste das meiste von mir selbst erledigt werden, da ich bereits Erfahrung mit verschiedenen Bildschirmen hatte.

Der DeepSleep-Modus wurde im Datenblatt gefunden, darin verbraucht der Bildschirm überhaupt nichts - 1,6 mkA!

Das Barometer verfügt bei Bedarf über einen Messmodus (auch als Standby bezeichnet). Der Sensor verbraucht ein Minimum an Energie und bietet gleichzeitig eine ausreichende Genauigkeit für eine einfache Anzeige von Änderungen (das Datenblatt gibt an, dass es sich nur um Wetterstationen handelt). Die Einbeziehung dieses Modus ergab einen Verbrauch von 6,2 uA. Weiter am Modul wurde der LDO-Regler vom LM6206N3 (oder vielleicht XC6206, beide als 662k getarnt) auf dem MCP1700 verlötet.



Dies ergab eine Verstärkung von 2 & mgr; A mehr.

Da ein minimaler Stromverbrauch erreicht werden muss, wurde die LowPower-Bibliothek verwendet. Es funktioniert bequem mit watchDog, auf dessen Grundlage der Traum der Atmosphäre verwirklicht wird. An sich verbraucht es jedoch etwa 4 μA. Ich sehe eine Lösung für dieses Problem mit einem externen Timer, der auf Texas Instruments TPL5010 oder ähnlichem basiert.

Um den Stromverbrauch zu senken, musste der atme mit anderen FUSE-Bits und einem Bootloader geflasht werden, was mit USBasp erfolgreich durchgeführt und der Datei board.txt hinzugefügt wurde

Der folgende Text:
## 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


Legen Sie auch den aus optiboot kompilierten Bootloader in den Ordner "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


Wie Sie wahrscheinlich vermutet haben, wurde dies alles auf der Basis von Arduino durchgeführt, nämlich Pro Mini bei 8 MHz, 3,3 V. Der LDO-Regler mic5203 wurde von dieser Platine verlötet (bei niedrigen Strömen zu gefräßig) und der LED-Widerstand wurde gelötet, um die Leistung anzuzeigen.

Infolgedessen konnte im Schlafmodus ein Energieverbrauch von 10 μAh erreicht werden, was etwa 462,96 Betriebstage ergibt. Von dieser Zahl können Sie sicher ein Drittel abziehen, wodurch Sie ungefähr 10 Monate erhalten, was bisher der Realität entspricht.

Ich habe die Version an Ionistoren getestet, mit einer Endkapazität von 3 mAh, die nicht länger als 6 Tage dauert (hohe Selbstentladung). Die Berechnung der Kapazität des Ionistors erfolgte nach der Formel C * V / 3,6 = X mAh. Ich denke, dass die Version mit der Solarbatterie und dem MSP430 im Allgemeinen ewig sein wird.

Ankündigungen:
#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;


Initialisierung:
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);
}


Hauptcode:
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();
}


Gehäuse


Da ich keinen 3D-Drucker habe, habe ich aber einen 3D-Stift MyRiwell RP800A. Es stellte sich heraus, dass es nicht so einfach war, planare und gleichmäßige Strukturen herzustellen. Alles wurde mit PLA-Kunststoff gezeichnet, was zu dieser Zeit war, so dass das Gehäuse mehrfarbig herauskam, was zusätzlich einen gewissen Charme verleiht (dann werde ich es unter dem Baum neu gestalten, wenn der Kunststoff mit Holzspänen ankommt).

Die ersten Teile wurden direkt auf Papier gezeichnet und lösten sich dann ab. Dies hinterließ Spuren auf dem Kunststoff. Außerdem waren die Details schief und mussten irgendwie korrigiert werden!



Die Lösung erwies sich als banal einfach - zeichnen Sie auf Glas und legen Sie „Zeichnungen“ der notwendigen Elemente des Gehäuses darunter.

Und hier ist was passiert:



Die Schaltfläche zum Aktualisieren des Bildschirms musste nur auf weißem Hintergrund rot sein!



Die Rückwand wird mit dem einfachsten Muster hergestellt, wodurch Belüftungslöcher entstehen.



Der Knopf wurde an einer horizontalen Strebe innen (in gelb) mit demselben Griff befestigt.



Der Knopf selbst stammt aus einem alten Computergehäuse (es hat einen schönen Klang).



Im Inneren ist alles mit Schmelzkleber und Kunststoff fixiert, so dass es nicht einfach ist, es zu zerlegen.



Natürlich bleibt der Anschluss zum Laden und Aktualisieren der Firmware übrig. Das Gehäuse musste leider für eine größere Festigkeit monolithisch gemacht werden.

Fazit


4 Monate vergingen und nach nicht vollständigem Laden (bis zu 4 V) sank die Spannung an der Batterie auf nur 3,58 V, was eine noch längere Lebensdauer bis zur nächsten Ladung garantiert.

Hausangestellte sind sehr an diese Erfindung gewöhnt, wenn Kopfschmerzen auftreten oder wenn Sie die genaue Wettervorhersage für die nächsten ein oder zwei Stunden kennen müssen, gehen Sie sofort zu ihr und sehen Sie, was mit dem Druck passiert ist. Bei KPDV ist beispielsweise ein starker Druckabfall zu beobachten, wodurch starker Schnee und Wind zu fallen begannen.

Links zu Repositories:

Bildschirmbibliothek
Bibliothek für lowPower
Bibliothek für BME280

Aktualisiert:

Aufgrund des gestiegenen Interesses am Körper veröffentlichte er mehr Bilder. Smart-Prototyping-Bildschirm der zweiten Revision. Ein Analogon zu ihm über Ali ist hier .

Klicken Sie mich an:




PS Der CPDV wurde am Abend hergestellt, da heute Abend in St. Petersburg sehr, sehr viel Schnee gefallen ist.
PPS Blue Tape wurde nicht für das Hinzufügen zur Umfrage bekannt.

Source: https://habr.com/ru/post/de410949/


All Articles