I2C通信まとめ

はじめに

マイコン工作でよく使う通信方式と言えば,概ね

  • UART
  • I2C
  • SPI

を挙げることに異を唱える人はいないだろう.
(カメラとか作る人はLVDSとかその辺も入ってくるけど)
このうち,UARTはどちらかと言うと基板内ではなく基板間
(というか大抵FT232RL通してパソコンと接続)
で使うことが多いので,基板内通信で使うものと言えばSPIとI2Cが主流である.

このうち,SPIは単純なシステム故にわかりやすい.
送受信がそれぞれ別の信号線を用いているうえに,アドレスでデバイスを指定する必要が無い.
バイスが増えると増えただけnCSを必要とする以外は使いやすいだろう.

問題はI2Cである.
I2Cもマイコン用の通信規格である以上,そこまで複雑なことはさせていない.
しかし,SPIと異なり色々と「初心者がハマる」ポイントがいくつかある.
しかも悪いことに,標準的に使われる規格である以上,
ArduinoWireライブラリやmbedのライブラリを駆使しつつサンプルコードをコピペして動かすだけの
資料がそろってしまっているのである.
単にサクッと動かしたいだけならそれはありがたいのだが,
ライブラリのないデバイスをデータシート頼りで動かそうとするとさすがに中身を知らないと
バグの取りようがない.

そこで,一念発起して一通り今まで理解したことを書いておくことにする.

I2Cでの通信

基本的な通信の流れとして

  1. 【マスタ→スレーブ】スタートコンディションを送る(というか示す)
  2. 【マスタ→スレーブ】アドレス(7ビット+リードライトを示すビットで8ビット)を送信する
  3. 【スレーブ→マスタ】該当するアドレスを持つデバイスはマスタに向けてACKを返す
  4. 【双方向】送受信する
    • 送信の場合
      1. 【マスタ→スレーブ】データを1バイト送信する
      2. 【スレーブ→マスタ】ACKを返す
      3. 上二つを送りたいデータ数分だけ繰り返す
    • 受信の場合
      1. 【スレーブ→マスタ】マスタのクロックに合わせてデータを送信する
      2. 【マスタ→スレーブ】ACKを返す
      3. 上二つを受け取りたいデータ数-1だけ繰り返す
      4. 【スレーブ→マスタ】最後のデータを受け取る
      5. 【マスタ→スレーブ】NACKを返す
  5. 【マスタ→スレーブ】ストップコンディションを送る(というか示す)

このようになる.

送信

概ね前の章で書いたことがI2Cの送受信のやり方だが,
送信だけなら難しいことはほぼ何も無い.

  • スタートコンディション
  • アドレス送信
  • データ送信
  • ストップコンディション

でおしまいである.あとはアドレス送信とデータ送信の間にスレーブからACKが返ってくるので,
それを待つことさえ忘れなければ良い.

受信

問題は受信である.ここで変に勘付いて「どうせ送信と逆でしょ」と

  • スタートコンディション
  • アドレス送信
  • データ受信
  • ストップコンディション

という流れを考え,
「データ受信したらマスタからスレーブにACKを送っておけばOK」
と思い込むとハマる.
結論から言えばその考えは80%は間違ってなかっただろう.
「最後の1バイトを受信したら,ストップコンディションの前にACKではなくNACKを返す必要がある」
という点には十分な注意が必要である.

ACKと言う信号は,一般には通信応答の意味を示す.これは
ACKを返す=データを正しく受信した
ACKを返せない(NACKを返す)=データが正しく受信できていない
という意味になる.マスタからスレーブに送信する場合, ACKの意味は上記で間違いない.

しかしI2Cの場合,マスタが受信する際に送るACKは
「データをさらに要求するかどうか?」
という意味に変化し,正しく受信できたか?という意味にはならない.
ここを勘違いしたままでいると,
(そしてArduinoライブラリなど,このあたりの処理が隠されたライブラリを使っていると),
最初の1回は上手くいくのだがそれ以降通信がタイムアウトしてしまう.

受信におけるACKとは,
ACKを返す=さらにデータを要求する
ACKを返せない(NACKを送る)=データの要求を終了する
と言う意味である,ということを念頭に置かないと処理がおかしくなってしまうのである.

レジスタを使った通信

さて,一般的なセンサデバイスと通信する場合,計測値の受信や設定値の送信は
バイス内のレジスタに対する読み書きという形で表現されることが多い.
これをI2Cの流れに沿って考えると,

