ものづくりコンテスト:演習課題

ものづくりコンテストでは新しくプロジェクトを作ってMCCを使って初期化部分の作成をおこなう必要がある。
ここでは新規プロジェクトの作り方とプロジェクトのコピーの練習をおこなう

演習の内容

1.ゼロから新しいプロジェクトを作る

新規プロジェクト mykadai1 を作成してRGBLEDを点滅させる(点滅間隔は人が点滅を認識できれば良い)

2.プロジェクトをコピーして新しいプロジェクトを作る

mykadai1をコピーしてmykadai2を作り回答例のプロジェクトを参考にして以下の処理をおこなう

A.7セグメントLEDに2桁の数値を表示する。

B.SW2(トグルSW)がONの時SW1(タクトスイッチ)を押す毎にLEDの数値がカウントアップする(最大12)

C.SW2がOFFの時SW1を押す毎に7セグメントの数値がカウントダウンする。―12以下にはならないようにする。

D.printf文を使って7セグメントの数値とスイッチの状態をPCのターミナルに表示する

3.おまけの課題

時間が余った人はスイッチを押す毎にステッピングモータが文字盤の1目盛りづつ動くプロジェクト mykadai3を作ってみる。

新規プロジェクト作成手順

新規プロジェクト mykadai1 を作る

メインメニューの
File -> Close All Projectsで全てのプロジェクトを閉じる
File -> New Projectを実行して下のウィンドウを開き Next をクリック

デバイスPIC18F45K22を選択する

Select DeviceのウィンドウでFamily (PIC18)の中からPIC18F45K22 を選択してNextをクリック
※たくさんのデバイスがあるので間違えないように

書き込みツールにPicKit3を指定する

Select ToolsウィンドウでPicKit3を選んでNextをクリック

コンパイラを選ぶ

Select CompilerウィンドウでコンパイラXC8を選んでNextをクリック

プロジェクト名とフォルダを指定

Select Project Name and Folder ウィンドウでプロジェクト名とフォルダを入力してFinishをクリックすると新しいプロジェクトが作られる
※ プロジェクト名は mykadai とする
※ フォルダはサンプルプロジェクトが入っている場所と同じでOK
※ Encoding にShift JISを選んでおくとWindowsのメモ帳で日本語が正常に表示される

続いてMCCを使って設定をおこなう(別資料参照)

ものづくりコンテスト:サンプルプログラム

PIC18F45K22CPUボード用ヘッダファイル(mono_con.h)

ものづくりコンテストではポート定義のヘッダファイルに限り持ち込みが認められていて、下記のようなヘッダファイルを用意しておくとプログラム作成の時間短縮に役立ちます。

/* 
 * File: mono_con.h
 * Author: jsd
 *
 * Created on 2018/04/17, 14:14
 */

#ifndef MONO_CON_H
 #define MONO_CON_H

#ifdef __cplusplus
 extern "C" {
 #endif

// LED G, B, R
 #define LEDG LATDbits.LATD1
 #define LEDB LATDbits.LATD5
 #define LEDR LATEbits.LATE2

// BUZZER
 #define BZ LATEbits.LATE0
 
 // Latch
 #define LT LATDbits.LATD2
 
 // 7seg 
 #define SEG7L LATBbits.LATB0
 #define SEG7R LATBbits.LATB1
 
 #define SEG7(d) (LATC=(d))
 #define SEG7_a LATCbits.LATC0
 #define SEG7_b LATCbits.LATC1
 #define SEG7_c LATCbits.LATC2
 #define SEG7_d LATCbits.LATC3
 #define SEG7_e LATCbits.LATC4
 #define SEG7_f LATCbits.LATC5
 #define SEG7_g LATCbits.LATC6
 #define SEG7_dp LATCbits.LATC7

// Stepping motor
 #define STM(d) (LATC=(d)&0x0f)

// DC motor
 #define DCM(d) (LATC=(((d)<<6)&0xc0)
 
 // SW status
 #define ENCA (PORTAbits.RA3)
 #define ENCB (PORTAbits.RA4)
 #define SW2 (PORTAbits.RA5==0)
 #define SW1 (PORTAbits.RA6==0)

#ifdef __cplusplus
 }
 #endif

