第15回 EEPROM(3)〜チャレンジ課題〜

EEPROM機能に関するチャレンジ課題に挑戦してみましょう!

目次

前回プログラムの問題点

前回のプログラムで、タイマー時間を管理するtimerValue変数は、EEPROMの読み出し、書き込みに合わせて1バイト(uint8_t型)に変更していました。

timerValueを秒にする場合、最大値は255ですので、255秒までしか設定できません。

保存できる値が255まで、というのはちょっと心許ないですよね。

timerValueの単位が秒の場合は4分ちょっと、分の場合は4時間ちょっとまでの設定時間になってしまう、という問題点があります。

チャレンジ課題

そこで、チャレンジ課題として、現在1バイトで保存しているデータを2バイトで保存するように変更してみたいと思います。

EEPROMは1バイト単位で扱いますので、2バイトデータを扱うには、1バイト+1バイトのデータに分解して扱う必要があります。

ちょっとややこしいところもありますので、変更の概要をまとめておきます。

EEPROMデータ形式

EEPROMは1バイト単位の保存ですので、2バイトデータは1バイトごとに分解して保存する必要があります。

複数バイトのデータを1バイト単位の記憶領域に保存する方法は大きく分けて2種類あります。

一つは下位バイトから保存する方法(リトルエンディアン)、もう一つは上位バイトから保存する方法(ビッグエンディアン)です。

例えば、0x1122という2バイトデータをEEPROMに保存する場合、下位バイトから保存する場合はEEPROMの初期データは次のようになります。

