Kinetisマイコン(TWR-KE18F)でEEPROMの使い方をまとめてみました。こんな感じで、SWボタンで変更したLEDの色をEEPROMに記憶して、パワーサイクル後にも同じ色で点灯するのが目標です。

EEPROMの概要説明
Kinetisマイコンには、FlexMemoryという機能があり、そこで、EEPROM機能を使用することができます。少し時間を割いて、内容を調べてみたので、ここで整理してみます。
記事の後半で、SDKのサンプルコードを動かしてみますが、使用されているレジスタの機能は、ここでの説明で網羅するつもりですので、ちょっと長めになってしまうかもしれません。
FlexMemory は下の絵のように、3つのコンポーネントからなり、FlexNVM, FlexRAM,EEPROM state machineで構成されてます。

FlexNVMはFlashメモリでボードの供給電源をオフしても、情報を保持できる不揮発性のメモリになります。設定によって、FlexNVMをData Flashとするか、EEPROM(バックアップ領域)として使用するかタイプを決められます。上の図みたいに、例えば64KBのFlexNVMを半分はData Flashに、残り半分はEEPROMに使用するみたいなこともできます。
このような設定を仕様書ではパーティションを設定するみたいな表現をしています。
Data Flash (D-Flash)
センサの学習値や較正情報、ユーザのカスタマイズ情報など、電源オフにしても、リセットしたくない情報はたくさんあります。この場合は、情報を不揮発性のメモリに保存する必要があります。その点では、別にこのD-Flashを使っても、EEPROMを使っても、実現できます。
今回の記事では、扱いませんが、D-Flashは、とくに保存する値が大量にある場合やプログラム内で値の変更が頻繁にないようなケースに向いています。逆に値のアップデートを頻繁にやるような場合や、データの保持を厳密に管理したいよって場合は、EEPROMを使うと良いようです。
EEPROM BackupメモリとFlexRAMの関係
EEPROMを使用する場合には、FlexRAMが、EEPROMとして機能します。ユーザはこのFlexRAMのアドレス空間に自由にアクセスできます。ただ、これは、通常のSRAMで、揮発性メモリなので、値をフラッシュメモリにバックアップして、保存しておく必要があります。その保存先が、さっきの図のEEPROM Backupになります。
つまり、電源ON時は、値は普通にRAMに書く(いや実際にそう書いてる)ように値を書き換えたり、アクセスしたりできるけど、実はバックヤードで、RAMのデータをフラッシュにコピーして、急に電源落ちても値を保つような仕組みになっているというわけです。
ちなみにEEPROMを使用しない場合は、このFlexRAMは通常のRAMと同じように使えます。そして、EEPROMを使う場合は4KBすべて、EEPROMに割り当てる必要はなく、たとえば、32バイトのみEEPROMとして、使うなどのユーザによって、EEPROMサイズの設定ができます。
EEPROM State Machineは何をしてる?
EEPROM state machineは、EEPROMを監視して、データのバックアップをおこない、FlexNVMのEEPROM Backupの領域に値をコピーしてくれます。ユーザはEEPROM Backupには直接アクセスすることはなく、このstate machineが裏でいい感じにしてくれるという訳です。
バックアップだけではなく、システムブート時には、state machineが、バックアップ領域から値を取得して、FlexRAMに値を反映してくれます。
ユーザは通常のRAMと同じような感覚でアクセスして、EEPROMの値を簡単にゲットできます。これはState Machineのおかげです。
EEPROMバックアップサイズと信頼性
データのバックアップサイズは、実際のEEPROMのサイズより大きいです。たとえば、FlexRAMの4KBをすべて、EEPROMとして機能させる場合、最低でも16倍のバックアップ領域が必要です。つまり、64KBすべてのFlexNVMのメモリをEEPROMバックアップに割り当てる必要があります。
このEEPROMとバックアップのメモリサイズの差が大きければ大きい程、データ耐久性が向上します。
EEPROMの分割
EEPROMのメモリは分割できます。割合も自由に変えられます。例えば、4KBで半分ずつとか、1/4をAで残りをBにするとか。ただし、このときのEEPROMバックアップの分割割合は変わりません。
つまり1/4をAにすると実際EEPROMは1KBで、Bは残りの3KB。一方でバックアップをとるフラッシュメモリは等分なので、32KBずつ。結果、使用するメモリに対するバックアップサイズが変わってきます。この場合Aの方がデータの耐久性が高くなり、用途に合わせて、耐久性をカスタマイズできるような仕組みになっています。
耐久性にEEPROMサイズとバックアップの割合がどの程度影響がでるのかはkinetisのマニュアルで計算式がのっています。
EEPROMサイズやパーティションを再設定するには?
後から説明するEEPROMサイズやパーティション設定は一回設定を反映させると、設定変更ができません。どうしても再設定したいよという場合には、フラッシュメモリ上で保持されているEEPROMの設定情報などを一旦、初期化する必要があります。
プログラム上からフラッシュをクリアするには、対応するコマンドをフラッシュからではなく、RAM上にコピーしてから、実行する必要があり、面倒です。
J-Linkで無料で配布されているJ-Link Commander を使えば、”mass erase”が簡単にできますので、便利です。方法は下のとおりです。
J-Link Commanderを起動します。コンソール画面が表示されます。
”connect”と打ちます。