#endif /* MONO_CON_H */

割り込みの使い方サンプルプログラム(プロジェクトkadaiのmain.c)

下のプログラムは割り込みを使うためのサンプルソースです。

プログラムの最初に ヘッダファイルmono_con.hを読み込むための#include文を追加します。

#include "mcc_generated_files/mcc.h"

#include "mono_con.h"

10mS 毎に呼び出される割り込み関数inttmr0()でスイッチの読み込みなどの処理をおこないます。 関数名inttmr0()をmain関数の最初で登録して割り込みを有効にすることで割り込み関数が使えるようになります。 割り込み関数を使う上での注意事項  関数内で宣言するローカル変数は割り込み関数を抜けると記憶されないので変数はグローバル(外部)変数か、関数内で宣言する場合はstatic宣言を使います。  割り込み関数の中で無限ループや長い時間待ちを使ってはいけません。出来るだけ早く終わるように処理を記述します。

/* 10ms毎に起動するタイマ割り込み関数
 *  mainの最初で登録することで周期的に呼ばれる
 * 入力処理と制御をここに記述
 * ※割り込み関数使用上の注意
 * 1.関数は出来るだけ早く抜けること(無限ループ厳禁)
 * 2.変数はグローバル変数(関数の外に書く)にするかstatic宣言をつけること
 */
int tmr1s,tmr10ms;  // これはグローバル変数
uint16_t tmr0_s,tmr0_e;    // デバッグ用の変数
void inttmr0(void)
{
    static int i;
    tmr0_s = TMR0_ReadTimer(); // デバッグ用、課題処理には不要
    
    // 課題1回答例 ここから
    if( SW2 && SW1 ){
        DCM(2);
        printf("dcm(2) PORTA=%02x,LATC=%02x\n",PORTA,LATC);
        LEDG = 1;
    }else if( !SW2 && SW1 ){
        DCM(1);
        printf("dcm(1) PORTA=%02x,LATC=%02x\n",PORTA,LATC);
        LEDB = 1;
    }else{
        DCM(0);
        LEDG = 0;
        LEDB = 0;
    }
    // ラッチの立ち上がりでモータドライバに信号が伝わる
    LT = 0;
    __delay_us(10); // パルスを観測し易くするために10us待つ 
    LT = 1;
    // 課題1回答例 ここまで
            
    // tmr10msをインクリメントし100になったらtmr1sをカウントアップ
    if( 100 <= ++tmr10ms ){ 
        // この部分は1秒ごとに実行
        tmr1s++;
        tmr10ms = 0;
    }
    
    tmr0_e = TMR0_ReadTimer(); // デバッグ用:課題処理には不要
}

tmr01msは100us毎に呼び出される割り込み関数です ブザーを大きな音で鳴らすためには1KHz以上の周波数でON/OFFを繰り返す 必要があるので10KHzの周期にしています。 tmr1_s, tmr1_eには割り込み開始時と終了時のタイマカウンタの値が 入り、printfで表示するとどのくらいの余裕があるかを確認できます。

/* 0.1ms毎に起動するタイマ割り込み関数
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
int tmr01ms;        // これはグローバル変数
uint16_t tmr1_s,tmr1_e;    // デバッグ用の変数

void inttmr1(void)
{
    static int t;   // スタティック変数
    
    tmr1_s = TMR1_ReadTimer(); // デバッグ用、課題処理には不要
    
    // 0.1ms * 9000 = 0.9秒毎に '*' を出力
    // mainのprintfを邪魔する様子を観察するために書いてある
    if( (tmr10ms==0)&&(45 <= ++tmr01ms) ){
        tmr01ms = 0;
        printf("*");
    }  
    tmr1_e = TMR1_ReadTimer(); // デバッグ用、課題処理には不要
}

main関数では割り込み関数を登録してタイマー割り込みを開始する処理だけを記述して 課題の処理は全て割り込み関数でおこないます。

void main(void)
{
    // Initialize the device
    SYSTEM_Initialize();

/* 次のコメントを除いた4行は新しく追加する必要があります。 登録関数とタイマースタート関数の名前は生成されたソースで確認することが出来ます。*/

    // タイマー0割り込み関数を登録 (tmr0.c の関数を呼ぶ)
    TMR0_SetInterruptHandler(inttmr0);
    // タイマー1割り込み関数を登録 (tmr1.c の関数を呼ぶ)
    TMR1_SetInterruptHandler(inttmr1);
    // タイマー0をスタート (tmr0.c の関数を呼ぶ)
    TMR0_StartTimer();
    // タイマー1をスタート (tmr1.c の関数を呼ぶ)
    TMR1_StartTimer();

