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はいつも通り。
% 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秒のカウントアップがすんごく遅くなるのだ。
というわけで、カウントオーバーフロー割り込みを使うのをやめて、コンペアマッチ割り込み&自動カウンタクリアで、正確な定時割り込みをかけよう。
ついでに、後の利用を考えて、タイマ割り込みの部分だけをticker.cという名前で独立させる。さらについでに、マイクロ秒単位の停止をする関数microwaitをlcd.cからticker.cに移す。
すると、サンプルプログラムは次のようになる。
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;
}
% 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円で売っていたもの。次の簡単な回路を作った。

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;
}
これを動かすと、offcountがどんどんカウントアップしてゆく。
赤外線センサに向けて適当なリモコンを押すと、oncountが数カウント進んだ。
どうやら、センサは反応しているようだ。
では、パルスを測定するプログラムを作ってみよう。
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
などを参照すると詳しくわかるだろう。