OLED焼き付き問題:電子ペーパーに変更

背景

  • 温湿度や大気センサーのデータをBLEで送信し、それを収集してWebサーバーへWi-Fi経由で送っています。 その際、収集したデータをOLEDに表示していましたが、約2ヶ月で画面焼けが発生しました。
  • そこで、表示画面を電子ペーパーに変更してみました。

回路図

Windows 10/11 で動作する回路図エディタである水魚堂のBSch3V(フリーソフト)を使いました。 - マイコンは、Seeed xiao ESP32C3を使いました - 技術資料 https://wiki.seeedstudio.com/ja/XIAO_ESP32C3_Getting_Started/

 - ePaperは、WeAct 1.54 インチ 1.54 インチ電子ペーパー モジュール電子ペーパー E インク E インク表示画面 SPI ブラック ホワイト  - 技術資料   https://github.com/WeActStudio/WeActStudio.EpaperModule

ハード作成

部品

  • マイコンと電子ペーパはアリエクで購入しました。アマゾンの同等品は、下記になります

  • 備品は LEDは抵抗入りにしました

リポバッテの電圧測定のため220kΩの抵抗を使いました

基板は

ソフト作成

開発環境 Arduino IDE 2.3.4 ソース

/*
  XIAO_ESP32C3_LED_Receive_Ant.ino
  Seeed XIAO BLE nRF52840から受信したBLEのアドバタイズデータを
  WiFiでWebサーバーにPHPに送信する

  ボードマネージャ
   ESP32 by Espressif Syatem: Version 2.0.17

  ライブラリーマネージャー
    GxEPD2 by Jean-Marc Zingg 1.68

  ツール
   Patiton Scheme: Huge App(3MB On OTA/1MB SPIFFS)

  PHPの位置
    /innar/temp_humi_press.php
    /outer/temp_humi_press.php
  
*/

// ------------GxEPD2------------------------------------
// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
// enable or disable GxEPD2_GFX base class
#define ENABLE_GxEPD2_GFX 0
#include <Arduino.h>

#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
#include <Fonts/FreeMonoBold12pt7b.h>
#include <Fonts/FreeMonoBold18pt7b.h>

// ESP32-C3 CS(SS)=7, SCL(SCK)=4, SDA(MOSI)=6, BUSY=3, RES(RST)=2, DC=1
// #define SDA_PIN (MOSI)
// #define CSK_PIN (SCK)
#define CS_PIN D6    //ChipSelect
#define BUSY_PIN D3  //Busy
#define RES_PIN D2   //Reset
#define DC_PIN D1    //Data/Command control

// 1.54'' EPD Module
GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(/*CS=5*/ CS_PIN, /*DC=*/DC_PIN, /*RES=*/RES_PIN, /*BUSY=*/BUSY_PIN));  // GDEH0154D67 200x200, SSD1681

// ------------WiFi------------------------------------
#include <WiFi.h>
#include "time.h"
#include "esp_sntp.h"
#include <HTTPClient.h>
const String WEB_API_URL_i = "http://192.168.10.117/innar/temp_humi_press.php";  // 送信先URL
const String WEB_API_URL_o = "http://192.168.10.117/outer/temp_humi_press.php";  // 送信先URL

const char* ssid = "????????????????";
const char* password = "????????????????";
const char* ntpServer1 = "ntp.nict.jp";
const char* ntpServer2 = "time.google.com";
const char* ntpServer3 = "ntp.jst.mfeed.ad.jp";
const long gmtOffset_sec = 9 * 3600;
const int daylightOffset_sec = 0;
// Callback function (get's called when time adjusts via NTP)--------------------
void timeavailable(struct timeval* t) {
  Serial.print("Got time adjustment from NTP! ");
}
void printLocalTime() {
  char str[256];
  unsigned long m;
  // timeとlocaltimeを使用して現在時刻取得
  time_t t;
  struct tm* tm;
  m = millis();
  t = time(NULL);
  tm = localtime(&t);
  sprintf(str, "\nday:%04d/%02d/%02d, time:%02d:%02d:%02d, ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
  Serial.print(str);
}

//------------BLE関連------------------------------
#include <BLEDevice.h>
BLEScan* pBLEScan;
// 設定
const size_t RCV_CNT_MAX = 10;                    // 同時に受信するデバイスの最大数
const int BLE_SCAN_TIME = 5;                      // BLEのスキャンを行う時間(s)
const char XIAO[4] = { 0xFF, 0xFF, 0x12, 0x35 };  // XIAO nRF52840識別値(FFFFは固定)
// シーケンス番号の記憶
#include <map>
std::map<uint16_t, uint16_t> seqHist;
// その他定義
struct RcvData {
  uint16_t id;   // 子機ID
  uint8_t type;  // 子機種別
  uint8_t ttl;   // TTL (time to live)
  uint16_t seq;  // シーケンス番号
  float pres;    // 気圧データ
  float temp;    // 気温データ
  float humi;    // 湿度データ
  float vbat;    // 電圧データ
  int16_t rssi;  // 電波強度 RSSI -50非常に強い -80弱い
};


// 初期化
void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.print(" System Start!  ");

  // voltage CHK ----------------------------------------
  pinMode(A0, INPUT);  // ADC

  // LED -------------------------------------------------
  pinMode(D7, OUTPUT);  // LED connected to digital pin 7
  delay(100);

  // set to Display(GxEPD2)-----------------------------
  //is GxEPD2
  pinMode(CS_PIN, OUTPUT);    //ChipSelect
  pinMode(BUSY_PIN, OUTPUT);  //Busy
  pinMode(RES_PIN, OUTPUT);   //Reset
  pinMode(DC_PIN, OUTPUT);    //Data/Command control
  display.init(115200, true, 50, false);
  delay(100);
  Gxprint(0.0, 0.0, 0.0);  //外気温 内気温 リポバッテリ

  // Conect to Wi-Fi-------------------------------------------
  Serial.printf("Connecting to %s ", ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print(" CONNECTED  ");
  //init and get the time
  sntp_set_time_sync_notification_cb(timeavailable);
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2, ntpServer3);


  // BLEの設定-----------------------------------------
  Serial.print("init BLE device...");
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setActiveScan(false);  // パッシブスキャンにする
}


