加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 1 環(huán)境搭建
    • 2 功能描述
    • 3 程序編寫
    • 4 修改工程中的內(nèi)存配置
    • 5 燒錄相關(guān)配置
    • 6 運(yùn)行測試
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

STM32 IAP應(yīng)用開發(fā)——通過串口/RS485實(shí)現(xiàn)固件升級(方式2)

07/15 14:27
3136
閱讀需 32 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

STM32 IAP應(yīng)用開發(fā)——通過串口/RS485實(shí)現(xiàn)固件升級(方式1)

什么是IAP?

IAP(In-Application Programming) 指MCU可以在系統(tǒng)中獲取新代碼并對自己重新編程,即可用程序來改變程序。在應(yīng)用編程(IAP)是用戶的應(yīng)用代碼對片內(nèi)Flash存儲器進(jìn)行擦除/編程的方法。這種方式的典型應(yīng)用就是用一小段代碼來實(shí)現(xiàn)程序的下載,實(shí)際上單片機(jī)ISP功能就是通過IAP技術(shù)來實(shí)現(xiàn)的,即片子在出廠前就已經(jīng)有一段小的boot程序在里面,片子上電后,開始運(yùn)行這段程序,當(dāng)檢測到上位機(jī)有下載要求時(shí),便和上位機(jī)通信,然后下載數(shù)據(jù)到數(shù)據(jù)存儲區(qū),從而實(shí)現(xiàn)固件升級。

什么是BootLoader?

百度百科:在嵌入式操作系統(tǒng)中,BootLoader是在操作系統(tǒng)內(nèi)核運(yùn)行之前運(yùn)行??梢猿跏蓟?a class="article-link" target="_blank" href="/tag/%E7%A1%AC%E4%BB%B6/">硬件設(shè)備、建立內(nèi)存空間映射圖,從而將系統(tǒng)的軟硬件環(huán)境帶到一個(gè)合適狀態(tài),以便為最終調(diào)用操作系統(tǒng)內(nèi)核準(zhǔn)備好正確的環(huán)境。在嵌入式系統(tǒng)中,通常并沒有像BIOS那樣的固件程序(注,有的嵌入式CPU也會內(nèi)嵌一段短小的啟動程序),因此整個(gè)系統(tǒng)的加載啟動任務(wù)就完全由BootLoader來完成。

實(shí)際上,BootLoader不僅僅在操作系統(tǒng)上使用,在一些內(nèi)存小,功能應(yīng)用較為簡單的單片機(jī)設(shè)備上面也可以通過BootLoader來完成固件升級。

我之前也有發(fā)過一些關(guān)于STM32遠(yuǎn)程升級的文章,實(shí)現(xiàn)的方式有很多種,感興趣的同學(xué)可以去看一下。

那么這一期我來介紹一下如何自己制作一個(gè)BootLoader程序,并且通過串口或者RS485實(shí)現(xiàn)固件升級。

1 環(huán)境搭建

關(guān)于STM32以及Keil的環(huán)境這里就不具體介紹了,網(wǎng)上教程也很多,不懂的同學(xué)自行查閱資料。

2 功能描述

在做bootloader之前一定要先想好升級的途徑和方式,這樣才好規(guī)劃分區(qū)以及制作bootloader。

關(guān)于bootloader詳細(xì)的講解,可以看下我之前發(fā)的博客:STM32 IAP應(yīng)用開發(fā)——自制BootLoader

分區(qū)介紹:

我用的是STM32F407,內(nèi)存是512K的(想用內(nèi)存更小的MCU也是可以的,改下各個(gè)分區(qū)的內(nèi)存分配就行了)。

注:F4系列的MCU不像F1那樣,內(nèi)存扇區(qū)都很大(最少也是16K),而且同一塊扇區(qū)只能一起擦除,所以就沒辦法分的那么細(xì)了。詳細(xì)的內(nèi)存分布可以參考下面的兩個(gè)圖。