**レジスタに書き込む

  1. 【マスタ→スレーブ】スタートコンディションを送る(というか示す)
  2. 【マスタ→スレーブ】アドレスを書き込みモードで送信する
  3. 【スレーブ→マスタ】該当するアドレスを持つデバイスはマスタに向けてACKを返す
  4. 【マスタ→スレーブ】書き込み対象のレジスタアドレスを送る
  5. 【マスタ→スレーブ】レジスタに書き込むデータを送る
  6. 【マスタ→スレーブ】ストップコンディションを送る(というか示す)

これで書き込みは実現できる.もちろん,レジスタアドレスやデータを送る際には,
スレーブからACKが返ってくる.

次に,レジスタから読み込みたい場合はどうすればよいだろう?
レジスタアドレスの指定はマスタ→スレーブなのだから書き込みモードだが,
読み込むのはスレーブ→マスタなのだから読み込みモードのはず・・・ ではデバイスにはどちらのモードで接続をかけるのが正解なのだろうか?

まず,余計なことを考えずに「送信と受信」で考えると以下のようになる.

  1. 【マスタ→スレーブ】スタートコンディションを送る(というか示す)
  2. 【マスタ→スレーブ】アドレスを書き込みモードで送信する
  3. 【マスタ→スレーブ】読み込み対象のレジスタアドレスを送る
  4. 【マスタ→スレーブ】ストップコンディションを送る(というか示す)
  5. 【マスタ→スレーブ】改めてスタートコンディションを送る(というか示す)
  6. 【マスタ→スレーブ】アドレスを読み込みモードで送信する
  7. 【スレーブ→マスタ】データを送信する
  8. 【マスタ→スレーブ】最後だけNACKを返す
  9. 【マスタ→スレーブ】ストップコンディションを送る(というか示す)

「送信するものは送信し,受信するものは受信する」で処理を分けた.
モノにもよるがこれでうまくいってくれるデバイスは多い.
実際には,このうち4.を省略することが可能であるし,
一般にはそのようにすることが多いようだ.その場合このようになる.

  1. 【マスタ→スレーブ】スタートコンディションを送る(というか示す)
  2. 【マスタ→スレーブ】アドレスを書き込みモードで送信する
  3. 【マスタ→スレーブ】読み込み対象のレジスタアドレスを送る
  4. 【マスタ→スレーブ】改めてスタートコンディションを送る(というか示す)
  5. 【マスタ→スレーブ】アドレスを読み込みモードで送信する
  6. 【スレーブ→マスタ】データを送信する
  7. 【マスタ→スレーブ】最後だけNACKを返す
  8. 【マスタ→スレーブ】ストップコンディションを送る(というか示す)

この場合,4.のスタートコンディションは別名「リスタートコンディション」と呼ばれる.
しかし,実態は見ての通りスタートコンディションを送りなおしているだけであって,
リスタートコンディションという特別なコンディションは存在しない

まとめ

そんなわけで,今までハマったポイントをまとめると以下のようになる.

  • 送信は単純.各データを送った際に1バイトごとにスレーブからACKが来る
  • 受信の際はACKの意味が「さらなるデータを要求するかどうか」に変化していることに注意
  • そのため,受信を最後はACKではなくNACKを送ってデータをもらい終わったことを示さなければならない
  • レジスタし指定して受信する際は「スタート,スタート,ストップ」コンディションを送る
  • 2回目のスタートコンディションをリスタートコンディションと言うが,特別なコンディションではない

これを覚えておけば,多少なりともI2Cの挙動が理解しやすくなるし,
また上手く通信ができない場合も原因究明に役立つことだろう.

それでは,また.

Ubuntu & ROS環境覚書

今使っているubuntu+ROSの環境を一応書いておきます.

 

OS:ubuntu 14.04+LTS

ROS:indigo

 

インストールすべきパッケージとインストール方法:

 

ROSのインストール:

ja/indigo/Installation/Ubuntu - ROS Wiki

 

catkinのインストール:

cyberworks.cocolog-nifty.com

 

PTAMのインストール:

さくっとPTAMをROSで試す(1)・・・サンプルデータでPTAMを試す - sabo laboratory

 

SPTAMのインストール:

GitHub - lrse/sptam: S-PTAM: Stereo Parallel Tracking and Mapping

 

