一、串口IAP簡(jiǎn)介
1.1 什么是IAP
IAP,英文全稱In Application Programming,在應(yīng)用中編程。很好理解,就是在程序運(yùn)行過程中我們進(jìn)行程序的燒寫,或者叫升級(jí)。
1.2 STM32下載程序
我們都知道,STM32可以利用串口下載程序,這是因?yàn)镾T公司在產(chǎn)線上就在產(chǎn)品中內(nèi)嵌了自舉程序。所謂的自舉程序,實(shí)際就是支持我們通過串口下載程序的代碼。自舉程序被存放在系統(tǒng)存儲(chǔ)區(qū),因此如果我們需要通過串口下載程序,需要將Boot0接高電平,Boot1接低電平,讓程序從系統(tǒng)存儲(chǔ)器開始運(yùn)行,運(yùn)行自舉程序。下載完成后我們?cè)賹oot0接地,讓程序從主閃存存儲(chǔ)器開始運(yùn)行。自舉程序是我們用戶無法修改的。
二、串口IAP有什么作用
上面我們介紹了什么是IAP,那么這個(gè)IAP到底有什么作用呢?
首先介紹兩個(gè)詞——Bootloader和 Application 。Bootloader實(shí)際就是一段引導(dǎo)程序,單片機(jī)上電后先執(zhí)行Bootloader程序,然后再執(zhí)行用戶編寫的應(yīng)用程序Application。介紹完這兩個(gè)詞,我們來介紹一下IAP有什么作用。比如我們生產(chǎn)了A,B兩款產(chǎn)品。A產(chǎn)品是某個(gè)精密器件的一部分,B產(chǎn)品是一款物聯(lián)網(wǎng)產(chǎn)品。我們的產(chǎn)品銷售范圍很廣,遠(yuǎn)銷海外。
某天A產(chǎn)品的某個(gè)客戶反映了一個(gè)Bug,我們編寫好了程序,需要進(jìn)行程序更新,或者叫升級(jí)。利用IAP,我們可以在程序運(yùn)行時(shí),通過預(yù)留的通信接口直接燒寫程序。而不需要再把整個(gè)設(shè)備拆開,像我們調(diào)試時(shí)那樣下載程序。甚至我們可以直接給客戶郵寄一個(gè)小設(shè)備,客戶直接進(jìn)行傻瓜式升級(jí)。
又過了一段時(shí)間,B產(chǎn)品的程序出現(xiàn)了一個(gè)Bug,風(fēng)險(xiǎn)等級(jí)比較低,但是依舊需要全體升級(jí)程序。我們總不能挨個(gè)產(chǎn)品派人去升級(jí),成本極大。這時(shí)候又輪到我們的IAP出場(chǎng)了。它可以在所有設(shè)備在線運(yùn)行的情況下,直接通過網(wǎng)絡(luò)下發(fā)升級(jí)程序,實(shí)現(xiàn)在線升級(jí),節(jié)約了大量的人力成本。
通過上面這兩個(gè)例子,大家應(yīng)該能夠基本了解IAP的用處,使用IAP讓我們不需要再使用調(diào)試器進(jìn)行下載,甚至實(shí)現(xiàn)設(shè)備的在線升級(jí)。
三、啟動(dòng)流程
在介紹如何實(shí)現(xiàn)IAP之前,我們先來簡(jiǎn)單了解以下STM32的啟動(dòng)流程。
3.1 正常啟動(dòng)流程
這里的正常啟動(dòng)流程指的是,沒有添加IAP的流程。
正常啟動(dòng)流程
程序啟動(dòng)時(shí)首先開辟??臻g,配置棧頂指針。然后配置堆空間。配置完成后,建立中斷向量表,在中斷向量表中找到復(fù)位中斷,開始執(zhí)行復(fù)位中斷服務(wù)函數(shù),然后跳轉(zhuǎn)到main函數(shù)中,執(zhí)行用戶代碼。當(dāng)用戶代碼中有中斷請(qǐng)求時(shí),會(huì)回到中斷向量表,根據(jù)中斷源執(zhí)行相應(yīng)的中斷服務(wù)函數(shù)。
3.2 加入IAP后的啟動(dòng)流程
下面是加入IAP之后的啟動(dòng)流程。
加入IAP啟動(dòng)流程
可以看到,與上面不同的是,加入IAP后,執(zhí)行完復(fù)位中斷服務(wù)函數(shù)后直接進(jìn)入IAP的main函數(shù)。在執(zhí)行完IAP之后,跳轉(zhuǎn)至新寫入程序的復(fù)位向量表,取出新程序的復(fù)位中斷向量的地址,并跳轉(zhuǎn)執(zhí)行新程序的復(fù)位中斷服務(wù)程序,隨后跳轉(zhuǎn)至新程序的main 函數(shù)。
由上面的兩個(gè)啟動(dòng)過程我們可以看出
? 新程序必須在 IAP 程序之后的某個(gè)偏移量為 x 的地址開始。
? 必須將新程序的中斷向量表相應(yīng)的移動(dòng),移動(dòng)的偏移量為 x。
四、必備知識(shí)
44.1 修改程序運(yùn)行起始地址
點(diǎn)擊魔術(shù)棒,選擇“Target”,修改運(yùn)行起始地址和代碼大小。
修改App運(yùn)行起始地址
4.2 設(shè)置中斷向量表偏移
VTOR 寄存器存放的是中斷向量表的起始地址。如果要設(shè)置中斷向量表偏移,只需要在main函數(shù)最開始添加如下語句即可
SCB- >VTOR = FLASH BASE | 偏移量:
4.3 生成.bin文件
點(diǎn)擊魔術(shù)棒,選擇“User”,按照如下配置,輸入下面的內(nèi)容
fromelf --bin -o "$L@L.bin" "#L"
生成.bin文件配置
點(diǎn)擊編譯,不報(bào)錯(cuò)就可以,去設(shè)置的輸出文件夾中就可以找到對(duì)應(yīng)的.bin文件。
編譯提示
五、串口IAP實(shí)現(xiàn)
本次的目標(biāo)是實(shí)現(xiàn)一個(gè)串口IAP,也就是編寫B(tài)ootloader,在程序運(yùn)行過程中實(shí)現(xiàn)程序的下載。Bootloader程序應(yīng)該可以通過串口接收上位機(jī)發(fā)來的.bin文件(App程序),檢查后將.bin文件寫入到Flash特定位置,然后跳轉(zhuǎn)到App程序運(yùn)行。
5.1 串口中斷服務(wù)函數(shù)
本次的串口中斷服務(wù)函數(shù)與之前不同,這里單獨(dú)貼出來。需要定義一個(gè)接收數(shù)組,接收數(shù)組的起始地址限制為0X20001000。
接收數(shù)組最多可以接收55K字節(jié),可以根據(jù)需要調(diào)整。但是需要注意的是,數(shù)組的大小需要比App程序要大,而且不能超過芯片的SRAM空間大小。
/*
*==============================================================================
*函數(shù)名稱:USART1_IRQHandler
*函數(shù)功能:USART1中斷服務(wù)函數(shù)
*輸入參數(shù):無
*返回值:無
*備 注:無
*==============================================================================
*/
u32 gReceCount = 0; // 接收計(jì)數(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) //接收到一個(gè)字節(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ù)功能:從指定地址開始寫入指定長(zhǎng)度的數(shù)據(jù)
*輸入?yún)?shù):WriteAddr:寫入起始地址;pBuffer:數(shù)據(jù)指針;
NumToRead:寫入(半字)數(shù)
*返回值:無
*備 注:對(duì)內(nèi)部Flash的操作是以半字為單位,所以讀寫地址必須是2的倍數(shù)
*==============================================================================
*/
// 根據(jù)中文參考手冊(cè),大容量產(chǎn)品的每一頁(yè)是2K字節(jié)
#if STM32_FLASH_SIZE < 256
#define STM32_SECTOR_SIZE 1024 // 字節(jié)
#else
#define STM32_SECTOR_SIZE 2048
#endif
// 一個(gè)扇區(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位字計(jì)算)
u16 secremain; // 扇區(qū)內(nèi)剩余地址(16位計(jì)算)
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; // 實(shí)際偏移地址
secpos = offaddr / STM32_SECTOR_SIZE; // 扇區(qū)地址
secoff = (offaddr % STM32_SECTOR_SIZE) / 2; // 在扇區(qū)內(nèi)的偏移(2個(gè)字節(jié)為基本單位)
secremain = STM32_SECTOR_SIZE / 2 - secoff; // 扇區(qū)剩余空間大小
if (NumToWrite <= secremain)
{
secremain = NumToWrite; // 不大于該扇區(qū)范圍
}
while (1)
{
// 讀出整個(gè)扇區(qū)的內(nèi)容
Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
// 校驗(yàn)數(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); // 擦除這個(gè)扇區(qū)
// 復(fù)制
for (i = 0;i < secremain;i ++)
{
STM32_FLASH_BUF[i + secoff] = pBuffer[i];
}
// 寫入整個(gè)扇區(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; // 下一個(gè)扇區(qū)還是寫不完
}
else
{
secremain = NumToWrite; // 下一個(gè)扇區(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程序大小
*返回值:無
*備 注:無
*==============================================================================
*/
// 對(duì)Flash操作的最小單位是16位
u16 iapbuf[1024]; // 要寫入Flash的內(nèi)容
void iap_write_appbin (u32 appxaddr,u8 *appbuf,u32 appsize)
{
u16 t; // 臨時(shí)循環(huán)變量
u16 i = 0; // 自增索引
u16 temp; // 臨時(shí)計(jì)算變量
u32 fwaddr = appxaddr; // 當(dāng)前寫入的地址
u8 *dfu = appbuf; // 指向App代碼數(shù)組首地址的指針
// for循環(huán),2K為單位進(jìn)行寫入
for(t = 0;t < appsize;t += 2)
{
temp = (u16)dfu[1] < < 8;
temp += (u16)dfu[0];
dfu += 2; // 偏移2個(gè)字節(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é)寫進(jìn)去
}
}
/*
*==============================================================================
*函數(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ū)第二個(gè)字為程序開始地址(復(fù)位地址)
MSR_MSP(*(vu32*)appxaddr); // 初始化APP堆棧指針(用戶代碼區(qū)的第一個(gè)字用于存放棧頂?shù)刂?
jump2app(); // 跳轉(zhuǎn)到APP
}
}
5.4 main函數(shù)
main函數(shù)設(shè)計(jì)如下
int main(void)
{
u32 curRecCunt = 0; // 當(dāng)前接收數(shù)量
// 設(shè)置NVIC中斷分組2:2位搶占優(yōu)先級(jí),2位響應(yīng)優(yōu)先級(jí)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(); // 延時(shí)初始化
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; // 清零接收計(jì)數(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ù)量
// 延時(shí)一定要有,給串口中斷留出時(shí)間
// 如果沒有會(huì)導(dǎo)致接收不完全
delay_ms(10);
}
}
}
六、注意事項(xiàng)
需要注意的是,上面的程序中App程序是從0x8010000開始,需要配置好程序起始地址和中斷向量表偏移
。
-
寄存器
+關(guān)注
關(guān)注
31文章
5343瀏覽量
120424 -
STM32
+關(guān)注
關(guān)注
2270文章
10901瀏覽量
356195 -
上位機(jī)
+關(guān)注
關(guān)注
27文章
942瀏覽量
54827 -
中斷向量
+關(guān)注
關(guān)注
0文章
14瀏覽量
8945 -
flash內(nèi)存
+關(guān)注
關(guān)注
0文章
5瀏覽量
2129
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論