pic24bootloader

2019-04-15 12:21发布

//PIC24系列的串行自举(Bootloader)代码设计如下:
//文件名:boot.c,该文件调用memory.c和C30编译器系统配置文件config.h。
//本Boot代码兼容AN851通信协议,是在PIC24F和PIC16/18 的AN851基础上开发出来
//的最新版本。
#include "PIC24F Serial Bootloaderconfig.h"
//全局变量
WORD responseBytes;        //命令响应的字节个数
DWORD_VAL sourceAddr;    //通用变量表
DWORD_VAL userReset;    //用户代码复位向量
DWORD_VAL userTimeout; //进入Boot的溢出时间
WORD userResetRead;        //缓冲池,用于重定位用户复位向量
//变量
#ifdef USE_RUNAWAY_PROTECT
volatile WORD writeKey1 = 0xFFFF;
volatile WORD writeKey2 = 0x5555;
volatile WORD keyTest1 = 0x0000;
volatile WORD keyTest2 = 0xAAAA;
#endif
//发送/接收缓存
BYTE buffer[MAX_PACKET_SIZE+1];
//配置位的设置,相关设置可以根据用户需求进行修改。
#ifndef DEV_HAS_CONFIG_BITS
#ifdef DEV_HAS_USB
_CONFIG1(JTAGEN_OFF & GWRP_OFF & ICS_PGx2 & FWDTEN_OFF)
_CONFIG2(POSCMOD_HS & FNOSC_PRIPLL & PLLDIV_DIV2 & PLL_96MHZ_ON)
#else
_CONFIG1(JTAGEN_OFF & GWRP_OFF & ICS_PGx2 & FWDTEN_OFF)
_CONFIG2(POSCMOD_HS & FNOSC_PRIPLL)
#endif
#else
_FOSCSEL(FNOSC_PRIPLL)
_FOSC(POSCFREQ_MS & POSCMOD_XT)
_FWDT(FWDTEN_OFF)
_FICD(ICS_PGx3)
#endif
//函数名int main(),使能32位定时器2/3,使能UART。初始化主程序和主循环。
int main()
{
    DWORD_VAL delay;
    //配置Boot的入口延时
    sourceAddr.Val = DELAY_TIME_ADDR;    //Boot定时器的地址
    delay.Val = ReadLatch(sourceAddr.word.HW, sourceAddr.word.LW); //读BL溢出时间
    //配置用户复位向量
    sourceAddr.Val = USER_PROG_RESET;
    userReset.Val = ReadLatch(sourceAddr.word.HW, sourceAddr.word.LW);
    //为防止Boot出错,如果不使用用户复位向量,则复位到Bool开始执行。
    if(userReset.Val == 0xFFFFFF)
    {
        userReset.Val = BOOT_ADDR_LOW;
    }
    userResetRead = 0;
    //如果溢出数值为0,则判断复位状态。
    if(delay.v[0] == 0)
    {
        //如果芯片从复位状态返回,则Boot是关闭的,系统直接调用用户代码。
//在其他状态下,将直接进入Boot或者从用户代码调用Boot。
        //otherwise assume the BL was called from use code and enter BL
        if(RCON & 0xFED3)
        {
            //如果Boot关闭,则系统跳转到用户代码区。
            ResetDevice(userReset.Val);
        }
        else
        {
            delay.Val = 0xFF;
        }
    }
    T2CONbits.TON = 0;
    T2CONbits.T32 = 1; //配置定时器2/3作为32位定时器,每个时钟周期加1
    IFS0bits.T3IF = 0;  //清除定时器3中断标志
    IEC0bits.T3IE = 0;  //关闭定时器3中断
    if((delay.Val & 0x000000FF) != 0xFF)      //将分钟转换成定时器计数值
    {
        delay.Val = ((DWORD)(FCY)) * ((DWORD)(delay.v[0]));
        PR3 = delay.word.HW;              //设置定时器的溢出数值
        PR2 = delay.word.LW;
        TMR2 = 0;
        TMR3 = 0;
        T2CONbits.TON=1;                //使能定时器
    }
#ifdef DEV_HAS_PPS                  //如果使用PPS部分,影射UART的I/O口
    ioMap();
#endif
#ifdef UTX_ANA                      //配置 UART 引脚作为数字I/O.
    UTX_ANA = 1;
#endif
#ifdef URX_ANA
    URX_ANA = 1;
#endif
    // 配置UART串行口:无奇偶效验位,1位停止位,自适应波特率,轮询模式。    UxMODEbits.UARTEN = 1;                //使能 uart
#ifdef USE_AUTOBAUD
    UxMODEbits.ABAUD = 1;            //使用自适应波特率
#else
    UxBRG = BAUDRATEREG;
#endif
#ifdef USE_HI_SPEED_BRG
    UxMODEbits.BRGH = 1;            //使用高速模式
#endif
    UxSTA = 0x0400;                     //使能发送端TX
    while(1)
    {
#ifdef USE_RUNAWAY_PROTECT
        writeKey1 = 0xFFFF;        //修改关键参数确保正确的编程流程            writeKey2 = 0x5555;
#endif
        GetCommand();               //从UART口获取命令
#ifdef USE_RUNAWAY_PROTECT
        writeKey1 += 10;           //修改关键参数确保正确的编程流程
        writeKey2 += 42;
#endif
        HandleCommand();             //处理命令
        PutResponse(responseBytes);       //响应命令
    }//结束 while(1)
}//结束 main(void)

