PIC18F4550を使って割込み処理の実験
2017/05/10


VC++にもちょっと慣れてきたので、もう一段進めてみようかと思ったら、なんだかおかしい。
以前作ったPICのプログラムでエラーが出るのである。
見ると、最新のXC8(V1.41)では、どうやら割り込み処理の書き方が違うらしい。

アチコチ調べてようやく動くようになったのだが、ついでにPICの割り込み処置がどうなっているのか見てみることにする。



まず、以前作ったプログラムがエラーになる部分がここである。

//***** 割込みベクタジャンプ命令セット

#pragma code isrcode = 0x8
void isr_direct(void)
{
    _asm
    goto isr
    _endasm
}
#pragma code lowcode = 0x18
void low_direct(void)
{   _asm
    goto tmr1
    _endasm
}
この_asmとか_endasmというところでエラーになり、どうやら新しいコンパイラでは、こういう書き方はしないらしい。

この部分はC言語の中で臨時にアセンブラーを書けるようにする指示になっており、特に重要なのがその前の0x8とか0x18という指示で、PICマイコンの絶対アドレスの指定なのである。
8番地と18番地が割り込みの飛び先ベクトルになっているのである。



調べると、新しいXC8の割り込み処理は以下のような書き方をするらしい。

作った割込み処理は、タイマー0を高いプライオリティで定義し、100ミリ秒毎に1ミリ秒だけLED1を点灯させている。
タイマー1は低プライオリティーで定義し、40ミリ秒毎に1ミリ秒だけLED2を点灯させている。

#include < stdio.h >
#include < stdlib.h >
#include < xc.h >
//
//  割り込み関数
//
void interrupt high_priority isr1(void)   // 高プライオリティ割り込み関数
{
    int i;
    int j;
    
    if (INTCONbits.TMR0IF) {
        INTCONbits.TMR0IF = 0;

// タイマーはFosc/4で動き、T0CONのプリスケーラーで1/256にしているので
// 48000000/(4*256)->46875
// 16ビットのカウンター1につき1/46875->21.33μ秒
// 必要な時間 20ms とすると
// 0.02/(1/46875)->937.5
// 65536-938->64598->FC56
// 0だと、21.333*65536->1.398秒
// 100ms 0.1*46875->4688->65536-4688->60848->0xEDB0->237+176
        TMR0L = 176;                 // timer0 count = 0
        TMR0H = 237;                 // timer0 count = 0  

        LATDbits.LD1 = 1;
        for(i=0;i<1;i++) {       // 1mS*1 1ms
            for(j=0;j<1000;j++) {  //1ms
                
            }
        }         
        LATDbits.LD1 = 0;
        
    }
}                  


void interrupt low_priority isr2(void)   // 低プライオリティ割り込み関数
{
    int i;
    int j;
    
    if( PIR1bits.TMR1IF )
	{            // タイマ1割り込み?
        PIR1bits.TMR1IF = 0;        // TMR1 register did not overflow

// タイマーはFosc/4で動き、T1CONのプリスケーラーで1/8にしているので
// 48000000/(4*8)->1500000
// 16ビットのカウンター1につき1/1500000->0.66666μ秒
// 必要な時間 40ms とすると
// 0.04/(1/1500000)->60000
// 65536-60000->5536->0x15A0
        TMR1L = 160;                // timer1 count = 0
        TMR1H = 21;                 // timer1 count = 22 40ms

        LATDbits.LD2 = 1;
        for(i=0;i<1;i++) {       // 1mS*1  1ms
            for(j=0;j<1000;j++) {  //1ms
                
            }
        }         
        LATDbits.LD2 = 0;
    }
}           
以前はまずgoto命令だけを書いておいて、実際の処理は飛び先で記述した。

それが、今回からは直接普通の関数のように処理を渡してくれるようになったのである。



さて、割り込みだけのテストをやってみるのだが、タイマーを二つTimer0とTimer1を使う。

