マイコンの記録

今はとある組込系開発の活動記録です。

ジャイロをつなごう(I2C基本編)

いま開発を嗜んでいるマイコンは簡単にいうと、車載してコースを走るときの車両の状況を記録しつつ、四駆の電気自動車?なので、各モーターの回転数を車両の姿勢にあわせて微調整する、というも役目も担っている。ようやく、I2C通信もわかってきて周辺デバイスも制御できるようになってきたので、ちょっとまとめておこうと思う。今回はジャイロ。こういう感じのものでアマゾンなんかでも買える。しかも結構安い。最初の基板でなんだか動かなくて、依頼主に「なんか基板の調子が」って言ったら、直ぐにもう2枚送ってくれた。でも基板の調子はすこぶる良かったみたい。

f:id:loopsketch:20190303123908j:plain
GY-521

このボードは、MPU-6050というデバイスが搭載されていて加速度と角速度が取れる6軸のもの。基板上にx軸、y軸の向きが書いてあるので、丸い穴がある方が前で端子がある方が後ろ向きに搭載すれば、なんとなく姿勢が取れそうだ。arduinoの記事は良くみかけるが LPC-1347 ではなかなか見つからなかったので、I2Cデバイスを制御する流れをまとめておこう。まずは初期化。LPC-1347ではLPCOpenというライブラリを使って開発するが、そのI2Cモジュールを設定し、クロックは400kHzとした。

void I2CInit() {
	Chip_SYSCTL_DeassertPeriphReset(1);
	Chip_Clock_EnablePeriphClock(5);
	Chip_I2CM_ResetControl(LPC_I2C);
	Chip_I2CM_SetBusSpeed(LPC_I2C, 400000);
}


続いて、ジャイロの基板とI2C通信してみる。I2Cはデバイスを制御したい側をマスタ、されるデバイス側がスレーブとしてその間で通信を行う規格。読込指定でアドレスを送信するとデバイスからデータが返されたり、書込指定でアドレスとデータを送信すると、デバイスにデータを送れたりできる。各デバイスにはスレーブアドレスという、予め決められたデバイスを指定するためのアドレスがあり、それがあることで複数のI2Cデバイスが同じバス上に接続できる。LPCOpenではだいたいこんな感じに書く。

I2CM_XFER_T xfer;
uint8_t gyro_buff[16];

uint32_t test_gyroscope() {
	gyro_buff[0] = 0x75; // WHO_AM_I
	gyro_buff[1] = 0x00;
	xfer.slaveAddr = 0x68;
	xfer.txSz = 1;
	xfer.txBuff = &gyro_buff[0];
	xfer.rxSz = 1;
	xfer.rxBuff = &gyro_buff[1];
	uint32_t res = Chip_I2CM_XferBlocking(LPC_I2C, &xfer);
	if (!res || gyro_buff[1] != 0x68) {
		DEBUGPRT("failed WHO_AM_I %x\r\n", gyro_buff[1]);
		return 0;
	}
      :
      :
    return 1;
}

I2CM_XFER_Tという構造体は、LPCOpenでI2C通信するときに使う情報を登録する構造体で、Chip_I2CM_XferBlocking()関数が、I2C通信をする関数だ。Blockingという関数名だけあって、ちゃんと通信が終わるまで帰って来ない。それもなんかアレなので、この関数を丸々コピーして、タイムアウトするようなバージョンを実際は使っていて、タイムアウトした場合はリトライしたり、以降亡き者として処理できるように工夫する必要はあると思う。なお、スレーブアドレスは幾つか、とか、どのアドレスがどのような機能か、などはメーカのサイトや部品購入時にデータシートという資料があって、そこにまとめられている。このジャイロ基板の場合は0x68というスレーブアドレスで通信できると、メーカのサイトにあるレジスターマップという資料に書いてある。また、0x75というアドレスを読込指定で送信すると自身のスレーブアドレスを返すWHO_AM_Iという機能があり、今回はtxSz/txBuffで指定したポインタに0x75を書いておき、rxSz/rxBuffで指定したポインタに、うまく通信ができると0x68が書き込まれる。ので、13行目で、それを判定し、0x68以外ならエラーとしている。

この後必要に応じてデバイスの初期設定を行う。この基板は、電源オンになるとまずSLEEPモードに入りその間に初期化を行い、SLEEPを解除するとデータが収集されるようになる。SLEEPを解除するためには、PWR_MGMT_1というアドレスにパラメータを設定する。尚、スレーブアドレスは同じなので、xferはそのまま使い回している。gyro_buffも使い回しているが、サイズや内容は通信内容に応じて都度変更する。書込だけで読込が無い場合は、rxSz=0 / rxBuff=NULLとする。

      :
      :
    gyro_buff[0] = 0x6B; // PWR_MGMT_1
    gyro_buff[1] = 0x00;
    xfer.txSz = 2;
    xfer.txBuff = &gyro_buff[0];
    xfer.rxSz = 0;
    xfer.rxBuff = NULL;
    res = Chip_I2CM_XferBlocking(LPC_I2C, &xfer);
    if (!res) {
        DEBUGPRT("failed set PWR_MGMT_1 %x\r\n", res);
        return 0;
    }
      :
      :

最初、xferやgyro_buffをグローバルに置かずにローカル変数にしていたが、誤動作して無駄に悩んだのでグローバルに置いて使い回すようにした。正しい書き方や振る舞いが判る人がいれば教えて欲しいところ。別の案件でもスタックは小さいから気をつけろ、とか、特殊ルールみたいなのがあったので、なんとなくそういうことなのかな〜と思ったけど、ちょっとポインタ引数はドキドキする。今回は半分趣味なので気楽だけど。では最後に、姿勢データを取り出すところ。0x3Bから14バイト読み込むと、それが、加速度、気温、角速度データになっている。

      :
      :
    int16_t raw[7];
    gyro_buff[0] = 0x3B;
    xfer.txSz = 1;
    xfer.txBuff = &gyro_buff[0];
    xfer.rxSz = 14;
    xfer.rxBuff = &gyro_buff[1];
    uint32_t res = Chip_I2CM_XferBlocking(LPC_I2C, &xfer);
    if (res) {
        raw[0] = (int16_t)((((uint16_t)gyro_buff[ 1]) << 8) | gyro_buff[ 2]);
        raw[1] = (int16_t)((((uint16_t)gyro_buff[ 3]) << 8) | gyro_buff[ 4]);
        raw[2] = (int16_t)((((uint16_t)gyro_buff[ 5]) << 8) | gyro_buff[ 6]);
        raw[3] = (int16_t)((((uint16_t)gyro_buff[ 7]) << 8) | gyro_buff[ 8]);
        raw[4] = (int16_t)((((uint16_t)gyro_buff[ 9]) << 8) | gyro_buff[10]);
        raw[5] = (int16_t)((((uint16_t)gyro_buff[11]) << 8) | gyro_buff[12]);
        raw[6] = (int16_t)((((uint16_t)gyro_buff[13]) << 8) | gyro_buff[14]);
    }
      :
      :

各データは、ビックエンディアンで並んでいる16bitのshortの値。キャストの仕方とか、もっと簡潔に書けるかもしれないので、誰か教えてください。また、取れるのはrawデータなので、この後加速度や気温、角速度に変換する必要がありますが、細かいところはレジスターマップを見よう。このジャイロ基板は様々な機能があって、まだうまくいってないんだけど、いろいろやると結構正確にデータを取れそうな気もする。このやり方だと、常に最新値を取るだけなので、場合によってはデータが欠落してて実際の姿勢よりも少なく出るんじゃないかと思うし、arudino界隈で使われてるソースを参考に、FIFOの辺りとかをもうちょっと調査しようと思っている。

割込みが使えない時に気にすること

今年こそは記事を続けるぞ、と毎年思うのに、1つか2つ書いたらついついそれで満足してしまい、今回も着実に日が空いてしまっているな。いつもすみません、誰にだ。