//函数名:void GetCommand(),用于轮询UART口,接收命令,接收缓冲区是buffer[1024]。
void GetCommand()
{
    BYTE RXByte;
    BYTE checksum;
    WORD dataCount;
    while(1)
    {
#ifndef USE_AUTOBAUD
        GetChar(&RXByte);   //获取第一个STX
        if(RXByte == STX)
        {
#else
        AutoBaud();            //根据第一个STX计算波特率大小
        RXByte = UxRXREG;    //冗余读
#endif
            T2CONbits.TON = 0;  //关闭定时器,该定时器用于数据接收
            GetChar(&RXByte);    //读2个字节
            if(RXByte == STX)     //2 STX, 数据部分
            {
                checksum = 0;      //数据效验和清零
                dataCount = 0;
                while(dataCount <= MAX_PACKET_SIZE+1)     //最大的接收字节数
                {
                    GetChar(&RXByte);
                    switch(RXByte)
                    {
                    case STX:                     //传输开始 STX
                            checksum = 0;
                        dataCount = 0;
                        break;
                    case ETX:                    //传输结束ETX
                            checksum = ~checksum +1;  //测试效验和
                        Nop();
                        if(checksum == 0) return;    //如果效验和正确,系统返回
                        dataCount = 0xFFFF;        //否则,重新启动
                        break;
                    case DLE:                    //如果DLE, 获取下一个数据
                            GetChar(&RXByte);
                    default:                        //获取数据放入缓存器中
                            checksum += RXByte;
                        buffer[dataCount++] = RXByte;
                        break;
                    }//结束 switch(RXByte)
                }//结束 while(byteCount <= 1024)
            }//结束 if(RXByte == STX)
#ifndef USE_AUTOBAUD
        }//结束 if(RXByte == STX)
#endif
    }//结束 while(1)
}//结束 GetCommand()
//函数名:void HandleCommand(),用于处理冲主机接收到的命令。
void HandleCommand()
{
    BYTE Command;
    BYTE length;
    //在EE和在配置字读/写中使用的变量
#if (defined(DEV_HAS_EEPROM) || defined(DEV_HAS_CONFIG_BITS))
    WORD i=0;
    WORD_VAL temp;
    WORD bytesRead = 0;
#endif
    Command = buffer[0];              //从缓冲区获得命令
    length = buffer[1];                  //从缓冲区获得数据的长度
    if(length == 0x00)               //复位命令
    {
        UxMODEbits.UARTEN = 0;  //关闭UART
        ResetDevice(userReset.Val);
    }
    //从缓冲区获得24位的地址
    sourceAddr.v[0] = buffer[2];
    sourceAddr.v[1] = buffer[3];
    sourceAddr.v[2] = buffer[4];
    sourceAddr.v[3] = 0;
#ifdef USE_RUNAWAY_PROTECT
    writeKey1 |= (WORD)sourceAddr.Val;        //修改关键参数确保程序流程的正确执行
    writeKey2 =  writeKey2 << 1;
#endif
    //处理命令
    switch(Command)
    {
    case RD_VER:                    //读版本号
            buffer[2] = MINOR_VERSION;
        buffer[3] = MAJOR_VERSION;
        responseBytes = 4; //set length of reply
        break;
    case RD_FLASH:                //读Flash存储区
            ReadPM(length, sourceAddr);
        responseBytes = length*PM_INSTR_SIZE + 5; //设置接收的长度                           break;
    case WT_FLASH:                  //写Flash存储区
#ifdef USE_RUNAWAY_PROTECT
            writeKey1 -= length;        //修改关键参数确保程序流程的正确执行
        writeKey2 += Command;
#endif
        WritePM(length, sourceAddr);
        responseBytes = 1;         //设置响应命令的长度
        break;
    case ER_FLASH:                //擦除Flash存储区
#ifdef USE_RUNAWAY_PROTECT
            writeKey1 += length;    //修改关键参数确保程序流程的正确执行
        writeKey2 -= Command;
#endif
        ErasePM(length, sourceAddr);
        responseBytes = 1;        //设置响应命令的长度
        break;
#ifdef DEV_HAS_EEPROM
    case RD_EEDATA:            //读EEPROM
            //如果芯片有板上EEPROM, 则允许读EE
            //读EEPROM的字节长度
            while(i < length*2)
            {
                temp.Val = ReadLatch(sourceAddr.word.HW,
                                     sourceAddr.word.LW);
                buffer[5+i++] = temp.v[0];
                buffer[5+i++] = temp.v[1];
                sourceAddr.Val += 2;
            }
        responseBytes = length*2 + 5; //设置响应命令的长度
        break;
    case WT_EEDATA:             //写EEPROM
            //写EEPROM字节的长度
            while(i < length*2)
            {
                temp.byte.LB = buffer[5+i++];  //装载写入的数据
                temp.byte.HB = buffer[5+i++];
                WriteLatch(sourceAddr.word.HW,sourceAddr.word.LW,
                           0, temp.Val);     //写入数据到寄存器中
#ifdef USE_RUNAWAY_PROTECT
                writeKey1++;
                writeKey2--;
                //配置应用程序保护测试键值
                keyTest1 =(((0x0009 | (WORD)(sourceAddr.Val-i)) -
                            length) + i/2) - 5;
                keyTest2 = (((0x557F << 1) + WT_EEDATA) - i/2) + 6;
                //初始化写序列
                WriteMem(EE_WORD_WRITE);
                writeKey1 += 5; //修改关键参数确保程序流程的正确执行
                writeKey2 -= 6;
#else
                //初始化写入序列
                WriteMem(EE_WORD_WRITE);
#endif
                sourceAddr.Val +=2;
            }
        responseBytes = 1;                      //设置响应命令的长度
        break;
#endif
#ifdef DEV_HAS_CONFIG_BITS
    case RD_CONFIG:                          //读存储区的配置字
            while(bytesRead < length)                //读配置存储器中的字节长度
            {
                //read flash
                temp.Val = ReadLatch(sourceAddr.word.HW, sourceAddr.word.LW);
                buffer[bytesRead+5] = temp.v[0];       //将读出的数据放入缓存
                bytesRead++;
                sourceAddr.Val += 2;               //地址数值自动加2
            }
        responseBytes = length + 5;
        break;
    case WT_CONFIG:                        //写存储器配置
            while(i < length)                       //写配置字字节长度
            {
                temp.byte.LB = buffer[5+i++];       //装载要写入的数据
                temp.byte.HB = 0;
#ifdef USE_RUNAWAY_PROTECT
                writeKey1++;
                writeKey2--;
#endif       //确保配置字写入是在内部配置存储空间执行
                if(sourceAddr.Val >= CONFIG_START &&
                        sourceAddr.Val <= CONFIG_END)
                {
                    TBLPAG = sourceAddr.byte.UB;
                    __builtin_tblwtl(sourceAddr.word.LW,temp.Val);
#ifdef USE_RUNAWAY_PROTECT
                    //设置程序流程,保护测试键值
                    keyTest1 =(((0x0009 | (WORD)(sourceAddr.Val-i*2)) -
                                length) + i) - 5;
                    keyTest2 = (((0x557F << 1) + WT_CONFIG) - i) + 6;
                    //初始化写入序列
                    WriteMem(CONFIG_WORD_WRITE);
                    writeKey1 += 5;  //修改键值确保程序正确的执行
                    writeKey2 -= 6;
#else
                    //初始化写入序列
                    WriteMem(CONFIG_WORD_WRITE);
#endif
                }//结束 if(sourceAddr.Val...)
                sourceAddr.Val +=2;
            }//结束 while(i < length)
        responseBytes = 1;            //设置响应命令的长度
        break;
#endif
    case VERIFY_OK:
#ifdef USE_RUNAWAY_PROTECT
            writeKey1 -= 1;           //修改键值确保程序正确的执行
        writeKey2 += Command;
#endif
        WriteTimeout();
        responseBytes = 1;           //设置响应命令的长度
        break;
    default:
            break;
    }// 结束 switch(Command)
}//结束 HandleCommand()
//函数名:void PutResponse(),用于配置UART,通过UART发送数据缓冲器中的数据响应//字节,表示接收到数据。
void PutResponse(WORD responseLen)
{
    WORD i;
    BYTE data;
    BYTE checksum;
    UxSTAbits.UTXEN = 1;        //确保 TX使能
    PutChar(STX);            //发送2个STX字节
    PutChar(STX);
    //输出缓冲作为响应数据报文
    checksum = 0;
    for(i = 0; i < responseLen; i++)
    {
        asm("clrwdt");        //清看门狗WDT
        data = buffer[i];    //从响应的数据缓存区中获得数据
        checksum += data;    //计算效验和
        if(data == STX || data == ETX || data == DLE)
        {
            PutChar(DLE);
        }
        PutChar(data);      //发送数据
    }
    checksum = ~checksum + 1;
    if(checksum == STX || checksum == ETX || checksum == DLE)
    {
        PutChar(DLE);
    }
    PutChar(checksum);        //发送效验和
    PutChar(ETX);            //发送结束字符
    while(!UxSTAbits.TRMT);    //等待发送过程完成
}//结束 PutResponse()
//函数名:void PutChar(BYTE Char),用于UART配置,参数Char是用于发送的字节。该函//数是通过UART2发送一个字节,等待TXREG的FIFO缓存区为空。
void PutChar(BYTE txChar)
{
    while(UxSTAbits.UTXBF);    //等待FIFO缓存区为空
    UxTXREG = txChar;        //发送一个字节给 UART的FIFO缓存区,用于发送数据
}//结束 PutChar(BYTE Char)