UVCカメラを使えるようにするためのパッケージのインストール:

試行錯誤な日々: ROSでカメラ画像を表示

(ただしこれはROSのバージョンが違うので,groovyをindigoに直すこと)

 

ステレオカメラを使えるようにするためのパッケージのインストール:

bril-tech.blogspot.jp

(USBカメラを2個使う場合のもの)

 

Playstation4のステレオカメラをROSで使えるようにするための方法:

orientalrobotics.blogspot.jp

 

とりあえずはこんなところ.

それでは,また.

 

DFUに関して

備忘録的な内容なのですごく基礎的なことを書きます.

最近,仕事でDFUを触る必要性が出てきたので,とりあえずお勉強.
当たり前ですが,組み込みシステムは一度製品を出荷してしまうと
中々ファームウェアのアップデートはできません.

無論,それでもやらなきゃならないことはあるので,
そのために弄する手段の一つがDFU(Device Firmware Update)

通常,マイコンJTAGやSWDといった専用のインターフェースを用いて
書き込み・デバッグをします.
しかし,こんなものは製品にとっては無用の長物であるばかりか
CPUの中身を覗き放題なので出来る限り潰したいわけです.
しかし,先に書いたようにファームウェアのアップデートだってやりたい・・・

そのため,UARTやCAN・I2Cなど,そもそも別の用途に使っている
(つまり引き出している)インターフェースから書き込めるようにしようと
考えるのは自然の摂理と言えるでしょう.

マイコンにプログラムを書き込むという行為は,
極論してしまえば「メモリのプログラム領域にコンパイルしたデータを書き込む」
行為に他ならないわけで.

ぶっちゃけて言えば,
「母艦から好きなインターフェースでデータを受け取り,プログラム領域に書き込むプログラム」
マイコンのどこかにあれば,それを起動して話は解決というわけです.

多くのマイコンでは,「マイコンどこか」をDFU機能のための領域として確保しています.
ここに先に書いたようなプログラムを開発・書き込んでおけば問題ないというわけですね.

さて,このDFUという機能,一番わかり易いのがArduinoだと考えられます.
通常,Arduinoに使われるAVRマイコンにプログラムを書き込むには,
ICP(In Circuit Programmer)端子を利用して書き込む必要があります.
まぁ,その正体はただのSPIではあるのですが.

しかし,実際のArduinoはUSB,回路でUARTに変換したものを使って書き込んでいますね.
要は,ArduinoのAVRマイコン内部には,UARTからのデータを使ってマイコン
プログラムを書き込む機能(プログラム)が備わっているというわけです.

Arduinoにおいては,これをブートローダーと読んでいますが,
機能的にはDFUに近いものとして考えることが出来るでしょう.

 

 

DesignSpark PCBでドリル穴データが狂ったときの対処法

私は趣味の領域では回路CADとしてDesignspark PCBを使っている.

(基本的な操作がきちんとできるのと,Mechanicalとの連動機能があるのが気に入っている.

正直RSコンポーネンツとの連携機能は微妙に使い勝手が悪い)

で,出来上がったガーバーデータをFusionPCBやElecrowに発注しているのだが

先日データを送ったところ「ドリル穴データがないんだけど?」

という返答が返ってきた.

 

DesignSpark+FusionPCBの組み合わせは初めてではないので

おかしいと思ってガ―バーデータを改めてビューワで見たところ

穴データのみ拡大率が間違っていた.以下修正方法

 

「Output」

→「Manufactureing Plots」

→「Drill Data (Thorugh Hole)」メニュー

→「Output」タブ

→「Device Setup」ボタン

→「Units」の"Integer"と"Decimal"をそれぞれ"1"と"4"に設定

 (私が見た時には3と5になっていた)

f:id:s_ajisaka:20170303141907p:plain

これでOKらしい.ついでに他のレイヤーの設定も同じにしておくとより安全.

何でこうなったんだろう?とりあえず対処法はわかったがなぜそうなるかがわからないのが若干気持ち悪い.

 

では,また.

LPC-LINK2で Flash Download failed - "Cortex-M3" というエラーが出たときの対処法

前回に引き続きLPC-LINK2周りのエラーである.

 

今回は前回と異なり,コンパイル・ダウンロードが終わった後に

Flash Download failed - "Cortex-M3"

というメッセージが出て正常にプログラムがスタートしないエラーである.

調べてみるとこんなフォーラムが出てきた