STM32F4x扇區(qū)分布圖如下:
請?zhí)砑訄D片描述

STM32F1x扇區(qū)分布圖如下:

請?zhí)砑訄D片描述

那么我這里呢,就用一個(gè)512k的內(nèi)存,分成3個(gè)區(qū)域,來實(shí)現(xiàn)一個(gè)升級的功能。

分區(qū)表如下:

name offset size function
boot 0x08000000 0x00004000 存放boot程序
setting 0x08004000 0x00004000 存放設(shè)備需要保存的一些參數(shù)
app 0x08008000 0x00078000 存放應(yīng)用程序

請?zhí)砑訄D片描述

方案介紹:

1)bootloader部分:

開始運(yùn)行后先等待5s,在這個(gè)時(shí)間內(nèi)如果收到串口2或者RS485的升級命令就進(jìn)入升級模式,如果超時(shí)則跳轉(zhuǎn)到用戶程序(APP)。

在升級模式,可以通過串口2或者RS485傳輸要升級的固件,傳輸?shù)臄?shù)據(jù)協(xié)議我這里圖方便就直接用Ymodem了,不知道Ymodem協(xié)議的可以先自行查閱一下資料。

請?zhí)砑訄D片描述

2)APP部分:

APP部分修改一下中斷向量表地址即可,其他的隨便你做什么應(yīng)用。

另外,我在分區(qū)的時(shí)候留了一塊settimg區(qū),在實(shí)際的應(yīng)該中如果有需要記錄一些掉電后還能保存的數(shù)據(jù),那么這塊區(qū)域就可以用得上了。

3 程序編寫

3.1 BootLoader部分

不管用的是什么MCU,要實(shí)現(xiàn)固件升級都離不開BootLoader,BootLoader是一個(gè)統(tǒng)稱,它其實(shí)只是一段引導(dǎo)程序,在MCU啟動的時(shí)候會先運(yùn)行這段代碼,判斷是否需要升級,如果不需要升級就跳轉(zhuǎn)到APP分區(qū)運(yùn)行用戶代碼,如果需要升級則先通過一些硬件接口接收和搬運(yùn)要升級的新固件,然后再跳轉(zhuǎn)到APP分區(qū)運(yùn)行新固件,從而實(shí)現(xiàn)固件升級。

BootLoader的制作需要根據(jù)實(shí)際的需求來做,不同的運(yùn)行方式或者升級方式在做法上都是有區(qū)別的,包括BootLoader所需要的內(nèi)存空間也不盡相同。

不過不管是用什么方式,Bootloader都應(yīng)該盡可能做的更小更簡潔,這樣的話內(nèi)存的開銷就更小,對于內(nèi)存較小的MCU來說壓力就沒那么大了。

注:我這里是基于正點(diǎn)原子的工程模板改的,增加了自己的功能。

示例代碼如下:

Bootloader分區(qū)定義:

#define FLASH_SECTOR_SIZE           1024
#define FLASH_SECTOR_NUM            512    // 512K
#define FLASH_START_ADDR            ((uint32_t)0x8000000)
#define FLASH_END_ADDR              ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))

//flash sector addr
#define ADDR_FLASH_SECTOR_0         ((uint32_t)0x08000000) 	//sector0 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_1         ((uint32_t)0x08004000) 	//sector1 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_2         ((uint32_t)0x08008000) 	//sector2 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3         ((uint32_t)0x0800C000) 	//sector3 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4         ((uint32_t)0x08010000) 	//sector4 addr, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5         ((uint32_t)0x08020000) 	//sector5 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6         ((uint32_t)0x08040000) 	//sector6 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_7         ((uint32_t)0x08060000) 	//sector7 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_8         ((uint32_t)0x08080000) 	//sector8 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_9         ((uint32_t)0x080A0000) 	//sector9 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_10        ((uint32_t)0x080C0000) 	//sector10 addr,128 Kbytes  
#define ADDR_FLASH_SECTOR_11        ((uint32_t)0x080E0000) 	//sector11 addr,128 Kbytes  

