Kotak Cuaca Bodoh dengan E-Ink



Sudah satu setengah tahun yang lalu, saya membeli sepasang layar E-Ink dengan eBay berdasarkan driver SSD1606, hanya untuk stasiun cuaca. Dan 4 bulan yang lalu, sebelum tahun baru, dia muncul.

Saya akan segera mengatakan bahwa tidak ada arloji di dalamnya, karena ada jam tangan di rumah secara harfiah di mana-mana! Tapi dia tahu bagaimana menunjukkan hal berikut:

  • suhu saat ini dalam Celcius;
  • kelembaban saat ini dalam persen;
  • tekanan saat ini dalam mmHg;
  • sejarah tekanan selama 15 jam terakhir dalam grafik;
  • tegangan baterai.

Sebenarnya itu saja. Diperlukan kesederhanaan minimum dan ultimate!

Bahkan tidak ada GUI seperti itu


Prinsip kerja


Pengontrol harus, dengan satu sentuhan tombol, menampilkan informasi yang relevan di layar. Sebagian besar waktu pengontrol tidur, seperti halnya layar, yang dalam tidur nyenyak.

Pengontrol secara berkala bangun dengan watchDog dan melakukan pengukuran tekanan setiap 5 menit untuk membuat grafik perubahan tekanan.

Ternyata sangat menarik dengan jadwal, karena tekanan dapat berubah dengan sangat cepat dan kuat (cuaca di kota utara umumnya tidak dapat diprediksi), maka pada titik tertentu grafik mungkin keluar skala. Untuk ini, sekali beberapa jam, titik tengah pengukuran dikalibrasi ulang (tekanan bisa naik dan turun). Namun, karena ini, perbedaan yang jelas antara nilai-nilai sebelumnya menyederhanakan pembacaan grafik (contoh pada CPDV).

Besi


Otak utama adalah mikrokontroler ATMega328P, BME280 digunakan sebagai meteran seluruh barometer, dan untuk layar adalah E-Ink dari revisi kedua berdasarkan SSD1606 dari Smart-Prototyping, yang telah dijelaskan sebelumnya.

Ini hampir layar yang sama dengan epaper WaveShare 2.7 ", hanya lebih tua (lembar data sangat mirip dengan mereka).

Semua ini bekerja pada baterai dari helikopter mainan 120 mAh. Baterai diisi menggunakan modul dengan perlindungan terhadap pengosongan dalam dan pengisian berlebih berdasarkan TP4056 dengan resistor 47 kΩ dipasang untuk pengisian daya dengan arus sekitar 20 mA.

Optimalisasi daya


Tidur yang nyenyak dan sehat adalah segalanya bagi kami! Karena itu, Anda perlu tidur dengan maksimal!

Karena tidak ada perangkat lunak untuk bekerja dengan layar, hanya contoh dasar kode dengan komentar dalam bahasa langit dan lembar data (layar hanya muncul satu setengah tahun yang lalu), sebagian besar semuanya harus dilakukan sendiri, karena saya sudah memiliki pengalaman bekerja dengan layar yang berbeda.

Mode DeepSleep ditemukan di lembar data, di dalamnya layar tidak mengkonsumsi sama sekali - 1.6mkA!

Barometer memiliki mode pengukuran sesuai permintaan (alias siaga), di dalamnya sensor mengkonsumsi energi minimum, sambil memberikan akurasi yang cukup untuk indikasi perubahan sederhana (lembar data menunjukkan bahwa itu hanya untuk stasiun cuaca). Dimasukkannya mode ini memberi konsumsi 6,2 μA. Selanjutnya pada modul, regulator LDO disolder dari LM6206N3 (atau mungkin XC6206, keduanya disamarkan sebagai 662k) pada MCP1700.



Ini memberi keuntungan 2 μA lebih.

Karena itu perlu untuk mencapai konsumsi daya minimum, perpustakaan LowPower digunakan. Ini memiliki pekerjaan yang nyaman dengan watchDog, atas dasar di mana mimpi atmega dibuat. Namun, dengan sendirinya mengkonsumsi sekitar 4 μA. Saya melihat solusi untuk masalah ini menggunakan timer eksternal berbasis Texas Instruments TPL5010 atau serupa.