するとDevice名が聞かれるので”?”と打ちます。

すると下のようなWindowが表示されるので、使ってるマイコンを選択してOKします。今回は、MKE18F512xxx16(allow security)を選択しました。

次にマイコンとのインターフェースについて、聞かれるので、SWDを選択。

インタフェースのクロック周波数は何もいれずにEnterを押します。

正しく接続されると、Cortex-M4 identifiedと表示されるので、”unlock kinetis”と打ちます。

Unlocking device…O.K.と表示されれば、完了です。これでEEPROMのパーティション設定などの情報を含めフラッシュ領域がすべてクリアされます。
EEPROMのメモリマップ
下はTWR-KE18Fのメモリーマップです。

FlexRAMのアドレス
KE18Fの場合は、0x1400_0000~0x1400_0FFFにFlexRAMが割り当てられています。0xFFF なので、(4095 + 1) × 1 B = 4096B = 4KB (4 kiB)分確認できます。
前述したとおり、EEPROMのデータサイズは自由に決めらるので、例えば32バイトで設定した場合、実際のFlexNVMは先頭の0x1400_0000 ~ 0x1400_001FまでがEEPROMのメモリーになり、その後の0x1400_0x0020~0x1400_0x0FFまではアクセスするとバスエラーになるので注意が必要です。
FlexNVMのアドレス
FlexNVMは、0x1000_0000~0x1000_FFFFです。メモリサイズは、(65535+1)/1024 = 64KB(64kiB)です。
パーティションがD-Flash = 32KB, EEPROM Backup = 32KBで分けられてる仮定すると、D-Flash は 0x1000_0000~0x1000_7FFF、EEPROM Backupは0x1000_8000~0x1000_FFFFに分けられます。
EEPROMの値の読み込みは普通のRAMと同じ
読込は基本的には、通常のRAMと同じように扱えばよいです。ただ、EEPROMデータをFlashにバックアップしている間にはRAMを読みに行けないので注意は必要です。この処理が完了しているかどうかは、レジスタFTFE_FCNFG[EEERDY]で確認できます。=1ならFlexRAMを読みにいけます。
ちなみに読込は、こんな感じで読むとスマートです。
for (uint32_t i = 0; i < BUFFER_LEN; i++)
//BUFFER_LEN:配列サイズ
{
s_buffer_rbc[i] = *(volatile uint32_t *)(destAdrss + i * 4);
//s_buffer_rbc:EEPROMの値を格納する配列
//destAdrss = 0x1400_0000 flexramの先頭アドレス
//destAdrss + i*4 …uint32のデータを読んだら4バイト分ジャンプ
}
コードはuint32型のデータを配列s_buffer_rbcにFlexRAMを読みにいって値を格納する時の例です。
EEPROMの書き込みもおなじ
書き込みも同じで、通常のRAMと同じように扱えます。
while (lengthInBytes > 0U)
{
*(uint32_t *)destAdrss = *(uint32_t *)(uint32_t)s_buffer_rbc;
destAdrss += 4U;
src = &s_buffer_rbc[4];
lengthInBytes -= 4U;
while (0U == (FTFx->FCNFG & FTFx_FCNFG_EEERDY_MASK))
{
}
}
EEPROMサイズ、パーティションの設定はコマンドベース
EEPROMの設定は、FCCOBレジスタを使います。Flash Common Command Objectの略で、その名のとおり、コマンドベースでメモリコントローラを介して、フラッシュの制御ができます。
FCCOBの使い方
FCCOBは合計12バイトのレジスタで、FCCOB0, FCCOB1, … , FCCOBA, FCCOBBという感じで定義されています。アドレスの並びは、マルチバイトでの書き込みを簡単にするためにビッグエンディアンになっていて、見た目まぎらわしいですが、下のような並びになってます。