#define BOOT_SECTOR_ADDR            0x08000000     // BOOT sector start addres
#define BOOT_SECTOR_SIZE            0x4000         // BOOT sector size    
#define SETTING_SECTOR_ADDR         0x08004000     // SETTING sector start addres  
#define SETTING_SECTOR_SIZE         0x4000         // SETTING sector size     
#define APP_SECTOR_ADDR             0x08008000     // APP sector start address  
#define APP_SECTOR_SIZE             0x78000        // APP sector size    

#define BOOT_ERASE_SECTORS_NUM      1  // 16k
#define SETTING_ERASE_SECTORS_NUM   1  // 16k
#define APP_ERASE_SECTORS_NUM       6  // 16k + 16k + 64k + 128k + 128k + 128k

main函數(shù):

#include "bootloader.h"
#include "usart.h"
#include "rs485.h"
#include "delay.h"
#include "ymodem.h"

#define WAIT_TIMEOUT   5

void print_boot_message(void)
{
    uart_log("---------- Enter BootLoader ----------rn");
    uart_log("rn");
    uart_log("======== flash pration table =========rn");
    uart_log("| name     | offset     | size       |rn");
    uart_log("--------------------------------------rn");
    uart_log("| boot     | 0x%08X | 0x%08X |rn", BOOT_SECTOR_ADDR, BOOT_SECTOR_SIZE);
    uart_log("| setting  | 0x%08X | 0x%08X |rn", SETTING_SECTOR_ADDR, SETTING_SECTOR_SIZE);
    uart_log("| app      | 0x%08X | 0x%08X |rn", APP_SECTOR_ADDR, APP_SECTOR_SIZE);
    uart_log("======================================rn");
}

void print_wait_message(void)
{
    uart_log("------- Please enter parameter -------rn");
	uart_log("[1].Start programrn");
	uart_log("[2].Update programrn");
    uart_log("--------------------------------------rn");
}

int main() 
{
    process_status process;
    uint16_t timerout = 0;

    delay_init(168);
    uart_init(115200);
    ymodem_init();
    print_boot_message();
    print_wait_message();

    while (1) 
    {
        process = get_ymodem_status();
        switch (process) 
        {
            case WAIT_START_PROGRAM:
                uart_log("wait start app...(%ds)rn", WAIT_TIMEOUT - timerout);
                delay_ms(1000);
                timerout ++;
                if(timerout >= WAIT_TIMEOUT)
                {
                    set_ymodem_status(START_PROGRAM);
                }
                break;
            case START_PROGRAM:
                uart_log("start app...rn");
                delay_ms(50);
                if (!jump_app(APP_SECTOR_ADDR)) 
                {
                    uart_log("start app failed: app no programrn");
                    delay_ms(1000);
                }
                break;
            case UPDATE_PROGRAM:
                ymodem_c();
                uart_log("update app program...rn");
                delay_ms(1000);
                break;
            case UPDATE_SUCCESS:
                uart_log("update successrn");
                uart_log("system reboot...rn");
                delay_ms(1000);
                system_reboot();
                break;
            default:
                break;
        }
    }
}

Ymodem協(xié)議處理:

#define YMODEM_SOH		0x01
#define YMODEM_STX		0x02
#define YMODEM_EOT		0x04
#define YMODEM_ACK		0x06
#define YMODEM_NAK		0x15
#define YMODEM_CA		0x18
#define YMODEM_C		0x43

#define MAX_QUEUE_SIZE  1200

typedef void (*ymodem_callback)(process_status);

typedef struct 
{
	process_status process;
	uint8_t status;
	uint8_t id;
	uint32_t addr;
	uint8_t sectors_size;
	ymodem_callback cb;
} ymodem_t;

//順序循環(huán)隊(duì)列的結(jié)構(gòu)體定義如下:
typedef struct
{
	uint8_t queue[MAX_QUEUE_SIZE];
	int rear;  //隊(duì)尾指針
	int front;  //隊(duì)頭指針
	int count;  //計(jì)數(shù)器
} seq_queue_t; 

