PICマイコン(PIC12F675)のEEPROMの使い方の応用

組み込みエンジニア

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

PIC12F675のEEPROMは読み書きに数msの時間が必要です。ウェイトで待つ方法もありますが、プログラムの規模が大きくなるほどウェイトを持たせると不都合が発生します。ウェイトせずにモード遷移の方法で管理する方法を説明しています。

モード遷移の方法は対象の処理が待機中の場合でも他の処理が非同期に動作させることができるためウェイトを持たせたくない時は有効な方法です。

基本構成やEEPの使い方は下記記事と同様ですが、プログラムをDIフィルタとタイマー管理の方法を使って拡張した応用編です。

PICマイコン(PIC12F675)のEEPROMの使い方

PIC12F675を使ってマイコンの動きを勉強するためにPIC12F675の機能でできることについてまとめています。

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

モード遷移の方法

モード遷移の方法を使わずにEEPROMを書き込みや読み込みを行う場合はEEPROMの書き込み完了の割り込みを使用したりすることで非同期の動作を作ることはできますが、書き込みデータとチェック用データの2か所を書き込むため管理が煩雑になります。そのため割り込みを使わない方法としてモード遷移の方法を考えていきます。

ウェイトを使った場合の問題点

ウェイトを使った場合の問題点
ウェイトを使った場合の問題点

EEPROMを書き込みは約5msかかります。データをチェックのため2か所に書いていることから1度の書き込みのシーケンスが終了するまでには約10msになります。

この間メイン関数に戻ってこないとなると不都合な面もあることからEEPROMの書き込みと途中でもメイン関数に戻る構成を考える必要があります。

モードの切り替えの考え方

EEPROMをモードの切り替えの考え方
EEPROMをモードの切り替えの考え方

EEPROMの書き込みの手順はシーケンスに基づいて行う必要があります。この手順をモードで分けて管理します。

  1. アドレス指定
  2. 書き込みたいデータをセット
  3. 書き込み許可をセット
  4. シーケンススタート(0x55、0xAAを書き込み)
  5. 書き込みスタート(WR)をセット
  6. WRが0になるまで待つ

書き込むデータはチェック用も含めて2か所になるので書き込みがデータ用とチェック用のモードを準備します。準備するモードは以下の7つとします。

  1. 待機モード
  2. データ書き込みスタートモード
  3. 書き込み完了待ちモード
  4. チェック用データ書き込みスタートモード
  5. チェック用書き込み完了待ちモード
  6. 書き込み完了モード
  7. 書き込みエラーモード

各モードの処理が終わると次のモードを指定してメイン関数に戻るようにすることで次回の書き込み処理関数においては指定したモードでの処理が実行されるようになります。

PIC12F675の初期化

モード遷移の方法の他にボタンのチャタリング防止のためDIフィルタやタイマー管理の方法を追加しています。これらの方法と考え方については下記を参考にしてください。

PICマイコン(PIC12F675)のDIピンのフィルタの考え方

PICマイコン(PIC12F675)のタイマーの管理の仕方の一例

定義部分について

EEPROMを書き込むためのモードをenumの型としてEEPWR_MODEとして定義しています。enumの順番は上記の7つのモードに従って、定義しています。

typedef enum{
    EEP_WR_IDLE = 0,
    EEP_WR_START,
    EEP_WR_START_WAIT,            
    EEP_WRCHK_START,
    EEP_WRCHK_START_WAIT,
    EEP_WR_END,
    EEP_WR_ERR,
    EEP_WR_MAX
}EEPWR_MODE;
//-----------------変数定義--------------------------
EEPWR_MODE wrmode;
short timWrStart = TIME_OFF;
short timWrWait = TIME_OFF;

変数としてEEPWR_MODEの型でwrmodeを宣言しています。モードの管理はwrmodeの値を変更することで行います。

タイマー管理についても変数を準備しています。timWrWaitはEEPROMの書き込みをスタートして書き終わるまでの時間の最大待ち時間としています。

EEPROMに異常があったときにエラーを通知するために準備しています。EEPROMの書き込み時間に対して余裕を持たせてセットする必要があります。

timWrStartはボタンを長押し任意の時間が経過するとEEPROMの書き込みを実行するためのタイマーとしています。

