【原创】在Linux下用Gcc 4.3.1进行STM32开发入门教程。
今天,尝试了在我使用的Gentoo系统上位Cortex-m3构建GNU工具链,没想到如此简单。以超级用户权限运行如下命令:
crossdev --g 4.3.1-r1 -t arm-elf
因为官方的gcc在4.3版本下加入了对cortex-m3的支持,所以上面的命令用 --g 4.3.1-r1参数,指定了4.3.1-r1版的GCC。整个编译过程非常顺利,编译
成功后得到了:arm-elf-gcc,arm-elf-ld,arm-elf-objcopy等命令,这些就是所需要的工具。
参考
1、
大侠 bozai 章其波 在
[原创] 支持cortex-M3 的GNU ARM编译器 CodeSourcery 上的第一个STM32F10x例子
http://www.ouravr.com/bbs/bbs_list.jsp?bbs_id=3011&bbs_page_no=2
一帖中给出的工程(makefile和ldscripts)
2、大侠bluelucky翻译的《Cortex-M3权威指南》中有关用gcc进行开发的章节。
写了一个简单的程序,经测试成功的点亮了LED。
所有心得不敢独享,在这里与大家分享一下,一并谢谢bluelucky和章其波的辛勤劳作。
-------------------------------------------------------------------------------------------------------------------------------------
一、安装GNU工具链
因为在Gentoo Linux下有crossdev这个非常强大的构建交叉编译工具链的工具,安装Cortex-m3的交叉工具链非常简单,方法前以述及,这里不赘述。
二、STM32F10x(Cortex-m3)基于GNU工具链的开发流程
《Cortex-M3权威指南》一书中有如下这个开发流程图:
http://cache.amobbs.com/bbs_upload782111/files_10/ourdev_405088.JPG
gnu工具链开发流程 (原文件名:GNU Toolchain development flow.JPG)
由图可知,用C语言进行stm32的程序开发,仍然是:写代码--->编译、连接--->下载到flash这样一个过程。只不过除此以外,我认为比较重要的还需要
知道这样几点:
1、如何访问此种单片机的外围设备寄存器;
2、如何书写此种单片机的中断服务程序;
3、此种单片机复位后,从什么地址处开始执行代码;然后我们如何告诉编译工具把代码按照这个入口地址开始安排我们的代码。
4、需不需要为构建C语言的运行环境作一些工作,也就是启动代码。
5、通过命令行选项通知编译器为特定的单片机生成代码。
三、编写一个最精简的代码
1、一个main函数就足够了吗?
先让我们简单回顾一下在PC机,一个程序的执行过程大概是怎样的。因为程序是在操作系统的管理下运行的,过程大概为:
操作系统----------> 启动代码(编译器自动加入,做一些堆栈、全局变量的初始化工作)-----------> main
然而在裸奔的单片机上,操作系统没有了,所以原来由操作系统和编译器作的事情,现在需要我们手工DIY了(如果交叉编译工具没有为我们做好这些事
情的话,因为我也不知道gcc现在有没有为stm32做好这一切,所以我暂时假定什么都得靠自己)。
2、C程序的典型内存布局
+-------------------------------+
| |
| 堆栈 |
| |
+ - - - - - - - - - - - - - - - +
| |
| |
| |
| |
| |
| |
| |
| |
| |
+ - - - - - - - - - - - - - - - +
| |
| 堆 |
| |
+-------------------------------+
| |
| 未初始化的数据 |
| .bss段 |
| |
+-------------------------------+
| |
| 初始化的数据 |
| .data段 |
| |
+-------------------------------+
| |
| 正文 |
| .text段 |
| .rodata段 |
| |
+-------------------------------+
上图中,正文对应的是可执行代码.text和常量表格数据等.rodata,.data对应初始化了的全局变量,编译后将位于可执行文件中,由启动代码负责加载
到数据区中(在单片机中这部分数据会存于flash中,需要有启动代码把这部分内容拷贝到sram中),.bss段是没有初始值的全局变量,由启动代码把这
部分内容全初始化为0;为了保证C程序的执行,还需要设置好程序运行时的堆栈区。
在有了这些基础知识后,除了main以外,我们还需要做些什么就比较清楚了:设置堆栈区,把编译好的内容放到单片机中正确的地方中去。
3、设置堆栈区和启动代码
Cortex-m3内核在地址0x0000 0000处存放一个向量表,向量表的第0个单元,也即地址0x0000 0000处存放的是堆栈顶的地址,Cortex-m3复位后即从该处
取出数据用以初始化MSP寄存器。向量表中的内容是32位的地址,这些地址是中断异常服务程序的入口地址,其中向量表的第一个单元,
即地址0x0000 0004处存放的是复位向量,也就是说Cortex-m3复位后,执行该向量(可理解为函数指针)指向的复位代码。看看代码吧:
__attribute__ ((section(".stackarea")))
static unsigned long pulStack;
这一句定义了一个pulStack的数组,程序把这个数组作为了堆栈区。这条语句使用了__attribute__ ((section(".stackarea"))) 把数组定位在
了.stackarea这个段中。
typedef void (* pfnISR)(void);
__attribute__ ((section(".isr_vector")))
pfnISR VectorTable[] =
{
(pfnISR)((unsigned long)pulStack + sizeof(pulStack)), // The initial stack pointer
ResetISR, // The reset handler
NMIException,
HardFaultException
};
定义了一个数组VectorTable,作为向量表,定位于.isr_vector段中。通过链接脚本的控制这个表将放在正文区的最开始,正文区又将从flash的最开始
存放,这样这个向量表就会起到相当于存放在0x0000 0000开始的地址空间的效果。
向量表的第0个单元是((unsigned long)pulStack + sizeof(pulStack)),这是数组的最后一个元素,因为Cortex-m3的堆栈是向下增长的。
向量表的第1个单元是ResetISR,它指向复位处理的代码,也是整个程序的入口。本程序用它来实现启动代码的功能。
extern unsigned long _etext;
extern unsigned long _data;
extern unsigned long _edata;
extern unsigned long _bss;
extern unsigned long _ebss;
void ResetISR(void)
{
unsigned long *pulSrc, *pulDest;
//
// Copy the data segment initializers from flash to SRAM.
//
pulSrc = &_etext;
for(pulDest = &_data; pulDest < &_edata; )
{
*pulDest++ = *pulSrc++;
}
//
// Zero fill the bss segment.
//
for(pulDest = &_bss; pulDest < &_ebss; )
{
*pulDest++ = 0;
}
//
// Call the application's entry point.
//
main();
}
这段代码用到了通过连接器赋值的几个变量值。_etext的值为正文段结尾处的地址,这之后的flash空间是初始化的数据值,应该复制到sram中去,
_data、_edata的值分别为数据段的开始和结尾处的地址,这部分应该是sram的地址。
pulSrc = &_etext;
for(pulDest = &_data; pulDest < &_edata; )
{
*pulDest++ = *pulSrc++;
}
这部分代码就是将保存于flash中的初始化数据复制到sram中。
上面代码中的第二个循环是将.bss段清零。最后调用main进入到我们的主程序。
4、访问外围设备寄存器
Cortex-m3的外围设备寄存器位于线性的4GB地址空间中,所以定义指向该外围设备所处地址的指针即可访问了。
#define GPIOC_CRL (*((volatile unsigned int*)(0x40011000)))
#define GPIOC_BSRR (*((volatile unsigned int*)(0x40011010)))
#define GPIOC_BRR (*((volatile unsigned int*)(0x40011014)))
#define RCC_APB2ENR (*((volatile unsigned int*)(0x40021018)))
使用宏GPIOC_CRL等即可访问相应的寄存器。
5、链接
gcc编译C源程序文件后,得到目标文件,目标文件需要连接得到最后的可执行文件,程序才能执行。一般来说,目标文件包含
.text段:可执行代码
.rodata段: 只读的数据,对应程序中的常量
.data段: 初始化的全局变量
.bss段: 未初始化的全局变量
连接器所作的工作简单的讲就是,把所有目标文件相应的段连接到一起,并把目标文件中的“变量地址”“函数地址”重定位至正确的地址空间;
比如,对于stm32来说向量表,.text和.rodata就应该放到从0x0800 0000开始的flash,.data,.bss和堆栈就应该定位至从0x2000 0000开始的sram中。
这些定位都可以通过链接脚本进行控制。
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x20000
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x5000
}
这些语句说明了flash和sram开始的地址以及大小。
.text :
{
KEEP(*(.isr_vector .isr_vector.*))
*(.text .text.*)
*(.rodata .rodata*)
_etext = .;
} > FLASH
按.isr_vector, .text, .rodata的顺序排列正文段的内容;回忆前述VectorTable[]数组就被定为与.isr_vector段中,所以这段脚本就保证了向量表为
与正文区的最开端,将存放于0x0800 0000开始的位置了。但向量表不是应该从0x0000 0000开始吗?原来stm32可以通过boot0、boot1引脚的配置将
flash映射到0x0000 0000处。具体可参考stm32的数据手册。
_etext = .; 这条语句把计数器“.”的值赋给了变量_etext;“.”现在的值就为.text的尾部。
另,后面的.data、.bss、.stackarea部分可自行分析,原理一样。
四、编译程序
step1:arm-elf-gcc -mcpu=cortex-m3 -mthumb gpio_test.c -nostartfiles -T stm32f103VBT6.ld -o gpio_test.o
注意参数 -nostartfiles指示不要包含编译器自带的启动代码,-T stm32f103VBT6.ld表示使用stm32f103VBT6.ld这个链接脚本。
step2:arm-elf-ld -T stm32f103VBT6.ld -o gpio_test.out gpio_test.o
同样使用stm32f103VBT6.ld这个链接脚本。
step3:arm-elf-objcopy -Obinary gpio_test.out gpio_test.bin
从elf的文件格式中得到最终需要的gpio_test.bin二进制目标文件。
step4:使用官方的flash下载demo程序将得到的gpio_test.bin通过usart1烧录至芯片。 /* filename: gpio_test.c */
#define GPIOC_CRL (*((volatile unsigned int*)(0x40011000)))
#define GPIOC_BSRR (*((volatile unsigned int*)(0x40011010)))
#define GPIOC_BRR (*((volatile unsigned int*)(0x40011014)))
#define RCC_APB2ENR (*((volatile unsigned int*)(0x40021018)))
#ifndef STACK_SIZE
#define STACK_SIZE 64
#endif
void ResetISR(void);
void NMIException(void);
void HardFaultException(void);
void delay(void);
typedef void (* pfnISR)(void); // Pointer to exception handle function
// mthomas: added section -> alignment thru linker-script
__attribute__ ((section(".stackarea")))
static unsigned long pulStack;
__attribute__ ((section(".isr_vector")))
pfnISR VectorTable[] =
{
(pfnISR)((unsigned long)pulStack + sizeof(pulStack)), // The initial stack pointer
ResetISR, // The reset handler
NMIException,
HardFaultException
};
void delay(void)
{
unsigned int i;
for( i = 0; i < 0x3ffff; ++i)
asm("nop");
}
int main(void)
{
RCC_APB2ENR |= (1<<4);
GPIOC_CRL &= 0x0000FFFF;
GPIOC_CRL |= 0x33330000;
while(1){
GPIOC_BRR = (1<<4);
GPIOC_BSRR = (1<<7);
delay();
GPIOC_BRR = (1<<7);
GPIOC_BSRR = (1<<6);
delay();
GPIOC_BRR = (1<<6);
GPIOC_BSRR = (1<<5);
delay();
GPIOC_BRR = (1<<5);
GPIOC_BSRR = (1<<4);
delay();
}
}
//*****************************************************************************
//
// The following are constructs created by the linker, indicating where the
// the "data" and "bss" segments reside in memory.The initializers for the
// for the "data" segment resides immediately following the "text" segment.
//
//*****************************************************************************
extern unsigned long _etext;
extern unsigned long _data;
extern unsigned long _edata;
extern unsigned long _bss;
extern unsigned long _ebss;
void ResetISR(void)
{
unsigned long *pulSrc, *pulDest;
//
// Copy the data segment initializers from flash to SRAM.
//
pulSrc = &_etext;
for(pulDest = &_data; pulDest < &_edata; )
{
*pulDest++ = *pulSrc++;
}
//
// Zero fill the bss segment.
//
for(pulDest = &_bss; pulDest < &_ebss; )
{
*pulDest++ = 0;
}
//
// Call the application's entry point.
//
main();
}
void NMIException(void)
{
return;
}
void HardFaultException(void)
{
return;
} /*************************************************/
/* filename: stm32f103VBT6.ld */
/* linkscript for STM32F103VBT6microcontroller */
/* */
/* modified by XiaoFeng Li (ifree64) China */
/* */
/* */
/* E-Mail: leexiaofeng@gmail.com */
/* 2008-9-6 */
/*************************************************/
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x20000
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x5000
}
/* Section Definitions */
SECTIONS
{
.text :
{
KEEP(*(.isr_vector .isr_vector.*))
*(.text .text.*)
*(.rodata .rodata*)
_etext = .;
} > FLASH
.data : AT (_etext)
{
_data = .;
*(.data .data.*)
. = ALIGN(4);
_edata = . ;
} > SRAM
/* .bss section which is used for uninitialized data */
.bss (NOLOAD) :
{
_bss = . ;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = . ;
} > SRAM
.stackarea (NOLOAD) :
{
. = ALIGN(8);
*(.stackarea .stackarea.*)
. = ALIGN(8);
} > SRAM
. = ALIGN(4);
_end = . ;
} step1:arm-elf-gcc -mcpu=cortex-m3 -mthumb gpio_test.c -nostartfiles -T stm32f103VBT6.ld -o gpio_test.o
step2:arm-elf-ld -T stm32f103VBT6.ld -o gpio_test.out gpio_test.o
step3:arm-elf-objcopy -Obinary gpio_test.out gpio_test.bin
step4:download gpio_test.bin to stm32, ok! 记号 Mark 好啊,以后有现成的可以捡了,GNU工具链曾经搞得我头痛不已,修改一下,重新编译2个小时过去了,硬盘还在狂闪 更新了楼主位的文章,希望大家多指正。 matk. 太感谢ifree64 我曾试着找GCC的编译工具,都不会用 呵呵。标记一下。 收藏了,难得的好文章! mark. 标记一下 mark,感谢楼主分享. 怎么下载呢?没有看到怎么从Linux下载到STM32的过程,很感兴趣呀 现在还没有看到有Linux下的isp下载工具,或者我还不知道。 非常好,谢谢LZ了。 Linux下的ISP工具要不?命令行界面的 楼上真是一个大好人啊。你把这个串上来好吗?
如果有源代码就更好了。 好文,做个记号! 楼主能否把编好的toolchain打包传上来? 我没有那么好,别人也没有那么坏
Linux下的ISP工具是移植stm32boot的代码,做了一些速度的优化
但主要是为了支持Versaloon的STM32USBBoot,我自己一般也很少使用硬件串口的ISP
所以,目前可能还不完善,如果有问题,LZ能解决并send patch的话,我可以把源代码发给你 ifree64你好:
请教下:
STM32下,能不能将一个嵌入另一个程序,一个是先写入MCU的,并且作为控件程序,常驻MCU中的;另一个是后写入MCU的,且是经常需变动的(也是可执行代码),并且是需被前一个程序调用的,大家说说这样是否可行?具体应如何做?谢谢 mark 编译型的PLC分两部分程序,前一部分是PLC的控制程序,对于STM32是写到了FLASH的0x80000000地址处;
而另一部分作为已编译成二进代码的“PLC用户应用程序”,是通过上位软件和PLC进行USART通信的
方法是写入PLC的,比如写在:0x80004000处,这第二部分程序如何运行呢?它使用什么内存作为堆栈,是主
控程序的堆栈么?什么内存作为堆?
大家有什么好的主意么? mark 问一下,在ubuntu或者fedora下怎么开发STM32啊 mark mark 这个记下 MARK 谢谢楼主共享,我现在就是在linux下进行嵌入式软件开发,受教了 有没有人用Eclipse IDE进行开发的呢? mark mmmm 【27楼】 shark313
就用sourceryG++啊,在ubuntu下也是图形界面安装的。 回家再试试! lz的这个命令有问题:
step1:arm-elf-gcc -mcpu=cortex-m3 -mthumb gpio_test.c -nostartfiles -T stm32f103VBT6.ld -o gpio_test.o
应该加上-c才对,不然就没必要ld了就能生成elf文件了(也就是gpio_test.o跟gpio_test.elf作用是一样的)。
不信你再加上-v运行下,看看你这个命令是不是也调用了ld(collect2)了。
判断这个还有一个判断标准就是:用readelf看看有没有.reloc段,如果只是编译了目标代码必然带有这个段的,可是用lz的ld script链接之后的代码肯定没有这个段,而且Type一栏也应该是REL而不是EXEC。
另外,编译的时候不需要给定ld script,链接的时候才需要,也就是-T stm32f103VBT6.ld也多余。
同时,如果指定了-c, -o也不需要了,自动会生成你想要的文件名。
修正后的命令应该是这样的:
arm-elf-gcc -mcpu=cortex-m3 -mthumb gpio_test.c -nostartfiles -c
另外,补充下,对于多源文件的项目,需要的是每个.c文件都上述编译,
然后在ld的时候把所有.o文件名(以及需要的库,但是库要注意摆放的顺序,不想确定顺序就用--start-group/--end-group来偷懒吧~)全放上即可。
to 【23楼】 liuzq
你的想法当然可行,固定的那个就是bootloader,可变的是app。
但是根据你app是否调用bootloader的代码以及是否需要app的下载到flash的地址可变问题复杂度不同。
还有,app和bootloader是否需要分开编译,也是增加复杂性的一个方面,比如如果你需要第三方在不知道bootloader代码的情况为你的应用开发app,就是最复杂的一种情况。 好贴 试试 收藏,好文章 mark 先收藏一下。过段时间要搞一下STM32。 好东西
Mark 好贴 多谢分享~~ 还有这个啊 哈哈 顶 mark //ifree64你好:
//请教下:
// STM32下,能不能将一个嵌入另一个程序,一个是先写入MCU的,并且作为控件程序,常驻MCU中的;另一个是后写入
//MCU,且是经常需变动的(也是可执行代码),并且是需被前一个程序调用的,大家说说这样是否可行?具体应如何做?谢谢
感觉这个应该可以实现分开编译的,主控程序编译好了以后,关键是确定应用的Flash和RAM的使用范围不要冲突即可,然后就是把APP提供的功能函数放在一个指针数组中(相当于中断向量的方式),编译的时候定位在一个固定的地址。在主控程序中就可以调用APP提供的功能了,当然也可以把主控程序实现的功能函数以指针数组定位在固定的位置,让APP调用。这个这就跟PC程序中的框架和控件的关系比较相似了。 好文章,正在找这方面的资料 MARK 好贴 太强大了,刚才在万利的199元板子试了一下,成功! 顶,怎么没置酷。 mark. 好文章,记住 爪印 顶了!mark! mark 受教了 留抓 mark MARK 好东东 mark mark mark! mark! mark! mark!!!! good!有空试试 哈哈,这个,留脚印给未来的自己。 mark mark 这个不错!gcc的效率不知道有没对比数据? mark
gcc stm32 IAP mark!!
好东西。。 赤脚盖章 mark! mark mark Coocox就是用GCC开发的。现在是V1.25版本,支持STM32,TI,芯唐等。 mark mark mark mark! 不错 收藏了以后慢慢看 mark mark MARK 牛x,linux下开发,不错,为啥我的状态还是等待审核呢 直接一条命令搞定问题(debian)
sudo apt-get install gcc-4.5-arm-linux-gnueabi mark 好文,收藏 mark 以下蓝色文字由版主:bluelucky 于:2012-02-14,19:48:14 加入。<font color=black>请发贴人注意:本贴放在这分区不合适,即将移走
原来分区:其它Cortex-M3技术讨论区(ST除外)
即将移去的分区:STM32/STM8 技术讨论区
移动执行时间:自本贴发表16小时后
任何的疑问或咨询,请可随时联系站长。谢谢你的支持!</font> Mark
现在都是在Windows上面用,以后可能会用到。
顶楼主! gcc,好帖,学习 好贴啊,果断顶!
页:
[1]
2