__EEPROM_DATA (0x22, 0x11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

また、上位バイトから保存する場合は次のようになります。

__EEPROM_DATA (0x11, 0x22, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

どちらもメリットデメリットがありますが、特に決めてはありません。

そこで、今回はPICマイコンはなんとなく?下位バイトから保存するケースが多いので、最初の下位バイト、上位バイトで保存する形式(リトルエンディアン)にします。

EEPROMの初期データは5秒ですので、次のようにしたいと思います。

__EEPROM_DATA (0x05, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

EEPROMデータ読み出し

上のように保存したEEPROMデータの読み出しは、次のように行います。

最初にアドレス0とアドレス1のデータを、下位1バイト、上位1バイトデータとして読み出します。

uint8_t timerValueLower = eeprom_read(0); // アドレス0から下位バイトを読み出し
uint8_t timerValueUpper = eeprom_read(1); // アドレス1から上位バイトを読み出し

このように取り出したデータは、「(上位バイト×256)+(下位バイト)」で16ビットデータとして計算できます。なお、256倍は8ビット左シフトになりますので、プログラムは次のように書くことができます。

uint16_t timerValue = ((uint16_t)timerValueUpper << 8) | timerValueLower; // 2バイトを結合して16ビットのtimerValueへ代入

EEPROMデータ書き込み

EEPROMへのデータ書き込みは読み出しと逆の操作をすればOKです。

下位1バイトは0xFFと論理積を取ればOKです。また上位1バイトは256で割り算してから0xFFと論理積を取ります。

timerValueLower = (uint8_t)(timerValue & 0xFF);        // timerValueの下位バイト
timerValueUpper = (uint8_t)((timerValue >> 8) & 0xFF); // timerValueの上位バイト

これで1バイトデータに分解できましたので、次のようにEEPROMに書き込みます。

eeprom_write(0, timerValueLower); // アドレス0に下位バイトを書き込み
eeprom_write(1, timerValueUpper); // アドレス1に上位バイトを書き込み

チャレンジ課題解答例

検討したコードを前回のプログラムに反映してみました。

なお、8ビットから16ビットに変更していますので、必要に応じて変数をuint8_tからuint16_tに変更しています。

/*
 * PICマイコン電子工作入門 応用編 第15回
 *   チャレンジ課題解答例
 *   タイマー設定値をEEPROMに保存 (2バイト版)
 */

#include <xc.h>

// PIC12F1822 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = OFF     // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will not cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

// クロック周波数指定
// __delay_ms()関数が使用する
#define _XTAL_FREQ 1000000

// EEPROM初期データ
// アドレス0にタイマー設定値の下位バイト、アドレス1に上位バイトの初期値を設定
// 次のデータは5の例
__EEPROM_DATA (0x05, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);

void main(void) {

    // PICマイコン設定
    OSCCON = 0b01011010;  // 内部クロック周波数を1MHzに設定
    ANSELA = 0b00000100;  // RA2ピンをアナログ、それ以外のピンはデジタルに設定
    TRISA  = 0b00001100;  // RA2とRA3を入力、それ以外は出力に設定(RA3ピンは設定にかかわらず入力ピン)

    // ADコンバータ設定
    ADCON0 = 0b00001001;  // RA2(AN2)をADコンバータピンに設定し、ADコンバータ機能をEbableにする
    ADCON1 = 0b10000000;  // 結果数値は右寄せ、ADコンバータクロックはFOSC/2、基準電圧はVDD

    // ブザーをOFFにする
    LATA4 = 0;

    // タイマー設定時間をEEPROMから読み出す (2バイトに変更)
    uint8_t timerValueLower = eeprom_read(0); // アドレス0から下位バイトを読み出し
    uint8_t timerValueUpper = eeprom_read(1); // アドレス1から上位バイトを読み出し
    uint16_t timerValue = ((uint16_t)timerValueUpper << 8) | timerValueLower; // 2バイトを結合して16ビットのtimerValueへ代入

    // タイマー時間をLEDの点滅回数で表現する
    //
    // 動作開始後少し待つ
    __delay_ms(500);

    // 設定時間分LEDを点滅する
    for(uint16_t i=0; i<timerValue; i++) { // iの変数型をuint16_tに変更
        LATA5 = 0;
        __delay_ms(200);
        LATA5 = 1;
        __delay_ms(200);
    }

    // 少しの間LEDをOFFにする
    LATA5 = 0;
    __delay_ms(800);

    // LEDを点灯する
    LATA5 = 1;            
    
    // スイッチが押されるまで、タイマー時間増減スイッチ処理を行う
    while( RA3 ) {
        // ADコンバータ読み取り
        GO = 1;     // 読み取り依頼
        while(GO) { // 読み取り完了待ち
        }
        
        // ADRESの数値に応じてタイマー時間の増減を行う
        //
        // タイマー時間を減らすスイッチの処理(ADRESが250未満の場合)
        if( ADRES < 250 ) {
            // タイマー時間を減らす(マイナスにならないようにする)
            if( timerValue > 0 ) {
                timerValue--;
            }

            // タイマー時間をLEDの点滅回数で表現する
            //
            // 少しの間LEDをOFFにする
            LATA5 = 0;
            __delay_ms(500);
            
            // 設定時間分LEDを点滅する
            for(uint16_t i=0; i<timerValue; i++) { // iの変数型を uint16_t に変更
                LATA5 = 0;
                __delay_ms(200);
                LATA5 = 1;
                __delay_ms(200);
            }

            // 少しの間LEDをOFFにする
            LATA5 = 0;
            __delay_ms(800);

            // LEDを点灯する
            LATA5 = 1;            
        } else {
            // タイマー時間を増やすスイッチの処理(ADRESが250以上750未満)
            if( ADRES < 750 ) {
                // タイマー時間を増やす
                timerValue++;
                
                // タイマー時間をLEDの点滅回数で表現する
                LATA5 = 0;
                __delay_ms(500);
                
                for(uint16_t i=0; i < timerValue; i++) { // i の型を uint16_t に変更
                    LATA5 = 0;
                    __delay_ms(200);
                    LATA5 = 1;
                    __delay_ms(200);
                }
                
                LATA5 = 0;
                __delay_ms(800);
                
                LATA5 = 1;
            }
        }
    }

    // タイマー設定値をEEPROMに書き込む (2バイトに変更)
    timerValueLower = (uint8_t)(timerValue & 0xFF);        // timerValueの下位バイト
    timerValueUpper = (uint8_t)((timerValue >> 8) & 0xFF); // timerValueの上位バイト
    
    eeprom_write(0, timerValueLower); // アドレス0に下位バイトを書き込み
    eeprom_write(1, timerValueUpper); // アドレス1に上位バイトを書き込み
    
    // LEDをtimerValue分点滅する
    for(uint16_t timer=0; timer<timerValue; timer++) { // timerの変数型をuint16_tに変更
        // LEDを950ms消灯する
        LATA5 = 0;
        __delay_ms(950);            
        // LEDを50ms点灯する
        LATA5 = 1;
        __delay_ms(50);
    }

    // アラーム音を3回鳴らす
    for(uint8_t i=0; i<3; i++) {
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        __delay_ms(70);
        // ピーッ
        LATA4 = 1;
        __delay_ms(70);
        LATA4 = 0;
        // 1回分の音パターンの間を少し空ける
        __delay_ms(800);
    }

    // LEDをPWM制御してスムーズな点滅制御をする
    //
    // PWM機能のピン割り当て設定
    APFCONbits.CCP1SEL = 1;     // PWM機能をRA5ピンに設定
    CCP1CONbits.CCP1M = 0b1100; // PWM機能を有効、active-highに設定
    CCP1CONbits.P1M = 0b00;     // RA2ピンはGPIOに設定

    // 周期とデューティーサイクルの設定
    //   周期は1ms
    //   デューティーサイクルは100%から開始するので1msに設定
    T2CONbits.T2CKPS = 0b00;    // プリスケーラ値を1に設定
    PR2 = 249;                  // 周期を1msに設定 (249 + 1) x 4 x 1us = 1000us = 1ms
    CCPR1L = 1000/4;            // デューティーサイクルを0.5msに設定
    CCP1CONbits.DC1B = 1000 & 0b11;

    // PWM制御スタート
    T2CONbits.TMR2ON = 1;

    // LEDをPWM制御
    // デューテー比は10%〜100%の制御にする
    while(1) {
        // 100% -> 5%の制御
        for(uint16_t duty=1000; duty>=50; duty--) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
        // 5% -> 100%の制御
        for(uint16_t duty=50; duty<=1000; duty++) {
            CCPR1L = (uint8_t) (duty / 4);    // 上位8ビット
            CCP1CONbits.DC1B = duty & 0b11; // 下位2ビット
            __delay_ms(1);
        }
    }

    // 以下の命令は実行されない
    return;
}

更新履歴

日付内容
2017.7.30新規投稿
2018.12.2誤記訂正
2025.5.20チャレンジ課題解答例追加
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
Wonder
Wonder
6 年 前

PICマイコン勉強の為、いつも参考にさせて頂いております。
チャレンジ課題の考え方としてアドレス0,アドレス1のデータを1バイトとして読出し、その値をtimerValueに代入し、演算(足し算)。書込み時は、分割して1バイトデータずつ保存するといった感じでしょうか。1バイト以上読み書き可能なライブラリがあればよいのですが、解らず、もう少しヒントを頂けないでしょうか。

管理者
管理者
返信  Wonder
6 年 前

Wonderさま、
ご質問どうもありがとうございます。

考え方としては書かれています通り、EEPROMに書き込む際には2バイトのデータを1バイトずつにバラしてアドレス0とアドレス1に保存すればOKです。また読み出す際にはアドレス0とアドレス1の2バイトのデータを読み取り、それを16ビットのtimerValueに再現すればOKです。

これを実現するには掛け算と割り算を使用してもいいですし、ビット演算子を使用してもできます。一般的には以下のビット演算子を使用するケースが多いです。

>> (右に1ビットシフト)
<< (左に1ビットシフト)

Wonder
Wonder
返信  管理者
6 年 前

ご教示ありがとうございます。
ビットシフトにて出来ました。また、256まで待てなったので下位4ビットを2ビットずつ分離し、EEPROMに読み書きも出来ました。ありがとうございました。

管理者
管理者
返信  Wonder
6 年 前

Wonderさま、

早速うまくいったとのことでよかったです!
情報がちょっと少なかったかな、と思ったのですが、C言語を使いこなされているようですね。2ビットずつ分割して、というのも面白いですね。色々と試して腕を上げていただければと思います!

目次