Juga, untuk mengurangi konsumsi daya, perlu mem-flash atme dengan bit FUSE lain dan bootloader, yang berhasil dilakukan dengan USBasp, dan ditambahkan ke file boards.txt

Teks berikut:
## 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


Juga masukkan bootloader yang dikompilasi dari optiboot ke folder "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


Sebenarnya, seperti yang Anda duga, semua ini dilakukan atas dasar Arduino, yaitu pro mini pada 8MHz 3.3V. Regulator LDO mic5203 disolder dari papan ini (terlalu rakus pada arus rendah) dan resistor LED disolder untuk menunjukkan daya.

Sebagai hasilnya, adalah mungkin untuk mencapai konsumsi energi 10 μAh dalam mode tidur, yang memberikan sekitar 462,96 hari operasi. Dari angka ini Anda dapat dengan aman mengurangi sepertiga, sehingga memperoleh sekitar 10 bulan, yang sejauh ini sesuai dengan kenyataan.

Saya menguji versi pada ionistors, dengan kapasitas akhir 3 mAh, itu berlangsung tidak lebih dari 6 hari (self-discharge tinggi). Perhitungan kapasitansi ionistor dilakukan sesuai dengan rumus C * V / 3,6 = X mAh. Saya berpikir bahwa versi dengan baterai surya dan MSP430 umumnya akan abadi.

Pengumuman:
#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;


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


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


Perumahan


Karena saya tidak punya printer 3D, tapi saya punya pena 3D MyRiwell RP800A. Ternyata tidak mudah membuat planar dan bahkan struktur. Semuanya digambar dengan plastik PLA, yang pada waktu itu, jadi kasing keluar multi-warna, yang selain memberikan pesona tertentu (maka saya akan membuat kembali di bawah pohon ketika plastik dengan serpihan kayu tiba).

Bagian pertama diambil langsung di atas kertas, dan kemudian lepas. Ini meninggalkan bekas pada plastik. Selain itu, detailnya bengkok dan mereka harus diluruskan entah bagaimana!



Solusinya ternyata sederhana dangkal - menggambar di atas kaca, dan meletakkan "gambar" dari elemen yang diperlukan dari kasus di bawahnya.

Dan inilah yang terjadi:



Tombol refresh layar hanya harus merah pada latar belakang putih!



Dinding belakang dibuat dengan pola paling sederhana, sehingga menciptakan lubang ventilasi.



Tombol itu diperbaiki pada penyangga horizontal di dalam (berwarna kuning) dengan pegangan yang sama.



Tombol itu sendiri diambil dari casing komputer lama (memiliki suara yang bagus).



Di dalam, semuanya dipasang dengan perekat panas meleleh dan plastik, sehingga tidak mudah dibongkar.



Tentu saja, konektor untuk mengisi daya dan memperbarui firmware tertinggal. Kasingnya, sayangnya, harus dibuat monolitik untuk kekuatan yang lebih besar.

Kesimpulan


4 bulan berlalu, dan setelah tidak mengisi penuh (hingga 4V), tegangan pada baterai turun menjadi hanya 3,58V, yang menjamin masa pakai yang lebih lama hingga pengisian berikutnya.

Pekerja rumahan sangat terbiasa dengan alat ini dalam kasus sakit kepala atau jika Anda perlu mengetahui ramalan cuaca yang tepat untuk satu atau dua jam berikutnya, maka segera pergi kepadanya dan lihat apa yang terjadi dengan tekanan. Di KPDV, misalnya, penurunan tekanan yang kuat terlihat, sebagai akibatnya, salju tebal dan angin mulai turun.

Tautan ke repositori:

perpustakaan layar
perpustakaan untuk daya rendah
perpustakaan untuk BME280

Diperbarui:

Karena meningkatnya minat pada tubuh, ia memposting lebih banyak gambar. Layar Smart-Prototyping dari revisi kedua. Analoginya dengan Ali di sini .

Klik saya:




PS CPDV dibuat pada malam hari, sebagai akibat turunnya salju di St. Petersburg malam ini.
PPS Blue tape tidak dikenal karena menambah survei.

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


All Articles