公開日:2021/09/11
title.png

17th Season


#17-9 双方向I/Oポート

 MoscoviumにSTWSERユニットを搭載してI2C機器との通信もできるようになったし、これまでに購入して来たモジュール類は大抵つながるんじゃないのか?そう思っていたら、ダメ、でした。そもそも、本HPのスタートでもある、気温・湿度センサ(AM2302とDHT11)が、繋がりません。

AM2302・DHT11との通信

 AM2302とDHT11は、以下図の様な3端子程(NC端子を除いて)のセンサです。

ar6_am2302.jpgar24_dht11mdl.jpg

 電源VCC,GNDと、双方向のシリアル通信線SDAの3本がインターフェイスです。2本が電源・グラウンドなので、通信線は1本だけです。1本で、こちらからのトリガ信号出力と、センサからのデータ入力をこなす必要があるので、双方向のシリアル通信線となります。

 通信仕様は以下図の通りです。

ar6_wave.png

 詳しくはこちら(「第6回 気温と湿度の測定」)に解説があります。

 上図の通り、赤線で書いた部分はArduinoからの出力、緑線部分がセンサからの出力となります。信号線SDAはプルアップされていて、Arduino側、センサ側とも、LOWへのシンクは行いますが、HIGHはソースしないオープンドレイン出力となっています。
 Arduino UNO(ATmega328)には、オープンドレイン出力端子が存在しないので、ポート出力の禁止と、許可+LOW出力の2つで、疑似的にオープンドレイン出力を実現しました(下コード例)。

  // Arduinoでオープンドレイン出力を実現
  if (out)
  {
    // "1"出力時
    pinMode (pin,INPUT);
  }
  else
  {
    // "0"出力時
    digitalWrite (pin,LOW);
    pinMode (pin,OUTPUT);
  }


 Moscoviumマイコンがこの時のArduino側の処理をするのに必要なのは、双方向I/Oポートです。しかし、Moscoviumマイコンの汎用I/OポートPORT8I8Oユニットには、出力ポートと、入力ポートはありますが、双方向の入出力ポートがありませんでした。
 オープンドレイン出力にするには、出力ポートのドライバを切って、ハイインピーダンス(HIGH-Z)状態にする必要があるので、今のままでは実現不可能です。

PORT8I8Oユニットの拡張

 PORT8I8Oユニットの機能はそのまま残して、入出力ポート機能を追加しようと思います。以下図が入出力ポートの実施図となります。

