標(biāo)題: 基于藍(lán)牙的STM32 IAP在線升級 [打印本頁]

作者: 13125378297    時(shí)間: 2019-3-24 12:10
標(biāo)題: 基于藍(lán)牙的STM32 IAP在線升級
最近開發(fā)的一個(gè)小項(xiàng)目需要支持藍(lán)牙在線升級,今天便詳細(xì)地了解一番。藍(lán)牙在線升級的方式,流程如圖
流程解釋:產(chǎn)品的最新程序放在云端的服務(wù)器上,并將程序更新的提醒通過手機(jī)APP推送給用戶,當(dāng)用戶點(diǎn)擊程序更新時(shí),APP將程序下載至手機(jī)上,并通過藍(lán)牙傳輸?shù)絊TM32上,這時(shí)單片機(jī)解析到的指令為程序更新,便觸發(fā)IAP在線刷新程序。

要實(shí)現(xiàn)這一功能,必須通過單片機(jī)的串口IAP在線升級功能。
1.什么是IAP?

       IAP(In Application Programming)即在應(yīng)用編程,IAP 是用戶自己的程序在運(yùn)行過程中對User Flash 的部分區(qū)域進(jìn)行燒寫,目的是為了在產(chǎn)品發(fā)布后可以方便地通過預(yù)留的通信口對產(chǎn)品中的固件程序進(jìn)行更新升級。

        所以要實(shí)現(xiàn)IAP功能,固件程序須分為兩個(gè)代碼,即引導(dǎo)程序(BootLoader)和用戶程序(APP)。
1.引導(dǎo)程序(BootLoader):只執(zhí)行串口數(shù)據(jù)的接收、燒寫程序并將程序執(zhí)行地址跳轉(zhuǎn)至用戶程序段(此代碼只能通過JTAG或SWD燒寫)
2.用戶程序(APP):執(zhí)行用戶所要實(shí)現(xiàn)的程序(此代碼通過串口接收,IAP燒寫入單片機(jī)flash中)

2.STM32程序的啟動(dòng)方式

        為什么能將程序分成兩個(gè)程序分別下載,要理解這個(gè),我們有必要了解一下STM32的啟動(dòng)方式,因?yàn)槲矣玫氖荢TM32F103C8T6,所以就以這個(gè)型號(hào)為例:
STM32上電或者復(fù)位后,代碼區(qū)始終從0x00000000開始,三種啟動(dòng)模式其實(shí)就是將各自存儲(chǔ)空間的地址映射到0x00000000中。其啟動(dòng)模式由BOOT0和BOOT1引腳的電平高低來控制。
    1、BOOT1=x BOOT0=0:從Flash啟動(dòng),將主Flash地址0x08000000映射到0x00000000,這樣代碼啟動(dòng)之后就相當(dāng)于從0x08000000開始,這是正常的工作模式。
    2、BOOT1=0 BOOT0=1:從系統(tǒng)存儲(chǔ)器啟動(dòng)。首先控制BOOT0 BOOT1管腳,復(fù)位后,STM32與上述兩種方式類似,從系統(tǒng)存儲(chǔ)器地址0x1FFF F000開始執(zhí)行代碼,這種模式啟動(dòng)的程序功能由廠家設(shè)置。
    3、BOOT1=1 BOOT0=1:從RAM啟動(dòng),將RAM地址0x20000000映射到0x00000000,這樣代碼啟動(dòng)之后就相當(dāng)于從0x20000000開始,這種模式可以用于調(diào)試。
    這里,我默認(rèn)使用的模式是主閃存控制器啟動(dòng),也就是從Flash啟動(dòng)。

3. Flash在STM32內(nèi)存空間的定義

STM32單片機(jī)內(nèi)存空間有明確的定義,
       查看STM32F103C8T6可知,其Flash是分配在空間從0x0800 0000到0x0801FFFF,最大的空間為127KByte。所以在主閃存控制器啟動(dòng)模式下,STM32一上電,單片機(jī)先將0x0800 0000映射到代碼區(qū),然后從0x0800 0000開始執(zhí)行程序。
        在進(jìn)入main函數(shù)前,單片機(jī)還做了以下處理(不需要自己編寫代碼,由單片機(jī)內(nèi)部自動(dòng)執(zhí)行)
        Cortex-M3上電后來到復(fù)位中斷(已將前4個(gè)字節(jié)的值存入MSP堆棧指針),轉(zhuǎn)到__main標(biāo)號(hào),完成RW段的移動(dòng)、ZI段的初始化,建立堆棧,初始化庫函數(shù),然后跳轉(zhuǎn)到main函數(shù),自此就開始執(zhí)行我們編寫的C程序。

