| ホーム | 私の電子工作作品集 |
[ 初公開日:2021年2月4日 ] |
半年以上も前になりますが、"187. FMステレオラジオ" を製作するために、「 RDA5807M IC チップ FMラジオモジュール」 を購入して以来、アマゾンの中華モールに少しばかりハマっている私ですが、
それ以降にもいろいろなもの(電子部品)を中華モールで購入をしています。 それらの中から今回は、「 LCD(1602A)モジュール」、「 I2C インタフェースモジュール」、「リアルタイムクロックモジュール」、「デジタル温度湿度センサーモジュール」を使用した、"I2C IF モジュール + LCD(1602A) 表示時計" を製作してみました。 実は、時計を製作することが本来の私の目的ではなく、これらの各モジュールを使用することはもちろん初めてなので、どのようなものなのかその各モジュールの機能(実力)を試したくて、とりあえず、"I2C IF モジュール + LCD(1602A) 表示時計" の製作にまとめてみたのです。 |
|
||||||||||||||||
RTC モジュールの赤色 LED が眩しいのでテープを貼って輝度を抑えました |
| 回路図 (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)センサーモジュールを使用して、 温度だけではなく湿度も一緒に表示をさせるようにしています。
このモジュールは上の写真に示すような外観で、裏面側(写真右)や下回路図からも分かるように、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 とのシリアル通信を行っているようです。 |
| ページトップ |
|
| ページトップ |
[ DS3231 Error! ] [ Init?_ ] ……… 応答を求めている [20xx/xx/xx (xxx)] [ xx xx:xx:xx ] モード 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 ]
[ ** SET MODE ** ] [ ] v [20xx/xx/xx (xxx)] ……… v の位置の文字がブリンク表示 [ xx xx:xx:xx ] v [20xx/xx/xx (xxx)] ……… v の位置の文字がブリンク表示 [ xx xx:xx:xx ] BACK BACK BACK BACK BACK BACK MODE ← ← ← ← ← ← /SET SET SET SET SET SET SET SET NORMALモード ─→ 年(下位2桁) → 月 → 日 → 24H/12H → 時 → 分 → 秒 ─→ NORMALモード │ ↑ BACK└──────────────────────┘ [20xx/xx/xx (xxx)] [20xx/xx/xx (xxx)] [ xx:xx:xx ] ……… 24 時間表示 [ AM xx:xx:xx ] ……… 12 時間表示 (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 [20xx/xx/xx (xxx)] [20xx/xx/xx (xxx)] [DHT22 Time Out! ] [DHT22 CRC Error!] |
| ページトップ |
私がこのホームページでプログラムと呼んでいるのは、主に 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 ====== ;========================================================================== ; 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 ====== しかし、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" は刻々と "時(とき)" を刻み始めるのですが、当然そのときのリアルなタイムとは異なっているため、 この後、メインプログラム側では、これらの年月日時分秒をリアルなタイムに変更をする処理が必要となります。 また、この初期設定の処理が必要なことを検知する方法には、電源を入れた直後に、上表で示したレジスタ群の内の 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 ====== このようにプログラムがトラブってしまうと、いろいろなことを考えてしまい、ハードウエアの不良をも疑ってしまいます。 その経緯は 使用した各モジュールについて の デジタル温度湿度センサーモジュール の項の後半部分で述べた通りですが、これがまた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 |
| ページトップ |
| プリント基板部品配置図 (I2Cmod_LCD1602_Clock_PC0.CE3) | ページトップ |
下図で LED と記された 2P のピンヘッダは、LCD のバックライト用 LED のことで、実際には I2C インタフェースモジュールの LED コネクタ に接続をします( モジュール写真、回路図 を参照)。
デフォルト使用では、ジャンパーピンでショートがされていますが、明るさが強過ぎるのでボリュームで調節ができるように、ジャンパーピンは外してこの基板の LED 端子と接続をします。 極性はありません。
なお、500Ωの半固定ボリュームは、右に回すと抵抗値が小さくなる(明るくなる)ように配線をします。 初め、基板の左横に位置する GND と +5V の 3P のピンヘッダに、上左の図のように通常のストレート型のピンヘッダを使用していました。 ところが、実際にケースに取り付ける段になって、このピンヘッダと右上隣りに位置する 4P のピンヘッダの、 それぞれの相棒であるピンソケットを固定している2つの小基板同士が、ぶつかり合ってしまうことが分かりました。 そこで、それを回避するために、その後、上右の写真のように GND と +5V の 3P のピンヘッダの方を、L 型のものに取り替えました。 |
| プリント基板パターン図 (部品面) (I2Cmod_LCD1602_Clock_1PC.CE3) | ページトップ |
| プリント基板パターン図 (部品面) (I2Cmod_LCD1602_Clock_1PC1.CE3) | ページトップ | |
真横から見たところ | 斜め上から見たところ |
コネクタ, ケーブルを 取り付け前の様子 , 取り付け後の様子 |
| プリント基板(2)パターン図 (部品面) (I2Cmod_LCD1602_Clock_2PC.CE3) | ページトップ |
コネクタ, ケーブルを 取り付け前の様子 , 取り付け後の様子 |
| プリント基板(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用 [並行輸入品] |