Seeed Studio XIAO ESP32C3と小型LCDモジュールAQM1602YとBME280を使った気圧・温湿度&WiFi時計

Seed Studio XIAO ESP32C3(以下、XIAO ESP32C3)とI2C接続したBME280、WiFi接続で情報通信研究機構(NICT)のNTPサーバと時刻合わせして、LCDモジュール AQM1602Y-NLW-FBW に日時、気圧、気温、湿度を表示する小型の環境モニタを作りました。

電源起動(スケッチ実行)時にWiFi経由でNTPサーバに接続してXIAO ESP32C3の内蔵時計をJSTに時刻合わせします。加えて、1日1回、NTPサーバに接続して時刻合わせすることで精度を維持します。

LCDモジュールの表示領域は16文字2行と少ないので、曜日の表記は漢字1文字です。AQM1602YのCGRAMに漢字のキャラクタパターンを登録して表示します。

1枚のミニブレッドボード BB-601上に実装
目次

準備したパーツ

XIAO ESP32C3は、Espressif ESP32-C3 WiFi / Bluetoothデュアルモードチップをベースにした超小型のマイコンボード。当サイトでよく利用する ESP32-DevKitC とはピン数が異なるものの、Arduino IDE(arduino-esp32)を使って開発できます。

小型モジュールのためピン数が限られますが独立したI2C、SPIピンがあるので、これらの接続インターフェースを持ったセンサモジュールを使って小型化できます。

ネット通販(秋月電子通商 など)でパーツを集めてブレッドボードで組み立てました。

 パーツ 個数 備考
1Seed Studio XIAO ESP32C31【M-17454】
2BME280 気圧、温湿度センサ モジュール
※今回使ったモジュールはI2C接続、電圧レギュレータとI2C電圧レベル変換回路を内蔵
1手持ち
3ミニブレッドボード BB-601(白)1【M-15178】
4LCDモジュール 16X2行
白色バックライト付 白文字 黒背景
AQM1602Y-NLW-FBW
1【P-12486】
5積層セラミックコンデンサー
0.1μF 50V X7R 2.54mm(10個入)

※利用するのは1個
1【P-13582】
6積層セラミックコンデンサー
1μF 50V Y5V 5mm(10個入)
※利用するのは2個
1【P-03093】
7抵抗 220Ω
※バックライトLED電流制限抵抗
1 手持ち
8丸ピンIC用ソケット
(シングル 9P)1×9
※AQM1602Yをブレッドボードに挿すときに利用
※ユニバーサル基板で一体化時には不要
1【P-01016】
9細ピンヘッダ 1×40(黒)
※バックライトLED端子(A、K)のピン
※ユニバーサル基板で一体化した際の引出し用ピン
※XIAO ESP32C3用のピン
1【C-06631】
10両面ユニバーサル基板
※AQM1602Yとパーツの一体化実装用
適量手持ち
11ジャンパーワイヤ
オス-オス 10cmセット
適量【C-05371】

・モジュールにピンヘッダが付属しているか、ピンヘッダのピンの太さがブレッドボードに適しているか、自分がはんだ付けする必要があるか等を確認します。
・モジュールがプルアップ抵抗を内蔵しているか、その抵抗値と有効化方法を確認します。無い場合は必要に応じで外部でプルアップ抵抗を実装します。
・基板内に電圧レギュレータやI2C電圧レベル変換回路を内蔵して5V系(Arduinoなど)と3.3V系(XIAO ESP32C3など)の両方で使えるモジュールもあります。一方で電圧レギュレータを内蔵して電源電圧は5V、3.3V両方で使えますがI2CやSPI等のロジックレベルは3.3V系といったモジュールもあるので仕様書で確認します。

組立と動作確認

結線図

動作確認できた現在の結線図です。XIAO ESP32C3のピンレイアウトは下記サイトを参照して結線します。XIAO ESP32C3には、シート状のWiFiアンテナ(裏面は3M 300LSE両面テープ)がつきます。

