December 14, 2011

ソフトウェアSPIが動かない (解決済)

マイコンを使っていると、時々マイコンに組み込まれた周辺機能だけでは事が足りなくなって、汎用I/Oポートを使って、ソフトウェア的に機能を実現するということがよくあります。ポートのH/Lをコントロールしたり、はたまた汎用ポートをHi-Z(ハイインピーダンス)の状態にしてH/Lを読み込んだりすることで実現されます。よくやられるのが、ソフトウェアSPIやIソフトウェアUARTといったものです。最近、そのソフトウェアSPIを実装していて嵌りましたので、今回は僕の失敗談を披露したいと思います。

ソフトウェアSPIに限らず、ポートの状態の書き出しとポートの状態の読み込みを行うものでは起こりうる問題なので、せっかくなので一般化して話を進めたいと思います。汎用ポートは、多くの場合、複数のポートをひとまとめとして、1つのレジスタで扱われます。ここではP1というレジスタに8個の汎用I/O、すなわちP1.0からP1.7の8個が割り当てられていたとしましょう。その中でP1.0を状態の書き込みに、P.1.1を状態の読み込みに使うことにします。

gpio.png

たとえば、P1.0(出力)にP.1.1(入力)と同じ信号レベルを出したければ、次のようなコードを書いて実現することが多いかもしれません。

if(P1 & 0x02){P1 |= 0x01;}else{P1 &= ~0x01;}

ところが、このコード、特定のマイコンでは、一度P1.1がLになると、たとえ他のマイコン等から再びP1.1にHが出力されようとも以降はLが読み出され、二度とP1.0がHにならなくなりました。僕はC8051というSilicon Laboratoriesのマイコンで今回この失敗を経験しました。

原因はP1というレジスタの構成にありました。レジスタに複数の機能が割り当てられているのでした。ポートが出力として設定されている場合は、1や0を書き込むことでHやLが出力されます。また入力の場合は読み出すことで、ポートのHやLを1や0で返してくれます。

ところが入力の場合に書き込むとどうなるでしょう。C8051では、1を書き込むことによってHi-Zの状態、0を書き込むことによってGNDとショート(厳密にいうと、オープンドレインのFETがONになって、電流を引き込む)になるのです。そのため、P1.1に一度0が書き込まれてしまうと、他のマイコン等ががんばってHを出力しても、電流がどんどん吸い込まれLとなってしまうのでした。

『えっ、入力ポートに書き込んでないんじゃないの?』と思われた方(主に僕)、コードをよくご覧ください。演算子として、|= や &= が使われていますね。この演算子が意味するところは、一度読み出したレジスタを加工した上で、またレジスタに代入するということです。ということは入力ポートとして設定したP1.1にも、当然書き込み処理は行われているのです。これに気づくのにかなりの時間を要しましたorz

結果、僕が使っているC8051では、例題としてあげたコードは、次のコードに書き換える必要がありました。

if(P1 & 0x02){P1 = (P1 | 0x01) | 0x02;}else{P1 = (P1 & ~0x01) | 0x02;}

最後にですが、汎用ポートを動かす際、レジスタ単位ではなく、ピット単位で動かせるものは、今回のような誤動作を避けるためにもピット単位での操作(P1.0 = 1; P1.0 = 0;)をするべきたと思いました。

21:02 fenrir が投稿 : 固定リンク | | このエントリーを含むはてなブックマーク | トラックバック
このエントリーのトラックバックURL: https://fenrir.naruoka.org/mt/mt-tb.cgi/827
コメント
コメントする









名前、アドレスを登録しますか?
(次回以降コメント入力が楽になります)
  • 匿名でのコメントは受け付けておりません。
  • 名前(ハンドル名可)とメールアドレスは必ず入力してください。
  • メールアドレスを表示されたくないときはURLも必ず記入してください。
  • コメント欄でHTMLタグは使用できません。
  • コメント本文に日本語(全角文字)がある程度多く含まれている必要があります。
  • コメント欄内のURLと思われる文字列は自動的にリンクに変換されます。