温湿度計の修理

今回は電子工作ネタです。
と書くと、疑似電波時計はどうなったのかと言われそうですが、いろいろとトラブルが発生していて、3歩進んで2歩下がるといった状態です。まず、PICの品番を16F19155から省電力の16LF19155に変更したところ、先方のミスでモノが届かないというトラブルがあり、ようやく入手できたと思ったら、どうも両者に微妙な違いがあるらしくプログラムがそのまま動かなかったり、発注した基板にミスがあって基板を再発注したり、TWELITEのプログラム用のTWELITE Rが壊れたりといろいろと苦しんでいます。ようやく何とかなりそうになってきたので、近々記事にできると思っています。
今回は、タイトルにあるように温湿度計を修理したという話です。温湿度計と言っても、市販のものではなく、こちらもTWELITEを使って自作したもので、部屋の外の温度と、部屋の中の温度を測って表示できるようになっているものです。簡単にシステムの概要を書くと、TWELITEの子機を2つ用意して、それに温湿度センサーをつなげて、無線タグアプリで温湿度を送信し、TWELITEの親機をARDUINO MEGAを接続し、測定データを受けて、グラフィックスLCDに表示するという仕組みになっています。よく壊れるのは外に置いてある子機に繋いでいるセンサーで、これまで2度ほど交換していました。今回もセンサーの故障と思って、交換したところ、一瞬正常に動作したようになったのですが、すぐに電池がなくなって動かなくなってしまいました。最初は、電池も寿命だったのだろうと思い、電池を交換したところまたすぐに電池切れになったので、どうやらTWELITE本体がダメになっているらしいということに気づきました。
そこで、TWELITE本体を交換して終わりにしようと思って、ふたを開けたところ、ソケットに刺さっているとばかり思っていたTWELITE DIP本体は基板に直付けされていて、簡単に交換できないことが判明してしまいました。

室外に置いていたTWLITEとセンサー。TAKACHIのケース付属の基板に直付けされている。

さて困ったと思い、モノワイヤレスのHPを見に行くと、温湿度計を製作した当時からいろいろと変わっていることがわかりました。

無線タグアプリに温湿度センサーが設定されていない

そうなんです、昔はTWELITEに温湿度センサーをI2Cで接続し、無線タグアプリで温湿度を間欠送信するということができたのに、今は、温湿度センサーについてはTWELITE PALという商品ができていて、そちらに環境センサーPALというARDUINOでいうところのシールドのようなものを装着して使うようになっている。アプリも無線タグアプリではなく専用のPALアプリというのを使うようになっている。
もちろん、無産タグアプリの旧バージョンもまだダウンロードできるようになっていて、こちらを使ってまた同じものを作れば動かすことはできるが、せっかくなので、今回はTWELITE PALを使ってみることにした。
購入するのは、TWELITE PAL 本体、環境センサーPALと PAL専用ケースの3点。今回は秋月電子で購入した。

購入したTWELITE PALと環境センサーPAL(左)とPAL用ケース(中)、昔のセンサー(右)に比べるとずいぶんとコンパクト

TWELITE PALは本体にコイン電池ホルダーが装着されていて、電池を入れるとPALアプリが動くようになっている。

TWELITE PAL(写真はRED PAL)

TWELITE PALも通常のTWELITEと同様に昔からあるBLUEと新し目で高出力のREDの2タイプがある。本当は室外に置くので高出力のREDにしたかったが、かなりお高いので、BLUEの方にした。まあ、今までもBLUEで問題なく通信できていたので何とかなると思っている。

親機のアプリを書き換える