Getting Started with Seeed Studio XIAO ESP32C3
Pinout diagram

https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/
結線図
結線図

LCDモジュール(AQM1602Y-NLW-FBW)

I2C接続のAQM1602Y-NLW-FBWは、薄型の16文字×2行のキャラクタ液晶、コントラスト調整はコマンドで行います。

このLCDモジュールを3.3Vで使う際はVoltage booster circuit(昇圧回路)を働かせるためにキャパシタが必須。CAP1P(4番ピン)とCAP1N(5番ピン)端子間に外付けキャパシタ(C1)を、VOUT(2番ピン)とVIN(3.3V)間にキャパシタ(C2)をつけます。
C1、C2の値は、秋月電子通商サイトの下記資料を参考に選択しました。VDDとVSS間のC3はパスコンです。

コントローラICデータシート(PDF)p61
ST7032i under Glass, IIC interface, with booster and follower circuit on
C1 : 0.1μF~1μF (SMD)
C2 : 0.47μF~2.2μF (SMD)

https://akizukidenshi.com/download/ds/sitronix/st7032.pdf

バックライトのLED電流制限抵抗として手持ちの220Ωを使っているので電流は15mA程度です(LEDの最大電流の仕様は30mA)。その際のコントラスト設定(lcd.setContrast(); は 40(0~63の範囲で調整))でした。
黒背景なので視認性も良いです。

LCDモジュールのピン引出し加工

LCDモジュール(AQM1602Y-NLW-FBW)と外付けキャパシタ(3個)、バックライトLED電流制限抵抗(1個)、接続用4ピン(3.3V、GND、SDA、SCL)をミニブレッドボード BB-601(白)で結線して動作確認しました。

LCDモジュールのバックライトLED端子(A、K)は板状なのでブレッドボードに直接挿せるように細ピンヘッダを曲げてはんだ付けしています。1~9番ピンはピンが細く短いので、丸ピンIC用ソケットを間に挿して引出しています。

LCDモジュールの端子の引き出し加工
LCDモジュールの端子の引き出し加工
ブレッドボード上でLCDモジュールの動作確認
ブレッドボード上でLCDモジュールの動作確認

LCDモジュールとパーツをユニバーサル基板で一体化(2022/12/17追加)

LCDモジュール、外付けのキャパシタ、抵抗、接続用ピンヘッダー(3.3V、GND、SDA、SCL)をユニバーサル基板上で配線・はんだ付けして組み立てました。一体化する際にはブレッドボードに挿すために使った丸ピンIC用ソケットは使いません。バックライトLED端子はユニバーサル基板のスルーホールの穴径をドリルで2.5mmΦに拡げます。

LCDモジュールを小型化できたので、1枚のミニブレッドボード BB-601上に全てのパーツを実装できました。

LCDモジュールをユニバーサル基板で一体化
ユニバーサル基板で一体化
1枚のミニブレッドボード BB-601上に実装
1枚のミニブレッドボード BB-601上に実装

ユニバーサル基板を取り付ける際、LCDモジュール裏面の型式が印字してある白いシート(両面テープ張り付け)が剥がれたので取り外したところ、LCD表示が暗くなり、場所によっては明るさにムラがでました。この白いシートはバックライトの光を反射・均一化する役割があります。
ユニバーサル基板で一体化した後で気が付き、はんだ付けのやり直しに苦労しました。

XIAO ESP32C3のSCLピンとSDAピンの信号波形(2022/12/19追加)

今回使ったBME280モジュールにはI2C電圧レベル変換回路内にプルアップ抵抗が内蔵されていたので、 外部に I2Cのプルアップ抵抗は実装していません。

ブレッドボード上のXIAO ESP32C3のSCLピンとSDAピンの信号波形をデジタルオシロスコープで見ると、信号の立ち上がりが少しいる波形でした。

現状動作に支障はないですが、外部にI2Cプルアップ抵抗を設けた方がより安心です。