まず、タイマーのヘッダーファイル2つ
ただのプロトタイプ宣言をしているだけである。

/* 
 * File:   Timer0
 * Author: 
 * Comments:
 * Revision history: 
 */

#include < xc.h > // include processor files - each processor file is guarded.  

void Open_Timer0();
void Start_Timer0();



/* 
 * File:   Timer1
 * Author: 
 * Comments:
 * Revision history: 
 */

#include < xc.h > // include processor files - each processor file is guarded.  

void Open_Timer1();
void Start_Timer1();



メインの関数

やっているのは、2つのタイマー関数を呼び出してタイマーを起動させ、後は無限ループで高速でLEDを点滅させているだけ。

/*
 * File:   main.c
 * Author: yasaka
 *
 * Created on 2017/05/07, 13:03
 */

#include < xc.h >
#include < stdio.h >
#include < stdlib.h >

// コンフィギュレーション pic18f4550  c18
#pragma config PLLDIV = 5, CPUDIV = OSC1_PLL2, USBDIV = 2
#pragma config FOSC = HSPLL_HS, FCMEN = OFF, IESO = OFF
#pragma config PWRT = ON, BOR = OFF, BORV = 0
#pragma config VREGEN = ON, WDT = OFF, WDTPS = 1
#pragma config CCP2MX = OFF, PBADEN = OFF, LPT1OSC = OFF
#pragma config MCLRE = OFF, STVREN = OFF, LVP = OFF
#pragma config ICPRT = OFF, XINST = OFF, DEBUG = OFF
#pragma config CP0 = OFF, CP1 = OFF, CP2 = OFF, CP3 = OFF
#pragma config CPB = OFF, CPD = OFF
#pragma config WRT0 = OFF, WRT1 = OFF, WRT2 = OFF, WRT3 = OFF
#pragma config WRTC = OFF, WRTB = OFF, WRTD = OFF
#pragma config EBTR0 = OFF, EBTR1 = OFF, EBTR2 = OFF, EBTR3 = OFF, EBTRB = OFF
#define _XTAL_FREQ 48000000     // 使用するPIC等により動作周波数値を設定する
                                // 水晶は20MHzでPLLで48MHz動作

void	main(void)
{
    TRISD=0;                    // ポートDをすべて出力ピンにする
	PORTD = 0;

    Open_Timer0();
    Open_Timer1();
    Start_Timer0();
    Start_Timer1();

	while(1)
	{
        LATDbits.LD0 = 1;
//        __delay_ms(5) ;        // 5msのディレイ
        LATDbits.LD0 = 0;
//        __delay_ms(5) ;        // 5msのディレイ
	}
}


タイマー0の関数

高プライオリティで割り込みを動作させること、割り込みに高低の区別があることを割り込み処理ルーチンに知らせている。


#include < stdio.h >
#include < stdlib.h >
#include < xc.h >
#include "timer1.h"

void Open_Timer0() {
        
    T0CONbits.T08BIT = 0;       // 16bit
    T0CONbits.T0CS = 0;        // internal clock
    T0CONbits.T0SE = 0;        // Edge Select low-to-high
    T0CONbits.PSA = 0;      // prescaler is assigned
    T0CONbits.T0PS = 0b111;      //  1/256

    TMR0L = 0;                  // timer0 count = 0
    TMR0H = 0;                  // timer0 count = 0
    
    INTCONbits.GIE = 1;   // Enables all high priority interruptsts
    INTCONbits.PEIE = 1;  // Enables all low priority peripheral interrupts
    
    INTCONbits.TMR0IE = 1;   // TMR0 Interrupt Enable
    INTCONbits.TMR0IF = 0;   // TMR0 did not overflow

    INTCON2bits.TMR0IP = 1;        // 0:Low priority   1:High priority
   
    RCONbits.IPEN =1;       // Enable priority levels on interrupts
}

