0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

STM32速成筆記(15)—串口IAP

冬至子 ? 來源:二土電子 ? 作者:二土電子 ? 2023-10-24 17:19 ? 次閱讀

一、串口IAP簡介

1.1 什么是IAP

IAP,英文全稱In Application Programming,在應(yīng)用中編程。很好理解,就是在程序運行過程中我們進行程序的燒寫,或者叫升級。

1.2 STM32下載程序

我們都知道,STM32可以利用串口下載程序,這是因為ST公司在產(chǎn)線上就在產(chǎn)品中內(nèi)嵌了自舉程序。所謂的自舉程序,實際就是支持我們通過串口下載程序的代碼。自舉程序被存放在系統(tǒng)存儲區(qū),因此如果我們需要通過串口下載程序,需要將Boot0接高電平,Boot1接低電平,讓程序從系統(tǒng)存儲器開始運行,運行自舉程序。下載完成后我們再將Boot0接地,讓程序從主閃存存儲器開始運行。自舉程序是我們用戶無法修改的。

二、串口IAP有什么作用

上面我們介紹了什么是IAP,那么這個IAP到底有什么作用呢?

首先介紹兩個詞——BootloaderApplication 。Bootloader實際就是一段引導(dǎo)程序,單片機上電后先執(zhí)行Bootloader程序,然后再執(zhí)行用戶編寫的應(yīng)用程序Application。介紹完這兩個詞,我們來介紹一下IAP有什么作用。比如我們生產(chǎn)了A,B兩款產(chǎn)品。A產(chǎn)品是某個精密器件的一部分,B產(chǎn)品是一款物聯(lián)網(wǎng)產(chǎn)品。我們的產(chǎn)品銷售范圍很廣,遠(yuǎn)銷海外。

某天A產(chǎn)品的某個客戶反映了一個Bug,我們編寫好了程序,需要進行程序更新,或者叫升級。利用IAP,我們可以在程序運行時,通過預(yù)留的通信接口直接燒寫程序。而不需要再把整個設(shè)備拆開,像我們調(diào)試時那樣下載程序。甚至我們可以直接給客戶郵寄一個小設(shè)備,客戶直接進行傻瓜式升級。

又過了一段時間,B產(chǎn)品的程序出現(xiàn)了一個Bug,風(fēng)險等級比較低,但是依舊需要全體升級程序。我們總不能挨個產(chǎn)品派人去升級,成本極大。這時候又輪到我們的IAP出場了。它可以在所有設(shè)備在線運行的情況下,直接通過網(wǎng)絡(luò)下發(fā)升級程序,實現(xiàn)在線升級,節(jié)約了大量的人力成本。

通過上面這兩個例子,大家應(yīng)該能夠基本了解IAP的用處,使用IAP讓我們不需要再使用調(diào)試器進行下載,甚至實現(xiàn)設(shè)備的在線升級。

三、啟動流程

在介紹如何實現(xiàn)IAP之前,我們先來簡單了解以下STM32的啟動流程。

3.1 正常啟動流程

這里的正常啟動流程指的是,沒有添加IAP的流程。

圖片

正常啟動流程

程序啟動時首先開辟??臻g,配置棧頂指針。然后配置堆空間。配置完成后,建立中斷向量表,在中斷向量表中找到復(fù)位中斷,開始執(zhí)行復(fù)位中斷服務(wù)函數(shù),然后跳轉(zhuǎn)到main函數(shù)中,執(zhí)行用戶代碼。當(dāng)用戶代碼中有中斷請求時,會回到中斷向量表,根據(jù)中斷源執(zhí)行相應(yīng)的中斷服務(wù)函數(shù)。

3.2 加入IAP后的啟動流程

下面是加入IAP之后的啟動流程。

圖片

加入IAP啟動流程

可以看到,與上面不同的是,加入IAP后,執(zhí)行完復(fù)位中斷服務(wù)函數(shù)后直接進入IAP的main函數(shù)。在執(zhí)行完IAP之后,跳轉(zhuǎn)至新寫入程序的復(fù)位向量表,取出新程序的復(fù)位中斷向量的地址,并跳轉(zhuǎn)執(zhí)行新程序的復(fù)位中斷服務(wù)程序,隨后跳轉(zhuǎn)至新程序的main 函數(shù)。

