基于USB2CAN適配器實現的CAN Bootloader
概述
本文詳細介紹了基于USB2XXX USB2CAN適配器實現的CAN Bootloader應用案例,該應用常用于基于CAN接口實現的產品中,在需要對產品進行在線固件升級更新的場合。閱讀此文可以了解到CAN Bootloader實現的基本思想和具體的實現步驟以及詳細的協議說明,同時也對用其他總線就行固件升級提供一定的參考價值。
此方案的上位機源碼和單片機端源碼都可以免費提供,有需要的可以聯系作者QQ(188298598)。
功能簡介
CAN Bootloader主要就是利用產品的CAN接口實現對產品內部固件進行升級更新,根據實際使用情況,目前我們提供的案例有以下幾個功能:
單片機端固件存儲區分為2個區域,一個區域存儲CAN Bootloader功能的固件程序(該區域在單片機上電或者復位后默認被執行),另外一個區域存儲實現產品自身功能的固件程序(需要通過CAN接口實現固件查詢,控制固件跳轉功能)。
所有接入CAN總線上,需要進行固件升級的產品,都必須具備一個唯一的ID信息(可以用撥碼開關或者寫入固定值方式實現),利用此ID才能分別對每個產品節點進行單獨控制。
CAN Bootloader和用戶App程序固件都具備通過CAN接口被動向CAN總線反饋固件信息的功能,利用此功能,上位機軟件可以查詢到當前CAN總線上每個節點的固件信息。
實現CAN Bootloader只需要占用1個或者2個CAN ID,這對于CAN ID非常珍貴的應用場合是非常大的一個優勢。
Bootloader代碼具備擦除App程序,接收App程序數據,寫入App程序數據到程序存儲器,控制程序跳轉到App程序執行的功能。
在實際使用情況下,由于產品固件是非常敏感的,不能隨便給其他人,所以我們提供給客戶的固件必須是經過加密之后的固件,所以CAN Bootloader固件還具備固件文件解密的功能。
CAN Bootloader上位機軟件界面如下所示:
此軟件使用Qt編寫,使用的C++語言,可以免費提供源碼。
使用步驟
編譯下載Bootloader
目前我們提供有STM32F103,STM32F105/107,STM32F205/207,STM32F405/407,TMS320F2808,TMS320F28335版本的單片機源碼,上位機是通用的,任何單片機都可以使用相同的上位機軟件,后續我們也會提供更多種類的單片機程序源碼。當然客戶自己也可以參考我們提供的現成單片機程序實現支持自己用的單片機。
加入我們使用的是STM32F103的單片機,大容量的打開stm32f103_HD目錄,小容量的打開stm32f103_MD目錄,目錄下有2個文件夾,一個是bootloader,一個app,我們首先打開bootloader目錄。STM32版本的固件我們使用的是KEIL MDK5版本的編譯器,所以直接打開“RVMDK/Project.uvprojx”文件即可。
打開工程中的“can_driver.c”文件,然后找到“CAN_GPIO_Configuration”函數,確認當前引腳配置跟自己的硬件是匹配的,若不匹配則需要進行修改,該函數實現如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * @brief CAN引腳配置 * @param None * @retval None */ void CAN_GPIO_Configuration( void ) { GPIO_InitTypeDef GPIO_InitStructure; /*外設時鐘設置*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); /* Configure CAN pin: RX PA11*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉輸入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure CAN pin: TX PA12 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 復用推挽輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); } |
打開“can_bootloader.c”文件,找到“GetNAD”函數實現的地方,這個函數實現獲取節點地址的功能,目前我們提供的代碼是通過STM32芯片內部的唯一ID運算出的一個節點地址,這種方法沒法保證多個單片機接入總線時每個節點的地址都是唯一的,所以這部分代碼在做實際產品的時候需要進行修改,可以通過撥碼開關或者從指定Flash地址獲取節點地址,最終目的就是要保證接入同一個CAN總線網絡的所有節點地址不能有重復的情況,節點地址最大值不能超過0x7E。
打開“can_bootloader.h”文件,根據自己 工程實際使用情況修改如下的配置信息,初次驗證基本功能的時候不建議修改配置值,除非你非常明確知道每個配置的作用,同時知道在其他地方也做相應的修改。
1 2 3 4 5 6 7 8 9 10 11 | //固件傳輸是否加密,若將此項配置為1,那么需要在app工程中勾選"After Build/Rebuild"下的"Run #1"和"Run #2"選項 #define ENCRYPT 1 //APP程序起始地址 #define APP_START_ADDR ((uint32_t)0x08008000) //APP程序運行標志存儲地址,該標志在APP程序中寫入和擦除 #define APP_EXE_FLAG_ADDR ((uint32_t)0x08004000) //對于CAN總線,數據收發ID可以定義為一個ID,也可以定義為不同的ID #define MSG_RECEIVE_ID 0x3C #define MSG_SEND_ID 0x3D //定義數據收發幀ID類型,0-標準幀,1-擴展幀 #define MSG_ID_TYPE 0 |
打開“main.c”文件,在主函數里面找到“CAN_Configuration”函數調用的地方,此函數調用需要傳入CAN總線波特率值,單位為Hz,可以將該值修改為產品最終需要的波特率值。
修改完成后,就可以編譯工程,然后利用調試下載器將固件下載到單片機中。
此時打開上位機端的CAN Bootloader軟件,將USB2CAN適配器接入電腦,同時將適配器的CAN總線跟產品的CAN總線進行連接,然后在上位機軟件上點擊“操作”->“掃描節點”就可以掃描到連接到當前CAN總線上的產品,且固件類型顯示為“BOOT”。
若掃描不到節點,可以從以下幾點去尋找問題:
CAN總線連接是否正確?
CAN Bootloader上位機的波特率跟固件中設置的波特率是否一致?
固件下載到產品中后,是否正常運行了?
CAN總線上的終端電阻是否接好了?
編譯下載App
編譯App
若能成功掃描到Bootloader固件節點后,我們就可以編譯App工程,然后通過CAN Bootloader上位機軟件給CAN總線上的節點下載App固件了,打開app目錄下的“RVMDK/Project.uvprojx”文件,我們對可能需要修改的地方做一些簡單的介紹。
首先是根據"can_bootloader.h"文件中的APP_START_ADDR宏定義修改程序的起始地址,修改后如下圖所示:
該地址必須跟"can_bootloader.h"文件中的APP_START_ADDR宏定義一致,否則可能會出現App程序無法正常執行的問題。
打開“can_app.h”文件,確認以下幾個宏定義值跟bootloader工程下的“can_bootloader.h”文件中的宏定義值一致。
1 2 3 4 5 6 7 8 9 10 11 | //固件傳輸是否加密,若將此項配置為1,那么需要在app工程中勾選"After Build/Rebuild"下的"Run #1"和"Run #2"選項 #define ENCRYPT 1 //APP程序起始地址 #define APP_START_ADDR ((uint32_t)0x08008000) //APP程序運行標志存儲地址,該標志在APP程序中寫入和擦除 #define APP_EXE_FLAG_ADDR ((uint32_t)0x08004000) //對于CAN總線,數據收發ID可以定義為一個ID,也可以定義為不同的ID #define MSG_RECEIVE_ID 0x3C #define MSG_SEND_ID 0x3D //定義數據收發幀ID類型,0-標準幀,1-擴展幀 #define MSG_ID_TYPE 0 |
若使能了固件加密功能,則還需要配置固件自動加密指令,如下圖所示:
1,Run #1,指令內容為“fromelf.exe --bin --output $L@L.bin $L@L.axf”,工程編譯后,會自動輸出未加密的bin文件;
2,Run #2,指令內容為“file_encrypt.exe $L@L.bin $L@L_encrypt.bin 123456789abcdefggdfrthfgdfgefsse”,該指令是對第一步生成的bin文件進行加密處理,命令的最后一個參數為秘鑰字符串,長度為32個字符,這個字符串必須跟bootloader工程中CAN_BOOT_ExecutiveCommand函數中的pKey變量定義一致,否則寫入Flash中的App數據將會是錯的,導致App程序無法正常執行。
打開“system_stm32f10x.c”文件,找到里面的VECT_TAB_OFFSET宏定義,確認該值為正確的偏移值,其值為App程序起始地址減去Bootloader程序起始地址,若該值設置不正確,可能會導致App程序中斷無法正常執行。
打開“main.c”文件,確認CAN_Configuration函數傳入的波特率參數是跟bootloader工程中配置的參數是一致的,否則可能會導致app正常執行后,在CAN Bootloader上位機軟件中無法正確識別到App正常執行的問題。
編譯工程,若成功后,則會在編譯輸出目錄下生成一個名字帶“encrypt”的bin文件,這個就是經過加密后的bin文件,我們就可以將這個固件提供給客戶實現對自己的產品進行固件升級。
下載App
在CAN Bootloader上位機軟件中掃描到節點后,點擊“打開文件”按鈕,選擇上一步生成的bin文件固件,設置好波特率,數據收發ID值,ID類型(必須跟bootloader固件中的配置一致),若有多個節點需要同時更新,還可以勾選“所有節點”復選框,然后再點擊“更新固件”就可以開始實現對節點進行固件更新,固件更新成功后,在軟件節點列表中的“固件類型”會變成“APP”,同時也會顯示當前App的固件版本。
CAN Bootloader協議
1,獲取固件信息
主發送:
ID | NAD | PCI | SID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xB2 | 0x80 | 0xFF | 0xFF | 0xFF | 0xFF |
ID:主節點給從節點發送數據的ID,可自定義值,其他命令也一樣。
NAD:從節點節點地址,正常的節點地址取值0x01~0x7D,0x7F為廣播地址,其他值保留。
從返回:
ID | NAD | PCI | RSID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xF2 | TYPE | VER3 | VER2 | VER1 | VER0 |
ID:從節點返回數據的ID,可自定義值,其他命令也一樣。
TYPE:當前固件類型,0x55-當前為Bootloader,0xAA-當前為App
VER3: 固件版本號 Major
VER2: 固件版本號 Minor
VER1: 固件版本號 Revision
VER0: 固件版本號 Build
2,進入BOOT模式
主發送:
ID | NAD | PCI | SID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xB3 | 0xC1 | 0x42 | 0x4F | 0x4F | 0x54 |
從返回:
該命令不返回數據,是否成功進入BOOT模式可以通過“獲取固件信息”命令來判斷。
3,擦除APP程序區數據
主發送:
ID | NAD | PCI | SID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xB4 | 0x42 | SIZE3 | SIZE2 | SIZE1 | SIZE0 |
SIZE3:擦除空間大小的bit[31..24]位
SIZE2:擦除空間大小的bit[23..16]位
SIZE1:擦除空間大小的bit[15..8]位
SIZE0:擦除空間大小的bit[7..0]位
從返回:
ID | NAD | PCI | RSID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xF4 | STATUS | 0xFF | 0xFF | 0xFF | 0xFF |
STATUS:固件擦除狀態,0-固件擦除成功,1-固件擦除出錯,2-當前模式為APP,不能擦除固件。
4,發送固件數據
主發送:
ID | NAD | PCI | SID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xB4 | 0x03 | ADDR3 | ADDR2 | ADDR1 | ADDR0 |
ADDR3:固件數據偏移地址bit[31..24]位
ADDR2:固件數據偏移地址bit[23..16]位
ADDR1:固件數據偏移地址bit[15..8]位
ADDR0:固件數據偏移地址bit[7..0]位
從返回:
ID | NAD | PCI | RSID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xF4 | STATUS | BUF_LEN1 | BUF_LEN0 | 0xFF | 0xFF |
STATUS:0-準備好接收數據,3-當前地址超出了正常的地址范圍,4-當前模式不能寫入固件數據。
BUF_LEN1:接收數據緩沖區大小bit[15..8]位
BUF_LEN0:接收數據緩沖區大小bit[7..0]位
注意:后續單次發送固件數據的長度不能大于緩沖區的大小減2。
若數據小于等于4字節,則發送:
ID | NAD | PCI | SID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x0L | 0xB4 | 0xC4 | W0 | W1 | W2 | W3 |
L:固件有效數據長度加2的bit[3..0]位
注意:若數據小于4字節,則以0xFF補全。
否則發送:
ID | NAD | PCI | LEN | SID | D1 | D2 | D3 | D4 |
ID | NAD | 0x1L | LEN | 0xB4 | 0xC4 | W0 | W1 | W2 |
L:固件數據長度加2的bit[11..8]位
LEN:固件數據長度加2的bit[7..0]位
ID | NAD | PCI | D1 | D2 | D3 | D4 | D5 | D6 |
ID | NAD | 0x2N | W3 | W4 | W5 | W6 | W7 | W8 |
N:包計數,從0到15循環
注意:若最后一幀數據不夠6字節,則以0xFF補全。
主發送:
ID | NAD | PCI | SID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xB4 | 0x85 | CRC1 | CRC0 | 0xFF | 0xFF |
CRC1:前面發送的APP數據的CRC16的bit[15..8]位
CRC0:前面發送的APP數據的CRC16的bit[7..0]位
從返回:
ID | NAD | PCI | RSID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xF4 | STATUS | 0xFF | 0xFF | 0xFF | 0xFF |
STATUS:0-數據成功寫入程序存儲器,4-當前模式不能寫入固件數據,5-數據寫入程序存儲器出錯,6-數據長度超出了程序存儲器范圍,7-數據傳輸CRC校驗出錯,8-數據寫入芯片CRC校驗出錯。
5,執行APP固件
主發送:
ID | NAD | PCI | SID | D1 | D2 | D3 | D4 | D5 |
ID | NAD | 0x06 | 0xB4 | 0x06 | 0x41 | 0x50 | 0x50 | 0xFF |
返回:
該命令不返回數據,是否成功執行APP可以通過“獲取固件信息”命令來判斷。