制御を行なうためのマイコンで必要とされる演算(計算)処理は比較的簡単なものから非常に複雑なものまで、用途に応じて大きく変わります。複雑な計算を演算だけで、決められた時間内に処理しようとすると、マイコンには非常に高い演算能力が要求されることがあります。その対応のためだけに演算能力を高くするのはあまり実用的とはいえません。そこで、用いられるのがテーブル参照です。
テーブル参照は与えられた入力(x)に対して、あらかじめ結果を計算しておき、その結果を表(テーブル)で準備しておくものです。計算そのものはある程度の範囲の入力値に対して実行しておく必要があるので、手間はかかります。しかし、PCやその他のコンピュータを利用することで比較的簡単にできます。
テーブルを作成する場合にはその大きさに注意する必要があります。上に例のように繰り返しの場合には全ての入力値の範囲でテーブルを作成しておく必要はなく、繰り返される部分を代表する範囲だけのテーブルを作成すれば十分です。その上で、入力される値を繰り返しの周期で割った余りやその等を用いてテーブルを参照すれば済みます。
テーブル参照では、入力データを元にして、テーブルの中でその先頭から欲しいデータがある場所までアドレスの差(これをオフセット値と呼びます)を計算します。欲しいデータがバイト幅のデータであれば、入力データはそのままオフセット値として使えますし、16ビット幅のデータの場合には入力データを2倍することでオフセット値となります。このオフセット値をテーブルの先頭アドレスと加算すれば、欲しい値が格納されたメモリのアドレスを得ることができます。そのアドレスのメモリを読み出すことで、欲しい演算結果が得られます。
テーブル作成の際に問題になるのは計算の精度や、準備するデータ数です。あまり精度を欲張りすぎるとテーブルが大きくなり、メモリを圧迫してしまいます。全てをテーブル参照で処理するのではなく、最後に補間処理を行なうことで、テーブルを小さくできます。
テーブル参照で大まかな結果を得て、細かな部分は簡単な比例計算で求めるものです。テーブルとして準備するデータはある程度大きな間隔の分で済むので、テーブルの大きさを数分の1に抑えられます。
例えば、データ量を1/16にすることを考えると、入力データの下位4ビットが0000に対応するデータだけをテーブルとして準備しておきます。そこで、以下の処理を行なうと補間処理を行なうことができます。
で計算した誤差には符号を含むので、その後の計算が面倒になります。そこで、実際の処理では加重平均を取るのが簡単です。これらの処理が等価であることは次のように式を変形できることから分かります。ここで、mは分割数でnが入力データの下位ビットで示される数値とします。
では参考として、テーブルのデータを1/32に圧縮した場合を示しています。通常はここまでのデータ圧縮は必要なく、1/2に圧縮しても十分な場合もあります。1/2圧縮では、補間処理としては単純な前後の値の平均で処理でき、プログラムを簡単にできるので、処理速度は速くなります。トータルのメモリ使用量も単なるテーブル参照よりはるかに小さくできます。ただし、テーブルのデータ量が多くなるので、1/2圧縮の例は省きます。
[プログラム例]
sin0〜sin
/2を10ビット精度で求めるプログラム例を示します。入力値としては、0〜
/2の範囲を1024分割して0〜1023の10ビットで示すものとします。なお、入力パラメータからオフセット値を計算する部分で#00111110bとの論理積をとっている部分を#01111110bに変更し、テーブルを拡張すれば0〜2047の11ビットまで対応できるようになり、sin 0〜sin
までの値を得ることができます。
このプログラムでは、入力パラメータ及び結果の格納用に16ビットの変数(CONVDATA)、それに引き続いた8ビット変数(WORK1)を作業用に使用し、これらをショート・ダイレクト・アドレッシング領域に確保します。
WORK00 DSEG SADDRP
CONVDATA:
DS 2 ;入力パラメータと結果の戻り領域
WORK01 DSEG SADDR
WORK1: DS 1
プログラムはサブルーチンとしており、大きくは4つの部分(3つはプログラム本体で、1つは内部のサブルーチン)から構成されます。
(1)最初の部分は入力パラメータからテーブル参照を行なう部分です。テーブルとしては1/32に圧縮したデータを格納しておきます。そのため、テーブルのオフセット値を求めるために、入力データを1/32しますが、データ長が10ビットであり2バイト必要なことから、これを2倍にすることになります。つまり、入力パラメータを1/16すればテーブル参照のオフセット値となります。ここでは入力の下位10ビットのみが意味をもちます。そこで、上位4ビットは無視して、78K0の命令のデイジット・ローテート命令(ROR4 [HL])を使用して4ビット右回転することで1/16しています。このようにして得られたオフセット値(8ビット以下の値です)にテーブルの先頭アドレスを加算することで、テーブル上のデータのアドレスを得ることができます。テーブルを200Hのように下位アドレスが00から配置してあれば、加算する必要はなく、単にオフセットアドレスを下位アドレスに代入するだけでも処理できます(ここの例では一応、加算して求めています)。ここまでの部分は1/16する処理を除けば通常のテーブル参照と同じです。
CONVSUB:
PUSH AX
PUSH HL
MOVW AX,CONVDATA ;Aに上位、Xに下位
MOVW HL,#CONVDATA
ROR4 [HL] ;Aの下位4ビットと[HL]を1/16する
MOV A,[HL]
AND A,#00111110b ;オフセット値を得る
ADD A,#LOW TABLE ;下位アドレスを計算
MOV L,A
MOV A,#0
ADDC A,#HIGH TABLE ;上位アドレスを計算
MOV H,A ;HLはテーブルの対象を指す
MOV A,[HL] ;下位バイトを読み出す
MOV CONVDATA,A ;下位バイトの初期値としてセット
MOV A,[HL+1]
MOV CONVDATA+1,A ;上位バイトのセット
(2)次は補間のための計算です。ここでは加重平均をとるためテーブルの値と次の値に重みを付けて積算します。補間が必要ない場合には既に出力パラメータとしてテーブル参照した結果がセットされているので、処理を終了します。補間を行なう場合には、補間データだけ次のデータを加算(実際には次のデータと乗算)し、32から補間データを引いた数だけテーブルのデータを加算します。実際の計算はサブルーチンで処理します。ここでは、計算のための必要なパラメータを準備しておき、必要な回数だけサブルーチンをコールします。計算は16ビット×8ビット+16ビットとなるので、処理を簡単にするため、必要なパラメータはスタックにいれておき、HLレジスタを積算データのインデックスで使用しています。なお、積算した結果は16ビットに収まりますが、処理の共通化のため、1バイトを作業用で使用しています。この作業用の1バイトはデータとしては意味がないのですが、初期値として0を入れてあります。
MOV A,X
AND A,#00011111b ;補間のためのデータを得る
BZ $EXITS ;補間不要なら終了
;
; 補間(加重平均)のための準備
; (スタックに計算データを入れておく)
;
MOV WORK1,#0 ;上位データを初期化
MOV X,A ;補間データをXレジスタにセット
MOV A,[HL+3] ;次のデータの上位をセット
PUSH AX ;計算データをセーブ
MOV A,[HL+2] ;次のデータの下位をセット
PUSH AX ;計算データをセーブ
MOV A,#31
SUB A,X ;32に対する残り数を計算
MOV X,A
MOV A,[HL+1] ;テーブルの上位データをセット
PUSH AX ;計算データをセーブ
MOV A,[HL] ;テーブルから下位データを
MOVW HL,#CONVDATA ;ポインターを設定
;
; テーブルの検索データ分の積算を行なう
;
CALL !SEKIWA ;下位データ演算
POP AX
INCW HL
CALL !SEKIWA ;上位の計算と結果の積算
;
; 補間データから次のデータ重み分の積算を行なう
;
POP AX
DECW HL
CALL !SEKIWA
POP AX
INCW HL
CALL !SEKIWA
(3)積算した結果を1/32して最終的な結果を計算します。ここまでの結果は10ビットと5ビットの乗算と同じであり、結果は16ビット以下に収まります。結果を切り捨てではなく四捨五入するために積算結果の0.5に相当する値を加算してから1/32処理を行ないます。この処理はこれまでの処理の結果、HLがCONVDATA+1を指していることを利用しています。また、1/32するのに、4ビットの左回転を行ない、バイトをずらして考えることで1/16し、その後さらに1/2処理を行います。
MOV A,CONVDATA ;結果の下位をロード
ADD A,#00010000b ;四捨五入のために0.5分を加算
BNC $NEXT
INC CONVDATA+1 ;上位に桁上げ
NEXT: AND A,#11100000b ;未使用ビットをマスク
ROR A,1 ;結果を下位4ビットに
ROR A,1 ;A=00XXX000、CY=0
ROR A,1 ;A=000XXX00、CY=0
ROR A,1 ;A=0000XXX0、CY=0
ROL4 [HL] ;結果を1/16する
RORC A,1 ;さらに1/2する
XCH A,[HL] ;上位3ビットと下位を交換
RORC A,1 ;下位も1/2する
MOV CONVDATA,A ;結果を保存
EXITS: POP HL
POP AX
RET
(4)実際に積算のための積和演算を行なう内部のサブルーチンです。A、Xレジスタに乗数と被乗数を設定し、HLで積算されるデータを指した状態でコールします。A、Xレジスタの内容を乗算した結果を[HL]及び[HL+1]に積算していきます。
; 8ビットの積和演算
; (結果は16ビットに収まるものとする)
;
SEKIWA:
MULU X ;重みの計算
XCH A,X ;結果の上位と下位を交換
ADD A,[HL] ;下位を積算
MOV [HL],A ;結果をセーブ
MOV A,X ;結果の上位をもってくる
ADDC A,[HL+1] ;上位への桁上げ計算
MOV [HL+1],A ;上位バイトをセーブ
RET
(5) 0(0)〜1024(=
/2)に対応したテーブルです。テーブルのデータとしては1024個のデータではなく、1/32に圧縮した(32個ごとのデータだけをとりだした)データです。
対象範囲を0(0)〜2048(=
)に広げたい場合には、このテーブルに0〜1022のデータを逆の順で追加します。
TAREA CSEG AT 200H
;
; sin0〜sin(
/2)を32分割したときの値を1023倍した値のテーブル
TABLE: DW 0, 50, 100, 150
DW 200, 249, 297, 345
DW 392, 437, 482, 526
DW 568, 609, 649, 687
DW 723, 758, 791, 822
DW 851, 877, 902, 925
DW 945, 963, 949, 992
DW 1003, 1012, 1018, 1022
DW 1023