アプリケーションノート 3577

MAXQマイクロコントローラをセクタ消去可能な(SE)フラッシュデバイスからページ消去可能な(PE)フラッシュデバイスに変換


要約: このアプリケーションノートでは、ソフトウェアエンジニアがアプリケーションをセクタ消去可能な(SE)からページ消去可能な(PE) MAXQマイクロコントローラに変換する場合に必要な基本情報について説明します。この情報は、SEとPE両方のバージョンで利用可能なMAXQフラッシュベースのマイクロコントローラに適用されます。

はじめに

このアプリケーションノートでは、一部のMAXQ7665マイクロコントローラで使用されるセクタ消去可能な(SE)フラッシュメモリとページ消去可能な(PE)フラッシュメモリの違いについて説明します。この記事では、アプリケーションをSEからPEのフラッシュバージョンに変換する基本手順についても説明します。SEフラッシュは、32kBを超えるプログラムフラッシュを備えたマイクロコントローラで利用可能であり、PEフラッシュと比較して、よりコスト効率に優れたソリューションです。

SEからPEのフラッシュデバイスに移行するのにはいくつかの理由がありますが、最も一般的な理由は、コストの削減が要求されるからであり、また現在利用可能なデバイスを使用して開発作業を開始することができることが要求されるからです。このアプリケーションノートは、できるだけ速く、かつスムーズに移行を実行するための情報を提供します。

使用しているMAXQ7665で利用可能なフラッシュタイプを確認するには、データシートを参照してください。SEとPEのデータとプログラムのフラッシュを使用する方法の詳細については、表1のアプリケーションノートを参照してください。

表1. SEとPEのフラッシュのアプリケーションノートの参照表

AN番号 アプリケーションノートの表題
3575 MAXQ7665のセクタ消去可能なプログラム/データフラッシュのインアプリケーションプログラミング(IAP)
3576 MAXQ7665のページ消去可能な(PE)プログラム/データフラッシュのインアプリケーションプログラミング(IAP)
3579 単一ワード消去のデータフラッシュを備えたMAXQ7665Cのページ消去可能な(PE)フラッシュのインアプリケーションプログラミング(IAP)

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フラッシュメモリのマップ
Table 4

表5. MAXQ7665のPEフラッシュメモリのマップ
Table 5

ユーティリティ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)を参照してください。

Figure 1. 1kB SE Data Flash - Sector Structure.
図1. 1kBのSEデータフラッシュ—セクタ構造

Figure 2. 1kB PE Data Flash - Sector/Page Structure.
図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

Figure 3. Bounded Queue with Bank Switch Flow for SE Flash.
図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

Figure 4. Bounded-Queue Flow for PE Flash.
図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プログラムフラッシュのページ消去を使用すると、プログラムフラッシュをアプリケーションとブートローダのセグメントに分割する際に優れた柔軟性が得られます。現在の設計でブートローダアプリケーションを使用していないのであれば、今が追加する良い機会です。

Figure 5. 32kB PE Program Flash - Sector/Page Structure.
図5. 32kBのPEプログラムフラッシュ—セクタ/ページ構造

図6のフローチャートは、SEとPEの両方のリフラッシュルーチンを示しています。通常、メインアプリケーションをリフラッシュするときの最初のステップは、たとえばflashEraseApplication()のようなユーザ記述関数を呼び出すことによって、メインアプリケーションのコード領域全体を消去することです。この関数は、メインアプリケーションコードを格納するのに使用されるセクタごとにflashEraseSector()を呼び出します。PEフラッシュを消去する方法も基本的には同じです。ただし、1つの呼出しによって少量のフラッシュを消去します。このため、flashEraseSector()やflashErasePage()への呼出しがより多く発生します。

問題は、フラッシュの書込みループを移植するときに発生する可能性があります。PEフラッシュの書込み関数flashWrite()は、一度に全ページ(32ワード)を書き込みます。データを書き込む方法に応じて、このセクションのコードを書き直し、flashWrite()の呼出しごとにプログラムデータの1ページをまとめて受け入れることが必要となる場合があります。アプリケーションが、上位の通信プロトコルへの影響を最小限に抑える必要がある場合、SEのflashWrite()関数の単一ワード書込みをエミュレートしたルーチンを記述することが最適の手法です。SEのリフラッシュフローをエミュレートする、更新されたPEリフラッシュのフローチャートについては、図7を参照してください。この手法を使用するときには、32ワードずつまとめて書込み要求を行う必要があるため、アプリケーション空間は常に32ワードの倍数にしなければなりません。

Figure 6. Flowcharts of Very Simple Reflash Routines.
図6. 極めて簡易なリフラッシュルーチンのフローチャート

Figure 7. Emulating SE Flash Flow.
図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;
}