/* 割り込みを有効にするために次の2行のコメントを外して有効にします。*/

   
    // If using interrupts in PIC18 High/Low Priority Mode you need to enable the Global High and Low Interrupts
    // If using interrupts in PIC Mid-Range Compatibility Mode you need to enable the Global and Peripheral Interrupts
    // Use the following macros to:

    // Enable the Global Interrupts:この下の行をコメントアウトし有効にする
    INTERRUPT_GlobalInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Enable the Peripheral Interrupts:この下の行をコメントアウトし、有効にする
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

/* ここにはデバッグ用のprintf文を書いていますが 課題の処理ではメイン関数の無限ループは空のままで構いません。*/

    // メインループはデバッグ用のprintfだけを書く
    // 課題の処理には必要無し
    while (1)
    {
        // 1秒毎にインクリメントされるtmr1sの値を表示
        // tmr0_s : tmr0割り込み開始時のタイマー値  
        // tmr0_e : tmr0割り込み終了時のタイマー値 
        //    ※0になると次の割り込みが始まる  
        // tmr1_s : tmr1割り込み開始時のタイマー値  
        // tmr2_e : tmr1割り込み開始時のタイマー値 
        //    ※0になると次の割り込み開始  
        if( tmr10ms == 0 ){
            printf("tmr1s=%d, tmr0_s=%u, tmr0_e=%u, tmr1_s=%u, tmr1_e=%u\n",
                    tmr1s, 0xffff-tmr0_s, 0xffff-tmr0_e, 0xffff-tmr1_s, 0xffff-tmr1_e);
        }  
    }
}

課題1回答例 (MCCが生成したコメントを除く)

ポイント 条件文で SW1 == 0 と書く代わりに !SW1 と記述している  教科書的には等号・不等号をきちんと書いた方が良いが、 プログラムを書いている時に等号==を代入の記号=と間違えて記述する場合があり、 気が付きにくいバグとなるので後者の記述にした方が有利である。

#include "mcc_generated_files/mcc.h"

#include "mono_con.h"

/* 10ms毎に起動するタイマ割り込み関数
 */

void inttmr0(void)
{
    if( !SW2 && SW1 ){ // SW2:OFF  SW1:ON
        DCM(2);
    }else if( SW2 && SW1 ){ // SW2:ON SW1:ON
        DCM(1);
    }else{
        DCM(0);
    }
    // ラッチの立ち上がりでモータドライバに信号が伝わる
    LT = 0;
    __delay_us(10); // パルスを観測し易くするために10us待つ 
    LT = 1;
}

/* 10ms毎に起動するタイマ割り込み関数
 * 出力処理をここに記述
 * 課題1では必要ないが、プロジェクトの初期化部分を共通化するために空の関数を記述
 */
void inttmr1(void)
{

}

void main(void)
{
    // Initialize the device
    SYSTEM_Initialize();

    // タイマー0割り込み関数を登録 (tmr0.c の関数を呼ぶ)
    TMR0_SetInterruptHandler(inttmr0);
    // タイマー1割り込み関数を登録 (tmr1.c の関数を呼ぶ)
    TMR1_SetInterruptHandler(inttmr1);
    // タイマー0をスタート (tmr0.c の関数を呼ぶ)
    TMR0_StartTimer();
    // タイマー1をスタート (tmr1.c の関数を呼ぶ)
    TMR1_StartTimer();

    // Enable the Global Interrupts:この下の行をコメントアウトし有効にする
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts:この下の行をコメントアウトし、有効にする
    INTERRUPT_PeripheralInterruptEnable();

    while (1)
    {
  
    }
}

