PICと温度計モジュールを利用した、
温度計測装置の作成


 名古屋の大須電気街を歩いていたら、デジタル温度計モジュールというのを 見かけたので、早速購入してきました。帰ってから見てみると、測定した温度 をデジタル出力する端子つき。出力されるデータのフォーマットも明記されて います。これは、パソコンに取り込んで、温度計測装置を作るしかない、と 思い、ゼミで製作してみることにしました。

 添付の説明書を見ると、この温度計モジュールには、16ピンの端子が付いており、それぞれの機能が紹介されています。時計機能やアラーム機能 などもあ り、時計やアラームの時刻合わせに使うピンや、温度測定の時間間隔を設定したり、温度を摂氏で表示するか華氏で表示するかを設定するピンもあります。

 このうち、測定温度を外部に出力するのは、9ピンと10ピンの2本のピンを使って行われます。データは9ピンから、13ビットのシリアルデー タとして出力されます。各ビットを、D1からD13とします。シリアルデータが出力されていることは、10ピンの電圧の立ち上がりで知ることができます。 つまり、10ピン(LATCH)の電圧が立ち上がっている間に、9ピン(DATA)をリードします

 シリアルデータの先頭は、この10ピンの立ち上がっている時間の長短で判定するようになっています。すなわち、データの先頭(D1)の ときは、10ピン(LATCH)の電圧が立ち上がっている時間が他(D2からD13)よりも長くなっています。説明書によると、D1のときの立ち 上がっている時間は約1[ms]、 D2からD13の時は約125[μs]となっています(下図参照)。また、D2からD13においては、パルスとパルスの間は、約1[ms]の間隔が空いて います。




 そこで、この9ピンと10ピンのデータをパソコンに送ってやれば、この温度計モジュールで測定した温度をパソコンに取り込むことができます。しかし、温 度計モジュールから出力されるシリアルデータそのままでは、パソコンのシリアル通信とは速度もフォーマットも電圧も違うので、その部分の“擦り合わせ”を する必要があります。そこで、PICを間にはさんでやることにしました。つまり、温度計モジュールのデータを取り込み、それを加工して、パソコンに送り出 すという仕事を、PICにさせるわけです。





 上は、そのために設計した回路です。この温度計モジュールは、乾電池一個で動作するように設計されているもの で、 PICに接続するためには、電圧を変換する必要があります。さらに、それをRS-232Cに送り出すために、再度電圧を変換する必要があります。PICと しては、前回のLED点灯回路と同じく、PIC12F629を用いることとしました。

 電源は、パソコンのRS-232Cそのものからとることにしました。以下に、RS-232Cの信号線の意味を書いておきます。入力/出力 はパソコン を基準としたものです。


ピン
入力/出力
記号
機能
意味

入力
CD
キャリア検出
通信相手が送受信可能状態であることをパソコンに知らせる信号 です。昔は、モデムが電話線のキャリアを検出したことをパソコンに知らせるために使われたので、この名前になっています。

入力
RxD
受信データ
通信相手からパソコンに送ってくるデータです。

出力
TxD
送信データ
パソコンから通信相手に送るデータです。

出力
DTR
データ端末レディー
パソコンがつながっていることを通信相手に知らせる信号です。


GND
グランド
信号のグランドです。

入力
DSR
データセットレディー
通信相手がつながっていることをパソコンに知らせる信号です。 パソコンはこの信号線を見て、通信相手の有無を判定します。

出力
RS
送信要請
パソコンがまもなくデータ送信することを示す信号です。

入力
CS
送信可
通信相手がパソコンにまもなくデータ送信することを示す信号で す。パソコンは、この信号を受けると、受信準備をします。

入力
RI
被呼表示
電話がかかってきたことをモデムがパソコンに通知するための信 号です。これを利用すれば、パソコンの電源を外部から制御できます。


 今の場合、パソコンからPIC12F629にデータを送る必要は無く、PIC12F629からパソコンにデー タを送るだけです。その意味では、使う信号線は、2ピンと5ピンの2本だけです。他のピンはすべて空きですので、そのうちの4ピンと7ピンを流用すること にします。RS-232Cの場合、0が+10[V]、1が-10[V]となっています(*注1)から、パソコン側から4ピンと7ピンに0を送り続けます。 す ると、この2本のピンはともに+10[V]になりますから、これをダイオードで2本束ねて、それをPICならびに温度計モジュールの電源とするわ けです。
