| ホーム |     私の電子工作作品集

190. I2C IF モジュール + LCD(1602A) 表示時計

[ 初公開日:2021年2月4日 ]

 半年以上も前になりますが、"187. FMステレオラジオ" を製作するために、「 RDA5807M IC チップ FMラジオモジュール」 を購入して以来、アマゾンの中華モールに少しばかりハマっている私ですが、 それ以降にもいろいろなもの(電子部品)を中華モールで購入をしています。

 それらの中から今回は、「 LCD(1602A)モジュール」、「 I2C インタフェースモジュール」、「リアルタイムクロックモジュール」、「デジタル温度湿度センサーモジュール」を使用した、"I2C IF モジュール + LCD(1602A) 表示時計" を製作してみました。 実は、時計を製作することが本来の私の目的ではなく、これらの各モジュールを使用することはもちろん初めてなので、どのようなものなのかその各モジュールの機能(実力)を試したくて、とりあえず、"I2C IF モジュール + LCD(1602A) 表示時計" の製作にまとめてみたのです。

■ 回路図 ■

| 回路図 (I2Cmod_LCD1602_Clock.CE3) | ページトップ |

■ 使用した各モジュールについて ■

 アマゾンを覗いていると、実に様々な電子部品がいろいろなネットショップから出品されていて、その価格の安さに驚かされます。 しかし、中には同一の(ものと思われる)ものが、その価格の大小差が十数倍以上にも及んでいて、 高値を付けたショップでは競争が成り立つのだろうか? と不思議に思います。 ものが同じであれば誰でも安い方を選ぶのでは? と思うのですが ・・・。

 最近、私がアマゾンの中華モールで購入をした数々の電子部品の中から、上述したように、この "I2C IF モジュール + LCD(1602A) 表示時計" の製作を通して使用をした(どのようなものなのかその各機能を試してみた)ものを、 私が感じた問題点やその他のことなどを交えて、次に順に挙げていきます。
● LCD(1602A)モジュール

 まず初めに私が興味を持ったものの1つが、次の写真に示すような LCD(1602A)モジュールの価格の安さで、最安値のショップでは1個 200 円を大幅に切って売られています。 バックライト LED とその電流制限抵抗(R8:100Ω)が実装され、 青色の画面に白抜きの文字で表示がされます。

 なお、このバックライト LED に 5 V を加えたときの消費電流の実測値は、約 18.35 mA ほどでした。 かなり明るく感じられたので、私は直列に 500Ωのボリュームを繋いで、明るさを調節できるようにして使用をしました。(回路図 を参照)


 この LCD(1602A)モジュールを実際に PIC と接続をする場合に、通常のパラレルでの接続は今回は止めにして、次に紹介をする I2C インタフェースモジュールを介して、シリアル接続で使用をしてみました。 そして、これら2つのモジュール間の接続方法には、 次の写真に示すように、お互いの裏面同士を合い向かわせて、I2C インタフェースモジュール側に取り付けられている 16 本のピンヘッダを、LCD(1602A)モジュールにハンダ付けをして合体するのが一般的のようです。


 この合体をさせるときの注意として、そのままでハンダ付けをしてしまうと、LCD(1602A)モジュール側のモジュール基板に取り付け用の金属部分が、I2C インタフェースモジュールのハンダ面に接触をするので、ショート事故を防ぐため、 私は 16 本のピンヘッダの内の両端(1 ピンと 16 ピン)に、右上写真のように下駄を履かせて、十分な空間が保てるようにしてハンダ付けをしました。

 このように両者を合体させて使用をする場合には、全体の厚みが大きくなるため、製作をするアプリケーションによってはケースに収納ができなくなる恐れもあるため、その点は注意をしなければならないと思います。

 余談ですが、LCD(1602A)モジュールの他に LCD(2004A)モジュールも数個購入をしてあり、今回は使用をしませんが、また何かの折に使用してみたいと思っています。


● I2C インタフェースモジュール

 次に紹介をするのが既に上述した、次の写真に示すような I2C インタフェースモジュールで、PIC と LCD モジュールとを接続をするのに、通常のパラレル接続では、少なくても 6 〜 7 本のインタフェース線が必要となりますが、これをたった 2 本だけの I2C インタフェース線で、 LCD モジュールを制御するための、シリアル - パラレル変換モジュールです。

 少ないピン数の PIC で LCD モジュールを使用したいような場合には重宝する存在で、夢のような製品のように思えるかもしれませんが(確かにそうなんですが)、過度な期待はしない方が良さそうです。 詳細は後述( プログラム の項で)します。


 次の図は、この I2C インタフェースモジュールの回路図で、"Philips Semiconductors 社"(オリジナル)の IC チップ "PCF8574" を核としていますが、ネットショップから送られてきた製品に使用されている IC チップには、 "Philips Semiconductors 社" のマークがないので、多分どこかのセカンド品だと思われます。(なお、この回路図はネット上で得たものを、私自身が分かり易くなるように書き直したもので、以下の各モジュールの回路図も同様です。 )

 また、図中で J3Y と記入のあるトランジスタは、ネットで調べてみると J3Y = S8050 という NPN トランジスタであることが分かりました。 LCD モジュールのバックライト LED の ON / OFF 制御をしています。 そして、その右上に位置している VCC と A という 2 ピンのピンヘッダは、 このモジュールのデフォルト使用では、ジャンパーピンでショートがされていますが、上述したように、私はこのジャンパーピンを外して、ここに 500Ωの半固定ボリュームを繋いで、バックライト LED の明るさが調節できるようにしています。

 なお、上写真に写っている青色の半固定ボリューム(下図の右下に位置する 10KΩ)は、LCD 自体のコントラストを調節するためのもので、バックライト LED とは関係がありません。


 この I2C インタフェースモジュールを使用する上で特に注意すべきと思ったことは、図中の左上に位置する(注)と記した 3 本の 10KΩのプルアップ抵抗で、これらは IC チップ "PCF8574" のスレーブアドレスを変更するためのものです。 モジュール基板にチップ抵抗として搭載がされていますが、私が購入をしたネットショップから送られてきたものすべて(10 個セットで購入)が、次の写真に示すように 10KΩ(103)ではなく 1 KΩ(102)が搭載がされていました。


 スレーブアドレスを変更しないでデフォルトのままで使用する場合には、恐らく何の問題もないと思われますが、変更をして使用する場合には A0 〜 A2 のソルダジャンパーをして、それらが GND に接続されるので電源が 5 V のときには、 1 本当たり 5 mA、最大 3 本で 15 mA も無駄な電流消費が起こります。 これは、先のバックライト LED の消費電流にも匹敵するもので、この場合には簡単には看過することができない問題だと思います。

 これらのプルアップ抵抗としては、常識的には 10KΩ(103)程度が妥当と思われるので、他のネットショップ数店の商品説明に使われている写真を拡大して調べてみると、10KΩ(103)が搭載がされているモジュール写真と、1 KΩ(102)が搭載がされているモジュール写真の、 両方が混在をしていました。 ここらあたりのいい加減さが、さすが中華製、とつくづくそう思いました。

 次の写真は、このモジュールを私が実際に使用するときに、メインのプリント基板(1) と接続をするための、コネクタ(ピンソケット(メス))、フラットケーブルを取り付けたときの様子です。


 また、次に述べることは中華製ということとはまったく関係がないことですが、この I2C インタフェースモジュールを使用したプログラムを、私が実際に作成をしてみて随分と苦労をさせられました。 アマゾンでは実に様々な電子部品が出品されていることは何度も述べていますが、 それらの内でモジュールとしてのほとんどすべて(といってもいいほど)が、Arduino 対応とか Raspberry Pi 対応とかを謳っていて、PIC 対応というのを目にしたことがありません。 (別に PIC 対応ではなくても、使用することができるのでいいのですが)

 そして、アマゾンだけでなく、これらの電子部品の使用方法などをネット検索をしてみても、Arduino 用とか Raspberry Pi 用の製作実例などは随分とヒットするのですが、PIC 用の製作実例などはほんの僅かです。 Arduino や Raspberry Pi というものの存在は知っていましたが、 PIC を抜くほどにポピュラーで、こんなにも世に広まっているとは知りませんでした。

 Arduino や Raspberry Pi 用の各種のライブラリも随分と充実をしていて、アマゾンで扱っているモジュールなどの電子部品用のライブラリ等は、すべてが揃っているようにさえ感じさせられます。 したがって、個々のユーザは自分にとっての新しいデバイスを使用する場合などにも、 自分で苦労をしてそのプログラムを作成することなく、如何にしてそのデバイス用のライブラリを見つけ出して利用をするか、にかかっているようにさえ思えるのは、PIC 大好き人間の私の思い過ごしでしょうか。 いずれにしてもライブラリが充実をしているということは羨ましい限りです。

 私がこの I2C インタフェースモジュールを PIC で使用するためのプログラム作成では、上述したように随分と苦労をさせられたのですが、実際に LCD モジュールに文字を表示することができるようになるまでには、1週間以上もかかってしまいました。 具体的なプログラム内容等の詳細については、プログラム の項で述べたいと思います。


● リアルタイムクロック( DS3231 )モジュール

 これまで私は PIC によって多くのディジタル時計を製作してきましたが、安定した動作を一番に考えた場合には、クロックに京セラの KTXO-18S(12.8MHz)を使用してきました。 10 数年も以前に纏まった数を "秋月電子" で購入したものですが、いよいよ在庫が無くなってしまいました。

 しかし、"秋月電子" でももう何年も前から在庫が無くなったようで、現在では入手が難しい状況です。( KTXO-18S に代わるオシレータ・モジュールは販売されていますが高価です。) そこで、どうしようかと考えていた折アマゾンの中華モールで、 これは使えそうと思えるリアルタイムクロックモジュール( DS3231 )を見つけ、さっそく購入をしてみました。


 上の写真に示すようなもので、表面側(写真左)には RTC IC の DS3231 の他に EEPROM の AT24C32 が搭載されており、裏面側(写真右)には DS3231 の電源バックアップ用の、コイン型バッテリーを使用するための電池ホルダーが搭載されています。

 そして、このバックアップ用のバッテリーには、充電池(二次電池) LIR2032 を使用することを想定した、下回路図に示すような充電回路(ダイオード 1N4148 と抵抗 200Ω)が付属されているため、そのままでは一次電池の CR2032 を使用することはできません。 したがって CR2032 を使用する場合には、ダイオード 1N4148 か抵抗 200Ωのどちらかを取り除いて、充電電流が流れないようにする必要があります。 充電池の LIR2032 は高価なので、私は右下写真のように、抵抗 200Ωを取り外して一次電池の CR2032 を使用することにしました。


