LoginSignup
7
4

More than 5 years have passed since last update.

LPC810の割り込みハンドラをアセンブリで書いてハマった

Last updated at Posted at 2016-03-13

はじめに

Disclaimer

この挙動がどの仕様で規定された物なのか確認してません。中の作りを想像するにCortex-M0+共通の仕様な気がしていますが、もしかしたらNXP社製のCortex-M0+系に特有の挙動かもしれませんし、LPC810のみの仕様かもしれません。

tl;dr

割り込み復帰にmov pc, lr使うとhardware faultで死ぬので注意しましょう。

とか書くと

割り込みからの復帰が普通のリターン命令じゃ駄目とか当たり前じゃね?というのが普通のおじさんの反応です。でもCortexは違うんだよ、というのがARMおじさん達の言い分。ちなみに正しい書き方は

bx lr

または

push {lr}
pop {pc}

後者はスタックをちょっと汚すだけで意味はまったく同じ。中で別の関数呼んでる時は(lrが壊れるので)後者のペアをprologとepilogに書くのが普通。

何が起きているのか

Cortex-M0+の割り込み

ChaN大先生32ビットへの誘いで詳しく説明している事の繰り返しですが、Cortex-M0+は割り込みが発生すると、caller saveレジスタと一部のシステムレジスタをユーザプログラム走行時のスタックに自動退避し、lrレジスタに特殊なアドレスを積んで割り込みベクタに書かれたアドレスに飛んできます。

この特殊なアドレスと言うのは、自分が見た値は0xfffffff9だったので、だいたいそんな感じの値だと思って下さい。Cortex-M0+のメモリモデルではコードやデータに使ってはいけないとされている領域なので、このアドレスへのジャンプは通常発生しません。

戻る時はこの特殊なアドレスにジャンプすれば、CPUがスタックに積んだはずのデータを使って割り込みからの復帰処理を自動的に行います。

ようするに割り込み発生時には、C言語の関数呼び出しの規約をハードウェアで処理してくれます。その結果普通に書いたCの関数が割り込みベクタに直接登録できるという、変なところで気の利いた仕様なのです。

落とし穴

で、この特殊なアドレスへのジャンプによる復帰ですが、どうも単に復帰すれば良いわけではないようで、popを使ってpcに書き戻して上げる必要があるようです。コンパイラは通常prologでcallee saveレジスタやら特殊レジスタをpushして、epilogでpopするというコード書くので、まずこの制限でひっかかる事はありません。

アセンブリで書く場合にもbl命令による関数呼び出しがlrレジスタを破壊するため、普通は最初にlrもpushして、最後にまとめてpcにpopで流し込んでリターンするのが普通です。

が、本当に簡単な処理だけして戻る場合には、そのままmov pc, lrとか書きたくなるのが人情というもの。うっかりこちらで書くと特殊アドレスに本気でジャンプしてhardware faultを起こすようです。

movとpop両方に手を入れたくなかった、という気持ちはわからなくもないけど。緩い条件で公開されてる資料には割り込み周りの事情はほとんど書かれていないので、ちょっとこれは辛かった。

おまけ

調査方法

Serial Wire Debug使いました。SWD用の機器を持ってないので困ったなー、とか思ってたら部屋にトラ技2014年3月号が転がってました。部屋の在庫部品で付属基板を組み立てて2月号のCD-ROMからCMSIS-DAPのファームを書き込み。これでLPCXpressoからSWDを使った書き込みとデバッグができるようになります。CMSIS-DAP自体はHIDとして見えるので特別なドライバは不要。Linux版でも問題なく動いてます。

もともとLPCXpressoでC言語使って開発してた人はこれだけでばっちり。自分はarm-none-eabi-gcc使ってアセンブラだけで開発してたので、ちょっと特殊な設定が必要でした。

  • New ProjectからC ProjectとしてProjectを作成
  • 自動生成されたソースファイルは即削除して、元々のソース一式をsrc以下にコピー
  • makeは元々自分で用意してたものは使わずにIDEの自動生成にお任せ
  • PropertiesからC/C++ Build>Settings>Tool Settings>MCU Assembler>General>Include paths (-I)を選んでアセンブリ用のインクルードパスを(srcに)設定
  • C/C++ Build>Settings>Tool Settings>MCU Linker>Managed Linker Scriptを開いてManaged linker scriptからチェックを外してLinker scriptに自分のLinker scriptを設定

これでLPCXpressoからBuild/Debugメニューを使ってアセンブリのコンパイル&リンクとCMSIS-DAP経由のデバッグができました。ソースファイル上でブレイクポイント設定したり、実行止めた時に自動でソース開いたりと、期待した以上に「普通に」動いてます。

ここで普通にCのソースとか追加すれば、crtに依存しない限りは普通(?)に混ぜて使えます。今回は問題の例外ハンドラを試しにCで書いてみたら、他の部分は変えずに動くようになったので、コンパイル版と手書き版の差を詰めていって問題に気づいた感じです。やっぱりキモいですよね、基本アセンブリで例外ハンドラだけCのコードとか。

割り込みベクタで使ってないとこにはダミー関数のアドレス入れてブレイクポイント張っておくと予期しない例外が起きている時に気づけて良いかと思います。今回も最初から張ってたんですが、それでhardware faultに気づきました。

mov pc, lrの制約

どうやらmovの場合にはARM/Thumbモードの切り替えも発生しないようで。海外のフォーラムでARMのドキュメントが紹介されてました。

Note
You must use a BX lr instruction at the end of the ARM subroutine to return to the caller. You cannot use the MOV pc,lr instruction to return in this situation because it does not cause the required change of state.

これはThumbから呼ばれたARMコードのリターンについて言ってますが、割り込みについても似たような制限があるのでしょう。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4