由上面的兩個啟動過程我們可以看出

  • ? 新程序必須在 IAP 程序之后的某個偏移量為 x 的地址開始。

  • ? 必須將新程序的中斷向量表相應(yīng)的移動,移動的偏移量為 x。

    四、必備知識

    44.1 修改程序運行起始地址

點擊魔術(shù)棒,選擇“Target”,修改運行起始地址和代碼大小。

圖片

修改App運行起始地址

4.2 設(shè)置中斷向量表偏移

VTOR 寄存器存放的是中斷向量表的起始地址。如果要設(shè)置中斷向量表偏移,只需要在main函數(shù)最開始添加如下語句即可

SCB- >VTOR = FLASH BASE | 偏移量:

4.3 生成.bin文件

點擊魔術(shù)棒,選擇“User”,按照如下配置,輸入下面的內(nèi)容

fromelf --bin -o "$L@L.bin" "#L"

圖片

生成.bin文件配置

點擊編譯,不報錯就可以,去設(shè)置的輸出文件夾中就可以找到對應(yīng)的.bin文件。

圖片

編譯提示

五、串口IAP實現(xiàn)

本次的目標(biāo)是實現(xiàn)一個串口IAP,也就是編寫B(tài)ootloader,在程序運行過程中實現(xiàn)程序的下載。Bootloader程序應(yīng)該可以通過串口接收上位機發(fā)來的.bin文件(App程序),檢查后將.bin文件寫入到Flash特定位置,然后跳轉(zhuǎn)到App程序運行。

5.1 串口中斷服務(wù)函數(shù)

本次的串口中斷服務(wù)函數(shù)與之前不同,這里單獨貼出來。需要定義一個接收數(shù)組,接收數(shù)組的起始地址限制為0X20001000。接收數(shù)組最多可以接收55K字節(jié),可以根據(jù)需要調(diào)整。但是需要注意的是,數(shù)組的大小需要比App程序要大,而且不能超過芯片的SRAM空間大小。

/*
 *==============================================================================
 *函數(shù)名稱:USART1_IRQHandler
 *函數(shù)功能:USART1中斷服務(wù)函數(shù)
 *輸入參數(shù):無
 *返回值:無
 *備  注:無
 *==============================================================================
 */
u32 gReceCount = 0;   // 接收計數(shù)變量
// 接收數(shù)組
// 限制起始地址為0X20001000
// 保證偏移量為 0X200的倍數(shù)
// 是為了給App留SRAM空間
u8 gReceFifo[USART_RECE_MAX_LEN]__attribute__ ((at(0X20001000)));

void USART1_IRQHandler(void)  
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)   //接收到一個字節(jié)  
    {
        if (gReceCount < USART_RECE_MAX_LEN)
        {
            gReceFifo[gReceCount++] = USART1- >DR;
        }
        else
        {
            printf ("APP code out of memory!rn");
        }
    }
}

5.2 Flash寫入程序

關(guān)于Flash程序,這里就不在贅述,只是貼一下帶檢查的寫入程序。其他具體內(nèi)容可以到博主STM32速成筆記專欄查看。

/*
 *==============================================================================
 *函數(shù)名稱:Med_Flash_Write
 *函數(shù)功能:從指定地址開始寫入指定長度的數(shù)據(jù)
 *輸入?yún)?shù):WriteAddr:寫入起始地址;pBuffer:數(shù)據(jù)指針;
                        NumToRead:寫入(半字)數(shù)
 *返回值:無
 *備  注:對內(nèi)部Flash的操作是以半字為單位,所以讀寫地址必須是2的倍數(shù)
 *==============================================================================
 */

// 根據(jù)中文參考手冊,大容量產(chǎn)品的每一頁是2K字節(jié)
#if STM32_FLASH_SIZE < 256
    #define STM32_SECTOR_SIZE   1024   // 字節(jié)
#else 
    #define STM32_SECTOR_SIZE   2048
#endif

// 一個扇區(qū)的內(nèi)存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];