赤色↓の位置

 また、このモジュールを使用するに当たって私が最も気になったことは、PIC とは I2C インタフェース線を介して接続をしているのですが、回路図 を見ていただくと分かるように、同一のインタフェース線上には、 他にも前項で紹介をした LCD 用の I2C インタフェースモジュールも接続をしています。

 両モジュールが共通のインタフェース線を使用するという、そのこと自体には何の不思議もないのですが、上に掲載をした両モジュールのそれぞれ I2C IF モジュールRTC モジュール の回路図に注目をしてください。 どちらのモジュールにもインタフェース線 SCL, SDA のそれぞれには、モジュール側でプルアップ抵抗 4.7 KΩが接続されています。

 したがって、全体のプルアップ抵抗としては 4.7 KΩ/ 2 となってしまい、このことが実際の I2C 動作に与える影響がどのようなものか、最悪の場合にはどちらかのモジュールのプルアップ抵抗を外す必要があるかもしれない、と考えていました。 しかし、結果としては現状のままでも影響はないようで、正常?に I2C 通信が行われているようです。

 話は変わりますが、このリアルタイムクロックモジュールに対しての、アマゾンのレビューなどでは話題にはなっていないようですが、RTC IC の DS3231 の内蔵発振子には、水晶発振子( DS3231S, DS3231SN )のものと MEMS発振子( DS3231M )のものの2種類があって、 後者の MEMS発振子を使ったものは水晶発振子のものと比べて精度が劣る、ということがどこかのサイトに書かれていましたが、ネットショップから送られてきたものは、残念ながら後者の MEMS発振子タイプでした。 指定をすることができないので致し方がありません。

 なお、上述したように、このモジュールには EEPROM の AT24C32 が搭載されている(上図の下半分)のですが、今回のプログラムでは使用する予定はありません。


● デジタル温度湿度センサー( DHT22 )モジュール

 最後に、これまで私が製作をしてきた PIC によるディジタル時計では、* 高精度 IC温度センサ LM35DZ とオペアンプ LMC662CN によって、時刻の他に温度を表示する機能を持たせてきましたが、今回はそれらに代わって DHT22(AM2302)センサーモジュールを使用して、 温度だけではなく湿度も一緒に表示をさせるようにしています。
          * 高精度 IC温度センサ LM35DZ : と "秋月電子" の商品カタログには書かれている。

 このモジュールは上の写真に示すような外観で、裏面側(写真右)や下回路図からも分かるように、DHT22(AM2302)センサーモジュール本体の他には、5.1 KΩの抵抗と 0.1μF のコンデンサが、それぞれチップ部品として基板に搭載されているだけのものです。

 DHT22 センサーモジュールのオリジナルメーカーは "AOSONG" らしいのですが、ネットショップから送られてきたものは、"ASAIR" と銘打たれていたので恐らくセカンド品なのでしょう。 私が購入をしたネットショップの商品説明には、 "AOSONG" の写真が使用されていたので信じていましたが、こういったところも通販故に送られてくるまでは、実際のところが分からないのが残念です。


 また、これはプログラムを作成する段階になって分かったことですが、この写真の黒色基板のモジュールには、上述のようにプルアップ用の 5.1 KΩチップ抵抗が取り付けられていますが、この点について疑問に思ったことを次に述べてみます。

 このモジュールと PIC との間の通信プログラムを作成していて、当初はトラブってしまってどうにも困り果てた末、ハードウエアの不良を疑って、まずこの抵抗値をテスターで計ってみたところ、何と 5.1 KΩの半分ほどしか表示がされません。 えっ?て感じです。 チップ抵抗の表面には間違いなく 512 と銘打たれています(上のモジュール写真を 拡大 して見てみてください)。

 DHT22 センサーモジュールを使用するに当たって、私が参考にした サイト の使用方法には、必ずプルアップ抵抗(通信距離が長い場合は 4.7 KΩ 〜 短い場合は 10 KΩ)を付けろ、 と書かれています。 ただし、このサイトで使用しているのは、私が使用をした上写真のような黒色基板のモジュールではなく、黒色基板上に搭載がされている白色の DHT22 センサー本体そのものを取り扱っていますが。 しかし、いずれにしても同じことで、 プルアップ抵抗が予め付けられたモジュールを使用するか、自前で付けるかの違いだけです。

 話を戻しますが、テスターで半分ほどの値しか示さないチップ抵抗が不良なのでしょうか。 プログラムがトラブルといろいろなものを疑ってみるようになります。 5.1 KΩの半分というのは 4.7 KΩ 〜 10 KΩの範囲から逸脱しているので、それが原因なのでしょうか。

 このモジュールを使用するのが初めてなのは上述したところですが、試しに2個を購入してありました。 どちらも同程度の抵抗値を示すことから、1つのモジュールのチップ抵抗を外してみたのです。 そして、外したチップ抵抗を改めてテスターで計ってみると、 5.1 KΩ程度と示し間違いはありませんでした。

 ということは、DHT22 センサー本体内には既に同程度の値の抵抗が内蔵されていることになります。 実際にチップ抵抗を外した DHT22 センサー本体の、VDD(1) - DATA(2) 間をテスターで確認をしてみると、約 4.7 KΩ程度を示します。 この事実から外付けのプルアップ抵抗などは不要なのでは? と思います。

 後の詳細はプログラムの項で述べますが、結果として、トラブルの原因はハードウエアではなくプログラム上にありました。 そして、本機の使用例のように通信距離が数 cm の場合には(と限るかどうかは分かりませんが)、プルアップ抵抗の値にはあまり拘らなくても良いようです。

 私が購入をした2個のモジュールは、1つはチップ抵抗を外したもの(約 4.7 KΩ程度)、もう1つはチップ抵抗がそのままのもの(約 4.7 KΩ/ 2 程度)、どちらも見かけ上? は正常のように、PIC とのシリアル通信を行っているようです。

| ページトップ |

■ ケース外観と内部の様子 ■

| ページトップ |

