スパイス  組み込み制御装置の受注製作

モニタプログラムを移植する
 平成26年4月 6日

シリアルポートの動作確認
 かなり手間が掛かりましたがやっとSIOの初期化が出来ました。これで動作確認のプログラムを書くことが出来ます。
ここで動作確認用のプログラムを示します。最初はスタートアップルーチンstart.xasです。以前のリストにはIOアドレスの入力ルーチンinp()関数を含んでいました。、今回はinp()関数を削除してあります。
 start.xasのリスト
   EXTNAL  _DATA_SIZE  ; 初期値のあるデータのサイズ
   EXTNAL  _DATA_TOP   ; 初期値のあるデータのアドレス(RAM)上
   EXTNAL  _BSS_SIZE   ; 初期値のないデータのサイズ
   EXTNAL  _BSS_TOP    ; 初期値のないデータのアドレス(RAM)上
   EXTNAL  _main
;
;-------------------------------+
;   Stack   (RAM)       +
;-------------------------------+
_STACK  sect    stak
   ds  100h
_STACK_END:
;-------------------------------+
;   init section    (ROM)   +
;-------------------------------+
_INIT_DATA  SECT    COMM    ; 初期値のあるデータの配置されたセクション(ROM)上
;-------------------------------+
;   Control data    (RAM)   +
;-------------------------------+
D_DATA  sect    comm
   global  _errno
   _errno  dw  0   ;for math lib
;
;----------------------------------------------------------------
;   Interrupt table
;----------------------------------------------------------------
INIT        SECT    CODE,ORG=0  ; nmi
       JP  START

C_START     SECT    CODE,ORG=100H
START:
       ld  HL,_STACK_END       ;Set Stack Pointer
       ld  SP,HL
;
; 初期値のあるデータをROMからRAMへ転送する
;
       LD  HL, _INIT_DATA
       LD  DE, _DATA_TOP
       LD  BC, _DATA_SIZE
       LDIR
;
; 初期値のないRAMエリアを0クリアする
;
       LD  IX, _BSS_TOP
       LD  HL, _BSS_SIZE
       LD  DE,1
CP_BSS1:        
       SBC HL,DE
       JP  C,CP_BSS2
       LD  (IX),0  
       INC IX
       JP  CP_BSS1
CP_BSS2:
;
; main 関数を呼び出す
;
       CALL    _main
       JR  START
   
       HALT
;
       END START

 次はSIOを介してPCのハイパーターミナルと通信するプログラム test.c のリストです。このCコンパイラにはIOアドレス空間にアクセスする関数 inp(), outp()がないので、C関数の表現方法にアセンブラを埋め込む形で実装してあります。

  test.cのリスト
#include <stdio.h>
#include "myint.h"
#include "aki80.h"

/*------------------------------------------------------------------
*      関数中でのアセンブラ使用
*  ローカル変数を持たない関数では、スタックフレームは生成されない
*  ようで、sp値から引数までのオフセットは+2になる。
*----------------------------------------------------------------*/
(平成26年4月11日追記) この関数の書き方は最適化で問題を起こします。最適化の悪影響を参照ください。
(追記ここまで)

void outp(uint addr, uchar data)
{
   //引数は右側の引数から順にスタックに積まれる。
   //一番左側の引数のアドレスは(sp+2)になる(スタックフレームなしの場合)
   #asm
       ld IY, 0
       add IY,SP
       ld c,(IY+2)
       ld b,(IY+3)
       ld a,(IY+4)
       out (c),a
   #endasm
}

uchar inp(uint addr)
{
   #asm
       ld IY, 0
       add IY,SP
       ld c,(IY+2)
       ld b,(IY+3)
       in a,(c)
       ld c,a          ;8ビットの返り値はCレジスタで返す
   #endasm
}

void initCTC3(void)
{
   outp(CTC3, 0x03);   //ダミーコマンド
   outp(CTC3, 0x07);   //タイマモード 1/16
   outp(CTC3, 0x4);    //TimeConstant, 0x4で9600bps
}

void initSIOA(void)
{
   outp(SIOA_CMD, SIO_CMD_Null | 0);       //WR0を選択
   outp(SIOA_CMD, SIO_CMD_ChRst | 0);      //リセット
   outp(SIOA_CMD, SIO_CMD_RstExtInt | 4);  //RR0リセット後、WR4を選択
   outp(SIOA_CMD, 0x44);                   //x16Clock, 1Stop, NoParity
   outp(SIOA_CMD, SIO_CMD_Null | 3);       //WR3を選択
   outp(SIOA_CMD, 0xc1);                   //受信8bits, 許可
   outp(SIOA_CMD, SIO_CMD_Null | 5);       //WR5を選択
   outp(SIOA_CMD, 0x68);                   //送信8bits, 許可
}

