H8-300Hのソフト開発をMacOSX上でやろう、の日記

←前へ
インデックス
次へ→


- タイマ割り込み


H8/300Hのタイマを使ってみよう。
とりあえずは、1msec毎に割り込みをかけて、カウンタをインクリメント。
起動時からの経過秒数と、PA0ボタンを押している間の経過時間をLCDに表示させよう。

ソースコードは次の通り。
File: timer1.c
#include "lcd.h"
#include "3052.h"
#include <stdio.h>

#define TIMER0_INTERRUPT_CYCLE 1000 /* usec */
#define BASE_CLOCK 25 /* MHz */

#define TIMER0_COUNT (BASE_CLOCK*TIMER0_INTERRUPT_CYCLE)

void timer0_interrupt() __attribute__((interrupt_handler));

volatile unsigned int timer0count = 0;

void timer0_init()
{
  ITU.TSTR.BIT.STR0 = 0; /* stop timer0 */
  ITU.TSNC.BIT.SYNC0 = 0; /* no sync */
  ITU.TMDR.BIT.PWM0 = 0; /* not PWM mode */
  ITU0.TCR.BYTE = 0x00; /* no auto clear; raise edge; clock 1x */
  ITU0.TIOR.BYTE = 0x00; /* no TIOCA, TIOCB */
  ITU0.TIER.BYTE = 0x04; /* interrupt on overflow */
  SYSCR.BIT.UE = 1; /* use UI bit for user bit */
  ITU0.TCNT = 0xffff-TIMER0_COUNT; /* count down */
  ITU0.TSR.BIT.OVF = 0; /* clear flag */
  ITU.TSTR.BIT.STR0 = 1; /* start timer0 */
}

void timer0_interrupt()
{
  ITU0.TSR.BIT.OVF = 0; /* clear flag */
  ITU0.TCNT = 0xffff-TIMER0_COUNT; /* reset counter */
  timer0count++;
}

int main()
{
  char msg[32];
  unsigned int sec,tm0,tm1;
  unsigned char pa,pa0;

  PA.DDR = 0x00;
  PB.DDR = 0xff;
  PB.DR.BYTE = 0xff;

  LCDInit();
  LCDDisplay("TIMER TEST",0);
  timer0_init();
  sec = tm0 = tm1 = 0;
  pa0 = 0x01;
  while(1){
    if(timer0count-tm0 >= 1000000/TIMER0_INTERRUPT_CYCLE){
      tm0 = timer0count;
      sprintf(msg,"TIMER %4dsec",++sec);
      LCDDisplay(msg,0);
    }
    pa = PA.DR.BYTE;
    if(pa^pa0){ /* detect change */
      if(pa&0x01){ /* off */
        sprintf(msg,"%d usec",(int)(timer0count-tm1)*TIMER0_INTERRUPT_CYCLE);
        LCDDisplay(msg,1);
      }else{ /* on */
        tm1 = timer0count;
      }
      pa0 = pa;
    }
  }
  return 0;
}


割り込みベクタを登録する関係で、crt0.Sもちょっとだけ書き換えた。
File: crt0.S
; H8/300H start up file.
; Customized for H8/3052 on AKI-LAN target

        .h8300h
        
        .equ	SYSCR,	0xFFFFF2
   	.equ	BRCR,	0xFFFFF3
	.equ	P1DDR,	0xFFFFC0
	.equ	P2DDR,	0xFFFFC1
	.equ	P1DR,	0xFFFFC2
	.equ	P2DR,	0xFFFFC3
	.equ	P3DDR,	0xFFFFC4
	.equ	P4DDR,	0xFFFFC5
	.equ	P3DR,	0xFFFFC6
	.equ	P4DR,	0xFFFFC7
	.equ	P5DDR,	0xFFFFC8
	.equ	P6DDR,	0xFFFFC9
	.equ	P5DR,	0xFFFFCA
	.equ	P6DR,	0xFFFFCB
	.equ	P7DDR,	0xFFFFCC
	.equ	P8DDR,	0xFFFFCD
	.equ	P7DR,	0xFFFFCE
	.equ	P8DR,	0xFFFFCF
	.equ	P9DDR,	0xFFFFD0
	.equ	PADDR,	0xFFFFD1
	.equ	P9DR,	0xFFFFD2
	.equ	PADR,	0xFFFFD3
	.equ	PBDDR,	0xFFFFD4
	.equ	PBDR,	0xFFFFD6
	.equ	ADCR,	0xFFFFE9

	.section .text
	.global	_start
_start:
        ;; Setup BUS
        mov.b   #0x03,r0l
        mov.b   r0l,@SYSCR:8
        mov.b   #0xfe,r0l
        mov.b   r0l,@BRCR:8
        mov.b   #0xff,r0l
        mov.b   r0l,@P1DDR:8
        mov.b   r0l,@P2DDR:8
        mov.b   r0l,@P5DDR:8
        mov.b   #0xf0,r0l
        mov.b   r0l,@PADDR:8
        mov.b   #0xff,r0l
        mov.b   r0l,@PBDDR:8
        mov.b   #0xfe,r0l
        mov.b   r0l,@P8DDR:8
        mov.b   r0l,@P8DR:8

        ;; Setup stack
	mov.l	#_stack,sp
        ;; Copy data section
	mov.l	#_data_start,er0
	mov.l	#_data_end,er1
        mov.l   #_text_end,er2