// Main loop
int BleTimer = 0;
float inVal, outVal, inVolt;

void loop() {
  RcvData rcvdatas[RCV_CNT_MAX];
  int rcvcnt = 0;
  int loopCunt;
  float pres, temp, humi;
  uint16_t rssi, InOutVal;
  char buff[64];

  // Perform a BLE scan
  BLEScanResults foundDevices = pBLEScan->start(BLE_SCAN_TIME);
  int hit = foundDevices.getCount();
  // Extract the target device from the received data
  for (int i = 0; i < hit; i++) {
    BLEAdvertisedDevice dev = foundDevices.getDevice(i);
    std::string data = dev.getManufacturerData();

    if (data.length() < 18) continue;

    // If the data is from the XIAO nRF52840 then store the value
    if (data[0] == XIAO[0] && data[1] == XIAO[1] && data[2] == XIAO[2] && data[3] == XIAO[3]) {
      onoffLED();
      rcvdatas[rcvcnt].id = data[5] << 8 | data[4];
      InOutVal = rcvdatas[rcvcnt].id;
      rcvdatas[rcvcnt].type = data[6];
      rcvdatas[rcvcnt].ttl = data[7];
      rcvdatas[rcvcnt].seq = data[9] << 8 | data[8];
      if (seqHist[rcvdatas[rcvcnt].id] == rcvdatas[rcvcnt].seq) continue;  // 同じデータは無視
      seqHist[rcvdatas[rcvcnt].id] = rcvdatas[rcvcnt].seq;
      if (rcvdatas[rcvcnt].type == 10 || rcvdatas[rcvcnt].type == 11) {
        rcvdatas[rcvcnt].pres = (float)(data[11] << 8 | data[10]) / 10.00;
        rcvdatas[rcvcnt].temp = (float)(data[13] << 8 | data[12]) / 10.00;
        rcvdatas[rcvcnt].humi = (float)(data[15] << 8 | data[14]) / 10.00;
        rcvdatas[rcvcnt].vbat = (float)(data[17] << 8 | data[16]) / 100;
        rcvdatas[rcvcnt].rssi = (uint16_t)dev.getRSSI();                    // Signal strength
        //マイナス温度補正
        if ((rcvdatas[rcvcnt].temp * 10.0) > 60000.0) {
          rcvdatas[rcvcnt].temp = rcvdatas[rcvcnt].temp - 6553.5;
        }
        rcvcnt++;
      }
      if (rcvcnt >= RCV_CNT_MAX) break;
    }
  }
  // ディスプレイに表示
  if (rcvcnt > 0) {
    rcvcnt--;
    if (BleTimer > 5) {
      //シリアル出力
      printLocalTime();
      // Serial.printf("Pres:%.1f,temp:%.1f,humi:%.1f,vbat:%.1f,rssi:%d, ID:%d,type:%d",
      //               rcvdatas[rcvcnt].pres - 1000, rcvdatas[rcvcnt].temp, rcvdatas[rcvcnt].humi, rcvdatas[rcvcnt].vbat, abs(rcvdatas[rcvcnt].rssi), rcvdatas[rcvcnt].id, rcvdatas[rcvcnt].type);

      // //液晶出力

      if (InOutVal == 101) {
        outVal = rcvdatas[rcvcnt].temp;
      } else if (InOutVal == 102) {
        inVal = rcvdatas[rcvcnt].temp;
      }

      inVolt = voltChk();

      //e-Paper出力
      Gxprint(outVal, inVal, inVolt);

      // Wifi出力-------------------------------
      postServer(rcvdatas, 1, InOutVal);
    }
    // Serial.printf("\nPres:%.1f,temp:%.1f,humi:%.1f,rssi:%d",
    // rcvdatas[rcvcnt].pres, rcvdatas[rcvcnt].temp, rcvdatas[rcvcnt].humi,-1 * rcvdatas[rcvcnt].rssi );
    BleTimer = 0;
  } else {
    BleTimer++;

    // if ((BleTimer % 2) == 0) digitalWrite(D7, 0);
    // else digitalWrite(D7, 1);
    for ( loopCunt = 0; loopCunt < 270 ; loopCunt ++){
      onoffLED();
    }

    if ((BleTimer % 5) == 0) {
      Serial.printf(" %d", BleTimer / 5);
    }
  }

}
// Webサーバーに送信
void postServer(struct RcvData* td, int cnt, uint16_t InOutVal) {
  HTTPClient http;
  Serial.print(" Send to Web Server ");
  // POSTデータ作成
  String jsonData = "{\"data\":[";
  char buff[128];
  for (int i = 0; i < cnt; i++) {
    if (InOutVal == 101) {
      sprintf(buff, "{\"id\":%d,", 101);
    } else if (InOutVal == 102) {
      sprintf(buff, "{\"id\":%d,", 102);
    }
    jsonData += buff;
    sprintf(buff, "\"pres\":%.1f,", td[i].pres);
    jsonData += buff;
    sprintf(buff, "\"volt\":%.1f,", td[i].vbat);
    jsonData += buff;
    sprintf(buff, "\"temp\":%.1f,", td[i].temp);
    jsonData += buff;
    sprintf(buff, "\"humi\":%.1f,", td[i].humi);
    jsonData += buff;
    sprintf(buff, "\"rssi\":%d,", td[i].rssi);
    jsonData += buff;
    sprintf(buff, "\"seq\":%d}", td[i].seq);
    jsonData += buff;
    if (i < cnt - 1) jsonData += ",";
  }
  jsonData += "],\"count\":" + String(cnt) + "}";
  Serial.print(jsonData);

  // 送信
  if (InOutVal == 101) {
    http.begin(WEB_API_URL_o);  //HTTP outer
  } else if (InOutVal == 102) {
    http.begin(WEB_API_URL_i);  //HTTP innar
  }

  http.addHeader("Content-Type", "application/x-www-form-urlencoded");
  http.addHeader("Content-Length", String(jsonData.length()));
  http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
  int httpCode = http.POST(jsonData);
  // printf("[HTTP] POST... code: %d ", httpCode);
  // 送信後の処理
  if (httpCode == HTTP_CODE_OK) {
    String payload = http.getString();
    printf(" OK %s ", payload);
  } else {
    printf("[HTTP] POST... failed, error: %s ", httpCode);
  }
  http.end();
}