I2C信号波形
I2C信号波形(上段;SCL、下段;SDA)

開発ツール arduino-esp32のインストールとライブラリの追加

ESP32の開発ツール arduino-esp32を準備してスケッチを作っていきます。

ESP32開発ツール arduino-esp32のインストール

XIAO ESP32C3サイトにarduino-esp32のインストール手順の記載があります。

Getting Started with Seeed Studio XIAO ESP32C3
Software setup

https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/#software-setup

当サイトにもESP32-DevKitCを例として、arduino-esp32 をArduino IDE 2.0.x版にインストールする手順メモを纏めています。

ボード(XIAO_ESP32C3を選択)とポート(当サイトのPCではCOM6)を切り替えることでXIAO ESP32C3でもそのまま使えます。

Arduino IDEでボードとポートの選択
ボード:XIAO_ESP32C3を選択

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

先達の方々が開発されたライブラリをインクルードすることでスケッチ作成が容易になります。

LCDモジュール

LCDモジュールのライブラリには ST7032.h を利用させていただきました。
ライブラリのzipファイル( arduino_ST7032-master.zip )をPCにダウンロードして保存(「Code」ボタンをクリックして「Download ZIP」)。Arduino IDEのメニューの「スケッチ」→「ライブラリをインクルード」→「.ZIP形式の ライブラリを インストール」で ダウンロードしたZIPファイルを追加します。

tomozh/arduino_ST7032

https://github.com/tomozh/arduino_ST7032

BME280センサ

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

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

TimeLib.h

Time.h、TimeLib.hライブラリは PaulStoffregen/Time を利用させていただきました。サイトの「Code」プルダウンから「Download ZIP」でPCに「Time-master.zip」をダウンロード。Arduino IDEのメニュー「スケッチ」–>「ライブラリをインクルード」–>「zip形式のライブラリをインクルード」で「Time-master.zip」を指定します。

esp_sntp.h

esp_sntp.h」は arduino-esp32同梱モジュールです。詳細は Official develooment framework for ESP32 (ESP-IDF) に記述があります。mulong.meサイトを参考に内蔵時計の時刻が NTPサーバから取得した時刻に一致しているかの判定に使っています。

スケッチ:LCDの動作確認、コントラスト調整

コントラスト確認用のスケッチです。1行目にLCDの型式を、2行目に数字をカウントアップ表示します。

今回バックライトのLED電流制限抵抗として手持ちの220Ωを使っています。その際のコントラスト設定 lcd.setContrast(); は、40(0~63の範囲で調整)でした。黒背景なので視認性も良いです。

XIAO_ESP32C3_AQM1602.ino
※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <ST7032.h>

ST7032 lcd;
int count=0;

void setup(){
  Wire.begin();        // I2C初期化
  lcd.begin(16, 2);    // LCDの文字数(16)と行数(2)
  lcd.setContrast(40); // LCDのコントラスト調整(0~63)
  lcd.setCursor(0, 0);
  lcd.print("AQM1602Y-NLW-FBW");
}

void loop() {
// 液晶にcount値を表示
  lcd.setCursor(0, 1);
  lcd.print(count);
// カウントアップ
  count++;
  delay(1000);
}

スケッチ:LCDモジュールに時刻、気圧、気温、湿度を表示

XIAO ESP32C3とI2C接続したBME280センサ、WiFi接続でNTPサーバと時刻合わせして、LCDモジュールAQM1602Yに日時、時刻、気圧、気温、湿度を表示します。

表示レイアウト、曜日をカタカナで表記

 LCDモジュールの表示領域は16文字2行、下記レイアウトで表示しています。年の表示、気圧、温度、湿度の単位表示(ppm、℃、%)は無しです。

上段:月/日、曜日(1文字)、時:分:秒
下段:気圧(4桁)、温度と湿度(整数2桁、小数点以下1桁)
AQM160に曜日をカタカナ1文字で表示
曜日をカタカナで簡略表示