int SIOA_getc(void)
{
   volatile uchar status=inp(SIOA_CMD);
   volatile uchar error;
   if((status & 0x01)==0)      return EOF; //受信データなし
   
   outp(SIOA_CMD, SIO_CMD_Null | 1);       //RR1を選択
   error=inp(SIOA_CMD);
   //エラー発生?
   if(error &= 0x70)       return -(int)error;
   else                    return inp(SIOA_DATA);
}

void SIOA_putc(char c)
{
   volatile uchar status;
   do{
       status=inp(SIOA_CMD);
   }while((status & 0x04)==0);
   outp(SIOA_DATA, c);
}

void SIOA_puts(char *s)
{
   while(*s!='\0')
       SIOA_putc(*s++);
   SIOA_putc('\n');
   SIOA_putc('\r');
}

void main(void)
{
   int in;
   initCTC3();
   initSIOA();
   SIOA_puts("AKI-80 test Monitor");
   for(;;){
       if((in=SIOA_getc())>=0)
           SIOA_putc(in);
   }
}

個々の関数については少し説明が必要です。まず、inp(), outp()関数では一部をアセンブラで記述してあります。このためアセンブラを混在させるために必要な知識を説明します。

このCコンパイラでは関数呼び出しに対してIX, SPは保存する必要があります。他のレジスタは破壊してもかまわないことになっています。引数へのアクセスはスタックフレームがある場合は、(IX+4)のアドレスに最も左側の引数が配置され、アドレスの増加方向で順に右側の引数が配置されるとヘルプに書かれています。コンパイラのオプション設定に強制的にスタックフレームを生成するというオプションがありますが今回はチェックを入れていません。test.cのリストファイル(Cをアセンブラに変換した出力でファイル名はtest.lis)を見るとinp(), outp()関数ともにスタックフレームは生成されていません。スタックフレームはローカル変数を効率よくアクセスするための手法ですので、ローカル変数が定義されないと使用されないのでしょう。この場合は、SP値を基準に(SP+2)に最も左側の引数が配置され、順に右側の変数が配置されます。
 これで引数のアドレスが特定できますので引数を参照出来ます。次に返り値は8ビットならCレジスタ、16ビットならBCレジスタペアに返り値をセットします。後はアセンブラの命令表を参照すれば動作は分かると思います。
 SIOA_getc()関数はエラー処理がいい加減ですが、今の段階ではエラー処理を入れても複雑になるだけなのでエラーが起きたことを検出するだけに留めてあります。

main()関数では、"AKI-80 test Monitor"の表示を行った後、受信文字をオーム返ししています。このプログラムの実行画面を示します。


 これだけのプログラムを書いて一発で動くことはまずありません。今回もCTCによるボーレートクロックの設定をミスしていました。過去の記述は本日付で訂正を追加してあります。もし動作しないとすると、プログラムとハードのどちらが間違っているかを順に追っていくことになります。具体的には、通信ケーブルを外した状態で
 ・ボーレートクロックは期待した周波数を出力しているか
 ・AKI-80のシリアル出力に0x55や0xaaといった値を連続的に出力させて信号波形を確認する。
  (出力できているなら初期化はある程度まで正しく動作している)
 ・通信ケーブルを接続してAKI-80の出力をハイパーターミナルで正しく受信できるか。
  (出来なければTX/RXの交換を忘れている可能性が高い)
 ・PCからのシリアル入力を正しく受信できるか
などを順に見ていきます。

 これでモニタプログラムを作る基本的な部品は出来ました。
 あとは80系特有の割り込み処理をどうするかを考える必要があります。8080以降のCPUでは、割り込みが発生するとROMエリア(アドレス8番地など)にPC値を設定します。ROMにはモニタプログラムしか置けない(この時点ではユーザープログラムは存在していない)ので、ROMアドレスからRAM上の何処かにPCを移動させる必要があります。

(平成26年4月7日追記)
 後で気がついたのですが、この説明は分かり難いですね。80系のCPUでは割り込みを受け付けると、プログラムカウンタの値(PC値)をスタックに退避して、割り込み毎に決まったアドレスから割り込み処理プログラムの実行を始めます。この開始アドレスは全て0x100以下のモニタプログラムROMが占有しているアドレスです。ユーザーが割り込みを使用できるためには、この割り込み処理を一旦ユーザーが自由に変更できるRAMアドレスに移動させる必要があります。具体的には、先の割り込み毎に決まったアドレスにジャンプ命令をおいてRAMアドレス上のユーザー割り込み処理ルーチンを呼び出します。しかし、Cでは特定のアドレスに関数や命令を配置するのはそれほど自由には出来ないので工夫が必要です。
(追記ここまで)

 ページ先頭へ 前へ 次へ ページ末尾へ