こんにちは、ENGかぴです。
Seeeduino XIAOはArduino環境と互換性あるためSPIライブラリ及びWireライブラリが使用できます。SPIを使用してBME280から温湿度・気圧の情報を取得しI2C通信を使用してLCDに表示する方法をまとめました。
BME280使用 温湿度・気圧センサモジュールキットAE-BME280(秋月電子)を使用してデータを取得します。LCDはAQM1602XA-RN-GBW(秋月電子で購入)を使用しています。
Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
BME280のデータをLCDに表示する
BME280はBOSCH社が製作している温湿度・気圧センサーです。BME280から取得したデータを温湿度・気圧のデータに換算する方法が複雑であるためBOSCH社からAPIが提供されています。APIを使って換算した結果をLCDに表示します。
BME280のデータを取得
BME280はI2CとSPIのどちらでも通信ができるように構成されています。今回はSPIを使用してBME280から温度・湿度・気圧のデータを取得します。取得する方法は下記記事にまとめています。
Seeeduino XIAOのSPIを使ってBME280のデータを取得
LCDの初期化
LCDで文字を表示するために初期化を行います。秋月電子のHPに公開されているAQM1602XA-RN-GBWのLCD資料(参考資料)の例を一部抜粋して説明します。
/* LCDへのコマンド処理 */
void LcdComand(uint8_t cmd){
Wire.beginTransmission(SLAVE_ADRS);
Wire.write(0x00);
Wire.write(cmd);
Wire.endTransmission();
delay(1);
}
LCDへのコマンドはスレーブアドレスを指定し0x00を書き込んだ後に指定コマンドを書き込みます。コマンド書き込みの例としてLcdComand()関数を実装します。下のLCD初期化の例の通り指定するコマンドを引数に指定します。
/* LCD初期化 */
void LcdInit(void){
LcdComand(FUNC1_SET); //8ビットバス・2LINE表示
LcdComand(FUNC2_SET); //拡張コマンド
LcdComand(INT_OSC); //内部周波数調整
LcdComand(CONST_SET); //コントラスト1
LcdComand(PWR_ICON_SET); //コントラスト2
LcdComand(FOLLOWER_SET); //フォロワー制御
LcdComand(FUNC1_SET); //拡張コマンドをオフ
DspClear();
LcdComand(DISP_ONOFF_SET);
delay(2000);
}
データシートの初期化の例に従ってコマンドを書き込んで初期化を行いますが、基本的に初期化の例の通りの値で問題ありません。
コントラスト設定を薄くしようとC5を0にしてみましたが文字が見えなくなるほど薄くなってしまったため変更の必要はないと感じています。Contrast Setに0x70、Power/ICON/Contrast controlを0x54をセットにしました。
ST7032のデータシートを確認するとI2C使用時のレイアウトではOPF1、OPF2は0とするためFonに1をセットすると内部フォロアが有効になります。コントラストは内部フォロアも関係するためFollower controlのRab0~Rab3の調整が有効なので、Follower controlに0x6Bをセットしました。
LCDに文字を表示
/* LCDに文字を表示 */
void LcdWriteData(uint8_t *data, uint8_t sz){
Wire.beginTransmission(SLAVE_ADRS);
Wire.write(0x40);
Wire.write(data, sz);
Wire.endTransmission();
}
//使用例
LcdWriteData( &initmoji[0][0], sizeof(initmoji[0])); //1段目の表示
LCDに文字を書き込むためLcdWriteData()関数を実装します。第1引数に表示したい文字を示すアドレス、第2引数にサイズを指定します。
initmojiの配列に表示したい文字を格納しているものとして使用例を示しています。第1引数にinitmojiの先頭アドレスを指定し、第2引数にinitmojiのデータ数を指定しています。
LcdWriteData()関数でスレーブアドレスをスタートで送信した後にcontrol byteを指定しますが2-line interface protocolに従って書き込みアドレスと文字データを指定します。
control byte以降に制御コードを書き込まないのでCoに0をセットしRSがHになるので0x40になります。R/WはW固定なのでLになります。
control byteの後に送信する文字をセットしてサイズ分書き込み、最後にSTOP状態の通知のためendTransmissionを送信します。
動作確認
BME280からSPIを使用してデータを取得してI2Cを使用してLCDに温湿度・気圧の情報を表示します。LCDの1段目に温度と湿度を表示し2段目に気圧を表示ます。
動作確認用の回路
電源を投入するとLCDに温度情報00.0、湿度情報50.0、気圧情報1000.0を表示するようにしています。LCDに文字が入りきらないので温度をT:湿度をH:気圧をP:と表現しています。電源投入から5秒経過すると測定結果が確定し各種データが表示されます。
以降は5秒経過毎にBME280からデータを取得してLCD表示を更新します。
動作結果
電源を投入から測定開始するまでにそれぞれの初期値を表示していますが、5秒後に温度と湿度及び気圧がLCDに表示できていることを確認しました。またシリアルモニタにも測定データを表示していますがLCDの表示と一致していることを確認しています。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <TimerTC3.h>
#include <SPI.h>
#include <Wire.h>
#include "bme280.h"
#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define GET_SENSOR_MAX 500
#define SLAVE_ADRS 0x3E
#define LINE1_ADRS 0x40
#define LINE2_TOP (0x40 +0x80)
#define FUNC1_SET 0x38
#define FUNC2_SET 0x39
#define INT_OSC 0x14
#define CONST_SET 0x70
#define PWR_ICON_SET 0x56
#define FOLLOWER_SET 0x6B
#define CLR_DISP 0x01
#define DISP_ONOFF_SET 0x0C
#define PIN_DO1 0
#define SPI_CS_ON digitalWrite(PIN_DO1,LOW)
#define SPI_CS_OFF digitalWrite(PIN_DO1,HIGH)
/* 変数宣言 */
bme280_dev bme280main;
bme280_data sensor_data;
int8_t cnt10ms;
int16_t timsensor = TIME_OFF;
uint8_t initmoji[2][16] ={"T: 00.0 H: 50.0","P:1000.0 "};
/* プロトタイプ宣言 */
void Bme280Init(void);
int8_t user_spi_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len);
int8_t user_spi_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len);
void user_delay_ms(uint32_t period);
int8_t stream_sensor_data_forced_mode(struct bme280_dev *dev);
void TimerCnt(void);
void LcdInit(void);
void LcdComand(uint8_t cmd);
void LcdWriteData(uint8_t *data, uint8_t sz);
void DspLine2Top(void);
void DspClear(void);
void ShowData(void);
void setup(){
pinMode(PIN_DO1, OUTPUT); //CSとして使用するDOを設定
SPI_CS_OFF; //Hレベルにする(アクティブLなのでHを初期値とする)
SPI.begin();
SPI.setBitOrder(MSBFIRST); //最上位ビットから順番に送信
SPI.setDataMode(SPI_MODE0); //SPIモードを0にする
//SPI.setClockDivider(SPI_CLOCK_DIV4); //クロックを4分周(基本は4MHz)
Wire.begin();
TimerTc3.initialize(1000);
TimerTc3.attachInterrupt(TimerCnt);
Serial.begin(115200);
Bme280Init();
LcdInit();
timsensor = GET_SENSOR_MAX;
}
void loop() {
if( cnt10ms >= BASE_CNT ){ //10msごとにここに遷移する
cnt10ms -=BASE_CNT;
if( timsensor > TIME_UP ){
timsensor--;
}
if( timsensor == TIME_UP ){
timsensor = GET_SENSOR_MAX;
stream_sensor_data_forced_mode(&bme280main);
ShowData();
}
}
}
/* callback function add */
void TimerCnt(void){
++cnt10ms;
}
/* Bme280 api use function add */
int8_t user_spi_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len){
int8_t rslt = 0;
SPI_CS_ON; //CSをLレベル
SPI.transfer(reg_addr); //対象アドレスを送信(書き込み)
for(uint8_t i=0; i < len; i++){
SPI.transfer(*reg_data); //対象データを送信(書き込み)
++reg_data;
}
SPI_CS_OFF; //CSをHレベル
return rslt;
}
/* Bme280 api use function add */
int8_t user_spi_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len){
int8_t rslt = 0;
SPI_CS_ON; //CSをLレベル
SPI.transfer(reg_addr); //対象アドレスを送信(書き込み)
for( uint8_t i=0;i < len; i++ ){
*reg_data = SPI.transfer(0xFF); //ダミーで0を送信
++reg_data;
}
SPI_CS_OFF; //CSをHレベル
return rslt;
}
/* Bme280 sensor iniialize */
void Bme280Init(void){
bme280main.dev_id = 0;
bme280main.intf = bme280_intf::BME280_SPI_INTF;
bme280main.read = user_spi_read;
bme280main.write = user_spi_write;
bme280main.delay_ms = user_delay_ms;
bme280_init(&bme280main);
stream_sensor_data_forced_mode(&bme280main);
}
/* Bme280 api use function add */
void user_delay_ms(uint32_t period){
delay(period);
}
/* Bme280 api use function add */
int8_t stream_sensor_data_forced_mode(struct bme280_dev *dev){
int8_t rslt;
uint8_t settings_sel;
uint32_t req_delay;
dev->settings.osr_h = BME280_OVERSAMPLING_1X;
dev->settings.osr_p = BME280_OVERSAMPLING_16X;
dev->settings.osr_t = BME280_OVERSAMPLING_2X;
dev->settings.filter = BME280_FILTER_COEFF_16;
settings_sel = BME280_OSR_PRESS_SEL | BME280_OSR_TEMP_SEL | BME280_OSR_HUM_SEL | BME280_FILTER_SEL;
rslt = bme280_set_sensor_settings(settings_sel, dev);
req_delay = bme280_cal_meas_delay(&dev->settings);
//while (1) {
rslt = bme280_set_sensor_mode(BME280_FORCED_MODE, dev);
dev->delay_ms(req_delay);
rslt = bme280_get_sensor_data(BME280_ALL, &sensor_data, dev);
//}
return rslt;
}
/* LCD初期化 */
void LcdInit(void){
LcdComand(FUNC1_SET); //8ビットバス・2LINE表示
LcdComand(FUNC2_SET); //拡張コマンド
LcdComand(INT_OSC); //内部周波数調整
LcdComand(CONST_SET); //コントラスト1
LcdComand(PWR_ICON_SET); //コントラスト2
LcdComand(FOLLOWER_SET); //フォロワー制御
LcdComand(FUNC1_SET); //拡張コマンドをオフ
DspClear();
LcdComand(DISP_ONOFF_SET);
LcdWriteData( &initmoji[0][0], sizeof(initmoji[0])); //1段目の表示
DspLine2Top(); //カーソル移動
LcdWriteData( &initmoji[1][0], sizeof(initmoji[1])); //2段目の表示
delay(2000);
}
/* LCDへのコマンド処理 */
void LcdComand(uint8_t cmd){
Wire.beginTransmission(SLAVE_ADRS);
Wire.write(0x00);
Wire.write(cmd);
Wire.endTransmission();
delay(1);
}
/* LCDに文字を表示 */
void LcdWriteData(uint8_t *data, uint8_t sz){
Wire.beginTransmission(SLAVE_ADRS);
Wire.write(0x40);
Wire.write(data, sz);
Wire.endTransmission();
}
/* 2段目にカーソル移動 */
void DspLine2Top(void){
LcdComand(LINE2_TOP);
delayMicroseconds(40);
}
/* ディスプレイクリア */
void DspClear(void){
LcdComand(CLR_DISP);
delayMicroseconds(40);
}
/* 表示データ作成 */
void ShowData(void){
int32_t temp;
int16_t temp_d;
int16_t temp_f;
int32_t humi;
int16_t humi_d;
int16_t humi_f;
int32_t press;
int16_t press_d;
int16_t press_f;
DspClear();
temp = sensor_data.temperature;
if( sensor_data.temperature < 0 ){
temp = abs(sensor_data.temperature );
initmoji[0][2] = '-';
}
else{
initmoji[0][2] = ' ';
}
temp_d = temp / 100;
temp_f = temp % 100;
initmoji[0][3] = temp_d /10 + 0x30;
initmoji[0][4] = temp_d %10 + 0x30;
initmoji[0][5] = '.';
initmoji[0][6] = temp_f /10 + 0x30;
humi = sensor_data.humidity;
humi_d = humi / 1024;
humi_f = humi % 1024;
if( humi_d / 100 == 1 ){
initmoji[0][10] = 0x31;
}
else{
initmoji[0][10] = ' ';
}
initmoji[0][11] = humi_d /10 + 0x30;
initmoji[0][12] = humi_d %10 + 0x30;
initmoji[0][13] = '.';
initmoji[0][14] = humi_f /100 + 0x30;
LcdWriteData( &initmoji[0][0], sizeof(initmoji[0])); //1段目の表示
DspLine2Top();
press = sensor_data.pressure;
press_d = sensor_data.pressure / 10000;
press_f = sensor_data.pressure % 10000;
if( press_d / 1000 == 0 ){
initmoji[1][2] = ' ';
}
else{
initmoji[1][2] = 0x31;
}
initmoji[1][3] = (press_d % 1000) / 100 + 0x30;
initmoji[1][4] = (press_d % 100) / 10 + 0x30;
initmoji[1][5] = (press_d % 100) % 10 + 0x30;
initmoji[1][6] = '.';
initmoji[1][7] = press_f / 1000 + 0x30;
LcdWriteData( &initmoji[1][0], sizeof(initmoji[1])); //2段目の表示
Serial.print("Temperature:");
Serial.print((double)sensor_data.temperature/100);
Serial.print("[℃]");
Serial.print(" Pressure:");
Serial.print((double)sensor_data.pressure/10000);
Serial.print("[hPa]");
Serial.print(" Humidity:");
Serial.print((double)sensor_data.humidity/1024);
Serial.println("[%]");
}
上記ソースコードのみでは動作しません。Seeeduino XIAOのSPIを使ってBME280のデータを取得で追加しているBME280APIをArudinoプロジェクトソースフォルダに追加する必要があります。
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
PR:アクセンチュアの転職なら【コンサルアクシスコンサルティング】
最後まで読んでいただきありがとうございました。
温度は室温計とほぼ同じでした。気圧はスマホで確認すると1003mBar[hPa]であったためそれなりに測定できていると思います。湿度についてはスマホの情報では68%とずれがありましたが屋外と屋内の差が出ているだろうという感じです。