TWELITEをPALにして、アプリもPALアプリにすると、親機側もそれに応じたアプリに変える必要がある。と言ってもPAL専用のアプリではなく、共通の親機アプリ、App_Wingsというのを使うらしい。昔は無線タグアプリなら、無線タグアプリ専用の親機アプリがあったのだが、共通化されてしまっている。当然、温湿度計のTWELITEには昔の無線タグ用親機アプリが入っているので、これを書き換える必要がある。
と思って、こちらもふたを開けたところ、またしてもTWELITE DIPは基板に直付けされてしまっている。全く!! と思ったが、MEGA用のプロトタイプシールドにTWELITE、などが搭載されていて、その上に、LCDシールド、そして、LCDディスプレイと4階建ての構造になっていてTWELITEをソケットを使って装着すると高さが高すぎて他のシールドが装着できないというのを思い出した。
さてどうしたものかと思案した。解決方法は2つ。ひとつは、TWELITE PALを無線タグアプリで動かして、親機のアプリは書き換えないで済ます方法。もう一つは、プロトタイプシールドにTWELITEのプログラム書き込み用にソケットをはんだ付けして親機アプリを書き換える方法。
簡単なのは前者の方法。一応、TWELITE PALの方のアプリを書き換えて旧版の無線タグアプリをインストールしてみた。すると、シグナルは送られてくるが、センサー種別が正しくないので温湿度のデータが送られていない。センサー種別を変更するにはインタラクティブモードに入る必要があるのだが、デフォルトの無線タグアプリは1秒間隔で送信をしているので、シリアル通信で”+++”を送るという方法が使えないらしい。電源投入時にM2をGNDに落としてやる必要があるらしい。

TWELITE PALに無線タグアプリを書き込んだところ。通常はすぐにインタラクティブモードに入れるが、この場合は特殊な方法が必要。

この時点で、面倒になった。わざわざ、古いアプリを使ってやるよりも、新しいアプリを使った方が、今後室内で使っている方が壊れた時も更新がしやすいしと思い、結局後者の方法を取ることにした。

TWELITEのプログラムは7Pソケットで

TWELITEプログラム用のTWELITE Rは初代のR、2代目R2、3代目R3と現在で3代目になっている。3代目のR3は、従来のR、R2と異なり、TWELITE DIPをそのままソケットに刺すのでなく、7Pソケットで接続してプログラムするようになっている。

最新のTWELITE R3 DIP用ソケットがなくなりシンプルなつくりになっている

冒頭で、TWELITE Rが壊れたと書いたが、おかげで買いなおすことになったが、DIPを使うことが多いので、R3よりも使いやすそうなR2にした。
こちらは、見た目は初代Rと大きく変わらないが、スイッチやジャンパーピンなどが省略されて少しシンプルになっている。

TWELITE R2 初代よりもシンプルだが、DIPが直刺しできるようになっている

今回は、DIPをプログラムし直すのだが、基板に直付けされているので、7Pソケットを使ってプログラムをする。プログラムをできるようにするためには、GND、VCC、TX、RX、PRG、SETの7ピンをTWELITE DIPのGND(1番ないし14番)、VCC(28番)、TX(10番)、RX(3番)、SPIMISO_PRG(7番)、SWSET(15番)ピンに接続する。ここで気を付けないといけないのは、TWELITE R2(R3も同じ)のTXはTWELITE DIPのTXと接続するようになっていること。通常のシリアル通信ではTXはRXと接続するようになっているがこの場合は同じTX同士を接続する。同じようにRX同士を接続する。また、SWSETに関しては、モノワイヤレスのHPのTWELITE R2の所には特に接続先が示されていなかったが、念のため、環境センサーPALの回路図を参考に15番ピンに接続しておいた。これで親機側のプログラムが可能になったので、App_Wingを書き込んでみた。

TWELITE R2の7Pソケットを使ってプログラムを書き込む。

そうすると、何もデータが送られてこない。おかしいと思って、TWELITE STICKにApp_Wingを書き込んでみると正常に動作する。よくよくモノワイヤレスのHPを読むと子機と親機のアプリケーションIDとチャンネルを同じにしないといけないらしい。そこで、STICKの画面を見て、アプリケーションIDとチャンネルを書き換えてみたところちゃんと受信できるようになった。アプリケーションIDは”67726305”でチャンネルは15だった。

正常に受信できているときのTWELITE STICKのインタラクティブモードの画面

送られてくるデータは無線タグアプリを書き込んでいるもう一つの子機からのデータも送られてきているが、データの長さが違う。

80000000から始まるのが2つの子機からのデータ。PALアプリと無線タグアプリからのデータなので長さが異なる、長い方がPALアプリのデータ