課題2回答例

main関数は課題1と同じなので省略しています ポイント  シーケンシャルな処理をするためにステートマシンを使っています。  ステートマシンとはswitch文と状態変数を使って割り込みが呼ばれるたびに  状態変数で処理内容を選択する書き方です。  慣れると複雑なシーケンスでも簡単に記述できます。  スイッチがONした瞬間をとらえるためにSWの状態を記憶しておいて  次のタイミングでスイッチがONでと記憶した状態がOFFの時にスイッチON時の処理をしています。

#include "mcc_generated_files/mcc.h"
#include "mono_con.h"

/* 10ms毎に起動するタイマ割り込み関数
 */
void inttmr0(void)
{
    static int sw1; // SW1の状態を記憶する変数 ※static宣言が必要
    static int state=0; // ステートマシン用変数
    
    switch(state){
        case 0: // 消灯
            LEDR = LEDG = LEDB = 0;  // 全部消灯
            if( SW2 )state = 1; // トグルSWがONになったら 1 へ
            break;
        case 1:  // 黄色
            LEDR = LEDG = 1;        // 緑と赤を点灯(黄色)
            if( !SW2 ) state = 0;   // トグルSWがOFFになったら 0 へ
            if( SW1 && !sw1 ){      // タクトSWの立ち上がりで2へ
                LEDR = LEDG = 0;
                state = 2;
            }
            break;
        case 2: // 緑点灯
            LEDG = 1;
            if( !SW2 ) state = 0;
            if( SW1 && !sw1 ){
                LEDG = 0;
                state = 3;
            }
            break;
        case 3: // 青点灯
            LEDB = 1;
            if( !SW2 ) state = 0;
            if( SW1 && !sw1 ){
                LEDB = 0;
                state = 4;
            }
            break;
        case 4: // 赤点灯
            LEDR = 1;
            if( !SW2 ) state = 0;
            if( SW1 && !sw1 ){
                LEDR = 0;
                state = 2;
            }
            break;
        default:
            state = 0;
            break;
    }
    sw1 = SW1;
    // printf文は無くても良いし、あっても課題の動作には影響が無い
    printf("state=%d,SW1=%d,sw1=%d\n",state,SW1,sw1);

}

/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
 *  mainの最初で登録することで周期的に呼ばれる
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
void inttmr1(void)
{

}

課題3回答例

ポイント  エンコーダ信号もスイッチ信号と同じく変化したタイミングでだけ処理をおこなうのがポイントです。  使っているエンコーダはA相の信号がHighからLowになった時でないと方向判別が出来ない仕様です。  割り込みを使った7SEGの時分割表示はパターンを覚えればとても簡単です。

#include "mcc_generated_files/mcc.h"
#include "mono_con.h"

/* 10ms毎に起動するタイマ割り込み関数
 */
int seg7R = 16, seg7L = 16;
int ct = 0;  // `エンコーダカウント

void inttmr0(void)
{
    static int encA; // SW1の状態を記憶する変数 ※static宣言が必要
    
    // ロータリーエンコーダの信号でct をカウントアップ・ダウン
    if( !ENCA && encA ){ // ENCAがHighからLowになった時
        if( ENCB ){
            ct++;
            if( 20 < ct ) ct = 20;
        }else{
            ct--;
            if( ct < -5 ) ct = -5;
        }
    }
    encA = ENCA; // 次のために記憶
    
    if( !SW2 ) ct = 0;
    
    // 7SEGに表示
    if( 0 <= ct ){
        seg7L = (ct/10) % 10; // seg7L に ctの上位桁を入れる
        seg7R = ct % 10;      // seg7R にctの下位桁を入れる
    }else{
        seg7L = 17;
        seg7R = -ct % 10;      // seg7R にctの下位桁を入れる        
    }
        
}