曜日は、表示領域が1文字しか取れなかったので、読みの先頭1文字をカタカナ(二、ケ、カ、ス、モ、キ、ト)とケ゛、ト゛のみ濁点付きの2文字(時刻との境のスペース部分に ゛を表示)で表現しています。

LCDモジュールの キャラクタ パターンは秋月電子通商サイトAQM1602Y-NLW-FBW PDFデータシートのp12にあります。

スケッチには1文字の英字(S、M、T、W、T、F、S)の配列も残してます。

曜日d_wdayLCD
表示
キャラクタ
コード
00xc6
1  ケ゛0xb9 + 0xde
20xb6
30xbd
40xd3
50xb7
6  ト゛0xc4 + 0xde
//曜日(カタカナ表示)
char weekStr[7] = {0xc6,0xb9,0xb6,0xbd,0xd3,0xb7,0xc4}; //1文字カタカナ(二,ケ,カ,ス,モ,キ,ト)
char dakuten = 0xde;   //1文字の濁点(゛)
・・・
// ----- 曜日(英字 or カタカナ表示)
  lcd.print(weekStr[d_wday]);
//濁点追加(ケ゛、ト゛のみ)、英字の場合は91~95行は不要)
  if ((String(d_wday) == "1") || (String(d_wday) == "6")) {
   lcd.print(dakuten);
  } else {
  lcd.print(" ");   // 残像消去@2022/12/18追加
  }

曜日を漢字(キャラクタパターンをCGRAM登録)で表記(2022/12/22追記)

LCDモジュールのCGRAMに任意のキャラクタパターンを登録し、そのコードを呼び出して表示させます。ST7032.hライブラリには、lcd.createChar()、lcd.write() 関数が用意されており、キャラクタパターンによる漢字表示が容易にできます。

漢字のキャラクタパターンを2進数に変換、”木”の場合はキャラクタパターンの上段より00100、00100、11111・・・00000の8行分をbyte型の配列で宣言します。

最下段はスペースなので5×7ドットのフォントです。粗さ(特に水、金)が気になりますが、漢字だと直感的な曜日の判断ができます。

1枚のミニブレッドボード BB-601上に実装
“木”の表示
//曜日(漢字キャラクタパターン)
byte nichi[8] = { 0b11111, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b11111, 0b00000 }; //日
byte getsu[8] = { 0b01111, 0b01001, 0b01111, 0b01001, 0b01111, 0b01001, 0b10001, 0b00000 }; //月
byte kayou[8] = { 0b00100, 0b10101, 0b10101, 0b00100, 0b01010, 0b01010, 0b10001, 0b00000 }; //火
byte suiyo[8] = { 0b00100, 0b00101, 0b11110, 0b00110, 0b01101, 0b10100, 0b00100, 0b00000 }; //水
byte mokuy[8] = { 0b00100, 0b00100, 0b11111, 0b00100, 0b01110, 0b10101, 0b00100, 0b00000 }; //木
byte kinyo[8] = { 0b00100, 0b01010, 0b10001, 0b01110, 0b10101, 0b01110, 0b11111, 0b00000 }; //金
byte doyou[8] = { 0b00000, 0b00100, 0b00100, 0b01110, 0b00100, 0b00100, 0b11111, 0b00000 }; //土
ビットマップによる漢字表示
曜日のキャラクタパターン

NTPサーバに接続して時刻合わせ(2023/01/15スケッチ修正)

電源起動(スケッチ実行)時にWiFi接続して、情報通信研究機構(NICT)のNTPサーバに接続してXIAO ESP32C3の内蔵時計をJSTに時刻合わせします。加えて、1日1回(下記スケッチでは12時30分0秒)、NTPサーバに接続して時刻合わせを実行して精度を維持します。

時刻合わせ実行時には、WiFi接続、NTPサーバ接続、JST同期の進捗状況をLCDの上段に表示しています。

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

