要約
このアプリケーションノートでは、ソフトウェアエンジニアがアプリケーションをセクタ消去可能な(SE)からページ消去可能な(PE) MAXQマイクロコントローラに変換する場合に必要な基本情報について説明します。この情報は、SEとPE両方のバージョンで利用可能なMAXQフラッシュベースのマイクロコントローラに適用されます。
はじめに
このアプリケーションノートでは、一部のMAXQ7665マイクロコントローラで使用されるセクタ消去可能な(SE)フラッシュメモリとページ消去可能な(PE)フラッシュメモリの違いについて説明します。この記事では、アプリケーションをSEからPEのフラッシュバージョンに変換する基本手順についても説明します。SEフラッシュは、32kBを超えるプログラムフラッシュを備えたマイクロコントローラで利用可能であり、PEフラッシュと比較して、よりコスト効率に優れたソリューションです。
SEからPEのフラッシュデバイスに移行するのにはいくつかの理由がありますが、最も一般的な理由は、コストの削減が要求されるからであり、また現在利用可能なデバイスを使用して開発作業を開始することができることが要求されるからです。このアプリケーションノートは、できるだけ速く、かつスムーズに移行を実行するための情報を提供します。
使用しているMAXQ7665で利用可能なフラッシュタイプを確認するには、データシートを参照してください。SEとPEのデータとプログラムのフラッシュを使用する方法の詳細については、表1のアプリケーションノートを参照してください。
表1. SEとPEのフラッシュのアプリケーションノートの参照表
SEとPEのフラッシュの比較
この項では、SEとPEのフラッシュの違いについて要約しています。
表2. フラッシュの構造と性能の概要
Data Flash | SE | PE |
Structure: | 2 sectors: 256W or 512W | 4 or 8 sectors: 64 pages/sector containing 1W |
Erase: | 1 Sector | 2 Pages—2 Words or 1 Page—1 Word1 |
Write: | 1 Word | 1 Word |
Program Flash | SE | PE |
Structure: | 1 or 2 sectors: 16kW or 32kW | 4 or 8 sectors: 64 pages/sector containing 32W |
Erase: | 1 Sector | 2 Pages—64 Words |
Write: | 1 Word | 32 Words |
表3. SEとPEのルーチンのマッピング
PE Flash | SE Flash | ||
Routine # | Function Name | Routine # | Function Name |
1 | flashWrite() | 15 | flashWrite() |
2 | flashErasePage() | ||
3 | flashEraseAll() | 3 | flashEraseAll() |
4 | moveDP0() | 4 | moveDP0() |
16 | flashEraseSector() | 2 | flashEraseSector() |
17 | dataFlashWrite() | 15 | flashWrite() |
18 | dataFlashWriteE()1 | ||
15 | flashWrite() | ||
19 | dataFlashErasePage() | ||
20 | dataFlashEraseSector() | 2 | flashEraseSector() |
21 | dataFlashEraseAll() | ||
22 | dataFlashReadE()1 | 4 | moveDP0() |
注
1単一ワード消去機能は、MAXQ7665Cデバイスでのみ利用可能です。詳細については、表1に示したアプリケーションノート3579を参照してください。
メモリマップの比較
表4と表5は、MAXQ7665の16kBと32kBのバージョンのフラッシュメモリマップを示しています。メモリオプションの全リストについては、データシートを参照してください。SEフラッシュには、0x5000にある第2のデータフラッシュセクタが含まれています。このデータセクタは通常、フォルト耐性のインシステムデータストレージをサポートするバンクスイッチング手法を実装する場合に使用します。PEフラッシュバージョンは、フォルト耐性のインシステムデータストレージを実装するための第2のデータセクタを必要としません。
表4. MAXQ7665のSEフラッシュメモリのマップ
表5. MAXQ7665のPEフラッシュメモリのマップ
ユーティリティROMのフラッシュルーチンの違い
ローレベルのフラッシュルーチンがSEとPEのフラッシュタイプでどのように異なるのかを把握しておくことが重要です。表6と表7は、両方のフラッシュタイプのユーティリティROMに用意されたフラッシュルーチンを示します。表8は、PEフラッシュの各関数が、それに対応するSEフラッシュの各関数にどのようにマッピングされているのかを示します。ユーティリティROMルーチンの全リストについては、使用する特定のMAXQ製品のユーザガイドを参照してください。
PEフラッシュには、データとプログラムのフラッシュメモリを管理する別々のルーチンセットがあります。このような別々のルーチンが必要となる理由は、データとプログラムのフラッシュメモリでページ構造が異なるからです。flashEraseAll()ルーチンのみが、データとプログラムの両方のフラッシュページで動作します。フラッシュのより小さなブロックを一度に消去することが可能なその他のページ消去ルーチンもあります。PEフラッシュがSEフラッシュと異なるのは、プログラムフラッシュに書き込むときに、ページ全体を一度に書き込む必要があるという点です。ユーティリティROMのフラッシュルーチンの詳細については、表1に示したアプリケーションノートを参照してください。
一度に1ワードを書き込むSEフラッシュのflashWrite()ルーチンとは異なり、PEプログラムフラッシュメモリへの書込みに使用するflashWrite()ルーチンは、一度に1ページ(32ワード)を書き込みます。コードサイズが大きくなることによって、SEプログラムフラッシュ用に当初書き込まれたソフトウェアをPEフラッシュに対応可能なよう変換する場合に問題を引き起こしてはなりません。いずれにせよ、プログラムコードは通常、大きなブロックに書き込まれます。
表6. SEフラッシュのユーティリティROMルーチン
Routine Number | Routine Name | Entry Point ROMTable = ROM[800Dh] |
Entry Point Physical Address |
2 | flashEraseSector() | ROM[ROMTable + 1] | 0x8XXX |
3 | flashEraseAll() | ROM[ROMTable + 2] | 0x8XXX |
4 | moveDP0() | ROM[ROMTable + 3] | 0x8XXX |
15 | flashWrite() | ROM[ROMTable + 14] | 0x8XXX |
表7. PEフラッシュのユーティリティROMルーチン
Routine Number | Routine Name | Entry Point ROMTable = ROM[800Dh] |
Entry Point Physical Address |
1 | flashWrite() | ROM[ROMTable] | 0x8XXX |
2 | flashErasePage() | ROM[ROMTable + 1] | 0x8XXX |
3 | flashEraseAll() | ROM[ROMTable + 2] | 0x8XXX |
4 | moveDP0() | ROM[ROMTable + 3] | 0x8XXX |
16 | flashEraseSector() | ROM[ROMTable + 15] | 0x8XXX |
17 | dataFlashWrite() | ROM[ROMTable + 16] | 0x8XXX |
18 | dataFlashWriteE() | ROM[ROMTable + 16] | 0x8XXX |
19 | dataFlashErasePage() | ROM[ROMTable + 18] | 0x8XXX |
20 | dataFlashEraseSector() | ROM[ROMTable + 19] | 0x8XXX |
21 | dataFlashEraseAll() | ROM[ROMTable + 20] | 0x8XXX |
22 | dataFlashReadE() | ROM[ROMTable + 21] | 0x8XXX |
表8. PEフラッシュとSEフラッシュのユーティリティROM関数のマッピング
PE Flash | SE Flash |
u16 flashWrite(u16 *pDest, u16 *pSrc) | u16 flashWrite(void *pAddress, u16 iData) |
u16 flashErasePage(void *) | |
u16 flashEraseSector(void *) | u16 flashEraseSector(void *) |
moveDP0 | moveDP0 |
u16 flashEraseAll(void) | u16 flashEraseAll(void) |
u16 dataFlashWrite(u16 *pAddress, u16 iData) | u16 flashWrite(void *pAddress, u16 iData) |
u16 dataFlashWriteE(u16 *pAddress, u16 iData) | u16 flashWrite(void *pAddress, u16 iData) |
u16 dataFlashErasePage(void *) | |
u16 dataFlashEraseSector(void *) | u16 flashEraseSector(void *) |
u16 dataFlashEraseAll(void) | |
u16 dataFlashReadE(u16 *pAddress) | moveDP0 |
データフラッシュ
通常、SEフラッシュ用に記述されたソフトウェアをPEフラッシュで動作するよう移植する作業は、簡単でなければなりません。2つのデータフラッシュ構造の違いを理解すれば、移植作業のための最適な手法が明らかになります。図1は、2つのセクタ(それぞれが256ワード)から構成されるSEデータフラッシュの構造を示しています。消去動作では、1セクタのすべての内容が消去されますが、書込み動作では一度に1ワードしか書き込めません。フォルト耐性のデータストレージ機構を必要とするアプリケーションにバンク切替を実装する場合には、2つのセクタが利用可能です。バンク切替を必要とするとき、有効なフラッシュデータのサイズは1セクタのみです。
図2は、8セクタから構成される1kBのPEデータフラッシュの構造を示しています。各セクタは64ページで構成され、各ページは1ワードで構成されます。PEを実行すると、連続した2ページ(2ワード)が消去されます。PEデータフラッシュの2ワードページ消去を使用すると、フォルト耐性のデータストレージ機構を構築する際に優れた柔軟性が得られます。
ユーティリティROMには、1ワード消去/書込みの動作をサポートする、MAXQ7665C用のルーチンも含まれています。flashWriteE()とflashReadE()ルーチンによって、EEPROMのような1ワード消去/書込みの機能がこのデバイスで有効になります。詳細については、MAXQ7665ユーザガイドとアプリケーションノート3579 (表1)を参照してください。
図1. 1kBのSEデータフラッシュ—セクタ構造
図2. 1kBのPEデータフラッシュ—セクタ/ページ構造
移植例:バンク切替を備えた有界キュー
以下の例では、SEデータフラッシュ用に設計されたバンク切替のアーキテクチャを備えた限定キュー手法を、PEデータフラッシュで動作するよう簡単に変更することができることを示しています。この例では、256 x 16のSEデータセクタサイズを使用しています。各セクタは、最大、32ワードの8エントリを保持します。表9は、各セクタのメモリマップを示します。図3は、エントリがSEデータフラッシュに書き込まれる仕組みを示しています。
表9. 有界キューのメモリマップの例
FQueueBank0[ ] | |
Queue Index | Data Flash |
7 | 0x40E0-0x40FF |
6 | 0x40C0-0x4FDF |
5 | 0x40A0-0x40BF |
.... | .... |
2 | 0x4040-0x405F |
1 | 0x4020-0x403F |
0 | 0x4000-0x401F |
FQueueBank1[ ] | |
Queue Index | Data Flash |
15 | 0x50E0-0x50FF |
14 | 0x50C0-0x5FDF |
13 | 0x50A0-0x50BF |
.... | .... |
10 | 0x5040-0x505F |
9 | 0x5020-0x503F |
8 | 0x5000-0x501F |

