アフィリエイト広告を利用しています
Ad×Ad


Ad×Adは表示されるだけで報酬がもらえます。
以下から登録すると100ptもらえます。
 → アドアド -あなたの街の無料広告サイト-
検索
最新記事

広告

posted by fanblog

2016年06月11日

Arduino IDE 環境にて、UncompatinoへI2C接続してデバッグする

UncompatinoにはUSB端子がありますが、これをデバッグ用にしてしまうと、USBを使うアプリはデバッグできません。そこで、デバッグの情報はI2Cでやり取りし、USBはアプリ側で使用できるようにしました。でも、無いよりはマシですが、物凄く便利というわけのものでもありません。その辺りをよく理解した上でもしご興味があればどうぞ試してみてください。

接続図の説明

Arduino IDE を使用しますので、Uncompatinoともう一つのUNO互換機はPCとUSBで接続されます。Uncompatino側のUSBは、アプリが動き始めるとアプリが使用することになります。UNO互換機側のUSBは、I2C経由でUncompatinoから受け取ったデバッグ情報をPCに出力します。

接続図.jpg

実物写真

デバッガー用にUNO互換機を買うつもりなら、Uncompatinoをお勧めします。Uncompatinoは、ISPライターの機能もあるので、これ1台でUncompatino上のATmega328Pにヒューズバイトとブートローダー、EEPROMを書くことができます。USB-シリアル変換ICのFTDI製FT232RLの機能です。写真の四角で囲ったISPのところがそうです。
I2Cの配線もA4、A5ピンをそれぞれ接続するだけです。ブレッドボードの一番右にはブレークポイントを抜けるためのstartButtonを配置しました。

DSC07580.JPG

デバッガー側のシリアルポートの表示例

You can write keywords is 234
TWBR is 0x48
TWSR is 0xF8
You can write keywords is 235
TWBR is 0x48
TWSR is 0xF8
You can write keywords is 236
TWBR is 0x48
TWSR is 0xF8
You can write keywords is 237
TWBR is 0x48
TWSR is 0xF8


Arduino IDEの起動の仕方

私の環境では、ターゲット側のMaster-Writer2はCOM7をシリアルポートに指定しました。デバッガー側は、Arduino IDEをもう一つ起動して、COM8をシリアルポートに指定しました。くどいようですが、Arduino IDEを2つ起動するということは、Arduino IDEのスケッチを2つ開くということとは違います。お間違えのないようにご注意ください。Arduino IDEを2つ起動して初めて、別々のCOMをそれぞれ選べるようになります。Arduino IDEでソースコードを表示したら、COMポートを選択して、シリアルモニターを開いてください。ターゲット側とデバッガー側でそれぞれ別のCOMポートを選択できたかは、ウィンドウの下部にCOM番号が表示されますのでそれでも確認できます。

I2C_test.jpg

デバッグ仕様

1.関数名などの文字列と、何番目のブレークポイントなのかを示す通し番号の2つを表示させる。
 実装したものが下記となります。シリアル番号の表現としてint型で宣言して上位バイトと下位バイトに分離してバイト表現するやり方もあると思います。ここではbyte型のものを受信側(デバッガー側)でint型に合成することにしました。
 Wire.beginTransmissionから始まってWire.write、Wire.endTransmissionで終わる一連の動作が一つのI2Cシーケンスとなります。
 void debugPrint(char thisStr[]) { ← thisStr[]が任意の文字列となります。
  Wire.beginTransmission(8); // transmit to device #8
  Wire.write(thisStr); // sends charcters ← 文字列を送ります。
  Wire.write(x[0]); // sends one byte ← シリアル番号の下位バイトを送ります。
  Wire.write(x[1]); // sends one byte ← シリアル番号の上位バイトを送ります。
  Wire.endTransmission(); // stop transmitting
  if (x[0] == 255){
  x[1]++; ← シリアル番号の更新です。
  }
  x[0]++; ← シリアル番号の更新です。
 }

