PICマイコン(PIC16F1827)でCMP割り込みを追加する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

PIC16F1827はコンパレータ機能を持っており基準電圧に対して入力電圧が高いか低いかを判断した結果を出力することができます。MPLAB X IDEのプラグインであるMCCでCMP割り込みを実装して動作確認を行いました。

コンパレータ(CMP)の使い方やヒステリシス特性の考え方について下記記事にまとめています。

PICマイコン(PIC16F1827)でコンパレータを実装する

本記事ではMCCで追加したCMP割り込みのソースコードを追加する方法をまとめています。動作確認のためDACで生成した模擬のAC電圧をコンパレータに取り込みます。PIC16F1827で動作確認したことについてリンクをまとめています。

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

MCCで各種機能を実装する

PIC16F1827の各種設定はMCCを使っています。MCCの使い方については下記記事にまとめています。

MPLAB Code Configurator(MCC)の追加と使い方

今回はコンパレータで使用するCMP1、FVR、AC電圧(50Hz)の生成に使用するDAC、TMR0、周波数測定に使用するTMR1の設定を行います。

広告
漠然としたキャリア形成の不安を打ち破る!

System Moduleの設定

最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

System Moduleの設定(MCC)
System Moduleの設定(MCC)

内部クロックを使用するためINTOSCを選択しています。TMR1の分解能を上げるためクロック周波数は16MHz_HFを選択しています。他にも設定項目はありますがクロックの設定のみとしています。

CMP1を設定する

CMP1をDevice Resources内のPeripherals欄のCMP1を選択して追加します。

CMP1の設定(MCC)
CMP1の設定(MCC)

CMP1を選択しEnable Comparatorにチェックを入れます。Positive Inputは基準電圧ですが、FVRを選択します。Negative Inputに入力ピンにCIN1-を選択します。

Enable Comparator Hysteresisは閾値付近で頻繁に動作しないようにヒステリシス特性を持たせるかの選択です。ヒステリシス特性はデータシートによると65mVほどです。

コンパレータ割り込みを発生させるためEnable Comparator Interruptにチェックを入れます。

MCCでコンパレータ割り込みを生成してもコールバック関数を登録する関数が生成されないためコンパレータ割り込みの処理をMCCが生成した関数に追加する必要があります。

MCCのアップデートによってコールバック関数を登録する関数が実装される可能性がありますが、2023年10月中旬時点では実装されていません。下記のMCCのソースコードに処理を追加するの項目で追加する方法をまとめています。

広告

FVRを設定する

FVRはDevice Resources内のPeripherals欄のFVRを選択しFVRをクリックして追加します。

FVRの設定(MCC)
FVRの設定(MCC)

CMPの基準電圧はFVR_buffer2に割り当てられているのでFVR_buffer2 Gainで基準電圧を設定します。1xを選択すると1.024V、2xを選択すると2.048V、4xを選択すると4.096Vになります。

DACの設定をする

DACはDevice Resources内のPeripherals欄のDACを選択しDACをクリックして追加します。

DACの設定(MCC)
DACの設定(MCC)

DACはPositive Referenceで選択した電圧を32段階で出力できる機能です。5ビットで値を制御します。DACCON1レジスタの値を変更することで出力電圧を変更することができます。電源電圧が5VのときDACCON1レジスタを0x10にすると出力電圧が2.5Vになります。

TMR0割り込みでDACの出力電圧を変更して周波数50HzのAC電圧を模擬します。50Hz(20ms)の波形を1ms毎に分割して出力電圧を変更します。

const uint8_t dac_v[20]={0x10,0x12,0x14,0x15,0x16,0x16,0x16,0x15,0x14,0x12,0x10,
                         0x0E,0xC,0x0B,0x0A,0x0A,0x0A,0x0B,0x0C,0x0E
};

DAC_SetOutput(dac_v[dacvalue]); //出力電圧を指定

基準点が2.5Vで振幅を1VのAC電圧を生成するため配列を準備します。DACは5ビット出力でVCCが5Vなので1デジット当たり0.156Vの分解能になります。振幅の1Vは6デジットとなるので6デジットを正弦波が模擬できるように分配して波形を調整します。