void Gxprint(float outFloat, float inFloat, float voltFloat) {
  int16_t tbx, tby;
  uint16_t tbw, tbh;
  char inString[8], outString[8], voltString[8];

  //floatを文字列に変換
  sprintf(inString, "%+4.1f", inFloat);
  sprintf(outString, "%+4.1f", outFloat);
  sprintf(voltString, "%4.1f", voltFloat);

  //初期値設定
  display.setRotation(1);
  display.setFont(&FreeMonoBold12pt7b);
  display.setTextColor(GxEPD_BLACK);
  display.setFullWindow();
  display.firstPage();

  do {
    display.fillScreen(GxEPD_WHITE);
    //外気温
    display.setFont(&FreeMonoBold12pt7b);
    display.setCursor(2, 30);
    display.print("Outside\ntemp.");
    display.setFont(&FreeMonoBold18pt7b);
    int16_t tbx, tby;
    uint16_t tbw, tbh;
    display.getTextBounds(outString, 0, 0, &tbx, &tby, &tbw, &tbh);
    display.setCursor(display.width() - tbw - 3, 55);
    display.print(outString);
    display.fillRect(0, 65, 199, 2, GxEPD_BLACK);
    //内気温
    display.setFont(&FreeMonoBold12pt7b);
    display.setCursor(2, 90);
    display.print("Indoor\ntemp.");
    display.setFont(&FreeMonoBold18pt7b);
    display.getTextBounds(inString, 0, 0, &tbx, &tby, &tbw, &tbh);
    display.setCursor(display.width() - tbw - 3, 115);
    display.print(inString);
    display.fillRect(0, 125, 199, 2, GxEPD_BLACK);
    //大気圧
    display.setFont(&FreeMonoBold12pt7b);
    display.setCursor(2, 150);
    display.print("LiPo battery\nvoltage");
    display.setFont(&FreeMonoBold18pt7b);
    display.getTextBounds(voltString, 0, 0, &tbx, &tby, &tbw, &tbh);
    display.setCursor(display.width() - tbw - 3, 176);
    display.print(voltString);
  } while (display.nextPage());
}

float voltChk() {
  uint32_t Vbatt = 0;
  for (int i = 0; i < 16; i++) {
    Vbatt = Vbatt + analogReadMilliVolts(A0);  // ADC with correction
  }
  return (2 * Vbatt / 16 / 1000.0);  // attenuation ratio 1/2, mV --> V
}

void onoffLED(){
  delay(13);
  digitalWrite(D7, 1);  
  delay(3);
  digitalWrite(D7, 0);
}

プライバシーポリシー |ページトップへ

`