PICのDelayの使い方について

〔PICの動かせ方入門に戻る〕


マイコンのプログラムを記述していると如何しても遅延を行いたい場合が有ります、
こんな時には便利なdelay( )関数を利用します。(あ、記事はXC8ですよ念の為)

通常は、__delay_ms( )/__delay_us( )マクロを使うのですが、
これを使うには_XTAL_FREQに使用するシステム周波数(下は8MHzの例)を指定しないとダメです。
#include <xc.h>

#define _XTAL_FREQ 8000000    // 使用するPIC等により動作周波数値を設定する

main (void)
{
     __delay_us(100) ;        // 100us遅延する
     __delay_ms(500) ;        // 500ms遅延する
}
こんな感じです。

__delay_ms( )/__delay_us( )は関数でなくマクロですので、"pic.h/pic18.h"のヘッダファイル内に
下記の様に記述されています。
#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))
#define __delay_ms(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000.0)))
__delay(x)はインライン関数です、数値(x)の指定は定数で変数指定は出来ません
指定する数値は"命令サイクル数"でシステム周波数/4(Fcyc)です、即ちシステム周波数の4クロック分
が1命令サイクルと言う事になります。
なので、システム周波数を8MHz(1サイクル=500ns)とした場合は、
 __delay_ms(100) ;
 __delay_us(100000) ;
 _delay(200000) ;     // (100)*(8000000/4000) 又は (100000)*(8000000/4000000)
これはすべて同じ長さ100ms(200000*500ns)待ちます。
__delay( )はインライン関数なのでコンパイラ(XC8)が、サイクル数に見合った長さだけの命令コード数を
計算し、インラインアセンブリ遅延ルーチンに展開します。
例えばこんな感じです、1命令辺り1〜3サイクル消費します。
;test.c: 88: _delay((unsigned long)((500)*(8000000/4000.0))) ;
	opt asmopt_off
movlw  6
	movlb 0	; select bank0
movwf	((??_main+0)+0+2),f
movlw	19
movwf	((??_main+0)+0+1),f
	movlw	177
movwf	((??_main+0)+0),f
u1517:
	decfsz	((??_main+0)+0),f
	goto	u1517
	decfsz	((??_main+0)+0+1),f
	goto	u1517
	decfsz	((??_main+0)+0+2),f
	goto	u1517
	nop2
opt asmopt_on
ですので、Delayは正確に動作しますが、指定は定数のみ変数不可と言う事です。
変数が使えれば凄く便利なのですが...

PIC18系の場合で、_delaywdt/__delaywdt_ms/__delaywdt_us が有りますがこれは、
CLRWDT()のウオッチドッグをクリアする命令が含まれているみたいです。

なら定数は何処まで指定できるのぉ?

__delay_ms/__delay_usに指定する数値定数には制限が有ります
下記表は"HI-TECH C Compiler Ver9.80"以降のコンパイラでの有効最大数値です、
また、PIC12/16系のみ該当します。

4MHz 8MHz 16MHz 32MHz 64MHz
__delay_us 50462464 25231232 12615616 6307808 3153904
__delay_ms 50462 25231 12615 6307 3153
_delay 50462464 50462464 50462464 50462464 50462464

こちらはPIC18系です、有効最大数値が少ないですね。
4MHz 8MHz 16MHz 32MHz 48MHz 64MHz
__delay_us 197120 98560 49280 24640 16426 12320
__delay_ms 197 98 49 24 16 12
_delay 197120 197120 197120 197120 197120 197120

尚、制限数値以上を記述するとコンパイルエラーが出ます。

制限数値以上遅延させるには如何するのぉ?

そうですね、例えばPIC18系で200ms以上の遅延をさせるには、
// 指定した時間(num x 10ms)だけウエイトを行う処理関数 *1)
void Wait(int num)
{
     // numで指定した回数だけ繰り返す
     do {
          __delay_ms(10) ;    // 10msプログラムの一時停止
     } while(--num > 0);
}
って感じでループさせれば良いのですがぁ、__delay_ms(10)は時間は正確です、
でも、ループする為の命令コードが必要になるのでその分だけループする毎に数サイクル加算
されます、なのでそれでも良ければ上のやり方が簡単です。

PIC Delay Code Generator

ある日ある時、ネットをさまよっていたらこんなサイトを見つけました。

Delay Code Generator

パラメータを設定するだけで、Delayするアセンブラコードを作成してくれます。
これを利用すれば上の様にループさせなくても制限値以上遅延させられます。

上の図は、システム周波数8MHzで1秒遅延の設定です。
・"Temporary registers names"はデフォルトでそのままです。
・"Generate routine"はチェックしましょう、関数のルーチンコードとして作成されます。
パラメータを設定したら[Generate code!]ボタンをクリックします。
こんな感じで作成されています。
; Delay = 1 seconds
; Clock frequency = 8 MHz

