<pre name="code" class="cpp">//PIC24系列的串行自举(Bootloader)代码设计如下: //文件名:boot.c,该文件调用memory.c和C30编译器系统配置文件config.h。 //本Boot代码兼容AN851通信协议,是在PIC24F和PIC16/18 的AN851基础上开发出来 //的最新版本。 #include "PIC24F Serial Bootloader\config.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<length) { i++; #ifdef USE_RUNAWAY_PROTECT writeKey1++; //修改关键参数确保程序正常执行 writeKey2--; #endif //如果保护使能, 将保护Boot和复位向量 #ifdef USE_BOOT_PROTECT if(sourceAddr.Val < BOOT_ADDR_LOW || //不擦除Boot 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 #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<length) }//ErasePM函数结束 //函数名:void WriteTimeout(),编程的数据在调用前需要首先效验。该函数用于将数据通过 //Boot写入到存储器中。该函数需要在成功效验后才能被编程写入到数据存储器中。 void WriteTimeout() { #ifdef USE_RUNAWAY_PROTECT WORD temp = (WORD)sourceAddr.Val; //将溢出数值写入到存储区中 #endif #ifdef DEV_HAS_WORD_WRITE //写入数据 WriteLatch((DELAY_TIME_ADDR & 0xFF0000)>>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中断地址 }