各種レジスタの初期化とDIフィルタなどの初期化の関係

typedef struct{
    char cnt;
    char buf[ DI_NUM ];
}FILT_DATA;
//-----------------変数定義--------------------------
FILT_DATA DiData;
//-----------------メイン関数------------------------
void main(void) {
    //各種レジスタ初期化
    
    EepRead();  //EEPROMの読み込み
 
    //タイマ起動
    T1CON = 0;
    TMR1H = TIME_START_H;//タイマ初期化
    TMR1L = TIME_START_L;//タイマ初期化
    TMR1ON = 1;//タイマ1ON
    TMR1IF = 0;//割り込みクリア
    
    //DIフィルタ初期起動
    timWait = TIME_WAIT_MAX;
    while( timWait > TIME_UP){ //タイムアップするまでにDIを確定
	CLRWDT();
        mainTimer();
	DiFilter();
    }
    timWait = TIME_OFF;
    
    while(1){
        CLRWDT();
        //各種処理
    }
}

DIフィルタを実装するためFILT_DATAの型を定義して変数で宣言しています。TMR1は10msでオーバーフローするように初期値を設定しています。DIフィルタによるDI情報などメインのwhile(1)に入る前に確定したいものを初期化時に実施しています。

各種機能を実装する

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

SW1を押すと押した回数をカウントします。SW1を長押し(3秒)するとEEPROMの書き込みを行います。LED2はメイン関数に入ると点灯するようにしています。

初期化時にEEPROMの値を参照して保存されたカウント値だけのLED1を点灯/消灯させてカウント値を表現します。

EEPROMの書き込み部分を実装

//-----------eepライト処理---------------------------------
void EepWrite(void){
    
    switch(wrmode){
        case EEP_WR_IDLE:
            //待機
            break;
        case EEP_WR_START:
            GPIO2 = BIT_SET;
            GIE =BIT_CLR;       //シーケンス中に割り込みが入らないように割り込み禁止
            EEADR = 0x00;       //アドレス指定
            EEDATA = eepWrData; //書き込みたいデータをセット
            WREN = BIT_SET;     //書き込み許可をセット
            EECON2 = 0x55;      //シーケンス
            EECON2 = 0xAA;      //シーケンス
            WR = BIT_SET;       //書き込みセット 
            GIE = BIT_SET;
      //割り込み禁止を解除
            wrmode = EEP_WR_START_WAIT;
            timWrWait = TIME_WAIT_MAX;
            break;
        case EEP_WR_START_WAIT:
            if(WR==BIT_CLR || timWrWait == TIME_UP){
                timWrWait = TIME_OFF;
                wrmode = EEP_WRCHK_START;
                if(WR == BIT_SET){ //タイムアップなので異常とみなす 
                    wrmode = EEP_WR_ERR;
                }
            }
            break;
        case EEP_WRCHK_START://書き込みデータ(チェック領域)
            GIE =BIT_CLR;
            EEADR = 0x40;       //アドレス指定
            EEDATA = eepWrData; //書き込みたいデータをセット
            EECON2 = 0x55;      //シーケンス
            EECON2 = 0xAA;      //シーケンス
            WR = BIT_SET;       //書き込みセット     
            GIE =BIT_SET;
            wrmode = EEP_WRCHK_START_WAIT;
            timWrWait = TIME_WAIT_MAX;
            break;
        case EEP_WRCHK_START_WAIT:
            if(WR==BIT_CLR || timWrWait == TIME_UP){
                timWrWait = TIME_OFF;
                wrmode = EEP_WR_END;
                if(WR == BIT_SET){ //タイムアップなので異常とみなす 
                    wrmode = EEP_WR_ERR;
                }
            }            
            break;
        case EEP_WR_END:
            GPIO2 = BIT_CLR;
            WREN = BIT_CLR;     //書き込み許可をクリア
            wrmode = EEP_WR_IDLE;
            break;
        case EEP_WR_ERR:
            //必要に応じてエラーを通知する処理などを入れる
            GPIO2 = BIT_SET;
            wrmode = EEP_WR_IDLE;
            break;
    }
}

