目次
前
次
sdcc利用
Windows、UNIXを問わずに、Z80用Cプログラムでの
開発ができるコンパイラに、sdcc(Small Device C Compiler)
があります。
Hitech_C、MiniCともに入手性がよくないので
sdccを利用し、ファームウエアを開発します。
sdccは、下記URLよりダウンロードできます。
http://sdcc.sourceforge.net/
Windows用のインストーラをダウンロードして
インストールすると、フォルダをつくり次の
ような構成になりました。
Z80関係の内容だけをインストールしたので
フォルダは18MB程度になっています。
sdccは、GUIで操作する環境ではないので
バッチファイルを作り、タイプ量を減ら
してみます。
次のようなバッチファイルを作成しました。
d:\sdcc\bin\sdcc %1.c -mz80 --code-loc 0x0100 --no-std-crt0 -Wlmycrt.o
copy %1.ihx %1.hex
del %1.ihx
自分が使いやすいように、次のようにタイプすると
コンパイル、リンクし、HEXファイルを生成するよう
にしてあります。
d:\SDCC>cl test{enter}
Cソースコードをコンパイル、リンクする場合
Cのスタートアップルーチンをリンクするのが
普通なので、main関数でハードウエア構成を
カスタマイズできるように、自前のスタート
アップルーチンを記述しました。
.globl _main
;.org #0x0000
ld SP,#0x0000
call _main
ret
スタックポインタを設定し、Cのmain関数に
制御を渡します。
Z80は、メモリとI/Oの空間を分けているので
I/O空間からデータを入出力する場合、inp、outp
等の関数を使います。
sdccでは、sfr(Special Function Register)を
利用して、ラベル定義によりアセンブリ言語の
in、outを使うように指定できます。
sfrを利用してI/Oアドレス指定するには
次のように記述します。
sfr at 0x00 P55A ;
sfr at 0x01 P55B ;
sfr at 0x02 P55C ;
sfr at 0x03 P55CTL ;
sfr at 0x10 CTC_C0 ;
sfr at 0x11 CTC_C1 ;
sfr at 0x12 CTC_C2 ;
sfr at 0x13 CTC_C3 ;
sfr at 0x18 SIOAD ;
sfr at 0x19 SIOAC ;
sfr at 0x1a SIOBD ;
sfr at 0x1b SIOBC ;
sfr at 0x1C PIO_AD ;
sfr at 0x1D PIO_AC ;
sfr at 0x1E PIO_BD ;
sfr at 0x1F PIO_BC ;
Cのソースコードに、上記定義を取り込んで
コンパイル、リンクしてみます。
テストするソースコードは、以下です。
/* define my system I/O area */
sfr at 0x00 P55A ;
sfr at 0x01 P55B ;
sfr at 0x02 P55C ;
sfr at 0x03 P55CTL ;
sfr at 0x10 CTC_C0 ;
sfr at 0x11 CTC_C1 ;
sfr at 0x12 CTC_C2 ;
sfr at 0x13 CTC_C3 ;
sfr at 0x18 SIOAD ;
sfr at 0x19 SIOAC ;
sfr at 0x1a SIOBD ;
sfr at 0x1b SIOBC ;
sfr at 0x1C PIO_AD ;
sfr at 0x1D PIO_AC ;
sfr at 0x1E PIO_BD ;
sfr at 0x1F PIO_BC ;
/* define data types */
typedef unsigned char UBYTE ;
typedef unsigned short UWORD ;
typedef signed char SBYTE ;
typedef signed short SWORD ;
/* define constant values */
#define LAST 10
int main(void)
{
UBYTE i;
UBYTE sum ;
sum = 0 ;
for ( i = 0 ; i < LAST ; i++ ) {
sum += i;
PIO_AD = sum ;
}
return 0;
}
コンパイルして生成されるアセンブリ言語コードの
内容を眺めてみます。
;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 2.9.1 #5456 (May 15 2009) (MINGW32)
; This file was generated Mon Dec 19 22:54:56 2011
;--------------------------------------------------------
.module test
.optsdcc -mz80
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
.globl _main
;--------------------------------------------------------
; special function registers
;--------------------------------------------------------
_P55A = 0x0000
_P55B = 0x0001
_P55C = 0x0002
_P55CTL = 0x0003
_CTC_C0 = 0x0010
_CTC_C1 = 0x0011
_CTC_C2 = 0x0012
_CTC_C3 = 0x0013
_SIOAD = 0x0018
_SIOAC = 0x0019
_SIOBD = 0x001a
_SIOBC = 0x001b
_PIO_AD = 0x001c
_PIO_AC = 0x001d
_PIO_BD = 0x001e
_PIO_BC = 0x001f
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
.area _DATA
;--------------------------------------------------------
; overlayable items in ram
;--------------------------------------------------------
.area _OVERLAY
;--------------------------------------------------------
; external initialized ram data
;--------------------------------------------------------
;--------------------------------------------------------
; global & static initialisations
;--------------------------------------------------------
.area _HOME
.area _GSINIT
.area _GSFINAL
.area _GSINIT
;--------------------------------------------------------
; Home
;--------------------------------------------------------
.area _HOME
.area _HOME
;--------------------------------------------------------
; code
;--------------------------------------------------------
.area _CODE
;test.c:33: int main(void)
; ---------------------------------
; Function main
; ---------------------------------
_main_start::
_main:
;test.c:38: sum = 0 ;
;test.c:39: for ( i = 0 ; i < LAST ; i++ ) {
ld bc,#0x0000
00101$:
ld a,b
sub a,#0x0A
jr NC,00104$
;test.c:40: sum += i;
ld a,c
add a,b
;test.c:41: PIO_AD = sum ;
ld c,a
out (_PIO_AD),a
;test.c:39: for ( i = 0 ; i < LAST ; i++ ) {
inc b
jr 00101$
00104$:
;test.c:44: return 0;
ld hl,#0x0000
ret
_main_end::
.area _CODE
.area _CABS
アセンブリ言語コードで、I/O処理でout命令に
変換されていることを確認できました。
生成されたアセンブリ言語コードから読み取れる
内容をリストしてみます。
- ファイル名を、.moduleの右に記述して情報としている
- 利用プロセッサは、.optsdccの右に、オプション指定の文字列で入れている
- sfrのラベル定義では、ひとつのアンダースコアを前に付加
- メモリ空間を、複数エリアに分割して管理
- プログラムは、エリア _CODE に記述
- データは、エリア _DATA に記述
- エリアには、_OVERLAY、_HOME、_GSINIT、_GSFINAL、_GSINITもある
- 関数内部のint型変数は、BCレジスタペアに割り当てる
- 関数からの戻り値は、HLレジスタペアで扱う
Z80では、演算は8ビットのAレジスタを利用するので
計算は必ずAレジスタを使うようにコード生成されて
います。
I/Oレジスタは、メモリマップドI/Oになるように
Cでは記述していても、展開するとI/OマップドI/O
に変換されます。
関数間では、どんな操作をするのかを簡単なプログラム
を記述して調べてみます。
テストするソースコードは、以下です。
/* define my system I/O area */
sfr at 0x00 P55A ;
sfr at 0x01 P55B ;
sfr at 0x02 P55C ;
sfr at 0x03 P55CTL ;
sfr at 0x10 CTC_C0 ;
sfr at 0x11 CTC_C1 ;
sfr at 0x12 CTC_C2 ;
sfr at 0x13 CTC_C3 ;
sfr at 0x18 SIOAD ;
sfr at 0x19 SIOAC ;
sfr at 0x1a SIOBD ;
sfr at 0x1b SIOBC ;
sfr at 0x1C PIO_AD ;
sfr at 0x1D PIO_AC ;
sfr at 0x1E PIO_BD ;
sfr at 0x1F PIO_BC ;
/* define data types */
typedef unsigned char UBYTE ;
typedef unsigned short UWORD ;
typedef signed char SBYTE ;
typedef signed short SWORD ;
/* define constant values */
#define LAST 10
void send_value(UBYTE x);
int main(void)
{
UBYTE i;
UBYTE sum ;
sum = 0 ;
for ( i = 0 ; i < LAST ; i++ ) {
sum += i;
send_value(sum);
}
return 0;
}
void send_value(UBYTE x)
{
UBYTE tmp ;
/* inverse */
tmp = x ^ 0xff ;
/* impress */
PIO_AD = tmp ;
}
このプログラムでは、関数にパラメータを渡すときに
スタック経由というCの仕様で、どのようにスタックを
使うのかを調べます。
コンパイルして生成されたアセンブリ言語コードは以下。
;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 3.4.0 #8981 (Apr 5 2014) (MINGW32)
; This file was generated Fri May 23 13:23:34 2014
;--------------------------------------------------------
.module z80tt1
.optsdcc -mz80
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
.globl _main
.globl _send_value
;--------------------------------------------------------
; special function registers
;--------------------------------------------------------
_P55A = 0x0000
_P55B = 0x0001
_P55C = 0x0002
_P55CTL = 0x0003
_CTC_C0 = 0x0010
_CTC_C1 = 0x0011
_CTC_C2 = 0x0012
_CTC_C3 = 0x0013
_SIOAD = 0x0018
_SIOAC = 0x0019
_SIOBD = 0x001a
_SIOBC = 0x001b
_PIO_AD = 0x001c
_PIO_AC = 0x001d
_PIO_BD = 0x001e
_PIO_BC = 0x001f
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
.area _DATA
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
.area _INITIALIZED
;--------------------------------------------------------
; absolute external ram data
;--------------------------------------------------------
.area _DABS (ABS)
;--------------------------------------------------------
; global & static initialisations
;--------------------------------------------------------
.area _HOME
.area _GSINIT
.area _GSFINAL
.area _GSINIT
;--------------------------------------------------------
; Home
;--------------------------------------------------------
.area _HOME
.area _HOME
;--------------------------------------------------------
; code
;--------------------------------------------------------
.area _CODE
;z80tt1.c:33: int main(void)
; ---------------------------------
; Function main
; ---------------------------------
_main_start::
_main:
;z80tt1.c:38: sum = 0 ;
;z80tt1.c:39: for ( i = 0 ; i < LAST ; i++ ) {
ld hl,#0x0000
00102$:
;z80tt1.c:40: sum += i;
ld a,h
add a, l
ld h,a
;z80tt1.c:41: send_value(sum);
push hl
push hl
inc sp
call _send_value
inc sp
pop hl
;z80tt1.c:39: for ( i = 0 ; i < LAST ; i++ ) {
inc l
ld a,l
sub a, #0x0A
jr C,00102$
;z80tt1.c:44: return 0;
ld hl,#0x0000
ret
_main_end::
;z80tt1.c:47: void send_value(UBYTE x)
; ---------------------------------
; Function send_value
; ---------------------------------
_send_value_start::
_send_value:
;z80tt1.c:51: tmp = x ^ 0xff ;
ld hl, #2+0
add hl, sp
ld a, (hl)
xor a, #0xFF
out (_PIO_AD),a
;z80tt1.c:53: PIO_AD = tmp ;
ret
_send_value_end::
.area _CODE
.area _INITIALIZER
.area _CABS (ABS)
int型変数は、hlレジスタペアに入っているので、スタックに
入れて、値渡しをしています。
2回hlレジスタペアの値をスタックにpushするのは
プログラムカウンタの値を入れ、関数(サブルーチン)
から戻るための準備だと解釈できます。
関数内部では、スタックポインタを操作し、引数を
スタックから取り出しています。
ld hl, #2+0
add hl, sp
ld a, (hl)
を見ると、hlレジスタペアをメモリ用ポインタとし
計算で引数のあるアドレスを求めています。
スタックポインタの他に、フレームポインタをもつCPUで
あれば、フレームポインタを使い、フレームの中に値を
入れるようになっています。Z80には、フレームを扱う
メカニズムがないので、スタックを使います。
Z80には、IX、IYというインデクスレジスタがあり
これを使う方が、計算は楽になりますが、1バイト
命令を使い、多少処理が高速になるようにしている
ようです。
メモリ上のどこに、どういう関数をおいたのか等は
シンボルファイルとマップファイルで確認できます。
シンボルファイルの内容は、以下。
ASxxxx Assembler V02.00 + NoICE + SDCC mods (Zilog Z80 / Hitachi HD64180), page 1.
Hexadecimal [16-Bits]
Symbol Table
.__.$$$. = 2710 L
.__.ABS. = 0000 G
.__.CPU. = 0000 L
.__.H$L. = 0000 L
_CTC_C0 = 0010
_CTC_C1 = 0011
_CTC_C2 = 0012
_CTC_C3 = 0013
_P55A = 0000
_P55B = 0001
_P55C = 0002
_P55CTL = 0003
_PIO_AC = 001D
_PIO_AD = 001C
_PIO_BC = 001F
_PIO_BD = 001E
_SIOAC = 0019
_SIOAD = 0018
_SIOBC = 001B
_SIOBD = 001A
0 _main 0000 GR
0 _main_end 0018 GR
0 _main_start 0000 GR
0 _send_value 0018 GR
0 _send_value_end 0022 GR
0 _send_value_start 0018 GR
ASxxxx Assembler V02.00 + NoICE + SDCC mods (Zilog Z80 / Hitachi HD64180), page 2.
Hexadecimal [16-Bits]
Area Table
0 _CODE size 22 flags 0
1 _DATA size 0 flags 0
2 _INITIALIZED size 0 flags 0
3 _DABS size 0 flags 8
4 _HOME size 0 flags 0
5 _GSINIT size 0 flags 0
6 _GSFINAL size 0 flags 0
7 _INITIALIZER size 0 flags 0
8 _CABS size 0 flags 8
関数はmain、send_valueの2種だけで、各関数は
ラベルにstart、endをつけて、開始と終了で構成
したサブルーチンにしていることが読み取れます。
マップファイルで、どこに配置されているのかを
確認できます。
ASxxxx Linker V03.00 + NoICE + sdld, page 1.
Hexadecimal [32-Bits]
Area Addr Size Decimal Bytes (Attributes)
-------------------------------- ---- ---- ------- ----- ------------
. .ABS. 00000000 00000000 = 0. bytes (ABS,CON)
Value Global Global Defined In Module
----- -------------------------------- ------------------------
00000000 .__.ABS. z80tt1
00000000 l__CABS
00000000 l__DABS
00000000 l__DATA
00000000 l__GSFINAL
00000000 l__GSINIT
00000000 l__HOME
00000000 l__INITIALIZED
00000000 l__INITIALIZER
00000000 s__CABS
00000000 s__DABS
00000029 l__CODE
00000100 s__CODE
00008000 s__DATA
00008000 s__GSFINAL
00008000 s__GSINIT
00008000 s__HOME
00008000 s__INITIALIZED
00008000 s__INITIALIZER
ASxxxx Linker V03.00 + NoICE + sdld, page 2.
Hexadecimal [32-Bits]
Area Addr Size Decimal Bytes (Attributes)
-------------------------------- ---- ---- ------- ----- ------------
_CODE 00000100 00000029 = 41. bytes (REL,CON)
Value Global Global Defined In Module
----- -------------------------------- ------------------------
00000107 _main z80tt1
00000107 _main_start z80tt1
0000011F _main_end z80tt1
0000011F _send_value z80tt1
0000011F _send_value_start z80tt1
00000129 _send_value_end z80tt1
ASxxxx Linker V03.00 + NoICE + sdld, page 3.
Files Linked [ module(s) ]
mycrt.o [ ]
z80tt1.rel [ z80tt1 ]
ASxxxx Linker V03.00 + NoICE + sdld, page 4.
User Base Address Definitions
_CODE = 0x0100
_DATA = 0x8000
必要な情報を抽出するには、AWKやPerlを使った方が
よさそうです。自分の記述コードがアセンブリ言語
に変換されたときに、どうなっているのかを見ると
ある程度、コンパイラのクセがわかります。
SDCCのZ80関係では、演算にAレジスタ、そのほかの
処理はHLレジスタペアを多用しています。
8080のもつ特徴を利用したコードになるように感じます。
関数に値を渡す、結果を返してもらうときのカラクリは
次のようになっていると考えてよいでしょう。
関数に値を渡すときは、スタックを利用する。
スタックにデータを積んでいくときにはHLレジスタ
ペアを利用する。
関数に値を渡すのに対して、関数から値を返すとき
次のようになっています。
返値がないときは、アセンブリ言語ではretを利用する。
もし、値を返すときには、HLレジスタペアを利用する。
返値を求める側では、HLレジスタペアの値を取り出せば
よいことになります。
Cでは、関数が値を返すか値を返しても、1個だと仕様で
決めているので、int型を16ビットにします。
例え、char型の8ビット値を返すときでも、16ビットに
拡張して処理します。
関数がポインタを返すときでも、Z80のメモリ空間は16ビット
で扱えるので、HLレジスタペアを使えば、int型、char型と
ポインタまでも賄えます。
高速化のため、関数をアセンブリ言語で記述するときには
このカラクリを知っていると、余分なスタック操作を避け
られます。
2014年にリリースされたSDCCの新しいVersionでは
アドレス割当てのディレイティブが変更になって
います。
次のように指定します。
__sfr __at 0x00 P55A ;
__sfr __at 0x01 P55B ;
__sfr __at 0x02 P55C ;
__sfr __at 0x03 P55CTL ;
__sfr __at 0x10 CTC_C0 ;
__sfr __at 0x11 CTC_C1 ;
__sfr __at 0x12 CTC_C2 ;
__sfr __at 0x13 CTC_C3 ;
__sfr __at 0x18 SIOAD ;
__sfr __at 0x19 SIOAC ;
__sfr __at 0x1a SIOBD ;
__sfr __at 0x1b SIOBC ;
__sfr __at 0x1C PIO_AD ;
__sfr __at 0x1D PIO_AC ;
__sfr __at 0x1E PIO_BD ;
__sfr __at 0x1F PIO_BC ;
また、I/O処理のときに、次のように記述すると
bcレジスタペアを使うようになります。
__sfr __banked __at 0x18 SIOAD ;
SIOAD = 0x12 ; があれば、アセンブリ言語に
展開されると、次のようになります。
ld bc,#0x18
ld a,#0x12
out (c),a
cレジスタにI/Oアドレスが入っているので
bcレジスタペアの内容は、アドレスピンに
アドレス情報として出力されます。
アドレス情報がピンに出ていると、オシロスコープ
やロジックアナライザを使えば、ソフトウエア動作
を測定機器で見ることが可能となります。
目次
前
次