让STM32F运行dll

让STM32F运行dll

因项目的一些实际问题,无奈之下开发了这种方式的程序。废话不多说,下面上干货。
背景:
STM32F429,外存16M,外扩SD卡,FatFS,USB(可选)
目标:STM32F429 运行SD卡中的DLL程序,可以直接通过USB或SD卡拷贝更新升级程序。
原理:在STM32F429中运行类似uboot的程序,找到SD卡中的elf文件,利用elf的特性,加载程序到外存,跳转到外存中运行。
需要的基础知识点:
1、FatFS
2、分散加载
3、ELF文件格式
4、C,ARM汇编基础,有最好,没有也不影响
5、RAM、ROM、外扩RAM硬件基础常识
6、其它一些帮助理解的知识(可有可无),有这些基础会帮助快速理解程序如何在单片机系统上运行:嵌入式操作系统、编译原理、UBoot、Bootloader、GCC、嵌入式OOP开发等等。

步骤:
1、先写个测试用的例程
2、再写个加载DLL的类Bootloader

注意:ARM的DLL与Window的DLL不是一回事,注意区分。 因CPU架构不同,指令格式也不同,Window下的DLL在ARM下是不能直接运行的!!!这里借用elf格式的文件(Keil会自动生成axf文件)实现DLL,可以使用GCC处理得到,也可以直接由Keil生成。
另外需要说明的是,通过外部RAM运行程序是一种无奈的选择,这会导致程序运行变慢!加载到内存运行也不是最优解(同样会变慢,只是比外存快一些,不过,你要是舍得花银子的话,当我没说)。原因是ARM架构本身可以同时处理指令和数据,都加载到内存或外存时,只能分两步来处理,这必然要比在指令在Flash中,数据在RAM中要慢。当然,有坏处,自然也有好处,而且好处也不少,至于是什么好处可以自行脑补。。。

测试例程如下:

//int argNum __attribute__((at(0X20000000)));
//int argValue __attribute__((at(0x20000004)));
int main(int argc,char **argv)
{
	Stm32_Clock_Init(336,25,2,7);//设置时钟,168M,OTG= 48M

	int * pargv;
	pargv = (int *)0x20000004;
	NVIC_SetVectorTable(*pargv,0);	
	
	USART1_Init(115200);
	
	//显示开机动画
	OLED.OLED_Init();			
	OLED.ShowAnimation();
	
	cout<<"This is a dll test!\r\n"<<endl;
	
	LED_Init();
	
	while(1)
	{
		LED_GREEN = !LED_GREEN;
		cout<<"Hello,I'm app!"<<endl;
		Delayms(500);
	}
	
	return 0;
}

上面代码很简单,意思就是让LED灯500ms翻一下(闪烁),顺便打印一下字符,说明“App还活着"。
关键代码:(分散加载)

LR_IROM1 0xC0010000 0x00100000  {    ; 外存地址
  ER_IROM1 0xC0010000 0x00100000  {  ; 加载地址
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00030000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

我这里不处理内部摆放问题,仅为演示运行原理。好了,到这里,测试例程就OK了。
经测试,程序可以使用C写,也可以使用C++写,没有影响。
2、类Bootloader的加载程序

int argNum __attribute__((at(0X20000000)));
int	argValue __attribute__((at(0x20000004)));
void LoaderDll(FIL *fp);		//加载DLL
unsigned int exMem_Addr_Base;		//加载的基地址

先模仿UBoot的做法,定义个约定好的地方,回头看下前面的例程,argValue这个参数可以直接用于重置复位地址。argNum:用于传递参数个数,argValue可以用于传递应用程序需要的参数或参数组

int main(void)
{
	FATFS *fs;//逻辑磁盘工作区.	 
	FIL *fp;		//操作的文件指针
	
	Stm32_Clock_Init(336,25,2,7);//设置时钟,168M,OTG= 48M
	
	USART1_Init(115200);
	printf("Boot is starting... \r\n");

	Beep_Init();
	LED_Init();				//初始化LED时钟  
	LED_GREEN = LED_ON;
	
	SDRAM_Init();
	my_mem_init(SRAMEX);
	MPU_Config();
	
	//初始化SD卡 & SDIO接口
	if(SD_Init())		Beep(200);

	//FatFS
	fs = (FATFS*)malloc(sizeof(FATFS));
	fres = f_mount(fs,"1:",1);		//挂载SD卡 	
	
	if(fres != FR_OK)		Beep(200);

	char *pPath ="1:/Test/ElfTest.axf";	
	
	fp = (FIL *)malloc(sizeof(FIL));

	fres = f_open(fp, pPath,  FA_READ);
	if(fres != FR_OK)
	{
		Beep(200);
		return 0;
	}
	
	//加载程序
	LoaderDll(fp);
	f_close(fp);
	
	typedef int (*Fun)();
	Fun fun;
	
	//重定位入口点
	argValue = exMem_Addr_Base;
//	fun = (Fun)(mem_Addr_Base + 5);	//OK 加载到内存,运行速度比较慢
	fun = (Fun)(exMem_Addr_Base + 5);	//OK 加载到外存,运行速度非常慢
	//执行程序
	fun();
	
	return 0;
}

关键代码:

	//加载程序到内存
	for(int i=0;i<elf_ehdr->e_phnum;i++)
	{
//		printf("//===== 程序头部 %d 信息 ======//\n",i);		
		f_lseek(fp,elf_ehdr->e_phoff + i * elf_ehdr->e_phentsize);
		index = 0;
		fres = f_read(fp,elf_phdr,sizeof(Elf32_Phdr),&index);
		if(index == 0)		Beep(200);
		if((elf_phdr->p_type == PT_LOAD)&&(elf_phdr->p_flags & PF_X))
		{
			//定位
			f_lseek(fp,elf_phdr->p_offset);
			//Copy segment
			index = 0;
			fres = f_read(fp,(void *)elf_phdr->p_paddr,elf_phdr->p_filesz,&index);	//因STM32F4无MMU,故直接装载到实际物理地址
			if(index == 0)		Beep(200);
			mem_Addr_Base = elf_phdr->p_paddr;
			
			//填充bss段
			if(elf_phdr->p_filesz < elf_phdr->p_memsz)		//填充bss段
				mymemset((elf_phdr->p_paddr + elf_phdr->p_filesz),0,(elf_phdr->p_memsz - elf_phdr->p_filesz));
		
//			//更新到下一段
//			mem_addr += elf_phdr->p_align;
		}
		//内存初始化
		//...
	}	

OK,是不是非常简单?大体意思就是,初始化应用程序的运行环境,我这里简单加载了FatFS,SD卡,然后从SD卡中读入程序文件,最后跳转到App去运行。
到此处就可以测试收工了。运行结果如下图所示:
运行结果

 

(1761条消息) 让STM32F运行dll_单片机使用dll_oMurphyo的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值