.Ldata:	cmp.l   er1,er0
        beq     .Ldataend
        mov.w	@er2,r3
        mov.w   r3,@er0
        adds    #2,er2
	adds	#2,er0
	bra	.Ldata
.Ldataend:
        ;; Clear bss section
        mov.l	#_bss_start,er0
	mov.l	#_bss_end,er1
	sub.w   r3,r3
.Lbss:	cmp.l   er0,er1
        beq     .Lbssend
        mov.w	r3,@er0
	adds	#2,er0
	bra     .Lbss
.Lbssend:
        ;; Go to the main
.Lmain:
        ldc     #0:8,ccr
	jsr	@_main
        bra     .Lmain

        .section .vectors
        .org    0               ; RESET
        .long   _start
        .org    0x0068          ; ITU0-OVF
        .long   _timer0_interrupt
        


Makefileはいつも通り。
Makefile を見る


% make
% h8load timer1.mot

LCDには1秒ごとに経過秒数がちゃんと表示されてゆく。
PA0ボタンを押して離すと、押していた間の時間をマイクロ秒単位で表示する。
動作OK。

.....実は、ここにたどり着くまでに、2つほど落とし穴に落ちたのである。

1つ目は、CPUの割り込み許可フラグ。今まで割り込みを全く使わなかったので気づかなかったが、以前のcrt0.Sは割り込み許可フラグを何もいじらなかったため、割り込み不許可のままずっと使っていたのであった。
crt0.Sをよく見るとわかるが、main関数を呼ぶ直前でCCRレジスタをクリアするコードを加えた。

2つ目は、割り込み要因の開放の仕方である。1つ目の問題を解決したら、今度は割り込みがかかりっぱなしになって動作がおかしくなってしまった。
H8のITU(タイマユニット)からの割り込みはレベルトリガで、自動開放機能はない。
そこで、最初、

  ITU0.TSR.BYTE = 0; /* clear flags */

としたのだが、割り込みかかりっぱなし状態は改善されなかった。
んで、3052のマニュアルをよーく読んでみたら、「10.2.12: タイマステータスレジスタ」のOVFのクリア条件の欄に、次のように書いてあるではないか。

[クリア条件] OVF=1の状態で、OVFフラグをリードした後、OVFフラグに0をライトしたとき

げげー。書いただけじゃダメなんだ。
というわけで、

  ITU0.TSR.BIT.OVF = 0; /* clear flag */

に書き換えて無事動作した。timer1.s(アセンブラコード)を出力させて確かめるとよくわかるが、bitアクセス時は読んで書くコードに展開されるため、これでOKなのである。

しかし、このプログラムにはまだまだ根本的な問題がある。
計測精度を上げようと思って、"TIMER0_INTERRUPT_CYCLE"を50にしてみたら、問題が明らかになった。(いや、最初からわかってはいたんですが。^_^;;)
割り込みが発生してからタイマをリセットするまでの遅延時間が、タイマ割り込み間隔に比べて無視できない大きさになるため、1秒のカウントアップがすんごく遅くなるのだ。


- タイマ割り込み2


というわけで、カウントオーバーフロー割り込みを使うのをやめて、コンペアマッチ割り込み&自動カウンタクリアで、正確な定時割り込みをかけよう。
ついでに、後の利用を考えて、タイマ割り込みの部分だけをticker.cという名前で独立させる。さらについでに、マイクロ秒単位の停止をする関数microwaitをlcd.cからticker.cに移す。

ticker.c を見る

ticker.h を見る

lcd.c を見る

lcd.h を見る

crt0.S を見る


すると、サンプルプログラムは次のようになる。

File: timer2.c
#include "lcd.h"
#include "ticker.h"
#include "3052.h"
#include <stdio.h>

int main()
{
  char msg[32];
  unsigned int sec,tm0,tm1;
  unsigned char pa,pa0;

  PA.DDR = 0x00;
  PB.DDR = 0xff;
  PB.DR.BYTE = 0xff;

  TickerInit();
  LCDInit();
  LCDDisplay("TIMER TEST",0);
  sec = tm0 = tm1 = 0;
  pa0 = 0x01;
  while(1){
    if(tick-tm0 >= 1000000/TICK){
      tm0 = tick;
      sprintf(msg,"TIMER %4dsec",++sec);
      LCDDisplay(msg,0);
    }
    pa = PA.DR.BYTE;
    if(pa^pa0){ /* detect change */
      if(pa&0x01){ /* off */
        sprintf(msg,"%d usec",(int)(tick-tm1)*TICK);
        LCDDisplay(msg,1);
      }else{ /* on */
        tm1 = tick;
      }
      pa0 = pa;
    }
  }
  return 0;
}

Makefile を見る


% make;h8load timer2.mot