4.引導(dǎo)程序和用戶程序內(nèi)存空間的劃分

        我們知道,單片機(jī)默認(rèn)是從0x0800 0000開始執(zhí)行的,其過程:
    1.STM32 在復(fù)位后,先從 0X08000004 地址取出復(fù)位中斷向量的地址,并跳轉(zhuǎn)到復(fù)位中斷服務(wù)程序,如圖標(biāo)號(hào)①所示;
    2.在復(fù)位中斷服務(wù)程序執(zhí)行完之后,會(huì)跳轉(zhuǎn)到我們的main 函數(shù),如圖標(biāo)號(hào)②所示;
    3.而我們的 main 函數(shù)一般都是一個(gè)死循環(huán),在 main 函數(shù)執(zhí)行過程中,如果收到中斷請求(發(fā)生重中斷),此時(shí) STM32 強(qiáng)制將 PC 指針指回中斷向量表處,如圖標(biāo)號(hào)③所示;
    4.然后,根據(jù)中斷源進(jìn)入相應(yīng)的中斷服務(wù)程序,如圖標(biāo)號(hào)④所示;
    5.在執(zhí)行完中斷服務(wù)程序以后,程序再次返回 main 函數(shù)執(zhí)行,如圖標(biāo)號(hào)⑤所示。
        我們將FLash的內(nèi)存空間分為引導(dǎo)程序和用戶程序,其0x0800 0000~0x0800 2000,8KByte的空間作為BootLoader,將0x0800 2000 ~0x0801 FFFF,共120KByte作為用戶空間(STM32F103C8T6實(shí)際只有64KByteFlash,用戶空間為56KByte)。

        通過此種方式后,STM32執(zhí)行程序的流程變?yōu)椋?/font>
    1.STM32 復(fù)位后,還是從 0X08000004 地址取出復(fù)位中斷向量的地址,并跳轉(zhuǎn)到復(fù)位中斷服務(wù)程序,在運(yùn)行完復(fù)位中斷服務(wù)程序之后跳轉(zhuǎn)到 IAP 的 main 函數(shù),如圖標(biāo)號(hào)①所示。
    2.此部分同圖 47.1.1 一樣;在執(zhí)行完 IAP 以后(即將新的 APP 代碼寫入 STM32的 FLASH,灰底部分。
    3.新程序的復(fù)位中斷向量起始地址為 0X08000004+N+M),跳轉(zhuǎn)至新寫入程序的復(fù)位向量表,取出新程序的復(fù)位中斷向量的地址,并跳轉(zhuǎn)執(zhí)行新程序的復(fù)位中斷服務(wù)程序,隨后跳轉(zhuǎn)至新程序的 main 函數(shù),如圖標(biāo)號(hào)②和③所示,
    4.同樣 main 函數(shù)為一個(gè)死循環(huán),并且注意到此時(shí) STM32 的 FLASH,在不同位置上,共有兩個(gè)中斷向量表。

5.引導(dǎo)程序的設(shè)計(jì)
(1)首先,實(shí)現(xiàn)串口中斷函數(shù),藍(lán)牙通過串口將數(shù)據(jù)傳輸?shù)絾纹瑱C(jī)上,因?yàn)镾TM32F103C8T6的RAM只有8KByte,所以上位機(jī)每次發(fā)送的數(shù)據(jù)為1KByte,單片機(jī)燒寫完后再發(fā)送下1KByte數(shù)據(jù)。

extern uint8_t usart_buf[1024+8];
//數(shù)據(jù)長度(2B)  數(shù)據(jù)(1KB)  序號(hào)(2B)  [CRC(4B)]
extern uint16_t buf_cnt;
extern uint8_t bootStatus;

void USART1_IRQHandler(void)
{
    uint8_t temp = 0;
    if( (USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET) )
    {
            temp = (uint8_t)USART_ReceiveData(USART1);
            if(++buf_cnt < (1024+8)  && ((buf_cnt & 0x8000) != 0x8000))
            {
                    usart_buf[buf_cnt - 1] = temp;
             }
            else buf_cnt |= 0x8000;
        }
}
(2)在main函數(shù)中,對接收到的數(shù)據(jù)進(jìn)行判斷

if(flag)      //是否串口是否有接收到數(shù)據(jù)
{
    printf("開始更新固件r\n");
    // 判斷APP程序的起始地址是否為0X08XXXXXX
     if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)
    {
        iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASHFLASH代碼
        printf("固件更新成功\r\n");
    }
    else
    {
        printf("非固件程序\r\n");
    }
    flag = 0;
}

(3)如果檢測到APP程序,進(jìn)入FLASH燒寫函數(shù)

