PIC24 通过USB在线升级 -- USB CDC bootloader
了解bootloader的实现,请加我Q扣: 1273623966 (验证填bootloader);欢迎咨询或定制bootloader;我的博客主页www.cnblogs.com/geekygeek
今年国庆完成了4个bootloader,前面介绍了2个,都是PIC32MZ的USB bootloader, 接着介绍2个PIC24 的USB bootloader, 首先是PIC24 USB CDC bootloader。PIC24 USB CDC bootloader 是我开发给我的PIC24FJ256GB106硬件板子的。
开发环境
1. IDE: MPLABX v4.01
2. Compiler: XC16, v1.11
3. Library&Example: c:/microchip_solutions_v2013-06-15/USB/Device-CDC-Basic Demo
这个PIC24 CDC bootloader 是在MLA_v2013-06-15的USB CDC basic demo的基础上修改而成。bootloader 占用空间从0x400开始, 长度= 0x1C00。 CDC bootloader模拟UART通信,一行一行接收串口发送过来的hex原文,然后对每行的hex原文进行解析,将里面的bin数据烧写到对应的地址上。整个的逻辑实现都在我写的Boot_DoProcess()函数中,函数代码如下。
uint8_t BOOT_DoProcess(uint8_t *buffer, uint16_t byteCount) { uint8_t i; uint8_t bcount, recType; DWORD_VAL pData; uint8_t retVal = 0; if (byteCount == 64) { return 1; } bcount = GetXbyte(buffer[LEN_NIBBLE1_INDEX],buffer[LEN_NIBBLE2_INDEX]); if (!Checksum(buffer, bcount)) { retVal = 2; return retVal; } srcAddress.v[1] = GetXbyte(buffer[ADDRH_NIBBLE1_INDEX],buffer[ADDRH_NIBBLE2_INDEX]); srcAddress.v[0] = GetXbyte(buffer[ADDRL_NIBBLE1_INDEX],buffer[ADDRL_NIBBLE2_INDEX]); srcAddress.Val >>= 1; recType = GetXbyte(buffer[TYPE_NIBBLE1_INDEX],buffer[TYPE_NIBBLE2_INDEX]); switch(recType) { case LINEAR_ADDRESS: srcAddress.v[3] = GetXbyte(buffer[TYPE_NIBBLE2_INDEX+1],buffer[TYPE_NIBBLE2_INDEX+2]); srcAddress.v[2] = GetXbyte(buffer[TYPE_NIBBLE2_INDEX+3],buffer[TYPE_NIBBLE2_INDEX+4]); retVal = 3; break; case DATA: if ((srcAddress.Val >= BOOT_START_ADDRESS) && (srcAddress.Val < APPL_RESET_ADDRESS)) // boot protection, avoid to overlap { retVal = 4; return retVal; } eraseAddress.Val = srcAddress.Val; if ((srcAddress.Val % ERASE_BLOCK) == 0) { NVM_EraseBlock(eraseAddress.Val); } for (i=0; i < 2*bcount;) { pData.byte.LB = GetXbyte(buffer[TYPE_NIBBLE2_INDEX+1+i+0],buffer[TYPE_NIBBLE2_INDEX+1+i+1]); pData.byte.HB = GetXbyte(buffer[TYPE_NIBBLE2_INDEX+1+i+2],buffer[TYPE_NIBBLE2_INDEX+1+i+3]); pData.byte.UB = GetXbyte(buffer[TYPE_NIBBLE2_INDEX+1+i+4],buffer[TYPE_NIBBLE2_INDEX+1+i+5]); pData.byte.MB = GetXbyte(buffer[TYPE_NIBBLE2_INDEX+1+i+6],buffer[TYPE_NIBBLE2_INDEX+1+i+7]); unsigned int error = NVM_WriteWord(srcAddress.Val, pData.Val); if ((error & 0x2000) > 0) { retVal = 5; return retVal; } error = 0; srcAddress.Val += 2; i += 8; } //retVal = 1; break; case END: retVal = 6; break; } return retVal; }
在main.c中ProcessIO()函数中调用Boot_DoProcess(). ProcessIO改动很大,代码如下。
void ProcessIO(void) { BYTE numBytesRead; WORD status; //Blink the LEDs according to the USB device status BlinkUSBStatus(); // User Application USB tasks if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) return; if(buttonPressed) { if(stringPrinted == FALSE) { if(mUSBUSARTIsTxTrfReady()) { putrsUSBUSART("Button Pressed -- \r\n"); stringPrinted = TRUE; } } } else { stringPrinted = FALSE; } if(USBUSARTIsTxTrfReady()) { numBytesRead = getsUSBUSART(USB_Out_Buffer,64); if(numBytesRead != 0) { BYTE i; BYTE j; for(i=0;i<numBytesRead;i++) { switch(USB_Out_Buffer[i]) { case 0x0A: case 0x0D: USB_In_Buffer[i] = USB_Out_Buffer[i]; if (!BOOT_Handshake) { BOOT_Handshake = 1; } else { if ((BOOT_RecordSOF == 1) && (BOOT_RecordEOF == 0)) { BOOT_RecordBuffer[BOOT_RecordCounter++] = USB_Out_Buffer[i]; BOOT_RecordEOF = 1; for (j=0; j<BOOT_RecordCounter; j++) { BOOT_OperationBuffer[j] = BOOT_RecordBuffer[j]; } BOOT_OperationCounter = BOOT_RecordCounter; BOOT_RecordLineFlag = 1; BOOT_RecordSOF = 0; BOOT_RecordEOF = 0; BOOT_RecordCounter = 0; } } break; case ':': USB_In_Buffer[i] = USB_Out_Buffer[i]; if (BOOT_Handshake) { if (!BOOT_RecordSOF) { BOOT_RecordBuffer[BOOT_RecordCounter++] = USB_Out_Buffer[i]; BOOT_RecordSOF = 1; } } break; default: USB_In_Buffer[i] = USB_Out_Buffer[i]; if ((BOOT_Handshake == 1) && (BOOT_RecordSOF == 1)) { BOOT_RecordBuffer[BOOT_RecordCounter++] = USB_Out_Buffer[i]; if (BOOT_RecordCounter == BOOT_RECORD_MAX) { BOOT_RecordEOF = 1; for (j=0; j<BOOT_RecordCounter; j++) { BOOT_OperationBuffer[j] = BOOT_RecordBuffer[j]; } BOOT_RecordLineFlag = 1; BOOT_OperationCounter = BOOT_RecordCounter; BOOT_RecordSOF = 0; BOOT_RecordEOF = 0; BOOT_RecordCounter = 0; } } break; } } putUSBUSART(USB_In_Buffer,numBytesRead); if (BOOT_RecordLineFlag) { if (BOOT_OperationCounter == BOOT_RECORD_MAX) { putUSBUSART("XXXXXXXXXXXXXXXX\r\n", 18); } else { status = BOOT_DoProcess(BOOT_OperationBuffer, BOOT_OperationCounter); if (status == 6) { //TODO Deinitialization (*((void(*)(void))APPL_RESET_ADDRESS))(); } else { //putUSBUSART("\r", 1); } } BOOT_RecordLineFlag = 0; } } if (!BOOT_Handshake) { BOOT_Timeout--; if (BOOT_Timeout == 0) { // TODO Jump to Application // TODO Deinitialization (*((void(*)(void))APPL_RESET_ADDRESS))(); } else if (BOOT_Timeout%20000 == 0) { putUSBUSART(".", 1); } } else { if (!BOOT_ProgramRequired) { BOOT_ProgramRequired = 1; putUSBUSART("\r\n",2); } } } CDCTxService(); } //end ProcessIO
整个CDC bootloader就完成了。合着bootloader的Linker script编译完成后,通过PICKit3烧写到硬件板子中。CDC是模拟UART通信,所以通过USB线连接到电脑,电脑可以侦测到COM口。
接着就是测试bootloader的功能了。写了个测试用的应用程序,应用程序地址是从0x2000开始到结束(地址分配参考AN1094)。应用程序合着客制的Linker script编译。测试时,我是通过超级终端发送应用程序的Hex原文。重启目标板,打开超级终端,选择CDC 模拟的COM口,配置成9600-8-none-1. 设置Line delay=40ms. 超级终端窗口出现”..."字符后,窗口中按下回车,菜单栏选择“发送文本文件”, 加载应用程序Hex, ,点击发送。然后就等着烧写完成。(上面贴的都是最终可以使用的完成函数代码,但是在实现过程中,修改几次,特别是测试过程中,发现不少问题,修改代码一一解决之后才最后完成)
以下是烧写步骤:
1. 打开超级终端
2. 重启烧录好CDC bootloader的目标板
3. 超级终端配置,选择CDC emulating后的出现的COM口,设置成9600-8-none-1, 设置Line Delay.
4. 一旦超级终端窗口出现”..."字符,立即发送回车。
5. 点击发送/发送文本文件..., 选择要发送的hex文件。
6. 等待升级完成,CDC bootloader 每接收完一行都会原文返回。