void Med_Flash_Write (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
    u32 secpos;   // 扇區(qū)地址
    u16 secoff;   // 扇區(qū)內(nèi)偏移地址(16位字計算)
    u16 secremain;   // 扇區(qū)內(nèi)剩余地址(16位計算)    
     u16 i;    
    u32 offaddr;   // 去掉0X08000000后的地址
    
    // 判斷寫入地址是否在合法范圍內(nèi)
    if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
    {
        return;   // 非法地址
    }
    
    FLASH_Unlock();   // 解鎖
    offaddr = WriteAddr - STM32_FLASH_BASE;   // 實際偏移地址
    secpos = offaddr / STM32_SECTOR_SIZE;   // 扇區(qū)地址
    secoff = (offaddr % STM32_SECTOR_SIZE) / 2;   // 在扇區(qū)內(nèi)的偏移(2個字節(jié)為基本單位)
    secremain = STM32_SECTOR_SIZE / 2 - secoff;   // 扇區(qū)剩余空間大小
    
    if (NumToWrite <= secremain)
    {
        secremain = NumToWrite;   // 不大于該扇區(qū)范圍
    }
    
    while (1) 
    {
        // 讀出整個扇區(qū)的內(nèi)容
        Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
        
        // 校驗數(shù)據(jù)
        for (i = 0;i < secremain;i ++)
        {
            // 需要擦除 
            if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
            {
                break; 
            }    
        }
        // 需要擦除
        if (i < secremain)
        {
            FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE);   // 擦除這個扇區(qū)
            
            // 復(fù)制
            for (i = 0;i < secremain;i ++)
            {
                STM32_FLASH_BUF[i + secoff] = pBuffer[i];   
            }
            
            // 寫入整個扇區(qū)
            Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
        }
        else
        {
            // 寫已經(jīng)擦除了的,直接寫入扇區(qū)剩余區(qū)間
            Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
        }
        
        if (NumToWrite == secremain)
        {
            break;   // 寫入結(jié)束了
        }
        // 寫入未結(jié)束
        else
        {
            secpos ++;   // 扇區(qū)地址增1
            secoff = 0;   // 偏移位置為0   
            pBuffer += secremain;   // 指針偏移
            WriteAddr += secremain;   // 寫地址偏移    
            NumToWrite -= secremain;   // 字節(jié)(16位)數(shù)遞減
            if (NumToWrite > (STM32_SECTOR_SIZE / 2))
            {
                secremain = STM32_SECTOR_SIZE / 2;   // 下一個扇區(qū)還是寫不完
            }
            else
            {
                secremain = NumToWrite;   // 下一個扇區(qū)可以寫完了
            }
        }  
    } 
    FLASH_Lock();   // 上鎖
}

5.3 IAP程序

IAP程序包含兩部分,一部分是將接收到的.bin文件寫入Flash,另一部分是跳轉(zhuǎn)到App執(zhí)行。

/*
 *==============================================================================
 *函數(shù)名稱:iap_write_appbin
 *函數(shù)功能:寫入.bin文件
 *輸入?yún)?shù):appxaddr:App程序起始地址;appbuf:緩存App程序的數(shù)組;
                        appsize:App程序大小
 *返回值:無
 *備  注:無
 *==============================================================================
 */
 // 對Flash操作的最小單位是16位
 u16 iapbuf[1024];   // 要寫入Flash的內(nèi)容
 
void iap_write_appbin (u32 appxaddr,u8 *appbuf,u32 appsize)
{
    u16 t;   // 臨時循環(huán)變量
    u16 i = 0;   // 自增索引
    u16 temp;   // 臨時計算變量
    
    u32 fwaddr = appxaddr;   // 當(dāng)前寫入的地址
    u8 *dfu = appbuf;   // 指向App代碼數(shù)組首地址的指針
    
    // for循環(huán),2K為單位進行寫入
    for(t = 0;t < appsize;t += 2)
    {  
        temp = (u16)dfu[1] < < 8;
        temp += (u16)dfu[0];   
        dfu += 2;   // 偏移2個字節(jié)
        iapbuf[i++] = temp;     
        if(i == 1024)
        {
            i = 0;
            Med_Flash_Write (fwaddr,iapbuf,1024); 
            fwaddr += 2048;   // 偏移2048  16=2*8所以要乘以2
        }
    }
    if(i)
    {
        Med_Flash_Write (fwaddr,iapbuf,i);   // 將最后的一些內(nèi)容字節(jié)寫進去
    }
}
/*
 *==============================================================================
 *函數(shù)名稱:iap_load_app
 *函數(shù)功能:跳轉(zhuǎn)到App
 *輸入?yún)?shù):appxaddr:App程序起始地址
 *返回值:無
 *備  注:無
 *==============================================================================
 */
iapfun jump2app;