typedef struct 
{
	uint8_t data[1200];
	uint16_t len;
} download_buf_t;

void ymodem_ack(void) 
{
    uint8_t buf;
    buf = YMODEM_ACK;
    RS485_Send_Data(&buf, 1);
}

void ymodem_nack(void) 
{
    uint8_t buf;
    buf = YMODEM_NAK;
    RS485_Send_Data(&buf, 1);
}

void ymodem_c(void) 
{
    uint8_t buf;
    buf = YMODEM_C;
    RS485_Send_Data(&buf, 1);
}

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;
                uart_log("erase flash: 0x%08Xrn", APP_SECTOR_ADDR);
                mcu_flash_erase(ymodem.addr, APP_ERASE_SECTORS_NUM);
                uart_log("erase flash successrn");
                ymodem_ack();
                ymodem_c();
                ymodem.status++;
            }
            else if (type == '1') 
            {
                uart_log("start program nowrn");
                ymodem.process = START_PROGRAM;
            }
            else if (type == '2') 
            {
                uart_log("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_init(void)
{
    RS485_Init(115200);
    timer_init();
    queue_initiate(&rx_queue);
}

關(guān)于bootloader詳細(xì)的講解,可以看下我之前發(fā)的博客:

STM32 IAP應(yīng)用開發(fā)——自制BootLoader

完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

3.2 APP的制作

APP部分根據(jù)自己實(shí)際的功能來做,只要記得修改中斷向量表地址即可。地址的值等于你APP區(qū)的起始地址。

示例代碼如下:

main函數(shù):

#include "main.h"
#include "usart.h"
#include "delay.h"

#define APP_VERSION          "V100"
#define NVIC_VTOR_MASK       0x3FFFFF80
#define APP_PART_ADDR        0x08008000

void ota_app_vtor_reconfig(void)
{
    /* Set the Vector Table base location by user application firmware definition */
    SCB->VTOR = APP_PART_ADDR & NVIC_VTOR_MASK;
}

void led_init(void)
{         
    GPIO_InitTypeDef  GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOF, &GPIO_InitStructure);
    GPIO_SetBits(GPIOF, GPIO_Pin_9);
}

void print_boot_message(void)
{
    uart_log("======================================rn");
    uart_log("-------------- Enter APP -------------rn");
    uart_log ("app version is: %srn", APP_VERSION);
    uart_log("======================================rn");
}

int main(void)
{
    ota_app_vtor_reconfig();
    delay_init(168);
    uart_init(115200);
    print_boot_message();
    led_init();

    uart_log ("app init successrn");
    while (1)
    {
        GPIO_SetBits(GPIOF, GPIO_Pin_9);
        delay_ms(1000);
        GPIO_ResetBits(GPIOF, GPIO_Pin_9);
        delay_ms(1000);
    }
}

完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

4 修改工程中的內(nèi)存配置

因?yàn)槲覀儗?a class="article-link" target="_blank" href="/e/1605630.html">stm32的內(nèi)存進(jìn)行了分區(qū),不同的代碼要存放在不同的區(qū)域,因此,我們在編譯工程之前需要先定義好各自的區(qū)域,以免出現(xiàn)內(nèi)存越界。

4.1 Bootloader工程內(nèi)存配置

Bootloader的起始地址不需要改,按flash默認(rèn)地址即可,size需要改成實(shí)際分區(qū)大小。

請?zhí)砑訄D片描述

4.2 APP工程內(nèi)存配置

APP的起始地址和size都需要根據(jù)實(shí)際的分區(qū)來改。
請?zhí)砑訄D片描述

5 燒錄相關(guān)配置

我們的Bootloader做好以后需要燒錄到MCU里面,可以直接用Keil uVison來下載,也可以用J-Flash或者其他,這個(gè)都沒關(guān)系,但是要注意內(nèi)存的分配,要把固件燒到對應(yīng)的內(nèi)存地址上。