//函数名:void GetChar(BYTE * ptrChar),用于UART配置,参数ptrChar是指向接收字符的//指针,该函数首先清除看门狗WDT,然后在UART2上接收一个字符。
void GetChar(BYTE * ptrChar)
{
    BYTE dummy;
    while(1)
    {
        asm("clrwdt");                     //主循环,首先清看门狗 WDT
        if((UxSTA & 0x000E) != 0x0000)  //判断接收是否出错
        {
            dummy = UxRXREG;         //冗余的读操作,用于清除FERR/PERR错误标志
            UxSTAbits.OERR = 0;        //清除溢出标志OERR,保持连续接收模式
        }
        //获取数据
        if(UxSTAbits.URXDA == 1)
        {
            * ptrChar = UxRXREG;        //从UART RX接收FIFO缓存器中接收数据
            break;
        }
#ifndef USE_AUTOBAUD
        //如果超时,跳出用户代码
        if(IFS0bits.T3IF == 1)
        {
            ResetDevice(userReset.Val);
        }
#endif
    }//结束 while(1)
}//结束 GetChar(BYTE *ptrChar)
//函数名:void ReadPM(WORD length, DWORD_VAL sourceAddr),参数length    表示读指令//的个数,参数sourceAddr表示源地址。该函数用于从程序存储器中读数据,然后保存数据//到缓冲器中。
void ReadPM(WORD length, DWORD_VAL sourceAddr)
{
    WORD bytesRead = 0;
    DWORD_VAL temp;
    //从Flash中读指令的长度
    while(bytesRead < length*PM_INSTR_SIZE)
    {
        //读Flash
        temp.Val = ReadLatch(sourceAddr.word.HW, sourceAddr.word.LW);
        buffer[bytesRead+5] = temp.v[0];     //将读到的数据放到响应缓冲区中
        buffer[bytesRead+6] = temp.v[1];
        buffer[bytesRead+7] = temp.v[2];
        buffer[bytesRead+8] = temp.v[3];
        //每条指令4个字节
        bytesRead+=PM_INSTR_SIZE;
        sourceAddr.Val = sourceAddr.Val + 2;  //地址字节每次加2
    }//结束 while(bytesRead < length*PM_INSTR_SIZE)
}//结束 ReadPM(WORD length, DWORD_VAL sourceAddr)
//函数名:void WritePM(WORD length, DWORD_VAL sourceAddr),参数length表示写入行//个数,参数sourceAddr表示写入的地址。该函数将行信息从缓冲器写入到Flash存储器中。
void WritePM(WORD length, DWORD_VAL sourceAddr)
{
    WORD bytesWritten;
    DWORD_VAL data;
#ifdef USE_RUNAWAY_PROTECT
    WORD temp = (WORD)sourceAddr.Val;
#endif
    bytesWritten = 0;
//开始的5个字节是1个命令字节(cmd),2个长度字节(len),2个地址字节(addr)
    //写入列长度到Flash中
    while((bytesWritten) < length*PM_ROW_SIZE)
    {
        asm("clrwdt");
        //从缓出区中获得数据,并将数据写入
        data.v[0] = buffer[bytesWritten+5];
        data.v[1] = buffer[bytesWritten+6];
        data.v[2] = buffer[bytesWritten+7];
        data.v[3] = buffer[bytesWritten+8];
        //每条指令4个字节
        bytesWritten+=PM_INSTR_SIZE;
        //Flash 配置字处理
#ifndef DEV_HAS_CONFIG_BITS
        if(sourceAddr.Val == CONFIG_END)
        {
            data.Val &= 0x007FFF;
        }
#endif
        //保护Boot以及复位向量
#ifdef USE_BOOT_PROTECT
        //保护Boot复位地址以及或者用户程序复位地址
        if(sourceAddr.Val == 0x0)
        {
            //获得用户应用程序复位地址低字节
            userReset.Val = data.Val & 0xFFFF;
            //获得Boot复位地址低字节
            data.Val = 0x040000 + (0xFFFF & BOOT_ADDR_LOW);
            userResetRead = 1;
        }
        if(sourceAddr.Val == 0x2)
        {
            //获得用户应用程序复位地址高字节
            userReset.Val += (DWORD)(data.Val & 0x00FF)<<16;
            //获得Boot复位地址高字节
            data.Val = ((DWORD)(BOOT_ADDR_LOW & 0xFF0000))>>16;
            userResetRead = 1;
        }
#else
        //获得用户应用程序复位地址低字节
        if(sourceAddr.Val == 0x0)
        {
            userReset.Val = data.Val & 0xFFFF;
            userResetRead = 1;
        }
        //获得用户应用程序复位地址高字节
        if(sourceAddr.Val == 0x2)
        {
            userReset.Val |= ((DWORD)(data.Val & 0x00FF))<<16;
            userResetRead = 1;
        }
#endif
        //在用户复位向量中从复位向量发送信息
        if(sourceAddr.Val == USER_PROG_RESET)
        {
            if(userResetRead)   //是否从地址0x0获得复位向量吗?
            {
                //如果是,使用该数值作为复位向量
                data.Val = userReset.Val;
            }
            else
            {
                //如果不是,是用用户数值作为复位向量
                userReset.Val = data.Val;
            }
        }
        if(sourceAddr.Val == DELAY_TIME_ADDR)
        {
            userTimeout.Val = data.Val;
            data.Val = 0xFFFFFF;
        }
#ifdef USE_BOOT_PROTECT            //不擦除Boot和复位向量    
        if(sourceAddr.Val < BOOT_ADDR_LOW || sourceAddr.Val > BOOT_ADDR_HI)
        {
#endif

#ifdef USE_CONFIGWORD_PROTECT    //不擦除最后一页
            if(sourceAddr.Val < (CONFIG_START & 0xFFFC00))
            {
#endif
#ifdef USE_VECTOR_PROTECT            //不擦除第一页
                //if(sourceAddr.Val >= PM_PAGE_SIZE/2){
                if(sourceAddr.Val >= VECTOR_SECTION)
                {
#endif
                    WriteLatch(sourceAddr.word.HW, sourceAddr.word.LW,
                               data.word.HW, data.word.LW);
#ifdef USE_VECTOR_PROTECT
                }//向量保护函数结束
#endif
#ifdef USE_CONFIGWORD_PROTECT
            }//配置保护结束
#endif
#ifdef USE_BOOT_PROTECT
        }//Boot保护结束
#endif
#ifdef USE_RUNAWAY_PROTECT
        writeKey1 += 4;                  //修改键值确保程序正确的执行
        writeKey2 -= 4;
#endif
        //如果行完成,写入数据到Flash存储器中
        if((bytesWritten % PM_ROW_SIZE) == 0)
        {
#ifdef USE_RUNAWAY_PROTECT
            keyTest1 =  (0x0009 | temp) - length + bytesWritten - 5;
            keyTest2 =  (((0x557F << 1) + WT_FLASH) - bytesWritten) + 6;
#endif
#ifdef USE_BOOT_PROTECT     //保护Boot和复位向量
            if((sourceAddr.Val < BOOT_ADDR_LOW || sourceAddr.Val > BOOT_ADDR_HI))
            {
#endif
#ifdef USE_CONFIGWORD_PROTECT    //不擦除最后一页
                if(sourceAddr.Val < (CONFIG_START & 0xFFFC00))
                {
#endif
#ifdef USE_VECTOR_PROTECT           //不擦除第一页
                    if(sourceAddr.Val >= VECTOR_SECTION)
                    {
#endif
                        //执行写入过程
                        WriteMem(PM_ROW_WRITE);
#ifdef USE_RUNAWAY_PROTECT
                        writeKey1 += 5; //修改关键参数确保程序正常执行
                        writeKey2 -= 6;
#endif
#ifdef USE_VECTOR_PROTECT
                    }//结束向量保护
#endif
#ifdef USE_CONFIGWORD_PROTECT
                }//结束配置保护
#endif
#ifdef USE_BOOT_PROTECT
            }//结束Boot保护
#endif
        }
        sourceAddr.Val = sourceAddr.Val + 2;  //地址变量加2
    } //结束 while((bytesWritten-5) < length*PM_ROW_SIZE)
}//结束 WritePM(WORD length, DWORD_VAL sourceAddr)
//函数名:void ErasePM(WORD length, DWORD_VAL sourceAddr),参数length表示擦除页//的个数,参数sourceAddr表示擦除页的地址,该函数用于从Flash存储器中擦除页。
void ErasePM(WORD length, DWORD_VAL sourceAddr)
{
    WORD i=0;
#ifdef USE_RUNAWAY_PROTECT
    WORD temp = (WORD)sourceAddr.Val;
#endif
    while(i BOOT_ADDR_HI)
        {
#endif
#ifdef USE_CONFIGWORD_PROTECT          //不擦除最后一页
            if(sourceAddr.Val < (CONFIG_START & 0xFFFC00))
            {
#endif
#ifdef USE_VECTOR_PROTECT                  //不擦除第一页
                if(sourceAddr.Val >= VECTOR_SECTION)
                {
#endif
#ifdef USE_RUNAWAY_PROTECT
                    //setup program flow protection test keys
                    keyTest1 = (0x0009 | temp) + length + i + 7;
                    keyTest2 = (0x557F << 1) - ER_FLASH - i + 3;
#endif
                    //执行擦除
                    Erase(sourceAddr.word.HW, sourceAddr.word.LW, PM_PAGE_ERASE);
#ifdef USE_RUNAWAY_PROTECT
                    writeKey1 -= 7;    //修改关键参数确保正确的编程流程
                    writeKey2 -= 3;
#endif
#ifdef USE_VECTOR_PROTECT
                }
#elif  defined(USE_BOOT_PROTECT) || defined(USE_RESET_SAVE)
                    //Boot 复位向量区
                    DWORD_VAL blResetAddr;
                    if(sourceAddr.Val < PM_PAGE_SIZE/2)
                    {
                        //如果擦除,Boot复位向量区在0x00和0x02中
                        blResetAddr.Val = 0;
#ifdef USE_RUNAWAY_PROTECT
                        //修改关键参数确保正确的编程流程
                        keyTest1 = (0x0009 | temp) + length + i;
                        keyTest2 = (0x557F << 1) - ER_FLASH - i;
#endif
                        replaceBLReset(blResetAddr);
                    }
#endif
#ifdef USE_CONFIGWORD_PROTECT
            }   //配置保护结束
#endif
#ifdef USE_BOOT_PROTECT
        }   //Boot保护结束
#endif
        sourceAddr.Val += PM_PAGE_SIZE/2;    //通过页增加
    }//end while(i>16,
               (DELAY_TIME_ADDR & 0x00FFFF), userTimeout.word.HW, userTimeout.word.LW);
