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

組み込みエンジニア

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

PIC16F1827はEEPROMを搭載しており電源をOFFしてもデータ保持ができます。MCCでMEMORYを追加してEEPROMにデータの読み書きを行います。実装例としてEEPROMに書き込んだデータを読み込んでLCDに表示しました。

LCDはAQM1602XA-RN-GBW(秋月電子)を使用しています。

EEPROMの使い方

PIC16F1827のEEP(MEMORY)の設定はMCCを使って行っています。MCCの使い方については下記記事にまとめています。

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

今回はMCCを使用してMEMORY・TMR0・MSSP1の設定を行っています。LCDの動作は下記記事を流用しておりEEPの処理を追加して動作確認を行っています。

PICマイコン(PIC16F1827)のI2C通信を実装する

本記事ではEEPの使い方を中心に説明しています。

System Moduleの設定

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

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

内部クロックを使用するためINTOSCを選択しています。クロック周波数は任意でもよいですが4MHz_Hzを選択しています。クロックを高速にするほど消費電流が増えてしまいます。他にも設定項目はありますがクロックの設定のみとしています。

MEMORYの設定

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

MEMORYの設定(MCC)
MEMORYの設定(MCC)

MEMORYを追加してAdd DataEE Routinesにチェックを入れます。GenerateをクリックするとFLASHとEEPROMに関する関数が追加されます。

FLASH領域においてはプログラムエリアを書き換えてしまうと動作復帰できなくなるなど致命的なバグの要因にもなるため、データを保持する目的であればEEPROMを使用することをお勧めします。

MCCが生成した関数の使用例(EEP)

#define EEP_ADRS_M  0x00
#define EEP_ADRS_C  0x80

/* EEPデータ読み込み処理 */
void EepRead(void){
    uint8_t dat;
    uint8_t chk;
    
    dat = DATAEE_ReadByte(EEP_ADRS_M);
    chk = DATAEE_ReadByte(EEP_ADRS_C); 
    
    if(dat == chk){ //二重化チェック
        if( dat == 0xFF){
            dat = 0;
        }
        btncntInit = dat;
    }
}

上の例ではEEPROM領域のアドレス0x00と0x80のEEPROMのデータを読み込んでいます。前提としてこれらのアドレスに同一のデータを書き込んでおく必要がありますが、読み込み時に2つのデータを比較してEEPROMのデータが健全であるかのチェックを行うことができます。

2つのデータが一致する場合はEEPROMのデータが健全であるとみなして変数に格納します。EEPROMのデータはイレースした場合や初期状態では0xFFとなるため0xFFが採用されないようにしています。

2つのデータを比較して健全性を確認する方法以外にも読み込んだデータの範囲チェックを行って健全性を確認する方法もあります。データが健全でない場合にシステムが異常動作しないようにデフォルト値を決めておくなど工夫が必要な場合もあります。

私の場合は2つのデータでチェックを行った後に範囲チェックする方法でソフト開発を行うことが多いのですがEEPROMがハード的に問題となったことが一度もないため範囲チェックのみ十分だと感じています。

uint8_t btncnt;
uint8_t btncntInit;

//EEP書き込みの例
if( btncnt != btncntInit){
    DATAEE_WriteByte(EEP_ADRS_M, btncnt); //EEP書き込み
    DATAEE_WriteByte(EEP_ADRS_C, btncnt); //EEP書き込み
    btncntInit = btncnt;
}

EEPROMの書き込み回数が有限であるため可能な限り不要な書き込みを防ぐことが推奨されます。上の例では2つの変数が不一致であった場合のみEEPROMのデータの書き込みを行うようにしています。

MSSP1(MSSP1)を設定する

MSSPはMaster Synchronous Serial Port の略称です。PICマイコンのI2C通信はMSSP機能に含まれています。Device Resources内のPeripherals欄のMSSP1を選択して追加します。

MSSP1の設定(MCC)
MSSP1の設定(MCC)

MSSP1を選択しSerial ProtocolをI2Cを選択するとModeやClockの設定画面が表示されます。PICマイコンがLCDに対してコマンドを送信するためMasterとなります。

Clock設定を高速にすることもできますが、高速にするほど電流が必要(抵抗値を低くする)になるためお勧めしていません。外部機器とPICマイコンの配線の長さによるインピーダンスから波形が鈍ってしまい通信不良の原因となります。

I2C通信が完了したときに割り込みを発生させることができますが特に処理する必要はないため割り込みは使用していません。

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毎にオーバーフロー割り込みが発生しますがCallback Function Rateを0x0A(10)を指定しているため10ms毎にコールバック関数が呼び出されます。

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