はい。OKです。今度は割り込み間隔を50マイクロ秒にしても大丈夫だった。

さて、割り込み間隔はいったいどれくらいまで縮めることができるだろうか?
ticker.hの定数を小さくしていって実験したら、30マイクロ秒はオッケーだったが25マイクロ秒では全く動作しなくなった。(実験する時にはmake cleanをお忘れなく。)

H8/3052の命令実行は、内部メモリ上で実行された時の最小で2クロック、大抵は十数クロックを必要とする。これが、今回のように外部メモリで3ステートアクセスで8ビットアクセスという悪条件になると、最小でも6クロック、大抵は40クロック程度かかってしまう。(これだからCISCは、、、、)
さらに、H8/3052の説明書の「5.4.3 割り込み応答時間」によると、この悪条件下では割り込み処理が始まるまで最悪73クロックかかることになっている。

AKI-LANは25MHzクロックだから、1マイクロ秒で25クロックである。例えば、20マイクロ秒毎に割り込みをかけたとすると、500クロックの時間ということになる。
割り込み処理に使える時間は、500-73=427クロック。1命令の平均クロック数が40ちょっととすると、10命令程度しか実行できない。
ticker.cをアセンブルコードにしてみたら、インタラプト処理部は次のようになっていた。

_timer0_interrupt:
    .def  .bf
    .val  .
    .scl  101
    .line  40
    .endef
    mov.l  er6,@-er7
    mov.l  er7,er6
    mov.l  er2,@-er7
    .ln  2
    mov.b  @16777063:8,r2l
    and  #-2,r2l
    mov.b  r2l,@16777063:8
    .ln  3
    mov.l  @_tick,er2
    adds  #1,er2
    mov.l  er2,@_tick
    .ln  4
    mov.l  @er7+,er2
    mov.l  @er7+,er6
    rte

12命令。しかも時間がかかるロングワードの命令がたくさん。
これでは、割り込み周期25マイクロ秒では実行できなかったのもうなずける。


- 赤外線センサ


ちょっと実用的なことをやってみたいので、赤外線センサを取り付けてみよう。
AKI-LANには、PORT-Aの5,6、PORT-Bの5,6が取り出しやすいようになっているので、ここに赤外線センサをつける。
センサはPL-IRM0208-A538という秋月で100円で売っていたもの。次の簡単な回路を作った。
Figure:image/irsensor.png
AKI-LANのPA5端子が10kΩでプルダウンされているのでコレクタの抵抗をずいぶん小さくしたが、本当はプルダウン抵抗を外してコレクタ抵抗は100kΩくらいにした方がよいだろう。

さて、センサ信号が読めているかのテストプログラム。
File: irtest1.c
#include "ticker.h"
#include "lcd.h"
#include "3052.h"
#include <stdio.h>

int main()
{
  char pa;
  int oncount, offcount;
  char msg[32];

  PA.DDR = 0x00;
  PB.DDR = 0xff;
  PB.DR.BYTE = 0xff;

  TickerInit();
  LCDInit();
  LCDDisplay("iR TEST",0);
  oncount = offcount = 0;
  while(1){
    pa = PA.DR.BYTE;
    if(pa&0x20){
      if(++oncount >= 10000)
        oncount = 0;
    }else{
      if(++offcount >= 10000)
        offcount = 0;
    }
    sprintf(msg,"%4d,%4d",oncount,offcount);
    LCDDisplay(msg,1);
  }
  return 0;
}

Makefile を見る


これを動かすと、offcountがどんどんカウントアップしてゆく。
赤外線センサに向けて適当なリモコンを押すと、oncountが数カウント進んだ。
どうやら、センサは反応しているようだ。

では、パルスを測定するプログラムを作ってみよう。
irtest2.c を見る

Makefileはirtest1のものとほぼ同じ("irtest1"を"irtest2"に書き換えただけ)なので割愛する。

ようやく、プログラムらしいプログラムになってきた。
動作は次のようなものである。

+ PA3ボタンを押すと、"Waiting..."と表示して赤外線センサに信号が入ってくるのを待つ。
+ 信号を読み取ると、何パルスあったかと、1パルス目のON時間とOFF時間を表示する。
+ PA1ボタンを押すと、次のパルスのON時間とOFF時間を表示する。
+ PA2ボタンを押すと、前のパルスのON時間とOFF時間を表示する。

ソースコードを見ればわかるが、PA3ボタンを押してから5秒以上パルスが始まらなければ、"Timeout"と表示する。
また、次のパルスが10msec以内に始まらなければ、信号の終わりと見なしている。

手近にあるいろんなリモコンでためしてみたら、おおむね30〜50パルス程度を出しているようだ。SHARPのテレビのリモコンが一番短くて15パルス、MITSUBISHIのエアコンが一番長くて114パルスもあった。
リモコンのパルスのフォーマットについては、
http://www.256byte.com/remocon.htm
などを参照すると詳しくわかるだろう。


←前へ
インデックス
次へ→
Last updated: 2005/4/24 14:14
Copyright (C) 2005 by SHIBUYA K.
All Rights Reserved.