どのレジスタに何をいれるべきかは、使用するコマンドによって、違うので、都度確認する必要がありますが、FCCOB0は、コマンド入力用で決まっています。
FCCOBでEEPROMを設定する
EEPROMを設定するには0x80というコマンドをFCCOB0に入力して、FCCOB4にEEPROMサイズ、FCCOB5にFlexNVMのパーティションを設定します。

FCCOB3はリセット時にState MachineがバックアップメモリからFlexRAMに値をコピーするかどうかの設定です。ここでは0とします。
EEPROMサイズコードの指定は、下の表をみて、FCCOB4 = 111001b = 0x39と設定します。

FlexNVMのパーティションコードは、下の表をみて、FCCOB5=0x3と設定します。

つまり、プログラムでは、こんな感じです。
FTFE->FCCOB0 = 0x80U;
FTFE->FCCOB3 = 0U;
FTFE->FCCOB4 = 0x39U;
FTFE->FCCOB5 = 0x3U;
FCCOBでFlexRAMをEEPROMとして設定する
この設定はかなりシンプルです。

FTFE->FCCOB0 = 0x81U;
FTFE->FCCOB1 = 0U;
FCCOBでEEPROM設定情報をゲットする
既に設定されているEEPROMの情報を取得するのも面倒ですが、コマンドベースで行います。コマンドは、FCCOB0=0x03で、これで、指定したアドレスから8バイト分のデータを取得できます。
取得したいデータのアドレスをFCCOB1~3で指定します。

取得されたデータはFCCOB4~Bに格納されます。ビッグエンディアンなので、LSBがFCCOBBで、MSBがFCCOB4になります。
参照するアドレスは?
設定情報は、Data Flash 0 IFRで保存されています。アドレスは0x80_0000~0x80_03FF。なのでFCCOB4=0x00として、Data Flash 0 IFRから値を拾えるようにします。

EEPROMとFlexNVMの設定は、Data Flash 0 IFR内の0x3FD, 0x3FCに保存されています。ローカルアドレスでは、0x80_03FDと0x80_3FCです。

例えば、下のように0x80_03F8を先頭アドレスにして、Data Flash 0 IFRのデータを取得すると、0x3FCはFCCOBBに、0x3FDはFCCOBAに値が格納されます。

実際コードにすると、下のような感じです。
FTFE->FCCOB0=0x3U;
FTFE->FCCOB1=0x80U;
FTFE->FCCOB2=0x3U;
FTFE->FCCOB3=0xF8;
下が実際のFCCOBレジスタです。

FCCOBA=0xF9がEEPROM設定で、実際のデータは以下のフォーマットに従っています。bit0:3のEEESizeを確認すると0b1001です。

下表から照らすと、EEPROMサイズは32バイトで設定されていると分かります。

次にFlexNVMのパーティションを確認します。FCCOBB=0xF3です。DEPARTは0b0011になります。

