はじめに

ここではPICを開発するうえで簡単に周辺機能などの設定が行えるMCC(MPLAB Code Configurator)の使い方について説明します.MCCを使えば初期設定が難しい周辺機能をGUIで設定できたり,簡単に周辺機能を操れる関数を自動的に生成してくれたしてくれます.なお,MCCは非常にたくさんの機能がありますが,この文書では動作周波数の設定,汎用端子の設定,一部のタイマの設定についてのみ説明しています.

環境

OS: Windows 11

MPLAB X: Ver 6.05

コンパイラ: XC8 Ver 2.4.1

MCC: Ver 5.3.0

ターゲットデバイス: 16F19155 DIP28

各種インストール

 MPLAB XとXC8のインストールについてはこちらにあるページを見てください.MCCについては,未確認ですがMPLAB X Ver 6以上ではデフォルトでインストールされている可能性があります.以下ではMCCがインストールされ,かつアクティブになっていることを確認し,もしアクティブになっていなかった場合にはアクティブ化します.その後,最新のMCCにする方法について説明します.

まずはMPLAB Xを起動し,MCCがアクティブ化されているか確認します.下の図のようにToolsメニューにあるPluginsを選択します.

HowToUseMCC 01

 

次にInstalledタブにあるMPLAB Code Configuratorを見つけます.ここで,右側にあるActivateが灰色になっている場合,アクティブ化していません.その場合には右側にあるSelectにチェックを入れてActivateボタンを押してください.

HowToUseMCC 02

 

MCCのインストーラが立ち上がると,下の図のようになりますのでActivateボタンを押してください.

HowToUseMCC 03

 

アクティブ化されると下の図のようになりますのでFinishボタンを押してください.

HowToUseMCC 04

 

次にアップデートを調べます.上側にあるUpdateタブを押し,もしMCCのアップデートがありましたらチェックマークを入れてアップデートしてください.

HowToUseMCC 05

 

アップデートの前に下のような確認ダイアログが現れます.

HowToUseMCC 06

 

アップデートが終わると下の図のようになります.MPLAB Xの再起動をしてください.

HowToUseMCC 07

 

以上でMCCのアクティブ化と更新はおしまいです.

プロジェクトの作成

続いてプロジェクトを作成していきます.ここでのターゲットデバイスはPIC16F19155です.まずは下のようにFileメニューからNew Projectを選択します.

HowToUseMCC 08

 

CategoriesをMicrochip Embedded,ProjectをStandalone Projectであることを確認し,Nextボタンを押してください.

HowToUseMCC 09

 

次にDeviceとしてPIC16F19155を選択します.正確に言えば選択するより打ち込んだ方が早いともいます.入力後,Nextボタンを押してください.

HowToUseMCC 10

 

次にコンパイラを選択します.この例ではXC8 Ver 2.4.1を指示しています.選択後,Nextボタンを押してください.

HowToUseMCC 11

 

Project NameとProject Locationを指定するとともに,EncodingをUTF-8にしてください.最後にFinishボタンを押します.

HowToUseMCC 12

 

 

これでプロジェクトが生成されたはずです.

MCCによる設定

次にMCCによる設定を行うため,MCCを起動します.MPLAB Xの上部にMCCと書かれたアイコンがありますのでこちらをクリックしてください.

HowToUseMCC 13

 

次にContentTypeを選択します.下の図のようにMCC Classicを選択してください.

HowToUseMCC 14

 

下の図のようにFinishを選択してください.

HowToUseMCC 15

周波数の設定

MCCが立ち上がりましたら周波数の設定をしましょう.多くのPICには内蔵の発振器が備わっっているため,外付けの発振器をつける必要がなく便利です.今回も内蔵の発振器を使ってシステムクロックを4MHzになるようにします.下の図をご覧ください.System Modduleタブを選択し,まずはOscillatorSelectをHFINITOSCにします.これは高周波数内蔵オシレータ(High Frequency Internal Oscilattor)の頭文字です.この発振器を使えば,最大で32MHzでの動作が可能です.ただ今回は周波数を抑えることで消費電力を抑えるため,4MHzにします.次にHF Internal Clockを4MHzを選択し,さらにClock Dividerを1にします.Dividerとは分周器のことでありこの分周比を大きくすれば周波数は遅くなります.