各モードによる処理が完了すると次のモードを指定して関数を抜けてメイン関数に戻ることができます。各モードではウェイトがないためモード内の処理が完了するとスムーズに次の処理に移ることができます。

モードで管理することで処理が占有することを防ぐことができるためメイン関数のループが多いほうが望まれるプログラムにおいては有効な方法であると言えます。

読み込み関数

読み込み関数はメイン関数に入る前に実行するためモードでの管理はしていません。実装例はソースコード全体で確認してください。

EEPROMに書き込んだ後にデータ読み込むような構成ならモードでの管理にすることを検討したほうが良いでしょう。

タイマー管理とDIフィルタを実装

//----------タイマー管理----------------------------------
void mainTimer(void){
    
    if( cnt10ms >=  CNT10_MAX){
        cnt10ms -=CNT10_MAX;

        if( timWait > TIME_UP ){
            --timWait;
        }
        if( timWrStart > TIME_UP ){
            --timWrStart;
        } 
    }
}

割り込み関数はTMR1が10ms毎に割り込みが発生するようにしています。割り込み関数内でcnt10msをカウントアップしています。タイマー管理個所においてCNT10_MAXは1と定義しているため10ms毎にソフトウェアタイマを更新するように管理しています。

//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){

    if( filtflg ){
	filtflg = BIT_CLR;
        DiData.buf[ DiData.cnt ] = GPIO3;
			
        if( DiData.buf[ 0 ] == DiData.buf[ 1 ] &&
            DiData.buf[ 1 ] == DiData.buf[ 2 ] &&
            DiData.buf[ 2 ] == DiData.buf[ 3 ] ){
            //比較して一致したら値を採用
            gpio3_buf = DiData.buf[ 0 ];
        }	
        
        if( ++DiData.cnt >= DI_NUM) DiData.cnt = 0;
     }
}

DIフィルタは10ms毎に動作するようにしているため割り込み関数の中でfiltflgをセットしDIフィルタの処理を行うようにしています。フラグ管理でなくタイマ管理にすると50ms毎にDIフィルタの処理したりと任意のタイミングで実行できます。

ボタン長押しで書き込み処理を行う

//--------ボタン処理----------------------------
void BtnMain(void){
    
    if( gpio3_buf == BIT_SET){
        if( btnOn == BIT_CLR ){
            btnOn = BIT_SET;
            timWrStart = TIME_BUTTON_WAIT_MAX; //ボタンの長押しをタイマ管理する
            
            if(++eepWrData >= LED_CNT_MAX){
                eepWrData = 0;
            }
        }
    }else{
        btnOn = BIT_CLR;
        timWrStart = TIME_OFF;
    }
    
    if(timWrStart == TIME_UP){ //タイムアップするとwrmodeを更新しEEPROMの書き込みがスタートする
        timWrStart = TIME_OFF;
        
        if(eepWrData != 0x00){
            --eepWrData;
        }
        wrmode = EEP_WR_START;
    }
}

ボタンを押したとき一度だけカウントアップするようにしたいのでbtnOnをフラグとして管理しています。ボタン長押しをやめるとtimWrStartをTIME_OFFとすることでタイマの更新を停止しています。

ボタンを長押してタイムアップするとwrmodeを更新するためEEPROMへの書き込みを処理がスタートします。長押しした際にカウントアップした分をカウントが増えているためカウントを戻す処理を入れています。(長押しして書き込むときはボタンを押した回数に含めたくなかったためそのようにしています。)

ソースコード全体

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

// CONFIG
#pragma config FOSC = INTRCIO
#pragma config WDTE = ON
#pragma config PWRTE = OFF
#pragma config MCLRE = OFF
#pragma config BOREN = OFF
#pragma config CP = OFF
#pragma config CPD = OFF

#include <xc.h>
//--------------定数定義----------------------------
#define _XTAL_FREQ	4000000
#define	BIT_SET	1
#define	BIT_CLR	0
#define LED_CNT_MAX 20
#define	TIME_START_H	0xD8	//タイマスタート位置 65536- 55536 = 10000
#define	TIME_START_L	0xF0	//タイマスタート位置 65536- 55536 = 10000
#define	CNT10_MAX	1	//10ms×1 = 10ms
#define	TIME_OFF -1
#define	TIME_UP 0
#define TIME_WAIT_MAX 10
#define TIME_BUTTON_WAIT_MAX 300
#define DI_NUM 4