上記例ではmainTimer()が10ms毎にコールされます。

動作確認

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

PIC16F1827のI2CをLCDに接続しSW1をDIに接続します。SW1を押した回数をLCD上に表示します。SW1を2秒間長押しすると現在のボタンを押した回数をEEPROMに書き込みます。長押しする際もSW1を押した回数がカウントされるためEEPROMに書き込む前にカウント値を-1するようにしています。MCCでのピン設定は以下の通りです。

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

EEPROMへの書き込みが終わる(カウントが-1された後)と電源をOFFします。EEPROMに保存されているデータは電源をOFFしても保持されるため次に電源をONしたときにEEPROMのデータの確認を行いデータが消えていないことを確認します。

電源をONすると初期画面が2秒表示されます。メイン処理に遷移すると初期化時に読み込んだEEPROMのデータを表示します。LCDの1段目に「EEP DATA READ:7」と表示しているためEEPROMのデータは0x07が書き込まれています。実際にPICKIT4を使ってEEPROMのデータを確認します。

EEPROMのデータを読み込んでLCDに表示
EEPROMのデータを読み込んでLCDに表示

PICマイコンのEEPROMのデータはMPLAB X IDEを使って確認することができます。ツール上部のDevice Memory ReadをクリックするとPICマイコンに書き込まれているプログラムメモリやEEPROMを読み込むことができます。

Device Memory Readでデバイス情報を読み込む
Device Memory Readでデバイス情報を読み込む

Device Memory ReadをクリックしてPICマイコンから読み込みます。Window欄の「Target Memory Views」からEE Data Memoryを選択します。

PICマイコンのEEPROMデータの確認
PICマイコンのEEPROMデータの確認

EEPROMのデータを確認するとアドレス0x00と0x80において0x07が書き込まれていることが分かります。LCDの表示も7であったためEEPROMのデータが読み込めていることが確認できました。

EEPデータをプロテクトしている場合はプログラムの内容やEEPROMの値を確認することができません。

ソースコード全体

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

#include "mcc_generated_files/mcc.h"
#include "mcc_generated_files/examples/i2c1_master_example.h"

#define TIME_OFF -1         //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define DI_NUM 4            //DIフィルタのサンプリング数
#define	CNT_INIT_MAX 10     //10ms×10 = 100ms
#define TIME_FILTER_MAX 1
#define LCD_TIM_MAX 10
#define EEP_MAX 200

#define SLAVE_ADRS 0x3E
#define LINE1_ADRS 0x40
#define LINE2_TOP (0x40 +0x80)
#define FUNC1_SET 0x38
#define FUNC2_SET 0x39
#define INT_OSC 0x14
#define CONST_SET 0x77 //0x73
#define PWR_ICON_SET 0x54 //0x56
#define FOLLOWER_SET 0x6C
#define CLR_DISP 0x01
#define DISP_ONOFF_SET 0x0C

#define EEP_ADRS_M  0x00
#define EEP_ADRS_C  0x80

typedef struct{
	uint8_t    wp;
	uint8_t    buf[ DI_NUM ];
    uint8_t    di;
}FILT_DATA;

uint8_t CntInit;	//初期化時のみ使用
FILT_DATA DiData;
int8_t timfilter;   //DIフィルタ起動
int16_t timlcd;
int16_t timbtnwait = TIME_OFF;
uint8_t btncnt;
uint8_t btncntInit;
bool btn1hold;
uint8_t initmoji[2][16] ={"PIC16F1827-EEP","         Ver1.00"};

void mainTimer(void);
void mainApp(void);
void DiFilterInit(void);
void DiFilter(void);
void LcdInit(void);
void DspLine2Top(void);
void DspClear(void);
void DispSet(void);
void EepRead(void);