HowToUseMCC 16

 

周波数の設定とは直接関係しませんが,System Moduleタブの一番下にあるLow-Voltage Programming Enableのチェックを外しておいてください.詳しい説明は省略しますが,このチェックをつける(=Low-Volage Programmingを有効にする)と「MCLR端子を無効にできません」というちょっとした警告が現れます.今回はMCLR端子が無効になっていてもそれほど困りませんが,ちょっとした英国を消しておきたいがため,このチェックを外しています.

HowToUseMCC 17

 

汎用端子の設定

次に汎用端子の設定をしていきます.下の図のように,上部にあるPin Moduleタブを選択するとともに,下部にあるPin Manage: Grid Viewタブを選択してください.この図ではまだ何も端子の設定がなされていないため,Pin ModuleタブにはNo Configurable Pins Selectedとなっています.

HowToUseMCC 18

 

この例では下に示す回路図のように端子を使用します.下の図では,オレンジ色の矩形で囲った箇所が接続時の信号名を表しています.14個のLED(DICE1_1~7,DICE2_1~7)と1個のスイッチ(SW)をPICに接続します.それ以外にもPICにはMCLR,ICSPCLK,ICSPDATが接続されていますがこれらはすべてダウンロードするための端子です.さて,DICE1_1~7,DICE2_1~7,SWが接続された端子には,PICの端子番号(水色の矩形で囲ったところの数字)とともに,各端子に備わるデフォルトの機能(緑色の矩形で囲ったもの)があります.今回,端子に対してはすべて汎用IOとしてしか使用しませんが,もしUARTのような通信をする機能をMCCに追加すると上記の図のPin Manager にそれが現れます.

 

HowToUseMCC 19

 

端子の設定をする前に,下の図様にPakageを指定しておきます.今回はDIP28(Dual Inline Package 28ピン)を用いているため,下の図のようにSPDIP28を選択します.次にスイッチ(SW)の設定を行いましょう.スイッチは26番端子に接続されています.このため,列はPin Noを26,行はGPIO inputのところにある南京錠のアイコンをクリックすると,下の図様に緑色になります.これで26番端子を入力端子にすることができます.

HowToUseMCC 20

 

次に追加した端子に名前を付けましょう.下の図のように画面上側にあるPin Moduleを見てください.先ほどまで空でしたが,1行追加されているはずです.ここでCustom NameをSWとしておきます.こうすることで,スイッチを操作するときの関数名の接頭辞が「SW」となります.なお,今回は入力端子のため,下の図のようにチェックを入れるところはありません.

HowToUseMCC 21

上記と同様にLEDが接続されている端子を出力端子にしましょう.ここではまず15番端子に接続してあるDICE1_1を設定します.まずはPin Manager内にある列が15番,行がoutputを南京錠をクリックしてください.その後,Pin Module内に現れる行にあるCustom NameをDICE1_1にしましょう.ここではStart HighとOutputのみ,チェックを入れておいてください.特にAnalogにチェックが入っていたら必ずはずしてください.ここでStart High,Analog,Outputについて説明しておきます.Start Highとは,プログラムが開始されたとき,この端子をHighにするというものです.今回の回路ではマイコンの端子からHighを出力するとLEDが消灯するようになっています.消灯させた状態でプログラムを介したいためにここにチェックを付けます.次にAnalogについてです.PICマイコンでは多くの端子をAnalog端子として使用することができます.これにより,例えばアナログ-ディジタル変換(通称,AD変換)をPICで行わせたいときなどに使います.今回はディジタル端子として使いたいため,Analogのチェックを外す必要があります.最後にOutputです.これは読んで字のごとくなのですぐわかると思いますが,出力端子にしたいときにこのチェックを付けます.反対に,チェックがついていなければInput端子です.