例ではピーク値までのデジットを正弦波に近づけるため2:2:1:1の割合で調整した値を配列にしています。

DAC_SetOutput()関数で出力電圧を指定します。引数に5ビットの値を指定しますが、5ビットを超えたビットは無効になります。

スポンサーリンク

TMR0の設定

TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。

TMR0の設定(MCC)
TMR0の設定(MCC)

Timer Clock内でクロックに関する設定を行います。Clock Sourceは内部クロックを使用しているためFOSC(FOSC/4)を指定します。PrescalerはClock Sourceに対してTMR0のカウントアップする分周比を指定します。

Timer PeriodはTMR0がオーバーフローするタイミングを設定します。1ms毎にオーバーフロー割り込みが発生します。

void CngDac(void); //タイマー0割り込みのコールバック関数
//コールバック関数の指定例
TMR0_SetInterruptHandler(CngDac); //コールバック関数を指定

TMR0_SetInterruptHandler()関数でコールバックするユーザーの関数を指定します。例ではCngDac()が1ms毎にコールされます。

/* DACによる模擬AC波形生成 */
void CngDac(void){
    DAC_SetOutput(dac_v[dacvalue]);
    if(++dacvalue >= 20){
        dacvalue = 0;
    }
}

CngDac()関数はAC電圧生成用の配列の番号を更新することでDAC出力電圧を変更しています。20回で1周期分となるので20以上でカウントを0にします。

TMR1の設定

TMR1の設定(MCC)
TMR1の設定(MCC)

TMR1の設定で周波数の測定範囲を設定します。カウントの分解能を細かくするほど周波数の測定の精度を上げることができますが測定範囲が短くなります。Timer Periodが1usになるようにClock Source及びPrescalerを設定しています。

TMR1はCMP割り込みでカウントスタートするため初期値のPeriod countを0x00にしています。次のCMP割り込みまでのTMR1の値を確認することで周波数を測定することができます。

/* コンパレータ動作時の割り込み */
void IntCmp1(void){
  TMR1_StopTimer(); //TMR1をストップ
  capbuf.data[capbuf.wp] = TMR1_ReadTimer(); //カウント値を取得
  TMR1_WriteTimer(0); //カウント値をクリア
  TMR1_StartTimer(); //TMR1をスタート
}

IntCmp1()関数はCMP割り込みが発生した時にコールする自作の関数です。TMR1_StopTimer()関数でTMR1のカウントをストップしてから、TMR1_ReadTimer()関数でTMR1のカウント値を取得します。このカウント値から周波数を判定することができます。

TMR1_WriteTimer()関数はTMR1のカウント数を指定します。引数にカウント開始の初期値を指定します。割り込み間の経過時間を測定するため0を指定します。TMR1_StartTimer()関数でTMR1をスタートして次の割り込みまでの測定を開始します。

ピンの設定

MCCのピン設定は以下の通りです。

ピンの設定(MCC)
ピンの設定(MCC)

CMP1のC12IN+は基準電源をFVRにしているため使用しません。

DACで電圧出力するためDACOUTを選択しています。DACOUTのDirectionがinputになっていますが、アナログ出力ピン設定になっているためPA2ピンからDAC電圧が出力できます。

スポンサーリンク

MCCのソースコードに処理を追加する

MCCでCMP割り込みを有効にすると割り込み関数が実装されますが、TMR0の割り込み関数を使用する例のようなコールバック関数を登録する関数が生成されません。

CMP割り込みで処理を行う場合はMCCが生成した割り込み関数内にユーザーの処理内容を追加する必要があります。

自作の関数であるIntCmp1()関数を割り込み処理に追加する方法をまとめています。

MCCの割り込み関数に関数を追加する

MCCが作成したinterrupt_manager.cに割り込みを管理するソースコードがあります。