データの意味はPALアプリの方は簡単に見つかった。最初の4バイトが中継器のID、中継器がない時は80000000になる。次の1バイトが信号強度を表すLQI、次の2バイトが続き番号(動作開始から信号を送るたびにひとつ増えて、2バイト分でオーバーフローしてゼロに戻る)、次の4バイトが子機のID(今回は810EFF6D)、次の1バイトが子機の論理ID、次の1バイトがセンサ種別、次の1バイトがPAL基板バージョンとPAL基板ID、次の1バイトがセンサーデータ数(ここでは5)、ここからは各センサのデータが5つ続いて、センサ毎に次のデータが送られてくる。1つ目が各種情報ビット、2つ目がデータソース、3つ目が拡張バイト、4つ目がデータ長でデータのバイト数、5つ目がデータ。データの中身は最初の2つが電圧(2つにどういう違いがあるのかよくわからない)。3つ目が温度、4つ目が湿度、5つ目が照度の順である。最後の2バイトはチェックサム。なので、温度を知りたければ、32-33バイト目、湿度を知りたければ38-39バイト目のデータを取り出せばよいということになる。
一方の無線タグアプリの方は、表向きのHPからは無線タグアプリの温湿度センサーでの扱いがなくなっているため、ネットで検索して意味を見つけた。
子機の論理IDまではPALアプリと同じで、その次の1バイトがセンサー種別(温湿度センサーなら31)、次の1バイトが子機の電源電圧、次の2バイトがADC1のデータ、次の2バイトがADC2のデータ、次の2バイトが温度、次の2バイトが湿度、最後の1バイトがチェックサム。なので、こちらの場合は、温度は15-16バイト、湿度は17-18バイト目のデータを取り出せばよいということになる。
温湿度のデータは16ビットの符号付き整数で実際の温度、湿度の100倍の値が入っている。
そして、ここでちょっとややこしいのがシリアルで送られてくるデータは各値のASCIIコードが送られてくる。つまり、データが1バイトの”01”なら、送られてくるシグナルは、0x30、0x31という具合。従って、2バイト分のデータならシリアルシグナルでいうと4バイト分になっているということ。マイコン同士で通信するなら素直にバイトデータをそのまま送ればよいようなものだが、PCの画面などに表示すると意味不明な記号が表示されることになるので、見やすくするためにこういう仕様になっていると思われる。
なので、受け取ったArduino側で数値に直してやる必要がある。これがPythonなら簡単に変換可能なのだが、Arduinoは10進、16進変換も貧弱で自分でプログラムしてやる必要がある。やり方は、まず、受け取ったASCIIコードを数字の0~9か、英字のA~Fかを判定し、0~9なら、0x30を引いて、A~Fなら、0x41を引いて10を足す。これで1桁の変換ができたので、これを16倍して次の桁を計算して足す。足したのをまた16倍して、次の桁を足し、さらに同じことを繰り返すと2バイト分のデータが変換できる。ただ、これだけでは、符号なしの整数になってしまうので、数値を32768と比較しそれよりも大きければ65536を引いてマイナスの値に変換するという操作をします。Arduinoのプログラム(スケッチ)のその部分(抜粋)です。

for (int j=0;j<4;j++){
          temp = d[22+j+k];
          if (('0' <= temp) && (temp <= '9')){
            n = temp - '0';
          }else if (('A' <= temp) && (temp <='F')){
            n = temp - 'A' + 10;
          }
          x = x *16 + n;
}
if (x>=32768){
          x = x - 65536;
}

配列d[]には、TWELITEから受け取ったシリアルデータが格納されています。kは子機のSIDを関数indexOf()で検索して結果が入っています。上の例は無線タグアプリの例ですので、そこから22バイト目から温度データが始まります。変数jでループして4回回って全部を取り出します。取り出した値は変数tempにいったん格納し、数字の0~9か、英字のA~Fかを判定して、数値に変換、xに1桁ずつ代入していきます。これで符号なし整数に変換できます。その下のif分で32768と比較し、大きければ65536を引いて符号付き整数に変換します。このため、変数xは、符号付き32ビットのlongで宣言をしてあります。
なお、PALアプリの場合は、温度のデータは”48+K”からの4バイトとなります。同様に湿度は”60+k”からの4バイト、無線タグアプリの湿度は”26+k”からの4バイトです。

プロトタイプシールドとArduino MEGAを接続し、通信データを取得する

これで、温湿度のデータを取得することができたので、後はLCDに表示をするということになります。

LCDに温湿度を表示する

LCDはAliExpressで買った、7インチのカラーグラフィックスLCDで、Arduino MEGAとは専用のシールドで接続します。シールドもAliExpressで購入しました。
今なら、こんな大げさなことをせず、ラズパイにグラフィックスLCD電子ペーパーを繋いで作るのですが、当時はこの方法が一番見やすい表示をさせる方法でコストもそれほどかからないので良い方法だと思って作りました。