■ 機能概要と使用法 ■

  • 本機の電源を投入すると、"ピポッ" と開始ブザー音を出力後、LCDに次の開始メッセージが3秒間ずつ交互に連続2回表示がされる。
      			[ LCD 1602 Clock ]				[    Ver.x.xx    ]
      			[ (Temp & Humi)  ]				[  by m.yamamoto ]
      

  • その後1秒間の空白を置いた後、RTC(リアルタイムクロック)モジュールの状態がチェックされ、初めて電源を入れたときや、バックアップ用バッテリーが異常なとき等に、次のエラーメッセージがLCDに表示されるとともに、"ブッブー" ブザー音が出力される。
      			[ DS3231 Error!  ]
      			[        Init?_  ]	………  応答を求めている
      

  • なお、このときLCDでは "Init?_" の後がブリンク表示されているが、これは RTC(リアルタイムクロック)モジュールの初期設定をするかどうかを、ユーザに応答を求めていることを表している。 ユーザはバックアップ用バッテリーの状態等を確認してその対処を行った後、 この場合には5つのスイッチの内のどれかを押して応答をする。

  • すると、RTC(リアルタイムクロック)モジュールの初期設定をした後、再度 RTC(リアルタイムクロック)モジュールの状態がチェックされ、なおも NG であれば同様にエラーメッセージの表示が繰り返されるが、OK であれば時計機能のLCD表示が開始され、 次のように年月日(曜日)、時分秒が刻々と変化をしながら表示が行われる。 この状態を NORMAL モード という。
      			[20xx/xx/xx (xxx)]
      			[  xx xx:xx:xx   ]
      

  • このように、RTC(リアルタイムクロック)モジュールの初期設定がされると、エラーフラグがクリアされるとともに、年月日(曜日)、時分秒が本機をプログラミングしたときの設定値、 "2021/01/01(FRI)"、 "00:00:00" にされるため、 次のスイッチ操作による手順で現在の年月日、時分秒に設定し直す必要がある。

  • その手順の説明の前に、先に本機におけるLCDに表示をする、項目(年月日、時分秒、温度湿度)の組み合わせパタン(モード)を説明しておく。 (このときのモードの意味は上述の NORMAL モードと対比するものではなく、単に組み合わせパタンの種類を意味し表している。)
      		モード 0          	モード 1          	モード 2          	モード 3          	モード 4          	モード 5
      
      		[20xx/xx/xx (xxx)]	[20xx/xx/xx (xxx)]	[  xx xx:xx:xx   ]	[  xx xx:xx:xx   ]	[-xx.x゚C xxx.x % ]	[-xx.x゚C xxx.x % ]
      		[  xx xx:xx:xx   ]	[-xx.x゚C xxx.x % ]	[-xx.x゚C xxx.x % ]	[20xx/xx/xx (xxx)]	[20xx/xx/xx (xxx)]	[  xx xx:xx:xx   ]
      
    モード 0 モード 2 モード 4
    モード 1 モード 3 モード 5

  • 以上のように6つの表示パタン(モード)があり、これらは MODE / SET スイッチ を押すごとに、LCDに表示される項目の組み合わせパタン(モード)が切り替わり、モード番号が増加して行く。 そして モード 5 の次は再び モード 0 に戻る。 なお、これらの3つの項目(年月日、時分秒、温度湿度)は刻々とリアルタイムに更新をしながらLCDに表示が行われる。

  • ここで話を戻すと設定手順としては、このようにどのパタン(モード)がLCDに表示されている場合においても可能で、まず、パタン(モード)切り替えのときのように "チョン押し" ではなく、同じ MODE / SET スイッチ を "長押し" することによって、 スイッチ操作による年月日、時分秒を自由に設定することができる、SET モード に移行をする。

  • すなわち、MODE / SET スイッチ を3秒以上 "長押し" をしていると、LCDに次のメッセージが表示されて、NORMAL モードから SET モード に移行をしたことを知らせる。
      			[ ** SET MODE ** ]
      			[                ]
      

  • ユーザはそれを確認してスイッチから指を離すと、次には必ず モード 0 の表示パタンで年月日、時分秒が表示される。 ただし、SET モードに移行後は表示の更新は停止して刻々と変化することはない。 そして、この移行直後は "20xx" の左の "x" のところがブリンク表示され、 "年の下位2桁" が変更の対象項目であることを示している。
      			   v
      			[20xx/xx/xx (xxx)]	………  v の位置の文字がブリンク表示
      			[  xx xx:xx:xx   ]
      

  • ここで変更が必要であれば、UP スイッチ を押すごとに現在値より1ずつ増加し、また DOWN スイッチ を押すごとに1ずつ減少するので、適宜希望する値に変更をする。 また、このときの増減幅を大きくしたい場合には、 UP スイッチ、または DOWN スイッチを押したままにしていると、1秒当たり 4 〜 5 の増減幅で更新をするようになる。

  • "年の下位2桁" の変更が済んだら、再び MODE / SET スイッチ を押すと、次には "月の2桁" である "/xx/" の左の "x" のところがブリンク表示され、変更の対象項目が変更されたことを示す。 このように各変更の対象項目2桁の内、 必ず1桁目(左側)がブリンク表示されて、そのときに選択されている対象項目を表現している。
      			      v
      			[20xx/xx/xx (xxx)]	………  v の位置の文字がブリンク表示
      			[  xx xx:xx:xx   ]
      

  • そして同様に変更が必要であれば、UP スイッチ、または DOWN スイッチを押すことによって、適宜希望する値に変更をする。

  • 以降、同様に SET スイッチ を押すごとに、日 → 24H/12H → 時 → 分 → 秒 と設定項目を変更することができるので、変更が必要であればその該当項目の選択時に、UP スイッチ、または DOWN スイッチを押すことによって、適宜希望する値に変更をする。

  • なお、曜日は年月日によって一意的に決まるものであって、曜日だけを勝手に変更をすることは許されないため、曜日については変更の対象項目には入っていない。 年、月、日のどれかを変更することによって、曜日も自動的に変更表示がされる。

  • この SET スイッチによって対象項目を選択中に、誤って希望する項目を通り過ぎてしまったような場合には、ZERO / BACK スイッチ を押すごとにひとつずつ前の項目に戻ることができる。

  • 最後の秒の項目選択時に更に SET スイッチを押すと、今まで変更をしてきた設定値の年月日、時分秒が RTC(リアルタイムクロック)モジュールに書き込み更新されるとともに、SET モードから再び NORMAL モード に切り替わって、LCDには新たな更新表示が再開される。

  • SET モードでの選択項目の推移をまとめると次図のようになる。
      	                                BACK  BACK  BACK       BACK  BACK  BACK
      	              MODE               ←    ←    ←         ←    ←    ←
      	              /SET              SET   SET   SET        SET   SET   SET   SET
      	  NORMALモード ─→ 年(下位2桁) → 月 → 日 → 24H/12H → 時 → 分 → 秒 ─→ NORMALモード
      	                         │                                            ↑
      	                     BACK└──────────────────────┘
      
  • ここで項目 24H/12H について補足をしておくと、これは時表示を 24 時間表示にするか 12 時間表示にするかの、どちらかを選択することで、この項目を選択時に UP スイッチ、または DOWN スイッチのどちらかを押すことによって、交互に切り替えることができる。
      			[20xx/xx/xx (xxx)]				[20xx/xx/xx (xxx)]
      			[     xx:xx:xx   ]	………  24 時間表示	[  AM xx:xx:xx   ]	………  12 時間表示
      

  • そして、24 時間表示を選択した場合には、LCDの該当の位置には何も表示がされないが、12 時間表示を選択した場合には、そのときに表示している時の値によって、LCDの該当の位置には AM または PM が表示される。 逆に、時の項目が選択時にも時の値を変更すると(12 時間表示が選択のときには)、その値によって AM / PM が更新表示される。

  • 次に、12 時間表示と 24 時間表示の対応を示す。 なお、12 時間表示の場合のみ 時 "xx" の先頭の1桁目は、ゼロサプレスを行なって表示をする。
      		(24H)  00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
      		(12H)  12  1  2  3  4  5  6  7  8  9 10 11 12  1  2  3  4  5  6  7  8  9 10 11
      		       am am am am am am am am am am am am pm pm pm pm pm pm pm pm pm pm pm pm
      
  • なお、この 12 時間表示と 24 時間表示の変更操作は、上述の SET モード中だけでなく、NORMAL モードにおいても UP スイッチ を押すごとに、いつでも交互に切り替えることができる。

  • ZERO/BACK スイッチは、SET モードでは上述のように BACK スイッチとして機能をするが、NORMAL モードでは 毎正時プラス・マイナス 5 分以内に押されると、00 分 00 秒に設定するための ZERO スイッチ として機能をする。

  • SIGNAL スイッチ は、毎時の時報音出力のブザー音を、押すごとに有効/無効に交互に設定をする。 このときの設定状態は視覚的な表示がないため、有効になったときには "ピピピッ" 音を、無効になったときには "ブー" 音を、それぞれ鳴らして知らせる。 なお、電源を投入した直後は有効状態に設定がされている。 また、有効に設定時には、時分秒が表示されない表示パタン(モード)においても、毎時の時報音は有効(鳴る)なので注意が必要である。

  • 本機では年月日(曜日)、時分秒の他に、温度湿度を表示するパタン(モード)があることは上述の通りであるが、年月日(曜日)、時分秒の情報は、上述した RTC(リアルタイムクロック)モジュール( DS3231 )と PIC とを、I2C シリアル通信によって情報を得ているのに対して、 温度湿度情報は、デジタル温度湿度センサーモジュール( DHT22(AM2302))と PIC との間を、たった1本だけのデータ線によるシリアル通信によって情報を得ている。

  • そして、前者の場合には1秒間隔で通信を行うが、後者の場合には4秒間隔(2秒以上が必要)で通信を行っている。 また、前者の場合には通信を休むことはない(1秒に1度必ず通信を行う)が、後者の場合にはLCD表示がないパタン(モード)に設定がされているときには、 本機ではまったく通信を行わずに休んでいる。

  • また、後者のデジタル温度湿度センサーモジュール( DHT22(AM2302))との通信においては、通信中にエラーが起こった場合には、次のようなエラーメッセージがLCDに表示されるとともに、 "ブッブー" ブザー音が出力される。 次の例では、表示パタンがモード 1 に設定されている場合で2行目にエラー表示がされているが、モード x によって1行目に表示がされる場合もある。
      			[20xx/xx/xx (xxx)]				[20xx/xx/xx (xxx)]
      			[DHT22 Time Out! ]				[DHT22 CRC Error!]
      

  • 左の例では通信中にタイムアウトエラーが起こった場合で、データ線上の "high" または "low" 電圧の続く時間が、仕様で決められた時間をオーバーした場合に発生をする。 また、右の例ではチェックサム(CRC)エラーが起こった場合で、 DHT22(AM2302)からは1度の通信で、湿度情報が 16 ビット、温度情報が 16 ビット、チェックサムが 8 ビットの、計 40 ビットが送られてくるが、それらの情報でパリティチェックを行った結果、エラーが起こった場合に発生をする。

| ページトップ |

■ プログラム ■

 私がこのホームページでプログラムと呼んでいるのは、主に PIC 用のアセンブラプログラムを対象としています。 昔、パソコンや職場の UNIX マシンでは、BASIC や C でプログラミングをしていたこともありましたが、 15 〜 16 年ほど以前から、趣味で PIC におけるプログラム作成をするようになってからは、全面的にアセンブラ一筋で行っています。

 私の他のページで紹介をしている "x05. 8080A CPU コンピュータシステム" を作製した大昔から、アセンブラプログラムというものが好きで、他の高級言語のような言語仕様による枠(制約)に縛られることもなく、 一つひとつの単純な命令構造と、それらを組み合わせるときの自由さの魅力に、今でもはまっています。

 しかし、今回のように(私にとって)初めてのデバイス(モジュール)を新たに扱うような場合には、上の 使用した各モジュールについて の項でも述べたたように、例えば Arduino や Raspberry Pi 用の標準的? な各種ライブラリのようなものは存在しないため(多分?)、そのハードウエアに密着をした低レベルな部分も含めて、すべてのプログラムを自前で作成をしなければなりません。 でも、そこがまた私にとっては魅力的な点でもあるのですが ・・・

 このプログラムの項では、本機、デジタル時計の製作を通して、私にとっては4つの新たなモジュールを使用していますが、それらのモジュールのためのプログラムを作成するに当たって、私が不具合の泥沼に陥ってしまったことや、 やがて泥沼から抜け出して完成をさせた、それぞれのサブルーチンとしての完成形を、自分自身への備忘録としても、次に記しておこうと思います。
