平成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日追記)
(追記ここまで)
#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番地に配置されていることが確認できます。
以上でターゲットプログラムが出来ました。次は動作確認です。