typedef struct{
    char cnt;
    char buf[ DI_NUM ];
}FILT_DATA;

typedef enum{
    EEP_WR_IDLE = 0,
    EEP_WR_START,
    EEP_WR_START_WAIT,            
    EEP_WRCHK_START,
    EEP_WRCHK_START_WAIT,
    EEP_WR_END,
    EEP_WR_ERR,
    EEP_WR_MAX
}EEPWR_MODE;
//-----------------変数定義--------------------------
unsigned char cnt10ms = 0;
char timWait = TIME_OFF;
short timWrStart = TIME_OFF;
short timWrWait = TIME_OFF;
unsigned char btnOn;
unsigned char eepData;
unsigned char eepChkData;
unsigned char eepWrData = 0;
FILT_DATA DiData;
char filtflg = BIT_CLR;
EEPWR_MODE wrmode;
char gpio3_buf;

//-----------------関数宣言--------------------------
void EepWrite(void);
void EepRead(void);
void mainTimer(void);
void DiFilter(void);
void BtnMain(void);
//-----------------メイン関数------------------------
void main(void) {
    ADCON0 = 0;		//AD変換しない
    ANSEL = 0;		//アナログモードは使用しない
    CMCON = 0x07;       //コンパレータは使用しない
    INTCON = 0xC0;      //割り込みを許可PIE1割り込み許可
    PIE1 = 0x01;	//タイマ1割り込みを許可
    TRISIO = 0x08;      //GP3入力、その他出力
    GPIO = 0x00;	//lowに設定
    OPTION_REG = 0x0f;  //プリスケーラ128 WDTで使用
    
    EepRead();  //EEPROMの読み込み
    GPIO2 = BIT_CLR;
    //タイマ起動
    T1CON = 0;
    TMR1H = TIME_START_H;//タイマ初期化
    TMR1L = TIME_START_L;//タイマ初期化
    TMR1ON = 1;//タイマ1ON
    TMR1IF = 0;//割り込みクリア
    //DIフィルタ初期起動
    timWait = TIME_WAIT_MAX;
    while( timWait > TIME_UP){
	CLRWDT();
        mainTimer();
	DiFilter();
    }
    timWait = TIME_OFF;
    GPIO1 = BIT_SET;
    
    while(1){
        CLRWDT();
        mainTimer();
        DiFilter();
        EepWrite();
        BtnMain();
    }
}
//-----------eepライト処理---------------------------------
void EepWrite(void){
    
    switch(wrmode){
        case EEP_WR_IDLE:
            //待機
            break;
        case EEP_WR_START:
            GPIO2 = BIT_SET;
            GIE =BIT_CLR;       //シーケンス中に割り込みが入らないように割り込み禁止
            EEADR = 0x00;       //アドレス指定
            EEDATA = eepWrData; //書き込みたいデータをセット
            WREN = BIT_SET;     //書き込み許可をセット
            EECON2 = 0x55;      //シーケンス
            EECON2 = 0xAA;      //シーケンス
            WR = BIT_SET;       //書き込みセット 
            GIE = BIT_SET;      //割り込み禁止を解除
            wrmode = EEP_WR_START_WAIT;
            timWrWait = TIME_WAIT_MAX;
            break;
        case EEP_WR_START_WAIT:
            if(WR==BIT_CLR || timWrWait == TIME_UP){
                timWrWait = TIME_OFF;
                wrmode = EEP_WRCHK_START;
                if(WR == BIT_SET){ //タイムアップなので異常とみなす 
                    wrmode = EEP_WR_ERR;
                }
            }
            break;
        case EEP_WRCHK_START://書き込みデータ(チェック領域)
            GIE =BIT_CLR;
            EEADR = 0x40;       //アドレス指定
            EEDATA = eepWrData; //書き込みたいデータをセット
            EECON2 = 0x55;      //シーケンス
            EECON2 = 0xAA;      //シーケンス
            WR = BIT_SET;       //書き込みセット     
            GIE =BIT_SET;
            wrmode = EEP_WRCHK_START_WAIT;
            timWrWait = TIME_WAIT_MAX;
            break;
        case EEP_WRCHK_START_WAIT:
            if(WR==BIT_CLR || timWrWait == TIME_UP){
                timWrWait = TIME_OFF;
                wrmode = EEP_WR_END;
                if(WR == BIT_SET){ //タイムアップなので異常とみなす 
                    wrmode = EEP_WR_ERR;
                }
            }            
            break;
        case EEP_WR_END:
            GPIO2 = BIT_CLR;
            WREN = BIT_CLR;     //書き込み許可をクリア
            wrmode = EEP_WR_IDLE;
            break;
        case EEP_WR_ERR:
            //必要に応じてエラーを通知する処理などを入れる
            GPIO2 = BIT_SET;
            wrmode = EEP_WR_IDLE;
            break;
    }
}
//-----------eepリード処理---------------------------------
void EepRead(void){
    unsigned char i;
    
    if( WRERR == BIT_CLR){
        EEADR = 0x00;       //アドレス指定
        RD = BIT_SET;       //読み込み開始
        while(RD){
            //RDが0になるまで待つ
        }
        eepData = EEDATA;   //読み出しデータをセット
        //読み込みデータ(チェック領域)
        EEADR = 0x40;       //アドレス設定
        RD = BIT_SET;       //読み込み開始
        while(RD){
            //RDが0になるまで待つ
        }
        eepChkData = EEDATA;   //読み出しデータをセット
        
        if(eepData == eepChkData){  //データをチェック領域のものと比較
            if(eepData > LED_CNT_MAX){
                eepWrData = 0;
            }else{
                eepWrData = eepData;
            }

            for( i = 0; i < eepWrData; i++){
                CLRWDT();
                GPIO2 = BIT_SET;
                __delay_ms(200);
                GPIO2 = BIT_CLR;
                __delay_ms(200);
            }
        }else{
            eepWrData = 0;
        }
    }else{
        WRERR = 0;
        eepWrData = 0;
    }
}
//-----------割り込み------------------------------------
void __interrupt() intr(void){

    if( TMR1IF == BIT_SET && TMR1IE == BIT_SET ){
        ++cnt10ms;
        filtflg = BIT_SET;
        TMR1H = TIME_START_H;//タイマ初期化
        TMR1L = TIME_START_L;//タイマ初期化
        TMR1IF = 0;//割り込みクリア
    }
}
//----------タイマー管理----------------------------------
void mainTimer(void){
    
    if( cnt10ms >=  CNT10_MAX){
        cnt10ms -=CNT10_MAX;

        if( timWait > TIME_UP ){
            --timWait;
        }
        if( timWrStart > TIME_UP ){
            --timWrStart;
        } 
    }
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){

    if( filtflg ){
	filtflg = BIT_CLR;
        DiData.buf[ DiData.cnt ] = GPIO3;
			
        if( DiData.buf[ 0 ] == DiData.buf[ 1 ] &&
            DiData.buf[ 1 ] == DiData.buf[ 2 ] &&
            DiData.buf[ 2 ] == DiData.buf[ 3 ] ){
            //比較して一致したら値を採用
            gpio3_buf = DiData.buf[ 0 ];
        }	
        
        if( ++DiData.cnt >= DI_NUM) DiData.cnt = 0;
     }
}
//--------ボタン処理----------------------------
void BtnMain(void){
    
    if( gpio3_buf == BIT_SET){
        if( btnOn == BIT_CLR ){
            btnOn = BIT_SET;
            timWrStart = TIME_BUTTON_WAIT_MAX; //ボタンの長押しをタイマ管理する
            
            if(++eepWrData >= LED_CNT_MAX){
                eepWrData = 0;
            }
        }
    }else{
        btnOn = BIT_CLR;
        timWrStart = TIME_OFF;
    }
    
    if(timWrStart == TIME_UP){
        timWrStart = TIME_OFF;
        
        if(eepWrData != 0x00){
            --eepWrData;
        }
        wrmode = EEP_WR_START;
    }
}
//---------------------end file----------------------------

関連リンク

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

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

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

TECH::CAMPプログラミング教養【無料体験会】

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

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