● LCD(1602A)モジュールと I2C インタフェースモジュールのプログラミング

 使用した LCD(1602A)モジュール は、アマゾンの中華モールで購入をしたもので、正式なメーカー、型名は分かりませんが、コントローラに "HD44780" コンパチブル IC を使用したものなので、 以前から "秋月電子" などで標準となっている、Sunlike 社の LCD モジュール "SC1602BSLB" などと同等に扱うことができます。

 したがって、LCD(1602A)モジュール単体ではパラレルインタフェース仕様なので、そのプログラムの作成は既に経験済みですが、本機、デジタル時計では、これに I2C インタフェースモジュール ( IC "PCF8574" を使用)を追加して、PIC とは I2C インタフェースで通信をし、追加モジュール内でシリアル - パラレル変換を行わせて、LCD(1602A)モジュールの制御を行います。

 しかし、ここで勘違いを避けるために初めに断っておくと、LCD モジュール自体に初めから I2C インタフェースを備えている、例えば "188. FMステレオラジオ II" などで使用をした、I2C LCD モジュールの "AQM0802A" などとは、I2C インタフェースに送り出すデータ内容が少々異なっているので注意が必要です。

 一番の相違は、アマゾン中華の LCD モジュールを含めた一般的な "SC1602BSLB" では、動作起動信号(E)のストローブ信号が必須なので、I2C インタフェースモジュールを追加したモジュールでも、疑似的にこのストローブ信号を作り出して、 I2C インタフェースに送り出してやらなくてはならないのです。 "AQM0802A" ではストローブ信号は不要でこのような面倒なことはしません。

 その他に基本的なコマンドはどちらもほぼ同じですが、"AQM0802A" にはコントラスト調整などの、拡張コマンドが追加されているので外付けボリュームも不要ですが、前者では当然必要です。

 次に、今回新たに作成をした、一般的な "SC1602BSLB" コンパチブルなパラレル LCD モジュールに、I2C インタフェースモジュールを追加した場合の、"LCD 制御サブルーチン( ファイル: I2C_XS_LCD.sub )" の内容を、まず示しておきます。 なお、次のリストは本機で使用した PIC16F684 のように、PIC 自体にハード的な I2C 機能が搭載されていない機種を使用する場合で、ソフトウエアによって I2C 機能を実現している例となります。
      ;==========================================================================
      ; PCF8574 I2C IF モジュール使用 LCD 制御サブルーチン (ソフトウエア I2C 版)
      ;==========================================================================
      
      ;<使用外部ルーチン>
      
      ;(i2c_start, i2c_stop, i2c_byte_send, i2c_byte_reciv, i2c_ack, i2c_nack)
      ;(wait_xxms, wait_100us, wait_40us)
      
      ;<使用レジスタ>
      
      ;lcdtmp					;LCD への出力データの一時保存
      ;lcdtmp2				;
      ;cntrbuf				;
      ;lcd_addr_high				;書き込みデータの high アドレス
      ;lcd_addr_low				;書き込みデータの low アドレス
      
      ;==========================================================================
      ;		定数の定義
      ;==========================================================================
      
      LcdAddr		equ	h'27'		;I2C IF モジュール(PCF8574) のスレーブアドレス
      ;LcdAddr	equ	h'3f'		;I2C IF モジュール(PCF8574A)のスレーブアドレス
      
      _RS		equ	0		;LCD RS:  レジスタ選択信号
      _RW		equ	1		;LCD R/W: 読み出し/書き込み選択信号
      _E		equ	2		;LCD E:   動作起動信号
      _BL		equ	3		;バックライト LED ON/OFF 制御
      
      _BF		equ	7		;LCD ビジーフラグ
      
      ;==========================================================================
      ;		マクロ命令定義
      ;==========================================================================
      
      ;		I2C IF モジュール LCD メッセージデータ表示マクロ
      
      Disp_I2CxLcd	macro	@com,@msg,@x
      	#if 	@com != 0
      		movlw	@com
      		call	I2CxLcd_Cmd	;I2C LCD へコマンドを出力
      	#endif
      	#if 	@x != 0
      		movlw	high @msg	;メッセージデータの先頭 high アドレス
      		movwf	lcd_addr_high	;
      		movlw	low @msg	;メッセージデータの先頭 low アドレス
      		movwf	lcd_addr_low	;
      	#endif
      		call	I2CxLcd_Disp	;メッセージデータ1行表示
      		endm
      
      ;==========================================================================
      ;		I2C IF モジュール LCD へコマンドを出力
      ;==========================================================================
      
      ;使用レジスタ:	lcdtmp, cntrbuf
      
      ;入力:		W reg: コマンド
      
      I2CxLcd_Cmd
      		bcf	cntrbuf,_RS	;RS = 0  (コマンドモードに設定)
      		goto	i2cxlcd01
      
      ;==========================================================================
      ;		I2C IF モジュール LCD へデータを出力
      ;==========================================================================
      
      ;使用レジスタ:	lcdtmp, cntrbuf
      
      ;入力:		W reg: データ
      
      I2CxLcd_Data
      		bsf	cntrbuf,_RS	;RS = 1  (データモードに設定)
      		nop
      
      i2cxlcd01	bcf	cntrbuf,_RW	;R/W = 0 (書き込みモードに設定)
      		movwf	lcdtmp		;コマンド/データを一時保存
      
      		call	i2c_start	;スタートコンディション
      		movlw	LcdAddr	<< 1	;スレーブアドレス
      		call	i2c_byte_send	;1 バイト送信
      
      		movf	lcdtmp,W	;
      		call	I2CxLcd_Out	;上位4ビットを転送
      		swapf	lcdtmp,W	;
      		call	I2CxLcd_Out	;下位4ビットを転送
      
      		call	i2c_stop	;ストップコンディション
      	;;	call	I2CxLcd_Busy	;LCD ビジーチェック
      		call	wait_40us
      		return
      
      ;==========================================================================
      ;		I2C IF モジュール LCD へ4ビット一回の送出
      ;==========================================================================
      
      ;		DB7, DB6, DB5, DB4, BL, E, R/W, RS	;BL: バックライト LED
      
      ;使用レジスタ:	cntrbuf, lcdtmp2
      
      ;入力:		W reg: コマンド/データ
      
      I2CxLcd_Out
      		andlw	h'f0'		;上位4ビットデータ
      		iorwf	cntrbuf,W	;コントロールとOR
      		movwf	lcdtmp2		;Wレジスタの内容を一時保存
      
      	;	movf	lcdtmp2,W	;コマンド/データ、E = 0 (動作起動信号の設定:L)
      		call	i2c_byte_send	;1 バイト送信
      		movf	lcdtmp2,W	;コマンド/データ
      		iorlw	1 << _E		;コマンド/データ、E = 1 (動作起動信号の設定:Hでストローブ)
      		call	i2c_byte_send	;1 バイト送信
      		movf	lcdtmp2,W	;コマンド/データ、E = 0 (動作起動信号の設定:Lに戻す)
      		call	i2c_byte_send	;1 バイト送信
      		return
      
      ;==========================================================================
      ;		I2C IF モジュール LCD の初期設定
      ;==========================================================================
      
      ;使用外部ルーチン: 	wait_xxms, wait_100us
      
      I2CxLcd_Init
      		clrf	cntrbuf		;bit0: RS = 0  (コマンドモードに設定)
      					;bit1: R/W = 0 (書き込みモードに設定)
      					;bit2: E = 0   (動作起動信号の設定)
      					;bit3: BL = 0  (バックライト LED = OFF に設定)
      		movlw	15
      		call	wait_xxms	;wait 15ms (15ms以上を確保)
      		movlw	b'00110000'	;ファンクションセット(8bitモードに設定)
      		call	I2CxLcd_CmdIni	;上位4ビットを転送
      
      		movlw	5
      		call	wait_xxms	;wait 5ms (4.1ms以上を確保)
      		movlw	b'00110000'	;ファンクションセット(8bitモードに設定) 
      		call	I2CxLcd_CmdIni	;上位4ビットを転送
      
      		call	wait_100us
      		movlw	b'00110000'	;ファンクションセット(8bitモードに設定)
      		call	I2CxLcd_CmdIni	;上位4ビットを転送
      
      		call	wait_100us
      		movlw	b'00100000'	;ファンクションセット(4bitモードに設定)
      		call	I2CxLcd_CmdIni	;上位4ビットを転送
      
      		call	wait_100us	;ここから 4bitモード
      		movlw	b'00101000'	;ファンクションセット
      		call	I2CxLcd_Cmd	;DL=0: 4bit, N=1: 2行, F=0: 5x7ドット
      
      		movlw	b'00001000'	;表示オン/オフコントロール
      		call	I2CxLcd_Cmd	;D=0: 表示オフ, C=0: カーソル非表示, B=0: 非ブリンク
      
      		movlw	b'00000001'	;表示クリア
      		call	I2CxLcd_Cmd	;
      
      		movlw	b'00000110'	;エントリーモードセット 
      		call	I2CxLcd_Cmd	;I/D=1: アドレスを+1しカーソルは右移動,S=0: 非シフト
      
      		bsf	cntrbuf,_BL	;バックライト LED = ON に設定
      		return
      
      I2CxLcd_CmdIni
      		movwf	lcdtmp		;コマンドを一時保存
      
      		call	i2c_start	;スタートコンディション
      		movlw	LcdAddr	<< 1	;スレーブアドレス
      		call	i2c_byte_send	;1 バイト送信
      
      		movf	lcdtmp,W	;
      		call	I2CxLcd_Out	;上位4ビットを転送
      		call	i2c_stop	;ストップコンディション
      		return
      
      ;==========================================================================
      ;		I2C IF モジュール LCD 1行メッセージ表示
      ;==========================================================================
      
      ;使用レジスタ:	lcd_addr_low, lcd_addr_high
      
      ;<使用例>	movlw	h'80'		;表示位置の DDRAM のアドレスをセット
      ;		call	I2CxLcd_Cmd	;LCD へコマンドを出力
      ;		movlw	high st_msg	;メッセージデータの先頭 high アドレス
      ;		movwf	lcd_addr_high	;
      ;		movlw	low st_msg	;メッセージデータの先頭 low アドレス
      ;		movwf	lcd_addr_low	;
      ;		call	I2CxLcd_Disp	;メッセージデータ1行表示
      ;		  :
      ;st_msg		dt	" LCD 1602 Clock ",0
      
      I2CxLcd_Disp
      		call	lcdtbl_read	;テーブルデータの読み出し
      		bcf	PCLATH,3	;ページ 0 に戻す
      		bcf	PCLATH,4
      		incf	lcd_addr_low,F	;読み出すデータの low アドレスの更新
      		btfsc	STATUS,Z	;オーバーフローした場合は
      		incf	lcd_addr_high,F	;読み出すデータの high アドレスの更新
      		addlw	0
      		btfsc	STATUS,Z	;h'00' か?
      		goto	i2cxldsp01	;Yes
      
      		call	I2CxLcd_Data	;LCD へデータを1文字出力
      		goto	I2CxLcd_Disp
      
      i2cxldsp01	return
      
      		; テーブルデータの1文字読み出し
      lcdtbl_read
      		movf	lcd_addr_high,W	;読み出すデータの high アドレス
      		movwf	PCLATH
      		movf	lcd_addr_low,W	;読み出すデータの low アドレス
      		movwf	PCL
      
      ;=============================================================== end ======
      
 上のリスト中で、サブルーチン ( I2CxLcd_Out ) が上述したところの、疑似的にストローブ信号を作り出して I2C インタフェースに送り出している部分で、次に再掲をしますが、 I2C インタフェースモジュール ( IC "PCF8574" )を使用した場合には、 このような面倒なプログラム操作が必要となります。
      ;==========================================================================
      ;		I2C IF モジュール LCD へ4ビット一回の送出
      ;==========================================================================
      
      ;		DB7, DB6, DB5, DB4, BL, E, R/W, RS	;BL: バックライト LED
      
      ;使用レジスタ:	cntrbuf, lcdtmp2
      
      ;入力:		W reg: コマンド/データ
      
      I2CxLcd_Out
      		andlw	h'f0'		;上位4ビットデータ
      		iorwf	cntrbuf,W	;コントロールとOR
      		movwf	lcdtmp2		;Wレジスタの内容を一時保存
      
      	;	movf	lcdtmp2,W	;コマンド/データ、E = 0 (動作起動信号の設定:L)
      		call	i2c_byte_send	;1 バイト送信
      		movf	lcdtmp2,W	;コマンド/データ
      		iorlw	1 << _E		;コマンド/データ、E = 1 (動作起動信号の設定:Hでストローブ)
      		call	i2c_byte_send	;1 バイト送信
      		movf	lcdtmp2,W	;コマンド/データ、E = 0 (動作起動信号の設定:Lに戻す)
      		call	i2c_byte_send	;1 バイト送信
      		return
      
 上では "LCD 制御サブルーチン" の全体リストをサラッと示しましたが、I2CxLcd_Out の部分がこのような形に落ち付くまでには、それなりの結構な時間を要しました。 当初は、疑似的なストローブ信号をどのようにして作り出せば良いのか、 この "LCD 制御サブルーチン" 全体を作成していて、まず初めに私が悩んだところで試行錯誤の結果このようになりました。

 また、上に示した "LCD 制御サブルーチン" の冒頭部分で、
      ;<使用外部ルーチン>
      
      ;(i2c_start, i2c_stop, i2c_byte_send, i2c_byte_reciv, i2c_ack, i2c_nack)
      
