目次

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命令に
 変換されていることを確認できました。

 生成されたアセンブリ言語コードから読み取れる
 内容をリストしてみます。

 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レジスタペアの内容は、アドレスピンに
 アドレス情報として出力されます。

 アドレス情報がピンに出ていると、オシロスコープ
 やロジックアナライザを使えば、ソフトウエア動作
 を測定機器で見ることが可能となります。


目次

inserted by FC2 system