HowToUseMCC 22

 

残り13個のLEDについても設定した様子を下に示します.

HowToUseMCC 23

 

このように,スイッチやLEDと接続された端子を入力にしたり出力にしたりしてあげればそれらを制御することができます.

注意

PICに備わる端子の多くは入力端子にしたり出力端子にしたりすることができますが,一部の端子については入力専用もしくは汎用端子として使用できないものがあります.詳しくはデータシートを確認してほしいです.今回用いているPIC16F19155では,RA5(7番端子)は入力専用です.データシート中にあるTRISレジスタ(Tri-State レジスタ)を調べ,もしTRISレジスタの設定ができないような端子であれば,それは入力もしくは出力専用端子です.

タイマの設定

次に周辺機能のタイマを使用するための設定をしましょう.まずは下の図のように画面左側にあるDevice Resourcesの中からTimerを選択し,その中のTMR0をクリックします.PIC16F19155には4種類のタイマがありますが,ここではTMR0を使用します.

HowToUseMCC 24

 

TMR0を追加すると下の図のようにTMR0タブが追加されます.その中の左側ではTimer Clockに関する設定を行えます.まず,Clock prescalerではクロックの前分周を行えます.これはカウントする前のクロックを分周する割合を設定できます.次にPostscalerでは後分周を行えます.クロックをカウントした後に分周する割合を設定できます.次にTimer modeではタイマの動作モードを設定できます.この説明はちょっと難しいのですが,TMR0には8-bitモードと16-bitモードがあり,これによりタイマの動作が変わります.ここでは8-bitモードにしておいてください.最後にClock Sourceです.これはタイマで使用するクロックをどうするかを設定します.ここではFOSC/4を選択します.FOSCはマイコンの動作周波数です.今回は4MHzに設定したため,FOSC/4は1MHzとなります.以上のように設定すると,2.048msから524.288msまで,このタイマで数えることができます.今回,10msから500ms程度まで時間を計りたいため,このように設定しました.もし,より長く時間を計りたいのであれば,Clock prescalerやPostscalerの比を大きくしたり,Clock Sourceをより遅いものを選択します.ただし,その場合には精度は落ちます.反対により精度の高いタイマを目指すのであれば,先ほどの設定の反対をすればよいです.ただし,その場合には数えられる時間の長さは短くなります.

さて,後ほどこのタイマを使うときに使う大事な数字(時間)についてここで述べておきます.それは2.048msです.今回のタイマでは1カウントで2.024ms数えられるようにしました.例えば10カウントすれば2.04×10=20.48msとなります.なお,10カウントさせるときには0から9までの10カウントをさせるため,10-1=9というように9という値をタイマに設定するということを覚えておいてください.

HowToUseMCC 25

 

以上でMCCによる設定は終わりです.最後にこれらの設定をもとにプログラムを生成しましょう.下の図のように画面左側にあるResource Management [MCC]のGenerateボタンを押してください.

 HowToUseMCC 26

 

こうすることでプロジェクトにmcc_generated_filesフォルダ内にヘッダファイルとともにソースファイルが生成されます.下の図はその様子を表しています.今回,TMR0の設定を行ったため,tmr0.cとtmr0.hが生成されています.それ以外のファイルは常に生成されるファイルです.

HowToUseMCC 27

 

下の図ではMPLAB XにおいてProjectsタブ内に現れている,先ほど追加したファイルの様子を表しています.この後,これらのファイルに書かれている関数を用いて端子や周辺機能を操作します.

HowToUseMCC 28

MCCにより生成される関数を利用したプログラミング

 次にプログラムを作成していきます.まずは下の図のようにSource Filesにあるmain.cファイルを開いてください.main.cファイルにはすでに端子と各種周辺機能(今回の場合であればTMR0)の初期化を行うよう,SYSTEM_Initialize()関数を呼び出すようになっています.この関数はmain.cの44行目にあるmcc.hヘッダファイルの中でプロトタイプ宣言がなされています.このmcc.hヘッダファイルではそのほかのヘッダファイルをインクルードするように記述されているため,例えばtmr0.hを改めてmain.cでインクルードする必要はありません.

