gpt4 book ai didi

gcc - 真正最小的STM32应用:链接器故障

转载 作者:行者123 更新时间:2023-12-02 02:04:13 24 4
gpt4 key购买 nike

我正在构建一个只有微不足道的微型微控制器,用于自学。这样,我可以刷新有关链接描述文件,启动代码等主题的知识。



编辑:
我有很多评论指出,下面显示的“绝对最小的STM32应用程序”是不好的。当注意到向量表不完整,.bss部分未处理,外围地址不完整时,您是完全正确的,请允许我解释原因。


在此特定章节中编写完整而有用的应用程序从来不是作者的目的。他的目的是逐步解释链接描述文件的工作方式,启动代码的工作方式,STM32的启动过程是什么样的……纯粹出于教育目的。我很欣赏这种方法,并且学到了很多东西。
我在下面给出的示例取自相关章节的中间。本章将继续在链接器脚本和启动代码(例如.bss-节的初始化)中添加更多部分。
之所以将文件放在他本章的中间,是因为我陷入了特定的错误消息中。我想先解决此问题,然后再继续。
有问题的章节在他书的结尾。它是为那些经验丰富或好奇的读者准备的,他们希望获得有关大多数人甚至不考虑的主题的更深入的知识(大多数人使用制造商提供的标准链接描述文件和启动代码,而不用阅读它们)。


请记住,请让我们专注于手头的技术问题(如以下错误消息中所述)。也请接受我没有早点澄清作者意图的真诚道歉。但是我现在已经完成了,所以我们可以继续;-)





1.绝对最小的STM32应用

我要学习的教程是本书的第20章:“精通STM32”(https://leanpub.com/mastering-stm32)。本书介绍了如何使用两个文件(main.clinkerscript.ld)制作微型微控制器应用程序。由于我没有使用IDE(例如Eclipse),因此我还添加了build.batclean.bat来生成编译命令。所以我的项目文件夹如下所示:

enter image description here

在继续之前,我可能应该提供有关系统的更多详细信息:


作业系统:Windows 10,64-bit
微控制器:带有STM32F401RE微控制器的NUCLEO-F401RE板。
编译器:arm-none-eabi-gcc版本6.3.1 20170620(发行版)[ARM /嵌入式6分支修订版249437]。


主文件如下所示:

/* ------------------------------------------------------------ */
/* Minimal application */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
typedef unsigned long uint32_t;

/* Memory and peripheral start addresses (common to all STM32 MCUs) */
#define FLASH_BASE 0x08000000
#define SRAM_BASE 0x20000000
#define PERIPH_BASE 0x40000000

/* Work out end of RAM address as initial stack pointer
* (specific of a given STM32 MCU) */
#define SRAM_SIZE 96*1024 //STM32F401RE has 96 KB of RAM
#define SRAM_END (SRAM_BASE + SRAM_SIZE)

/* RCC peripheral addresses applicable to GPIOA
* (specific of a given STM32 MCU) */
#define RCC_BASE (PERIPH_BASE + 0x23800)
#define RCC_APB1ENR ((uint32_t*)(RCC_BASE + 0x30))

/* GPIOA peripheral addresses
* (specific of a given STM32 MCU) */
#define GPIOA_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_MODER ((uint32_t*)(GPIOA_BASE + 0x00))
#define GPIOA_ODR ((uint32_t*)(GPIOA_BASE + 0x14))

/* Function headers */
int main(void);
void delay(uint32_t count);

/* Minimal vector table */
uint32_t *vector_table[] __attribute__((section(".isr_vector"))) = {
(uint32_t*)SRAM_END, // initial stack pointer (MSP)
(uint32_t*)main // main as Reset_Handler
};

/* Main function */
int main() {
/* Enable clock on GPIOA peripheral */
*RCC_APB1ENR = 0x1;

/* Configure the PA5 as output pull-up */
*GPIOA_MODER |= 0x400; // Sets MODER[11:10] = 0x1

while(1) { // Always true
*GPIOA_ODR = 0x20;
delay(200000);
*GPIOA_ODR = 0x0;
delay(200000);
}
}

void delay(uint32_t count) {
while(count--);
}



链接描述文件如下所示:

/* ------------------------------------------------------------ */
/* Linkerscript */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */

/* Memory layout for STM32F401RE */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
}

