動手寫一個STM8的輕量級bootloader
STM8憑藉其低廉的成本、超高的價效比獲得了許多公司的青睞。而在產品中由於方便、安全等需求,往往要使用到IAP下載的方式對已經拿到產品客戶進行軟體升級(如果產品在批量生產後發現你的程式有問題,而不能IAP更新,那售後維護成本就高了)。
而STM8S003等只有8K Flash的型號是不自帶Bootloader的,且ST官方的Bootloader足足會佔領4KB Flash空間,且還要考慮韌體的保密性等因素,所以編寫一個自己的輕量級的Bootloader尤為重要。
下面向大家分享一下本人在一個專案中寫的一個Bootloader,最少只需佔用0.5KB Flash空間!
【串列埠收發】
對於微控制器而言,Bootloader最重要的功能就是把從串列埠傳送過來的程式資料儲存到MCU 的Flash上(即IAP下載),並跳轉到所下載程式的起始地址並執行。所以串列埠功能必不可少。下面是我們用到的關於串列埠收發的函式(標頭檔案新增stm8s.h,直接操作暫存器以節省Flash空間),MCU型號為STM8S003。
void UART1_SendByte(u8 data) //串列埠發一個位元組
{
UART1->DR=data;
while (!(UART1->SR & 0x80));//等待傳送完成
}
void UART_Init(void) //串列埠初始化函式
{
UART1->CR2 = 0;
UART1->CR3 = 0;// b5,b4 = 00,1個停止位
UART1->BRR2=0x00;
UART1->BRR1=0x1a;
//BRR2和BRR1設定串列埠波特率,這裡設定的在2MHz默頻下是4800的波特率
UART1->CR2 = 0x2C; // b3 = 1,允許傳送
UART1->CR1 |= (0<<5);
}
void UART1_SendStr(u8* data)//傳送字串函式
{
while (*data)
UART1_SendByte(*data++);
}
u8 UART1_RcvB(void)//串列埠接收函式,以掃描(等待)方式
{
while(!(UART1->SR & (u8)UART1_FLAG_RXNE));//等待接收到資料
return ((uint8_t)UART1->DR);
}
有小夥伴就會問了,串列埠接收為何不用中斷?因為STM8沒有STM32那樣的向量地址的偏移,因此你的APP(正式的程式)和Bootloader中只能有一個使用中斷。當然也有同時都可以用中斷的方法,但是本人愚見,覺得實現基本的下載升級功能用不著中斷,新增中斷還會增加Bootloader的程式碼量不是?不過如果真的需要,CSDN論壇有一篇《集合帖:STM8之支援中斷方式的IAP技術實現》的帖子大家可以搜來看看。
【Flash的寫入】
查閱Datasheet可以知道STM8S003 FLASH的起始地址是0x8000,到0x9fff結束共8KB,每64個位元組在一個Block(塊)。而我這裡的Bootloader放在開頭,即從0x8000開始,這樣上電預設就進入bootloader。那麼APP程式放在哪裡呢,有空的地方就可以放~,比如你想把bootloader的存放空間預留1KB,那麼你的APP就從0x8400開始存放,0x8400-0x9fff共7KB空間可供你程式設計。如果你IAP功能比較精簡,沒有資料加密、校驗什麼的,0.5KB就夠,那麼甚至可以把APP起始地址提到0x8200。
下面是個寫一個塊的Flash的函式,因為寫Flash時程式不能在Flash中執行,故函式在RAM中執行。
//寫Flash函式,addr地址必須是每個Block的開頭(0x40的倍數)
IN_RAM(void FLASH_ProgBlock(uint8_t *addr, uint8_t *Buffer))
{
u8 i;
FLASH->NCR2 &= (uint8_t)(~FLASH_NCR2_NPRG);
FLASH->CR2 |= FLASH_CR2_PRG;
for (i = 0; i < 64; i++)//一個塊共寫64個位元組
{
*((PointerAttr uint8_t*) (uint16_t)addr + i) = ((uint8_t)(Buffer[i]));
}
}
在寫Flash之前別忘了對Flash解鎖
#define FLASH_RASS_KEY1 ((uint8_t)0x56)
#define FLASH_RASS_KEY2 ((uint8_t)0xAE)//巨集定義新增
FLASH->PUKR = FLASH_RASS_KEY1;//程式碼新增,解鎖Flash程式區
FLASH->PUKR = FLASH_RASS_KEY2;
最常見的IAP方式是在剛上電時等待一定時間檢測有沒有串列埠命令,有就進入下載模式,沒有就跳到APP,以下是最基本的IAP實現:
int main(void)
{
u16 j = 0;
u8 ch, high, low;
u8 buf[64]; //接收緩衝區
asm("sim"); //關閉總中斷使能
UART_Init();
UART1_SendStr("START\r\n");//傳送開機提示
while (j < 50000)//等待迴圈這麼長時間
{
if(UART1->SR & (u8)0x20) //如果串列埠收到資料
{
ch = (uint8_t)UART1->DR;
if(ch == 0xa5) break; //收到了0xa5,則進入下載模式
}
j++;
}
if (j == 50000)//迴圈是超時退出的
{
asm("JPF $8400");//跳到0x8400這個地址去執行APP。
}
//unlock flash,解鎖flash
FLASH->PUKR = FLASH_RASS_KEY1;
FLASH->PUKR = FLASH_RASS_KEY2;
UART1_SendStr("pro\r\n");
//傳送開始程式設計提示
while(1)
{
high = UART1_RcvB();
low = UART1_RcvB();
addr = (u8 *)((high << 8) | low);//先接收到本次資料16位的起始地址
if (addr == 0)//如果收到的是0,就是下載結束了
{
UART1_SendStr("OK!\r\n");//傳送下載完成提示給上位機
FLASH->IAPSR &= FLASH_MEMTYPE_PROG; //鎖住flash
asm("JPF $8400");//跳轉到0x8400執行剛下載完的APP
}
for (i = 0; i < 64; i++)
buf[i] = UART1_RcvB();//接收64位元組資料
FLASH_ProgBlock(addr, buf);//將收到的資料寫到相應地址的Flash
}
}
上述程式使用IAR編譯,僅用不到0.5KBFlash就實現了最基本的IAP功能,當然,下載程式需要使用協議與之匹配的上位機。
【中斷向量重對映】
使用上述bootloader程式下載app程式後你會發現中斷無法使用,因為STM8的預設中斷向量地址是在0x8000-0x8080,stm8編譯出的二進位制檔案會把向量表放在前0x80個位元組。即如果在app中使用了中斷,那麼程式就會跳回bootloader程式中去了,解決這個問題的方是進行中斷向量重對映。
在bootloader程式main.c檔案的函式之外新增以下程式碼:
__root const long reintvec[]@".intvec"=
{
0x82008080,0x82008404,0x82008408,0x8200840c,
0x82008410,0x82008414,0x82008418,0x8200841c,
0x82008420,0x82008424,0x82008428,0x8200842c,
0x82008430,0x82008434,0x82008438,0x8200843c,
0x82008440,0x82008444,0x82008448,0x8200844c,
0x82008450,0x82008454,0x82008458,0x8200845c,
0x82008460,0x82008464,0x82008468,0x8200846c,
0x82008470,0x82008474,0x82008478,0x8200847c,
};//當應用程式地址不是0x8400時則要相應改掉除第一個0x82008080以外的數值
便完成了中斷向量的重對映,這裡對映到了0x8400-0x8480,即APP程式的中斷向量表存放區。
但是這時候你的IAR編譯器會報錯:
中斷向量空間不夠?當然,因為加入了重定向陣列。
接下來進入IAR設定下,Linker的Config下,可以看到使用了inkstm8s003f3.icf 這個配置檔案:
我們用文字編輯器開啟:
這不就是報錯的INTVEC size嗎,把它從0x80改成0x100,並儲存,再回IAR編譯就完美通過了!
【APP程式的設定】
現在有bootloader了,APP存放的地址改變了,那麼APP中一些關於地址的量也需要跟著改變,換句話說就是編譯器編譯出的地址也要變。所以你需要將上面改INTVEC size大小的那張圖改成下面這樣再對APP進行編譯:
嗯,所有的0x8000都改成了0x8400(APP程式的起始地址),中斷向量表佔用空間為0x80。在這樣設定、儲存編譯後,APP通過bootloader下載進去就可以用了。
【資料的校驗】
前面我們的bootloader程式是收到位元組便馬上寫入Flash,寫完後直接執行,可是萬一出現串列埠多收/漏收/錯收或者是Flash寫入失敗的情況怎麼辦?那就…
void get_flash_verify(u8 *add)//傳送校驗碼的函式
{
u16 i;
u8 ch;
u8 verify = 0;
for (i = 0; i < 64; i++)
{
ch = *((PointerAttr uint8_t*) (uint16_t)add + i);//讀取flash並累加
verify += ch;
}
UART1_SendByte(verify);
}
此函式讀取了add地址開始的64位元組Flash的值,並將他們累加,相加完的和取低8位得到一個校驗碼,並將該碼通過串列埠傳送出去。此函式在微控制器每次寫完一個塊的Flash後呼叫,上位機將這個碼與自己的資料算出的值進行比對便實現了校驗,如果校驗不正確,上位機便可重新傳送命令寫該快。
未完待續
相關文章
- 仿Laravel寫了一個輕量級的框架Laravel框架
- [轉載]用 Go 寫一個輕量級的 ldap 測試工具GoLDA
- Pekwm:一個輕量級的 Linux 桌面Linux
- CherryPy :一個輕量級的 Python Web 框架PythonWeb框架
- Soa: 一個輕量級的微服務庫微服務
- iOS 一個輕量級的元件化思路iOS元件化
- LogFX:JavaFX編寫一個漂亮、輕量級的日誌檢視器
- PHP實現一個輕量級容器PHP
- 一個輕量級react埋點元件React元件
- ThinkGo:一個輕量級的 Go 語言 MVC 框架GoMVC框架
- GKCycleScrollView - 一個輕量級的自定義輪播圖元件View元件
- GJLightBlueTooth——一個輕量級的iOS藍芽開發庫iOS藍芽
- 基於gogs和drone的一個輕量級的開發部署自動化流程Go
- JXPopupView:一個輕量級的自定義檢視彈出框架View框架
- 一個輕量級,0配置orm框架 sharkchili-feifeiORM框架
- 一個輕量級的基於RateLimiter的分散式限流實現MIT分散式
- 手寫一個 React 動畫元件React動畫元件
- Spring Boot 動手寫一個 StartSpring Boot
- 推薦一個 ASP.NET Core 的輕量級外掛框架ASP.NET框架
- 一個輕量級的iOS皮膚切換方案(內附Demo)iOS
- 適合移動端的輕量級網路
- sal原始碼解析-輕量級的滾動動畫庫原始碼動畫
- 自己動手寫一個持久層框架框架
- Bootloader Linux啟動bootLinux
- 一個工業級、跨平臺、輕量級的 tcp 網路服務框架:geventTCP框架
- 一個用於建立react+Figma外掛的輕量級的UI庫ReactUI
- 微信小程式--手寫一個地區選擇器(多級聯動)微信小程式
- docker快速建立輕量級的可移植的容器(一)Docker
- 「造個輪子」——cicada(輕量級 WEB 框架)Web框架
- 手寫一個自己的PromisePromise
- azeroth-event輕量級事件驅動框架事件框架
- Spring的輕量級實現Spring
- 一個更適合Java初學者的輕量級開發工具:BlueJJava
- 一個現代化輕量級的跨平臺Redis桌面客戶端Redis客戶端
- 一個輕量級的分散式定時任務排程平臺-Cloudtask分散式Cloud
- 開源一個基於dotnet standard的輕量級的ORM框架-Light.DataORM框架
- 輕量級超級 css 工具CSS
- 手寫一個PromisePromise
- 不想寫sql?試試這款輕量級JAVA ORM框架!SQLJavaORM框架