#else
    DWORD_VAL address;
    WORD bytesWritten;
    bytesWritten = 0;
    address.Val = DELAY_TIME_ADDR & (0x1000000 - PM_ROW_SIZE/2);
    //程序Boot进入需要延时,以完成Boot的过程。
//在FLASH中装载0xFFFFFF,确保操作正常执行。
    while(bytesWritten < PM_ROW_SIZE)
    {
        if(address.Val == DELAY_TIME_ADDR)
        {
            WriteLatch(address.word.HW, address.word.LW,
                       userTimeout.word.HW,userTimeout.word.LW);
        }
        else
        {
            WriteLatch(address.word.HW, address.word.LW,
                       0xFFFF,0xFFFF);
        }
        address.Val += 2;
        bytesWritten +=4;
    }
#endif
#ifdef USE_RUNAWAY_PROTECT
    keyTest1 =  (0x0009 | temp) - 1 - 5;
    keyTest2 =  ((0x557F << 1) + VERIFY_OK) + 6;
#endif
#ifdef DEV_HAS_WORD_WRITE
    WriteMem(PM_WORD_WRITE);
#else
    WriteMem(PM_ROW_WRITE);      //执行写入设置
#endif
#ifdef USE_RUNAWAY_PROTECT
    writeKey1 += 5;                //修改关键参数确保正确的编程流程
    writeKey2 -= 6;
