//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中断地址
}