プロとシールドの上にLCD用シールドを載せて、LCDを接続したところ。LCDは一番下

さて、LCDに表示をさせるのに、UTFTというライブラリを使いました。下記のサイトからZIP形式のライブラリをダウンロードし、Arduino IDEでスケッチ->ライブラリをインクルードー>.ZIP形式のライブラリをインストールでダウンロードしたファイルを指定してインストールします。
www.rinkydinkelectronics.com
使い方は、
myGLCD.InitLCD();でイニシャライズ。
myGLCD.clrScr();でスクリーンのクリア
myGLCD.setBackColor(0, 0, 0);でバックグラウンドのカラーを指定(ここでは黒)
myGLCD.setColor(0,191,255);でフォントのカラーを指定(ここではディープスカイブルー)
myGLCD.setFont(Grotesk32x64);でフォントの指定。今回は、英数字の表示にGrotesk32x64を大きな数字の表示にSevenSegment96x144Numを使いました。フォントを使うには、上記のUTFTのサイトに行って、フォントのページからそれぞれのフォントデータをダウンロードし、Arduinoのスケッチと同じフォルダに置くだけです。
LCDにフォントを表示するには、myGLCD.print("-",106,60)のようにします。はじめの”-”が表示するフォント、次がx座標、最後がy座標です。
まず初めに、室内と室外を区別するために、”In”と”Out”、それから温度の”oC"と湿度の”%”をそれぞれ室内用と室外用の位置に表示。そして、温度は小数点1位まで表示するので小数点も表示しておきます。あとは、取得した温湿度を1分おきに書き換えるだけです。
温湿度は、32ビットの符号付き整数です。まず初めに、符号なし整数の形式から、符号付きに変換するところで、プラスかマイナスかが判定できるので、マイナスなら温度の中の位のところに”-”を表示します。数字の表示フォントはSevenSegment96x144Numを使うのですが、これには数字しかないので、マイナスだけは
Grotesk32x64を使いました。また、寒冷地ではないので気温が外でも零下10℃以下になることはないと考え、マイナスの表示位置は10の位の位置にしました。上のプログラムのマイナス判定の所に、フォント表示のコマンドを追記しています。なお、寒いということがわかりやすいようにフォントの色は青にしています。

myGLCD.setColor(0,0,0);
myGLCD.fillRect(106,0,206,144);
myGLCD.setColor(255,255,255);
if (x>=32768){
  x = x - 65536;
  myGLCD.setColor(0,0,255);  //Blue
  myGLCD.setFont(Grotesk32x64);
  myGLCD.print("-",106,60);       
}
myGLCD.setFont(SevenSegment96x144Num);

なお、マイナスを表示する位置は十の位の場所で、まず黒で十の位の部分を塗りつぶしてから表示しています。十の位が0のときには何も表示しないので、先に表示を消しておくためです。
温度がプラスの場合は、室内の場合は、温度が16℃以下の場合にフォントの色を青に、室外の場合は温度が5℃以下の場合はフォントの色を青にし、室内、室外とも温度が30℃以上の場合はフォントの色をオレンジにしています。それ以外の場合は色は白です。室内の場合の例を下記します。

if (x < 1600){
  myGLCD.setColor(0,0,255);  //Blue
}else if (x > 3000){
  myGLCD.setColor(255,0,0);  //Red
}else {
  myGLCD.setColor(255,255,255);  //White
}

後は、温度を中の位、1の位、小数点以下第一位の3つに分解してそれぞれを表示します。表示するときに注意するのは十の位が0の場合は表示しないようにしないといけないことです。そうしないと、”08.5℃”のような間抜けな表示になってしまいます。

n1 = String(int(x/10) % 10);
n2 = String(int(x/100) % 10);
n3 = String(int(x/1000) % 10);
if (n3!="0"){
  myGLCD.print(n3,106,0);
}else if (x<0){
  myGLCD.setColor(0,0,0);
  myGLCD.fillRect(106,0,206,144);
  myGLCD.setColor(255,255,255);
}
myGLCD.print(n2,206,0);
myGLCD.print(n1,322,0);
myGLCD.setColor(255,255,255);

