技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 349|回复: 0
收起左侧

【单片机】给STM32F10x编写内存到内存DMA把ROM中的数据段复制到RAM里

[复制链接]

995

主题

2207

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16461 个
贡献
32335 次
宅之契约
0 份
在线时间
1565 小时
注册时间
2014-1-26
发表于 2018-4-3 00:08:13 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
光是把数据段里面的已初始化变量复制到RAM里是不行的,你还要让所有引用数据的代码都引用RAM里的数据,而不是ROM里的。
这就需要了解一下链接器脚本的编写了。

这有个代码链接相关的概念要介绍一下:VMA和LMA。

VMA,Virtual Memory Address,和LMA,Load Memory Address。
LMA是你的ROM被加载的地址,而VMA则是你的ROM运行的时候,你的指令里面各种指针引用数据或者代码时的地址。
通常这两个地址在PC平台上,尤其是把代码加载到RAM里运行的平台,比如各种桌面版Linux,或者安卓手机、各种派等,LMA和VMA一般都是相同的。

Windows的PE在加载的时候,为了提高运行效率和内存管理效率,代码段和数据段的位置有时候会做出一些调整,比如调整到4K对齐的页面上,再运行。而它们在磁盘上以文件的形式存储的时候,为了节省存储空间、加快读取速度,它是一个段紧挨着另一个段的。所以此时的LMA相当于PE文件里面用到的地址,而VMA相当于PE文件被加载到内存里面以后用到的地址。

我们需要实现的效果是:单片机启动的时候,我们把数据段的数据从ROM(0x08000000到0x0800FFFF,LMA)的位置复制出来到RAM里(0x20000000到0x20004FFF),并且所有使用了这些变量的代码都是使用它们对应于RAM里的地址而不是ROM里的地址。

首先从链接器指令开始写。我们要让数据段的数据存储在ROM的位置,然后让代码引用它的时候,按照RAM的地址来引用它。
  1. ENTRY(Reset_Handler)

  2. MEMORY
  3. {
  4.         FLASH (rx) :  ORIGIN = 0x08000000, LENGTH = 64K
  5.         RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
  6. }

  7. SECTIONS
  8. {
  9.         .isr_vector : ALIGN(4)
  10.         {
  11.                 KEEP(*(.isr_vector))
  12.         } > FLASH

  13.         .text : ALIGN(2)
  14.         {
  15.                 *(.text)
  16.                 *(.text*)
  17.                 *(.rodata)
  18.                 *(.rodata*)
  19.                 *(.glue_7)
  20.                 *(.glue_7t)
  21.                 *(.vfp11_veneer)
  22.                 *(.ARM.extab* .gnu.linkonce.armextab.*)
  23.                 *(.ARM.exidx* .gnu.linkonce.armexidx.*)
  24.                 PROVIDE(_etext = .);
  25.                 PROVIDE(_sidata = .);
  26.         } > FLASH
  27.        
  28.         .stack : ALIGN(4)
  29.         {
  30.                 PROVIDE(__stack_start__ = .);
  31.                 . = 0x400;
  32.                 PROVIDE(__stack_end__ = .);
  33.         } > RAM
  34.        
  35.         .data : ALIGN(4)
  36.         {
  37.                 PROVIDE(_sdata = .);
  38.                 *(.data)
  39.                 *(.data*)
  40.                 PROVIDE(_edata = .);
  41.         } > RAM AT > FLASH
  42.        
  43.         .bss : ALIGN(4)
  44.         {
  45.                 PROVIDE(__bss_start__ = .);
  46.                 *(.bss)
  47.                 *(.bss*)
  48.                 PROVIDE(__bss_end__ = .);
  49.         } > RAM
  50. }
复制代码
这里面的“AT > FLASH”是关键,它告诉链接器,数据段和代码段一起存储到ROM里,然后引用的时候从RAM引用。

此时我们的目标已经完成了一半,因为数据它仍然存储在ROM里,需要我们手动把它拷贝出来,它才会到RAM里。
接下来就是如何拷贝的问题了。

因为不想为了这个占用DMA1通道1的中断,我不使用中断。
[C] 纯文本查看 复制代码
extern char __bss_start__;
extern char __bss_end__;
extern char _sidata;
extern char _sdata;
extern char _edata;

static void _dma_copy_dataseg()
{
	if(&_sdata == &_edata) return;
	
	uint32_t regval = DMA1_Channel1->CCR & 0xFFFF8000;
	regval |=
		DMA_DIR_PeripheralSRC |
		DMA_PeripheralInc_Enable |
		DMA_MemoryInc_Enable |
		DMA_PeripheralDataSize_Word |
		DMA_MemoryDataSize_Word |
		DMA_Mode_Normal |
		DMA_Priority_Medium |
		DMA_M2M_Enable;
	DMA1_Channel1->CCR = regval;
	
	DMA1_Channel1->CNDTR = ((uint32_t)&_edata - (uint32_t)&_sdata - 1) / 4 + 1;
	DMA1_Channel1->CPAR = (uint32_t)&_sidata;
	DMA1_Channel1->CMAR = (uint32_t)&_sdata;
	
	DMA1_Channel1->CCR = regval | DMA_CCR1_EN;
}

static void _dma_copy_wait()
{
	if(&_sdata == &_edata) return;
	while(DMA1_Channel1->CNDTR);
}

static void _clear_bss()
{
	if(&__bss_start__ == &__bss_end__) return;
	uint32_t *ptr = (uint32_t*)&__bss_start__;
	while(ptr < (uint32_t*)&__bss_end__) *ptr++ = 0;
}

void Reset_Handler()
{
	do
	{
		_dma_copy_dataseg();
		_clear_bss();
		_dma_copy_wait();
		
		main();
	}while(1);
}
其中Reset_Handler()是真·入口点,而main()则是以此为模板时使用的入口点。

我们用extern关键字把链接器脚本里生成的那些符号引用进来,来定位数据段在ROM中的位置,以及在RAM中的对应位置。
_dma_copy_dataseg()函数开启了一次内存到内存DMA,_clear_bss()函数使用循环来把bss段的RAM清零。最后通过一个忙等待来等待DMA的计数器归零。

DMA计数器归零后DMA传输就结束了,因此可以不用管它。

这里有个要注意的地方。_clear_bss()这个函数是使用循环来把一个地址上的内存清零。使用gcc编译器的时候,如果你把优化开到了O3,它就会检测到你的这个行为,并且把你的循环写内存操作替换为一个memset()调用。在编译参数上,使用-fno-tree-loop-distribute-patterns可以禁用这一类优化。

参考资料:
https://www.embeddedrelated.com/showthread/comp.arch.embedded/
77071-1
.php

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/
4
/html/Using_ld_the_GNU_Linker/index.html

https://stackoverflow.com/questions/
46996893
/gcc-replaces-loops-with-memcpy-and-memset

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-9-20 08:32 , Processed in 0.097448 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表