というコメントがありますが、これらの外部のサブルーチンたちは、最もハードウエアに密着をした低レベルな部分で、それらを次に示します。
      ;==========================================================================
      ;   I2C (SCLクロック周波数 = 100 KHz) サブルーチン (ソフトウエア I2C 版)
      ;==========================================================================
      
      ;#define 	TRISX	TRISC		;例. PORTC で I2C を行う場合は、
      ;#define 	PORTX	PORTC		;include する側で #define 定義をすること
      
      ;<使用レジスタ>
      
      ;i2ctmp					;送受信データの一時保存
      ;bit_cnt				;ループカウンタ
      
      ;--------------------------------------------------------------------------
      ;		ウェイト・ルーチン (8 MHz のとき)
      ;--------------------------------------------------------------------------
      
      i2c_wait
      		goto	$+1		;2
      		goto	$+1		;2
      		goto	$+1		;2
      	;;	goto	$+1		;2
      		return			;2
      					;Total	10/8MHz*4 = 5 μS
      
      ;--------------------------------------------------------------------------
      ;		スタートコンディション
      ;--------------------------------------------------------------------------
      
      i2c_start
      		bsf	PORTX,SCL	;SCL="H"
      		bsf	PORTX,SDA	;SDA="H"
      		call	i2c_wait
      		bcf	PORTX,SDA	;SDA="L"
      		call	i2c_wait
      		bcf	PORTX,SCL	;SCL="L"
      		return
      
      ;--------------------------------------------------------------------------
      ;		ストップコンディション
      ;--------------------------------------------------------------------------
      
      i2c_stop
      		bcf	PORTX,SDA	;SDA="L"
      		bsf	PORTX,SCL	;SCL="H"
      		call	i2c_wait
      		bsf	PORTX,SDA	;SDA="H"
      		return
      
      ;--------------------------------------------------------------------------
      ;		データ 1 バイト送信
      ;--------------------------------------------------------------------------
      
      ;使用レジスタ:	i2ctmp, bit_cnt
      
      ;入力:		W reg: 送信データ
      
      i2c_byte_send
      		movwf	i2ctmp		;Wレジスタの内容を一時保存
      		movlw	8
      		movwf	bit_cnt		;ループカウンタにセット
      
      bsend01		bcf	PORTX,SDA	;SDA="L"
      		btfsc	i2ctmp,7	;送り出しデータの 7ビット目 = 1 か?
      		bsf	PORTX,SDA	;Yes, SDA="H"
      		call	i2c_wait
      		bsf	PORTX,SCL	;SCL="H"
      		call	i2c_wait
      		bcf	PORTX,SCL	;SCL="L"
      		rlf	i2ctmp,F	;送り出しデータを 1ビット左にシフト
      		decfsz	bit_cnt,F	;8ビット送信した か?
      		goto	bsend01		;No
      
      		; 1 バイト送信後の ACK チェック 
      		bsf	STATUS,RP0	;バンク 1
      		bsf	TRISX,SDA	;SDA: 入力ポート
      		bcf	STATUS,RP0	;バンク 0
      		call	i2c_wait
      		bsf	PORTX,SCL	;SCL="H"
      		call	i2c_wait
      		nop			;ACK のチェックをしない
      		bcf	PORTX,SCL	;SCL="L"
      		bsf	STATUS,RP0	;バンク 1
      		bcf	TRISX,SDA	;SDA: 出力ポート
      		bcf	STATUS,RP0	;バンク 0
      		return
      
      ;--------------------------------------------------------------------------
      ;		データ 1 バイト受信
      ;--------------------------------------------------------------------------
      
      ;使用レジスタ:	i2ctmp, bit_cnt
      
      ;出力:		W reg: 受信データ
      
      i2c_byte_reciv
      		movlw	8
      		movwf	bit_cnt		;ループカウンタにセット
      		bsf	STATUS,RP0	;バンク 1
      		bsf	TRISX,SDA	;SDA: 入力ポート
      		bcf	STATUS,RP0	;バンク 0
      
      brecv01		call	i2c_wait
      		bsf	PORTX,SCL	;SCL="H"
      		bcf	STATUS,C	;C=0
      		btfsc	PORTX,SDA	;受け取り SDA = "H" か?
      		bsf	STATUS,C	;Yes, C=1
      		rlf	i2ctmp,F	;受け取りデータを 1ビット左にシフト
      		call	i2c_wait
      		bcf	PORTX,SCL	;SCL="L"
      		decfsz	bit_cnt,F	;8ビット受信した か?
      		goto	brecv01		;No
      
      		bsf	STATUS,RP0	;バンク 1
      		bcf	TRISX,SDA	;SDA: 出力ポート
      		bcf	STATUS,RP0	;バンク 0
      
      		movf	i2ctmp,W	;W reg = 受信データ
      		return
      
      ;--------------------------------------------------------------------------
      ;		ACK 応答
      ;--------------------------------------------------------------------------
      
      i2c_ack
      		bcf	PORTX,SDA	;SDA="L"
      		bsf	PORTX,SCL	;SCL="H"
      		call	i2c_wait
      		bcf	PORTX,SCL	;SCL="L"
      		bsf	PORTX,SDA	;SDA="H"
      		return
      
      ;--------------------------------------------------------------------------
      ;		NACK 応答
      ;--------------------------------------------------------------------------
      
      i2c_nack
      		bsf	PORTX,SDA	;SDA="H"
      		bsf	PORTX,SCL	;SCL="H"
      		call	i2c_wait
      		bcf	PORTX,SCL	;SCL="L"
      		return
      
      ;=============================================================== end ======
      
 これらの "I2C サブルーチン( ファイル: I2C_XS_100K.sub )" は、今回のように、一般的な "SC1602BSLB" コンパチブルなパラレル LCD モジュールに、I2C インタフェースモジュールを追加した場合の、 "LCD 制御サブルーチン" を実現させるために最も重要な働きをしてくれるサブルーチン群ですが、当初はこれらのサブルーチン群ではなく何も深く考えもしないで、先に例を挙げた "188. FMステレオラジオ II" などで使用をした、 I2C LCD 制御サブルーチン (I2C_S_LCD.sub) 内に含まれる "I2C サブルーチン" を使用したのです。

 しかし、LCD(1602A)モジュールには何も文字が現れませんでした。 そして、これが長い泥沼との戦いの始まりになったのでした ・・・

 結論から先に言うと、上で示した "I2C サブルーチン" には、随所に
      		call	i2c_wait
      
の一文が挿入してあるのが分かると思いますが、これが解答でした。

 次に示す図と表は I2C インタフェースモジュールで使用されている、IC "PCF8574" の動作タイミングを表したものですが、右表の3行目 f SCL という項目に注目をしてください。 SCL のクロック周波数が 100 kHz となっています。 そうなんです。 この "PCF8574" という IC の動作は遅いんです。 (これらの図と表は Philips 社のデータシート PCF8574 から抜粋)


 この事実に気が付くまでに、上述の疑似的なストローブ信号を作り出すことと絡んで、恥ずかしながら何と1週間ほどを費やしてしまいました。 本機では PIC16F684 を使用していますが、そのシステムクロックに内部発振の 8 MHz で動作をさせているため、 4 / 8 MHz = 0.5 μS が命令動作の基本時間となっています。

 したがって、SCL のクロック周波数が 100 kHz の "PCF8574" を、正常に動作をさせるためには上述のようなウエイトルーチンを、所定のところに挿入をしてやる必要があるのです。

 ちなみに、"188. FMステレオラジオ II" などで使用をした、I2C LCD モジュールの "AQM0802A" の動作タイミングを、比較のために次に示しておきます。 "AQM0802A" では f SCLK が4倍の 400 kHz となっているのが分かると思います。 (図と表は "秋月電子" ホームページの AQM0802A 添付資料から抜粋)


 こうして LCD に文字を表示することができるようにはなったのですが、これらの I2C インタフェースの通信速度の違いは、LCD モジュールに表示される文字を見ていると顕著に現われ、明らかに異なっていることが分かります。

 今まで、"SC1602BSLB" コンパチブル LCD モジュールを、通常のパラレルインタフェースで使用していたときや、I2C LCD モジュールの "AQM0802A" を使用したときには、何の違和感も特には感じなかったのですが、今回のようにパラレル LCD モジュールに、 I2C インタフェースモジュールを追加して使用する場合には、今表示されている文字列が他の文字列に変更されるときに、パラパラ、と表示が瞬くのが見て取れるのです。

 したがって、本機では 16 x 2 の表示画面上で、当初はプログラムを簡単にすることもあって、1行ごとを表示単位として書き換えていた(このときには、例え表示文字列がすべて同じであっても画面を斜め方向から見ていると、 その書き換えた行全体が瞬くのがよく分かります。)のですが、この表示の瞬きを少しでも減らすために、例えば時刻表示のときには、時、分、秒のそれぞれを表示単位として、更新があった表示単位だけを書き換えるようにプログラムを改めました。

 このように、デジタル時計である本機では、時刻表示のときには最低でも1秒ごとに部分的な画面の書き換えを行っていますが、今回のようにパラレル LCD モジュールに、I2C インタフェースモジュールを追加して使用するような方法は、 PIC の使用する I / O 数を減らすことには貢献をしますが、もっと頻繁に書き換えを行うことが必要なアプリケーションの場合には、LCD の表示速度の面からは多少なりとも無理が生じるものと思いました。

 なお、I2C インタフェースモジュールに使用されている "PCF8574" という IC は、上で示した図表のデータシートの日付を見ると( 1997 Apr 02 )と記されており、24 年も以前のもので IC 自体が古いものであることが分かります。 この IC についてもう少し調べてみました。

 メーカー Philips 社は、2006 年 9 月に半導体部門が NXP Semiconductors として独立をしたとのことで、その NXP 社の "PCF8574" のデータシート( 27 May 2013 )を見ると、次のような表がありました。


 この表に書かれているように、I2C-bus frequency が 100 kHz ( PCF8574/74A ) のものの他に、400 kHz ( PCA8574/74A )、1 MHz ( PCA9674/74A ) などがあるようです。

 | プログラムのトップに戻る |