2.レジスター名とその内容(HEX形式)を表示させる。
 実装したものが下記となります。
 (レジスターによっては2バイト返すものがあるらしいのですが勉強不足で未対応です。そのうちに改良します。)
 void debugRegPrint(char thisStr[], byte regValue) { ← レジスター名、その内容(1バイト)の順です。
  Wire.beginTransmission(8); // transmit to device #8
  Wire.write(thisStr); // sends charcters ← レジスター名の送信
  Wire.write(regValue); // sends one byte ← レジスターの内容の送信 Arduino IDE では変数としてレジスタ名を書くだけでレジスタの内容が表示される仕様となっています。便利と言えばかなりお手軽な使い勝手です。これは、wire.hをインクルードしたので、幾重にもネストされたどこかのヘッダーファイルにマクロ定義がされているものと思います。そこまで調べて突き止める気力がありませんでした。

  Wire.write(0); // dummy byte(debugPrintと揃える為) ← ダミーバイトの送信
  Wire.endTransmission(); // stop transmitting
 }

3.スタートボタンを押したらブレークポイントから抜けることができる。
 実装したものが下記となります。
 void breakPoint(void) { ← 引数も戻り値もありません。
  while (1) { ← 無限ループです。
  if (digitalRead(startButton) == LOW) { ← スタートボタンを押すとLOWになります。
  delay(500);//wait a finger to take off ← 500ms待つのは、ボタンを押してから離すまでの時間です。ボタンを押してから離すまでは500ms以内に行わないと次のブレークポイントもすり抜けることになりますので注意してください。逆に長押しするということは、ブレークポイントを複数個所スルーできるので、適宜使い分けると便利かも。
  break;//when the startButton got pushed ← 無限ループを抜けます。
  }
  }
 }

4.上記1と2で送られたデータを解析して表示する。
 実装したものが下記となります。
 I2Cシーケンスが始まると割り込みが発生します。割り込みを処理する関数を割り込みハンドラーと呼びます。
 void receiveEvent(int howMany) { ← 割り込みハンドラーです
  while (2 < Wire.available()) { // loop through all but the last ← 受信したデータが最後の2バイトになるまでループします。
  c = Wire.read(); // receive byte as a character ← 1バイトずつ文字として読みます。
  if (c == '*'){ ← 読んだ文字が*なら、ここで打ち切りします。
  break;
  }
  Serial.print(c); // print the character ← USB経由で1文字ずつPCに表示させます。
  }
  Serial.print(" is "); ← USB経由で文字列「 is 」をPCに表示させます。
 
  if (c == '*'){ ← レジスターとしての処理を行います
  //debugRegPrint
  z = Wire.read(); // receive byte as a byte ← 1バイトをbyte型として読みます。
  Serial.print("0x"); ← PCに「0x」を送ります。
  Serial.println(z, HEX);// print the regValue as a hex ← 読んだデータをHEX形式でPCに送ります。
  z = Wire.read(); // throw out a dummy byte ← ダミーバイトを読み捨てします。
  } else { ← 任意の文字列の処理となります。
  //debugPrint
  x = Wire.read(); ← シリアル番号の下位バイトをint型として読みます。
  y = Wire.read(); // receive byte as an integer ← シリアル番号の上位バイトをint型として読みます。
  x = 256 * y + x; ← シリアル番号に戻しています。x += 256*y とも表現できますが分かり難いですよね。
  Serial.println(x);// print the integer ← シリアル番号PCに送ります。
  }
 }

全体のサンプルコードは下記の通りです。Arduino IDE に付属していたコードを参考にさせていただきました。
(元ファイルは、Arduino IDEを立ち上げたら、メニューバーの「ファイル」−「スケッチの例」−「Wire」と開いて行くとあります)

ターゲット側のソースコード(Master-Writer2)

 loop()の中の、debugPrint、debugRegPrint、breakPointの3点で一組と考えていますが、debugPrint、breakPointの2点だけもありかなと思います。ここでは、レジスターTWBRとTWSRの値を読んでいますが、この2つのレジスターの値を読むとI2CのSCLの速度が計算できるからです。
 最大の注意点なのですが、レジスタ名としてデバッガーに認識させるためには文字列の最後はアスタリスク(*)で終わってください。これが任意の文字列とレジスター名の文字列との識別子となります。