(*注1) 正確には、0の場合は+3[V]から+25[V]の間、1の場合は-3[V]から-25[V]の間と決められています。

 回路図では、コネクターの中で4ピンの出力を1ピンと6ピンに、7ピンの出力を8ピンにそれぞれつないでいます。これは必須ではないのですが、OSや データ取り込みソフトの仕様によっては、ここをつないでおかないといつまでも入力待ちとなって、PICから送り出したデータを受け取ってくれない可能性が ありますので、念のためにこのように結線しておきました。

 では、回路図を順に見ていきます。温度計モジュールは、既述の通り、9ピンがシリアルデータ、10ピンがデータのタイミングを表す同期データです。1ピ ンがグランド、16ピンが電源です。その他のピンは今回は使いません。この温度計モジュールは単三電池一個で動作するように設計されており、説明書には動 作可能電圧範囲についての既述は無かったのですが、実際に電源電圧を変えて実験してみると、1.2[V]から1.8[V]の範囲でしか正常に動作しません でした。そこで、温度計モジュールの電源としては、LEDの定電圧特性を利用した1.7[V]の簡易電源を用いることとしました。それが回路図中の LED (D3) です。

 9ピンあるいは10ピンから出力されるデータは、0が0[V]、1が1.5[V]となっており、このままではPICで拾うことができません。作った回路 では、PICは5[V]で動いていますので、1.5[V]では『1』と判定されません。PIC12F629は、電顕電圧を2.5[V]ぐらいまでは落とす ことができるのですが、それでも1.5[V]を『1』と判定するのは苦しいと思います。そこで、Q1とQ2の2つのトランジスターで、電圧を変換すること にしました。これは単なるインバーター回路で、温度計モジュールの9ピンが0(0[V])の時、Q2のコレクター(したがってPICの5ピンも)は5 [V]に、温度計モジュールの9ピンが1(1.5[V])の時、Q2のコレクター(したがってPICの5ピンも)は0[V]になります。インバー ター回路ですので、温度計モジュールの出力とPICの入力では0と1が反転しますが、これは、PICで読み込んだ後で、プログラムで再度反転させて元に戻 すことにします。電圧変換回路の副作用で反転した0と1を、ソフトウェアで吸収するわけです。なお、トランジスターは汎用の物なら何でも良く、こ こではQ1、Q2ともに、2SC1815を用いました。

 PICはセラミック振動子を用いて、クロック周波数4MHzのXTモード(*注2)で発振させることにしました。クロック周波数が低すぎると、パソコン に送り出すRS-232Cのタイミングがシビアになってきますし、かと言ってクロック周波数を高くしすぎると消費電力が増えるので、このあたりで妥協する ことにしました。電源をパソコンのRS-232Cポートの信号線から取っているので、電流容量にあまり余裕がありませんので、これは仕方ありません(特 に、ノートパソコンのとき)。古いタイプのノートパソコンを使うときなどは、特に電流容量に余裕が少ないので、そういう時はレギュレーターICを低ドロッ プ型でしかも待機電流の少ない AN8003 などに変えて、PICも3[V]動作とし、RS232CドライバーICも低電圧型のADM3202などに交換する必要があるかも知れません。
(*注2) PIC12F629のクロック発振モードの一種。詳しくは、前回の実習、『PIC を使った、LED 点灯回路の実験』を参照。

 このように、温度計モジュールの9ピンと10ピンの出力は、トランジスターによる電圧変換回路を通って、PIC12F629の5ピンと4ピンにそれぞれ 入力されます。PIC12F629では、7ピンをポート0、6ピンをポート1、5ピンをポート2、4ピンをポート3とそれぞれ名付けています。つまり、温 度計モジュールの9ピンのデータを読みたい時は、ソフトウェアでポート2を、温度計モジュールの10ピンのデータを読みたい時は、ソフトウェアでポート3 をそれぞれ読めばよい、ということになります。

 具体的には、PIC12F629の特殊レジスターの一つである、GPIOレジスターの値を読みます。PIC12F629ではデータは8ビットで表される のですが、それを2進数で表したとき、下位ビットから上位ビットに向かって、順に、ポート0、ポート1、ポート2、ポート3のデータを格納する領域になっ ています(下図参照)。なお、ポート4、ポート5というのもありますが、今回は使いません(XTモードでは、2ピンと3ピンはクロック発振のために使うか ら)。



GPIOレジスターの各ビットの意味


 PIC12F629の7ピン(ポート0)は、RS232CドライバーICである、ADM232の10ピンに入力され、RS232Cの電圧レベルに変換さ れて、13ピンから出力されます。これは、直接、D-SUB 9ピンコネクターの2番ピンにつないで、そのままパソコンにつなぐことができます。ADM232は電圧を変換するだけのICで、PICの5[V]/0 [V]を、-10[V]/+10[V]に変換してくれる(逆も可)、便利なICです。IC内部に20kHzの発振回路を持ち、DCDCインバータの原理 で、+5[V]単一電源から+10[V]と-10[V]とを作り出します。なお、ICは、MAX232でも同様に使えます。

 PIC12F629の2ピンと3ピンには、セラミック発振子(コンデンサー内蔵型)を回路図のように接続します。前回の実習では周波数安定度は必要な かったので、抵抗とコンデンサーを用いたRCモードで発振させましたが、今回はパソコンとシリアル通信をするので、周波数安定性を重視して、セラミック発 振子を用いたXTモードでのクロック発振としました。

 PIC12F629の7ピンが、ADM232を通してパソコンにつながるので、PICがデータを送り出すときは、ソフトでポート0に値を書き込め ばよい、ということになります。ただし、シリアル通信の規格に合うように、値を書き込んでやらなければなりません。これについて は、後で、アセンブラープログラムのところで詳述します。

 では、PICのプログラムの説明に移ります。まず、先ほども紹介しました、温度計モジュールからのデータ出力のタイミングを再 度掲げます。





 プログラミングの考え方は、以下のようになります。

 まず、ポート3(温度計モジュールの10ピン、つまりLATCH)を常に監視します。温度計モジュールからデータが出力されていないときは、LATCH の値は0ですから、Q1を通って0、1が反転し、PICのポート3は1になっているはずです。そのためには、GPIOレジスタの第3ビットを監視します。 それが1の間は温度計モジュールからデータは来ていない、ということになります。

 もし、ポート3が0になれば、LATCHが立ち上がったことになります。しかし、D1の長いパルスなのか、D2〜D13の短いパルスなのか分かりませ ん。それで、200[μs]だけ間を置いて、再度ポート3を読むことにします。規格によると、D1のパルスは継続時間が1[ms]、D2〜D13のパルス は継続時間が125[μs]なので、200[μs]後に再度ポート3を読んでみて、それがまだ0ならD1の長いパルス、1ならD2〜D13の短いパルスと 判定できます。しかし、短いパルスが入った後、たまたま、200[μs]後にノイズが入ってポート3が0となることもありえないことではありません。 もしそうなると、データの先頭を誤認するということになります。そこで、念には念を入れて、さらに200[μs]の間隔を置いて、再々度ポート3の値を読 むことにします。つまり、200[μs]の間隔を置いて3回ポート3の値を読み、それがすべて0ならば長いパルスであると考えて、データの先頭であ ると判断する、と いうことにします。

 ポート3の値をプログラム中で読むのは簡単で、ビットテスト命令である、 「BTFSC  GPIO,3」または、「BTFSS  GPIO,3」を使います。「BTFSC   レジスター名, n」とすると、そのレジスターの第nビット(最下位が第0ビット、最上位が第7ビット)を見て、そのビットが0ならば次の命令をスキップします。 「BTFSS」はその逆で、テストしたビットが1ならば次の命令をスキップします。ポート3はGPIOレジスタの第3ビットですので、上述の命令を用いれ ば、それが0か1かが分かります。

 3回続けてポート3の値を読み、それがすべて0ならばデータの先頭と判断して、まず、GPIOレジスタの値を BUF という変数に取っておく事にします。そして、その BUF の第2ビット(=元のGPIOレジスタの第2ビット)を見ます。GPIOレジスタの第2ビットは、ポート2の値です。ポート2は、Q2を通した、温度計モ ジュールの9ピンの値です。Q2を通っているので、01が反転しています。ですから、これを逆にして格納します。すなわち、BUF の第2ビットが0なら1を、1なら0を格納します。これを、汎用レジスターのアドレス30H番地(*注3)に格納することにしました。