前回の記事に出てきた「Cプログラムでいきなりマイコン制御」を読みながら、まずはマイコンのGPIOの使い方を覚え、GPIOはただの接点入出力だと思っていたのだけど、設定すればいろんなことに使えるんだ、ということを知る。オンボードのLEDはすぐにLチカできるようになり、適当に短絡して入力できるようにもなった。今取り組んでいる開発は、電圧値のアナログ入力を行って、それをモーターを制御するためのPWMに変えて出力する、ジャイロのデータも使って4つのPWMを微妙に制御する、みたいなことをやろうとしている。僕個人的には、小学生の頃好きだったFM音源のドライバをまた作りたいな。YAMAHAさまのIC(2608と2610、どちらも取扱超難らしい)とDACはもうたくさんあるので早くつなぎたい。

ちなみにボードは、例えば秋月電子であれば2019年2月現在2500円くらいで手に入る。開発環境はEclipseベースで、たくさんのサンプルとともにインストールされるので、たぶんすぐに使えるようになるだろう。でもちょっとだけハマったところがあるのでまとめておこうと思う。

 

f:id:loopsketch:20190214161507j:plain
LPC-1347全体。下は制御する周辺機器がつながるボード

自分がちょっと苦労したのは、開発環境上で、まっさらなプロジェクトを作る方法。既述の本にプロジェクトウィザードを使った流れで細かく書いてあって、その通りに作れば基本的には動くよ。でも、ADCとか、タイマーを使おうとして、いざ割込みを使おうとなったら一向に動かなくて悩んだ。ADCはポーリングで順番に各chを見に行くと動くけど、バーストモードを使おうと思うとダメ。USBは基本割込でみたいで、まったく使えない。デバッガで起動しても、ブート直後にダンマリとなり、強制的にBREAKかけると、IntDefaultHandlerにいらっしゃることが多い。SysTickだけを使うような定期的なLチカ点滅とかは問題無かったのだけど、他のペリフェラルを使おうとすると途端にダメになる感じ。なのに、サンプルのコードは問題無く動く。main関数の内容は説明に必要なものだけ書いたものが下記。

int main(void) {
    SystemCoreClockUpdate();
    SysTick_Config(SystemCoreClock / 10000);

    Chip_GPIO_Init(LPC_GPIO_PORT);
    Chip_GPIO_SetPinDIROutput(LPC_GPIO_PORT, 0, 7);
 
    USBInit();
 
    while (1) {
        Chip_GPIO_WritePortBit(LPC_GPIO_PORT, 0, 7, true);
        delay(1);
        Chip_GPIO_WritePortBit(LPC_GPIO_PORT, 0, 7, false);
        delay(1000);
        /** ここにUSBの処理いろいろ */
    }
}

ここで、8行目のUSBInit()を呼ぶとこの症状が出るが、呼ばないと出ない。もちろん呼ばないとUSBの処理いろいろは動かない。症状が出る状態だと、14行目のdeley()あたりで止まってしまう。delay()はSysTickを使ってウェイトをしている関数で、どうやらハンドラが全然飛ばなくなり、カウンタが進まなくなる。ちなみに、割込みテーブルみたいなものは、プロジェクトを作成した時にウィザードが自動的に作成し、cr_startup_lp13xx.cというファイルになる。ブレイクしたときも、このファイルのIntDefaultHandlerの中でwhileしている。いろいろ調べて行くと、ADCやUSBのサンプルコードのプロジェクトは動くのに、今回まっさらから作ったプロジェクトは動かない、ということに気がついた。「あれ、もしかして、割込みテーブルって既述違う?」

サンプルのプロジェクトにあるcr_startup_lp13xx.cに書かれている割込みテーブルって、チップごとにdefineでいろいろ定義がかかれている。でも、まっさらから作ったプロジェクトの割込みテーブルには、そういった既述が少なく、SysTick_Handler以降の並びも大きく違った。ちょっと引用長いかもしれないが、自分で忘れないように、の意味もこめて。

プロジェクトウィザード製 cr_startup_lp13xx.c