void iap_load_app (u32 appxaddr)
{
    if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)   // 檢查棧頂?shù)刂肥欠窈戏?/span>
    { 
        jump2app=(iapfun)*(vu32*)(appxaddr+4);   // 用戶代碼區(qū)第二個字為程序開始地址(復(fù)位地址)  
        MSR_MSP(*(vu32*)appxaddr);   // 初始化APP堆棧指針(用戶代碼區(qū)的第一個字用于存放棧頂?shù)刂?
        jump2app();   // 跳轉(zhuǎn)到APP
    }
}

5.4 main函數(shù)

main函數(shù)設(shè)計如下

int main(void)
{
    u32 curRecCunt = 0;   // 當(dāng)前接收數(shù)量
    
    // 設(shè)置NVIC中斷分組2:2位搶占優(yōu)先級,2位響應(yīng)優(yōu)先級
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    delay_init();   // 延時初始化
    uart_init(115200);   // 串口初始化
    
    printf ("Enter the Bootloader Code!rn");
    
    while(1)
  {
        // 如果接收到內(nèi)容且傳輸完成
        if (curRecCunt == gReceCount && gReceCount != 0)
        {
            printf ("App Code reception complete!rn");
            printf ("The length of the received App code is %d!rn",gReceCount);
            
            // 開始準(zhǔn)備寫入Flash
            if(((*(vu32*)(0X20001000+4)) & 0xFF000000) == 0x08000000)   // 判斷是否為0X08XXXXXX.
            {
                iap_write_appbin(FLASH_APP1_ADDR,gReceFifo,gReceCount);   // 更新FLASH代碼  
                gReceCount = 0;   // 清零接收計數(shù)變量
                printf("Update complete!rn");
            }
            else 
            { 
                printf("Update error!rn");
            }
            
            // 準(zhǔn)備跳轉(zhuǎn)App執(zhí)行
            if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)   //判斷是否為0X08XXXXXX.
            {
                printf("Enter App!rn");
                iap_load_app(FLASH_APP1_ADDR);   //執(zhí)行FLASH APP代碼
            }else 
            {
                printf("Enter error!rn");
            } 
        }
        else   // 未傳輸完成
        {
            curRecCunt = gReceCount;   // 更新當(dāng)前接收數(shù)量
            // 延時一定要有,給串口中斷留出時間
            // 如果沒有會導(dǎo)致接收不完全
            delay_ms(10);
        }
    }
}

六、注意事項

需要注意的是,上面的程序中App程序是從0x8010000開始,需要配置好程序起始地址和中斷向量表偏移

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 寄存器
    +關(guān)注

    關(guān)注

    31

    文章

    5268

    瀏覽量

    119640
  • STM32
    +關(guān)注

    關(guān)注

    2262

    文章

    10846

    瀏覽量

    353650
  • 上位機
    +關(guān)注

    關(guān)注

    27

    文章

    928

    瀏覽量

    54643
  • 中斷向量
    +關(guān)注

    關(guān)注

    0

    文章

    14

    瀏覽量

    8912
  • flash內(nèi)存
    +關(guān)注

    關(guān)注

    0

    文章

    5

    瀏覽量

    2116