スケッチ修正
2022/12/22:曜日を漢字1文字表記(CGRAMにキャラクタパターン書込み)に変更
2022/12/23:delay(1000)では秒表示の飛びが起きたのでdelay(100)に変更、delay値変更にともない、BME280の測定間隔を10秒に変更(起動時、測定タイミングになるまで表示は”0″)
2023/01/15:起動時、WiFi接続ルーチンで取得したIPアドレスをLCD画面に表示

XIAO_ESP32C3_AQM1602_bme280.ino
※ここをクリックするとコード表示を開閉できます。
#include <Wire.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <TimeLib.h>           // https://github.com/PaulStoffregen/Time
#include <ST7032.h>            // https://github.com/tomozh/arduino_ST7032
#include <Adafruit_Sensor.h>   // https://github.com/adafruit/Adafruit_Sensor
#include <Adafruit_BME280.h>   // https://github.com/adafruit/Adafruit_BME280_Library

ST7032 lcd;
Adafruit_BME280 bme;
bool status;
float pressure;
float temp;
float humid;

const char* ssid      = "your ssid";
const char* password  = "your password";
const char* ntpServer = "ntp.nict.jp";
const long  gmtOffset_sec = 32400;
const int   daylightOffset_sec = 0;

struct tm *tm;
//曜日(英字表示)
//static const char *weekStr[7] = {"S","M","T","W","T","F","S"}; //1文字の英字
//曜日(カタカナ表示)
//char weekStr[7] = {0xc6,0xb9,0xb6,0xbd,0xd3,0xb7,0xc4}; //1文字カタカナ(二,ケ,カ,ス,モ,キ,ト)
//char dakuten = 0xde;   //1文字の濁点(゛)

//曜日(漢字キャラクタパターン)@2022/12/22追加
byte nichi[8] = { 0b11111, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b11111, 0b00000 }; //日
byte getsu[8] = { 0b01111, 0b01001, 0b01111, 0b01001, 0b01111, 0b01001, 0b10001, 0b00000 }; //月
byte kayou[8] = { 0b00100, 0b10101, 0b10101, 0b00100, 0b01010, 0b01010, 0b10001, 0b00000 }; //火
byte suiyo[8] = { 0b00100, 0b00101, 0b11110, 0b00110, 0b01101, 0b10100, 0b00100, 0b00000 }; //水
byte mokuy[8] = { 0b00100, 0b00100, 0b11111, 0b00100, 0b01110, 0b10101, 0b00100, 0b00000 }; //木
byte kinyo[8] = { 0b00100, 0b01010, 0b10001, 0b01110, 0b10101, 0b01110, 0b11111, 0b00000 }; //金
byte doyou[8] = { 0b00000, 0b00100, 0b00100, 0b01110, 0b00100, 0b00100, 0b11111, 0b00000 }; //土

int d_mon ;
int d_mday ;
int d_hour ;
int d_min ;
int d_sec ;
int d_wday ;

void setup(){
  Wire.begin();        // I2C初期化
  lcd.begin(16, 2);    // ディスプレイの文字数(16)と行数(2)
  lcd.setContrast(40); // ディスプレイのコントラスト調整(0~63の範囲で調整)
// BME280初期化
status = bme.begin(0x76);
while (!status) {
  lcd.setCursor(0, 0);
  lcd.clear();
  lcd.print("BME280 failed");
}
  lcd.setCursor(0, 0);
  lcd.clear();
  lcd.print("BME280 connected");
delay(1000);
//---------内蔵時計のJST同期(起動時)--------
  wifisyncjst();
//---------漢字_CGRAM登録@2022/12/22追加
lcd.createChar(0, nichi); //日
lcd.createChar(1, getsu); //月
lcd.createChar(2, kayou); //火
lcd.createChar(3, suiyo); //水
lcd.createChar(4, mokuy); //木
lcd.createChar(5, kinyo); //金
lcd.createChar(6, doyou); //土
}


