ESP32-DevKitCと2.8インチ240×320液晶モジュール(SDカード)をSPI接続、気圧・温湿度センサBME280をI2C接続

ESP32-DevKitCとILI9341を搭載した240×320の2.8インチTFT液晶モジュールをSPI接続(液晶とタッチパネルはVSPI、SDカードはHSPI)、気圧・温湿度センサBME280をI2C接続しました。

グラフィック型の液晶モジュールなので日本語の画像表示や大小のフォントサイズを使い分けることで画面表示にメリハリがついて視認性が良くなりました。

このTFT液晶モジュールにはSDカードソケットが実装されているので、測定データの画面表示とSDカード記録がコンパクトにできて便利です。

ESP32-DevKitCとILI9341を搭載した240×320の2.8インチTFT液晶モジュールをSPI接続(液晶とタッチパネルはVSPI、SDカードはHSPI)、気圧・温湿度センサBME280をI2C接続
右下がI2C接続したBME280センサ。気圧・温湿度をグラフィック型TFT液晶に表示
目次

モジュールの組立て、結線図

2.8インチ液晶モジュールはSPI接続、初期設定ファイル User_Setup.h の編集

2.8インチ液晶モジュールには、ドライバーICとしてILI9341を使った240×320の液晶モジュールに加えて、XPT2046を使ったタッチパネル、SDカードスロットが実装されています。利用するにあたり必要となるモジュール間の結線とライブラリの初期設定を下記ページに纏めました。

ESP32-DevKitCとは4線式SPIで接続(液晶とタッチパネルはVSPI、SDカードはHSPI)。TFT_eSPIライブラリをインクルード(初期設定ファイル User_Setup.h の編集)して、グラフィック型の液晶表示とタッチパネル制御を行います。

あわせて読みたい
ESP32-DevKitCと2.8インチ液晶モジュール(240×320、タッチパネル、SDカード)をSPI接続して動作確認 ILI9341を搭載した240×320の2.8インチTFT液晶モジュールをESP32-DevKitCにSPI接続して動作確認した際の作業メモです。液晶とタッチパネルはVSPI、SDカードはHSPI接続で...

気圧・温湿度センサBME280はI2Cで接続

BME280モジュールとESP32-DevKitCとの結線は、I2Cの2線(SCL、SDA)と電源の2線(3.3V、GND)の4線なので容易です。

今回使ったBME280モジュールは基板裏面に電源電圧Vccのレギュレータ(LDO:Low Dropout)と I2Cの電圧レベル変換回路を実装しているので、5V系(Arduinoなど)と3.3V 系(ESP32-DevkitCなど)のどちらとも直接つないで使えます。
このモジュールのデフォルトのI2Cアドレスは0x76です。

BME280モジュール
モジュール基板の裏面(左:VccとI2Cのレベル変換回路)と表面のBME280センサ(右)

結線図

ESP32-DevKitCと2.8インチTFT液晶モジュール、BME280の結線図
ESP32-DevKitCと2.8インチTFT液晶モジュール、BME280の結線図

ライブラリのインクルード、スケッチの作成と動作確認

BME280センサで測った温度、湿度、気圧データを2.8インチ液晶モジュールへの表示とファイル名「logdata.txt」でSDカードに書き込むスケッチを作りました。
SDカードはFAT32でフォーマットしておきます。

ライブラリのインクルード

BME280センサライブラリ

BME280センサ(0x76)から測定データを取得するライブラリとしてGitHub掲載の adafruit/Adafruit_BME280_Library と adafruit/Adafruit_Sensor を利用させていただきました。

Adafruit_I2CDevice.hが無いとのエラーメッセージがでた場合はadafruit/Adafruit_BusIO(Adafruit_BusIO-master.zip)をインクルードします(2022/9/24追記)。

TFT_eSPIライブラリ、項目名はtft.drawXBitmap()で日本語表示

2.8インチ液晶モジュール表示のライブラリとして「TFT_eSPI.h」を利用させていただきました。GitHubサイトからTFT_eSPIライブラリのzipファイル( TFT_eSPI-master.zip )をPCにダウンロードして保存(「Code」ボタンをクリックして「Download ZIP」)。Arduino IDEのメニューの「スケッチ」→「ライブラリをインクルード」→「.ZIP形式の ライブラリを インストール」でダウンロードしたZIPファイルを追加します。ライブラリをインクルードした後、初期設定ファイル(User_Setup.h)編集)を行います。

