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

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

  本文の前に単なる愚痴ですが、
    マイクロソフトのバカヤロー。Windows Updateをダウンロードしたからといって、勝手にスリープボタンを再起動に変更するな!!!!!
 せめて、いつもの調子でボタンを押してしまった人の救済用に”本当にいいいですね!”くらいの確認してくれ。おかげで2時間近くかけて書いた文書が全て吹っ飛んだ。

ターゲットプログラムの作成
 二度目の文書作成になります。出来るだけ説明が(既に書いたつもりで)飛ばないように注意して書いていきます。
モニタプログラムの動作検証用にターゲットプログラムを作成します。最初のターゲットプログラムはやはり”hello world”でしょう。下記にターゲットプログラムのリストを示します。単に一行表示するだけですが、実際には組み込みの前提となるハードウェアの初期化や通信用の送受信ルーチンを用意しないといけないので、それなりに長くなります。ハードウェア関連の大半はモニタプログラム用に作った物と同じです。同じメモリ空間にあるとはいえ、モニタプログラム側の関数をターゲットプログラム側から利用するのは色々と面倒です。別々に用意します。
  ターゲットプログラム test.c のリスト
#include <stdio.h>
#include "myint.h"
#include "aki80.h"
/*------------------------------------------------------------------
*      関数中でのアセンブラ使用
*  ローカル変数を持たない関数では、スタックフレームは生成されない
*  ようで、sp値から引数までのオフセットは+2になる。
*----------------------------------------------------------------*/
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');
}
uchar SIOA_transmitNow(void)
{
   volatile uchar status2;
   outp(SIOA_CMD, SIO_CMD_Null | 1);       //WR1を選択
   status2=inp(SIOA_CMD);
   if(status2 & 0x01)      return 0;
   else                    return 1;
}
void main(void)
{
   initCTC3();
   initSIOA();
   SIOA_puts("hello world");
   while(SIOA_transmitNow())
       ;
   #asm
       DI
       JP 0
   #endasm
}
#pragma int_reti_n
void defaultInterrupt(void)
{
}
#pragma noint_reti_n
#asm
   global __rst8, __rst10, __rst18, __rst20, __rst28, __rst30, __rst38
   __rst8  EQU _defaultInterrupt
   __rst10 EQU _defaultInterrupt
   __rst18 EQU _defaultInterrupt
   __rst20 EQU _defaultInterrupt
   __rst28 EQU _defaultInterrupt
   __rst30 EQU _defaultInterrupt
   __rst38 EQU _defaultInterrupt
#endasm
#pragma int_retn_n
void _NMI(void)
{
}
 処理内容は至って単純で”hello world”を表示した後、送信完了を待ってモニタプログラムへ復帰(リセット動作)しているだけです。このプログラムにはモニタプログラム特有の記述が見られます。まず、未使用のINT割り込み処理をトラップするための関数defaultInterrupt()と同じく未使用のNMI割り込みをトラップする_NMI()関数が定義されています(青色太字部分)。ユーザープログラムで不測の割り込みが起きた場合はこれらの関数が呼ばれます。関数内に適切な対処を記述します。次に未使用の割り込みアドレスに対して上記の関数を設定するためのアセンブラ記述(赤色太字部分)があります。もし、特定の割り込みアドレスを使用するなら赤字部分のアドレス行を削除し、そのアドレスに対応する割り込み処理関数を記述します。割り込み処理関数の名前は”_rst[割り込みアドレス番地]”に固定されています。
   例: INT割り込み38番地を割り込みとして使用する場合は、
   __rst38 EQU _defaultInterrupt
を削除して、
(平成26年4月10日追記)
 同時にglobal宣言部の関数名も削除が必要でした。割り込み処理部分の検証を参照してください。
(追記ここまで)
#pragma int_reti_n
void _rst38(void)
{
   //割り込み処理内容を記述する
}
#pragma noint_reti_n
を追加します。ここでの#pragma宣言はGAIO Cコンパイラ特有の割り込み処理関数の宣言です。関数の始め部分と終わり部分の両方で宣言が必要です(宣言文は前と後ろで異なります)。
 次にターゲットプログラム用のスタートアップルーチンを用意します。スタートアップルーチンではユーザーROM領域の先頭にジャンプテーブルを作成します。主要な変更箇所を青太文字で示します。EXTNAL文はC言語のexternと同じく外部での変数定義です。9個分のジャンプテーブルが追加されていますが、この部分がどのアドレスに配置されるかはリンカスクリプトの記述によって決まります。
  ターゲットプログラム用のスタートアップルーチン
   EXTNAL  _DATA_SIZE  ; 初期値のあるデータのサイズ
   EXTNAL  _DATA_TOP   ; 初期値のあるデータのアドレス(RAM)上
   EXTNAL  _BSS_SIZE   ; 初期値のないデータのサイズ
   EXTNAL  _BSS_TOP    ; 初期値のないデータのアドレス(RAM)上
   EXTNAL  _main
   EXTNAL  __rst8, __rst10, __rst18, __rst20, __rst28, __rst30, __rst38,__nmi
;
;-------------------------------+
;   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   ;C standard
;
;----------------------------------------------------------------
;   Interrupt table
;----------------------------------------------------------------
C_START     SECT    CODE
       JP START
       JP __RST8
       JP __RST10
       JP __RST18
       JP __RST20
       JP __RST28
       JP __RST30
       JP __RST38
       JP __NMI
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
 最後にターゲットプログラム用リンカスクリプトを示します。ここでリンカがどのようにしてプログラムやデータの配置を決めているかについて簡単な概要を説明します。青色で示す宣言によってユーザーROMアドレスは0x8000番地から開始します。ここに最初に集められるのはコードつまりプログラム部分です。リンカは複数あるソースプログラムを順番に捜査してプログラム部分を集めてここに配置します。ここで重要になるのは「どの順番にファイルを捜査するか」です。この順番は赤色で示した/Moduleの順番に行われるようです。従って、ユーザーROMの開始アドレスである0x8000番地にジャンプテーブルを配置したければ、先頭の/Moduleにジャンプテーブルのあるファイルを指定し、そのファイル内のプログラム部分の先頭にジャンプテーブルを記述します。
 先のスタートアップルーチンでのプログラムの開始は下記宣言によって開始します。確かに開始直後にジャンプテーブルが配置されています。
C_START     SECT    CODE
;-------------------------------+
;ROM Area|
;-------------------------------+
/ADDR=8000
/SECT=C_*|CODE
/SECT=I_*
;-------------------------------+
;RAM Area|
;-------------------------------+
;/ADDR=8000
/SECT=D_*|COMM(data=_DATA)
/SECT=B_*|COMM(bss=_BSS)
/SECT=_STACK
;-------------------------------+
;init data section+
;-------------------------------+
/init_section = _INIT_DATA(_DATA)
;-------------------------------+
;linkage module|
;-------------------------------+
/Name=test
/Module=START.xao
/Module=test.xao
/slib=C:\PROGRA~1\AKIZ80\lib\z80\cs\csze1
/slib=C:\PROGRA~1\AKIZ80\lib\z80\STD\STDZE1
/slib=C:\GAIO\LIB\OBJ\printf.xlb
 アセンブラやリンカの動作については、この程度にしておきます。次の目標であるCコンパイラの移植ではもう少し細かな説明をするつもりでいます。
 実際にマップファイルを開いてジャンプテーブルのアドレスを参照すると正しく0x8000番地に配置されていることが確認できます。
 以上でターゲットプログラムが出来ました。次は動作確認です。
 ページ先頭へ 前へ 次へ ページ末尾へ