void loop() {
//---------内蔵時計の表示--------
time_t t = time(NULL);
tm = localtime(&t);
d_mon  = tm->tm_mon+1;
d_mday = tm->tm_mday;
d_hour = tm->tm_hour;
d_min  = tm->tm_min;
d_sec  = tm->tm_sec;
d_wday = tm->tm_wday;
  lcd.setCursor(0, 0);
  lcdzeroSup(d_mon);
  lcd.print("/");
  lcdzeroSup(d_mday);
lcd.setCursor(6, 0);
// ----- 曜日(英字 or カタカナ表示)
//lcd.print(weekStr[d_wday]);
//濁点追加(ケ゛、ト゛のみ)、英字の場合は91~95行は不要)
//if ((String(d_wday) == "1") || (String(d_wday) == "6")) {
// lcd.print(dakuten);
//} else {
//lcd.print(" ");   // 残像消去@2022/12/18追加
//}
// ----- 曜日(漢字表示)@2022/12/22追加
       if (String(d_wday) == "0") {
    lcd.write(0); //日
} else if (String(d_wday) == "1") {
    lcd.write(1); //月
} else if (String(d_wday) == "2") {
    lcd.write(2); //火
} else if (String(d_wday) == "3") {
    lcd.write(3); //水
} else if (String(d_wday) == "4") {
    lcd.write(4); //木
} else if (String(d_wday) == "5") {
    lcd.write(5); //金
} else if (String(d_wday) == "6") {
    lcd.write(6); //土
}
  lcd.setCursor(8, 0);
  lcdzeroSup(d_hour);
  lcd.print(":");
  lcdzeroSup(d_min);
  lcd.print(":");
  lcdzeroSup(d_sec);

// ----- BME280センサからデータ取得、測定値を10秒毎に表示 ----- 
  if ((String(d_sec) == "0")  || (String(d_sec) == "10") || 
      (String(d_sec) == "20") || (String(d_sec) == "30") || 
      (String(d_sec) == "40") || (String(d_sec) == "50")) { 
pressure=bme.readPressure() / 100.0F;
temp=bme.readTemperature();
humid=bme.readHumidity();
}
  lcd.setCursor(0, 1);
  lcd.print("    ");  //気圧が4桁→3桁に変わった時の残像消去
  lcd.setCursor(0, 1);
  lcd.print(String(pressure,0));
  lcd.setCursor(6, 1);
  lcd.print(String(temp,1));
  lcd.setCursor(12, 1);
  lcd.print(String(humid,1));

//---------内蔵時計のJST同期(1日1回、12時30分0秒に実行)--------
if ((String(d_hour) == "12") && (String(d_min) == "30") && (String(d_sec) == "0")) {
     wifisyncjst();
}
delay(100);
}


void wifisyncjst() {
//---------内蔵時計のJST同期--------
// WiFi接続
WiFi.begin(ssid, password);
while(WiFi.status() != WL_CONNECTED) {
  lcd.setCursor(0, 0);
  lcd.clear();
  lcd.print("WiFi bigin");
}
delay(1000);
// WiFi接続の表示
  lcd.setCursor(0, 0);
  lcd.clear();
  lcd.print("WiFi connected");
delay(1000);
  lcd.clear();
  lcd.print(WiFi.localIP());  //LCD画面にIPアドレス表示@2023/01/15追加
delay(2000);
// NTPサーバからJST取得
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  lcd.setCursor(0, 0);
  lcd.clear();
  lcd.print("JST synchronized");
delay(1000);
// 内蔵時計の時刻がNTP時刻に合うまで待機
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) {
delay(1000);
}
//WiFi切断
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
  lcd.clear();
}


void lcdzeroSup(int digit){
//---------月、日、時、分、秒が0~9の場合、1桁目を 空白 もしくは 0 に置換--------
if(digit < 10)
lcd.print(' ');   // 現在「空白」
lcd.print(digit);
}
よかったらシェアしてね!
  • URLをコピーしました!
目次