ULINK: FLASH DOWNLOADING WITH CORTEX-M3 DRIVER

どうやら,書き込み領域の設定云々がエラーのようで・・・

 

しかし,TMPM37xのデフォルトのアドレスを指定しているはずなので

上手く動かないわけがない(というか領域外に書くのが怖くて変更したくない)

 

試しに,

「Project」→「Options for Target 'TMPM374'」→「Utilities」タブ

→「Settings」ボタン→「Flash Download」タブ→「Download Function」

を"Erase Full Chip"に指定するとなんかうまくいってしまった.

おそらくこれは書き込み前にマイコン内を全部消去するオプションだが

なぜこれでエラーが出なくなるかはよくわからない.

(変更されたところだけ書き込むアルゴリズムに不具合でもあるのかな?)

 

 

LPC Link2で"RDDI-DAP Error"が出たとき

先日,いざプログラミングとTMPM374にデータを書き込もうとした際

 

「RDDI-DAP Error」

 

というエラーが出て書き込めないトラブルが発生した.

何がなんやらわからず,

・LPC Link2のCMSIS-DAPファームウェアを書き直し

・ドライバの入れ直し

・別PCでの起動

・LPC Link2を買いなおしてやり直し

いずれも問題を解決することはできなかった.

 

やけくそになっていろいろ弄ってみたところ

TMPM374側のリセットボタンを押したり離したりしているうちに認識した.

どうやら,リセット周りがうまく動作していないことが原因だったようである.

リセットボタン周りのプルアップ抵抗が悪さしているのでは・・・と思っているが,

さてどうなんだろう?

(その後,抵抗をはずしては居ないがずっと動作はしている.)

 

それでは,また.

ATmega32u4は使いにくいという話

Arduinoを使った電子工作も慣れてくると色々と融通を効かせたくなるもので.

最近ではもっぱら純正基板の回路図をコピーして自分なりに作り替えて使ってます.
(部品を秋葉原で入手しやすいものに変えたり,サイズ変えたり)

 

今まで使っていたのは,Arduino UNOでおなじみにATmega328P,

その表面実装版であるATmega328P-AUだったわけですが・・・

 

いちいちUSBからアクセスするためにインタフェースICを追加するのが面倒だったので

いっそUSB機能付きのICに交換したれ,

ということでATmega32u4を使うことにしました,

 

ATmega32u4はUSB機能付きのICの中でも,Arduino Pro Microという

種類に使われているもの.当然回路図もあるので,それをコピーすれば楽勝・・・

と思っていたのが運の尽き.

 

実際に基板を設計し,実装した後でいざブートローダーを書き込もうとすると

一向に成功しない.

 

・まず,Aitendoで買ったPro MicroのコピーボードとUNOを接続し書き込み

→5V16MHzで成功.まぁこれで問題あるとどうしようもないんだけど.

・次に,自作ボードで同じように接続して書き込み

→3.3V8MHzで失敗.

"avrdude: Yikes!  Invalid device signature.
         Double check connections and try again, or use -F to override
         this check."

エラーの内容はこんなもの.どうせ接続が間違っているんだろうと

テスタで当たってみるも問題ない.

 

・何度かやってみると,Verifyに失敗しただのデバイスIDが違うだのと

毎回エラーの内容そのものが安定しない.

 

・もうどうにでもなれと,試しに5V16MHzに設定を変更して書き込み

→まさかの成功.ただし自作ボードに実装しているのは8MHzのクリスタル.

(まぁ,書き込む時はクロックの管理UNO側だから関係ないんだろうけど)

 

・その後,UNOを外して自作ボードをPCと接続するも,

USBデバイスが確認できないというエラーが発生.

 

その後,ブレイクアウトボードで試してみるなどいろいろやってみたが

問題は解決せず.

 

32u4で作れれば基板がすっきりするんだが,どうにもうまくやる方法が思いつかない.

(328Pの場合はUSB接続したいならFT232RL/Qを実装する必要がある.

IC2個はなかなかに面倒だ.何?今のUNOはFT232RじゃなくてATmega16u使ってる?

今回と同じようなオチになりそうなので使いたくありません.

シリアル通信できれば当面は何の問題もないしね.)

 

そんなわけで,しばらくは安全を取ってATmega328P+FT232Rの組み合わせで

設計することとしよう.ちょっと古臭いけどね.

 

では,また.