HowToUseMCC 29

 

今回のサンプルプログラムでは,以下に示す4つの状態が順番に変化していきます.

  1. スイッチが押されておらずにさいころの目が止まった状態
  2. スイッチが押し続けられている間さいころが高速(およそ10ms)に変化する状態
  3. スイッチが離されてさいころの目が8回ゆっくりと変化する状態
  4. さいころの目が決定して4回の点滅をする状態

なお,4の状態の次には再び1の状態に戻ります.このような動作をさせるプログラムを下に示します.

/** スイッチが押された時の状態 */
#define PRESSED (0)

/** 高速動作中の間隔(2.048[ms]*(FAST_RUN_INTERVAL+1)=10.24[ms] */
#define FAST_RUN_INTERVAL (4)

/** 低速動作中の間隔(2.048[ms]*(SLOW_RUN_INTERVAL+1)=249.856[ms] */
#define SLOW_RUN_INTERVAL (121)

/** 点滅の間隔(2.048[ms]*(BLINKING_INTERVAL+1)=124.928[ms] */
#define BLINKING_INTERVAL (60)
void main(void)
{
    Roll dice1_roll = ROLL_ZERO;
    Roll dice2_roll = ROLL_ZERO;
    
    // initialize the device
    SYSTEM_Initialize();


    /* さいころの初期化 */
    Dice_initialize();

    /* さいころに乱数を入れる */
    dice1_roll = rand()%6+1;    
    dice2_roll = rand()%6+1;
    /* さいころを表示する */
    Dice_setRoll(DICE1, dice1_roll);
    Dice_setRoll(DICE2, dice2_roll);

    // When using interrupts, you need to set the Global and Peripheral Interrupt Enable bits
    // Use the following macros to:

    // Enable the Global Interrupts
    //INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptEnable();

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

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

    while (1)
    {
        /* スイッチが押されたら */
        if(SW_GetValue() == PRESSED){
            /* 高速モードの間隔にする */
            TMR0_Reload(FAST_RUN_INTERVAL);
            /* タイマを動作開始 */
            TMR0_StartTimer();
            /* 押されている間ここで待つ */
            while(SW_GetValue() == PRESSED){
                /* 新しい出目を得る */
                dice1_roll = rand()%6+1;    
                dice2_roll = rand()%6+1;
                /* さいころを表示する */
                Dice_setRoll(DICE1, dice1_roll);
                Dice_setRoll(DICE2, dice2_roll);
                /* 一定時間になったら */
                while(!TMR0_HasOverflowOccured());
                /* フラグをクリア */
                TMR0IF = 0;
            }
            /* 低速モードの間隔にする */
            TMR0_Reload(SLOW_RUN_INTERVAL);
            for(uint8_t count=0; count<8; count++){
                /* 新しい出目を得る */
                dice1_roll = rand()%6+1;    
                dice2_roll = rand()%6+1;
                /* さいころを表示する */
                Dice_setRoll(DICE1, dice1_roll);
                Dice_setRoll(DICE2, dice2_roll);
                /* 一定時間になったら */
                while(!TMR0_HasOverflowOccured());
                /* フラグをクリア */
                TMR0IF = 0;
            }
            /* 点滅モードの時の間隔にする */
            TMR0_Reload(BLINKING_INTERVAL);
            for(uint8_t count=0; count<4; count++){
                /* 一旦,出目を消す */
                Dice_setRoll(DICE1, ROLL_ZERO);
                Dice_setRoll(DICE2, ROLL_ZERO);
                /* 一定時間になったら */
                while(!TMR0_HasOverflowOccured());
                /* フラグをクリア */
                TMR0IF = 0;
                /* さいころを表示する */
                Dice_setRoll(DICE1, dice1_roll);
                Dice_setRoll(DICE2, dice2_roll);
                /* 一定時間になったら */
                while(!TMR0_HasOverflowOccured());
                /* フラグをクリア */
                TMR0IF = 0;
            }
            /* タイマを停止 */
            TMR0_StopTimer();
        }
    }
}

