STM32单片机程序dll实现

简介

一个Windows程序一般是由一个exe和多个dll文件组成,在exe程序运行时调用dll的二进制代码,这样做相对所有的程序都写到一个exe文件里边有有几个优势:
1.增加代码的复用,比如DuiLib.dll一个界面库,微信客户端使用这个dll,百度网盘客户端也使用这个这个dll。
2.单个工程的文件少,简单,减少编译时间。
3.方便团队工作的分工.
4.效率高,体积小,相比一般脚本语言,脚本的速度太慢.
5.方便程序升级.相比静态库做升级时,exe程序也要重新编译,而dll升级,其它文件不需要重新编译.
6.和引导的区别:引导只是完成从程序的下载,然后跳到从程序去运行,然后不再运行引导程序了,从程序的编程对象还是STM32库函数和寄存器,和引导程序有很大一部分驱动程序是冗余的.Dll的这种架构编程是通过调用结构体函数指针,来实现业务功能,这和单片机标准库和寄存器完全无关,只和业务结构体函数指针接口相关,dll和主Hex完全没有冗余,这样就做到电路板的结构更变(指令集不变),dll在二进制的机器代码层面都不需要做改变.

那么能否将Windows的dll这种架构也可以应用到STM32单片机开发中呢?答案是可以的。Keil编译的STM32程序一般是以Hex或者Bin结尾的。STM32使用DLL时意味着单片机存在着两个或者以上的Hex文件. 负责主动调用的主Hex相当于EXE文件,被动调用的从Hex文件相当于Dll文件。主Hex文件存放Flash的0x8000000开始的前几个扇区,RAM可以分配在前48K,从0x20000000开始,第一个Dll存放可以存放在Flash的后面的一个扇区,如从0x8020000开始的第5个扇区,RAM可以分配8K,从2000C000开始.第二个Dll存放可以存放在Flash的最后扇区,从0x8040000开始,RAM可以分配8K,从2000C000开始.主Hex文件通过读取从Hex Dll文件的第一条指令确定从Hex文件的函数入口,然后跳到函数入口完成从Hex Dll的全局变量和静态变量的初始化,以及从Hex的接口函数指针的初始化!

STM32程序结构

STM32程序包括机器指令和变量数据,机器指令存储在Flash里边,只可以读取,不能写.变量存储在RAM里边,可以进行读取操作,也可以写操作。一般变量都有初始值,这是在Keil编译代码的时候分配的。Keil编译器编译代码后会生成一个Hex文件,这个Hex文件的前面部分存放机器指令,后面部分存放变量的初始值,烧录程序时会把机器代码连同变量的初始值写到Flash。单片机上电之后一般会跳到main函数去执行代码,但是在跳到main函数之前会先执行StartUP.s启动文件的代码,在StartUp.S文件一般会有一个__Main函数,这个是个系统函数和main函数不同,它会把Flash的变量初始值拷贝到RAM变量对应的位置,从而在main之前就完成了变量的初始化,然后跳到main函数去执行用户编写的代码。

STM32 DLL文件结构及Keil工程设置

主Hex的启动文件StartUp.S会自动调用库函数__Main完成全局变量,静态变量的初始化,然后跳到用户自定的Main函数.Dll不使用自带的__Main函数完成变量的初始化,那就需要自己写代码完成变量的初始化,这个是在主Hex加载Dll前完成.Dll的启动文件相对主Hex文件要简单很多,只有9行汇编代码,用于指示初始化函数的入口,变量的开始结束位置.

主Hex完成Dll的全局 静态变量初始化

void InitDllVar()
{
	unsigned int HexVarStartAddr = *(unsigned int*)0x8020004; //变量初始值在Flash的开始地址
	unsigned int HexVarEndAddr = *(unsigned int*)0x8020008;	 //变量初始值在Flash的结束地址
	unsigned char* pRamStartAddr = (unsigned char*)0x2000C000;
	memcpy(pRamStartAddr,(unsigned char*)HexVarStartAddr,(HexVarEndAddr-HexVarEndAddr));
}

主hex加载dll流程:

	InitAppVar();				//初始化dll全局 局部变量
	InitAppParam();
	gETree = InitApp(&gLib);	//加载业务dll初始化入口
	LockAppParam();

Dll启动文件StartUp.S

   AREA    RESET, Code, READONLY,ALIGN=4
   IMPORT InitDll
   IMPORT |Load$$ER_IROM1$$Limit|
   IMPORT |Load$$RW_IRAM1$$Limit|
   DCD InitDll
   DCD |Load$$ER_IROM1$$Limit|	; 变量初始值的开始位置
   DCD |Load$$RW_IRAM1$$Limit|	; 变量初始值的结束位置
   DCD 365						; dll存在标志
   END

这段汇编代码相对与主Hex要简单很多,和单片机体系结构无关,只是申明了当前工程的程序大小 变量所使用的RAM大小,在单片机中是这样存储的:
在这里插入图片描述

由于Dll是由主的Hex文件被调用的,所以Dll存放的位置没在第一扇区0x8000000的位置,RAM也没在默认的0x20000000的开始位置,这两个位置可以根据自己的需求设置,我的设置如图所示.
Dll Flash Ram地址

