STM32第1部分:基础知识

您不能相信自己没有完全编写的代码。-汤普森(Ken Thompson)
也许是我最喜欢的报价。正是她成为了我决定跳入兔子洞深处的原因。我是最近才开始进入编程领域的,大约一个月过去了,因此我决定写文章来巩固材料。一切始于一项简单的任务,即使用Arduina同步照相馆中的灯。问题已解决,但我不再去照相馆了,没有时间了。从那时起,我决定彻底从事微控制器编程。 Arduin,尽管其简单性吸引人,但我不喜欢该平台。选择权属于ST公司及其受欢迎的产品。那时,我仍然不知道有什么区别,但是作为一个典型的消费者,我比较了“处理器”的速度和内存的数量,我给自己买了一块带STM32F746NG显示屏的令人印象深刻的板-Discovery。我会怀念绝望的时刻,然后说清楚。

沉浸在程序员的形象中,我读了很多书,进行了研究和实验。正如我上面已经描述的,我想学习得很好,仅此而已。为此,我设定了一个目标,而不是任何现成的解决方案,而是我的目标。如果一切对我都成功,那么您将成功。

您需要的一切清单:

  1. Ubuntu 16+ 虚拟机或任何其他版本
  2. ARM编译器-在developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads下载
  3. openocd调试器和程序员-您将无法从链接下载,我们会从源中收集信息,并随附说明

    git clone https://git.code.sf.net/p/openocd/code openocd
  4. 文字编辑器随您喜欢

在安装并组装完所有组件之后,我们可以继续第一个项目!不,它甚至不是闪烁的灯泡。首先,我们必须深入研究微孔本身的初始化过程。

我们需要的:

  1. 生成文件
  2. 链接器
  3. 初始化程序

让我们从最后一段Init.c开始。首先,我们的mk应该加载地址“堆栈指针”,该地址是指向用于PUSH和POP指令的内存地址的指针。我强烈建议您仔细研究这两个说明,因为我不会详细解释所有说明。如何执行此操作,请参见下文。

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

现在让我们来看这个例子。Extern表示该符号是外部符号,我们在Linker.ld文件中声明了该符号,稍后将返回它。

 __attribute__((section(".isr_vector"), used))

在这里,我们使用一个属性,该属性告诉编译器将数组放在isr_vector节中,即使我们在代码中不使用它,它也必须包含在程序中。它的第一个元素将是相同的指针。

现在让我们弄清楚为什么这个数组是什么,以及它会被吃掉什么。与常规处理器不同,手臂架构上的微米不是从零地址而是从该数组中的指针指示的地址开始执行的,所以一切都很复杂。同样,此数组中的第一个指针始终指向堆栈的开头,但是第二个指针已经指向代码的开头。

我举一个例子。假设堆栈以0x20010000开头,程序代码为0x0800008。那么数组可以写成

void *vectors[] __attribute__((section(".isr_vector"), used)) = {
    0x20010000,
    0x08000008
}

也就是说,控制器首先初始化堆栈,然后考虑第一条指令的地址并将其加载到程序计数器寄存器中。现在最重要的是,取决于型号,这些数字可能有所不同,但是以stm32f7为例,我可以自信地说该数组应该在内存中,地址为0x08000000。正是从该地址开始,mk会在打开或重置后开始工作。

现在,我们将停止并注意如何将此数组放入所需的部分。这是通过“ ld”或链接程序完成的。该程序将我们的整个程序收集在一起,并且为此使用了Linker.ld脚本。我举一个例子并进一步分析。

MEMORY{
	ROM_AXIM (rx) : ORIGIN = 0x08000000, LENGTH = 1M
	RAM_DTCM (rwx): ORIGIN = 0x20000000, LENGTH = 64K
}

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

SECTIONS{
	.isr_vector : {
	KEEP(*(.isr_vector))
	} >ROM_AXIM</code>
}

让我们看看这里发生了什么。 MEMORY定义内存部分,而SECTIONS定义部分。在这里,我们看到了_estack,它等于内存开始和内存长度之和,即内存结束的总和。还定义了放置数组的.isr_vector部分。>ROM_AXIM在本节的末尾表示应按照微米的要求将此部分放在以0x08000000开头的内存部分中。

我们将数组放置在有必要的位置,现在我们需要某种指令来使微米工作。这是增强的init.c:

extern void *_estack;

void Reset_Handler();

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

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

如前所述,第二个地址必须是指向第一个函数或指令的指针。再说一次属性,但是一切都很简单,该函数不返回任何值并且在入口处遵循任何ABI,即裸露的``裸''。在这样的功能中,通常的汇编程序被推入。

现在是时候编译我们的代码,看看幕后的内容。我们暂时不会触摸Makefile。

arm-none-eabi-gcc -c init.c -o init.o -mthumb

arm-none-eabi-gcc -TLinker.ld -o prog.elf init.o -Wl,--gc-sections -nostartfiles -nodefaultlibs -nostdlib

在这里,我们看到了很多不可理解的东西。为了:

  1. -mthumb为armv7编译,仅使用拇指指令
  2. -TLinker.ld指定链接描述文件。默认情况下,它将编译以在Linux环境中执行
  3. -Wl-gc-sections -nostartfiles -nodefaultlibs -nostdlib删除所有标准库,环境C初始化文件以及所有其他辅助库,例如数学库。

当然,将其装入微米是没有意义的。但是在查看和研究二进制文件方面有一种感觉。

objcopy -O ihex prog.elf prog.bin

hexdump prog.bin

然后我们将看到结论。“ 00000000:00 01 00 02 09 00 00 08”将象征我们的成功。这是我的第一篇文章,我无法完全揭示所有材料和本质,因此在接下来的文章中,我将更详细地描述机制,我们的朋友将能够进入一个不会闪烁指示灯,而是配置处理器和总线时钟的程序。

All Articles