/* Main application */
void main(void)
{
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(mainTimer);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();
    
    EepRead();  //EEPのデータを読み出す
    DiFilterInit();
    LcdInit();

    while (1)
    {
        mainApp();
        DiFilter();        
    }
}
/* メインアプリ処理 */
void mainApp(void){
    
    if( DiData.di == 0){

        if( btn1hold == false ){
            btn1hold = true;
            if( timbtnwait == TIME_OFF ){
                timbtnwait = EEP_MAX;
            }
            if(++btncnt >= 100 ){
                btncnt = 0;
            }
        }
    }
    else{
        timbtnwait = TIME_OFF;
        btn1hold = false;
    }    
 
    if( timlcd == TIME_UP ){
        timlcd = LCD_TIM_MAX;
        DispSet(); //LCD表示部分をセット
    }
    if( timbtnwait == TIME_UP){
        timbtnwait = TIME_OFF;
        
        if(btncnt != 0x00){
            --btncnt;
        }
        
        if( btncnt != btncntInit){
            DATAEE_WriteByte(EEP_ADRS_M, btncnt); //EEP書き込み
            DATAEE_WriteByte(EEP_ADRS_C, btncnt); //EEP書き込み
            btncntInit = btncnt;
        }
    }
}
/* タイマ管理関数 */
void mainTimer(void){
            
    if( timlcd > TIME_UP ){
        --timlcd;
    }
    if( timfilter > TIME_UP ){
        --timfilter;
    }
    if( timbtnwait > TIME_UP ){
        --timbtnwait;
    }
}
// DIフィルタの初期化 //
void DiFilterInit(void){
	
    CntInit = CNT_INIT_MAX;
	while(CntInit > 0){  //0になるまでフィルタを実施
		DiFilter();         //DIフィルタ処理
        __delay_ms(10);     //10ms遅延させてDIフィルタ処理
        CntInit--;
		timfilter = TIME_UP;
	}    
}
//--------SWのDIフィルタ----------------------------
void DiFilter(void){
    uint8_t i;
    bool    boo = true;

    if( timfilter == TIME_UP ){
        timfilter = TIME_FILTER_MAX;

        DiData.buf[ DiData.wp ] = IO_RB3_GetValue();

        for( i = 1; i < DI_NUM; i++){
            if( DiData.buf[i-1] != DiData.buf[i] ){
                boo = false;
                break;
            }
        }

        if( boo ){ ////比較して一致したら値を採用
             DiData.di = DiData.buf[ 0 ]; 
        }

        if( ++DiData.wp >= DI_NUM){
                DiData.wp = 0;
        }
    }
}
/* LCDの初期化*/
void LcdInit(void){
    uint8_t i;
    
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, FUNC1_SET ); //8ビットバス・2LINE表示
    __delay_us(40);
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, FUNC2_SET ); //拡張コマンド
    __delay_us(40);
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, INT_OSC ); //内部周波数調整
    __delay_us(40);    
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, CONST_SET ); //コントラスト1
    __delay_us(40);    
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, PWR_ICON_SET );//コントラスト2
    __delay_us(40);   
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, FOLLOWER_SET );//フォロワー制御
    __delay_ms(250);
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, FUNC1_SET ); //拡張コマンドをオフ
    __delay_us(40);
    
    DspClear();
    
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, DISP_ONOFF_SET );
    __delay_us(40);
    
    I2C1_WriteNBytes( SLAVE_ADRS,&initmoji[0][0], sizeof(initmoji[0]));
    
    DspLine2Top();

    for( i = 0; i < sizeof(initmoji[1]); i++ ){
        I2C1_Write1ByteRegister(SLAVE_ADRS,LINE1_ADRS,initmoji[1][i]);
    }        
    __delay_ms(2000);
    DspClear();   
}
/* LCDのカーソル移動 */
void DspLine2Top(void){
    
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, LINE2_TOP );
    __delay_us(40);    
}
/* LCDの表示初期化*/
void DspClear(void){
  
    I2C1_Write1ByteRegister(SLAVE_ADRS, 0x00, CLR_DISP );
    __delay_us(40);
}
/* DispSet function add */
void DispSet(void){
    uint8_t i;
    uint8_t dsp[16];

    sprintf(dsp,"EEP DATA READ:%u", btncntInit);
    for( i = 0; i < sizeof(dsp); i++ ){
        I2C1_Write1ByteRegister(SLAVE_ADRS,LINE1_ADRS,dsp[i]);
    }
  
    DspLine2Top();
    sprintf(dsp,"btncnt:%u       ", btncnt);
    for( i = 0; i < sizeof(dsp); i++ ){
        I2C1_Write1ByteRegister(SLAVE_ADRS,LINE1_ADRS,dsp[i]);
    } 
}
/* EEPデータ読み込み処理 */
void EepRead(void){
    uint8_t dat;
    uint8_t chk;
    
    dat = DATAEE_ReadByte(EEP_ADRS_M);
    chk = DATAEE_ReadByte(EEP_ADRS_C); 
    
    if(dat == chk){ //二重化チェック
        if( dat == 0xFF){
            dat = 0;
        }
        btncntInit = dat;
    }
}
/* End of File */

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていないと使用できません。

関連リンク

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

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

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

あなたの市場価値を見いだす転職サイト【ミイダス】無料会員登録

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

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