#endif
}// WriteTimeout函数结束
//函数名:void AutoBaud(),用于设置自适应波特率。
void AutoBaud()
{
    BYTE dummy;
    UxMODEbits.ABAUD = 1;            //设置自适应波特率模式
    while(UxMODEbits.ABAUD)        //等待同步字符0x55
    {
        asm("clrwdt");                    //首先清除看门狗定时器WDT
        if(IFS0bits.T3IF == 1)          //如果超时溢出,则跳转到用户代码区
        {
            ResetDevice(userReset.Val);
        }
        if(UxSTAbits.OERR) UxSTAbits.OERR = 0;
        if(UxSTAbits.URXDA) dummy = UxRXREG;
    }
#ifdef USE_WORKAROUNDS      //自适应波特率的校准
    if(UxBRG == 0xD) UxBRG--;
    if(UxBRG == 0x1A) UxBRG--;
    if(UxBRG == 0x09) UxBRG--;
#ifdef USE_HI_SPEED_BRG
    UxBRG = (UxBRG+1)*4 -1;
    if(UxBRG == 0x13) UxBRG=0x11;
    if(UxBRG == 0x1B) UxBRG=0x19;
    if(UxBRG == 0x08) UxBRG=0x22;
    if (UxBRG & 0x0001)    UxBRG++;
#endif
#endif
}//结束 AutoBaud()
#ifdef DEV_HAS_PPS
//函数名:void ioMap(),在PPS设备上影射UART IO作为通信
void ioMap()
{
    //清除 IOLOCK 位
    __builtin_write_OSCCONL(OSCCON & 0xFFBF);
    //输入
    PPS_URX_REG = PPS_URX_PIN;     //UxRX = RP19
    //输出
    PPS_UTX_PIN = UxTX_IO; //RP25 = UxTX
    //锁住 IOLOCK位,因此IO不会被随机该变。
    __builtin_write_OSCCONL(OSCCON | 0x0040);
}//结束 ioMap()
#endif
#if defined(USE_BOOT_PROTECT) || defined(USE_RESET_SAVE)
//函数名:void replaceBLReset(DWORD_VAL sourceAddr),参数sourceAddr ,是复位向量
//的起始地址。本函数用于给输入的存储区中写入Boot的复位向量。
void replaceBLReset(DWORD_VAL sourceAddr)
{
    DWORD_VAL data;
#ifndef DEV_HAS_WORD_WRITE
    WORD i;
#endif
#ifdef USE_RUNAWAY_PROTECT
    WORD tempkey1;
    WORD tempkey2;
    tempkey1 = keyTest1;
    tempkey2 = keyTest2;
#endif
    //获得Boot复位向量的低字节,同时执行写入操作。
    data.Val = 0x040000 + (0xFFFF & BOOT_ADDR_LOW);
    WriteLatch(sourceAddr.word.HW, sourceAddr.word.LW, data.word.HW, data.word.LW);
#ifdef DEV_HAS_WORD_WRITE
#ifdef USE_RUNAWAY_PROTECT
    writeKey1 += 5;         //修改关键参数确保正确的程序流程
    writeKey2 -= 6;
#endif                     //通过流程保护模式,执行Boot复位向量字节写入            WriteMem(PM_WORD_WRITE);    
#endif                         //获得Boot复位向量高字节,同时执行写入操作
    data.Val = ((DWORD)(BOOT_ADDR_LOW & 0xFF0000))>>16;
    WriteLatch(sourceAddr.word.HW,sourceAddr.word.LW+2,data.word.HW,data.word.LW);
#ifdef USE_RUNAWAY_PROTECT
    keyTest1 = tempkey1;
    keyTest2 = tempkey2;
#endif
#ifdef DEV_HAS_WORD_WRITE
#ifdef USE_RUNAWAY_PROTECT
    writeKey1 += 5;              //修改关键参数确保正确的程序流程
    writeKey2 -= 6;
#endif
    WriteMem(PM_WORD_WRITE);   //执行写入Boot复位向量字节
#else
    for(i = 4; i < (PM_ROW_SIZE/PM_INSTR_SIZE*2); i+=2)
    {
        WriteLatch(sourceAddr.word.HW,sourceAddr.word.LW+i,0xFFFF,0xFFFF);
    }
#ifdef USE_RUNAWAY_PROTECT
    writeKey1 += 5;             //修改关键参数确保正确的程序流程
    writeKey2 -= 6;
#endif
    WriteMem(PM_ROW_WRITE);   //执行写入Boot复位向量字节
#endif
}//结束replaceBLReset()
#endif
为了测试上叙Boot代码,还需要用户应用程序代码,下面详细介绍应用程序文件代码。
//文件名:test app.c,该Demo用户应用程序用于测试PIC24F系列的Boot功能。
#include "p24fxxxx.h"
//配置
_CONFIG1(JTAGEN_OFF & GWRP_OFF & ICS_PGx1 & FWDTEN_OFF )
_CONFIG2(POSCMOD_HS & FNOSC_PRIPLL & OSCIOFNC_ON)
//函数申明
void _ToggleLED(void);
void _T1Interrupt();
void _T2Interrupt();
void ISRTable();               //中断服务程序
#ifdef __PIC24FJ64GA004__    //兼容24FJ64GA004系列
#define BL_ENTRY_BUTTON PORTAbits.RA9
#else
#define BL_ENTRY_BUTTON PORTDbits.RD7
#endif
//复位向量指针和Boot模式入口地址分别保存在0x100和0x102地址单元
#define USE_VECTOR_SPACE   //Boot向量区
#ifdef USE_VECTOR_SPACE
//在0x100保存延时数值和用户复位向量(在复位向量保护模式不能使用)。用户复位向量数
//值必须和C30编译器中设置的链接脚本文件(linker script)中的复位向量匹配。Boot 复位//向量必须保存在链接脚本文件(linker script)中
unsigned int userReset  __attribute__ ((space(prog),section(".BLreset"))) = 0xC00;
unsigned char timeout  __attribute__ ((space(prog),section(".BLreset"))) = 0xA ;
#else
//在用户空间启始地址保存延时数值和用户复位向量,用户复位向量数值必须是实际程序代//码的起始地址。因为这些变量都保存在同一个区域。
unsigned int userReset  __attribute__ ((space(prog),section(".init"))) = 0xC04;
unsigned char timeout  __attribute__ ((space(prog),section(".init"))) = 0x0A ;
#endif
int main(void)
{
    AD1PCFG = 0xFFFF;              //设置I/O为输出
#ifdef __PIC24FJ64GA004__      //兼容24FJ64GA004系列
    TRISAbits.TRISA10 = 0;
    TRISAbits.TRISA7 = 0;
    TRISBbits.TRISB8 = 0;
    TRISAbits.TRISA9 = 1;
#else
    TRISAbits.TRISA0 = 0;
    TRISAbits.TRISA1 = 0;
    TRISAbits.TRISA2 = 0;
    TRISDbits.TRISD7 = 1;
#endif
    IEC0bits.T1IE = 1;
    IFS0bits.T1IF = 0;
    PR1 = 0xFFFF;
    T1CON = 0x8010;
    IEC0bits.T2IE = 1;
    IFS0bits.T2IF = 0;
    PR2 = 0xFFFF;
    T2CON = 0x8020;
    while(1)
    {
        _ToggleLED();
        if(BL_ENTRY_BUTTON == 0)
        {
            T1CON = 0;            //关闭中断,防止意外操作影响Boot运行
            T2CON = 0;
            IEC0bits.T1IE = 0;
            IEC0bits.T1IE = 0;
            //配置和调用Boot
            RCON = 0;
            asm("goto 0x400");
        }
    }
}
void _ToggleLED(void)
{
    static int count = 0;
    if(++count == 0)
    {
#ifdef __PIC24FJ64GA004__    // LED D5翻转
        LATBbits.LATB8 ^= 1;
#else
        LATAbits.LATA2 ^= 1;
#endif
    }
}
//中断重影射方法1:使用直接的中断地址
void __attribute__ ((interrupt,address(0xF00),no_auto_psv)) _T1Interrupt()
{
    IFS0bits.T1IF = 0;
#ifdef __PIC24FJ64GA004          //LED D3 翻转
    LATAbits.LATA10 ^= 1;
#else
    LATAbits.LATA0 ^= 1;
#endif
}
//中断重影射方法2:使用Goto或者跳转指令
void __attribute__ ((interrupt,no_auto_psv)) _T2Interrupt()
{
    IFS0bits.T2IF = 0;
    //Toggle LED D4
#ifdef __PIC24FJ64GA004
    LATAbits.LATA7 ^= 1;
#else
    LATAbits.LATA1 ^= 1;
#endif
}
//中断服务程序
void __attribute__ ((address(0x1000))) ISRTable()
{
    asm("reset");                       //复位指令防止代码跑飞
    asm("goto %0"::"i"(&_T2Interrupt));  //T2中断地址
}