//MCCのinterrupt_manager.cの内容
void __interrupt() INTERRUPT_InterruptManager (void)
{
    // interrupt handler
    if(INTCONbits.TMR0IE == 1 && INTCONbits.TMR0IF == 1)
    {
        TMR0_ISR();
    }
    else if(INTCONbits.PEIE == 1)
    {
        if(PIE2bits.C1IE == 1 && PIR2bits.C1IF == 1)
        {
            CMP1_ISR();
            IntCmp1(); //処理を追加
        } 
        else
        {
            //Unhandled Interrupt
        }
    }      
    else
    {
        //Unhandled Interrupt
    }
}

ソースコード内のCMP1割り込みを判定する箇所にCMP1_ISR()関数があります。この関数の直下にIntCmp1()関数を追加します。今回の例ではinterrupt_manager.cファイルに処理を追加しています。

ソースコードを追加した後、MCCの「Generate」で設定を変更するとユーザーのメンテナンスが必要である通知がOutputのMPLAB Code Configurator欄に表示されます。ユーザーが追加した箇所はマージ(結合)したファイルとして表示されるため元のMCCファイルと比較することができます。

MCCファイルとユーザーが追加したファイル(マージ)との比較
MCCファイルとユーザーが追加したファイル(マージ)との比較

IntCmp1()関数を追加しましたが、ユーザーが宣言している変数や関数と関連付けができていないためコンパイルエラーになります。次の項目で説明するヘッダーファイルを作成してMCCファイルとユーザー宣言を関連付けします。

PR:【CreatorsFactory】転職率96%!Webスクール説明会申し込み

ヘッダーファイルを生成する

MCCが生成したファイルにmain.cで宣言した関数を指定した場合、MCCが生成したinterrupt_manager.cにおいてIntCmp1()関数が見つからずコンパイルエラーになってしまいます。

ヘッダーファイルを生成しIntCmp1()関数がグローバル関数として全体で使用できるように管理する必要があります。ヘッダーファイルを生成しグローバル関数としてIntCmp1()関数を宣言します。

ヘッダーファイルの作成(Otherを選択)
ヘッダーファイルの作成(Otherを選択)

ProjectsからHeader Fileを選択して右クリックします。Newの項目欄で「Other」を選択してNew File画面に遷移します。

ヘッダーファイルの作成(Cを選択してC Header Fileを選択)
ヘッダーファイルの作成(Cを選択してC Header Fileを選択)

Categories:でCを選択し、File Types:でC Header Fileを選択して「Next」をクリックします。

ヘッダーファイルの作成(File Nameを指定)
ヘッダーファイルの作成(File Nameを指定)

File Name:にヘッダーファイルの名前を指定します。例ではmainにしています。「Finish」をクリックするとヘッダーファイルであるmain.hがHeader Files内に作成されます。

#ifndef MAIN_H //本ファイルを初めて読み込む時だけ実行
#define	MAIN_H
  //型宣言やEnum宣言などを追加
#endif	/* MAIN_H */

#ifdef MAIN_C //本ファイル専用のCソースからの読込みのみ宣言で、それ以外は参照
    #define GLOBAL
#else
    #define GLOBAL extern
#endif

GLOBAL void IntCmp1(void);

#undef GLOBAL

ヘッダーファイルの内容を初期状態から不要な部分を削除して追加編集します。MAIN_Hはファイルを作成した時に実装されています。ヘッダーファイルを始めて読み込んだ時だけ実行したい定義や型宣言などを2行目以降に追加し、#endif /* MAIN_H */の間に追加します。

MAIN_Cは専用のC言語ファイルで読み込んだ時に宣言(7行目)し、それ以外のファイルからの読み込みの場合はexternをつけて外部参照(9行目)になるようにしています。MAIN_Cはmain.cで定義して使用します。

//main.cの先頭でMAIN_Cを定義してincludeする
#define	MAIN_C	//本ファイル専用のヘッダを読み込む
#include	"main.h"
#undef	MAIN_C

例のように専用のファイルでMAIN_Cを定義してインクルードすることでmain.hのMAIN_Cが定義が有効になっている状態となり宣言することができます。

main.hをMCCのファイルで使用できるようにmcc.hのインクルード部分に追加します。

//mcc.hの#includeの一番下に追加する
#ifndef MCC_H
#define	MCC_H
//各種インクルード(省略)
#include "../main.h" //追加するインクルード
//省略
#endif	/* MCC_H */