(*注3 PIC12F629のメモリーマップについては、前回の前回の実習、『PIC を使った、LED 点灯回路の実験』を参照。)

 その後、ポート3を監視します。監視していると、どこかで、ボート3の値が0から1に戻るはずです。それが、D1の長いパルスの終わりです。D1のパル スの終わりを検出すると、再びポート3を監視しますが、どこかでまた1から0に変わるはずです。それが、D2の短いパルスの先頭のはずです。しかし、ノイ ズかもしれませんので、40[μs]の間隔を置いて、再度読んで見ます。2度とも0であれば、D2の短いパルスであると判断して、そのときのGPIOレジ スタの値を BUF に入れ、その第2ビットを反転して格納します。D1のデータは汎用レジスタのアドレス30H番地に入れたので、D2のデータはアドレス31H番地に入れる ことにします(以下、順番に次のアドレスに入れていきます)。

 D2のデータを格納したら、再びポート3を監視します。また、どこかでポート3の値が1に戻るはずです。そこがD2のパルスの終わりです。続けて監視 し、またポート3の値が0になれば、40[μs]の間隔を置いて、再度読み、それも0ならば、D3のパルスの先頭と判断して、データをアドレス32Hに格 納します。以下、これをD13まで繰り返します。

 D1からD13まで13ビットなので、データの個数を数えて、13個になれば一連のデータの終わり、としてもよいのですが、何らかの理由で温度計モ ジュールが13ビットすべてを送信しないまま終了してしまうと、PICは残りのデータをいつまでも待ち続けることになります。また、逆に、温度計モジュー ルが何らかの理由でいつまでもシリアルデータを送り続けると、PICのバッファーが溢れてしまうことも考えられます。あるいは、たまたまノイズが 40[μs]の間隔を置いて2回入ったりすると、それを短いパルスと誤認して、1ビット余計に勘定する可能性もあります。それらを避けるため、ポー ト3が1に戻ったら、40 [μs]の間隔を置きながらポート3を繰り返し読み、100回連続で1ならば、温度計モジュールからのデータ送信は終了したと判断することにし、それとは 別に、格納されたデータ数を数えて、それが13でなければ、温度計モジュールとPIC12F629との間のどこかでデータの送信ミスまたはデータの受け取 り ミスがあったと判断して、データを破棄することにします

 データが正常に取り込めたら、次は、それをパソコンに送信しなければなりません。そのためには、まず、取り込んだ生データを加工して、温度の数字に直し ます。続いて、それを、RS232CドライバーICである、ADM232に入力してやります。

 この温度計モジュールから出力されるデータのフォーマットは、説明書によると、以下のようになっています。


ビット
内容
D1
温度表示が摂氏のときは、0なら+、1なら−を表す。
温度表示が華氏のときは、温度の100の位を表す。
D2
温度 の10の位を2進数で表す。D2が高位ビット、D4が下位ビット。
D3
D4
D5
D6
温度の1の位を2進数で表す。D6が高位 ビット、D9が下位ビット。
D7
D8
D9
D10
温度の小数第一位を2進数で表す。D10が 高位ビット、D13が下位ビット。
D11
D12
D13


 プログラムでは、ビットD1がアドレス30Hに、ビットD2がアドレス31Hに、以下、順にビットD13がアドレス3CHに入っていますから、これらを 順に読み出して、ビットデータを温度の数字に直します。たとえば、10の位の場合は、D2を左に1ビットシフトしてD3を足し、それをまた左に1ビットシ フトしてD4を足し、またそれを左に1ビットシフトしてD5を足せば、2進数から10進数に変換できますね。しかし、パソコンに送り出すときは、10進数 の数そのものではなく、10進数の数を表す“数字”を送ってやらなければなりません。つまり、数値データではなく、文字データとして送ってやらなけ ればなりません(*注4)。