下表からFlexNVMは32KBはData flash、32KBはEEPROM backupで設定されていることがわかりました。

FCCOBのコマンドはRAMから実行する
FCCOBのコマンドは、フラッシュメモリの設定を変更するため、コマンド実行中は、フラッシュメモリには、アクセスできません。なので実行プログラムはRAMにコピーして、RAM上からプログラムを実行しなければいけません。
FCCOBの書き換えだけでは、設定は反映されないため、レジスタ設定後にこのプロセスでもって、処理を完了させないといけません。
ここでは、長くなるので、下のサンプルコードでも利用されているAPIを紹介します。fsl_ftfx_controller.cで定義されている関数です。
static status_t ftfx_command_sequence (ftfx_config_t * config)
FCCOB設定時は、必ずこのAPIを呼んで、処理を実行させます。引数はフラッシュメモリのドライバです。
サンプルコード twrke18f_flexnvm_eeprom
EEPROMを使うためのサンプルコードはSDKのdriver_examples->flash->flexnvm_eepromにあります。(ここでは、SDK v2.7.0 TWR-KE18Fを使用)

サンプルを実行してみる
NXPが公開しているマニュアル(How to use FlexMemory as D-Flash and EEPROM in KE1xF)にサンプルコードのフローチャートがありましたので、下にのせておきます。
実行すると、コンソール上に長々テキストが出力されますが、途中二回リセットが入り、サンプルコードを合計3回実行します。

一回目。EEPROMの設定がないので、EEPROMの領域を32バイト分に設定します。そして、前半の16バイトに0を代入してます。そして、システムリセット。下がコンソールの出力結果。
FlexNVM EEprom Example Start
Flash is UNSECURE!
There is no available EEprom (FlexNVM) on this Device by default.
Example is trying to configure FlexNVM block as EEprom.
Perform a system reset
FlexNVM EEprom Example Start
Flash is UNSECURE!
EEprom Information:
EEprom Base Address: (0x10008000)
EEprom Total Size: 32 B
Make FlexRAM available for EEPROM
Now EEPROM data is read and written by accessing the FlexRAM address space
FlexRAM Base Address: (0x14000000)
Read 16 bytes data from start of EEPROM space
The first 16 bytes data in EEPROM are not all 0x00s
Program the first 16 bytes memory space of EEprom as 0x00s
Successfully Programmed Location 0x14000000 -> 0x14000010
二回目、前回にEEEPROMにいれた0を確認できれば、次に
for (uint32_t i = 0; i < 4; i++) s_buffer[i] = i + 1
で定義した値を同じ場所に16バイト(uint32_t × 4)分上書きます。
Perform a system reset
FlexNVM EEprom Example Start
Flash is UNSECURE!
EEprom Information:
EEprom Base Address: (0x10008000)
EEprom Total Size: 32 B
Make FlexRAM available for EEPROM
Now EEPROM data is read and written by accessing the FlexRAM address space
FlexRAM Base Address: (0x14000000)
Read 16 bytes data from start of EEPROM space
The first 16 bytes data in EEPROM are all 0x00s
Program a buffer(16 bytes) into the first 16 bytes memory space of EEprom
Successfully Programmed Location 0x14000000 -> 0x14000010
Perform a system reset
三回目、前回書き込んだ値を確認できれば、今度は
for (uint32_t i = 0; i < 4; i++) s_buffer[i] = 0xFFFFFFFFU;
を書き込みます。
Perform a system reset
FlexNVM EEprom Example Start
Flash is UNSECURE!
EEprom Information:
EEprom Base Address: (0x10008000)
EEprom Total Size: 32 B
Make FlexRAM available for EEPROM
Now EEPROM data is read and written by accessing the FlexRAM address space
FlexRAM Base Address: (0x14000000)
Read 16 bytes data from start of EEPROM space
The first 16 bytes data in EEPROM are what we have progrommed before
Recover the first 16 bytes memory space of EEprom as 0xFFs
End of FlexNVM EEprom Example
使用しているAPI
サンプルコードで重要な点は、
- Read resources コマンドで設定されているEEPROMサイズを取得する
- EEPROM設定コマンドでEEPROMサイズとパーティションを設定する
- FlexRAMをEEPROMに設定する
- EEPROMの値を読み書きする
です。内容は全てはじめに説明したとおりですが、サンプルコードはAPIを使用してるので、主な関数をリストアップしておきます。
フラッシュメモリドライバーを初期化
result = FLEXNVM_Init(&s_flashDriver);
s_flashDriverは、EEPROMサイズなどのフラッシュメモリの設定情報を保持した構造体です。下がその定義式ですが、Init関数は内部で、Read resource commandを使って、メンバに現状の設定値を反映させています。この関数を呼べば、ユーザは特にFCCOBレジスタを操作する必要はありません。
typedef struct _ftfx_config
{
ftfx_mem_desc_t flashDesc;
ftfx_ops_config_t opsConfig;
uint32_t flexramBlockBase; /*!< The base address of the FlexRAM/acceleration RAM */
uint32_t flexramTotalSize; /*!< The size of the FlexRAM/acceleration RAM */
uint16_t eepromTotalSize; /*!< The size of EEPROM area which was partitioned from FlexRAM */
uint16_t reserved;
function_ptr_t runCmdFuncAddr; /*!< An buffer point to the flash execute-in-RAM function. */
ftfx_ifr_desc_t ifrDesc;
} ftfx_config_t;
EEPROM設定値を読み込む関数
FLEXNVM_GetProperty(&s_flashDriver,
kFLEXNVM_PropertyEepromTotalSize,
&eepromTotalSize);
初期化関数でフラッシュメモリの情報はドライバに格納されているので、この関数は、第二引数で指定されたメモリのサイズやアドレスを第三引数の変数に代入します。
EEPROMを設定する関数
result = FLEXNVM_ProgramPartition(
&s_flashDriver,
kFTFx_PartitionFlexramLoadOptLoadedWithValidEepromData,
eepromDataSizeCode, flexnvmPartitionCode);
上が実際にパーティションを設定するコマンドです。第三引数と第四引数にEEPROMサイズとパーティション設定のためのコマンド値を設定します。
既に説明しましたが、この二つのレジスタのフォーマットに従います。。