mcc.hはHeader FilesのMCC Generated Filesカテゴリにあります。mcc.hに追加したmain.hファイルを追加します。main.hはMCC Generated Filesカテゴリよりも上の階層に配置しているため../で上の階層を参照するようにしてインクルードします。

mcc.hを追加編集した場合もマージしたファイルと元のファイルの差分を確認することができます。

開いているファイルの上部のタブをSourceからHistoryに切り替えると更新した時刻の差分を確認することができます。

スポンサーリンク

動作確認

動作確認用の回路図
動作確認用の回路図

C12IN1-にDACOUTで生成した模擬のAC電圧を入力してコンパレータの動作確認を行います。コンパレータが50Hz相当の間隔で動作するとLED1を点灯して通知します。

PB0はCMP割り込みが発生するとトグル出力するようにした測定用のピンです。PB0をオシロスコープで確認し、PICkit4を使ったデバッグによるTMR1の値を確認します。

模擬のAC電圧波形とCMPが動作した間隔の測定結果
模擬のAC電圧波形とCMPが動作した間隔の測定結果

DACOUT(PA2)の波形を確認すると50HzのAC電圧が出力できていることが確認できました。PB0はCMP割り込みが発生した時にトグル出力するためPB0のパルス変化を測定すると周波数を測定することができます。カーソル機能でPB0のパルス間隔を測定すると20ms(50Hz)でコンパレータ割り込みが発生していることが分かりました。

デバッグ機能のウォッチでTMR1の値の平均値(capbuf.avr)を確認すると20089になっています。TMR1は1カウント当たり1usになるため20.09msになります。オシロスコープの結果と一致していることが分かります。

50Hz相当のAC電圧が検出できているためLED1が点灯することも確認できました。

広告

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include "mcc_generated_files/mcc.h"

#define	MAIN_C	//本ファイル専用のヘッダを読み込む
#include	"main.h"
#undef	MAIN_C

#define CAP_MAX 4

typedef struct{
  uint8_t wp;
  uint16_t data[CAP_MAX];
  uint16_t avr;
}CAP_FILT;

const uint8_t dac_v[20]={0x10,0x12,0x14,0x15,0x16,0x16,0x16,0x15,0x14,0x12,0x10,
                         0x0E,0xC,0x0B,0x0A,0x0A,0x0A,0x0B,0x0C,0x0E
};

bool cmpflg = false;
uint8_t dacvalue;
CAP_FILT capbuf;

void CngDac(void);
void mainApp(void);

/* Main application */
void main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(CngDac);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();

    while (1)
    {   
        mainApp();
    }
}
/* メイン処理 */
void mainApp(void){
    
    if( capbuf.avr > 19600 && capbuf.avr < 20400 ){
       IO_RB3_SetHigh(); 
    }
    else{
        IO_RB3_SetLow();
    }    
}
/* DACによる模擬AC波形生成 */
void CngDac(void){
    
    DAC_SetOutput(dac_v[dacvalue]);
    if(++dacvalue >= 20){
        dacvalue = 0;
    }
}
/* コンパレータ動作時の割り込み */
void IntCmp1(void){
    uint8_t i;
    uint32_t sum=0;
    
    IO_RB0_Toggle();
    TMR1_StopTimer(); //TMR1をストップ
    capbuf.data[capbuf.wp] = TMR1_ReadTimer(); //カウント値を取得
    TMR1_WriteTimer(0); //カウント値をクリア
    TMR1_StartTimer(); //TMR1をスタート
    
    if( ++capbuf.wp >= CAP_MAX){
        capbuf.wp = 0;
    }
    
    for(i=0; i < CAP_MAX; i++){
        sum += capbuf.data[i];
    }
    capbuf.avr = (uint16_t)(sum / CAP_MAX);    
}

/* End of File */

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていることが前提となります。MCCをインストールする方法は下記記事を参考にしてください。

MPLAB Code Configurator(MCC)の追加と使い方

またMCCのソースコードに処理を追加するの項目で説明しているヘッダーファイルを追加してMCCファイルにソースコードを追加する必要があります。

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

PR:スキマ時間を有効に!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました