読者です 読者をやめる 読者になる 読者になる

ヾノ*>ㅅ<)ノシ帳

技術ブログに見せかけて、ジャンル制限のないふりーだむなブログです。

STM32F7 Discovery でOpenOCD+gdbによるLチカ

近いうちにOpenOCDを使う用件があるので、練習のためにSTM32F7 Discoveryで遊んでみました。 初心者向けに書きましたが、執筆時間の時間の都合で以下の事項は既習としてます。

  • 基本的なLinuxコマンドライン操作
  • gdb
  • MMIO(メモリマップドI/O)
  • GPIOが何なのか(と言っても文字通りの意味でしか無い…)

akizukidenshi.com

静電容量式のタッチディスプレイやSDカードスロット、オーディオI/Fなどがついてて8000円と良心的な値段設定です。 僕の手元にあるものは、ETという展示会(宇宙人はいませんが、昨年はR2-D2がいました?!)でSTマイクロエレクトロニクスのワークショップ的なものに参加してもらったものです。 来月にETやりますし、きっと似たような方法でもらえるかもしれません。
# ワークショップは有償開発環境の1日限定ライセンスのもとで実施されました

デバッグポートはST-Linkというもので、なんとUSBケーブル一本でデバッグできちゃいます。 組み込み特有のジャンパーコードやらきしめんみたいなリボンケーブルが必要ありません。 経済的で、初心者に優しいですね! 同じボードを用意できない場合は、Nucleoというボードが3000円くらいなので、そちらの方がお求めやすいかもしれません。

akizukidenshi.com

本エントリーでは、OpenOCDというデバッグ環境を用意した後、OpenOCDでターゲット(デバックするボードのこと)に接続し、 gdbでOpenOCDにアタッチし、手動でLチカするという内容でお送りします。

OpenOCDのビルド

詳細はそこまで知りませんが、OpenOCDはOpen On-Chip-Debuggerの略で、 JTAGデバッガなどを通じて、ホストPCからCPUが入っているマイコンに直接命令できるようなシステムです。 組み込み開発で有償開発環境(IAR Embedded Workbenchとか)を用意できない場合、gdbと合わせて使われるソフトウェアとして名前が上がるような代物です。 はじめに書きますが、100%ちゃんと動くことを期待しちゃダメです。気楽に使いましょう。

OpenOCDのビルドは比較的簡単です。 ダウンロード先はsourceforgeとgit(github)の2通りあります。 基本的にどちらでも良いですが、githubに上がっている方が良いかもしれません。 というのも、OpenOCDではJTAGデバッガやターゲットの設定を書いたスクリプトファイル(cfgファイル)が付属しているのですが、 github版のほうがそのファイルが豊富です。

OpenOCDのビルドは、基本的なビルド環境が揃っていることを前提に、以下のパッケージを必要とします。 ビルド前にaptとかで入れてください。

  • pkg-config
  • libusb-1.0*

さらに、gccのバージョンが重要で、4系でmakeが通ります。 色々考えるのが面倒なので、僕はVMのREMNuxでmake&&make installしてから、ホストにインストールしました。

以下は、ダウンロード済みのソースをビルドするためのコマンドです。

./bootstrap # git版のみ必要。configureが生成される
./configure --enable-ftdi # ftdiチップ関連の機能を有効にする
### configureにより、ftdiの項目がyesになっていることを確認する
make -j4
sudo make install

OpenOCDでターゲットに接続

OpenOCDでは、コマンドラインオプション-fで、ターゲットやハードウェアの方のデバッガに対応したcfgファイルを与えねばなりません。 バージョン0.9時点では、それらはtclというディレクトリの中に入っています。 注意すべきことは、-fで与えたパスが深すぎると依存関係を解決出ないという理由でエラーが出ます。 以下のように、-sオプションでベースディレクトリを教えるとうまく動いてくれます。 (まあカレントディレクトリにcfgをコピるのが一番楽ですがね)

root権限のあるシェルで実行しないと Error: libusb_open() failed with LIBUSB_ERROR_ACCESS と出て動きません

openocd -s tcl -f board/stm32f7discovery.cfg 

うまく接続するとこんな感じになります。

[root@K_atc openocd-git]# openocd -s tcl -f board/stm32f7discovery.cfg 
Open On-Chip Debugger 0.10.0-dev-00411-g607edef (2016-11-05-14:18)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v28 API v2 SWIM v16 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.201043
Warn : Silicon bug: single stepping will enter pending exception handler!
Info : stm32f7x.cpu: hardware has 8 breakpoints, 4 watchpoints

gdb起動

arm版のgdbを忘れずに用意します。 arm-none-eabi-gdbあたりがパッケージマネージャー使えば入るはずです。

OpenOCDがlocalhostの3333版ポートでgdbからのアタッチを待っています。 target remote :3333で会いに行きましょう〜
# -xは今は無視で