上記プログラムの中で用いられている関数について,一つずつ説明していきます.

MCCにより生成される関数

SW_GetValue関数

 今回の回路で用いているプッシュスイッチSWの状態を得るための関数です.回路の構成上,押されていたらLow(=0),離されていたらHigh(=1)を得られます.このため,押されているかを確認するときにはif(SW_GetValue()==0)とすればよいです.

TMR0_Reload関数

TMR0でカウントする数を設定するための関数です.前に説明しましたように,今回のタイマでは1カウントで2.048ms数えられます.例えば10カウント(=20.48ms)させたければ10-1=9をこの関数の引数にすれば,20.48msを数えられます.

TMR0_StartTimer関数

TMR0のカウントを開始するための関数です.

TMR0_HasOverflowOccured関数

カウントに到達したか調べるための関数です.この関数の中ではTMR0IFが1になっているか,チェックしています.なお,この関数が真を返したら必ずTMR0IFを0にする必要があります.

TMR0_StopTimer関数

TMR0のカウントを停止するための関数です.

ここまでで説明した関数はすべてMCCにより生成された関数です.これらの関数をはじめとしてMCCで生成された関数については,すべてヘッダに使い方が書かれていますのでそちらをご覧ください.

独自に追加した関数

MCCで追加される関数を組み合わせてさいころの目を表現する関数を用意しました.それがDice.cとDice.hに書きました.下に示すものがDice.hです.

#ifndef DICE_H
#define	DICE_H

/** さいころ
 * @author ashida
 */
typedef enum{
    /** さいころ1 */
    DICE1,
    /** さいころ2 */
    DICE2,
}Dice;

/** さいころの目
 * @author ashida
 */
typedef enum{
    /** 0(無点灯) */
    ROLL_ZERO = 0,
    /** 1 */
    ROLL_ONE = 1,
    /** 2 */
    ROLL_TWO = 2,
    /** 3 */
    ROLL_THREE = 3,
    /** 4 */
    ROLL_FOUR = 4,
    /** 5 */
    ROLL_FIVE = 5,
    /** 6 */
    ROLL_SIX = 6
}Roll;
/** 初期化 */
extern void Dice_initialize(void);
/** さいころの目をセットする
 * @param [in] dice さいころ
 * @param [in] roll さいころの目
 */
extern void Dice_setRoll(Dice dice, Roll roll);

#endif	/* DICE_H */

下に示すものがDice.cです.

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <xc.h>

#include "mcc_generated_files/pin_manager.h"
#include "Dice.h"

/** さいころ1の目 */
static Roll _dice1Roll;
/** さいころ2の目 */
static Roll _dice2Roll;

static void _setDice1Roll(Roll roll);
static void _setDice2Roll(Roll roll);

void Dice_initialize(void){
    _setDice1Roll(ROLL_ZERO);
    _setDice2Roll(ROLL_ZERO);
}

void Dice_setRoll(Dice dice, Roll roll){
    switch(dice){
        case DICE1:
            _setDice1Roll(roll);
            break;
        case DICE2:
            _setDice2Roll(roll);
            break;
    }
}

