STM32第2部分:初始化

编程正在将大而不可能的东西分解为小而非常真实的东西。


大家好,对于初学者,我要感谢主持人错过了我的第一篇(令人作呕的)帖子,并向妈妈问好!我还要感谢所有指出我的错误并帮助纠正错误的读者和人们。我立即提出保留意见,说我从六年级开始就没有写过,总的来说不要生气。

STM32第1部分:基础知识

让我们开始吧。在上一篇文章中,我快速浏览了第一点。这是我们的startup.c文件,它负责2个向量(堆栈,Reset_Handler)和有关链接描述文件的一些内容。今天,我们将补充初始化代码,分析链接器的备件,并找出所有工作原理。


extern void *_estack;

void Reset_Handler();

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}


如果任何人都不清楚此代码,那么您可以在此处阅读(本文不是源文件,而是试图进行解释)。

现在,让我们找出这里缺少的内容以及如何添加它。显然,微米具有其他向量。例如,Hard_Fault向量是在使用μ进行不当操作时调用的向量,例如,如果他尝试执行处理器不理解的指令,则他将跳转到Hard_Fault向量的地址。这样的向量很多,在我们的程序中我们将使用所有东西并不是事实。同样,中断向量在同一表中。

(对于仍然不知道向量是什么的人,这是指向地址的指针,又称“指针”)。

我们补充我们的代码,然后我将解释我做了什么。

extern void *_estack;

void Reset_Handler();
void Default_Handler();

void NMI_Handler()   __attribute__((weak, alias("Default_Handler")));
void HardFault_Handler()   __attribute__((weak, alias("Default_Handler")));
//     

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    &_estack,
    &Reset_Handler
    &NMI_Handler,
    &HardFault_Handler
};

void __attribute__((naked, noreturn)) Reset_Handler()
{
    while(1);
}

void __attribute__((naked, noreturn)) Default_Handler(){
    while(1);
}


在这里,我添加了一个新函数Default_Handler();绝对可以处理所有中断。但在帮助下,__attribute__((weak, alias("Default_Handler")));我注意到如果声明了具有相同名称的函数,则它将作为此中断的处理程序。换句话说,现在它只是Default_Handler的昵称。因此,我们可以从参考手册中添加列表中的所有向量。这样做是为了如果您突然决定使用中断,但忘记创建函数处理程序,则会调用通常的Default_Handler。

我认为,在这一点上,使用矢量您可以完成并转到扇区的初始化和链接器。首先,让我们通过将数据和bss扇区的初始化以及几个外部变量添加到Reset_Handler主体来再次更新startup.c。

extern void *_sidata, *_sdata, *_edata, *_sbss, *_ebss;

void __attribute__((naked, noreturn)) Reset_Handler()
{


	void **pSource, **pDest;
	for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)
		*pDest = *pSource;

	for (pDest = &_sbss; pDest != &_ebss; pDest++)
		*pDest = 0;

	while(1);
}


因此,新变量再次在我们的链接描述文件中声明。回顾以前的脚本并将其与新的进行比较。

MEMORY{
	ROM(rx) : ORIGIN = 0x08000000, LENGTH = 1M
	SRAM (rwx):     ORIGIN = 0x20010000, LENGTH = 240K
}

_estack = LENGTH(SRAM) + ORIGIN(SRAM);

SECTIONS{
	.isr_vector : {
	KEEP(*(.isr_vector))
	} >ROM
	
	.text : {
        . = ALIGN(4);
        *(.text)
    } >ROM
	
	_sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM
	
	.bss : {
		. = ALIGN(4);
		_sbss = .;
		*(.bss)
		. = ALIGN(4);
		_ebss = .;
	} >SRAM
}


添加了两个新部分,分别是.data和.bss。我们为什么需要它们?那么.data将离开全局变量,而static将进入bss。这两个部分都在RAM中,如果bss部分的内容很简单(这些都为零),则data部分会有一些困难。首先,数据是在编译时已知的已初始化数据。因此,最初,数据段将位于闪存中的某个位置。其次,我们需要以某种方式向链接器指示该脚本在闪存中,但是在程序执行期间它将被传输到RAM。让我们看一下描述数据部分的脚本。


        _sidata = LOADADDR(.data);
        .data : {
		. = ALIGN(4);
		_sdata = .;
		*(.data)
		. = ALIGN(4);
		_edata = .;
	} >SRAM AT>ROM


它以_sidata的声明开始,并且此刻的赋值含义尚不明确。接下来,开始对该部分本身的描述。我建议您在此处阅读有关ALIGN函数的信息,但总而言之,我们为我们的点(当前地址)分配了一个值,当处理器尝试从中加载数据或指令时,该值将更容易被我们的处理器接受。

还包括_sdata,_edata和本节末尾。但是在本节的最后,我们只需要我们。SRAM AT>ROM。该行告诉我们的链接器该段位于SRAM中,但此段已加载到ROM或闪存中。现在回到我们的_sidata变量和LOADADDR函数。此函数将返回一个等于加载部分地址的值。也就是说,我们表明该部分已加载到闪存中,但我们在RAM中使用了该部分,为了传输此部分,我们需要知道其在闪存中的地址,这就是LOADADDR函数返回的地址。同时,变量_sdata和_edata指示该节在RAM中的位置,从而使我们有机会将数据节加载到正确的内存中。让我提醒您,这就是我们上面补充的初始化代码所处理的。

为什么我们需要此链接器以及应用程序各部分分布中的此类困难?数据部分只是为什么我们需要能够在内存中放置和移动部分的一个小例子。下面是我使用的文本部分的示例。

.text (ORIGIN(ROM_ITCM) + SIZEOF(.isr_vector)): {
        . = ALIGN(4);
        *(.text)
    } AT>ROM_AXIM


该块的本质是,我还将文本部分也加载到闪存中,但使用不同的总线来访问指令和ART加速器(类似于常规缓存)。为了使所有内容都能正常工作,我在代码执行过程中指出了加载地址和地址,但是如果我没有指出该部分应该偏移的话,它将不起作用。这类发明还有很多,我不会全部介绍。

在此阶段,将在必要时加载节并进行初始化。我们有一个进一步编写代码的工作基础。在下一篇文章中,我们将编写第一个驱动程序并使LED闪烁。

谢谢大家的关注以及您的成功。

All Articles