(*注4  数値データを一切送れないわけではなく、PICは数値データを送って、それをパソコン側のプログラムで文字データに直しても良いのです。しかし、ここで は、PIC側であらかじめ文字データにして送ることにしただけのことです。以下に述べるように、パソコン側にも、受信用のプログラムを作らなければなりま せんが、あらかじめ文字に直して送っておけば、Windows標準のハイパーターミナルなどの多くの通信ソフトでも受信できてるから、そうしただけのこと で す。)

 文字データに直すには、アスキーコードに変換する必要があります。パソコンでは、文字はすべて、通し番号で管理されています。たとえば、「A」という文 字そのものはパソコンは理解できず、内部では「第66番文字(16進数で表すと、第42H番文字)」として処理しています。このように、パソコンで扱うす べての文字には通し番号があります。これがアスキーコードです。この温度計測装置では、「0」から「9」までの数字と、「+」「-」「.」「C」の4つの 文字を送るだけです。そのうち、「+」「-」「.」「C」の4つは始めから文字ですから、結局、0から9までの数値を「0」から「9」までの文字に直せば いいことになります。例えば、気温が35℃のとき、単に35という数字をPICが送り出すと、受けるパソコン側は「第35番文字が来た」と思って、画面に 「#」と表示してしまいます。これを避けるために、35という数字を送るときに、5153と送ってやるのです。すると、パソコンは、「第51番文字が来 た」と思って画面に「3」と表示し、続いて「第53番文字が来た」と思って、画面に「5」と表示してくれます。

 数値を文字に直すのは実は簡単で、「0」という文字はアスキーコードでは「第48番文字(16進数では第30H番文字)」、「1」という文字はアスキー コードでは「第49番文字(16進数では第31H番文字)」、「2」という文字はアスキーコードでは「第50番文字(16進数では第32H番文字)」、以 下同様に、「9」という文字はアスキーコードでは「第57番文字(16進数では第39H番文字)」と決まっていますから、数値に48を足してやれ ば、その数値を表す文字データとなります。10進数で48を足す代わりに、16進数で30Hを足しても同じことです。具体的には、例えば表示した い数値が BUF という汎用レジスターに入っているとして、

    MOVLW     H'30'
    ADDWF     BUF,F

としてやるだけで、数値データから文字データに変換できます。同じことですが、もう少しかっこ良くやりたければ、

    MOVLW     H'30'
    IORWF      BUF,F

としてもかまいません。慎重にやるならば、

    MOVLW    H'0F'
    ANDWF     BUF,F
    MOVLW    H'30'
    IORWF      BUF,F

とするとなお良いと思います(なぜだか分かりますか?)。

 続いて、これを、RS-232Cに送り出してやる必要があります。その際には、通信速度、データビット長、パリティー、ストップビット長を 決めてやらなければなりません。これらは、送信側、受信側で一致さえしておればよく、自由に設定できるものです。例えば、通信速度は、 300bps、600bps、1200bps、2400bps、4800bps、9600bps、19200bpsなどのビットレートが定められており、 送信側と受信側で同じ速度を使う限り、これらのうちのどの速度を使うかは自由となっています。ここでは、通信速度は2400bps、データビット長は8 ビット、パリティーなし、ストップビット長1ビットで通信することにしました。

 RS232Cでデータを送るときは、送るべきデータの下位ビットから順に送ります。さらに、送るべきデータの前にスタートビット1ビット と、ストップビットを付けて送ります。今の場合、データビット長は8ビット、ストップビット長は1ビットと決めましたから、スターとビット1ビットと合わ せて、合計10ビットをPICからパソコンに送らなければなりません。

 RS232Cの場合、スタートビットの内容は0、ストップビットの内容は1と決められています。また、データを送っていないときは、常に1を送り 続けなければなりません。シリアル通信の入門書などを見ても、今一つ良く分からないのは、実は、このことについての説明が欠けているからです。

 先にも述べたように、シリアル通信は負論理を採用していますから、0を送出するときは+10[V]、1を送出するときは-10[V]となっています。何 もデータを送っていないときは、常に1を送り続けるというのは、つまり、何もデータを送っていないときは、信号線は-10[V]となっている、ということ です。回路図を見ていただくと分かりますが、PICからの出力は、ADM232を通って、D-SUBコネクターの2ピンにつながっています。これは、パソ コンから見ると、データ入力のピンです。ここが継続して-10[V]になっていると、パソコンはデータが来ていないと判断して、ここが+10[V]になる のをひたすら待っています。

 PICでデータを送るときは、まず、データ内容に先立って、スタートビットである0を送ります。すると、D-SUBコネクターの2ピンは、+10[V] となります。すると、パソコンは、「来た!」と思って、データ受信を開始します。そして、決められた通信速度に対応する時間間隔で決められたデータ長だけ 順にデータを読み込みます。最後にPICがストップビットの1を送出すると、D-SUBコネクターの2ピンが-10[V]になりますので、パソコンはそれ を見届けて、また次のデータが来るまで、受信の待機状態となります。ストップビットが1で、データを送らないときも常に1を送出していなければならないの ですから、要するにスタートビットとデータ以外は、常に1を送っていることになります。

 今の場合、通信速度は2400bpsと決めました。これは、1秒間に2400ビットのデータを送れる通信速度ということです。すると、1ビットあたりの 時間は、1÷2400=0.0004166666…[s]、つまり、約417[μs]となります。逆に言うと、417[μs]ごとにデータを送り出してや ると、2400bpsの通信速度となります。そこで、PICでは、データを1ビット送り出したら、空ループなどで417[μs]だけ時間待ちをして から、次のデータを送り出してやります。こうすることにより、パソコンと2400bpsの通信速度で通信することができます

 では、ひとつ、例題をやってみましょう。温度計モジュールから、D2=0、D3=1、D4=0、D5=1というデータが来たときは、PICはパソコンに 対してどのようなシリアルデータを送出したらよいでしょうか?

 答えは、こうなります。まず、温度計から来たデータは4ビットの2進数と考えられますから、0101です。これを10進数に直すと、5となります。5は 単なる数値ですので、これをアスキーコードに直します。10進数の5は16進数でも5Hです。これに16進数の30Hを加えて、35Hが送出すべきアス キーコードになります。これを再び2進数に直します。ただし、データ長8ビットで通信するので、8桁の2進数に直さなければなりません。すると、 00110101となります。RS232Cでは下位ビットから送信するので、これを逆に並べて、10101100とします。さらに、前にスタートビットの 0と最後にストップビットの1を加えて合計10ビットとし、結局、0101011001という10ビットのデータを送ってやればよいということになりま す。もちろん、各ビットは417[μs]の時間間隔を置いて送り出さなければなりません。これは、417[μs]ごとに、GPIOレジスターの第0ビット に、0または1を書き込むことで実現できます。このようなプログラムを作って、PICに焼きこめば、パソコンとシリアル通信をすることができるのです。

 私の作ったプログラムでは、BINTOASCというサブルーチンで、温度計モジュールから送られてきた4ビットの2進数をアスキーコードに変換してWレ ジスターに入れています。また、SEND232Cというサブルーチンで、Wレジスターに入っている文字をRS232Cに送信しています。以下に、そのプロ グラムの全リストを示します。


    LIST    P=12F629
    INCLUDE    P12F629.INC
    ERRORLEVEL -302

