以文本方式查看主题

-  曙海教育集团论坛  (http://sun4.cn/bbs/index.asp)
--  Linux系统开发  (http://sun4.cn/bbs/list.asp?boardid=34)
----  在Linux下用Gcc 4.3.1进行STM32开发入门  (http://sun4.cn/bbs/dispbbs.asp?boardid=34&id=1767)

--  作者:wangxinxin
--  发布时间:2010-11-25 10:10:27
--  在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_li ... 1&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权威指南》一书中有如下这个开发流程图:

由图可知,用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[STACK_SIZE];
这一句定义了一个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进入到我们的主程序。