#include <Wire.h>

byte x[2];  //as a serial number
const int startButton = 7;

void setup() {
 Wire.begin(); // join i2c bus (address optional for master)
 pinMode(startButton, INPUT);
 x[0] = 0;
 x[1] = 0;
}

void debugPrint(char thisStr[]) {
 Wire.beginTransmission(8); // transmit to device #8
 Wire.write(thisStr);        // sends charcters
 Wire.write(x[0]);           // sends one byte
 Wire.write(x[1]);           // sends one byte
 Wire.endTransmission();     // stop transmitting
 if (x[0] == 255){
   x[1]++;
 }
 x[0]++;
}

void debugRegPrint(char thisStr[], byte regValue) {
 Wire.beginTransmission(8);  // transmit to device #8
 Wire.write(thisStr);        // sends charcters
 Wire.write(regValue);       // sends one byte
 Wire.write(0);              // dummy byte(debugPrintと揃える為)
 Wire.endTransmission();     // stop transmitting
}

void breakPoint(void) {
 while (1) {
   if (digitalRead(startButton) == LOW) {
     delay(500);//wait a finger to take off
     break;//when the startButton got pushed
   }
 }
}

void loop() {
 debugPrint("You can write keywords");//ここは適当な文字列
 debugRegPrint("TWBR*", TWBR); //read TWBR reg.  レジスター名には*をつける
 debugRegPrint("TWSR*", TWSR); //read TWSR reg.
 breakPoint();
}



デバッガー側のソースコード(Slave-Receiver2)

 負の数字は扱わないのでx,yは、intではなくunsigned intとしました。そんな大きなプログラムは扱わないと思うのですが、気になったのでそうしておきました。


#include <Wire.h>

void setup() {
 Wire.begin(8);                // join i2c bus with address #8
 Wire.onReceive(receiveEvent); // register event
 Serial.begin(230400);        // start serial for output
}

void loop() {
 delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
char c;
unsigned int x, y;
byte z;
void receiveEvent(int howMany) {
 while (2 < Wire.available()) { // loop through all but the last
   c = Wire.read();             // receive byte as a character
   if (c == '*'){
     break;
   }
   Serial.print(c);       // print the character
 }
 Serial.print(" is ");

 if (c == '*'){
   //debugRegPrint
   z = Wire.read();        // receive byte as a byte
   Serial.print("0x");
   Serial.println(z, HEX);// print the regValue as a hex
   z = Wire.read();        // throw out a dummy byte
 } else {
   //debugPrint
   x = Wire.read();
   y = Wire.read();   // receive byte as an integer
   x = 256 * y + x;
   Serial.println(x);// print the integer
 }
}



I2CのSCLの周波数計算

ATmega328Pの日本語データシートだとP136の「22.5.2.ビット速度発生器」のところに計算式があります。
  SCL周波数=CPUクロック周波数/(16+2×(TWBR)×前置分周値)
  前置分周値:P153の表22-7参照。→そのページに行くと、TWSRレジスターのbit1、bit0が、TWPS1、TWPS0とあり、表22-7から前置分周値が読み取れる。
  今回の場合、TWBRが0x48で、TWSRが0xF8でした。0x48は10進で72、0xF8の下位2ビットは00なので分周値は1である。これを式に代入すると、SCL=16MHz/(16+2×72×1)=100kHz となりました。

一休み

後の問題は、ここで考えたブレークポイントを<どうやってターゲットのソースコードに埋め込むか>ですね。
まさか、関数ごとに手打ちで入れ込むなんて気が遠くなりそうだし間違えそう。なんとかしなくては。
この記事へのコメント
コメントを書く

お名前: 必須項目

メールアドレス: 必須項目


ホームページアドレス: 必須項目

コメント: 必須項目

※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバックURL
https://fanblogs.jp/tb/5145369
※ブログオーナーが承認したトラックバックのみ表示されます。

※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック
×

この広告は30日以上新しい記事の更新がないブログに表示されております。