● リアルタイムクロックモジュールのプログラミング

 使用した リアルタイムクロックモジュール は、"使用した各モジュールについて" の項で既に述べたように、私がデジタル時計を製作するときに従来使用をしてきた、 京セラのクリスタルモジュール KTXO-18S(12.8MHz)に代わるものとして、今回、本機で初めて使用をしてみた RTC モジュールです。 RTC IC の "DS3231" を核とし、PIC とは I2C インタフェースで通信をして、次の表に示す "DS3231" のレジスタ群とデータのやり取りを行います。 (次表は Maxim Integrated 社のデータシート DS3231 から抜粋)

 この RTC IC "DS3231" の使用方法の概要を述べると、初めに各レジスタの初期設定をするだけで、後は勝手に "時(とき)" を刻み続けるので、極端なことを言えば、それに合わせてタイミング良く 刻んでいる "時(とき)" 情報を PIC 内に読み込むだけで、 簡単にデジタル時計を製作することが可能となります。

 これまで私が製作をしてきた多くのデジタル時計では、どれもまず基本となるパルス信号を作り出して、その数をカウントしながら、秒、分、時、日、月、年という順に、プログラムでそれぞれを更新させて行きますが、 本機では、そのカウントやそれぞれの更新を RTC IC "DS3231" が代行をしてくれるのです。

 そして、上で "タイミング良く" と書きましたが、このタイミングも "DS3231" が1秒ごとにパルス信号を外部に出力するので、この信号を PIC の外部(INT)割り込み用ポートで受けて、そのときの "時(とき)" 情報、すなわち、レジスタ 00h 〜 06h の内容を PIC 内に読み込めば、 後は、それぞれの BCD データを文字データに変換後、LCD モジュールに送り出すだけとなります。

 また、この RTC モジュールにバックアップ用のバッテリーをセットしておけば、例え電源が切れても "時(とき)" を刻み続けてくれるので、これまで私が製作をしてきた多くのデジタル時計にはなかった、新たな停電対策の機能を持たすことができるので、 その点は安心ができるようになります。


 なお、上表から分かるように、アラームを設定するためのレジスタも2組備えていますが、本機ではそれらを一切使用をしません。 本機には毎時の時報を鳴らす機能も持たせてありますが、これらのレジスタを使用しないで実現をさせています。

 次に、今回作成をした "I2C RTC(DS3231)モジュール 制御サブルーチン( ファイル: I2C_RTC.sub )" の内容を示しますが、上で、"初めに各レジスタの初期設定をする" ことを述べましたが、 その初期設定用サブルーチン( I2cRtc_Init )で使用するための、初期設定用テーブル( ds3231_init_table )が、リストの最終の部分にあるのでそこに注目をしてください。

 (この続きはリストの後で述べています。)
      ;==========================================================================
      ;   I2C RTC(DS3231)モジュール 制御サブルーチン (ソフトウエア I2C 版)
      ;==========================================================================
      
      ;<使用外部ルーチン>
      
      ;(i2c_start, i2c_stop, i2c_byte_send, i2c_byte_reciv, i2c_ack, i2c_nack)
      
      ;<使用レジスタ>
      
      ;rtc_addr_high				;読み出すデータの high アドレス
      ;rtc_addr_low				;読み出すデータの low アドレス
      ;rtc_lpcnt				;ループカウンタ
      
      ;(読み書きバッファ)
      ;seconds				;00h: Seconds: 00-59
      ;minutes				;01h: Minutes: 00-59
      ;hours					;02h: Hours:   1-12 + AM/PM, 00-23
      ;day					;03h: Day:     1-7
      ;date					;04h: Date:    01-31
      ;month					;05h: Month/Century: 01-12 + Century
      ;year					;06h: Year:    00-99
      ;alarm1s				;07h: Alarm 1 Seconds: 00-59
      ;alarm1m				;08h: Alarm 1 Minutes: 00-59
      ;alarm1h				;09h: Alarm 1 Hours:   1-12 + AM/PM, 00-23
      ;alarm1dy				;0Ah: Alarm 1 Day:     1-7
      ;alarm1d				;0Bh: Alarm 1 Date:    1-31
      ;alarm2h				;0Ch: Alarm 2 Hours:   1-12 + AM/PM, 00-23
      ;alarm2d				;0Dh: Alarm 2 Day:     1-7, Alarm 2 Date:    1-31
      ;control				;0Eh: Control
      ;cont_stat				;0Fh: Control/Status
      ;aging					;10h: Aging Offset
      ;temp_m					;11h: MSB of Temp
      ;temp_l					;12h: LSB of Temp
      
      ;==========================================================================
      ;		定数の定義
      ;==========================================================================
      
      RtcAddr		equ	h'68'		;RTC モジュール(DS3231) のスレーブアドレス
      RtcEepAddr	equ	h'57'		;RTC モジュール(AT24C32)のスレーブアドレス
      
      ;==========================================================================
      ;		マクロ命令定義
      ;==========================================================================
      
      ;		Rtc_Write_M マクロ
      
      Rtc_Write_M	macro	@bufreg,@n
      		movlw	@bufreg		;書き込みバッファのレジスタアドレス
      		movwf	FSR		;間接アドレスに設定
      		movlw	@n
      		movwf	rtc_lpcnt	;ループカウンタ
      		call	I2cRtc_Write	;RTC(DS3231)モジュールのレジスタへ書き込み
      		endm
      
      ;		Rtc_Read_M マクロ
      
      Rtc_Read_M	macro	@bufreg,@n
      		movlw	@bufreg		;読み出しバッファのレジスタアドレス
      		movwf	FSR		;間接アドレスに設定
      		movlw	@n
      		movwf	rtc_lpcnt	;ループカウンタ
      		call	I2cRtc_Read	;RTC(DS3231)モジュールのレジスタから読み出し
      		endm
      
      ;==========================================================================
      ;		RTC(DS3231)モジュールのレジスタへ書き込み
      ;==========================================================================
      
      ;入力:		FSR:       書き込みバッファの(先頭)レジスタアドレス
      ;		rtc_lpcnt: 書き込み数
      
      I2cRtc_Write
      		call	i2c_start	;スタートコンディション
      		movlw	RtcAddr	<< 1	;スレーブアドレス
      		call	i2c_byte_send	;1 バイト送信
      		movlw	seconds
      		subwf	FSR,W		;レジスタアドレス
      		call	i2c_byte_send	;1 バイト送信
      
      rtcwr01		movf	INDF,W		;書き込みデータ
      		call	i2c_byte_send	;1 バイト送信
      		incf	FSR,F		;間接アドレスの更新
      		decfsz	rtc_lpcnt,F	;ループカウンタ - 1 = 0 か?
      		goto	rtcwr01		;No
      
      		call	i2c_stop	;ストップコンディション
      		return
      
      ;==========================================================================
      ;		RTC(DS3231)モジュールのレジスタから読み出し
      ;==========================================================================
      
      ;入力:		FSR:       読み出しバッファの(先頭)レジスタアドレス
      ;		rtc_lpcnt: 読み出し数
      
      I2cRtc_Read
      		call	i2c_start	;スタートコンディション
      		movlw	RtcAddr	<< 1	;スレーブアドレス
      		call	i2c_byte_send	;1 バイト送信
      		movlw	seconds
      		subwf	FSR,W		;レジスタアドレス
      		call	i2c_byte_send	;1 バイト送信
      
      		call	i2c_start	;再スタートコンディション
      		movlw	(RtcAddr << 1) + 1 ;スレーブアドレス
      		call	i2c_byte_send	;1 バイト送信
      		goto	rtcrd02
      
      rtcrd01		call	i2c_ack		;ACK
      
      rtcrd02		call	i2c_byte_reciv	;1 バイト受信
      		movwf	INDF		;読み出しデータ
      		incf	FSR,F		;間接アドレスの更新
      		decfsz	rtc_lpcnt,F	;ループカウンタ - 1 = 0 か?
      		goto	rtcrd01		;No
      
      		call	i2c_nack	;NACK
      		call	i2c_stop	;ストップコンディション
      		return
      
      ;==========================================================================
      ;		I2C RTC(DS3231)モジュールの初期設定
      ;==========================================================================
      
      I2cRtc_Init
      		movlw	high ds3231_init_table	;テーブルデータの先頭 high アドレス
      		movwf	rtc_addr_high	;
      		movlw	low ds3231_init_table	;テーブルデータの先頭 low アドレス
      		movwf	rtc_addr_low	;
      		movlw	ds3231_init_table_end - ds3231_init_table
      		movwf	rtc_lpcnt	;ループカウンタ
      
      		call	i2c_start	;スタートコンディション
      		movlw	RtcAddr	<< 1	;スレーブアドレス
      		call	i2c_byte_send	;1 バイト送信
      
      rtcini01	call	rtctbl_read	;テーブルデータの読み出し
      		bcf	PCLATH,3	;ページ 0 に戻す
      		bcf	PCLATH,4
      		call	i2c_byte_send	;1 バイト送信
      
      		incf	rtc_addr_low,F	;読み出すデータの low アドレスの更新
      		btfsc	STATUS,Z	;オーバーフローした場合は
      		incf	rtc_addr_high,F	;読み出すデータの high アドレスの更新
      		decfsz	rtc_lpcnt,F	;ループカウンタ - 1 = 0 か?
      		goto	rtcini01	;No
      
      		call	i2c_stop	;ストップコンディション
      		return
      
      		; テーブルデータの1文字読み出し
      rtctbl_read
      		movf	rtc_addr_high,W	;読み出すデータの high アドレス
      		movwf	PCLATH
      		movf	rtc_addr_low,W	;読み出すデータの low アドレス
      		movwf	PCL
      
      ;--------------------------------------------------------------------------
      
      ;		I2C RTC(DS3231)モジュールの初期設定用テーブルデータ
      
      ds3231_init_table
      		dt	h'00'		;DS3231 レジスタのスタート(先頭)アドレス
      		dt	h'00'		;00h: Seconds
      		dt	h'00'		;01h: Minutes
      		dt	h'00'		;02h: Hours
      		dt	h'06'		;03h: Day(Week)
      		dt	h'01'		;04h: Date
      		dt	h'01'		;05h: Month/Century
      		dt	h'21'		;06h: Year
      		dt	h'00'		;07h: Alarm 1 Seconds
      		dt	h'00'		;08h: Alarm 1 Minutes
      		dt	h'00'		;09h: Alarm 1 Hours
      		dt	h'00'		;0Ah: Alarm 1 Day
      		dt	h'00'		;0Bh: Alarm 1 Date
      		dt	h'00'		;0Ch: Alarm 2 Hour
      		dt	h'00'		;0Dh: Alarm 2 Day
      		dt	h'00'		;0Eh: Control
      		dt	h'00'		;0Fh: Control/Status
      		dt	h'00'		;10h: Aging Offset
      ;		dt	h'00'		;11h: MSB of Temp
      ;		dt	h'00'		;12h: LSB of Temp
      ds3231_init_table_end
      
      ;=============================================================== end ======
      
 上のリスト中で最終の部分にあるテーブルデータの内容を、"DS3231" のレジスタ群に送り込むことによって、"DS3231" を初期設定することができます。 そして、これらのテーブルデータは上表に示した "DS3231" のレジスタ群 00h 〜 12h に対応をしていて、 このプログラム例では 21 年 01 月 01 日 (金曜) 00 時 00 分 00 秒に設定をし、後のレジスタはすべて h'00' に設定をしています。

 このような初期設定は、本機に初めて電源を入れたときや、バックアップ用のバッテリーを交換した後などに必要となり、その初期設定をした直後から "DS3231" は刻々と "時(とき)" を刻み始めるのですが、当然そのときのリアルなタイムとは異なっているため、 この後、メインプログラム側では、これらの年月日時分秒をリアルなタイムに変更をする処理が必要となります。

 また、この初期設定の処理が必要なことを検知する方法には、電源を入れた直後に、上表で示したレジスタ群の内の Control/Status ( 0Fh ) レジスタの OSF ビットを監視することで、処理の実現をさせることが可能となります。

 上に示した "RTC(DS3231)モジュール制御サブルーチン" の冒頭部分でも、
      ;<使用外部ルーチン>
      
      ;(i2c_start, i2c_stop, i2c_byte_send, i2c_byte_reciv, i2c_ack, i2c_nack)
      