Dll函数接口结构体

Dll的功能函数由一个函数结构体暴露给主Hex,使用一个结构体包含Dll的函数,主Hex使用一个结构体针指向从Hex 的结构体即可.

Dll函数接口
typedef struct _ActLib
{
		//步进
	void (*RunSM)(char sm_id,int nLen);
	void (*BrakeSM)(char sm_id);
	void (*RunABS)(char sm_id,int step);
	int (*GetAbsPos)(unsigned char sm_id);
	void (*SetAbsOrg)(unsigned char sm_id);
	void (*DisableSM)(unsigned char sm_id);
	char (*IsSMStop)(char sm_id);
	void (*StopSM)(unsigned char sm_id);
	char (*GetDir)(char sm_id);	
	void (*SetSMSpeed)(char ID,int nSpeed);
    void (*SetSoftNLimt)(char sm_id,char sensor);
	void (*SetSoftPLimt)(char sm_id,char sensor);
	
	//输入传感器,输出
	char (*IsSensorOn)(char SensorNum);
	char (*IsSensorOff)(char SensorNum);
	void (*SetSensorLogic)(char Id,char Logic);
	void (*WriteIO)(char dm_id,char status);
	
	void (*NextTo)(unsigned char NextStep);
	void (*SetActionError)(unsigned int nErrorCode);
	unsigned short (*IsActionOk)(unsigned char ActionID);	
	unsigned short (*IsActionRun)(unsigned char ActionID);
	void (*SetActionOk)(void);
	void (*SetResult)(char id,unsigned short result);
	
	unsigned char (*StartAction)(char ActionId);
	void (*AddAction)(int nActionNum,void (*OpApp)(),const char* pActionName);
	void (*AddSlaveAction)(int nActionNum,char SlaveActionNum,char BoardID,const char* pActionName);
	void (*AddActionParam)(unsigned char ID,char ParamPos,char paramtype,const char* pParamName);
	void (*SetActionParam)(char ActionId,char* ActionParam,char Len);
	
	void (*ExitAction)(char ActionId);
	unsigned char (*GetByteParam)(int pos);
	
	//事件
	void (*WaitSMStop)(unsigned char SmId,unsigned char StopStep);
	void (*WaitSensorOn)(unsigned char SensorId,unsigned char OnStep);
	void (*WaitSensorOff)(unsigned char SensorId,unsigned char OffStep);
	void (*WaitAction)(unsigned char ActionId,unsigned char OkStep,unsigned char ErrorStep);
	void (*JustWaitAction)(unsigned char ActionId,unsigned char OkStep,unsigned char ErrorStep);
	void (*WaitTimeOut)(unsigned int Time,unsigned char OutStep);
	void (*WaitRunLength)(unsigned char SmId,signed long nLen,unsigned char StopStep);
	void (*SetErrorInfo)(const char* error_text);
	void (*SetSubError)(void);
	
	void (*FindSensor)(unsigned char MotorId,signed int nLen,unsigned char SnId,unsigned char OkStep,unsigned char ErrorStep);
	void (*LeaveSensor)(unsigned char MotorId,signed int nLen,unsigned char SnId,unsigned char OkStep,unsigned char ErrorStep);
}ActLib;

主Hex的结构体指针声明:

ActLib* L;

从Hex结构体定义和初始化:

	ActLib mActLib;
	mActLib.RunSM = RunSM;
	mActLib.BrakeSM = BrakeSM;
	mActLib.SetSMSpeed = SetSMSpeed;
	mActLib.RunABS = RunABS;
	....

主Hex加载Dll以及初始化函数接口的结构体

void InitDll(ActLib* pLib)
{
	L = pLib;
}

主Hex文件使用Dll库函数:

STEP_START(MoveBelt):
	if(L->IsSensorOn(SN_PUSHEMS_DONE))
	{
		L->SetErrorInfo("分拣衔接有信封");
		break;
	}
		
	if(L->IsSensorOn(SN_BELT_ORG))
	{
		L->LeaveSensor(SM_BELT,BeltAngle(360),SN_BELT_ORG,STEP2,STEP4);
	}
	else
	{
		L->FindSensor(SM_BELT,BeltAngle(360),SN_BELT_ORG,STEP3,STEP5);
	}	
lSTEP2:
	L->WaitSensorOn(SN_BELT_ORG,STEP3);
	L->WaitSMStop(SM_BELT,STEP5);
lSTEP3:
	L->StopSM(SM_BELT);
	L->SetActionOk();
lSTEP4:
	L->LeaveSensor(SM_BELT,BeltAngle(360),SN_BELT_ORG,STEP2,STEP_ERROR);
lSTEP5:
	L->FindSensor(SM_BELT,BeltAngle(360),SN_BELT_ORG,STEP3,STEP_ERROR);
lSTEP_ERROR:
	L->SetErrorInfo("皮带电机丢步");
STEP_END

Dll更新

Dll的更新一般是主Hex接收来自串口的Dll的二进制数据,然后把DLL数据写到指定的扇区。也可以JLink把Dll烧录到特定的扇区。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值