需求
前文中實現(xiàn)了一款簡單的 2nd Bootloader,能夠跳轉(zhuǎn)執(zhí)行存儲在 QSPI Flash 中的應(yīng)用程序,但 2nd Bootloader 如果僅僅只是用于跳轉(zhuǎn)執(zhí)行程序的話,豈不是有些太簡單了?從本章開始,將會講解幾種 2nd Bootloader 進(jìn)階設(shè)計,實現(xiàn)類似 ISP 更新固件的功能,以及在 OTA 升級時避免變“磚”等設(shè)計,以及講解一些 2nd Bootloader 的程序設(shè)計思路。
本文將以 Ymodem 協(xié)議獲取應(yīng)用程序的二進(jìn)制文件為例,實現(xiàn)類似 ISP 更新固件的功能。
需要注意:
下文中提到的 ISP 僅指由 2nd Bootloader 實現(xiàn)的定制 ISP,而非微控制器本身的 ISP。
上位機(jī)發(fā)送的文件是二進(jìn)制(.bin)文件,而不是 Intel Hex 標(biāo)準(zhǔn)的 hex (.hex)文件。
目前僅考慮直接覆蓋的方式燒寫程序,即獲取到一段二進(jìn)制數(shù)據(jù)后,直接寫入到 QSPI Flash 的對應(yīng)位置。
Ymodem 介紹
Ymodem 協(xié)議是一個文件傳輸協(xié)議,通常用于在資源受限的設(shè)備中傳輸文件,它可以一次傳輸1024字節(jié)的信息塊,同時還支持傳輸多個文件。
Ymodem 協(xié)議有較多的變種,本文使用的是常用的 Ymodem-1K 協(xié)議。
通信時序
通訊時序如圖1:
圖1 Ymodem 通信時序
幀格式
Ymodem 有兩種幀格式:
幀頭為 SOH 時,信息塊長度為128字節(jié),總長度133字節(jié)。
幀頭為 STX 時,信息塊長度為1024字節(jié),總長度1029字節(jié)。
兩種幀的幀格式如表1所示:
表1 SOH / STX 幀格式
包號從0x00起始,每成功傳輸一幀數(shù)據(jù)后包號加1,計數(shù)到0xFF后,下一次包號重新從0x00開始計數(shù)。
包號反碼是包號取反的數(shù)值,如0x00的包號,包號反碼為0xFF,0x01的包號,包號反碼為0xFE。
信息塊是要傳輸?shù)木唧w數(shù)據(jù)塊,起始幀包含了文件名和文件大小,數(shù)據(jù)幀包含了分段的數(shù)據(jù)內(nèi)容。
校驗采用 CRC 校驗,僅校驗信息塊的內(nèi)容。
除了兩種幀格式外,還有 ACK、NAK、CAN、EOT、字符 'C' 五種命令,長度僅有1字節(jié)。
起始幀、數(shù)據(jù)幀、結(jié)束幀
起始幀采用幀頭為 SOH 的幀格式傳輸,包號為 0x00,信息塊中包含文件名字符串和文件大小字符串 (十進(jìn)制表示),字符串以0x00結(jié)尾,信息塊剩余部分以0x00填充。
數(shù)據(jù)幀采用幀頭為 STX 的幀格式傳輸,包號從0x01開始計數(shù),信息塊中包含分段的文件內(nèi)容。
當(dāng)最后一段要發(fā)送的數(shù)據(jù)塊大小超過128字節(jié)但小于1024字節(jié)時,采用幀頭 STX 的幀格式傳輸,信息塊結(jié)尾用 0x1A 填充。小于128字節(jié),采用幀頭為 SOH 的幀格式傳輸,信息塊結(jié)尾依然用 0x1A 填充。
結(jié)束幀和起始幀一樣,唯一不同的是沒有文件名和文件大小,即信息塊的內(nèi)容全為 0x00。
通訊指令
通訊指令如表2所示。
表2 Ymodem通訊指令
協(xié)議實現(xiàn)
CRC 校驗:
Ymodem 協(xié)議中提供了 CRC 校驗的 C 代碼片段,但由于該協(xié)議發(fā)布時的 C 標(biāo)準(zhǔn)不同于現(xiàn)在,因此不能直接使用,此處提供一份 CRC 校驗的實現(xiàn)代碼,通過調(diào)用 crc_calc() 來實現(xiàn)對信息塊內(nèi)容的校驗:
staticuint16_tcrc_update(uint16_tcrc,uint8_tdata) { uint32_tcrc32=crc; uint32_tdata32=data; for(uint32_ti=0u;i8u;?i++) ????{ ????????if?(0?!=?(crc32?&?0x8000u)?) ????????{ ????????????crc32?<<=?1u; ????????????crc32?+=?(?(?(data32?<<=?1)?&?0x0100u)?!=?0u); ????????????crc32?^=?0x1021; ????????} ????????else ????????{ ????????????crc32?<<=?1u; ????????????crc32?+=?(?(?(data32?<<=?1)?&?0x0100u)?!=?0u); ????????} ????} ????return?(uint16_t)(crc32?&?0xFFFFu); } static?uint16_t?crc_calc(uint8_t?*?buffer,?uint32_t?size) { ????uint16_t?crc?=?0u; ????for?(?uint32_t?i?=?0u;?i?
當(dāng)我們需要對一段數(shù)據(jù)進(jìn)行 CRC 計算時,調(diào)用 crc_calc() 函數(shù),傳入數(shù)據(jù)起始地址和數(shù)據(jù)長度即可計算出 CRC 校驗值。
STX 包處理:
通過對 Ymodem 協(xié)議的介紹可知,STX 包只在接收數(shù)據(jù)的過程中使用,因此收到 STX 包時,僅需要進(jìn)行如下處理:
CRC校驗信息塊。
計算信息塊的有效數(shù)據(jù)長度(需要注意最后一幀數(shù)據(jù)的有效長度不定)。
存儲數(shù)據(jù)。
發(fā)送 ACK 指令或 NAK 指令。
SOH 包處理:
SOH 包的處理要比 STX 包的處理復(fù)雜,因為包含了起始幀和結(jié)束幀的處理。
起始 / 結(jié)束幀和數(shù)據(jù)幀只能通過當(dāng)前狀態(tài)來判斷,其中,除信息塊長度不同外,數(shù)據(jù)幀的處理同 STX 包的處理一致。
由于 Ymodem 可以多文件傳輸?shù)奶匦裕幱谠撌盏浇Y(jié)束幀的狀態(tài)時也有可能收到起始幀,因此起始幀和結(jié)束幀需要進(jìn)行一個判斷:信息塊第一個字節(jié)是否為 0x00。如果不是 0x00 則為起始幀,否則為結(jié)束幀。
起始幀要攜帶文件名和文件大小,信息塊的第一個字節(jié)一定是一個可顯示的字符,當(dāng)收到起始幀時,需進(jìn)行如下處理:
讀取文件名和文件大小。
進(jìn)入讀數(shù)據(jù)塊的狀態(tài)。
發(fā)送 ACK。
發(fā)送 字符 'C'。
結(jié)束幀的信息塊全為0x00,收到結(jié)束幀時,需進(jìn)行如下處理:
發(fā)送 ACK。
結(jié)束 Ymodem 傳輸。
EOT 指令處理:
EOT 代表本次文件傳輸結(jié)束(但不代表所有文件都已發(fā)送完畢),因此,收到 EOT 指令時,需將當(dāng)前狀態(tài)調(diào)整為起始狀態(tài),準(zhǔn)備接收新的文件,具體處理如下:
進(jìn)入起始狀態(tài)。
發(fā)送 ACK。
發(fā)送字符 'C'。
CAN 指令處理:
CAN 是 cancel 的縮寫,當(dāng)收到 CAN 指令后,表示后續(xù)的 Ymodem 傳輸終止,該指令是雙向的,既可以由 Host 發(fā)送, 也可以是 Device 發(fā)送,收到 CAN 指令后,具體操作如下:
退出 Ymodem 傳輸。
接收超時處理:
Device 在接收數(shù)據(jù)前,會先向 Host 發(fā)送字符 'C',但如果此時 Host還沒有將文件準(zhǔn)備好,則會卡死在準(zhǔn)備接收狀態(tài)。
Device 在接收數(shù)據(jù)過程中,如果少接收到某個字節(jié)數(shù)據(jù),信息不完整,則會卡死在接收數(shù)據(jù)的過程中。
Device 發(fā)送某個指令后,Host 可能沒有收到指令,不會繼續(xù)下一幀數(shù)據(jù)的發(fā)送,Device 還是會卡死在接收的過程中。
因此,需要引入接收超時的操作。
當(dāng)接收超時后,判斷狀態(tài),如果是起始狀態(tài),且沒有收到任何字節(jié),則可能是 Host 還沒有準(zhǔn)備發(fā)送文件,重新發(fā)送字符 'C'。
如果數(shù)據(jù)沒有接收完整,則可能是少收到幾個字節(jié)的數(shù)據(jù),發(fā)送 NAK,讓 Host 重新發(fā)送數(shù)據(jù)。
如果沒有收到數(shù)據(jù),則 Host 可能沒有收到回復(fù)的指令,重新發(fā)送上次發(fā)送的指令。
軟件設(shè)計
就像是計算機(jī)進(jìn)入 BIOS 設(shè)置,需要用戶在開機(jī)的瞬間不停按下鍵盤上某個按鍵那樣,為了使 2nd Bootloader 知道自己是該跳轉(zhuǎn)執(zhí)行應(yīng)用程序,還是進(jìn)入 ISP 等模式,需要外界有一個輸入:這個輸入可以是某個引腳的電平變化,也可以是在有限的時間里通過某種通信接口獲取到一段外界指令,當(dāng) 2nd Bootloader 讀取到這個來自外界的輸入后,才能知道自己接下來要干什么。因此,除了實現(xiàn) ISP 下載的功能外,我們還需要實現(xiàn)選擇工作模式的功能,如圖2所示:
圖2 軟件設(shè)計
Ymodem 只是獲取二進(jìn)制文件的一種方式,除了 Ymodem,我們也可以采用 Xmodem,Zmodem協(xié)議,除了串口,還可以使用 CAN,甚至通過 USB 讀取 U 盤里的文件等方式。
綜上所述,在設(shè)計 2nd Bootloader 時,不能綁死選擇工作模式的方式,也不能綁死 ISP 的工作方式,甚至,不能綁死 2nd Bootloader 只能在兩種工作模式下二選一(不要使用 if & else 的語句區(qū)分工作模式,而應(yīng)使用 switch 語句區(qū)分工作模式),因此,2nd Bootloader 的頂層應(yīng)用邏輯,只能是下面的設(shè)計:
intmain(void) { ...... switch(get_run_mode()) { caseEXEC_QSPI: jump_to_app(QSPI_BASE); break; caseISP: isp(); break; ...... default: jump_to_app(QSPI_BASE); break; } ...... }
如果我們期望從某種工作模式下切換到另一種工作模式,最好的做法是先讓外界輸入保持為目標(biāo)工作模式的狀態(tài),然后讓微控制器復(fù)位,再次進(jìn)入 2nd Bootloader,這樣的做法是能夠保持微控制器切換工作模式后,仍然保持相對 “干凈” 的環(huán)境狀態(tài),例如,微控制器前一次進(jìn)入到了 ISP 模式,通過串口更新了應(yīng)用程序,如果直接跳轉(zhuǎn)到應(yīng)用程序,則發(fā)現(xiàn)串口依然保持打開的狀態(tài),這對應(yīng)用程序而言可能不是期望的結(jié)果,那提前關(guān)閉串口呢?還有 GPIO 引腳的配置沒有改動……最簡單省事的做法,其實就是直接讓微控制器復(fù)位,而串口和串口的 GPIO 引腳也就會在微控制器復(fù)位之后,處于默認(rèn)相對比較 “干凈” 的狀態(tài)。這也是為什么圖x所示的流程圖,ISP 模式的下一步是復(fù)位微控制器。
當(dāng)然,如果在 get_run_mode() 的時候就用到了串口,那還是老老實實在 get_run_mode() 執(zhí)行到 return 之前,就把串口和 GPIO 處理干凈。
這里提一個比較“花”的設(shè)計方法,我們可以把 ISP 也做成應(yīng)用程序,下載到片內(nèi) Flash 中 一塊確認(rèn)好的位置(假設(shè)起始地址為 ISP_BASE),然后同樣使用 jump_to_app() 跳轉(zhuǎn),只是輸入參數(shù)從 QSPI_BASE 變?yōu)榱?ISP_BASE,這個做法會用在 USB DFU 模式上,因為一旦進(jìn)入了 USB DFU 模式,USB 就不能再作為其它設(shè)備進(jìn)行工作,當(dāng) USB 設(shè)備支持 USB DFU 時,就需要使用這種辦法單獨進(jìn)入到 DFU 模式下。
測試
選擇工作模式:
在這里,我們通過讀取指定引腳的電平狀態(tài)來確定該進(jìn)入何種工作模式。
uint32_tget_run_mode() { ...... if(GPIO_ReadInDataBit(BOARD_BOOT_GPIO_PORT,BOARD_BOOT_GPIO_PIN)) { returnEXEC_QSPI; } else { returnISP; } ...... }
ISP 模式:
當(dāng)進(jìn)入 ISP 模式后,開始使用 Ymodem 協(xié)議接收數(shù)據(jù)。
voidisp() { ...... /*getnewappbin&writetoqspiflash.*/ ymodem_recv_start(&ym,100000); while(0==(YMODEM_STATUS_DONE&ym.status)) { ymodem_recv_byte_handler(&ym); } /*resetmcu.*/ __set_FAULTMASK(1); NVIC_SystemReset(); }
生成應(yīng)用程序的二進(jìn)制文件:
我們?nèi)匀灰?MindSDK 的 hello_world 樣例工程為例,修改其 Linker 文件并檢查代碼,使其成為一個可存儲在 QSPI Flash 上的應(yīng)用程序,隨后在 MDK 工程中,點擊魔術(shù)棒(Options for Target...),點擊 User 列表,如圖3所示,在指定位置(紅框中的 User Command)加入下面這句話,并在前面打上對勾:
fromelf.exe --bin -o "@L.bin" "#L"
然后編譯工程,就能在工程文件所在的目錄下找到生成的 bin 文件。
圖3 生成二進(jìn)制文件
本文使用 TeraTerm 軟件進(jìn)行 Ymodem 傳輸文件,如圖4所示:
圖4 TeraTerm Ymodem 發(fā)送文件
但在測試時發(fā)現(xiàn),當(dāng)文件傳輸?shù)?100% 時,TeraTerm 并沒有結(jié)束傳輸,但對 2nd Bootloader 的代碼進(jìn)行分析后并沒有發(fā)現(xiàn)存在邏輯問題,因此對 TeraTerm 的 Ymodem 協(xié)議產(chǎn)生了懷疑,如圖5所示。
圖5 TeraTerm 傳輸文件,總卡在 100% 處
使用兩個 USB 串口模塊,將其 TXD 與 RXD 相連,其中一個串口模塊使用 TeraTerm 打開,另一個使用 SSCOM 打開(為了能夠發(fā)送和顯示一些非字符類的控制指令),使用 TeraTerm 的 Ymodem 協(xié)議發(fā)送文件, SSCOM 接收來自 TeraTerm 的數(shù)據(jù),并按照 Ymodem 協(xié)議回復(fù)指令,模擬完整的 Ymodem 傳輸協(xié)議,如圖6所示。
圖6 模擬 Ymodem 協(xié)議傳輸文件過程
結(jié)果發(fā)現(xiàn),TeraTerm 實現(xiàn)的 Ymodem 協(xié)議在發(fā)送單個文件的時候,存在以下問題:
發(fā)送 EOT 指令后,需接收兩次 ACK 和字符 ‘C’。
沒有發(fā)送 last block。
因此,我們需要針對 TeraTerm 的問題,對 Ymodem 的實現(xiàn)做一些改動,或者使用其它軟件通過 Ymodem 傳輸二進(jìn)制文件。修改后的時序圖如下:
圖7 針對 TeraTerm 的 Ymodem 實現(xiàn)進(jìn)行的改動
刪去了對 last block 的接收,并且在收到 EOT 后,主動發(fā)送兩次 ACK 和 字符 ‘C’,經(jīng)過修改后測試,TeraTerm 的 Ymodem 能夠按照傳輸完成的方式正常退出。
下載程序,如圖8所示:
圖8 下載應(yīng)用程序
運行應(yīng)用程序,如圖9所示:
圖9 運行應(yīng)用程序
結(jié)語
本文在 2nd Bootlaoder 的基礎(chǔ)上實現(xiàn)了基于 Ymodem 協(xié)議的 ISP 功能,能夠通過復(fù)位后指定引腳的電平狀態(tài)來區(qū)分該執(zhí)行應(yīng)用程序還是進(jìn)入 ISP 模式,進(jìn)入 ISP 模式后,可以使用 TeraTerm 等軟件,通過串口,使用 Ymodem 協(xié)議將二進(jìn)制文件下載到與微控制器連接的 QSPI Flash 中,實現(xiàn)固件更新的功能。
但本文并沒有對固件更新過程中可能出現(xiàn)的意外進(jìn)行處理,所以這種 ISP 的辦法不能直接用在 OTA 升級中,在下一章中,我們將會探討 OTA 升級時可能會出現(xiàn)的意外情況,并且進(jìn)行處理。
審核編輯:湯梓紅
-
FlaSh
+關(guān)注
關(guān)注
10文章
1642瀏覽量
148677 -
ISP
+關(guān)注
關(guān)注
6文章
478瀏覽量
52016 -
應(yīng)用程序
+關(guān)注
關(guān)注
38文章
3292瀏覽量
57917 -
bootloader
+關(guān)注
關(guān)注
2文章
235瀏覽量
45736 -
Ymodem
+關(guān)注
關(guān)注
0文章
3瀏覽量
3633
原文標(biāo)題:靈動微課堂 (第259講)|mm32-2nd-bootloader技術(shù)白皮書(8)——進(jìn)階:實現(xiàn) Ymodem 更新代碼
文章出處:【微信號:MindMotion-MMCU,微信公眾號:靈動MM32MCU】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論