というコメントがありますが、これらの外部のサブルーチンたちも、既に上で示した "I2C サブルーチン( ファイル: I2C_XS_100K.sub )" を使用しています。 したがって、RTC IC "DS3231" は上述の "PCF8574" とは異なって、 f SCL が 400 kHz の Fast mode を備えていますが、本機デジタル時計では "PCF8574" と同じ 100 kHz の Standard mode で使用をしました。

 また、"RTC(DS3231)モジュールのレジスタへ書き込み ( I2cRtc_Write ) サブルーチン"、"RTC(DS3231)モジュールのレジスタから読み出し ( I2cRtc_Read ) サブルーチン" の作成には、次に示す各図を参考にしました。 (いずれも Maxim Integrated 社のデータシート DS3231 から抜粋)


 そして、これらの "RTC(DS3231)モジュール制御サブルーチン" については、実行をさせた結果は特に大きな躓きもなく、比較的すんなりと動作をさせることができました。

 | プログラムのトップに戻る |


● デジタル温度湿度センサーモジュールのプログラミング

 本機で使用した デジタル温度湿度センサーモジュール も、今回、本機で初めて使用をしてみた温度湿度センサーモジュールで、従来私が使用をしてきた、温度センサ LM35DZ とオペアンプ LMC662CN の組み合わせに代わるものです。 しかも、このセンサーモジュール( DHT22(AM2302))は温度だけではなく湿度データの出力もあり、また、直接デジタルデータとして得ることができます。 したがって、従来の LM35DZ を使用時のように、PIC にはアナログポートの必要もありません。

 また、PIC との通信には、たった1本だけのデータバス(SDA)によるシリアル通信によって、次の概要図のように行われます。

 通常の通信が行われていない待機状態のときには、データバス(SDA)は "H" にプルアップされていますが、通信を始めるときには、まず初めに PIC が DHT22 に対してデータ要求のために、データバス(SDA)を 1 mS 間 "L" を保持するスタート信号を送出します。 そして、PIC は再びデータバス(SDA)を "H" に戻して、DHT22 からの応答信号を待ちます。

 DHT22 はスタート信号を検出すると、応答信号としてデータバス(SDA)に 80 μS 間の "L" を送出します。 その後、また "H" に戻して送信データが準備できるまで 80 μS 間待ちます。

 DHT22 が PIC にデータを送信する場合は、その後のすべてのビットで初めに 50 μS 間 "L" を送り、次に、それに続く "H" の長さで、ビットが "0" か "1" かの区別をします。 すなわち、 "H" が 26 μS であれば "0" であり、 70 μS であれば "1" を送信したことになるのです。

 そして、これらの送信データは、まず初めに湿度上位 8 bit、続いて湿度下位 8 bit、温度上位 8 bit、温度下位 8 bit と続き、最後にチェックサム 8 bit の、計 8 bit x 5 = 40 bit が DHT22 から PIC に送信がされます。 すべての bit が送信されるとデータバス(SDA)は解放され、再び "H" に保たれて次の通信に備えます。


 上の概略図に、詳細なタイミングを付け加えて表したものが、以下に示した図と表です。 (これらの図と表はすべて AOSONG 社のデータシート AM2302 から抜粋)


 以上の概略説明と図表から、実際にプログラミングをした "デジタル温度湿度センサーモジュール DHT22 制御サブルーチン( ファイル: DHT22.sub )" の内容を次に示します。 このサブルーチンを作成するに当たって、サイト "O-Family 電子工作の部屋" さんのページに収録がされている、 "DHT22Test_101.TXT" を参考にさせていただきました。 ありがとうございました。
      ;==========================================================================
      ;	デジタル温度湿度センサーモジュール DHT22 制御サブルーチン
      ;==========================================================================
      
      ;#define 	TRISX	TRISC		;例. DHT22 で使用する DATA ポートを
      ;#define 	PORTX	PORTC		;include する側で #define 定義をすること
      
      ;<使用外部ルーチン>
      
      ;(wait_10us, wait_1ms)
      
      ;<使用レジスタ>
      
      ;humidity_h				;湿度上位 8 bit
      ;humidity_l				;湿度下位 8 bit
      ;temperature_h				;温度上位 8 bit
      ;temperature_l				;温度下位 8 bit
      ;time_cnt				;bit0-5:時間計測カウンタ, 
      					;bit6:チェックサム(CRC)エラー, bit7:タイムアウトエラー
      ;lp_cnt1				;汎用ループカウンタ
      ;lp_cnt2				;汎用ループカウンタ
      ;work1					;ワーク
      ;work2					;ワーク
      
      ;==========================================================================
      ;	    デジタル温度湿度センサーモジュール DHT22 から読み出し
      ;==========================================================================
      
      dht22_measure
      		bsf	STATUS,RP0	;バンク 1
      		bcf	TRISX,DHT	;出力ポート
      		bcf	STATUS,RP0	;バンク 0
      
      		bcf	PORTX,DHT	;DHT="L", スタート信号
      		call	wait_1ms	;1m 秒ウェイト
      
      		bsf	STATUS,RP0	;バンク 1
      		bsf	TRISX,DHT	;入力ポート, プルアップで DHT="H"
      		bcf	STATUS,RP0	;バンク 0
      
      		call	dht_low		;DHT22 からの応答信号 "L" を待つ
      		btfsc	time_cnt,7	;タイムアウトエラー か?
      		goto	dht_m05		;Yes
      
      		movlw	humidity_h	;湿度上位 8 bit のアドレス
      		movwf	FSR		;間接アドレスに設定
      		clrf	work1		;ワーク, チェックサム計算用
      		movlw	5
      		movwf	lp_cnt1		;バイトカウンタ
      
      dht_m01		clrf	work2		;ワーク, ビットデータ保管用
      		movlw	8
      		movwf	lp_cnt2		;ビットカウンタ
      
      dht_m02		call	dht_low		;DHT22 からのビットの同期信号 "L" を待つ
      		btfsc	time_cnt,7	;タイムアウトエラー か?
      		goto	dht_m05		;Yes
      
      		call	dht_high	;DHT22 からの "H" 信号の長さを計測
      		btfsc	time_cnt,7	;タイムアウトエラー か?
      		goto	dht_m05		;Yes
      
      		bcf	STATUS,C
      		rlf	work2,F		;左にシフト
      
      		movlw	4		;3 〜 4
      		subwf	time_cnt,W
      		btfsc	STATUS,C	;time_cnt >= 4 か?
      		bsf	work2,0		;Yes. ビット "1"
      
      		decfsz	lp_cnt2,F	;ビットカウンタ lp_cnt2 - 1 = 0 か?
      		goto	dht_m02		;No
      
      		movf	work2,W		;受信データ 1 バイト
      
      		decfsz	lp_cnt1,F	;バイトカウンタ lp_cnt1 - 1 = 0 か?
      		goto	dht_m03		;No
      		goto	dht_m04		;Yes
      
      dht_m03		movwf	INDF		;受信データ格納
      		incf	FSR,F		;間接アドレスの更新
      		addwf	work1,F		;チェックサムの計算
      		goto	dht_m01		;No
      
      dht_m04		subwf	work1,W		;
      		btfss	STATUS,Z	;チェックサムは一致 か?
      		bsf	time_cnt,6	;No. チェックサム(CRC)エラー
      
      dht_m05		return
      
      ;--------------------------------------------------------------------------
      
      		; DHT22 からの "L" 信号を待つ "H" → "L" → "H"
      
      dht_low
      		call	dht_high	;DHT22 からの "H" 信号の長さを計測
      		btfsc	time_cnt,7	;タイムアウトエラー か?
      		goto	dht_l02		;Yes
      
      		clrf	time_cnt	;初期設定
      
      dht_l01		call	wait_10us
      		incf	time_cnt,F	;
      		btfsc	PORTX,DHT	;DHT="H" か?
      		goto	dht_l02		;Yes
      
      		movlw	10
      		subwf	time_cnt,W
      		btfss	STATUS,C	;time_cnt >= 10 か?
      		goto	dht_l01		;No
      
      		bsf	time_cnt,7	;タイムアウトエラー
      
      dht_l02		return
      
      ;--------------------------------------------------------------------------
      
      		; DHT22 からの "H" 信号の長さを計測
      
      dht_high
      		clrf	time_cnt	;初期設定
      
      dht_h01		call	wait_10us
      		incf	time_cnt,F	;
      		btfss	PORTX,DHT	;DHT="L" か?
      		goto	dht_h02		;Yes
      
      		movlw	10
      		subwf	time_cnt,W
      		btfss	STATUS,C	;time_cnt >= 10 か?
      		goto	dht_h01		;No
      
      		bsf	time_cnt,7	;タイムアウトエラー
      
      dht_h02		return
      
      ;=============================================================== end ======
      
 ところがまた、この "DHT22 制御サブルーチン" がすぐには、まともに動いてはくれませんでした。 まともどころか、まったく DHT22 からの温湿度データを正常に受け取ることができないのです。 LCD には " 0.0 ゚C  0.0 % " としか表示がされないのです。 本機デジタル時計の全体プログラムも終盤に近づいていたので、力が抜けてしまって正直がっくりな気分でした。

 このようにプログラムがトラブってしまうと、いろいろなことを考えてしまい、ハードウエアの不良をも疑ってしまいます。 その経緯は 使用した各モジュールについてデジタル温度湿度センサーモジュール の項の後半部分で述べた通りですが、これがまた2度目の長い泥沼との戦いの始まりになったのでした ・・・

 この "DHT22 制御サブルーチン" は、サイト "O-Family 電子工作の部屋" さんのページに収録がされている、"DHT22Test_101.TXT" を参考にさせていただいたことは上述したところですが、 "DHT22Test_101.TXT" は AVR BASIC で記述がされています。 私にとって久し振りに見る BASIC プログラムであっても、文脈から何をしているのかは分かるので、私が必要としている部分を PIC のアセンブラプログラムに変換をして、"DHT22 制御サブルーチン" に組み込んでいます。

 しかし、まともに動作をしないのです。 私の "DHT22 制御サブルーチン" でも、上のリストを見ていただければわかるように、タイムアウトエラーやチェックサム(CRC)エラーの検出を行っています。 LCD に " 0.0 ゚C  0.0 % " としか表示がされない、 ということは例え "0" であっても、エラーにはならずに通信が行われている、ということになります。

 タイムアウトエラーというのは、上述した各 bit を送信するときの "L"や "H" の長さが 100 μS をオーバーしたときに、上に示したプログラムではエラーとしています。

 また、チェックサム(CRC)エラーは、DHT22 からの送信されてくる全データ 40 bit で、次の計算を行った結果、 右辺と左辺が = にならなかったときにエラーとしています。
      		湿度上位 8 bit + 湿度下位 8 bit + 温度上位 8 bit + 温度下位 8 bit = チェックサム 8 bit
      
 しかし、タイムアウトエラーにならないということは、"L"や "H" の長さは 100 μS をオーバーすることはない。 また、チェックサム(CRC)エラーにならないですべて "0" が表示されるということは、 DHT22 から送信されてくるすべての bit が、やはり "0" としか考えようがないではないか。 そうであれば、ハードウエアの不良? ・・・ 泥沼 ・・・ 泥沼。

 上のリスト中で dht22_measure サブルーチンの中ほど過ぎにある
      		movlw	4		;3 〜 4
      		subwf	time_cnt,W
      		btfsc	STATUS,C	;time_cnt >= 4 か?
      		bsf	work2,0		;Yes. ビット "1"
      