第二引数はリセット時のEEPROMバックアップからFlexRAMに値をロードするしないのフラグで、0がロードする。1がロードしないの設定です。
EEPROM書き込み用関数
EEPROMは通常のRAMと同じように値をかけますが、API関数を使えば、最初の方に説明したバックアップ処理中の確認(レジスタFTFE_FCNFG[EEERDY])や任意のバイトサイズのパラメータの書き込みに対応してくれるので、便利です。
result = FLEXNVM_EepromWrite
(&s_flashDriver,
destAdrss,
(uint8_t *)s_buffer_rbc,
sizeof(s_buffer_rbc));
destAdrssがFlexRAM上に書き込む先頭アドレスで、s_buffer_rbcはデータ配列です。これで、s_buffer_rbcの配列を1バイトずつ書き込み、全ての値をEEPROMに書き込みをしてくれます。
冒頭のGIFは
GPIOの設定や割り込み処理の使い方などは他の記事で説明していますので、参考にしてください。他は上のサンプルコードから特別なことはしてません。
SW入力の割り込み処理毎にカウンタをインクリメントして、カウンタの値に応じて、各LEDのON/OFFの組み合わせを決めておき、LEDを点灯します。
インクリメントした値はEEPROMに保存し、電源OFF->ONでその値を読みにいき、その値に応じたLEDを点灯して、電源OFF前の状態を復帰させているだけです。
以上です、多機能なため、無駄に混乱する箇所が多いですが、基本は、RAMの読み書きと変わらないという点では、シンプルに感じました。
プログラムをRAMから実行する箇所はやや複雑なので、ハショリました;
EEPROM ばんざい 🙂