目次

ライントレーサ処理

 専門学校で講師をしているとき、知人からZ80を
 利用したライントレーサがあるので、動かして
 欲しいと依頼がありました。



 現在は、販売されていませんが、Z80を使った
 組込みシステムが全盛の頃で、相撲ロボットに
 このハードウエアを参考にしたものが多数あり
 ました。

 今、手元にはTAMIYAのTwin Gear Boxを利用した
 メカがあります。これに、Z80のコアをもつUECを
 載せて、動かしてみます。



 手元にあるUECとして、SRAMが32kバイトの製品で
 外国人の研修で利用していました。



 ライントレーサを実現するには、以下のI/Oが必要です。

 それぞれのI/Oは、どう利用するのかを簡単に見てみます。

 PWM波形生成器

  DCモータは、PWM波形のDUTY比を可変して
  回転を制御します。DCモータの平均電力を
  パルスの幅で制御するので、よく使われて
  います。

  PWM波形のDUTY比を制御するには、タイマー
  割込みを使います。

 パラレル入力

  パラレル入力は、ラインセンサーからの入力と
  スイッチ類の入力に使います。

  ラインセンサーは、フォトインタラプタを使い
  コース上に密着させる基板で実現します。

  (写真で、シャーシ前にあるのがラインセンサー)



  スイッチは、スタート、ストップの他、リセット等
  の緊急停止に使うため必要になります。

 パラレル出力

  パラレル出力は、ラインセンサーのデータモニタや
  内部状態表示に使います。情報量が多い状態表示に
  する場合、LCDを使うこともあります。

 利用するZ80基板には、タイマーカウンタ、パラレル入出力は
 用意されていますが、パラレル入出力は2ポートだけです。
 そこで、I/Oアドレスにデジタルパラレル出力として74LS374を
 接続します。

 Z80CPUチップ上に用意された、既存周辺I/Oのアドレスと
 バッティングしないように、20hから2バイトを使います。

 アドレスデコーダを含めた回路は、次のようにします。



 I/Oに対する書き込みは、nIOWRが担当しているので
 アドレスをA0〜A7から生成し、出来上がった負論理
 のアクセス信号との論理積をとります。
 A6、A7は、ダイオードによるAND回路を使いました。

 ラインセンサー、スイッチ類の入力は、PIOを使います。

 I/Oは、次のように割り当てます。

 表示器には、2行x16桁のLCDを使います。

 I/Oの仕様を決めたので、I/O操作のコードを作成します。

 ラインセンサー入力

  ラインセンサーは、デジタルで8ビットの
  データを出力する専用品を使います。



  単純なデジタルデータ入力なので、PIOAを
  モード1で使います。

  入力設定は、以下。

PIOAC	EQU	01dh

	ld	c,PIOAC
	ld	a,1fh
	out	(c),a

  センサーデータは、1バイトの変数領域を
  宣言して、そこに値を転送します。

PIOAD	EQU	01ch

GET_SENSOR:
	push	af
	push	bc
	;
	ld	c,PIOAD
	in	a,(c)
	;
	ld	(SENSOR),a
	;
	pop	bc
	pop	af
	ret

SENSOR:	defs	1

 スイッチ入力

  スイッチは、start、stop、resetの3ビットにします。

  3つのスイッチだけでなく、後で拡張できるように
  8ビットのスイッチを扱えるようにします。

  PIOBにスイッチを接続するとして、PIOBを入力に
  設定します。これはモード1を使うことになります。

PIOBC	EQU	01fh

	ld	c,PIOBC
	ld	a,1fh
	out	(c),a

  スイッチには、チャタリングがつきものなので
  チャタリングを除去することを考えます。

  シフトレジスタを用意し、タイマー割込みで10ms周期で
  各ビットの値を入力して、シフトレジスタのLSBに格納
  していきます。タイマー割込みで、起動されるハンドラ
  を記述します。

PIOBD	EQU	01eh

GETSW:
	push	af
	push	bc
	push	hl
	;
	ld	c,PIOBD
	in	a,(c)
	ld	b,a
	; shift
	ld	hl,swstart
	ld	a,(hl)
	sla	a
	and	07h
	ld	(hl),a
	;
	ld	hl,swstop
	ld	a,(hl)
	sla	a
	and	07h
	ld	(hl),a
	;
	ld	hl,swreset
	ld	a,(hl)
	sla	a
	and	07h
	ld	(hl),a
	; store bit datum
GETSW0:
	ld	a,b
	;
	bit	0,a
	jr	z,GETSW1
GETSW01:
	ld	hl,swstart
	inc	(hl)
GETSW1:
	bit	1,a
	jr	z,GETSW2
GETSW11:
	ld	hl,swstop
	inc	(hl)
GETSW2:
	bit	2,a
	jr	z,GETSW3
GETSW21:
	ld	hl,swreset
	inc	(hl)
	; judge
GETSW3:
	ld	a,(swstart)
	cp	3
	jr	z,GETSW4
	cp	1
	jr	z,GETSW4
	jr	GETSW5
GETSW4:
	ld	a,1
	ld	(startflag),a
	jr	GETSWEND
GETSW5:
	ld	a,(swstop)
	cp	3
	jr	z,GETSW6
	cp	1
	jr	z,GETSW6
	jr	GETSW7
GETSW6:
	ld	a,1
	ld	(stopflag),a
	jr	GETSWEND
GETSW7:
	ld	a,(swreset)
	cp	3
	jr	z,GETSW8
	cp	1
	jr	z,GETSW8
	jr	GETSWEND
GETSW8:
	ld	a,1
	ld	(resetflag),a
	;