a179_inout.png

 現時点では、PORT8I8Oユニットの入出力I/Fであるout’,inp’端子が、MCOC115のI/F端子out,inp端子に直接接続されています。ここに、出力outのドライバをon/offするenb信号とトライステートバッファ、入力inp’を選択するsel信号とセレクタ、を追加します(上図、緑色着色部分)。

 新規に追加したenb,sel信号を制御するため、以下3個のレジスタを追加します。

 論理回路(RTL)としては、以下のような記述となります。

  wire [7:0] port_enb=~(pordir[7:0] | (porode[7:0]&port_out[7:0]));
  wire [7:0] port_sel=porsel[7:0];


 一応、後方互換性を保持しているので、新設した3個のレジスタを設定しない限り、動作は今までのPORT8I8Oユニットと全く同じにすることができます。ただし、port_init_hizo=0に設定した場合です。

 port_init_hizo=1を設定した場合は、port_out[7:0]端子の初期状態が変化するため、従来のPORT8I8Oユニットとは初期動作が異なります。最新のMCOC115TW0408以降ではport_init_hizo=1となっているので、注意してください。その他の古いデザインではport_init_hizo=0に設定してあり、互換性を保っています。
 MCOC115最上位モジュールの先頭付近にある以下のコンパイルオプション(`defineマクロ)で制御可能です。

  `define MCOC_PORT_HIZO
    …… `defineすると、port_init_hizo=1、初期out=HIGH-Z
    …… `defineしなければ、port_init_hizo=0、初期out=LOW出力(従来と同じ)


 入力選択レジスタporselでは、入力端子としてinp端子を使うか、out端子を使うか選択することができます。従来版ではinp端子が入力端子でしたので、初期値porsel=0x00では、従来版と同じ動作となります。
 一方、out端子にトライステート制御enbを追加したことで、out=HIGH-Zとした時に、out端子からの信号入力が可能となりました。この場合、porselレジスタのビットに1を設定して、out端子状態を入力信号として使用することが可能となります。

 以上で、出力ポートout[7:0]と入力ポートinp[7:0]だけだったPORT8I8Oユニットに、入出力ポートout[7:0]の機能を拡張することができました。

DHT11から気温・湿度データを読み出す

 冒頭で説明した様に、入出力ポートが1本あると、DHT11(AM2302)からデータを読み出すことができます。
 なお、手元のAM2302は、「第9回 気温・湿度のログを取る(後編、最終回)」で完成させた自動ログ機として今も稼働し続けているので、AM2302を実験に使うことができません。よって、以降、DHT11で実験を進めます。

 Arduino UNOで作成したプログラム(meas_am2302.ino)をMoscoviumアセンブラに移植します。ただし、DHT11用として移植します。
 AM2302とDHT11では、一部、アクセス信号のACタイミング規定や読み出したデータの意味等が異なるため、AM2302と通信する場合には、以下記事や元のプログラム(meas_am2302.ino)を参考に、修正してください。

  「#2-4 二代目のスタータキット購入」: DHT11とAM2302の違いについて記載

HIGHパルス幅の計測

 Arduino UNOで作ったプログラム(meas_am2302.ino)では、pulseInLong()関数を使って、入力ポートのHIGHパルス幅を計測し、DHT11が出力してくるデータを読み取りました。
 MoscoviumマイコンでpulseInLong()関数に相当するサブルーチンを作成します。測定したいのはHIGHパルス幅なので、以下の手順とします。

 DHT11が接続されていなかった場合等、レスポンスが返らないとハングアップしてしまうので、SDA信号のポーリング時間中にもsytmmcrレジスタを読み出して、一定の時間を経過してもSDAに変化が無かった場合、タイムアウトエラーを返すようにしておきました。

アセンブラの機能拡張

 さてここで、Moscoviumアセンブラに拡張した機能について軽く触れておきます。

 詳しくは、各項目に貼ったリンク先をご覧ください。
 Moscoviumコア自体の命令には一切変更有りませんが、アセンブラの記述は圧倒的に楽になります。以下にいくつか例を示しておきます。

  例)選択アセンブル
  def s,""  // simulation="" / real fpga="#"
    :
    :
  $(s)// ********************************
  $(s)// simulation finish
  $(s)ldwi r0,simpass
  $(s)ldwi r7,simfnsh
  $(s)stw [r7],r0
  $(s)bra pcnt-2
  $(s)// ********************************
    :
    :

 上の例は、ちょっとトリッキィな使い方です。本来、プログラム名やバージョンなどを文字列マクロに定義しておいて、複数の場所で同じ文字列に統一して使いたい場合などに使用します。アセンブラの構文と無関係に、「$(〜)」の書式で文字列置換が発生します。


  例)32ビットロングワード処理マクロ
  macro cmpl rdh,rdl,rsh,rsl
  // cmpl $(rdh):$(rdl),$(rsh):$(rsl)
  cmp $(rdl),$(rsl)
  cmb $(rdh),$(rsh)
  endm


 ↑等、16ビットCPUでは複数命令を組み合わせなくては実行できない32ビット処理をユーザマクロに定義することで、まるで32ビット処理命令を追加したかのような使用感になります。
 以下、使用例です。

  cmpl r0,r1, r2,r3

    ↓ 展開されて以下と解釈される。

  // cmpl r0:r1,r2:r3
  cmp r1,r3
  cmb r0,r2


 これが、引数付きユーザマクロの威力です。


  例)配列データ(4個)のチェックサム計算
  // sum
  ldbiu r1,0
  rept 4
  ldb r0,[r7]
  addi r7,1
  add r1,r0
  endr


 汎用レジスタr7で指し示す配列データ4個のチェックサムを取ろうとしています。データ数=128個なら、おとなしくループを回すでしょうが、4個です。上例の様に反復展開(rept疑似命令)してしまった方が、ループに使う汎用レジスタを汚さずに済むし、楽ちんです。
 コンパイラの最適化技術であるループアンローリングを手動で実施していることになるので、処理も高速になります。


 (↑)と言うような、アセンブラの新規機能も使っているので、以下にリンクする完成プログラムでご確認ください。

  完成プログラム:meas_dht11.asm

※必要なインクルードファイルなどはこちらからダウンロードできます。

 シリアルモニタから、以下のコマンドを受け付けるようになっています。元にしたUNO用のプログラム(meas_am2302.ino)と互換です。

実験と言う名の動作確認

 実験して動作確認しましょう。

 以下リンクより、デザインデータ(MCOC_TW.al、MCOC115TW0408)をダウンロードして、前節のプログラム(meas_dht11.asm)と共に、FPGAボード(Sipeed Lichee Tang Primer)に書き込んでください。手順はこちらです。

btn_moscovium.png

 I/O制約ファイルは、MCOC_full.adcを使います。


 FPGAボードとUSB⇔UART変換モジュール(FTDI)、DHT11を以下表の通り接続します。

DHT11 接続 FPGAボード 接続 FTDI
VCC 3.3V  
SDA C15(port_out[7])
GND GND GND
  R7(uart_rxd) TXD
T7(uart_txd) RXD
  × VCC(ジャンパ:3.3V側)

※DHT11の定格電圧はVCC=5V(TYP)です。3.3V(MIN)の規定があるので、今回はマージン無しのオンスペックで使います。DHT11モジュールには、モジュールボード上にVCCへのプルアップ抵抗が付いているので、VCC=5Vを接続してしまうと、3.3V定格のFPGAボードにとっては定格違反となります。


 以下図に、実験中の様子を示しておきます。

a179_expr.jpg


  実験サンプル:【 】内はシリアルモニタからの送信データ
  【rrq】
  OK,31.8,60.0

 現在の気温(31.8℃)・湿度(60%RH)データがエラー無く読み出せました。夏場の室内🎐ですので、読み出したデータに矛盾はありません。
 もしかして、DHT11の電圧不足で読み出しエラーが発生する場合があるかもしれませんが、その場合には、1)DHT11へ5V給電、2)DHT11とFPGAボードの間にレベルシフタ(L/S)を挟んでくださいm(1__)m。


 意外に盲点だった「入出力ポート」に対応しました。RTC(DS1302)なども双方向のクロック同期式シリアル通信なので、プログラムさえ作ればつながるはずです。


btn_arduino.png

btn_prev.png btn_next.png

home