今回はPIC32MX270F256BをターゲットにMPLAB X v5.45+Harmony3+XC32 v2.50を使用して、PIC32マイコンのフラッシュをEEPROMとしてユーザーデータを保存する方法について紹介します。
PICマイコンのシリーズによってはマイコン内にフラッシュ書き込み回路が内蔵されていないため、内蔵のフラッシュに単体で書き込むことができないものが多いです。一方、PIC32マイコンはフラッシュ書き込み回路が内蔵されているため、単体で書き込むことが可能です。この機能を活用することで外付けのEEPROMの代わりとして内蔵フラッシュに簡単な設定データや固定データ等を保存、読み込みすることができます。
なお、EEPROM同様にUSBメモリやSDカードのようにメモリ領域を分散して書き込む機能がないため、毎回同じメモリセルに書き込まれます。そのため、高頻度に書き換える用途ではフラッシュの該当するメモリセルのみ早期に寿命を迎える可能性があります。PIC32MXシリーズでは最低2万回の読み書きできる仕様ですが、RAMのような使い方をすると早期に寿命になり正常に読み書きできなくなります。設定データや固定データ等の書き換え回数が少ないデータ保存に使用するのが適切です。書き込みの回数に制約があるものの、読み込みは高頻度であっても問題はありません。
また、内蔵フラッシュにデータを保存する注意点として内蔵フラッシュの空き領域であることを確認する必要があります。デフォルトではMedia Start Addressは0x9D020000以降に128kbの領域で設定されています。つまり、内蔵フラッシュの半分以降がEEPROMとして使用する領域として設定されています。この場合、プログラムメモリの使用率を内蔵フラッシュの半分以下にする必要があります。プログラムメモリを多く使用する場合や逆にNVMエリアを広くする場合はプログラムメモリと重複しないようにMedia Start Addressの設定に注意してください。
重複の有無はMPLAB Xのファームを書き込む際に表示されるコンソールから領域を確認できます。
The following memory area(s) will be programmed:
@program memory:
@program memory:
start address = 0x1d000000, end address = 0x1d0083ff
Aprogram memory:
start address = 0x1d03f000, end address = 0x1d03f7ff
上記の領域に0x80000000をオフセットされた領域が実際の領域です。
@0x9d000000, end address = 0x9d0083ff
A0x9d03f000, end address = 0x9d03f7ff
つまり、内蔵フラッシュを0x1F000以上書き込むとAの領域と重複することが分かります。今回のテストコードは0x0400(1024)サイズの書き込みのため、問題ないことが分かります。
Harmony3ではNVMコンポーネントとしてライブラリが準備されています。NVMコンポーネントだけでも読み書きできますが、MEMORYコンポーネントと合わせて使用することで他のメモリでも同じ関数で読み書きできるため、汎用性高く実装できます。今回はNVMコンポーネントとMEMORYコンポーネントを合わせて使用して内蔵フラッシュに対して読み書きしてみました。
Harmony3のプロジェクトグラフは下記の通りです。
実装は下記の通りです。
app.c内に変数定義及び読み書き完了割込み関数を追加します。
#define BUFFER_SIZE 1024
uint8_t writeBuffer[BUFFER_SIZE];
uint8_t readBuffer[BUFFER_SIZE];
uint32_t blockStart = 0x0;
uint32_t nBlock = BUFFER_SIZE;
DRV_MEMORY_COMMAND_HANDLE commandHandle;
bool xfer_done = false;
uint8_t writeBuffer[BUFFER_SIZE];
uint8_t readBuffer[BUFFER_SIZE];
uint32_t blockStart = 0x0;
uint32_t nBlock = BUFFER_SIZE;
DRV_MEMORY_COMMAND_HANDLE commandHandle;
bool xfer_done = false;
void appTransferHandler
(
DRV_MEMORY_EVENT event,
DRV_MEMORY_COMMAND_HANDLE commandHandle,
uintptr_t context
)
{
switch(event)
{
case DRV_MEMORY_EVENT_COMMAND_COMPLETE:
xfer_done = true;
break;
case DRV_MEMORY_EVENT_COMMAND_ERROR:
break;
default:
break;
}
}
(
DRV_MEMORY_EVENT event,
DRV_MEMORY_COMMAND_HANDLE commandHandle,
uintptr_t context
)
{
switch(event)
{
case DRV_MEMORY_EVENT_COMMAND_COMPLETE:
xfer_done = true;
break;
case DRV_MEMORY_EVENT_COMMAND_ERROR:
break;
default:
break;
}
}
app.c内のAPP_Tasks内に読み書きのテストコードを追加します。
DRV_HANDLE memoryHandle;
memoryHandle = DRV_MEMORY_Open(DRV_MEMORY_INDEX_0, DRV_IO_INTENT_READWRITE);
if (DRV_HANDLE_INVALID == memoryHandle)
{
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "Open ERR\r\n");
}
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "Open OK\r\n");
if (DRV_HANDLE_INVALID == memoryHandle)
{
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "Open ERR\r\n");
}
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "Open OK\r\n");
DRV_MEMORY_TransferHandlerSet(memoryHandle, appTransferHandler, (uintptr_t)NULL);
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "TransferHandlerSet OK\r\n");
memset(writeBuffer,'\0',sizeof(writeBuffer));
memset(readBuffer,'\0',sizeof(readBuffer));
sprintf((char*)writeBuffer, "Hello!! ");
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "sprintf OK\r\n");
//Write Data
xfer_done = false;
DRV_MEMORY_AsyncEraseWrite(memoryHandle, &commandHandle, writeBuffer, blockStart, nBlock);
if(DRV_MEMORY_COMMAND_HANDLE_INVALID == commandHandle)
{
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "AsyncWrite ERR\r\n");
}
while(!xfer_done)
{
DRV_MEMORY_Tasks(sysObj.drvMemory0);
}
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "AsyncWrite Complited\r\n");
//Read Data
xfer_done = false;
DRV_MEMORY_AsyncRead(memoryHandle, &commandHandle, readBuffer, blockStart, nBlock);
if(DRV_MEMORY_COMMAND_HANDLE_INVALID == commandHandle)
{
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "AsyncRead ERR\r\n");
}
while(!xfer_done)
{
DRV_MEMORY_Tasks(sysObj.drvMemory0);
}
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "AsyncRead Complited\r\n");
{
DRV_MEMORY_Tasks(sysObj.drvMemory0);
}
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "AsyncRead Complited\r\n");
SYS_DEBUG_PRINT(SYS_ERROR_DEBUG, "W:%s\tR:%s\r\n",writeBuffer,readBuffer);
実際に実行した結果は下記の通りです。
今回は動作テストとして非同期関数を同期関数のように実装しました。注意点として、読み書き完了後の割込み関数appTransferHandlerはDRV_MEMORY_Tasksを実行しないと呼び出されません。main.cのSYS_Tasks内でDRV_MEMORY_Tasksが自動的に追加されているため、非同期として使用する場合には問題ありませんが、ブロックさせて同期関数のように使用する場合はDRV_MEMORY_Tasksを呼び出してメモリ処理を別途実行する必要があります。
今回のプロジェクトファイル一式はこちらにアップ(PIC32MX_EEPROM.zip)しています。Harmony3を使用することでSTM32マイコン同様に簡単に内蔵フラッシュにユーザーデータを読み書きできることが分かりました。