/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
 *  mainの最初で登録することで周期的に呼ばれる
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
/*****************************************************************
  7SEG LED の表示データ
 ****************************************************************/
const unsigned char SEG7D[] = {      // 7セグメントLEDの表示データ
    0b00111111,                         //  0 = 0
    0b00000110,                         //  1 = 1
    0b01011011,                         //  2 = 2
    0b01001111,                         //  3 = 3
    0b01100110,                         //  4 = 4
    0b01101101,                         //  5 = 5
    0b01111101,                         //  6 = 6
    0b00100111,                         //  7 = 7
    0b01111111,                         //  8 = 8
    0b01101111,                         //  9 = 9
    0b01110111,                         // 10 = A
    0b01111100,                         // 11 = b
    0b00111001,                         // 12 = C
    0b01011110,                         // 13 = d
    0b01111001,                         // 14 = E
    0b01110001,                         // 15 = F
    0b00000000,                         // 16 = 消灯
    0b01000000,                         // 17 = -(マイナス)
    0b01010100,                         // 18 = OFF(冖)
    0b00001000,                         // 19 = ON (_)
};

void inttmr1(void)
{
     static int selseg, t;
    
    // 7SEG 表示処理
    if( selseg ){ // 右の7SEG点灯
        SEG7R = 1;
        SEG7L = 0;
        SEG7(SEG7D[seg7R]);       
    }else{        // 左の7SEG点灯
        SEG7R = 0;
        SEG7L = 1;
        SEG7(SEG7D[seg7L]);
    }
    selseg = !selseg; // フラグを反転する
    
    // ブザーを鳴らす
    if( ct == -5  ){
        if( 2 <= ++t ){
            t = 0;
            BZ = !BZ;
        }
    }    
}

課題4回答例

ポイント エンコーダのカウントと7SEG表示は課題3と同じでカウンタの範囲を±3に制限するだけです。  割り込みを使うとステッピングモータ駆動も簡単に追加することが出来ます。  ステッピングモータ駆動信号と7SEG表示信号が重なっているため、 割り込み関数の中で最初に駆動信号をラッチしてから表示信号をセットするように注意しなければなりません。  順序が逆の場合は7SEGの表示がでたらめになります。  その理由を考えてみるのはハードを理解するうえで良い勉強になるでしょう。

/* 10ms毎に起動するタイマ割り込み関数
 */
int seg7R = 16, seg7L = 16;
int ct = 0;
int stmct = 0; // ステッピングモータ回転指令カウンタ

void inttmr0(void)
{
    static int sw1,encA; // SW1の状態を記憶する変数 ※static宣言が必要
    static int stmpos = 0, tgtpos = 0;
    static int state = 0;
    
    // ロータリーエンコーダの信号でct をカウントアップ・ダウン
    if( !ENCA && encA ){ // ENCAがHighからLowになった時
        if( ENCB ){
            ct++;
            if( 3 < ct ) ct = 3;
        }else{
            ct--;
            if( ct < -3 ) ct = -3;
        }
    }
    encA = ENCA; // 次のために記憶
    if( !SW2 ) ct = 0;

    // ステッピングモーター制御
    if( SW1 && !sw1 ) tgtpos = ct*120; // 目標位置
    sw1 = SW1;
    
    switch(state){
        case 0: // 目標位置との差分に指令値をセット
            if( tgtpos != stmpos ){
                stmct = tgtpos - stmpos;
                stmpos = tgtpos;
                state = 1;
            }
            break;
        case 1: // ステッピングモーターが止まるまで待つ
            if( stmct == 0 ) state = 0;
            break;
        default:
            state = 0;
            break;                
    }
    if( tgtpos != stmpos ){
        
    }
        
    // 7SEGに表示
    if( 0 <= ct ){
        seg7L = (ct/10) % 10; // seg7L に ctの上位桁を入れる
        seg7R = ct % 10;      // seg7R にctの下位桁を入れる
    }else{
        seg7L = 17;
        seg7R = -ct % 10;      // seg7R にctの下位桁を入れる        
    }
        
    //printf("stmct=%d\n",stmct);
}