// 從addr起燒錄程序
void Iap_Write(uint32_t addr)
{
    uint16_t temp = 0;
    uint16_t data_size = 0;    //燒錄大小
    uint16_t data_len = 0;   //數(shù)據(jù)長度
    uint16_t index = 0;         //數(shù)據(jù)塊索引
    uint32_t addr_now = addr;    //寫入地址
    uint8_t data_write[1024] = {0};//數(shù)據(jù)緩沖
    //準(zhǔn)備燒錄,數(shù)據(jù)大小為1K
    while(1)
    {
        while((buf_cnt & 0x8000) == 0);//等待數(shù)據(jù)接收完畢
        //解析數(shù)據(jù)
        data_len = (uint16_t)usart_buf[1] << 8 | usart_buf[0];//獲取data有效長度
        for(temp = 2; temp < data_len + 2; temp++)//從第二位開始拷貝數(shù)據(jù)
        {
            data_write[temp - 2] = usart_buf[temp];
        }
        index = usart_buf[1024 + 3] << 8 + usart_buf[1024 + 2];//獲取索引
        //開始寫數(shù)據(jù)
        if(data_len < 1024)//寫入剩下的數(shù)據(jù)
        {
            if(data_len % 2 != 0)data_len += 1;
            STMFLASH_Write(addr_now, (uint16_t *)data_write, data_len / 2);//
            data_size += 1;
            STMFLASH_Write(IAP_INFO, &data_len, 1);
            putString("OK\n");
            break;
        }
        else
        {
            STMFLASH_Write(addr_now, (uint16_t*)data_write, data_len / 2);
            data_size += 1;
            addr_now += 1024;//下一個(gè)1K
            buf_cnt = 0;
            for(temp = 0; temp < USART_BUF_SIZE; temp++)
                usart_buf[temp] = 0;
            putString("Next\n");
        }
    }//燒錄完成
    //清空串口緩存
    buf_cnt = 0;
    for(temp = 0; temp < USART_BUF_SIZE; temp++)usart_buf[temp] = 0;
}

(4)燒錄好APP程序后,引導(dǎo)程序?qū)C指針地址指向APP程序的起始地址,即0x0800 2000;要對PC指針進(jìn)行操作,需使用MSR匯編指令來操作。

//設(shè)置棧頂?shù)刂?/font>
//addr:棧頂?shù)刂?/font>
__asm void MSR_MSP(u32 addr)
{
    MSR MSP, r0 //set Main Stack value
    BX r14
}

然后執(zhí)行PC指針跳轉(zhuǎn)函數(shù)

void Iap_load(uint32_t addr)
{
    if(((*(vu32*)addr) & 0x2FFE0000) == 0x20000000)
    {
        jump = (iapfun) *(vu32*)(addr + 4);//強(qiáng)制轉(zhuǎn)化為函數(shù)
        MSR_MSP(*(vu32*)addr);
        jump();
    }
    else
    {
        printf("Error\n");
        while(1);
    }
}

(5)最后,main函數(shù)的實(shí)現(xiàn)就很簡單了,以輪詢的方式讀取串口數(shù)據(jù),然后燒寫FLASH,最后跳轉(zhuǎn)至APP程序

while(1)
{
     while((readBootTime() != 0)  && (flag == 0))//以輪詢的方式讀取串口
     {
         if(Iap_wait() == 8)
        {
            USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
            flag = 1;
        }
    }
    if(flag)
    {
        IAP_WRITE();
        flag = 0;
    }
    else
        IAP_LOAD();       //跳轉(zhuǎn)至APP程序
}

6.用戶程序APP的實(shí)現(xiàn)

        用戶程序?qū)崿F(xiàn)比較簡單,只需要對地址進(jìn)行設(shè)計(jì)一下就可以,所以我就使用點(diǎn)亮LED來作為APP程序
        圖中 IROM1 的起始地址(Start)一般為 0X08000000,大。⊿ize)為 0X10000,即從 0X08000000 開始的64K 空間為我們的程序存儲(chǔ)(因?yàn)槲覀僑TM32F103C8T6 的 FLASH大小是 64K)。而圖中,我們設(shè)置起始地址(Start)為 0X0800 2000,即偏移量為 0X2000字節(jié)),因而,留給 APP 用的 FLASH 空間(Size)只有0X8000(56K 字節(jié))大小了。設(shè)置好 Start 和 Szie,就完成 APP 程序的起始地址設(shè)置。

(2) 設(shè)置APP程序的中斷向量表的偏移量
        在 systemInit 函數(shù)中的,設(shè)置中斷向量表的偏移量

SCB->VTOR = FLASH_BASE | 0x2000;

以上設(shè)置完成之后,點(diǎn)擊rebuild按鈕重新編譯,生成LED.hex
(3)生成APP程序bin文件
我們通過 MDK 自帶的格式轉(zhuǎn)換工具 fromelf.exe,來實(shí)現(xiàn).axf 文件到.bin 文件的轉(zhuǎn)換。該工具在 MDK 的安裝目錄\ARM\BIN40 文件夾里面
我們就可以在 MDK 編譯成功之后,調(diào)用 fromelf.exe,根據(jù)當(dāng)前工程的 LED.axf,生成一個(gè) LED.bin 的文件。

7.APP程序的藍(lán)牙在線升級

        由于公司云端服務(wù)器還沒搭好,我就先自己的電腦藍(lán)牙與產(chǎn)品藍(lán)牙連接,然后通過用QT寫的串口調(diào)試助手發(fā)送至STM32中,完成APP程序的升級。
        至此,基于藍(lán)牙的STM32  IAP在線升級就完成了!


作者: jiahanjia    時(shí)間: 2020-10-22 13:59
你好,請問這個(gè)IAP有完整的程序嗎?
作者: n_n    時(shí)間: 2021-5-25 17:39
你好,請問可以分享IAP有完整的程序嗎?




歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1