Arduino スケッチで AVR インライン・アセンブラを使う

Arduino IDE では、特に「ファイルをプロジェクトに追加」のような操作をしなくても、メインのスケッチ (.pde / .ino ファイル) があるフォルダに含まれている C/C++ ソース・ファイル (.c / .cpp) を拾ってくれて自動的にコンパイル/リンクしてくれます。
ただし、アセンブラ・ソースファイルである .s / .S ファイルは無視されます。
IDE のメニューバーから Sketch / Add File... とたどっていくと、あたかも指定のファイルをプロジェクトに追加してくれるような雰囲気となりますが、実際にはそのファイルをエディットできるだけで、.s / .S ファイルは拾ってくれません。
したがって、どうしてもアセンブラで記述したい場合には、gcc 拡張機能のインライン・アセンブラを利用することになります。
割り込み応答のオーバーヘッドを限界まで切り詰めたい場合を想定して、ISR (Interrupt Service Routine: 割り込みサービス・ルーチン) をフルにインライン・アセンブラで記述する簡単な例を示します。
スケッチを下に示します。 プログラムの機能自体は単なる「LED チカチカ」です。

// AVR inline assembler test 
// (LED blinking at 2 Hz)

volatile int16_t gi16;

ISR(INT0_vect)
{
} // ISR(INT0_vect)

ISR(INT1_vect, ISR_NAKED)
{
  __asm__ __volatile__ (
    "lds r0, gi16   \n\t"
    "lds r1, gi16+1 \n\t"
  );
  reti();
} // ISR(INT1_vect, )

ISR(TIMER1_OVF_vect, ISR_NAKED)
{
  __asm__ __volatile__ (
    "PINB = 0x03      \n\t" // PINB
    "sbi  PINB,5      \n\t" // PINB5 (D13)
    "reti             \n\t"
 );
} // ISR(TIMER1_OVF_vect, )

void setup( void )
{
// LED = D13 = PORTB5
  pinMode(13, OUTPUT);
// TIMER1 setup  
  TCCR1A = 0x00; // normal mode
  TCCR1B = 0x03; // clk = 16 MHz / 64
  TIMSK1 = 0x01; // OVF int enable
} // void setup()

void loop( void )
{
} // void loop()

ISR(INT0_vect) と ISR(INT1_vect, ) は説明のために付け加えたもので、「LED チカチカ」の機能には寄与していません。 ISR(TIMER1_OVF_vect, ) と setup() 関数だけが重要です。
まず、ISR(INT0_vect) は「普通の C 関数」として記述した、中身が「空」の ISR です。
コンパイル結果を avr-objdump で逆アセンブルした結果は次のようになります。

00000100 <__vector_1>:
 100:	1f 92       	push	r1
 102:	0f 92       	push	r0
 104:	0f b6       	in	r0, 0x3f	; 63
 106:	0f 92       	push	r0
 108:	11 24       	eor	r1, r1

 10a:	0f 90       	pop	r0
 10c:	0f be       	out	0x3f, r0	; 63
 10e:	0f 90       	pop	r0
 110:	1f 90       	pop	r1
 112:	18 95       	reti

ソースの中身は空ですが、SREG / R0 / R1 をセーブ / リストアするプロローグ / エピローグ・コードが出力されています。 (0x100 〜 0x109 までがプロローグ・コード、0x10a 〜 0x111 までがエピローグ・コード)
中身が空なので、これだけですが、普通は ISR の中で使用されるレジスタのセーブ / リストアのためのコードも出力されます。
そんなに無駄なコードというわけではありませんが、それも切り詰めたいという場合には、ISR() マクロに ISR_NAKED という引数を追加して、プロローグ / エピローグ・コードのない「裸」の状態にすることができます。
その指定をしたものが、ISR(TIMER1_OVF_vect, ISR_NAKED) です。
アセンブル結果を下に示します。

0000011e <__vector_13>:
 11e:	1d 9a       	sbi	0x03, 5	; 3
 120:	18 95       	reti

ここに現れているのは、ソースで指定したインライン・アセンブラ

  __asm__ __volatile__ (
    "PINB = 0x03      \n\t" // PINB
    "sbi  PINB,5      \n\t" // PINB5 (D13)
    "reti             \n\t"
  );

中の sbi 命令と reti 命令の 2 つのアセンブル結果そのものです。
もし、ソースの中身が空なら、(reti 命令さえ生成されずに) オブジェクトの中身も空となります。
__asm__ __volatile__ ("") が gcc のインライン・アセンブラの構文です。
普通は、インライン・アセンブラでも C で記述した計算の結果を利用したり、アセンブラで記述した計算の結果を C 側の変数に渡したりと、両者が協調して動作するように「拡張アセンブリ構文」と呼ばれる記述を使いますが、ここでは「基本インライン構文」を使います。
基本インライン構文では、丸括弧 () の中に記述した文字列がそのまま avr-as アセンブラへ渡されます。
アセンブラ・ソースとして見やすいように、各行の終わりに改行文字 (\n) とタブ文字 (\t) を付加しておきます。
また、アセンブラ・ソースの 1 行分を、C ソース・プログラムの 1 行内で完結する 1 つのストリングとして記述しておきます。
avr-as アセンブラ用のレジスタ定義は用意されていないので、上の例のように B ポートの PIN レジスタをシンボル「PINB」で扱いたければ「PINB = 0x03」のように「自前」で定義する必要があります。
この ISR では、約 0.25 秒周期で発生する TIMER1 のオーバーフロー割り込みごとに PINB のビット 5 をセットしています。
ポート B のビット 5 は Arduino では D13 ピンであり LED が接続されているので、約 0.25 秒 ごとにトグルされて約 0.5 秒周期で点滅することになります。
PINB5 のセットを sbi 命令で行なっているので、レジスタもフラグも不変であり、レジスタ類のセーブ / リストアは全て省略しています。
ISR(INT1_vect, ISR_NAKED) はグローバル変数をアクセスするための記述のテスト用で、特に意味はありません。

  __asm__ __volatile__ (
    "lds r0, gi16   \n\t"
    "lds r1, gi16+1 \n\t"
  );
  reti();

のように、普通に書いてアクセスできるようです。 逆アセンブル結果を下に示します。

00000114 <__vector_2>:
 114:	00 90 00 01 	lds	r0, 0x0100
 118:	10 90 01 01 	lds	r1, 0x0101
 11c:	18 95       	reti

この例では、最後の reti 命令をインライン・アセンブラではなく、C のマクロの reti() を使って発生させています。 (reti() マクロの定義の中身はインライン・アセンブラ)