; Actual delay = 1 seconds = 2000000 cycles
; Error = 0 %

	cblock
	d1
	d2
	d3
	endc

Delay
			;1999996 cycles
	movlw	0x11
	movwf	d1
	movlw	0x5D
	movwf	d2
	movlw	0x05
	movwf	d3
Delay_0
	decfsz	d1, f
	goto	$+2
	decfsz	d2, f
	goto	$+2
	decfsz	d3, f
	goto	Delay_0

			;4 cycles (including call)
	return

; Generated by http://www.piclist.com/cgi-bin/delay.exe (December 7, 2005 version)
 ; Wed Feb 11 09:33:48 2015 GMT

; See also various delay routines at  http://www.piclist.com/techref/microchip/delays.htm 
このルーチンは関数になっているので、CALL/RETURN命令で4サイクル消費するから
2000000cycles - 4cycles = 1999996cycles分のステップが実行されます。
このルーチンをXC8でコンパイル出来る様に、インラインアセンブラに記述(下記)し直します。
インラインアセンブラは[#asm][#endasm]の間に直接アセンブラ言語を記述する方法です。
#include <xc.h>

volatile persistent char d1,d2,d3 ; // 変数名は変えてもOK

void Delay1s(void)
{
#asm
			;1999996 cycles
	movlw	0x11
	movwf	_d1
	movlw	0x5D
	movwf	_d2
	movlw	0x05
	movwf	_d3
Delay_0:
	decfsz	_d1, f
	goto	$+2
	decfsz	_d2, f
	goto	$+2
	decfsz	_d3, f
	goto	Delay_0

			;4 cycles (including call)
#endasm
}
main()
{
     while(1) {
          RA0 = 1 ;
          Delay1s() ;
          RA0 = 0 ;
          Delay1s() ;
     }
}
こんな感じです、アセンブラ内で変数名を書く場合は変数の前に_(アンダーバー)を付けます。
それと、"Delay_0"の後ろに(コロン)をつけます。
これをMPLAB Xにて記述すると赤い下線が出てきますが使用出来るのでOKです、がまんしましょう。
XC8の場合は、asm("movlw 0x11"); の様に記述するらしいが書き換えるの面倒なのでぇ....
但しこの操作方法はPIC12/16系のみ(PIC10系は未実験)です。

PIC18系で動作させて見る

上記ツールでシステム周波数8MHzで0.5s(500ms)遅延のパラメータ設定で作成した内容が下記です。
; Delay = 0.5 seconds
; Clock frequency = 8 MHz

; Actual delay = 0.5 seconds = 1000000 cycles
; Error = 0 %

	cblock
	d1
	d2
	d3
	endc

Delay
			;999990 cycles
	movlw	0x07
	movwf	d1
	movlw	0x2F
	movwf	d2
	movlw	0x03
	movwf	d3
Delay_0
	decfsz	d1, f
	goto	$+2
	decfsz	d2, f
	goto	$+2
	decfsz	d3, f
	goto	Delay_0

			;6 cycles
	goto	$+1
	goto	$+1
	goto	$+1

			;4 cycles (including call)
	return
これをインラインアセンブラ用に書き換えます。
void Delay500ms()
{
#asm
			;999990 cycles
	movlw	0x07
	movwf	_d1,0
	movlw	0x2F
	movwf	_d2,0
	movlw	0x03
	movwf	_d3,0
Delay_0:
	decfsz	_d1,1
	goto $+6
	decfsz	_d2,1
	goto $+6
	decfsz	_d3,1
        goto	Delay_0

			;6 cycles
	goto	$+4
	goto	$+4
	goto	$+4
			;4 cycles (including call)
#endasm
}
PIC18系で動作させるには赤文字の様に書き換えます、goto命令は2ワードなので飛び先も変更です。
但し、PIC18系の場合はdecfsz命令が0になった場合に1サイクル増えるのでその分長くなります、
上をMPLAB X:シミュレータの[stopwatch]機能でサイクル数をカウントさせて見ました、
"999990 cycles"のはずが"1000553 cycles"と表示されました。
下で紹介のツールも同じです、如何もPIC18系での計算が考慮されていないようですね。
(ツールにPICの種別選択も追加して欲しい....、でないと計算していると頭がループしてしまう)


注意1)
 CALL/RETURN命令は其々2サイクル消費だが、3サイクル消費の場合が有るような感じぃ。
注意2)
 Delay1s( )/Delay500ms( )の様に2個以上作成する場合、ループ名の"Delay_0"を
 "Delay_0"と"Delay_1"の様に名前を変更しないとダメです。

PicLoops:Delay Loop Calculator

こんなツールも有りますと紹介のみして置きましょう。
上のツールとループの仕方が若干異なる様ですね、下は8MHzで500msに設定した場合の図です。

PicLoops

こちらのサイトからダウンロードしてインストールすれば使用できます。



プログラム記述変更(*1) 2015/03/08


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2015 Shigehiro Kimura All Rights Reserved.