16ビットレジスタへのアクセス

pp.301-302

非公式訳
AVRは8ビットマイコンです。つまりデータは一度に8ビットずつしか処理できません。しかしTimer1のレジスタのうち、TCNT1OCR1AICR1などは16ビットレジスタです。こうした16ビットレジスタは2つの8ビットレジスタに分割されていて、それぞれ個別にアクセスするしくみになっています。ほとんどの場合はこれで問題ありません。たとえば、(訳註: 16ビットレジスタである)SPレジスタ(スタックポインタ)に値をロードするときは、下に示したように、最初は半分だけ(訳註: SPL)に値をロードし、次いで残りの半分(訳註: SPH)に値をロードします。

LDI R16, 0x12
OUT SPL, R16
LDI R16, 0x34
OUT SPH, R16 ; これでSPが0x3412になった。

しかし16ビットタイマーでは、レジスタの値を一度に全部読み書きしないと問題になることがあります。たとえば以下のような状況を考えてみましょう。

TCNT1レジスタ0x15FFという値が入っているとします。TCNT1の下位バイト0xFFを読み出して、それをR20にストアするとします。このとき同時にタイマークロックが発生すると、TCNT1の値は0x1600になります。そうなってからTCNT1の上位バイト0x16を読み出して、その値をR21にストアするとします。今読んだばかりの値を見ると、R21:R20 = 0x16FFです。そのため、本当はTCNT1には0x15FFという値が入っていたのに、0x16FFという値が入っていたように見えてしまいます。

この問題は、多くの8ビットマイコンが抱えています。しかしAVRは、バッファーとして使うTEMPという8ビットレジスタでこの問題を解消しています。図9-22を見てください。16ビットレジスタの上位バイトを読み書きするときは、その値がTEMPレジスタに書き込まれます。16ビットレジスタの下位バイトに書き込むときには、TEMPレジスタの内容も16ビットレジスタの上位バイトに書き込まれます。たとえば以下のプログラムを検討してみましょう。

LDI R16, 0x15
STS TCNT1H, R16 ; TEMPに0x15がストアされる。
LDI R16, 0xFF
STS TCNT1L, R16 ; TCNT1Lに0xFFがストアされると同時にTCNT1HにTEMPの内容(0x15)がストアされる。

STS TCNT1H, R16が実行されたあと、R16 の内容である0x15TEMPレジスタにストアされます。STS TCNT1L, R16が実行されると、R16の内容である0xFFTCNT1Lにロードされ、TEMPレジスタの内容である0x15TCNT1Hにロードされます。そのため、0x15FFが一度にTCNT1レジスタにロードされます。

AVRの内部回路を考慮すれば、16ビットレジスタの上位バイトに先に書き込んでから下位バイトに書き込むべきです。そうしないと、プログラムがうまく動きません。例として下のコードを見てください。

LDI R16, 0xFF
STS TCNT1L, R16 ; TCNT1Lに0xFFが代入され、TCNT1HにTEMPの内容(不定値)が代入される。
LDI R16, 0x15
STS TCNT1H, R16 ; TEMPに0x15が代入される。

このコードは正常には動作しません。その理由は、TCNT1Lに値がロードされるときにTEMPの内容がTCNT1Hにロードされるからです。TCNT1Lレジスタに値がロードされるときには、TEMPにはガーベジ(不適切なデータ)が入っています。これでは、望みどおりの動作にはなりません。

16ビットレジスタの下位バイトを読み出すときは、上位バイトの内容がTEMPレジスタにコピーされます。したがって、下のプログラムを実行するとTCNT1の内容が読み出されます。

LDS R20, TCNT1L ; R20にTCNT1Lの内容が代入され、TEMPにTCNT1Hの内容が代入される。
LDS R21, TCNT1H ; R21にTEMPの内容が代入される。

16ビットレジスタの上位バイト、下位バイトを読み出すときは、その順番に気をつけなければなりません。そうしないと結果を誤ります。

OCR1AOCR1Bの各レジスタを読み出すときはTEMPレジスタはからんでこないことに注目してください。(以下略~)

――――――――――――――――――――――――――――――――――
訳註:
まとめると

  • 16 ビットレジスタの上位バイトを相手に値を読み書きをしようとしても、実際に読み書きの相手になるのはTEMPレジスタである。
  • 16 ビットレジスタの下位バイトを相手に値を読み書きをすると、下位バイトは実際に読み書きされるが、同時に上位バイトとTEMPレジスタとの間でも読み書きが実行される。  
     

具体的にいうと

  • 16 ビットレジスタの上位バイトに書き込もうとしても、実際にはTEMPレジスタに書き込まれる。
  • 16 ビットレジスタの下位バイトに書き込もうとすると、下位バイトに書き込まれるのと同時に、上位バイトにTEMPレジスタの内容が書き込まれる。  
     
  • 16 ビットレジスタの上位バイトから読み出そうとしても、実際にはTEMPレジスタから読み出される。
  • 16 ビットレジスタの下位バイトから読み出そうとすると、下位バイトから読み出されるのと同時に、上位バイトの値がTEMPレジスタに読み出される。  
     

結論だけいうと

  • 16ビットレジスタ一度に書き込みたいときは、上位バイト、下位バイトの順に書き込むべし。
  • 16ビットレジスタから一度に読み出したいときは、下位バイト、上位バイトの順に読み出すべし。
  • C言語で記述するときは、コンパイラが処理をしてくれるので気にする必要はない。