CB = _CPD_OFF
CB &= _CP_OFF
CB &= _BODEN_ON
CB &= _MCLRE_OFF
CB &= _PWRTE_ON
CB &= _WDT_OFF
CB &= _XT_OSC

    __CONFIG     CB
    __IDLOCS     H'0100'
この部分は、いつも通りの設定です。クロック発振モードとして、XTモードを使います。
    CBLOCK     H'20'
        CNT1
        CNT2
        CNT3
        BUF
    ENDC
CNT1、CNT2、CNT3、BUFの4つの変数を使いますので、汎用レジスター領域に確保 します。


    ORG           H'0'
    GOTO         L1               
リセットベクターです。


    ORG           H'4'
                     RETFIE            
割り込みベクターです。


L1:
    BANKSEL    GPIO
    MOVLW       B'00000001'
    MOVWF       GPIO
データを送信しないときは常に1を送出していなければならないので、GPIOレジスタの第0 ビットを1にします。
    BANKSEL    CMCON
    MOVLW       B'00000111'
    MOVWF       CMCON
入出力を、デジタルI/Oモードに切り替えます。
    BANKSEL    TRISIO
    MOVLW       B'00001100'
    MOVWF       TRISIO
ポート0と1を出力、ポート2と3を入力に設定します。
    BANKSEL    GPIO
L2:
    BTFSC        GPIO,3
    GOTO         L2
    CALL          W200
    BTFSC        GPIO,3
    GOTO         L2
    CALL          W200
    MOVF         GPIO,W
    MOVWF       BUF
    BTFSC        BUF,3
    GOTO          L2
ポート3を200μ秒間隔で3回読み、3回とも0であれば、D1の長いパルスと判定して次の処 理に移ります。
    MOVLW        H'30'
    MOVWF        FSR
データを30H番地から格納する準備です。
L3:
    MOVF          FSR,W
    XORLW        H'3D'
    BTFSC         STATUS,Z
    GOTO          L2
データの個数のチェックです。もし途中で3DH番地まで格納されれば、データの数が多すぎま す。
    CLRF           INDF
    BTFSS         BUF,2
    BSF             INDF,0
    INCF            FSR,F
BUFの第2ビット(元はポート2のデータ)を見て、それが0なら1、1なら0をFSR番地に 格納します。間接アドレス指定です。
L4:
    BTFSS         GPIO,3
    GOTO          L4
ポート3を監視します。ポート3が0から1に変わるまで、ループして待ちます。
    MOVLW       D'100'
    MOVWF       CNT2
L5:
    BTFSC        GPIO,3
    GOTO          L6
ポート3が1に変わった後、再びポート3が0になるのを待ちます。
    CALL          W40
    MOVF         GPIO,W
    MOVWF       BUF
    BTFSS        BUF,3
    GOTO         L3
ポート3が0になったら、40μ秒の間隔を置いて、再度、ポート3を読みます。それでも0なら ば、D2からD13の短いパルスと判定し、L3にジャンプしてデータを格納します。
L6:
    CALL          W40
    DECFSZ      CNT2,F
    GOTO         L5
ポート3が1ならば、40μ秒の間隔を置きながら、最高100回まで読み続けます。100回連 続して1ならば、データの終わりと判定します。
    MOVF          FSR,W
    XORLW        H'3D'
    BTFSS         STATUS,Z
    GOTO          L2
データの個数の再チェックです。もし3CH番地まで格納されていなければ、データの数が一致し ません。
    MOVLW       H'30'
    MOVWF       FSR
    MOVLW       A'+'
    BTFSC        INDF,0
    MOVLW       A'-'
    CALL          SEND232C
30H番地のデータを見て、それが0なら「+」、1なら「-」の文字を送出します。
    CALL          BINTOASC
    CALL          SEND232C