図3. SEフラッシュ用のバンクスイッチを備えた有界キューのフロー
図3の例の定数とデータの構造は、以下のように定義されています。
変数と定数
#define C_Q_BANKSIZE ( 8 ) // The number of entries in each bank #define C_Q_BANKS ( 2 ) // The number of banks #define C_Q_ERASE_THRESH ( C_Q_BANKSIZE - 2 ) #define C_Q_MAX ( C_Q_BANKSIZE * C_Q_BANKS ) #define C_Q_MAX_ID ( C_Q_MAX * 2 ) #define C_Q_DATASIZE ( 31 ) // Data size in words #define C_Q_FULL ( -1 ) #define C_Q_XSUM_ERROR ( -2 ) #define C_FLASH_EMPTY ( 0xFF ) // For this example each entry is 31 words of arbitrary data. typedef struct { u16 iData[C_Q_DATASIZE]; u8 iID; u8 iChecksum; } QENTRY; extern bool ChecksumValid(QENTRY *pEntry); extern u16 flashEraseSector(void *pAddress); extern u16 flashWrite(void *pAddress, u16 iData); extern QENTRY FQueueBank0[C_Q_MAX]; // Mapped to SE data flash sector 0 extern QENTRY FQueueBank1[C_Q_MAX]; // Mapped to SE data flash sector 1 extern u8 iQID; // ID number of the last entry extern u8 iQIndex; // Index of current entry extern u8 iBank; // Index of current flash bank QENTRY *FQueue[C_Q_BANKS] = {FQueueBank0, FQueueBank1};初期化ルーチンは、以下のとおりです。
初期化コード
/* // queueInitialize() // This routine returns the current valid entry in the queue and // sets the global variable iQID to the ID of the current valid entry. */ short queueInitialize(void) { s16 iIndex; iQID = 0; // Find the active sector for (iBank=0;iBank < C_Q_BANKS; ++iBank) { // Only the current sector will have the first entry used // and the last entry empty. if (FQueue[iBank][0].iID != C_FLASH_EMPTY && FQueue[iBank][C_Q_BANKSIZE-1].iID == C_FLASH_EMPTY) { // Find the last valid entry. for (iIndex=1;iIndex < C_Q_BANKSIZE-1; ++iIndex) { if (FQueue[iBank][iIndex].iID == C_FLASH_EMPTY) { iQID = FQueue[iBank][iIndex-1].iID; return iIndex - 1 + C_Q_BANKSIZE*iBank; } } } } // Should never get here. The queue is full, return error. return C_Q_FULL; }書込みルーチンコードを次に示します。
書込みデータコード
/* // queueWriteEntry() // Writes new entry into the flash queue. */ short queueWriteEntry(QENTRY *pEntry) { void *pDest; u16 i; if (pEntry != NULL) { // Compute the new packet ID. iQID = (iQID + 1) % C_Q_MAX_ID; // Compute the new Index. iQIndex = (iQIndex + 1) % C_Q_MAX; // Set the packet ID. pEntry->iID = iQID; // Calculate the checksum. pEntry->iChecksum = iQID; for (i = 0; i < C_Q_DATASIZE; ++i) pEntry->iChecksum += pEntry->iData[i]; // Write packet to flash. pDest = &FQueue[iQIndex / C_Q_BANKSIZE][iQIndex % C_Q_BANKSIZE]; for (i = 0; i < sizeof(QENTRY)/2; ++i) flashWrite(pDest, ((u16 *)pEntry)[i]); return true; } return false; }最後に、消去セクタのルーチンは、次に使用するセクタが確実に消去されるようにするため、あらゆる書込みの後、少なくとも一度は呼び出す必要があります。
消去セクタの保守コード
/* // queueEraseSectorMaintenance() // This routine monitors the flash sectors and, when the current sector is // almost full, it erases the next sector to free up more room. // Returns true if a sector erase occurred during the call. // // This routine must be called at least once per queueWriteEntry() call. */ bool queueEraseSectorMaintenance(void) { // If the sector is almost full then we want to try and erase the next // sector. if ((iQIndex % C_Q_BANKSIZE) > C_Q_ERASE_THRESH) { short iBank = (iQIndex / C_Q_BANKSIZE + 1) % C_Q_BANKS; // If there are restrictions on when flash can be erased // then additional conditional can be added here. // Just make sure that the next sector is erased before the current // sector is full. if (FQueue[iBank][0].iID != C_FLASH_EMPTY) { flashEraseSector(FQueue[iBank]); return true; } } return false; }これで作業は、この極めて簡単な例を256 x 16のPEデータフラッシュで使用することができるよう変換するだけになります。エントリを個別に消去することができるため、PEデータフラッシュではバンク切替は必要ありません。これによってコードが簡略化されます。表10は、更新されたメモリマップを示し、図4は、エントリがPEデータフラッシュに書き込まれる仕組みを示しています。
表10. PEフラッシュの有界キューのメモリマップの例
FLASHQueue[ ] | |
Queue Index | Data Flash Address |
7 | 0x40E0-0x40FF |
6 | 0x40C0-0x40DF |
5 | 0x40A0-0x40BF |
. . . . | . . . . |
2 | 0x4040-0x405F |
1 | 0x4020-0x403F |
0 | 0x4000-0x401F |

図4. PEフラッシュの有界キューのフロー
更新される定数とデータ構造を以下に定義します。エントリのデータ構造は同じ状態のままですが、バンク切替に使用されるコードはすべて削除されています。
変数と定数
#define C_Q_MAX ( 8 ) #define C_Q_MAX_ID ( C_Q_MAX * 2 ) #define C_Q_DATASIZE ( 31 ) // Data size in words #define C_Q_FULL ( -1 ) #define C_Q_XSUM_ERROR ( -2 ) #define C_FLASH_EMPTY ( 0xFF ) // For this example each entry is 31 words of arbitrary data. typedef struct { u16 iData[C_Q_DATASIZE]; u8 iID; u8 iChecksum; } QENTRY; extern bool ChecksumValid(QENTRY *pEntry); extern u16 flashEraseSector(void *pAddress); extern u16 flashWrite(void *pAddress, u16 iData); extern QENTRY FlashQueue[C_Q_MAX]; // Mapped to PE data flash extern u8 iQID; // ID number of the last entry extern u8 iQIndex; // Index of current entryここでも、バンク切替に対応するコードはいずれも削除されます。更新された初期化ルーチンは、以下のとおりです。
初期化コード
/* // queueInitialize() // This routine returns the current valid entry in the queue and // sets the global variable iQID to the ID of the current valid entry. */ short queueInitialize(void) { s16 iIndex; iQID = 0; // Find the last valid entry. for (iIndex=0;iIndex < C_Q_MAX-1; ++iIndex) { if (FlashQueue[iIndex].iID == C_FLASH_EMPTY) { iIndex = (iIndex + C_Q_MAX - 1) % C_Q_MAX; iQID = FlashQueue[iIndex].iID % C_Q_MAX_ID; return iIndex; } } // Should never get here.The queue is full,return error. return C_Q_FULL; }ここでも、バンク切替に対応するコードはいずれも削除されます。数行のコードを追加して、書き込む予定のエントリの次のエントリを消去します。つまり、flashWrite()をdataFlashWrite()に置き換えます。これらのステップによって、初期化のときに必ず最新のエントリが存在するようにすることができます。更新された書込みルーチンは、以下のとおりです。
書込みデータコード
/* // queueWriteEntry() // Writes new entry into the flash queue. */ short queueWriteEntry(QENTRY *pEntry) { void *pDest; u16 i; if (pEntry != NULL) { // Compute the new packet ID. iQID = (iQID + 1) % C_Q_MAX_ID; // Compute the new Index. iQIndex = (iQIndex + 1) % C_Q_MAX; // Set the packet ID. pEntry->iID = iQID; // Calculate the checksum. pEntry->iChecksum = iQID; for (i = 0; i < C_Q_DATASIZE; ++i) pEntry->iChecksum += pEntry->iData[i]; // If next entry is full erase it. There must always be at least one // empty entry so the latest entry can be located. if (FlashQueue[(iQIndex + 1) % C_Q_MAX].iID != C_FLASH_EMPTY) { // dataFlashErasePage erases two pages (2 words) at a time // so we need to call it for every other word address in the // entry. for (i = 0; i < sizeof(QENTRY)/4; ++i) dataFlashErasePage (((u16 *)&FlashQueue[(iQIndex + 1) % C_Q_MAX]) + i*2); } // Write packet to flash. // Assumption is that entry will already be erased due to previous // two lines of code. pDest = &FlashQueue[iQIndex]; for (i = 0; i < sizeof(QENTRY)/2; ++i) dataFlashWrite(pDest, ((u16 *)pEntry)[i]); return true; } return false; }queueEraseSectorMaintainance()関数は、必要でなくなったため削除されます。
プログラムフラッシュ
SEフラッシュに書き込むために記述されたインアプリケーションプログラミング(IAP)ソフトウェアをPEフラッシュで動作するように移植する作業は簡単でなければなりません。2つのプログラムフラッシュ構造の違いを理解すれば、移植作業のための最適な手法が明らかになります。MAXQ7665の32kBのSEフラッシュデバイスと、すべての16kBのSEフラッシュデバイスには、セクタが1つしかありません。消去動作では、1セクタのすべての内容が消去されますが、書込み動作では一度に1ワードしか書き込めません。図5は、8セクタから構成される32kBのPEプログラムフラッシュの構造を示しています。各セクタは64ページで構成され、各ページは32ワードで構成されます。16kBのデバイスは、4セクタのみで構成されます。ページ消去を実行すると、連続した2ページ(64ワード)が消去されます。PEプログラムフラッシュのページ消去を使用すると、プログラムフラッシュをアプリケーションとブートローダのセグメントに分割する際に優れた柔軟性が得られます。現在の設計でブートローダアプリケーションを使用していないのであれば、今が追加する良い機会です。
図5. 32kBのPEプログラムフラッシュセクタ/ページ構造
図6のフローチャートは、SEとPEの両方のリフラッシュルーチンを示しています。通常、メインアプリケーションをリフラッシュするときの最初のステップは、たとえばflashEraseApplication()のようなユーザ記述関数を呼び出すことによって、メインアプリケーションのコード領域全体を消去することです。この関数は、メインアプリケーションコードを格納するのに使用されるセクタごとにflashEraseSector()を呼び出します。PEフラッシュを消去する方法も基本的には同じです。ただし、1つの呼出しによって少量のフラッシュを消去します。このため、flashEraseSector()やflashErasePage()への呼出しがより多く発生します。
問題は、フラッシュの書込みループを移植するときに発生する可能性があります。PEフラッシュの書込み関数flashWrite()は、一度に全ページ(32ワード)を書き込みます。データを書き込む方法に応じて、このセクションのコードを書き直し、flashWrite()の呼出しごとにプログラムデータの1ページをまとめて受け入れることが必要となる場合があります。アプリケーションが、上位の通信プロトコルへの影響を最小限に抑える必要がある場合、SEのflashWrite()関数の単一ワード書込みをエミュレートしたルーチンを記述することが最適の手法です。SEのリフラッシュフローをエミュレートする、更新されたPEリフラッシュのフローチャートについては、図7を参照してください。この手法を使用するときには、32ワードずつまとめて書込み要求を行う必要があるため、アプリケーション空間は常に32ワードの倍数にしなければなりません。
図6. 極めて簡易なリフラッシュルーチンのフローチャート
図7. SEフラッシュフローのエミュレート
以下のコードは、指定されていないシリアルリンクを経由してコマンドとデータを受信する、SEフラッシュを備えたデバイスのための簡単なリフラッシュルーチンです。このルーチンは、プログラムを書き換えたフラッシュのセクタと同じセクタに配置することができないことを忘れないでください。
/* // VerySimpleReFlash() // Sector Erasable Flash // Application code resides in Sector 1 only. // Step 1. Wait for erase command, then erase flash. // Step 2. Wait for program command, then program flash one word // at a time. */ void VerySimpleReFlash() { u16 iStatus; // The status returned from flash utility ROM // calls u16 iSize; // The size of the main code to program u16 *pAddress = 0x2000; // The starting address of the main application InitializeCOMM(); // Can be CAN or UART. WaitForEraseCommand(); SlowDownWatchdogUpdate(); // If watchdog enabled set timeout > 15s. iStatus = flashEraseSector(C_ADDRESS_SECTOR_1); SendFlashErasedResponse(iStatus); UpdateWatchdog(); // Prevent timeout if (iStatus) ResetMicro(); iSize = WaitForProgramCommand(); while (iSize--) { u16 iData = GetWordFromCOMM(); iStatus = flashWrite(pAddress, iData); if (iStatus) break; ++pAddress; UpdateWatchdog(); // Prevent timeout } SendFlashWriteResponse(iStatus); ResetMicro(); }以下のコードは、PEフラッシュを備えたデバイスで使用する簡易リフラッシュルーチンの更新バージョンであり、SEフラッシュデバイスの単一ワード書込みをエミュレートします。以下のコードを使用するには、次の制限があります。プログラムするアプリケーションの開始アドレスは、ページの先頭から始まる必要があり、長さ(ワード単位)は32で割り切れる必要があります。
/* // VerySimpleReFlash() // Page Erasable Flash // Application code resides in Sector 1 only. // Step 1. Wait for erase command, then erase flash. // Step 2. Wait for program command, then program flash one word // at a time. */ void VerySimpleReFlash() { u16 iStatus; // The status returned from flash utility ROM // calls u16 iSize; // The size of the main code to program u16 *pAddress = 0x2000; // The starting address of the main application u16 i; InitializeCOMM(); // Can be CAN or UART WaitForEraseCommand(); SlowDownWatchdogUpdate(); // If watchdog enabled set timeout > 15s for (i=C_APP_SECTOR_START;i < C_APP_SECTOR_END; ++i) { iStatus = flashEraseSector(i * C_SECTOR_SIZE); // Stop on error if (iStatus) break; } SendFlashErasedResponse(iStatus); UpdateWatchdog(); // Prevent timeout if (iStatus) ResetMicro(); iSize = WaitForProgramCommand(); flashWriteEmulateInit(); // Just in case we aborted before while (iSize--) { u16 iData = GetWordFromCOMM(); iStatus = flashWriteEmulate(pAddress, iData); if (iStatus) break; ++pAddress; UpdateWatchdog(); // Prevent timeout } SendFlashWriteResponse(iStatus); ResetMicro(); } static u8 iCount = 0; // Keeps track of how many words in the buffer static u16 iBuffer[32]; // Buffer that temporarily holds data to write static u16 *pAddr; // Holds the starting address of the page to // write. /* // flashWriteEmulate() // Emulates single word write by collecting 32 words in a buffer // and calling flashWrite() when necessary. // */ u16 flashWriteEmulate(u16 *pAddress, u16 iData) { u16 i; iBuffer[iCount++] = iData; if (iCount == 0) { pAddr = pAddress; // Store the starting address of the page. } else if (iCount == 32) { iCount = 0; return flashWrite(pAddr,iBuffer); } return 0; } /* // flashWriteEmulateInit() // Resets the counter in case of any error conditions abort the write // process in the middle of a page. */ void flashWriteEmulateInit() { iCount = 0; }