湿度の表示も基本的に同じです。湿度は、小数点以下は切り捨てて十の位と、一の位だけを表示しています。湿度の場合は、湿度が70%以上で青、30%以下でオレンジ、それ以外が白色で表示するようにしています。また、湿度が10%を切ることは常識では考えられないですが、一応計測ミスという可能性もあるので、1桁の表示にも対応するようにしています。

x = 0;
for (int j=0;j<4;j++){
  temp = d[26+j+k];
  if (('0' <= temp) && (temp <= '9')){
    n = temp - '0';
  }else if (('A' <= temp) && (temp <='F')){
    n = temp - 'A' + 10;
  }
  x = x *16 + n;
}
if (x>10000){
  x = 10000;
}
n1 = String(int(x/10) % 10);
n2 = String(int(x/100) % 10);
n3 = String(int(x/1000) % 10);
if (x > 7000){
  myGLCD.setColor(0,0,255);  //blue
}
if (x < 3000){
  myGLCD.setColor(199,97,20);  //rawsienna
}else{
  myGLCD.setColor(255,255,255);  //White
}
if (n3!="0"){
  myGLCD.print(n3,520,0);
}
myGLCD.print(n2,620,0);
x = 0;
myGLCD.setColor(255,255,255);  //white

気圧を測定して表示する

上の方のプロトシールドが写っている写真を見てもらうとわかるのだが、実は、TWELITE以外に2つ基板が乗っかっている。一つはロジックレベルコンバーターで、TWELITEが3.3V系なのに対し、Arduino MEGAが5V系なので、そのまま接続するとTWELITE側が壊れてしまうのでロジックレベルコンバーターを咬ませている。そしてもう一つがBMP280。以前にTWELITEの記事でBME280を動かしてみることをやってみたが、こちらに載っているのはBMP280という廉価版で湿度センサーが省略されているもの。
alasixosaka.hatenablog.com
alasixosaka.hatenablog.com
alasixosaka.hatenablog.com
alasixosaka.hatenablog.com
alasixosaka.hatenablog.com

はじめ、室内の温湿度をこいつで測ってやろうと思って買ったのだが、湿度センサーがないタイプと分かって、気圧だけを測定するために使っている。BMP280はI2CでArduinoと繋いで、ライブラリで値を取得している。ライブラリはいくつかあるのだが、使っているのは、こちら。
github.com
こちらのライブラリも先ほどのUTFTと同様にZIP形式でダウンロードして、Arduino IDEにインストールして使用する。
BMP280の使い方は、過去の記事に詳しいので今回は省略する。また、ライブラリの細かい使い方はリンクを見ていただくとして、イニシャライズはこんな感じ。

BME280.readCompensationParams();
BME280.writeStandbyTime(tsb_0p5ms);        // tsb = 0.5ms
BME280.writeFilterCoefficient(fc_16);      // IIR Filter coefficient 16
BME280.writeOversamplingPressure(os16x);    // pressure x16
BME280.writeOversamplingTemperature(os2x);  // temperature x2
BME280.writeOversamplingHumidity(os1x);     // humidity x1  
BME280.writeMode(smNormal);

測定する部分はこんな感じ。湿度センサーはついていないので、測定値はゼロになる。

BME280.readMeasurements();
temp      = BME280.getTemperature();
humidity  = BME280.getHumidity();
pressure  = BME280.getPressure();
pressureMoreAccurate = BME280.getPressureMoreAccurate();  
tempMostAccurate     = BME280.getTemperatureMostAccurate();
humidityMostAccurate = BME280.getHumidityMostAccurate();
pressureMostAccurate = BME280.getPressureMostAccurate();    

そして、湿度を表示する部分はこんな感じ。フォントは小さい方のGrotesk32x64を使い、気圧が1010hPa以上ならオレンジ、1000hPa未満なら青、それ以外は白色で表示するようにしている。

myGLCD.setFont(Grotesk32x64);
da2 = String(pressure);
if (pressure>1010){
  myGLCD.setColor(255,153,18);  //cadmiumyellow
}
if (pressure<1000){
  myGLCD.setColor(0,255,255);  //cyan aqua
}
myGLCD.print(da2, 300, 380);  
myGLCD.setColor(255,255,255);  //white    

