實際上,BootLoader不僅僅在操作系統(tǒng)上使用,在一些內(nèi)存小,功能應(yīng)用較為簡單的單片機(jī)設(shè)備上面也可以通過BootLoader來完成固件升級。
我之前也有發(fā)過一些關(guān)于STM32遠(yuǎn)程升級的文章,但用的是第三方BootLoader,而且是基于操作系統(tǒng)實現(xiàn)的,BootLoader占用的內(nèi)存也比較大,而且不開源。
那么這一期我就來介紹一下如何自己制作一個BootLoader程序,并且通過USB實現(xiàn)固件升級。
1 環(huán)境搭建
關(guān)于STM32以及Keil的環(huán)境這里就不具體介紹了,網(wǎng)上教程也很多,不懂的同學(xué)自行查閱資料。
2 功能描述
在做bootloader之前一定要先想好升級的途徑和方式,這樣才好規(guī)劃分區(qū)以及制作bootloader。
方案介紹:
1)bootloader部分:
運行時從setting里面讀一些參數(shù),確定是否需要升級,如果需要,則把download
分區(qū)的固件搬運到app
分區(qū),如果不需要升級則直接跳轉(zhuǎn)到app
分區(qū)。
2)APP部分:
運行時先連接USB(以USB CDC的方式),然后等待上位機(jī)發(fā)送升級命令,如果收到命令,則進(jìn)入下載模式。
我這里圖方便,USB傳輸固件的方式我采用的是Ymodem協(xié)議,因為這個協(xié)議很多tool都可以用,就不用專門做一個上位機(jī)了。如果你想用其他的協(xié)議或者自定義協(xié)議其實都是可以的,稍做修改就行。
分區(qū)介紹:
我用的是STM32F103,內(nèi)存是128K的(想用內(nèi)存更小的MCU也是可以的,改下各個分區(qū)的內(nèi)存分配就行了)。
分區(qū)表如下:
name | offset | size |
---|---|---|
boot | 0x08000000 | 0x00003000 |
setting | 0x08003000 | 0x00001000 |
app | 0x08004000 | 0x0000E000 |
download | 0x08012000 | 0x0000E000 |
3 BootLoader的制作
不管用的是什么MCU,要實現(xiàn)固件升級都離不開BootLoader,BootLoader是一個統(tǒng)稱,它其實只是一段引導(dǎo)程序,在MCU啟動的時候會先運行這段代碼,判斷是否需要升級,如果不需要升級就跳轉(zhuǎn)到APP分區(qū)運行用戶代碼,如果需要升級則先通過一些硬件接口接收和搬運要升級的新固件,然后再跳轉(zhuǎn)到APP分區(qū)運行新固件,從而實現(xiàn)固件升級。
BootLoader的制作需要根據(jù)實際的需求來做,不同的運行方式或者升級方式在做法上都是有區(qū)別的,包括BootLoader所需要的內(nèi)存空間也不盡相同。
不過不管是用什么方式,Bootloader都應(yīng)該盡可能做的更小更簡潔,這樣的話內(nèi)存的開銷就更小,對于內(nèi)存較小的MCU來說壓力就沒那么大了。
示例代碼如下:
分區(qū)定義:
#define FLASH_SECTOR_SIZE 1024
#define FLASH_SECTOR_NUM 128 // 128K
#define FLASH_START_ADDR ((uint32_t)0x8000000)
#define FLASH_END_ADDR ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))
#define BOOT_SECTOR_ADDR 0x08000000 // BOOT sector start address
#define BOOT_SECTOR_SIZE 0x3000 // BOOT sector size
#define SETTING_SECTOR_ADDR 0x08003000 // SETTING sector start address
#define SETTING_SECTOR_SIZE 0x1000 // SETTING sector size
#define APP_SECTOR_ADDR 0x08004000 // APP sector start address
#define APP_SECTOR_SIZE 0xE000 // APP sector size
#define DOWNLOAD_SECTOR_ADDR 0x08012000 // Download sector start address
#define DOWNLOAD_SECTOR_SIZE 0xE000 // Download sector size
程序跳轉(zhuǎn):
uint8_t jump_app(uint32_t app_addr)
{
uint32_t jump_addr;
jump_callback cb;
if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000)
{
jump_addr = *(__IO uint32_t*) (app_addr + 4);
cb = (jump_callback)jump_addr;
__set_MSP(*(__IO uint32_t*)app_addr);
cb();
return 1;
}
return 0;
}
主函數(shù):
void print_boot_message(void)
{
printf("---------- Enter BootLoader ----------rn");
printf("rn");
printf("======== flash pration table =========rn");
printf("| name | offset | size |rn");
printf("--------------------------------------rn");
printf("| boot | 0x08000000 | 0x00003000 |rn");
printf("| setting | 0x08003000 | 0x00001000 |rn");
printf("| app | 0x08004000 | 0x0000E000 |rn");
printf("| download | 0x08012000 | 0x0000E000 |rn");
printf("======================================rn");
}
int main()
{
process_status process;
uint16_t i;
uint8_t boot_state;
uint8_t down_buf[128];
uint32_t down_addr;
uint32_t app_addr;
uart1_init();
print_boot_message();
boot_parameter.process = read_setting_boot_state();
boot_parameter.addr = APP_SECTOR_ADDR;
while (1)
{
process = get_boot_state();
switch (process)
{
case START_PROGRAM:
printf("start app...rn");
delay_ms(50);
if (!jump_app(boot_parameter.addr))
{
printf("no programrn");
delay_ms(1000);
}
printf("start app failedrn");
break;
case UPDATE_PROGRAM:
printf("update app program...rn");
app_addr = APP_SECTOR_ADDR;
down_addr = DOWNLOAD_SECTOR_ADDR;
printf("app addr: 0x%08X rn", app_addr);
printf("down addr: 0x%08X rn", down_addr);
printf("erase mcu flash...rn");
mcu_flash_erase(app_addr, APP_ERASE_SECTORS);
printf("mcu flash erase successrn");
printf("write mcu flash...rn");
// memset(down_buf, 0, sizeof(down_buf));
for (i = 0; i < APP_ERASE_SECTORS * 8; i++)
{
mcu_flash_read(down_addr, &down_buf[0], 128);
delay_ms(5);
mcu_flash_write(app_addr, &down_buf[0], 128);
delay_ms(5);
down_addr += 128;
app_addr += 128;
// printf("mcu_flash_write: %drn", i);
}
printf("mcu flash write successrn");
set_boot_state(UPDATE_SUCCESS);
break;
case UPDATE_SUCCESS:
printf("update successrn");
boot_state = UPDATE_SUCCESS_STATE;
write_setting_boot_state(boot_state);
set_boot_state(START_PROGRAM);
break;
default:
break;
}
}
}
關(guān)于bootloader詳細(xì)的講解,可以看下我之前發(fā)的博客:STM32 IAP應(yīng)用開發(fā)——自制BootLoader
完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87462312
4 APP的制作
APP部分根據(jù)自己實際的功能來做,我這里用的是USB CDC連接PC端,然后傳輸固件的協(xié)議用的是Ymodem。
實際上USB也可以用HID或者其他的,協(xié)議也是可以自定義,只要能正確的把固件從PC端搬運到MCU的flash就行了。
示例代碼如下:
Ymodem協(xié)議部分:
注:詳細(xì)的協(xié)議解析這里就不講解了,不懂的同學(xué)自行查閱資料。
void ymodem_ack(void)
{
usb_printf("%c", YMODEM_ACK);
}
void ymodem_nack(void)
{
usb_printf("%c", YMODEM_NAK);
}
void ymodem_c(void)
{
usb_printf("%c", YMODEM_C);
}
void set_ymodem_status(process_status process)
{
ymodem.process = process;
}
process_status get_ymodem_status(void)
{
process_status process = ymodem.process;
return process;
}
void ymodem_start(ymodem_callback cb)
{
if (ymodem.status == 0)
{
ymodem.cb = cb;
}
}
void ymodem_recv(download_buf_t *p)
{
uint8_t type = p->data[0];
switch (ymodem.status)
{
case 0:
if (type == YMODEM_SOH)
{
ymodem.process = BUSY;
ymodem.addr = APP_SECTOR_ADDR;
mcu_flash_erase(ymodem.addr, ERASE_SECTORS);
ymodem_ack();
ymodem_c();
ymodem.status++;
}
else if (type == '1')
{// 為了方便調(diào)試,簡單發(fā)一個字符"1"就可以進(jìn)入升級模式了
printf("enter update modern");
ymodem.process = UPDATE_PROGRAM;
}
break;
case 1:
if (type == YMODEM_SOH || type == YMODEM_STX)
{
if (type == YMODEM_SOH)
{
mcu_flash_write(ymodem.addr, &p->data[3], 128);
ymodem.addr += 128;
}
else
{
mcu_flash_write(ymodem.addr, &p->data[3], 1024);
ymodem.addr += 1024;
}
ymodem_ack();
}
else if (type == YMODEM_EOT)
{
ymodem_nack();
ymodem.status++;
}
else
{
ymodem.status = 0;
}
break;
case 2:
if (type == YMODEM_EOT)
{
ymodem_ack();
ymodem_c();
ymodem.status++;
}
break;
case 3:
if (type == YMODEM_SOH)
{
ymodem_ack();
ymodem.status = 0;
ymodem.process = UPDATE_SUCCESS;
}
}
p->len = 0;
}
void ymodem_handle(void)
{
uint8_t boot_state;
process_status process;
process = get_ymodem_status();
switch (process)
{
case START_PROGRAM:
break;
case UPDATE_PROGRAM:
usb_printf("Crn");
delay_ms(1000);
break;
case UPDATE_SUCCESS:
boot_state = UPDATE_PROGRAM_STATE;
mcu_flash_erase(SETTING_BOOT_STATE, 1);
mcu_flash_write(SETTING_BOOT_STATE, &boot_state, 1);
printf("firmware download successrn");
printf("system reboot...rn");
delay_ms(2000);
system_reboot();
break;
default:
break;
}
}
主函數(shù):
#define APP_VERSION "V100"
void print_boot_message(void)
{
printf("======================================rn");
printf("-------------- Enter APP -------------rn");
printf ("app version is: %srn", APP_VERSION);
printf("======================================rn");
}
void user_usb_init(void)
{
USB_Port_Set(0);
delay_ms(700);
USB_Port_Set(1);
Set_USBClock();
USB_Interrupts_Config();
USB_Init();
}
int main(void)
{
SysTick_Init_Config();
USART1_Init_Config(115200);
print_boot_message();
ymodem_init();
user_usb_init();
printf ("app init successrn");
while (1)
{
ymodem_handle();
}
}
修改中斷向量:
bootloader的運行地址是在起始地址上的,所以中斷向量是0,不用改。
但是app的運行地址是在起始地址上做了偏移的,所以中斷向量也要改,不然會運行會出問題。
#define VECT_TAB_OFFSET 0x4000
完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87462312
5 燒錄下載配置
我們的Bootloader做好以后需要燒錄到MCU里面,可以直接用Keil uVison來下載,也可以用J-Flash或者其他,這個都沒關(guān)系,但是要注意內(nèi)存的分配,要把固件燒到對應(yīng)的內(nèi)存地址上。
1)BootLoader部分:
我這里做出來的bootloader bin只有8K,不過為了方便后續(xù)在這部分增加新功能,我實際分配了12K的空間,地址區(qū)間是0x08000000-0x08003000。
如果是用keil下載的話,需要注意flash的配置,具體如下:
如果是用J-Flash或者STlink的工具燒錄的話注意燒錄的起始地址是0x08000000就好了。
2)APP部分:
跟BootLoader一樣,我們按照前面分配好的空間配置APP的參數(shù)。
如果是用J-Flash或者STlink的工具燒錄的話注意燒錄的起始地址是0x08004000就好了。
6 運行測試
用串口助手查看運行l(wèi)og(我這里用的是XShell,用其他的也是可以的)。
不需要升級時直接跳轉(zhuǎn)到App區(qū),如下圖:
進(jìn)入APP之后,往USB發(fā)送一個字符"1",進(jìn)入升級模式,然后通過調(diào)試工具發(fā)送新固件的bin文件。
注:為了方便調(diào)試才用了一個字符"1",實際使用的話最好改一下,太簡單的話容易出現(xiàn)誤操作。調(diào)試工具我用的是XShell,實際上用其他工具也行,只要支持Ymodem方式傳輸文件即可。
串口調(diào)試窗口log如下圖:
USB調(diào)試窗口log如下:
結(jié)束語
好了,關(guān)于自制BootLoader并實現(xiàn)USB IAP升級的介紹就講到這里,本文列舉的例子其實只是升級的其中一種方式,只是提供一個思路,不是唯一的方法,實際上最好還是根據(jù)自己實際的需求來做。
需要源碼的同學(xué)可以在下面的鏈接下載,我把BootLoader和APP都上傳了。
完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87462312
如果你有什么問題或者有更好的方法,歡迎在評論區(qū)留言。