void (* const g_pfnVectors[])(void) = {
        &_vStackTop, // The initial stack pointer
        ResetISR, // The reset handler
        NMI_Handler, // The NMI handler
        HardFault_Handler, // The hard fault handler
        MemManage_Handler, // The MPU fault handler
        BusFault_Handler, // The bus fault handler
        UsageFault_Handler, // The usage fault handler
        __valid_user_code_checksum,  // LPC MCU Checksum
        0, // Reserved
        0, // Reserved
        0, // Reserved
        SVC_Handler, // SVCall handler
        DebugMon_Handler, // Debug monitor handler
        0, // Reserved
        PendSV_Handler, // The PendSV handler
        SysTick_Handler, // The SysTick handler
        /** ここから */
        WAKEUP_IRQHandler, // PIO0_0  Wakeup
        WAKEUP_IRQHandler, // PIO0_1  Wakeup
              :
        WAKEUP_IRQHandler, // PIO3_2  Wakeup
        WAKEUP_IRQHandler, // PIO3_3  Wakeup
        /** ここまでのIRQHandlerが凄く多い */
        I2C_IRQHandler, // I2C0
        TIMER16_0_IRQHandler, // CT16B0 (16-bit Timer 0)
        TIMER16_1_IRQHandler, // CT16B1 (16-bit Timer 1)
        TIMER32_0_IRQHandler, // CT32B0 (32-bit Timer 0)
        TIMER32_1_IRQHandler, // CT32B1 (32-bit Timer 1)
        (以下省略)


サンプルコードに含まれている cr_startup_lp13xx.c

void (* const g_pfnVectors[])(void) = {
        &_vStackTop,            //  initial stack pointer
        ResetISR,               // The reset handler
        NMI_Handler,            // The NMI handler
        HardFault_Handler,      // The hard fault handler
        MemManage_Handler,      // The MPU fault handler
        BusFault_Handler,       // The bus fault handler
        UsageFault_Handler,     // The usage fault handler
        __valid_user_code_checksum, // LPC MCU Checksum
        0,                      // Reserved
        0,                      // Reserved
        0,                      // Reserved
        SVC_Handler,            // SVCall handler
        DebugMon_Handler,       // Debug monitor handler
        0,                      // Reserved
        PendSV_Handler,         // The PendSV handler
        SysTick_Handler,        // The SysTick handler
        /** ここから */
        PIN_INT0_IRQHandler,    // All GPIO pin can be routed ...
        PIN_INT1_IRQHandler,    // ... to PIN_INTx
        PIN_INT2_IRQHandler,
        PIN_INT3_IRQHandler,
        PIN_INT4_IRQHandler,
        PIN_INT5_IRQHandler,
        PIN_INT6_IRQHandler,
        PIN_INT7_IRQHandler,
        GINT0_IRQHandler,
        GINT1_IRQHandler,       // PIO0 (0:7)
        0,
        0,
        /** ここまでが圧倒的に少ないし、以降defineは省略したが有効なものの並びも大分異なる */
        RIT_IRQHandler,
        0,
        SSP1_IRQHandler,        // SSP1
        I2C_IRQHandler,         //  I2C
        TIMER16_0_IRQHandler,   // 16-bit Counter-Timer 0
	TIMER16_1_IRQHandler,   // 16-bit Counter-Timer 1
	TIMER32_0_IRQHandler,   // 32-bit Counter-Timer 0
	TIMER32_1_IRQHandler,   // 32-bit Counter-Timer 1
        (以下省略)

バージョンとか、いろいろあるのかもしれない。実は割込みテーブルも自分で書くモノ、とかかもしれない。でもリザーブとかも書かれてる位だから、何番目は何の割込みであること、って決まってるに違いないし、こんなに違っちゃいけない気がする。ユーザマニュアルのNVICの章をみたところ、同じような並びのテーブルがあった、正解はサンプルコードの方だ。そこで、サンプルコードの cr_startup_lpc13xx.c を拝借しまっさらなプロジェクトに上書きしたところ問題無く動作した。ウィザードでボードの型番とか使うライブラリとか指定するのに、ちゃんと正解のテーブルにならないのなんでだろう。ちなみに、プロジェクト作成の操作手順は冒頭の参考図書に書いてあり(ちょっと増えてる画面もあるけど)、その手順で行っているので、今やろうとしてて同じ状況の人は意外と居るかもしれない。

とりあえず割込み問題は解決できたようなので、先に進めることにしようと思う。

新年立読始め。

最近の立読みのお供がこちら。

今、LPC1347で実験しているのだけど、ちょっと古いからかググってもあまり情報が無いし、オフィシャルもリンク切れが多いしでちょっと困る。やっと開発環境が出来てデバッグできるようになったのに急に書込み出来なくなったり。さすがにISPのことは載って無さそうだったけど、各ペリフェラルの使い方のサンプルコードが載っていて、他のデバイスとの比較もできるし参考になりそうだった。ハードのことはよくわからんけど、そこそこ開発した経験があったり他のプラットフォーム触ったりしたことがある人はこれくらいのアプリケーションノート的なのが良いよね。それに、LPC1347のサンプルコードをみると、なんか簡単な関数呼び出すだけでGPIOの設定とかしてるし。何それ俺も使いたい。 |= とか &=~ とか面倒くさいよ。買おうと思ったのだけど、いつもここでこれを読んでいるような気がするし、もしかしたらもう既に買っていて家にあったような読まずに積ん読?、って気もして買わずに帰った。駐車場代800円もしたけど買わずに帰った。家で探してみたけど、FPGAやARM系の本はあるにはあったが、これは無かった(苦笑)。

 

結局帰ってからamazonでポチりました。ポイント使って1000円くらいになったのでまぁいいか。さぁADCのバーストモードについて調べるぞ。

デバッガに接続出来なくなったとき。

このところ、LPC1347というマイコンでちょっとした開発を嗜んでいる。

f:id:loopsketch:20180909215551j:plain


昨晩、とある実験でちょっと悩んでいた部分が巧くいき、ホクホクでベットに入ったのだが、翌朝、LPCXpressoでDebug As C/C++ MCU Application で起動すると、

 Error: Error reported by server (redlinkserv):
RedlinkAPI: Hardware interface transfer error
Redlink Server has been terminated and will be restarted.
Please restart your debug session.
If the problem recurs, please power cycle your debug probe and restart LPCXpresso.

 というダイアログが表示され、全く起動できなくなった。一晩開けてしまったし、何かOSやドライバがアップデートされてしまったり、カスペルスキーが何かやらかしてないかとも思ったので、カスペルスキーを停めたり、OSXなのでWindows10にLPCXpressoをインストールしなおしてやり直してみたりしたが、たしかに Redlink Server が返すStatusはfailになっていて、どの環境でも同じだった。マイコン壊しちゃったかー、と思ったが、必死にぐぐると、ISPモードなる救世主がいることがわかった。

LPCXpresso: デバッグアクセスできない場合の対処法 | easy labo

 

こちらのサイトによると、アクセス出来なくなるケースはいろいろあるみたいだが、心当たりがあった! 標準でJTAGアサインされているGPIOをADCから入力するように設定変更したのを焼いてしまったのだった。自ら接続を切ってしまったのだからデバック出来なくなるの当たり前だよね。。。サイトによるとISPを立てればISP(In-circuit Serial Programmer) Bootloaderが起動できるみたい。書込専用モードってことだよね。そのサイトにも書いてあったが、ユーザマニュアルをISPで検索すると、LPC1347では PIO0_1 をLowにしてリセットすればいいみたい。そこで、PIO0_1をGNDに落として、LPC-LinkのUSBをさすと、LEDが半輝度位で起動した。この状態でLPCXpressoからDebugAsしたりすればプログラムを送り直すことができた。そのままデバッグは出来なかったのでISPをオープンにして、もう一回USBをさしなおしてからデバッガもやり直すとデバッガも接続できるようになった。借り物のボードなので復活できてめっちゃ良かった。。。

 

元々の設定を変えなければ、ボードはそのままつなぎっぱなしで直ぐデバッグできて便利だが、全てのGPIOは使えない、ということか。ADCを使えるピンは決まっているし、それがLPC-Linkにつながっているのとかぶっているので仕方無いか。書込みは常にISPモードで、と考えれば全てのGPIOを自由に使えるのだから、そっちの方がいいのかも。