プログラムの全文です。タイマーライブラリを読み込んで、一秒ごとに割り込みをかけて、変数をカウントアップをしています。カウント値は3つで、countiが室内用の温湿度センサーからの値を最後に読み込んでからの経過時間。countoは同じく室外用の温湿度センサーの値を最後に読み込んでからの経過時間。TWELITE子機からのデータはいずれも1分おきに送られることになっているが、電波障害や電池切れで2分以上データが来なかった場合は、”ー.ーー”と表示するようにしている。また、最後の一つはcountpで気圧を1分間隔で測定するためのカウンタに使っている。countpが60を越えたら気圧を測定して表示し、ゼロにリセットしている。割り込み処理ルーチンは最後のvoid time_count(void){ 以下の部分です。それ以外はあらかた上で説明したのでわかると思います。

#include <stdio.h>
#include <BME280_MOD-1022.h>
#include <Wire.h>
#include "MsTimer2.h"
#include <UTFT.h>

#define BME280_ADDRESS 0x76
extern uint8_t SevenSegment96x144Num[];
extern uint8_t Grotesk32x64[];
unsigned long counti = 0;
unsigned long counto = 0;
unsigned long countp = 0;
char d[100];
int i = 0;

float temp, humidity,  pressure, pressureMoreAccurate;
double tempMostAccurate, humidityMostAccurate, pressureMostAccurate;
char buffer[80];

void time_count(void);
UTFT myGLCD(ITDB50,38,39,40,41);
void setup() {
  Serial2.begin(115200);
  Wire.begin();
  BME280.readCompensationParams();

  BME280.writeStandbyTime(tsb_0p5ms);        // tsb = 0.5ms
  BME280.writeFilterCoefficient(fc_16);      // IIR Filter coefficient 16
  BME280.writeOversamplingPressure(os16x);    // pressure x16
  BME280.writeOversamplingTemperature(os2x);  // temperature x2
  BME280.writeOversamplingHumidity(os1x);     // humidity x1
  
  BME280.writeMode(smNormal);
  myGLCD.InitLCD();

  myGLCD.clrScr();
  myGLCD.setBackColor(0, 0, 0);
  myGLCD.setFont(Grotesk32x64);
  myGLCD.setColor(0,191,255);    //deep skyblue1
  myGLCD.print("In", 0, 0);
  
  myGLCD.print("o", 424,60);
  myGLCD.print("C", 454,80);
  myGLCD.print("%", 720,80);
  myGLCD.setColor(0,255,0);   //Green
  myGLCD.print("Out", 0, 190);
  
  myGLCD.print("o", 424,250);
  myGLCD.print("C", 454,270);
  myGLCD.print("%", 720,270);
  myGLCD.setColor(255,69,0);   //OrengeRed1
  myGLCD.print("Press", 0, 380);
  myGLCD.print("hPa", 650, 380);
  myGLCD.setColor(255,255,255);  //White
  myGLCD.print(".", 298, 80);
  myGLCD.print(".", 298,270);
  MsTimer2::set(1000, time_count);
  MsTimer2::start();
}

void loop() {
  short n;
  long x = 0;
  char temp;
  String str;
  String n1;
  String n2;
  String n3;
  int k;
  String da2;

  double temp_act = 0.0, press_act = 0.0,hum_act=0.0;
  signed long int temp_cal;
  unsigned long int press_cal,hum_cal;

  if (Serial2.available()){
    d[i] = Serial2.read();
    if (d[i]==13){
      i=0;
      str = String(d);
      k = str.indexOf("81009B3C");
      if (k!=-1){
        counti=0;
        for (int j=0;j<4;j++){
          temp = d[22+j+k];
          if (('0' <= temp) && (temp <= '9')){
            n = temp - '0';
          }else if (('A' <= temp) && (temp <='F')){
            n = temp - 'A' + 10;
          }
          x = x *16 + n;
        }
        myGLCD.setColor(0,0,0);
        myGLCD.fillRect(106,0,206,144);
        myGLCD.setColor(255,255,255);
        if (x>=32768){
          x = x - 65536;
          myGLCD.setColor(0,0,255);  //Blue
          myGLCD.setFont(Grotesk32x64);
          myGLCD.print("-",106,60);
        }
        myGLCD.setFont(SevenSegment96x144Num);
        n1 = String(int(x/10) % 10);
        n2 = String(int(x/100) % 10);
        n3 = String(int(x/1000) % 10);
        if (x < 1600){
          myGLCD.setColor(0,0,255);  //Blue
        }
        else if (x > 3000){
          myGLCD.setColor(255,0,0);  //Red
        }else {
          myGLCD.setColor(255,255,255);  //White
        }
        if (n3!="0"){
          myGLCD.print(n3,106,0);
        }
        myGLCD.print(n2,206,0);
        myGLCD.print(n1,322,0);
        myGLCD.setColor(255,255,255);
        x = 0;
        for (int j=0;j<4;j++){
          temp = d[26+j+k];
          if (('0' <= temp) && (temp <= '9')){
            n = temp - '0';
          }else if (('A' <= temp) && (temp <='F')){
            n = temp - 'A' + 10;
          }
          x = x *16 + n;
        }
        if (x>10000){
          x = 10000;
        }
        n1 = String(int(x/10) % 10);
        n2 = String(int(x/100) % 10);
        n3 = String(int(x/1000) % 10);
        if (x > 7000){
          myGLCD.setColor(0,0,255);  //blue
        }
        if (x < 3000){
          myGLCD.setColor(199,97,20);  //rawsienna
        }else{
          myGLCD.setColor(255,255,255);  //White
        }
        if (n3!="0"){
          myGLCD.print(n3,520,0);
        }
        myGLCD.print(n2,620,0);
        x = 0;
        myGLCD.setColor(255,255,255);  //white
      }
      k = str.indexOf("810B02E7");
      if (k!=-1){
        counto=0;
        for (int j=0;j<4;j++){
          temp = d[48+j+k];
          if (('0' <= temp) && (temp <= '9')){
            n = temp - '0';
          }else if (('A' <= temp) && (temp <='F')){
            n = temp - 'A' + 10;
          }
          x = x *16 + n;
        }
        myGLCD.setColor(0,0,0);
        myGLCD.fillRect(106,190,206,334);
        myGLCD.setColor(255,255,255);
        if (x>=32768){
          x = x - 65536;
          myGLCD.setColor(0,0,255);  //blue
          myGLCD.setFont(Grotesk32x64);
          myGLCD.print("-",110,260);
        }
        n1 = String(int(x/10) % 10);
        n2 = String(int(x/100) % 10);
        n3 = String(int(x/1000) % 10);
        myGLCD.setFont(SevenSegment96x144Num);
        if (x < 500){
          myGLCD.setColor(0,0,255);  //blue
        }
        if (x > 3000){
          myGLCD.setColor(255,0,0);  //red
        }else{
          myGLCD.setColor(255,255,255);  //white
        }
        if (n3!="0"){
          myGLCD.print(n3,106,190);
        }
        myGLCD.print(n2,206,190);
        myGLCD.print(n1,322,190);
        x = 0;
        for (int j=0;j<4;j++){
          temp = d[60+j+k];
          if (('0' <= temp) && (temp <= '9')){
            n = temp - '0';
          }else if (('A' <= temp) && (temp <='F')){
            n = temp - 'A' + 10;
          }
          x = x *16 + n;
        }
        if (x>10000){
          x = 10000;
        }
        n1 = String(int(x/10) % 10);
        n2 = String(int(x/100) % 10);
        n3 = String(int(x/1000) % 10);
        if (x < 3000){
          myGLCD.setColor(199,97,20);  //rawsienna
        }
        if (x > 8000){
          myGLCD.setColor(0,0,255);  //blue
        }else{
          myGLCD.setColor(255,255,255);  //white
        }
        if (n3!="0"){
          myGLCD.print(n3,520,190);
        }
        myGLCD.print(n2,620,190);
        x = 0;
      }
    }else{
      i++;
    }
  }
  if (counti>120){  //no signal over 120second
    counti = 0;
    myGLCD.setColor(0,0,0);
    myGLCD.fillRect(106,0,302,144);
    myGLCD.fillRect(322,0,418,144);
    myGLCD.fillRect(520,0,712,144);
    myGLCD.setColor(255,255,255);
    myGLCD.setFont(Grotesk32x64);
    myGLCD.print("--",110,60);
    myGLCD.print("-",322,60);
    myGLCD.setFont(SevenSegment96x144Num);
  }
  if (counto > 120){  //no signal over 120second
    counto = 0;
    myGLCD.setColor(0,0,0);
    myGLCD.fillRect(106,190,302,334);
    myGLCD.fillRect(322,190,418,334);
    myGLCD.fillRect(520,190,712,334);
    myGLCD.setColor(255,255,255);
    myGLCD.setFont(Grotesk32x64);
    myGLCD.print("--",110,260);
    myGLCD.print("-",322,260);
    myGLCD.setFont(SevenSegment96x144Num);
  }
  if (countp > 60){
    countp = 0;
    while (BME280.isMeasuring()) {
    }
    BME280.readMeasurements();
    temp      = BME280.getTemperature();
    humidity  = BME280.getHumidity();
    pressure  = BME280.getPressure();
    pressureMoreAccurate = BME280.getPressureMoreAccurate();  
    tempMostAccurate     = BME280.getTemperatureMostAccurate();
    humidityMostAccurate = BME280.getHumidityMostAccurate();
    pressureMostAccurate = BME280.getPressureMostAccurate();
   
    myGLCD.setFont(Grotesk32x64);
    da2 = String(pressure);
    if (pressure>1010){
      myGLCD.setColor(255,153,18);  //cadmiumyellow
    }
    if (pressure<1000){
      myGLCD.setColor(0,255,255);  //cyan aqua
    }
    myGLCD.print(da2, 300, 380);  
    myGLCD.setColor(255,255,255);  //white    
  }
}

void time_count(void){
  counti++;
  counto++;
  countp++;
}

室外センサーの取り付け

最後に室外用のセンサーを取り付ける。
取り付けるために、PALケースに穴を空けてL字金具を取り付けた。

PALケースにL字金具を取り付けたところ。
PALケースの反対側。センサーの所(写真では上部)に穴が開いている。

取り付けるのは自作の放射シールド。こちらのサイトを参考に作製した。
otobs.org
使ったのはダイソーで買ってきた植木鉢用の皿。これの中心部に穴を空けて、スペーサーを介して接着して重ね合わせてある。

自作の放射シールド

ちなみに、内部はこんな感じでセンサーがついている。

放射シールドの内部。ちょっとわかり難いが、L字金具でPALケースを取り付けている。

これで、室内の温湿度、室外の温湿度、気圧が表示されるようになった。

温湿度、気圧が表示されているところ

TWELITE DIPにPALアプリを書き込んだら?

最後に番外編として、TWELITE DIPにPALアプリを書き込んだらどうなるか試してみた。
結論から言うとうまく動作しない(当たり前か)。
温湿度を測れないかとおもい、手持ちのSHT21を繋いでみた。
一応何らかのデータは送ってくるが、データに意味のあるものが含まれていない。データの数も環境センサーPALでは5つになっているが、送られてきたデータはデータ数が7つ。

:80000000B10015810075CE01808007C0320000113008020D3411300102042E1130020201A31130030202D8113004020145103104010057C7

データソースも電圧の0x30以外は0x32とか意味のないものでどうもうまくいっていない。ちなみに、温度なら0x01湿度なら0x02がデータソース。
環境センサーPALの回路図を見てみると、つながっている温湿度センサーはSHTC3となっていて種類が違う。また、EEPROMが基板にあって、拡張PALの種類を判別できるようになっているようだ。なので、拡張PALを買ってきて繋ぐと動く可能性はあるが、ただセンサーを繋いだだけではだめのようだ。もっともセンサーをSHTC3にすると動く可能性はあるかもしれないが。

23/4/17追記
温度表示がおかしくなることがあったため、プログラムを修正しました。
送信されてきたデータがノイズの影響を受け、温度が100℃以上になった場合に、表示がおかしくなります。
そこで、温度を計算した後に、次の文を入れて、計算結果の温度が3桁になったらスキップするようにしました。マイナスの場合も同様ですので、判断する範囲は10000以上55536以下としました。16ビットの55536は補数表現で-100と同じです。xがTweliteから送られてきたデータを基に計算した温度(の100倍)です。

if (x>=10000)&(x<=55536){
          break;
}

またbreakでループアウトできるように、データの文字列判断をしているところを、例えば外に置いてある温湿度計なら、

k = str.indexOf("810B02E7");
if(k!=-1){
 処理
}

から

k = str.indexOf("810B02E7");
while (k!=-1){
 ループ内処理
}

のように改めました。室内の温湿度計の処理も同様です。
ただ、こうすると、kの値が-1でない限り、永遠にループするので、処理の最後にk=-1として、処理が終わったらループを抜け出すようにしています。