单片机电路板一般专有的,如汽车的车灯控制电路板和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的方法.
全部0条评论
快来发表一下你的评论吧 !