31H番地から34H番地までのデータを基に、温度の10の位を送出します。
    CALL          BINTOASC
    CALL          SEND232C
35H番地から38H番地までのデータを基に、温度の1の位を送出します。
    MOVLW       A'.'
    CALL          SEND232C
小数点を送出します。
    CALL          BINTOASC
    CALL          SEND232C
39H番地から3CH番地までのデータを基に、温度の小数第1位を送出します。
    MOVLW       A'C'
    CALL          SEND232C
    GOTO          L2
最後に、「C」という文字を送出します。


BINTOASC:
    INCF           FSR,F
    MOVF         INDF,W
    MOVWF       BUF
    MOVLW       D'3'
    MOVWF       CNT2
FSR番地に格納された、最初のビット(第3ビット)をBUFに入れます。次に、残りのビット (第2ビット、第1ビット、第0ビット)を処理するため、カウンターに3をセットします。
L8:
    INCF           FSR,F
    BCF            STATUS,C
    BTFSC        INDF,0
    BSF            STATUS,C
    RLF            BUF,F
    DECFSZ      CNT2,F
    GOTO         L8
BUFの値を左にシフトしながら、次のビットを加えていきます。こうして、バラバラのビット データを、数値データに直します。
    MOVLW       H'30'
    IORWF        BUF,W
    RETURN
さらに、それを、アスキーコードに直して、最後に、Wレジスターに入れて、元のルーチンに復帰 します。


SEND232C:
    MOVWF       BUF
    MOVLW       B'00000000'
    MOVWF       GPIO
    CALL          W2400BPS
Wレジスターより、送出すべき文字をBUFに受け取ります。続いて、スタートビットとして、と にかく、0を送出します。その後、417μ秒だけ待ちます。
    MOVLW       D'8'
    MOVWF       CNT3
L9:
    MOVLW       B'00000000'
    BTFSC        BUF,0
    MOVLW       B'00000001'
    MOVWF       GPIO
    CALL         W2400BPS
    RRF            BUF,F
    DECFSZ      CNT3,F
    GOTO         L9
データを、下位ビットから順に8ビット送出します。
    MOVLW       B'00000001'
    MOVWF       GPIO
    CALL          W2400BPS
    RETURN
最後に、ストップビットとして、1を送出します。


W2400BPS:
    MOVLW       D'80'
    MOVWF       CNT1
    GOTO         WL1
417μ秒だけ待つために、80回、空のループを回します。
W200:
    MOVLW       D'38'
    MOVWF       CNT1
    GOTO         WL1
200μ秒だけ待つために、38回、空のループを回します。
W40:
    MOVLW       D'6'
    MOVWF       CNT1
40μ秒だけ待つために、6回、空のループを回します。
WL1:
    GOTO         $+1
    DECFSZ      CNT1,F
    GOTO         WL1
    RETURN
1回の空ループです。これで5サイクル。また、1サイクル=4クロックですので、1回の空ルー プは20クロックです。従って、クロック周波数4MHzなら、一回の空ループは5μ秒となります。


    END
プログラムの最後を示す制御命令です。

 これで、ハードウェアの製作と、PICのプログラムの製作まで完了しました。

 次に、PICからRS-232Cを通して送られて来たデータを、パソコン側で受信するためのプログラムを作らなければなりませ ん。
ただし、とりあえずの間に合わせならば、Windows付属のハイパーターミナルでも受信することができます。





 図では、PICが10秒間隔で送ってくるデータをハイパーターミナルで受けている様子が分かります。温度が65度を越えていますが、実は、これは、この 画面を取り込む数日前に温度計モジュールが静電気(?)のために壊れて、華氏でしか温度を送ってこなくなったためで、例えば最初のデータは 「+65.6C」と表示されていますが、実際の温度は、「+65.6F」です(もちろん、壊れる前はちゃんと摂氏で温度を送ってきていました)。皆さん も、静電気には充分に注意しましょう…。

 では、温度データを受信するためのプログラムを作ることにします。ここでは、Visual Basic を用いて、簡単に作ることにしました。

 まず、Visual Basic を立ち上げ、ウィザードから、標準EXEを選びます。白紙のフォームが生成されます。
 




 シリアル通信をするためには、コンポーネントを追加する必要があります。「プロジェクト」の 「コンポーネント」を選びます。





 「コントロール」タブの、「Microsoft Comm Control」を選びます。ここでは、Ver. 6.0 のようです。チェックボックスにチェックを付けたら、OKを押します。





 シリアル通信のためのコンポーネントが追加されます。





 このプログラムでは、ラベル一つと、シリアル通信コンポーネント一つが必要です。まず、ラベルを貼り付けま す。





 次に、シリアル通信のためのコンポーネントを貼り付けます。





 これで、プログラミングの準備は完了です。次に、コーディングに移ります。と言っても、ただ文字を受けるだけ なら、下のような短いプログラムでOKです。

Private Sub Form_Load()
        MSComm1.CommPort = 1
        MSComm1.Settings = "2400,n,8,1"
        MSComm1.RThreshold = 6
        MSComm1.DTREnable = True
        MSComm1.RTSEnable = True
        MSComm1.PortOpen = True
End Sub
まず、シリアル通信の各種設定をして、それからシリアルポートを開きます。詳しくは本文を参照 してください。


Private Sub Form_Unload(Cancel As Integer)
        MSComm1.PortOpen = False
End Sub
プログラム終了に先立って、使ったシリアルポートを閉じます。