GETSWEND:
	;
	pop	hl
	pop	bc
	pop	af
	;
	reti

swstart:	defs	1
swstop:		defs	1
swreset:	defs	1
startflag:	defs	1
stopflag:	defs	1
resetflag:	defs	1

  該当のシフトレジスタが、2進で011か001で
  あれば、フラグを設定します。

  タイマー割込みハンドラとしたいので
  retiを利用して、通常処理に戻ります。

 モータ出力

  PWM波形を出力するのは、非常に単純です。

  0〜99の状態の中で、1が連続して何度出力
  されるかを確定します。

  Cで擬似コーディングすると、次のようになります。

      portx = 0 ;
      /* generate */
      if ( pcnt < ldutyx ) { portx |= 2 ; }
      if ( pcnt < rdutyx ) { portx |= 1 ; }
      /* impress */
      XPORT = portx ;
      /* update counter */
      pcnt++ ;
      /* judge */
      if ( pcnt == 100 ) {
        pcnt = 0 ;
        ldutyx = lduty ;
        rdutyx = rduty ;
      }

  変数pcntをカウンタに使い、ldutyx、rdutyxの値と
  比較後、出力値を確定してportxに格納します。
  portxの値を、I/Oに転送すると、パルスが出力されます。

  変数pcntの値を、タイマー割込みで更新していくと
  DUTY比に相当するパルスが出てきます。

  Cのコードを、アセンブリ言語で記述し直します。

MOTOR	EQU	20h

PWMGEN:
	push	af
	push	de
	push	hl
PWMGEN0:
	ld	hl,portx
	ld	d,h
	ld	e,l
	xor	a
	ld	(hl),a
	;
	ld	a,(pcnt)
	ld	hl,ldutyx
	cp	(hl)
	jr	nc,PWMGEN1
	ld	h,d
	ld	l,e
	set	0,(hl)
	ld	hl,rdutyx
	cp	(hl)
	jr	nc,PWMGEN1
	ld	h,d
	ld	l,e
	set	1,(hl)
PWMGEN1:
	ld	hl,portx
	ld	a,(hl)
	out	(MOTOR),a
PWMGEN2:
	ld	a,(pcnt)
	inc	a
	cp	100
	jr	nz,PWMGEN3
	ld	a,(lduty)
	ld	(ldutyx),a
	ld	a,(rduty)
	ld	(rdutyx),a
	xor	a
PWMGEN3:
	ld	(pcnt),a
PWMGENE:
	;
	pop	hl
	pop	de
	pop	af
	;
	reti

pcnt:	defs	1
lduty:	defs	1
ldutyx:	defs	1
rduty:	defs	1
rdutyx:	defs	1
portx:	defs	1

  DUTY比を変更する場合、lduty、rdutyの値を
  使い、ldutyx、rdutyxの値設定は、割込み
  ハンドラに任せます。出力されるPWM波形が
  乱れないようにするためです。

 表示器出力

  表示器には、1桁の7セグメントLEDを利用します。



  7セグメントLEDが表示できるのは、0〜9の数字
  だけなので、数値に状態を割当てます。

  数値と状態の対応は、次のようにします。

  パラレルI/Oには、8ビットレジスタを接続している
  ので表示したい数値をZ80のAレジスタに入れ、サブ
  ルーチンを呼ぶと、表示されるようにします。

SEGLED	EQU	21h

SHOWS:
	push	af
	push	de
	push	hl
SHOWS0:
	; calculate location
	ld	hl,SHOWT
	ld	d,0
	ld	e,a
	add	hl,de
SHOWS1:
	; get bit pattern
	ld	a,(hl)
	out	(SEGLED),a
SHOWSE:
	;
	pop	hl
	pop	de
	pop	af
	;
	ret

SHOWT:	db	0c0h	; 0
	db	0f9h	; 1
	db	0a4h	; 2
	db	0b0h	; 3
	db	099h	; 4
	db	092h	; 5
	db	082h	; 6
	db	0d8h	; 7
	db	080h	; 8
	db	090h	; 9

  センサー情報のモニタには、8個のLEDを
  接続して対応します。



  この基板用のサブルーチンを定義します。

MONS:
	push	af
	; inverse
	cpl
	; show
	out	(SEGLED),a
	;
	pop	af
	;
	ret

  センサーの8ビット情報をZ80のAレジスタに
  入れて、サブルーチンをコール。

 下準備ができたので、動かすための構成を考えます。

 ライントレーサには、スイッチとセンサーが接続される
 ので、2種類のイベントにより各サブルーチンが呼出さ
 れる構成でファームウエアを記述。

 RTOS(Real Time Operating System)を利用して
 mainでは、タスクのディスパッチ、スケジューリング
 ウエイトカウンタを制御することに。

int main(void)
{
  TCBP  pcur_tsk ;
  /* initialize */
  init_usr() ;
  /* initialize task */
  init_os();
  /* endless loop */
  run_tsk = TSK_ID0 ;
  while ( ON ) {
    /* RTOS handling */
    pcur_tsk = tcb[run_tsk] ;
    if ( is_tsk_ready( run_tsk ) == YES ) { (*(pcur_tsk.tsk))(); }
    run_tsk++;
    if ( run_tsk == TSK_ID_MAX ) { run_tsk = TSK_ID0 ; }
    /* 10ms counter */
    if ( wflag == ON ) {
      wflag = OFF ;
      timer_handler();
    }
  }
  /* dummy */
  return 0;
}

 RTOSを導入すると、路面状況に対応した走行は
 ひとつのタスク処理の中に埋め込むことが可能
 になります。

 今回、8タスクに処理を振り分けました。

(under construction)

目次

inserted by FC2 system