收藏 人收藏

    評論

    相關(guān)推薦

    基于STM32串口環(huán)形隊列IAP調(diào)試

    基于STM32串口環(huán)形隊列IAP調(diào)試心得
    的頭像 發(fā)表于 09-18 15:33 ?1305次閱讀
    基于<b class='flag-5'>STM32</b>的<b class='flag-5'>串口</b>環(huán)形隊列<b class='flag-5'>IAP</b>調(diào)試

    STM32是如何實現(xiàn)IAP功能的

    STM32實現(xiàn)IAP功能的學(xué)習(xí)筆記最近因項目需求要實現(xiàn)STM32的在線升級即IAP功能,先將這幾天的學(xué)習(xí)體會和
    發(fā)表于 08-11 08:07

    求大佬分享STM32F4串口IAP學(xué)習(xí)筆記

    求大佬分享STM32F4串口IAP學(xué)習(xí)筆記
    發(fā)表于 12-08 06:41

    STM32 IAP 應(yīng)用編程幾個要點

    STM32 IAP應(yīng)用編程幾個要點
    的頭像 發(fā)表于 03-08 11:42 ?9503次閱讀

    STM32F103學(xué)習(xí)筆記串口通信

    STM32F103學(xué)習(xí)筆記串口通信
    發(fā)表于 11-25 09:06 ?71次下載
    <b class='flag-5'>STM32</b>F103學(xué)習(xí)<b class='flag-5'>筆記</b>三   <b class='flag-5'>串口</b>通信

    STM32--STM32F051 IAP的實現(xiàn)

    一、IAP原理及過程《正點原子--STM32F10x串口IAP實驗》《stm32f030 IAP
    發(fā)表于 11-29 15:06 ?32次下載
    <b class='flag-5'>STM32--STM32</b>F051 <b class='flag-5'>IAP</b>的實現(xiàn)

    [筆記]|[stm32]|[寄存器存儲器區(qū)別]|[PWM]|[串口]|[Timer]stm32f103筆記

    [筆記]|[stm32]|[寄存器存儲器區(qū)別]|[PWM]|[串口]|[Timer]stm32f103筆記
    發(fā)表于 12-06 18:51 ?10次下載
    [<b class='flag-5'>筆記</b>]|[<b class='flag-5'>stm32</b>]|[寄存器存儲器區(qū)別]|[PWM]|[<b class='flag-5'>串口</b>]|[Timer]<b class='flag-5'>stm32</b>f103<b class='flag-5'>筆記</b>

    STM32實現(xiàn)IAP功能的學(xué)習(xí)筆記

    最近因項目需求要實現(xiàn)STM32的在線升級即IAP功能,先將這幾天的學(xué)習(xí)體會和IAP的具體實現(xiàn)總結(jié)出來,分享給大家,希望對同樣實現(xiàn)IAP的童鞋有所幫助,文中
    發(fā)表于 12-27 18:41 ?11次下載
    <b class='flag-5'>STM32</b>實現(xiàn)<b class='flag-5'>IAP</b>功能的學(xué)習(xí)<b class='flag-5'>筆記</b>

    基于STM32F103的IAP串口升級源碼

    基于STM32F103的IAP串口升級源碼代碼,共兩個工程,bl+app分享
    發(fā)表于 09-23 17:08 ?41次下載

    N32WB452系列芯片串口IAP升級應(yīng)用筆記

    N32WB452系列芯片串口IAP升級應(yīng)用筆記
    發(fā)表于 11-10 19:51 ?0次下載
    N32WB452系列芯片<b class='flag-5'>串口</b><b class='flag-5'>IAP</b>升級應(yīng)用<b class='flag-5'>筆記</b>

    N32G4FR系列芯片串口IAP升級應(yīng)用筆記

    N32G4FR系列芯片串口IAP升級應(yīng)用筆記
    發(fā)表于 11-10 19:51 ?10次下載
    N32G4FR系列芯片<b class='flag-5'>串口</b><b class='flag-5'>IAP</b>升級應(yīng)用<b class='flag-5'>筆記</b>

    N32G457系列芯片串口IAP升級應(yīng)用筆記

    N32G457系列芯片串口IAP升級應(yīng)用筆記
    發(fā)表于 11-10 19:51 ?1次下載
    N32G457系列芯片<b class='flag-5'>串口</b><b class='flag-5'>IAP</b>升級應(yīng)用<b class='flag-5'>筆記</b>

    N32G455系列芯片串口IAP升級應(yīng)用筆記

    N32G455系列芯片串口IAP升級應(yīng)用筆記
    發(fā)表于 11-10 19:51 ?9次下載
    N32G455系列芯片<b class='flag-5'>串口</b><b class='flag-5'>IAP</b>升級應(yīng)用<b class='flag-5'>筆記</b>

    N32G452系列芯片串口IAP升級應(yīng)用筆記

    N32G452系列芯片串口IAP升級應(yīng)用筆記
    發(fā)表于 11-10 19:51 ?5次下載
    N32G452系列芯片<b class='flag-5'>串口</b><b class='flag-5'>IAP</b>升級應(yīng)用<b class='flag-5'>筆記</b>

    AN3965 STM32F40x和STM32F41x基于串口IAP

    AN3965 STM32F40x和STM32F41x基于串口IAP
    發(fā)表于 11-24 08:31 ?0次下載
    AN3965 <b class='flag-5'>STM32</b>F40x和<b class='flag-5'>STM32</b>F41x基于<b class='flag-5'>串口</b>的<b class='flag-5'>IAP</b>