Private Sub MSComm1_OnComm()
    If MSComm1.CommEvent = comEvReceive Then Label1.Caption = MSComm1.Input
End Sub
シリアルデータの入力があった場合、その文字を表示します。


 エラー処理も何も無い、非常に原始的なプログラムですが、データをただ受信するだけならこれでも一応は動きます。

 少し説明をしますと、まず、プログラムが起動すると、Form_Load というプロシージャが実行されます。ここには、シリアル通信のための各種設定 を記述しておきます。CommPort プロパティーには、シリアルポートが COM1 の場合は1、COM2 の場合は2、COM3 の場合は3、COM4 の場合は4をそれぞれ指定します。Settings プロパティーには、通信速度、パリティー、データ長、ストップビット長をカンマで区切りながら文字列データで指定します。RThreshold プロパティーには、何文字受信したら受信イベントを発生させるかを指定します。今の場合、PICからは「+または-」、「温度の10の位の数字」、「温度 の1の位の数字」、「.(小数点)」、「温度の小数第1位の数字」、「C」の6文字がひとかたまりに送られてくるので、6以下の値を指定します。 DTREnable プロパティーは、True  または False を指定することができます。False の状態では、D-SUB コネクターの4ピンは-10[V]になっていますが、ここを True に変えてやると、D-SUB コネクターの4ピンに+10[V]が出てきま す。この温度計測装置は、この+10[V]を電源として使用します。RTSEnable プロパティーも同様で、D-SUB コネクターの7ピンに+10[V]を出力させることができます。これらの設定は、このようにプログラム中に書かなくても、フォーム設計時に、 MSCOMM1 コントロールを選んで、そのプロパティーウィンドウの中で設定してもかまいません。

 これらの設定が済んだら、いよいよ、ポートをオープンします。PortOpen プロパティーを True にすることにより、通信が始まります

 通信が始まると、PICの方から、10秒に一度の割合で、シリアルデータを送ってきます。パソコン側は、常に D-SUBの2ピンの電圧を見ていて、それが -10[V] から +10[V] に変わった瞬間、スタートビットを受信したと解釈して、受信イベントを発生させます(*注5)。
(*注5 実際には、RThreshold プロパティーを6にしたので、6文字受信してから、受信イベントが発生します。)

 通信イベントが発生すると、MSComm1_OnComm というプロシージャが実行されます。受信イベントも、通信イベントの一種ですので、このプロシージャが実行されます。受信イベント以外にも、通信イベント を発生させる要因はいくつもあるのですが、受信イベントの場合は、CommEvent プロパティーの値が、comEvReceive になります。つまり、CommEvent プロパティーの値が、comEvReceive 以外の通信イベントはすべて無視します。一方、 CommEvent プロパティーの値が、comEvReceive の場合は、受信データが Input プロパティーの中に格納されていますので、それを Label1 に書き込んでやります。そうすることにより、受信した温度データの文字列を表示することができます(なお、上のプログラム中の If からMSComm1.Input までは、上のリストでは2行に渡っていますが、実際には1つの文として記述します)。

 プログラムを終了するときには、通信を終了してからプログラムを終わります。そのため、Form_Unload プロシージャに、ポートを閉じるための命令を書いておきます。PortOpen プロパティーを False に戻せば、通信は終了します。DTREnable プロパティーと RTSEnable プロパティーは意味を失い、したがって、D-SUB の4ピンと7ピンも-10[V]に戻ります。そのため、温度計測装置の電源は自動的に切れることになります。


 実際にデータを受信した画面です。上のようなシンプルで原始的なプログラムですが、ちゃんとデータは受信できています。





 動作OKならば、実行形式ファイルを作っておくと良いでしょう。






 下の写真が、製作した回路です。真ん中近くの四角いICがPIC12F629です。右端の縦長のICが、 ADM232です。





 パソコンに接続したところです。華氏でしか温度を表示しなくなったので、パソコン側のプログラムで対応して、 受信した華氏の温度データを、摂氏に変換することにしました。計算式は、

(摂氏温度)=((華氏温度)−32)×5÷9

となります。この写真では、華氏 65.2度ですから、摂氏 18.4℃となります。パソコンの画面上には、摂氏温度で表示されるように、プログラムを少し修正しました。






 受信プログラムは、その後も改良を加えて、色々な機能を追加しました。例えば、通信のための設定を変えたり、 計測した温度データをファイルに保存したりする機能などです。以下にその画面を示します。







 下は、実際の測定データの一例です。2005年2月5日18時より6日12時まで、10分間隔で測定しまし た。夜間、次第に気温が下がり、午前6時台に気温が一番低くなって、太陽の出る午前7時台から急激に気温が上昇する様子が良く分かります。







おまけ


 PICとパソコンの通信にあたっては、通信速度を互いに合わせる必要があります。本文中でも触れていますが、例えば、2400bpsの速度で通信する場 合、PICは、417[μs]ごとにビットデータを送り出す必要があり、その時間稼ぎのために、規定回数だけ空ループを回して時間待ちをします。

 では、空ループを何回回せば通信速度に合わせられるのか、また、この規定回数からループ回数がずれた場合、どのようなことになるの か、ひとつ、考察してみることにしました。

 以下は、PICに焼きこんだプログラムのうち、通信速度に関する部分を抜き出したものです。PIC12F629では、命令はすべてサイクルと いう時間単位で実行されます。また、1サイクルは4クロックと決まっています。今、CPUクロック4MHzで動かしていますので、1クロッ クは、1÷4000000 = 0.00000025[s] = 0.25[μs]です。従って、1サイクルはその4倍で、1[μs]です。

 PIC12F629では、ほとんどの命令は1サイクルで実行されますが、ジャンプを伴う命令(GOTO、CALL、RETURN など)は2サイクル、また、条件分岐命令(BTFSC、BTFSS、DECFSZ など)は、条件が成立しないときは1サイクル、成立するときは2サイクルで実行されます。

 以下のリストは、SEND232Cサブルーチンの中の、ビットを送出している部分です。「BTFSC  BUF,0」は条件が成立したら2サイクル、成立しなければ1サイクルですが、条件が成立した場合は次の「MOVLW  B'00000001'」を実行しませんので、結局、この二つの命令をあわせて、いつも2サイクルと考えられます。すると、この部分の実行に必要な時間 は、1+2+1+2+α+1+1+2で、10+α サイクルとなります。ただし、α は、W2400BPS サブルーチンの中身を実行するのに必要な時間です。