このスケッチで使った漢字と記号は「気、圧、温、湿、度、(℃)」と少ないので、メモリ消費を抑えるために、利用する特定の文字のみをHEXデータで表示します。

最大1310720バイトのフラッシュメモリのうち、スケッチが334101バイト(25%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が18236バイト(5%)を使っていて、ローカル変数で309444バイト使うことができます。

記号や漢字のBMPファイルは、Windows標準アプリのペイント3Dで170×170ピクセル程度で作った後キャンパスを32×32ピクセル(「(℃)」のみ40×40ピクセル)に変更して圧縮します。このBMPファイルをHEXデータに変換してtft.drawXBitmap() で描画します。HEXデータへの変換にはProgramResource.netサイトの「nfBmptoHex.exe」を利用させていただきました。

スケッチの作成と実行結果(2022/5/18 スケッチ修正、2022/9/24追記)

測定データの表示スペースは、気圧は4桁、温湿度は整数部2桁、少数点以下1桁です。

SDカードには、約2秒毎に4つのデータをカンマセパレータ形式で記録します。
・Arduino IDE実行後の(1)経過時間(millis()を秒変換、カウントアップ)
・BME280センサ の(2)気圧、(3)温度、(4)湿度(SDカード記録は全て少数点以下2桁)
測定(記録)間隔は、スケッチ122行の「delay(2000);」で調整します。

SDカードに記録した気圧・温湿度データ
SDカードに記録した気圧・温湿度データ
グラフィック型TFT液晶に日本語表記
グラフィック型TFT液晶に日本語表記

millis()は「Arduino ボードが現在のプログラムを実行し始めてから経過したミリ秒数を返します。この数は、約 50 日後にオーバーフロー (ゼロに戻る) になります」とArduino Referenceに記載があります。
長期間の測定には、I2C接続のリアルタイムクロック(RTC)を設けて取得時刻を記録Tera Termのログに時刻(タイムスタンプ)を記録といった手段もあります。

スケッチを実行すると初期化ステータスを表示した後、測定データの表示画面に遷移します。BME280との接続に成功すると「BME280 connected」、失敗すると「BME280 connection failed」と赤文字で表示します。

BME280との接続に成功
BME280との接続に成功
BME280との接続に失敗
BME280との接続に失敗
esp32-devkitc_ili9341-lcd_bme280.ino
※ここをクリックするとコード表示を開閉できます。
#include <SD.h>
#include <TFT_eSPI.h>   // https://github.com/Bodmer/TFT_eSPI
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>   // https://github.com/adafruit/Adafruit_Sensor
#include <Adafruit_BME280.h>   // https://github.com/adafruit/Adafruit_BME280_Library

TFT_eSPI tft = TFT_eSPI();
SPIClass spiSD(HSPI);
Adafruit_BME280 bme;   // 気圧・温湿度センサ
float pressure;
float temp;
float humid;
const unsigned char kii_bmp[] PROGMEM = {0x80, 0x01, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x1F, 0xF0, 0xFF, 0xFF, 0x1F, 0x30, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x8E, 0xFF, 0xFF, 0x03, 0x86, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x01, 0xFC, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x80, 0x83, 0x01, 0xE0, 0xC0, 0x81, 0x01, 0xC0, 0xC3, 0x80, 0x01, 0x00, 0x67, 0x80, 0x01, 0x00, 0x7E, 0x80, 0x01, 0x00, 0x3C, 0x80, 0x01, 0x00, 0x7C, 0x80, 0x03, 0x00, 0xEF, 0x00, 0x43, 0x80, 0xC3, 0x01, 0x63, 0xE0, 0x81, 0x03, 0x63, 0xF8, 0x00, 0x07, 0x66, 0x3E, 0x00, 0x06, 0x7E, 0x0E, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x18, };
const unsigned char atu_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x7F, 0xF0, 0xFF, 0xFF, 0x7F, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0xFE, 0xFF, 0x1F, 0x30, 0xFE, 0xFF, 0x1F, 0x30, 0x00, 0x0E, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x0C, 0x00, 0x38, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x18, 0x00, 0x0C, 0x00, 0x1C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x0E, 0x00, 0x0C, 0x00, 0xCE, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char onn_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x38, 0xF8, 0xFF, 0x07, 0x70, 0xF8, 0xFF, 0x07, 0xE0, 0x18, 0x00, 0x06, 0xC0, 0x18, 0x00, 0x06, 0x00, 0x18, 0x00, 0x06, 0x00, 0xF8, 0xFF, 0x07, 0x00, 0xF8, 0xFF, 0x07, 0x02, 0x18, 0x00, 0x06, 0x07, 0x18, 0x00, 0x06, 0x1E, 0x18, 0x00, 0x06, 0x38, 0x38, 0x00, 0x06, 0x70, 0xF8, 0xFF, 0x07, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x8C, 0xE3, 0x18, 0x60, 0x0C, 0x63, 0x18, 0x60, 0x0C, 0x63, 0x18, 0x70, 0x0C, 0x63, 0x18, 0x30, 0x0C, 0x63, 0x18, 0x30, 0x0C, 0x63, 0x18, 0x18, 0x0C, 0x63, 0x18, 0x18, 0x0C, 0x63, 0x18, 0x1C, 0x0C, 0x63, 0x18, 0x8C, 0xFF, 0xFF, 0xFF, 0xCE, 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char doo_bmp[] PROGMEM = {0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0xF0, 0xFF, 0xFF, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0x30, 0x00, 0x00, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0xB0, 0xFF, 0xFF, 0x7F, 0x30, 0xE7, 0xE0, 0x70, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0xE0, 0xFF, 0x00, 0x30, 0xE0, 0xFF, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0x03, 0x38, 0xFF, 0xFF, 0x07, 0x18, 0x30, 0x00, 0x03, 0x18, 0x60, 0x80, 0x01, 0x18, 0xE0, 0xC0, 0x01, 0x18, 0xC0, 0xE1, 0x00, 0x1C, 0x80, 0x7B, 0x00, 0x0C, 0x00, 0x1F, 0x00, 0x0E, 0x00, 0x3F, 0x00, 0x0E, 0xE0, 0xFB, 0x01, 0x06, 0xFE, 0xC0, 0x3F, 0xC6, 0x1F, 0x00, 0xFE, 0xC0, 0x01, 0x00, 0x60, };
const unsigned char deg_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0x04, 0x10, 0x3C, 0xC0, 0x07, 0x08, 0x18, 0x24, 0xF0, 0x1F, 0x10, 0x0C, 0x42, 0x38, 0x18, 0x30, 0x04, 0x42, 0x18, 0x30, 0x20, 0x06, 0x24, 0x0C, 0x70, 0x60, 0x06, 0x18, 0x0C, 0x00, 0x40, 0x02, 0x00, 0x0C, 0x00, 0x40, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x06, 0x00, 0xC0, 0x02, 0x00, 0x06, 0x00, 0xC0, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x0E, 0x00, 0xC0, 0x02, 0x00, 0x0C, 0x00, 0x40, 0x06, 0x00, 0x0C, 0x60, 0x40, 0x04, 0x00, 0x1C, 0x30, 0x60, 0x0C, 0x00, 0x38, 0x38, 0x20, 0x08, 0x00, 0xF0, 0x1F, 0x30, 0x18, 0x00, 0xC0, 0x0F, 0x18, 0x30, 0x00, 0x00, 0x00, 0x08, 0x60, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, };
const unsigned char sit_bmp[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x38, 0xF8, 0xFF, 0x1F, 0x70, 0xF8, 0xFF, 0x1F, 0xE0, 0x18, 0x00, 0x18, 0xC0, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0x18, 0x00, 0xF8, 0xFF, 0x1F, 0x00, 0xF8, 0xFF, 0x1F, 0x06, 0x18, 0x00, 0x18, 0x1E, 0x18, 0x00, 0x18, 0x3C, 0x18, 0x00, 0x18, 0x70, 0x18, 0x00, 0x18, 0x20, 0xF8, 0xFF, 0x1F, 0x00, 0xF8, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x00, 0x0C, 0x63, 0x30, 0x60, 0x0C, 0x43, 0x30, 0x60, 0x18, 0x43, 0x18, 0x70, 0x18, 0x43, 0x18, 0x30, 0x30, 0x43, 0x0C, 0x30, 0x30, 0x43, 0x0C, 0x38, 0x30, 0x43, 0x06, 0x18, 0x00, 0x63, 0x00, 0x1C, 0x00, 0x63, 0x00, 0x8C, 0xFF, 0xFF, 0xFF, 0x8E, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };

void setup(void) {
// SDカード初期化
spiSD.begin(14, 33, 13, 15); //SCK,MISO,MOSI,CS
// TFT液晶初期化
  tft.init();
  tft.setRotation(1);
  tft.setTextSize(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("Initializing SD library & card", 0, 20, 4);
delay(1000);
// SDカードマウント確認
if (!SD.begin(15, spiSD)) {
  tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.drawString("Card Mount Failed", 0, 50, 4);
return;
}
else  {
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("Card Mount Successful", 0, 50, 4);
}
delay(1000);
// SDカードファイル書き込み
File dataFile = SD.open("/datalog.txt", FILE_WRITE);
dataFile.println("File written");
dataFile.close();
  tft.drawString("File written", 0, 80, 4);
delay(1000);
// BME280初期化
bool status;
status = bme.begin(0x76);
while (!status) {
  tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.drawString("BME280 connection failed", 0, 110, 4);
}
  tft.setTextColor(TFT_YELLOW, TFT_BLACK);
  tft.drawString("BME280 connected", 0, 110, 4);
delay(1000);
// 画面クリア
  tft.fillScreen(TFT_BLACK);
}

void loop() {
// ----- BME280センサからデータ取得 ----- 
pressure=bme.readPressure() / 100.0F;
temp=bme.readTemperature();
humid=bme.readHumidity();
tft.setTextColor(TFT_WHITE, TFT_BLACK);
// ----- 項目名を日本語で表示 ----- 
// 気圧(hPa)
   tft.drawXBitmap(10, 10, kii_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 10, atu_bmp, 32, 32, 0xFFFF);
   tft.drawString("(hPa)", 10, 45, 4);
// 温度(℃)
   tft.drawXBitmap(10, 92, onn_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 92, doo_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(10, 110, deg_bmp, 40, 40, 0xFFFF);
// 湿度(%)
   tft.drawXBitmap(10, 170, sit_bmp, 32, 32, 0xFFFF);
   tft.drawXBitmap(42, 170, doo_bmp, 32, 32, 0xFFFF);
   tft.drawString("(%)", 10, 205, 4);
// ----- 液晶画面に気圧、温度、湿度の測定値を表示 ----- 
   tft.setTextColor(TFT_YELLOW, TFT_BLACK);
   String p_tmp = String(pressure, 0);
  int len_p_tmp = p_tmp.length(); 
  if (len_p_tmp == 3 ) {
   tft.fillRect(265, 0, 55, 76, TFT_BLACK);  // 4桁から3桁に変わった時の残像消去@2022/05/18
  }
   tft.drawFloat(pressure, 0, 100, 0, 8); 
   tft.drawFloat(temp, 1, 100, 80, 8);
   tft.drawFloat(humid, 1, 100, 160, 8);
// ----- SDカードへの書き込み用データファイルの生成 ----- 
// 経過時間millis()を秒に変換
    String dataString = "";
    dataString += String(millis() / 1000); 
// 測定データ1:BME280の気圧
    dataString += ",";  // カンマセパレータ
    if(!isnan(pressure)){
      dataString += String(pressure);
    }else{
      dataString += " ";
    }
// 測定データ2:BME280の温度
    dataString += ",";  // カンマセパレータ
    if(!isnan(temp)){
      dataString += String(temp);
    }else{
      dataString += " ";
    }
// 測定データ3:BME280の湿度
    dataString += ",";  // カンマセパレータ
    if(!isnan(humid)){
      dataString += String(humid);
    }else{
      dataString += " ";
    }
// ----- SDカードのdatalog.txtにdataStringを追加 ----- 
File dataFile = SD.open("/datalog.txt", FILE_APPEND);
dataFile.println(dataString);
dataFile.close();
// 測定間隔を調整
delay(2000);
}
よかったらシェアしてね!
  • URLをコピーしました!
目次