[katc@K_atc jtag]$ arm-none-eabi-gdb -q -x stm32f7.gdb 
/home/katc/.gdbinit:1: Error in sourced command file:
future__ import absolute_import:8: Error in sourced command file:
Undefined command: "from".  Try "help".
0x00000000 in ?? ()
(gdb) target remote :3333
Remote debugging using :3333
0x00000000 in ?? ()
(gdb) monitor targets
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32f7x.cpu       hla_target little stm32f7x.cpu       running
(gdb) 

手動Lチカ

gdbを使ってLチカさせていきましょう。

[datasheet]のBlock Diagramと[manual]を見ると、LD1というLEDがPI1に、PI1はGPIOI[1]につながっていることが分かります。
# このボードはArduinoのシールドとしても使えるらしいです。LD1はArduinoから操作することを考慮しているように見えますね。

f:id:katc:20161106000603p:plain f:id:katc:20161106000611p:plain

[datasheet]を見ると、GPIOIはメモリアドレス0x4002 2000 ~ 0x4002 2fffにマッピングされており、 [2]によると、0x40022000をベースアドレスとして、オフセット0にGPIOIのモードを変更するレジスタ(MODER)と、オフセット0x14に出力値を入れるレジスタ(ODR)があることが分かります。

f:id:katc:20161106000628p:plain

GPIOでは、ポートで外部入力を期待するのか外部出力するのか、またはその他(めいんどいので省略)なのかを特定のレジスタに教えることになります。 今回はそのレジスタはこのMODERというレジスタです。 [2]によると、今回はLEDを操作したいのでポートは出力モードで、そのためにはGPIOI[1]のモード設定でMODERの[3:2](2ビットから3ビット)を1にすれば良いことが分かります。

MODERレジスタの構成と値の意味

[2]を見る限り、とりまPI1に1を送るにはODR[1]に1をセットすれば良さそうです(←プルアップされているかどうかで話が変わりうる。結果としては1がLEDのONを意味する)

([2]にプルアップやプルダウンの文字がありますが、見なかったことにします。 0か1でLEDが点く話ですからね。)

以上より、LEDに関する初期化とLEDをon/offするコード、果てはLチカするユーザー関数のコードは次のgdbスクリプトに落ち着きます。
(ついでに、アタッチとターゲットのリセットを行います)

target remote :3333
monitor reset

### wait for target
shell sleep 2

set $GPIOI=0x40022000
set $GPIOI_MODER=$GPIOI+0x0
set $GPIOI_ODR=$GPIOI+0x14

### init led port
# MODER[2:3] = 2 (General purpose output mode)
set *(int *)$GPIOI_MODER=*$GPIOI_MODER|0x4 

def -ld1-on
    # ODR[1] = High
    set *(int *)$GPIOI_ODR=*$GPIOI_ODR|1<<1
end

def -ld1-off
    # ODR[1] = Low
    set *(int *)$GPIOI_ODR=*$GPIOI_ODR&~(1<<1)
end

def -LD1
    printf "SIGINT (Ctrl+C) to quit..."
    while(1)
        -ld1-on
        shell sleep 0.5
        -ld1-off
        shell sleep 0.5
    end
end

Lチカコマンドこと-LD1を実行すると無限ループの中で0.5秒毎にLD1が明滅します。おわり

[katc@K_atc jtag]$ arm-none-eabi-gdb -q -x stm32f7.gdb 
/home/katc/.gdbinit:1: Error in sourced command file:
future__ import absolute_import:8: Error in sourced command file:
Undefined command: "from".  Try "help".
0x00000000 in ?? ()
(gdb) -LD1
SIGINT (Ctrl+C) to quit...^CQuit
(gdb) 

f:id:katc:20161106000453p:plain

おまけ:OpenOCDのこんなときは

ターゲットをリセットしたい

ボードのリセットボタンを押すか、gdb(3333番ポート)からmonitor resetを送るか、telnet/nc(4444番ポート)からresetを送ります。

ターゲットをhaltしたい。haltをrunningに戻したい

gdbでの方法の説明に絞ります。

haltにするとき:monitor halt runningに戻したいとき: monitor reset run

(gdb) monitor halt
stm32f7x.cpu: target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x61000000 pc: 0x0803981c psp: 0x20007658
(gdb) monitor targets
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32f7x.cpu       hla_target little stm32f7x.cpu       halted
(gdb) monitor reset run
(gdb) monitor targets
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32f7x.cpu       hla_target little stm32f7x.cpu       running

# monitor reset run だと一回リセットが入るみたいだし、haltから再開する方法は無いのかな?

参考文献


ところで抵抗とコンデンサとLEDをラベルを見ずに外見だけで見分けるいい方法無いですかね…