/* The ENTRY(..) directive overrides the default entry point symbol _start.
* Here we define the main-routine as the entry point.
* In fact, the ENTRY(..) directive is meaningless for embedded chips,
* but it is informative for debuggers. */
ENTRY(main)

SECTIONS
{
/* Program code into FLASH */
.text : ALIGN(4)
{
*(.isr_vector) /* Vector table */
*(.text) /* Program code */
*(.text*) /* Merge all .text.* sections inside the .text section */
KEEP(*(.isr_vector)) /* Don't allow other tools to strip this off */
} >FLASH


_sidata = LOADADDR(.data); /* Used by startup code to initialize data */

.data : ALIGN(4)
{
. = ALIGN(4);
_sdata = .; /* Create a global symbol at data start */

*(.data)
*(.data*)

. = ALIGN(4);
_edata = .; /* Define a global symbol at data end */
} >SRAM AT >FLASH

}



build.bat文件在main.c上调用编译器,然后在链接器上调用:

@echo off
setlocal EnableDelayedExpansion

echo.
echo ----------------------------------------------------------------
echo. )\ ***************************
echo. ( =_=_=_=^< ^| * build NUCLEO-F401RE *
echo. )( ***************************
echo. ""
echo.
echo.
echo. Call the compiler on main.c
echo.
@arm-none-eabi-gcc main.c -o main.o -c -MMD -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -O0 -g3 -Wall -fmessage-length=0 -Werror-implicit-function-declaration -Wno-comment -Wno-unused-function -ffunction-sections -fdata-sections
echo.
echo. Call the linker
echo.
@arm-none-eabi-gcc main.o -o myApp.elf -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -specs=nosys.specs -specs=nano.specs -T linkerscript.ld -Wl,-Map=output.map -Wl,--gc-sections
echo.
echo. Post build
echo.
@arm-none-eabi-objcopy -O binary myApp.elf myApp.bin
arm-none-eabi-size myApp.elf
echo.
echo ----------------------------------------------------------------



clean.bat文件删除所有编译器输出:

@echo off
setlocal EnableDelayedExpansion

echo ----------------------------------------------------------------
echo. __ **************
echo. __\ \___ * clean *
echo. \ _ _ _ \ **************
echo. \_`_`_`_\
echo.
del /f /q main.o
del /f /q main.d
del /f /q myApp.bin
del /f /q myApp.elf
del /f /q output.map
echo ----------------------------------------------------------------


建立这个作品。我得到以下输出:

C:\Users\Kristof\myProject>build

----------------------------------------------------------------
)\ ***************************
( =_=_=_=< | * build NUCLEO-F401RE *
)( ***************************
""


Call the compiler on main.c


Call the linker


Post build

text data bss dec hex filename
112 0 0 112 70 myApp.elf

----------------------------------------------------------------




2.正确的启动代码

也许您已经注意到,最小的应用程序没有适当的启动代码来初始化.data节中的全局变量。第20.2.2章.data和.bss“ Mastering STM32”一书中的初始化部分介绍了如何执行此操作。

随着我的前进,我的 main.c文件现在看起来像这样:

/* ------------------------------------------------------------ */
/* Minimal application */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
typedef unsigned long uint32_t;

/* Memory and peripheral start addresses (common to all STM32 MCUs) */
#define FLASH_BASE 0x08000000
#define SRAM_BASE 0x20000000
#define PERIPH_BASE 0x40000000

/* Work out end of RAM address as initial stack pointer
* (specific of a given STM32 MCU) */
#define SRAM_SIZE 96*1024 //STM32F401RE has 96 KB of RAM
#define SRAM_END (SRAM_BASE + SRAM_SIZE)

/* RCC peripheral addresses applicable to GPIOA
* (specific of a given STM32 MCU) */
#define RCC_BASE (PERIPH_BASE + 0x23800)
#define RCC_APB1ENR ((uint32_t*)(RCC_BASE + 0x30))

/* GPIOA peripheral addresses
* (specific of a given STM32 MCU) */
#define GPIOA_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_MODER ((uint32_t*)(GPIOA_BASE + 0x00))
#define GPIOA_ODR ((uint32_t*)(GPIOA_BASE + 0x14))

/* Function headers */
void __initialize_data(uint32_t*, uint32_t*, uint32_t*);
void _start (void);
int main(void);
void delay(uint32_t count);

/* Minimal vector table */
uint32_t *vector_table[] __attribute__((section(".isr_vector"))) = {
(uint32_t*)SRAM_END, // initial stack pointer (MSP)
(uint32_t*)_start // _start as Reset_Handler
};

/* Variables defined in linkerscript */
extern uint32_t _sidata;
extern uint32_t _sdata;
extern uint32_t _edata;

volatile uint32_t dataVar = 0x3f;

/* Data initialization */
inline void __initialize_data(uint32_t* flash_begin, uint32_t* data_begin, uint32_t* data_end) {
uint32_t *p = data_begin;
while(p < data_end)
*p++ = *flash_begin++;
}

/* Entry point */
void __attribute__((noreturn,weak)) _start (void) {
__initialize_data(&_sidata, &_sdata, &_edata);
main();

for(;;);
}

/* Main function */
int main() {
/* Enable clock on GPIOA peripheral */
*RCC_APB1ENR = 0x1;

/* Configure the PA5 as output pull-up */
*GPIOA_MODER |= 0x400; // Sets MODER[11:10] = 0x1

while(dataVar == 0x3f) { // Always true
*GPIOA_ODR = 0x20;
delay(200000);
*GPIOA_ODR = 0x0;
delay(200000);
}
}

void delay(uint32_t count) {
while(count--);
}


我在 main(..)函数上方添加了初始化代码。链接描述文件也进行了一些修改:

/* ------------------------------------------------------------ */
/* Linkerscript */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */

/* Memory layout for STM32F401RE */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
}

/* The ENTRY(..) directive overrides the default entry point symbol _start.
* In fact, the ENTRY(..) directive is meaningless for embedded chips,
* but it is informative for debuggers. */
ENTRY(_start)

SECTIONS
{
/* Program code into FLASH */
.text : ALIGN(4)
{
*(.isr_vector) /* Vector table */
*(.text) /* Program code */
*(.text*) /* Merge all .text.* sections inside the .text section */
KEEP(*(.isr_vector)) /* Don't allow other tools to strip this off */
} >FLASH


_sidata = LOADADDR(.data); /* Used by startup code to initialize data */

.data : ALIGN(4)
{
. = ALIGN(4);
_sdata = .; /* Create a global symbol at data start */

*(.data)
*(.data*)

. = ALIGN(4);
_edata = .; /* Define a global symbol at data end */
} >SRAM AT >FLASH

}


这个小应用程序不再编译了。实际上,从 main.cmain.o的编译仍然可以。但是链接过程陷入困境:

C:\Users\Kristof\myProject>build

----------------------------------------------------------------
)\ ***************************
( =_=_=_=< | * build NUCLEO-F401RE *
)( ***************************
""


Call the compiler on main.c


Call the linker

c:/gnu_arm_embedded_toolchain/bin/../lib/gcc/arm-none-eabi/6.3.1/../../../../arm-none-eabi/lib/thumb/v7e-m/fpv4-sp/hard/crt0.o: In function `_start':
(.text+0x64): undefined reference to `__bss_start__'
c:/gnu_arm_embedded_toolchain/bin/../lib/gcc/arm-none-eabi/6.3.1/../../../../arm-none-eabi/lib/thumb/v7e-m/fpv4-sp/hard/crt0.o: In function `_start':
(.text+0x68): undefined reference to `__bss_end__'
collect2.exe: error: ld returned 1 exit status

Post build

arm-none-eabi-objcopy: 'myApp.elf': No such file
arm-none-eabi-size: 'myApp.elf': No such file

----------------------------------------------------------------




3.我尝试过的

我已经省略了这部分,否则这个问题太长了;-)



4.解决方案

@berendi提供了解决方案。谢谢@berendi!显然我需要将标志 -nostdlib-ffreestanding添加到gcc和链接器。 build.bat文件现在如下所示:

@echo off
setlocal EnableDelayedExpansion

echo.
echo ----------------------------------------------------------------
echo. )\ ***************************
echo. ( =_=_=_=^< ^| * build NUCLEO-F401RE *
echo. )( ***************************
echo. ""
echo.
echo.
echo. Call the compiler on main.c
echo.
@arm-none-eabi-gcc main.c -o main.o -c -MMD -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -O0 -g3 -Wall -fmessage-length=0 -Werror-implicit-function-declaration -Wno-comment -Wno-unused-function -ffunction-sections -fdata-sections -ffreestanding -nostdlib
echo.
echo. Call the linker
echo.
@arm-none-eabi-gcc main.o -o myApp.elf -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -specs=nosys.specs -specs=nano.specs -T linkerscript.ld -Wl,-Map=output.map -Wl,--gc-sections -ffreestanding -nostdlib
echo.
echo. Post build
echo.
@arm-none-eabi-objcopy -O binary myApp.elf myApp.bin
arm-none-eabi-size myApp.elf
echo.
echo ----------------------------------------------------------------


现在可以了!
在他的答案中,@ berendi还给出了有关 main.c文件的一些有趣的评论。我已经应用了大多数:


缺少 volatile关键字
空循环
缺少内存屏障(我是否将内存屏障放置在正确的位置?)
启用RCC后缺少延迟
误导的符号名称(显然它应该是 RCC_AHB1ENR而不是 RCC_APB1ENR)。
向量表:这部分我已跳过。现在我真的不需要 HardFault_HandlerMemManage_Handler ...了,因为这只是出于教育目的的微小测试。
但是,我确实注意到@berendi在声明向量表的方式上做了一些有趣的修改。但是我并没有完全掌握他在做什么。


main.c文件现在如下所示:

/* ------------------------------------------------------------ */
/* Minimal application */
/* for NUCLEO-F401RE */
/* ------------------------------------------------------------ */
typedef unsigned long uint32_t;

/**
\brief Data Synchronization Barrier
\details Acts as a special kind of Data Memory Barrier.
It completes when all explicit memory accesses before this instruction complete.
*/
__attribute__((always_inline)) static inline void __DSB(void)
{
__asm volatile ("dsb 0xF":::"memory");
}


/* Memory and peripheral start addresses (common to all STM32 MCUs) */
#define FLASH_BASE 0x08000000
#define SRAM_BASE 0x20000000
#define PERIPH_BASE 0x40000000

/* Work out end of RAM address as initial stack pointer
* (specific of a given STM32 MCU) */
#define SRAM_SIZE 96*1024 //STM32F401RE has 96 KB of RAM
#define SRAM_END (SRAM_BASE + SRAM_SIZE)

/* RCC peripheral addresses applicable to GPIOA
* (specific of a given STM32 MCU) */
#define RCC_BASE (PERIPH_BASE + 0x23800)
#define RCC_AHB1ENR ((volatile uint32_t*)(RCC_BASE + 0x30))

/* GPIOA peripheral addresses
* (specific of a given STM32 MCU) */
#define GPIOA_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_MODER ((volatile uint32_t*)(GPIOA_BASE + 0x00))
#define GPIOA_ODR ((volatile uint32_t*)(GPIOA_BASE + 0x14))

/* Function headers */
void __initialize_data(uint32_t*, uint32_t*, uint32_t*);
void _start (void);
int main(void);
void delay(uint32_t count);

/* Minimal vector table */
uint32_t *vector_table[] __attribute__((section(".isr_vector"))) = {
(uint32_t*)SRAM_END, // initial stack pointer (MSP)
(uint32_t*)_start // _start as Reset_Handler
};

/* Variables defined in linkerscript */
extern uint32_t _sidata;
extern uint32_t _sdata;
extern uint32_t _edata;

volatile uint32_t dataVar = 0x3f;

/* Data initialization */
inline void __initialize_data(uint32_t* flash_begin, uint32_t* data_begin, uint32_t* data_end) {
uint32_t *p = data_begin;
while(p < data_end)
*p++ = *flash_begin++;
}

/* Entry point */
void __attribute__((noreturn,weak)) _start (void) {
__initialize_data(&_sidata, &_sdata, &_edata);
asm volatile("":::"memory"); // <- Did I put this instruction at the right spot?
main();

for(;;);
}

/* Main function */
int main() {
/* Enable clock on GPIOA peripheral */
*RCC_AHB1ENR = 0x1;
__DSB();

/* Configure the PA5 as output pull-up */
*GPIOA_MODER |= 0x400; // Sets MODER[11:10] = 0x1

while(dataVar == 0x3f) { // Always true
*GPIOA_ODR = 0x20;
delay(200000);
*GPIOA_ODR = 0x0;
delay(200000);
}
}

void delay(uint32_t count) {
while(count--){
asm volatile("");
}
}




PS:Carmine Noviello的《 Mastering STM32》一书是绝对的杰作。您应该阅读它! => https://leanpub.com/mastering-stm32

最佳答案

您可以告诉gcc不要使用该库。
编译器
默认情况下,gcc假定您使用的是标准C库,并且可以发出调用某些函数的代码。例如,启用优化后,它会检测到复制内存的循环,并可以用对memcpy()的调用来代替它们。用-ffreestanding禁用它。
链接器
链接器还假定您要链接程序与C库和启动代码。库启动代码负责初始化库和程序执行环境。它具有一个名为_start()的函数,必须在重置后调用它。其功能之一是用零填充.bss段(见下文)。如果未定义分隔.bss的符号,则无法链接_startup()。如果您将启动函数命名为_startup()以外的任何其他名称,则链接器会将库启动作为未使用的函数悄无声息地删除,并且可以链接代码。
您可以告诉链接器不要将任何标准库或启动代码与-nostdlib链接,那么库提供的启动函数名称将不会与您的库冲突,并且每次意外调用库函数时,您都会收到链接器错误。
缺少volatile
您的寄存器定义缺少volatile限定符。没有它,随后对*GPIOA_ODR的写入将被优化。编译器会将这个“不变代码”移出循环。将寄存器定义中的类型更改为(volatile uint32_t*)将解决此问题。
空循环
优化器可以识别出延迟循环没有任何作用,并完全消除了延迟循环以加快执行速度。在延迟循环中添加一个空但不可移动的asm volatile("");指令。
缺少内存屏障
您正在初始化在C函数中保存.datadataVar部分。 *p中的__initialize_data()实际上是dataVar的别名,并且编译器无法知道它。从理论上讲,优化器可以在dataVar之前重新安排__initialize_data()的测试。即使dataVarvolatile,也不是*p,因此不能保证排序。
数据初始化循环之后,应告诉编译器程序变量是由编译器未知的机制更改的:

asm volatile("":::"memory");

这是一个老式的gcc扩展,最新的C标准可能已定义了一种可移植的方法(较早的gcc版本无法识别)。
启用RCC后缺少延迟
勘误说

为了管理外设对寄存器的读/写操作,应考虑RCC外设时钟使能和有效外设使能之间的延迟。
此延迟取决于外围设备映射:
•如果外围设备映射在AHB上:延迟应等于2个AHB周期。
•如果外围设备映射在APB上,则延迟应等于1 +(AHB / APB预分频器)周期。
解决方法

使用DSB指令来停止Cortex®-M4CPU管道,直到该指令完成。


因此,请插入
__DSB();

*RCC_APB1ENR = 0x1;之后(应称为其他名称)
误导的符号名称
尽管在 GPIOA中启用 RCC的地址似乎正确,但该寄存器在文档中称为 RCC_AHB1ENR。这会使试图理解您的代码的人感到困惑。
向量表
尽管从技术上讲,您只需要拥有堆栈堆栈器和重置处理程序就可以摆脱困境,但我还是建议您多添加一些条目,至少是故障处理程序,以便进行简单的故障排除。
__attribute__ ((section(".isr_vector"),used))
void (* const _vectors[]) (void) = {
(void (*const)(void))(&__stack),
Reset_Handler,
NMI_Handler,
HardFault_Handler,
MemManage_Handler,
BusFault_Handler,
UsageFault_Handler
}

链接描述文件
它至少必须为向量表和代码定义一个部分。程序必须有一个起始地址和一些代码,静态数据是可选的。其余的取决于程序正在使用哪种数据。如果没有特定类型的数据,则可以从链接器脚本中技术上省略它们。

.rodata:只读数据, const数组和结构在此处。它们保持闪烁状态。 (简单的 const变量通常放在代码中)
.data:初始化变量,您声明的所有带有 =符号且没有 const的变量。
.bss:应在C中进行零初始化的变量,即全局变量和 static变量。

由于您现在不需要 .rodata.bss,所以很好。

关于gcc - 真正最小的STM32应用:链接器故障,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49885034/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com