Chapter 3. レッスン
3.12 レッスン 11: 間接アドレス指定
3.12.1 レッスンの紹介
このレッスンでは、間接アドレス指定という非常に重要なトピックを学びます。この コードは間接アドレス指定を使って移動平均フィルタを実装しています。このレッス ンでは、レッスン4のA/D変換のコードに移動平均フィルタを追加します。移動平均 では、直近n個のA/D変換結果をリストとして保持し、その平均値を求めます。この フィルタには、循環キューと平均値を求める関数の2つが必要です。
ポテンショメータを回すとA/D変換結果の値が変化します。この値に移動平均フィル タを適用した値をLEDに出力して表示します。
このフィルタは最新8個のA/D変換結果の平均値を求めます。サンプル数を2のべき 乗とすると、汎用の除算ルーチンではなくローテート命令を使って簡単に除算が使え ます。また、8個のA/D変換結果の値を毎回合計するのではなく、キューを使って最 も古い値を引き、最も新しい値を足した方が処理が高速です。
図3-14: 間接アドレス指定による移動平均
3.12.2 ハードウェアの動作
このレッスンの出力はレッスン 4 と同じです。ポテンショメータを回転すると LED の表示が変化します。A/D変換結果の上位4ビットの状態がLEDに反映されます。
3.12.3 概要
プログラムメモリのアドレスはプログラム カウンタでしか指定できませんが、データ メモリ空間のアドレスはいくつかの方法で指定できます。ほとんどの命令はアドレス 指定モードが固定されています。しかし一部の命令では、どのオペランドを使うか拡 張命令セットを使うかどうかに応じて、最大3つのモードでアドレスを指定できます。
PICkit™ 3 スタータキット ユーザガイド
DS41628B_JP - p.80 2015 Microchip Technology Inc.
アドレス指定モードには以下の種類があります。
1. 含意
2. リテラル
3. 直接
4. 間接
3.12.3.1 含意およびリテラル
PIC MCU の制御命令の多くは引数を必要としません。これらの命令はデバイス全体
に影響する処理を実行するか、1つのレジスタに暗黙的に処理を実行するかのどちら かです。このようなアドレス指定モードを含意アドレス指定と呼びます。例として、
EEPROMのレッスンで使うSLEEPおよびRESET命令があります。
似たような動作の命令で、オペコードに明示的な引数をとるものがあります。このよ うに何らかのリテラル値を引数として必要とするものをリテラル アドレス指定モー ドと呼びます。例として、addlw、movlb、call、goto命令があります。
3.12.3.2 直接アドレス指定
直接アドレス指定では、演算のソースアドレスおよび結果の格納先アドレス(または そのどちらか一方)の、アドレス全体またはその一部をオペコードの中で指定します。
オプションは命令の引数として指定します。PIC MCU のコア命令セットでは、ビッ ト/バイト指向命令が既定値で直接アドレス指定を使います。これらの命令は、いず れも下位バイトに 7ビット(PIC18では8 ビット)のリテラルアドレスを含みます。
このアドレスは、データRAMのいずれかのバンクにおけるレジスタアドレスか、ア クセスバンク (PIC18を使う場合)内の位置を表し、命令のデータソースとして参照 されます。
演算結果の格納先は、格納先ビット「d」によって決まります。「d」が「1」の場合、
結果はソースレジスタに上書きの形で書き戻されます。「d」が「0」の場合、結果は Wレジスタに格納されます。
3.12.3.3 間接アドレス指定
間接アドレス指定では、命令内で固定アドレスを指定しなくてもデータメモリ内のア ドレスにアクセスできます。この場合、読み書き対象のメモリアドレスへのポインタ としてファイル セレクト レジスタ(FSR)を使います。FSR自体も特殊ファイルレジ スタとしてRAM内に存在するため、プログラム制御で直接操作できます。このため、
FSR はデータメモリ内にテーブルや配列等のデータ構造を実装する時に非常に便利 です。間接アドレス指定で使うレジスタとして、間接ファイル オペランド(INDF)も 実装されています。このオペランドを使って、ポインタ値の自動インクリメント、自 動デクリメント、別の値によるオフセットの操作を自動で実行できます。
INDFnレジスタは物理的なレジスタではありません。これらはSFR空間にマッピン
グされていますが物理的には実装されておらず、「仮想」レジスタと見なす事ができ ます。特定の INDFレジスタに対する読み書きは、実際には対応する FSRレジスタ ペアへのアクセスです。例えばINDF1からの読み出しの場合、データはFSR1H:FSR1L が示すアドレスから読み出されます。
3.12.4 新しく学ぶレジスタ 3.12.4.1 両デバイス共通
3.12.4.1.1 INDFx/FSRx
PIC18とエンハンスト ミッドレンジ コアのどちらも間接アドレス指定ではアドレス
レンジ全体を使うため、データRAMのバンク切り換えは不要です。PIC18のFSRレ ジスタは 12ビットアドレスを構成し、エンハンストミッドレンジのFSRレジスタ は16ビットアドレスを構成します。つまりPIC18のFSRではデータメモリ全体へ のアクセスが可能で、エンハンストミッドレンジのFSRでは読み出し専用のプログ ラムメモリを含むメモリバンク全体へのアクセスが可能です。
図3-15: エンハンストミッドレンジの間接/直接アドレス指定
3.12.5 新しく学ぶ命令
3.12.5.1 両デバイス共通
3.12.5.1.1 INCF
指定したファイルレジスタの値に1を加算します。
表3-36: 新しく学ぶ両デバイス共通のレジスタ
レジスタ 目的
INDFx 仮想的な間接レジスタ
FSRx 仮想レジスタのターゲット アドレスを格納
Indirect Addressing Direct Addressing
Bank Select Location Select
4 BSR 6 From Opcode0 7 FSRxL 0
Bank Select Location Select
00000 00001 00010 11111 0x00
0x7F
Bank 0 Bank 1 Bank 2 Bank 31
0 7 FSRxH 0
0 0 0 0
表3-37: 新しく学ぶ両デバイス共通の命令
命令 英語名 目的
incf Increment f 現在の値に1を加算する
PICkit™ 3 スタータキット ユーザガイド
DS41628B_JP - p.82 2015 Microchip Technology Inc.
3.12.6 アセンブリ言語
3.12.6.1 両デバイス共通 例3-43:
これで、FSR0はQueueのアドレスを指し示します。図3-16で図3-43のコードを 説明します。ページ境界を超えてプログラムメモリのアドレスを指定できるように、
FSR0は2バイト幅である事に注意してください。
図3-16: FilterInit呼び出し前
図3-17: FilterInit呼び出し後
FilterInitを呼び出すと、FSR0レジスタはキューの最初のバイトを指し示します。
これで INDF0レジスタの読み書きが可能になります。INDF0レジスタの値を変更す
ると、FSR0レジスタが指し示すアドレスのレジスタの値が変更されます。FSR0を インクリメントすると、Queueレジスタの次のバイトを指し示します。この例では、
これは2回目のA/D変換結果です。
右方向へ1ビットローテートまたはシフトすると、値を簡単に2で割る事ができます。
FilterInit:
movlw low Queue ;point to the Queue holding the ADC values movwf FSR0L
movlw high Queue movwf FSR0H
INDF0
Queue [7]
Address:
Value:??????
Address:0x000 Value:??????
Address:0x0054 Value[0]: d’15’
FSR0L:FSR0H 0x004:0x005
INDF0
Queue [7]
Address:
Value:0x0054
Address:0x000 Value: d’15’
Address:0x0054 Value[0]: d’15’
FSR0L:FSR0H 0x004:0x005
rrcf RunningSum,w ; divide by 2 and copy to a version we can corrupt
式3-5:
3.12.7 C言語
3.12.7.1 両デバイス共通
PIC16/PIC18の INDF/FSRペアと同じ働きを、C ではポインタを使って実装してい
ます。
例3-44:
アセンブリコード同様、メインループの最初にポインタの参照先をキューの最初のバ イトに設定してポインタをリセットしています。次に、A/D変換結果を取得してキュー に保存します。次に、LATCにキューの平均値を代入します。このキューは8バイト 幅であるため、A/D変換結果のサンプルを8個格納できます(変換結果の下位2ビッ トは保存しません)。
例3-45:
関数averageはキューへのポインタをパラメータとして持ちます。_sumはグロー バル変数のため、この関数の外側でも値を保持します。移動合計から現在値を引き ます。アスタリスク (*) は間接演算子で、ポインタの参照先の値を使う事を意味し ます。新しい A/D 変換結果を取得し、移動合計とキューに追加します。次に、関数
averageの戻り値がメインループに返され、シフトした値がLEDに表示されます。
ウェブにはC言語のポインタについて解説したサイトが多くあります。詳細は、それ らのサイトを参照してください。
右ローテート前: b'00001010'= d'10' 右ローテート後:b'00000101'= d'5'
while (1) {
ptr_queue = &queue; //point to the first byte in this array (RESET the pointer)
for (i = NUM_READINGS; i != 0; i--){
LATC = (average(ptr_queue) >> 4 ); //only want the 4 MSbs for 4 LEDs ptr_queue++;
}
unsigned char average(unsigned char *ptr) { unsigned char adc_value;
_sum -= *ptr; //subtract the current value out of the sum adc_value = adc();
*ptr = adc_value; //assign ADC value to the queue _sum += adc_value; //add to the sum
return (_sum/NUM_READINGS); //compute the average }
PICkit™ 3 スタータキット ユーザガイド
DS41628B_JP - p.84 2015 Microchip Technology Inc.