/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
 *  mainの最初で登録することで周期的に呼ばれる
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
/*****************************************************************
  7SEG LED の表示データ
 ****************************************************************/
const unsigned char SEG7D[] = {      // 7セグメントLEDの表示データ
    0b00111111,                         //  0 = 0
    0b00000110,                         //  1 = 1
    0b01011011,                         //  2 = 2
    0b01001111,                         //  3 = 3
    0b01100110,                         //  4 = 4
    0b01101101,                         //  5 = 5
    0b01111101,                         //  6 = 6
    0b00100111,                         //  7 = 7
    0b01111111,                         //  8 = 8
    0b01101111,                         //  9 = 9
    0b01110111,                         // 10 = A
    0b01111100,                         // 11 = b
    0b00111001,                         // 12 = C
    0b01011110,                         // 13 = d
    0b01111001,                         // 14 = E
    0b01110001,                         // 15 = F
    0b00000000,                         // 16 = 消灯
    0b01000000,                         // 17 = -(マイナス)
    0b01010100,                         // 18 = OFF(冖)
    0b00001000,                         // 19 = ON (_)
};

/* ステッピングモーター相励磁データ
 *  STMD[stmidx]の形でstmidxを0,1,2,3,0,1,2,3,...とすると
 *  ここに設定したパターンが順番に呼び出される
 * それをモータードライバーに接続されたポートに出力してモーターを回す
*/
const unsigned char STMD[] = {
    // oooo にモーターが回転する相励磁パターンを入れる
	0b0001, // 0 A 励磁 (A相だけ1にする)
	0b0010, // 1 B 励磁 (B相だけ1)
	0b0100, // 2 /A 励磁(/A相だけ1)
	0b1000, // 3 /B 励磁(/B相だけ1)
};

void inttmr1(void)
{
   static int selseg, t, stmidx;
    
    //  ステッピングモーター駆動
    //  7SEGの信号と共通(時分割)のため先にステッピングモーター信号をセット
    SEG7R = SEG7L = 0;  // ちらつき防止のため7SEGGをOFFにする
    if( 50 <= ++t ){ // ステッピングモータの速度調整
        t = 0;
        if( 0 < stmct ){
            if( --stmidx < 0 ) stmidx = 3; // 時計回り
            stmct--;
        }else if( stmct < 0 ){
            if( 3 < ++stmidx ) stmidx = 0; // 反時計回り
            stmct++;
        }
        STM(STMD[stmidx]);  // 相励磁信号セット
        LT = 0;        // 相励磁信号をラッチ
        LT = 1;
    }
#if 1
    // 7SEG 表示処理
    if( selseg ){ // 右の7SEG点灯
        SEG7R = 1;
        SEG7L = 0;
        SEG7(SEG7D[seg7R]);       
    }else{        // 左の7SEG点灯
        SEG7R = 0;
        SEG7L = 1;
        SEG7(SEG7D[seg7L]);
    }
    selseg = !selseg; // フラグを反転する
#endif
}

ものづくりコンテスト:PIC18F45K22ボードの初期化

MPLAB-XのMCCを使って、割り込みを使ったものづくりコンテスト対応のPIC18F45K22 CPUボードを初期化する手順を説明します。

タイマー割り込みとシリアル通信の設定

PICの機能のうちタイマー割り込みとシリアル通信機能の設定をおこないます。

何故割り込みを使うのか

プログラム初心者にとって割り込みはハードルが高い技術のように思えます。
 では、何故面倒に見える割り込みを使うのでしょうか。

組み込みプログラミングではタイマー割り込みの使用が基本

組み込みプログラムの世界で割り込みは必須のテクニックです。
 制御プログラムではタイミング管理が重要なのでタイマー割り込みを使った処理が基本となります。
 ものづくりコンテストでは簡単な課題は割り込みを使わないループ処理だけでプログラムを作れますし、複雑な課題でも割り込み無しでプログラムすることは可能です。

