單片機電路板一般專有的,如汽車的車燈控制電路板和EPS控制的電路板是完全不同的。專有的電路板,軟件就比較難通用,軟件編程比較強調的是單片機系統。用單片機來做工控板則有很強的通用性,如一個工控電路板既可以用來控制口罩設備,也可以用來控制電梯運行,還可以控制自助發銀行的設備等等.如此說來,工控電路板不僅僅是電路板,而是一個工控平臺.平臺的軟件是高度抽象的,如個人電腦,手機, 瀏覽器都是平臺,開發者都是基于平臺接口編程的,不用知道底層CPU寄存器,時鐘等.工控平臺也一樣,也是希望業務程序代碼中不要有單片機的寄存器設置,固件庫,時鐘相關的代碼.
要達到這個目的,就要把業務代碼和驅動系統代碼實現工程級分離;或者業務代碼使用腳本實現(PLC的梯形圖就是一種圖形腳本),下面說明一下前面的一種方式.
業務代碼和驅動系統代碼實現工程級分離
工程分離有靜態分離和動態分離兩種.靜態分離是把業務程序當做一個靜態庫嵌入到驅動系統工程. 這個有一個缺點,業務程序修改,整個驅動軟件系統都要重新編譯,驅動系統和業務程序是一個Hex文件,業務程序也無法單獨升級。動態分離是把業務程序當做一個單獨的Hex放到單片機內部flash某一個扇區,業務程序代碼修改時,驅動系統不用重新編譯,工程代碼少,簡潔,代碼復用最大化.只需編譯業務程序代碼的工程,這時驅動系統不止是完成電機驅動,傳感器狀態獲取,還要完成業務程序的在線升級,加載業務程序運行.
內存及Flash區域劃分
由于驅動和業務是兩個不同的Hex,那么兩者的RAM就不能有重疊的空間.于是可以把整個單片機RAM前48K用作驅動系統軟件,后面的12K RAM留給App.
keil驅動工程設置:
keil App設置:
Flash區域也應該劃分為兩個區域,前64K用作Boot驅動,從0x8000000開始。中間的128K扇區用作App程序存儲,其它扇區用作設備參數,電機參數等.
App加載
App加載分三步:檢測App是否存在以及完整性檢測,App的全局變量初始化,跳轉到App區域運行.
App完整性檢測可以通過App的啟動文件的DCD偽指令實現,start.s:
AREA RESET, Code, READONLY,ALIGN=4
IMPORT InitApp
IMPORT |Load$$ER_IROM1$$Limit|
IMPORT |Load$$RW_IRAM1$$Limit|
DCD 365 ;標志單片機中已經存在App
DCD 111 ;CRC校驗數值
DCD |Load$$ER_IROM1$$Limit| ;RO Code大小,用于確定程序大小,用于確定Hex二進制代碼的RAM初始化位置
DCD |Load$$RW_IRAM1$$Limit| ;RW結束位置,用于確定Hex二進制代碼的RAM結束位置
DCD InitApp
END
DCD 365是單片機的第一條指令,位于Flash區域的0x8020000處,這樣DCD是在0x8020000處放置數值365。驅動Boot執行讀取0x8020000處數值是否為365。
unsigned int IsAppExist = *(unsigned int*)0x8020000;
if(IsAppExist!=365)//App沒有燒錄
{
return ;
}
DCD 111是單片機的第二條指令,位于0x8020004,預留存放App的CRC數值,App在升級前,上位機先計算好Hex文件的CRC數值,下發到單片機,單片機收到CRC,寫到0x8020004處.
**DCD |Load$$
ER_IROM1
Limit|** 單片機第三條指令,位置0x8020008,Load
ER_IROM1
Limit是Keil的內置宏,代表App編譯后的Code大小.
**DCD |Load
RW_IRAM1
Limit|** 是單片機的第四條指令,位置0x802000c,Load
RW_IRAM1
Limit也是Keil內置宏,表示App編譯后的RW大小,也就是全局變量的大小.
uint16_t Crc_16(uint8_t *buf, int len) {
uint16_t crc = 0;
uint16_t i;
while (len--) {
crc ^= *buf++;
for (i = 0; i < 8; i++) {
crc = (crc >> 1) ^ ((crc & 1) ? 0xA001 : 0);
}
}
return crc;
}
//App CRC完整性檢測
uint8_t Check_AppCRC()
{
uint16_t SrcCRC= *(unsigned int*)0x8020004;
int CodeSize = *(unsigned int*)0x8020008;
int RWSize = *(unsigned int*)0x802000c;
uint16_t DstCRC = Crc_16((uint8_t*)0x8020000,CodeSize+RWSize);
if(SrcCRC==DstCRC)
{
return 1;
}
return 0;
}
Check_AppCrc校驗App的CRC是否正確,正確才能跳轉到App運行.
App全局變量初始化
全局變量的初始化一般是啟動文件里邊調用keil內置的__main函數實現的,初始化完成以后就跳到main函數去了.為了使App的啟動文件簡單,可以Boot跳轉App前,Boot實現App的全局變量的初始化,全局變量初始化即把全局變量的初始數值拷貝到RAM區域,初始值編譯時屬于RW數據,存在hex文件里邊,對于單片機就是存在于Falsh區域.
要實現初始化就需要知道RW區域的起始地址,區域大小.Load
ER_IROM1
Limit即是RW區域的偏移地址,Load
RW_IRAM1
Limit是區域大小,再一次使用這兩個宏
/***************************************************************************************************************
* 函數名稱: InitAppVar
* 函數描述: 初始化業務程序的全局變量 靜態變量
* 其它說明 :
****************************************************************************************************************/
void InitAppVar()
{
unsigned int HexVarStartAddr = *(unsigned int*)0x8020000;//Load$$ER_IROM1$$Limit
unsigned int HexVarEndAddr = *(unsigned int*)0x8020004;//Load$$RW_IRAM1$$Limit
unsigned char* pRamStartAddr = (unsigned char*)0x2000C000;
memset(pRamStartAddr,0,10*1024); //默認App最多10K數據清零
memcpy(pRamStartAddr,(unsigned char*)HexVarStartAddr,(HexVarEndAddr-HexVarStartAddr));
}
Boot與App交互
不同于一般的Boot,App的代碼和Boot代碼有很大程度的復用的,這個復用就是Boot把自己的一些功能函數封裝到一個函數結構體,也就是一個接口文件,Boot跳轉到App時把這個函數結構體作為跳轉函數的參數傳遞給App,App就可以通過這個結構體調用系統功能.
Boot區加載代碼:
typedef struct _ActLib
{
//步進
void (*RunSM)(char sm_id,int nLen);
//輸入傳感器,輸出
char (*IsSensorOn)(char SensorNum);
void (*SetPrjName)(char* name);
}ActLib;
ActLib gLib;
PtrInitApp InitApp;
InitApp = (PtrInitApp)0x8020008;
InitApp(&gLib); //加載業務app初始化入口
App入口代碼
ActLib* l;
void InitApp(ActLib* pLib)
{
l = pLib;
l->SetPrjName("口罩設備項目");
if(l->IsSensorOn(X101))
{
l->RunSM(SM101,200);
}
}
上面簡單介紹了工程分離及引導App的方法.
-
單片機
+關注
關注
6040文章
44606瀏覽量
637252 -
電路板
+關注
關注
140文章
4988瀏覽量
98485 -
EPS
+關注
關注
6文章
195瀏覽量
31327 -
軟件編程
+關注
關注
1文章
41瀏覽量
11327
發布評論請先 登錄
相關推薦
評論