5.1 BootLoader部分

1)使用Keil uVision下載
如果是用keil下載的話,需要注意flash的配置,具體如下:
請?zhí)砑訄D片描述

2)使用其他下載工具

如果是用J-Flash或者STlink的工具燒錄的話注意燒錄的起始地址是0x08000000就好了。

5.2 APP部分

1)使用Keil uVision下載

跟BootLoader一樣,我們按照前面分配好的空間配置APP的參數(shù)即可。

請?zhí)砑訄D片描述

2)使用其他下載工具

如果是用J-Flash或者STlink的工具燒錄的話注意燒錄的起始地址是0x08008000就好了。

6 運(yùn)行測試

用串口助手查看運(yùn)行l(wèi)og(我這里用的是XShell,用其他的也是可以的)。

1)開始運(yùn)行代碼

等待5s,如果不需要升級就跳轉(zhuǎn)到App區(qū),如下圖:

請?zhí)砑訄D片描述

2)發(fā)送命令1

在等待的5s內(nèi)通過串口2或者RS485發(fā)送一個(gè)’1’,直接跳轉(zhuǎn)到APP。

注:我這里為了方便調(diào)試才用的這種方式,實(shí)際上可以根據(jù)自己的需求來做。

請?zhí)砑訄D片描述

3)發(fā)送命令2,進(jìn)入升級模式

在等待的5s內(nèi)通過串口2或者RS485發(fā)送一個(gè)’2’,進(jìn)入升級模式。

注:我這里為了方便調(diào)試才用的這種方式,實(shí)際上可以根據(jù)自己的需求來做。比如用按鍵進(jìn)入,或者用其他串口,USB之類的,也可以在APP部分做這個(gè)功能。

串口調(diào)試窗口log如下圖:

請?zhí)砑訄D片描述

4)通過Ymodem傳輸新固件

調(diào)試工具我用的是XShell,實(shí)際上用其他工具也行,只要支持Ymodem方式傳輸文件即可。

請?zhí)砑訄D片描述

請?zhí)砑訄D片描述

5)升級固件

固件升級完成后自動重啟,重新運(yùn)行Bootloader和APP。

請?zhí)砑訄D片描述

至此,整個(gè)升級流程就走完了。

結(jié)束語

好了,關(guān)于自制BootLoader并實(shí)現(xiàn)串口以及RS485升級固件的介紹就講到這里,本文列舉的例子其實(shí)只是升級的其中一種方式,只是提供一個(gè)思路,不是唯一的方法,實(shí)際上最好還是根據(jù)自己實(shí)際的需求來做。我之前也發(fā)給幾篇升級相關(guān)的文章,用的都是不同的方式,各有各的優(yōu)點(diǎn)和缺點(diǎn),感興趣的同學(xué)可以去看一下。

需要源碼的同學(xué)可以在下面的鏈接下載,我把BootLoader和APP都上傳了。

如果你有什么問題或者有更好的方法,歡迎在評論區(qū)留言。

完整代碼下載地址:https://download.csdn.net/download/ShenZhen_zixian/87553496

推薦器件

更多器件
器件型號 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊 ECAD模型 風(fēng)險(xiǎn)等級 參考價(jià)格 更多信息
XRCGB25M000F3A00R0 1 Murata Manufacturing Co Ltd Parallel - Fundamental Quartz Crystal, 25MHz Nom, ROHS AND REACH COMPLIANT, SMALL, SMD, 3 PIN

ECAD模型

下載ECAD模型
$0.34 查看
M25P05-AVMN6P 1 Rochester Electronics LLC 64KX8 FLASH 2.7V PROM, PDSO8, 0.150 INCH, ROHS COMPLIANT, PLASTIC, SOP-8
$0.79 查看
S29GL256P10FFI020 1 AMD Flash, 16MX16, 100ns, PBGA64,
$9.29 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