ループ処理とタイマー割り込みのどちらが有利?

ものづくりコンテストの課題を決められた時間内で解くのにループ処理と割り込みを使った場合のどちらが有利かを考えてみます。
 割り込みを使うための基礎知識は必要ですが、プログラム難易度は簡単な課題では同等で、複雑な課題になるほど割り込みを使うほうが素早く解けるようになります。
MPLAB-Xを使えばタイマー割り込みに関する初期化設定はmain関数に数行の追加が必要なだけなのでトータルで見ればタイマー割り込みを使った方が有利です。

また、ものづくりコンテストの主旨である「社会で認められる技術を身につける。」という点から見れば割り込みは是非習得したい技術です。

シリアル通信について

ものづくりコンテストではシリアル通信を使った課題は出題されていませんが、シリアル通信を有効にしてprintfが使えるようにしておくとプログラムのデバッグに役立つので初期化の中にシリアル通信の設定も含めておきます。
 

設定の手順

MCCを使った初期化設定の手順について説明します。

1・クロックの設定

左のProject Resourcesタブで System Moduleを選択してクロックの設定をおこないます。
右のSysitem Module設定でSoftware PLL Enableにチェックを入れてCurrent System Clock が64MHzになるようにします。

下の赤丸で囲まれた部分でPDIP40を選択してPIC18F45K22 CPUボードと同じパッケージが表示されるようにします。

2.シリアル通信の設定

左のDevice Resourcesで下図の赤丸で囲まれたEUSART2をダブルクリックして、上のProject Resourcesに追加します。
DeviceResourcesにはこのCPUで使える内蔵機能が列挙されていて、ダブルクリックして選択することでProject Resourecesに追加されて設定が出来るようになります。

表示されたEUSART2の設定画面で通信速度=115200、Enable Transmit等赤丸で囲まれた部分にチェックを入れて有効にします。
Ridirect STDIO to USARTにチェックを入れることでprintfの結果をシリアルポートを通じてPCのターミナルプログラムで表示できるようになります。

3.タイマー0割り込みの設定

EUSART2の場合と同様にDevice ResourcesでTMR0を選んでダブルクリックし、Project Resourcesに追加します。
Timer Periodに”10ms”を書き込み下のEnable Timer Interruptにチェックを入れると10ms毎に割り込み関数が呼ばれます。
Timer Periodで意図する時間が設定できない場合は緑丸で囲まれた部分の設定を変えるとより長い周期が選べるようになります。

4.タイマー1割り込みの設定

Device ResourcesでTMR1を選んでダブルクリックし、Project Resourcesに追加します。
Timer Periodに”100us”を書き込み下のEnable Timer Interruptにチェックを入れると100us毎(10000回/秒)に割り込み関数が呼ばれます。
実際の割り込み間隔が緑丸で囲まれた部分に表示されるので意図した周期の割り込みになっているかを確認しておきます。

5.入出力の設定

最後に入出力の設定をおこないます。
PinManager:GridViewタブで鍵のアイコンをクリックして入力と出力を設定します。鍵がロックして緑色になった部分が有効な設定となります。

6.初期化プログラムの生成

設定が終わったらProject ResoucesタブでGenerateボタンをクリックして初期化プログラムとmain.cを生成します。
プログラムを書いている途中で設定を変更したい場合も再度MCCを開いて設定を変更した後にGenerateボタンをクリックします。
main.cを編集している場合に再度MCCのGenarateをおこなっても、編集した部分はそのまま保存されます。

7.生成されたプログラムの確認

MCCで初期化プログラムを生成した後にProjectタブの内容を見るとmain.cとMCC Generated Filesが作られているのが確認できます。
生成されたファイルのmain.cを直接編集して課題のプログラムを書いていきます。
MCC Generated Filesフォルダにあるプログラムを編集することはありませんが、この中の関数を使う場合には関数名や使い方を確認するために開いてみることが出来ます。