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

Bootloader系統(tǒng)使用新應(yīng)用代碼和/或數(shù)據(jù)管理組件閃存的更新流程

bootloader如何更新

stm32f103f8t6+keil+IAP+Ymodem(有線(xiàn)傳輸)+keil

mm32-2nd-bootloader配置軟硬件環(huán)境
mm32-2nd-bootloader技術(shù)白皮書(shū)(5)——編譯可在QSPI Flash上運(yùn)行的程序

評(píng)論