というところに注目をしてください。 これは修正後であって、修正前は
      		movlw	5
      		subwf	time_cnt,W
      		btfsc	STATUS,C	;time_cnt >= 5 か?
      		bsf	work2,0		;Yes. ビット "1"
      
としてありました。 これが泥沼の原因でした。 ちなみに AVR BASIC プログラムの "DHT22Test_101.TXT" では
         If Dht22_timer > 5 Then                                  'If [DATA]が[1]か? Then
            Set Temp3.0
         End If
      
となっています。 これらは "H" の長さを判定して、ビットを "1" にするか "0" にするかを決めているところで、BASIC プログラムでは 50 μS を超えたときに "1" にしているのに対して、 アセンブラプログラムでは 50 μS 以上を "1" にしています。 それにも関わらず、アセンブラプログラムでは "1" となることがなく、すべて "0" になってしまうのです。

 上に示したリスト中の最終部分を再掲すると、
      		; DHT22 からの "H" 信号の長さを計測
      
      dht_high
      		clrf	time_cnt	;初期設定
      
      dht_h01		call	wait_10us
      		incf	time_cnt,F	;
      		btfss	PORTX,DHT	;DHT="L" か?
      		goto	dht_h02		;Yes
      
      		movlw	10
      		subwf	time_cnt,W
      		btfss	STATUS,C	;time_cnt >= 10 か?
      		goto	dht_h01		;No
      
      		bsf	time_cnt,7	;タイムアウトエラー
      
      dht_h02		return
      
というところがありますが、この部分で次の一文の実行回数をカウントしています。
      dht_h01		call	wait_10us
      
 すなわち、基本となるウエイト wait_10us の実行回数をカウントして、"H" の長さを計測しているのです。

 このウエイトルーチン wait_10us の中身は、上に示したリスト中にはなく、実はメインルーチンである "ソースファイル ( I2Cmod_LCD1602_Clock.asm )" の中にあり、その部分を示してみると、
      		; 10μ秒 ウェイトルーチン
      wait_10us
      		movlw	4		;1
      		movwf	wt1cnt		;1
      wu03		nop			;1
      		decfsz	wt1cnt,F	;1,2
      		goto	wu03		;2,0	1+1+(1+1+2)*3+(1+2+0)=17
      		nop			;1
      		return			;2	17+1+2=20
      					;Total	20/8MHz*4 = 10 μS
      
のようになっています。 このウェイトルーチンは基本クロックが 8 MHz のときのもので、本機で使用している PIC16F684 も、内部クロックを 8 MHz に設定をしているので間違いはない筈です。

 それにも関わらず "1" と "0" の判定に 50 μS では大きすぎるというのです。 上述したようにデータシート上では、"H" が 26 μS であれば "0" であり、70 μS であれば "1" の筈なんですが。 もう少し正確に言うと、上で示した Table 6: Single bus signal characteristics によると、TH1 は min 68 μS、typ 70 μS、max 75 μS と規定がされています。

 結局、次のようにこの判定には 30 μS、または 40 μS と修正をすることで、"1" と判定をしてくれるようになりました。
      		movlw	4		;3 〜 4
      		subwf	time_cnt,W
      		btfsc	STATUS,C	;time_cnt >= 4 か?
      		bsf	work2,0		;Yes. ビット "1"
      
 この事実と、データシートの値とでは大きく異なっていました。 このように修正をすることで一件落着となったのですが、貴重な時間を、この間約1週間ほどを費やしてしまいました。 このように、不具合の泥沼に陥ってしまってから、やがてその泥沼から抜け出すまでを長々と書いてきましたが、こうして結論付けた内容で、ひょっとしたら私の思いもよらない勘違い、あるいは考慮不足から少なからず考えが及ばなかった点があるかもしれません。 このページをご覧になった皆さんはどのようにお考えでしょうか。

 | プログラムのトップに戻る |


現在の最新バージョン: Ver. 1.00

| ページトップ |

■ プリント基板の加工と部品配置 ■

 使用したプリント基板は、"秋月電子" の "片面ガラス・ユニバーサル基板 Cタイプ(72×47.5mm)めっき仕上げ(通販コード P-00517)" で、下図に示すように まず、横の ----- 線の位置で切断をします。 次に、切り離した下側の基板を、更に縦の ----- 線2ヶ所の位置で切断をし、ケース取り付け用のΦ3.2 の穴を2個開けます。

 図中で、長方形の中にで表したものは通常のピンヘッダ(オス)で、同様にで表したものはピンソケット(メス)を使用することを表しているので、注意をしてください。

| プリント基板部品配置図 (I2Cmod_LCD1602_Clock_PC0.CE3) | ページトップ |

■ プリント基板(1)パターン図 (部品面) ■

 下図で LED と記された 2P のピンヘッダは、LCD のバックライト用 LED のことで、実際には I2C インタフェースモジュールの LED コネクタ に接続をします( モジュール写真回路図 を参照)。 デフォルト使用では、ジャンパーピンでショートがされていますが、明るさが強過ぎるのでボリュームで調節ができるように、ジャンパーピンは外してこの基板の LED 端子と接続をします。 極性はありません。 なお、500Ωの半固定ボリュームは、右に回すと抵抗値が小さくなる(明るくなる)ように配線をします。


 初め、基板の左横に位置する GND と +5V の 3P のピンヘッダに、上左の図のように通常のストレート型のピンヘッダを使用していました。 ところが、実際にケースに取り付ける段になって、このピンヘッダと右上隣りに位置する 4P のピンヘッダの、 それぞれの相棒であるピンソケットを固定している2つの小基板同士が、ぶつかり合ってしまうことが分かりました。 そこで、それを回避するために、その後、上右の写真のように GND と +5V の 3P のピンヘッダの方を、L 型のものに取り替えました。

| プリント基板パターン図 (部品面) (I2Cmod_LCD1602_Clock_1PC.CE3) | ページトップ |

■ プリント基板(1)パターン図 (ハンダ面) ■


| プリント基板パターン図 (部品面) (I2Cmod_LCD1602_Clock_1PC1.CE3) | ページトップ |

 次の写真は、リアルタイムクロックモジュール、デジタル温度湿度センサーモジュールを、プリント基板(1) のピンソケット(メス)に差し込んで、実際に使用時の状態にしたものです。

真横から見たところ 斜め上から見たところ

■ プリント基板(2)パターン図 (部品面) ■

コネクタ, ケーブルを 取り付け前の様子 , 取り付け後の様子

| プリント基板(2)パターン図 (部品面) (I2Cmod_LCD1602_Clock_2PC.CE3) | ページトップ |

■ プリント基板(2)パターン図 (ハンダ面) ■

コネクタ, ケーブルを 取り付け前の様子 , 取り付け後の様子

| プリント基板(2)パターン図 (ハンダ面) (I2Cmod_LCD1602_Clock_2PC1.CE3) | ページトップ |

■ ケース加工図 ■

 使用したケースは、100均(セリア)で購入した "クリアケース ミニ (L-8033) サナダ精工" という、ポリスチレンケースです。

 ( 下箱上面図 ) の上部分にたくさんのΦ3.2 の丸穴が開けてあるのは、このケースの使用時には蓋 ( 上箱上面図 ) をするために箱内が密閉状態になるのを防いで、箱外の空気が自由に流出入するように開けました。 本機では温度湿度センサーモジュールを使用しているため、その機能が妨げられないようにするのが目的です。

 I2C インタフェースモジュールと合体している LCD(1602A)モジュールは、ケース底面との間に大きな空間が必要なため、手持ちの 20 mm 長のジュラコンスペーサ を挟んで取り付けましたが、 できれば 25 mm 長位のスペーサの方がよりベターです。

 ( 下箱右側面図 ) において、プリント基板(2)(スイッチ基板)の取り付け位置に 13.5 mm と記入がしてありますが、実際には 15 〜 16 mm にしないと不具合が起こります。 私は図のように 13.5 mm で加工をしてしまったために、 基板と圧電スピーカーが干渉をしてしまって取り付けができないため、仕方なく基板の干渉する部分をやすりで削ることになりました。

| ケース加工図 (I2Cmod_LCD1602_Clock_CS.CE3) | ページトップ |

■ 使用部品表 ■

(主要部品: IC, トランジスタ等)

(データシート)
PICマイコン .................... PIC16F684
LCDモジュール ....................
I2C接続モジュール ....................
RTCモジュール ....................
温度湿度センサーモジュール ....................

| 部品表 | Excel ファイル (I2Cmod_LCD1602_Clock_parts.xls) | ページトップ |

■ 参考資料・サイト ■

O-Family 電子工作の部屋 .......... http://www.ne.jp/asahi/shared/o-family/ElecRoom/ElecMAIN.htm
 ATB_DS3231_102.TXT .......... http://www.ne.jp/asahi/shared/o-family/ElecRoom/AVRMCOM/DS3234Scnt/ATB_DS3231_102.TXT
 DHT22Test_101.TXT .......... http://www.ne.jp/asahi/shared/o-family/ElecRoom/AVRMCOM/SHT11_DHT22/DHT22Test_101.TXT
LCD-1602A データシート .......... http://www.datasheet.jp/PDF/519148/LCD-1602A-%E3%83%87%E3%82%BF ..... .html
PCF8574/PCF8574A データシート .......... https://www.nxp.com/docs/en/data-sheet/PCF8574_PCF8574A.pdf
PCF8574/PCF8574A データシート(日本語版) .......... https://www.tij.co.jp/jp/lit/ml/jajb003/jajb003.pdf
DS3231 データシート .......... https://datasheets.maximintegrated.com/en/ds/DS3231.pdf
AM2302 (DHT22) データシート .......... https://akizukidenshi.com/download/ds/aosong/AM2302.pdf
DHT22 データシート(日本語訳) .......... https://pub.idisk-just.com/fview/Ofo5BntkKx3FZmmLkRMOsMRNS439HU6_QbdmJ9kpFT_ ..... .pdf
Rasbee 1602 LCD ディスプレイモジュール DC 5V 16×2キャラクタ LCDブルーブラックライト 白文字 2行×16文字 LCDモジュール Arduino に対応
KKHMF 10PCS 1602 LCD ブラック IIC/ I2C / TWI/SPI シリアル インタフェース ボード モジュール
Rasbee 5個 DS3231 時計モジュール 国内配送 AT24C32 高精度 リアル時間時計モジュール IIC RTCモジュール リアルタイムクロック DC 3.3-5.5V DS1307 ...
Rasbee DHT22 デジタル温度センサーモジュール AM2302 接続線付き Arduino DIY用 [並行輸入品]

| ページトップ | ホーム |


Copyright (C) 2020-2023 やまもとみのる
初版:2020年12月10日、初公開:2021年2月4日、最終更新:2023年10月24日