L9:
    MOVLW       B'00000000'
    BTFSC        BUF,0
    MOVLW       B'00000001'
    MOVWF       GPIO
    CALL         W2400BPS
    RRF            BUF,F
    DECFSZ      CNT3,F
    GOTO         L9

1サイクル命令
1または2サイクル命令
1サイクル命令
1サイクル命令
2サイクル命令
1サイクル命令
1または2サイクル命令
2サイクル命令


 そこで、W2400BPS サブルーチンの中身を見てみます。サブルーチンの最初の「MOVLW  D'80'」で、空ループの回数を設定しています。この例では、80回の空ループを回します。ループそのものは、リストの青太字になった部分です。

 一般的に、ループを n 回回すとしましょう。青太字の部分のサイクル数は、2+1+2=5サイクルです。ただし、ループの最終回だけは、「DECFSZ  CNT1,F」で条件が成立するため、1サイクル多くなります。しかし、次の「GOTO  WL1」をスキップするので、2サイクル少なくなり、合計して、1サイクル少ない、4サイクルとなります。従って、このサブルーチンを実行するのに必要な サイクル数は、α =1+1 +2+ { 5×(n-1)+4 } +2=5n+5 サイクルとなります。


W2400BPS:
    MOVLW       D'80'
    MOVWF       CNT1
    GOTO         WL1
WL1:
    GOTO         $+1
    DECFSZ      CNT1,F
    GOTO         WL1

    RETURN

1サイクル命令
1サイクル命令
2サイクル命令

2サイクル命令
1または2サイクル命令
2サイクル命令

2サイクル命令


 結局、RS232Cに1ビット送出するのに必要な時間は、10+α =10+(5n+5)=5n+15 サイクルということになります。先ほども述べたように、CPUクロック4MHzの時は、1サイクル=1[μs]ですから、1ビット送出するのにかかる時間 は、5n+15[μs]です。2400bpsにするためには、これが417[μs]になればいいのですから、5n+15=417 という方程式が成立します。これより、n=80.4 を得ます。つまり、空ループの回数は、80回としなければなりません

 同様に、200[μs]待機したり、40[μs]待機するためのループ回数を計算することができますが、これらはメインルーチンのあちこちで呼ばれてい ますので、呼ばれる場所ごとに、サイクル数が微妙に違います。しかし、それらの微妙な違いは無視して、大雑把にループ回数を設定しても、実用上は問題あり ません。

 さて、ここからが実験です。もし、ループ回数をわざと80回からずらせてやると、どのようなことになるでしょうか?

 パソコンは、シリアルポートにスタートビットの0が送られてくるのをずっと待っています。もし、スタートビットを検出すると、定められた通信速度の半分 の時間(例えば、2400bpsなら、417[μs]の半分で、208[μs])だけ待ってから、定められた時間間隔(例えば、2400bpsなら、 417[μs])で、定められたビット数だけデータを読み込みます。それが、下の図です。「↑」は、パソコンがシリアルデータを読み込むタイミングで、ス タートビットを検出してから208マイクロ秒後にまず読み、それから、417マイクロ秒間隔で読んで行きます。送信側と受信側とが全く同じ速度で通信して おれば、正確にパルスの真ん中でリードの動作を行います。





 もし、PIC側がこの通信速度を守らず、違う速度でデータを送ってきたら、どうなるでしょうか?全然違う速度で送ってくれば、受信できないことは言うま でもありません。問題は、規定速度とは、ほんの少しだけ違う速度で送ってきた時、どのようなことが起きるのか、ということです。

 もう一度図で考えますと、下図のように、PICが規定速度よりも少しだけ速い速度でデータを送ってきたとします。パソコン側は、PIC側の事情にはお構 いなしに、規定の時間間隔でデータを読み込みます。もし、PIC側の転送速度のズレが小さければ、それでもデータは読み込めるはずです。読み込むタイミン グが、段々、パルスの真ん中からズレて行きますが、それでも、パルス幅から外れなければ、何とかデータは正確に読めるはずです。スタートビット、ストップ ビットまで入れて全部で10ビットですから、最後の10ビット目でのズレが±0.5ビット以内ならば、データは正常に読み込めるはずです。つまり、転 送速度には、±5%程度の許容範囲があるのではないか、と考えられます。






 今の場合、適正ループ回数は80回ですから、その±5%ということは、±4回ぐらいは、ループ回数に誤差があっても、データ転送できるのではないか、と 予想されます。

 以下が、その実験画面です。予想通り、ループ回数が 76回から85回までの間のときは、正常に受信できました





 しかし、ループ回数が75回になると、ビットずれが生じて、正常には受信できなくなりました。






ゼミ作品に戻る