static void _setDice1Roll(Roll roll){
    _dice1Roll = roll;
    switch(roll){
        case ROLL_ZERO:
            DICE1_1_SetHigh();            
            DICE1_2_SetHigh();
            DICE1_3_SetHigh();
            DICE1_7_SetHigh();
            DICE1_6_SetHigh();
            DICE1_5_SetHigh();            
            DICE1_4_SetHigh();
            break;
        case ROLL_ONE:
            DICE1_1_SetHigh();            
            DICE1_2_SetHigh();
            DICE1_3_SetHigh();
            DICE1_7_SetHigh();
            DICE1_6_SetHigh();
            DICE1_5_SetHigh();            
            DICE1_4_SetLow();
            break;
        case ROLL_TWO:
            DICE1_1_SetLow();            
            DICE1_2_SetHigh();
            DICE1_3_SetHigh();
            DICE1_7_SetLow();
            DICE1_6_SetHigh();
            DICE1_5_SetHigh();            
            DICE1_4_SetHigh();
            break;
        case ROLL_THREE:
            DICE1_1_SetLow();            
            DICE1_2_SetHigh();
            DICE1_3_SetHigh();
            DICE1_7_SetLow();
            DICE1_6_SetHigh();
            DICE1_5_SetHigh();            
            DICE1_4_SetLow();
            break;
        case ROLL_FOUR:
            DICE1_1_SetLow();            
            DICE1_2_SetHigh();
            DICE1_3_SetLow();
            DICE1_7_SetLow();
            DICE1_6_SetHigh();
            DICE1_5_SetLow();            
            DICE1_4_SetHigh();
            break;
        case ROLL_FIVE:
            DICE1_1_SetLow();            
            DICE1_2_SetHigh();
            DICE1_3_SetLow();
            DICE1_7_SetLow();
            DICE1_6_SetHigh();
            DICE1_5_SetLow();            
            DICE1_4_SetLow();
            break;
        case ROLL_SIX:
            DICE1_1_SetLow();            
            DICE1_2_SetLow();
            DICE1_3_SetLow();
            DICE1_7_SetLow();
            DICE1_6_SetLow();
            DICE1_5_SetLow();            
            DICE1_4_SetHigh();
            break;
    }
            
}
static void _setDice2Roll(Roll roll){
    _dice2Roll = roll;
    switch(roll){
        case ROLL_ZERO:
            DICE2_3_SetHigh();
            DICE2_2_SetHigh();
            DICE2_1_SetHigh();
            DICE2_5_SetHigh();
            DICE2_6_SetHigh();
            DICE2_7_SetHigh();
            DICE2_4_SetHigh();
            break;
        case ROLL_ONE:
            DICE2_3_SetHigh();
            DICE2_2_SetHigh();
            DICE2_1_SetHigh();
            DICE2_5_SetHigh();
            DICE2_6_SetHigh();
            DICE2_7_SetHigh();
            DICE2_4_SetLow();
            break;
        case ROLL_TWO:
            DICE2_3_SetLow();
            DICE2_2_SetHigh();
            DICE2_1_SetHigh();
            DICE2_5_SetLow();
            DICE2_6_SetHigh();
            DICE2_7_SetHigh();
            DICE2_4_SetHigh();
            break;
        case ROLL_THREE:
            DICE2_3_SetLow();
            DICE2_2_SetHigh();
            DICE2_1_SetHigh();
            DICE2_5_SetLow();
            DICE2_6_SetHigh();
            DICE2_7_SetHigh();
            DICE2_4_SetLow();
            break;
        case ROLL_FOUR:
            DICE2_3_SetLow();
            DICE2_2_SetHigh();
            DICE2_1_SetLow();
            DICE2_5_SetLow();
            DICE2_6_SetHigh();
            DICE2_7_SetLow();
            DICE2_4_SetHigh();
            break;
        case ROLL_FIVE:
            DICE2_3_SetLow();
            DICE2_2_SetHigh();
            DICE2_1_SetLow();
            DICE2_5_SetLow();
            DICE2_6_SetHigh();
            DICE2_7_SetLow();
            DICE2_4_SetLow();
            break;
        case ROLL_SIX:
            DICE2_3_SetLow();
            DICE2_2_SetLow();
            DICE2_1_SetLow();
            DICE2_5_SetLow();
            DICE2_6_SetLow();
            DICE2_7_SetLow();
            DICE2_4_SetHigh();
            break;
    }
}

上記のプログラムではMCCにより生成されたDICE1_1_SetLowやDICE1_1_SetHighなどの関数を使い,さいころの出目を表現しています.

以上でMCCを使ったプログラムの説明を終わりにします.MCCを使えば複雑な集権機能の初期化などをスムーズに行えます.一度使ってみてください.