void Start_Timer0() {
    
    T0CONbits.TMR0ON = 1;       // timer start

}


タイマー1の関数。
低プライオリティで動作させることを定義している。

#include < stdio.h >
#include < stdlib.h >
#include < xc.h >
#include "timer1.h"

void Open_Timer1() {
        
    T1CONbits.RD16 = 1;         // 16bit
    T1CONbits.T1RUN = 1;        //
    T1CONbits.T1CKPS1 = 1;      //
    T1CONbits.T1CKPS0 = 1;      //
    T1CONbits.T1OSCEN = 0;      //
    T1CONbits.T1SYNC = 1;       // 
    T1CONbits.TMR1CS = 0;       // internal clock

    TMR1L = 0;                  // timer1 count = 0
    TMR1H = 0;                  // timer1 count = 0
    
    INTCONbits.GIE = 1;   // Enables all high priority interruptsts
    INTCONbits.PEIE = 1;  // Enables all low priority peripheral interrupts
    
    PIR1bits.TMR1IF = 0;        // TMR1 register did not overflow
    
    PIE1bits.TMR1IE = 1;        // Enables the TMR1 overflow interrupt
    
    IPR1bits.TMR1IP = 0;        // Low priority
    
    RCONbits.IPEN = 1;          // Enable priority levels on interrupts

}

void Start_Timer1() {
    
    T1CONbits.TMR1ON = 1;       // timer start

}

さて、このプログラムを動作させると、こうなる。

test_pgm022
上の黄色いのがタイマー0で、100ミリ秒毎にパルスが出ている。

下の青いのがタイマー1で、20ミリ秒毎にパルスが出ている。



さて、タイマーの時間間隔をこのままにして、タイマー0(高い優先度)の処理が長くかかったという想定で、タイマー0の処理を大幅に増やし、40ミリ秒かかったとする。

        LATDbits.LD1 = 1;
        for(i=0;i<40;i++) {       // 1mS*40 40ms
            for(j=0;j<1000;j++) {  //1ms
                
            }
        }         
        LATDbits.LD1 = 0;


結果はこうなる。

test_pgm023
上の黄色いのがタイマー0で、100ミリ秒毎に出ているパルスが広がり、その間低プライオリティの割り込みが発生しないため、青いパルスが大幅に減っている。

高プライオリティの処理が終了するとすぐに低プライオリティの処理が開始されているので、おそらく実際は低プライオリティが無視されているのではなく、保留にされており、高プライオリティの処理が終了した時点で低プライオリティの割り込みが発生していると思われるのである。

こうなると、当然ながら高プライオリティの動作に低プライオリティの動作が同期するようになる。



今度は逆に、高プライオリティの方が割り込み回数を多くした設定にしてみる。

test_pgm024
高プライオリティのタイマー0は15ミリ秒毎に1ミリ秒のパルス。

低プライオリティのタイマー1は40ミリ秒毎に1ミリ秒のパルス。

トリガーはタイマー1に変更してある。



低プライオリティの処理が伸びて、高プライオリティの処理へはみ出したという想定で、20ミリ秒にしてみる。

test_pgm025
高プライオリティの処理が明らかに優先されて実行されており、低プライオリティの処理時間が若干伸びていると思われる。



高プライオリティの処理を伸ばして、5ミリ秒にしてみる。

test_pgm026
低プライオリティの処理が明らかに伸びて実行されており、しかも高プライオリティの処理時間が終了してから低プライオリティの処理になるので、結局、高プライオリティの処理と同期して実行されるようになる。



1、割り込みの処理は、高プライオリティの処理が優先で、たとえ低プライオリティの処理が開始されていても、低プライオリティの処理は中断されて高プライオリティの処理が先に実行される。
2、高プライオリティの処理の実行中は、低プライオリティの割込み処理は保留され実行されない。

だいたいこんな法則で動いているようである。


HOME