Top

ESD ARM-CORTEX-A01 DAY03

  1. GNU ARM工具链的安装、使用
  2. 编写LED驱动代码

1 GNU ARM工具链的安装、使用

1.1 问题

编辑、通信软件的安装都比较简单,只有按照其使用说明安装即可,或者参考经典案例中的方法。

在开发基于T-PAD的软件前必须完成其开发环境的建立,嵌入式软件开发环境是由编辑工具,编译、链接、调试工具,下载通讯工具等。

本课程开发主机系统选择为Linux兼容系统,所以编辑工具软件可以使用linux下支持的任何文本编辑工具软件,推荐用vim,另外在windows下可以使用source insight。串口终端软件,在Linux下可以使用minicom,ckermit,gtkterm等,在windows下可以使用SecureCRT、putty和超级终端等。tftp网络通信软件在linux系统下可以使用tftpd-hpa等,在windows下可以使用tftpd32。开发调试工具软件我们使用GNU ARM工具集。

本案例主要完成GNU ARM 工具软件的获取,安装,使用,为后续软件开发打下基础。

1.2 方案

使用arm-2009q3软件包,先完成该工具软件的安装和PATH配置,然后熟悉arm-linux-gcc等相关程序的使用。

1.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:将arm-2009q3.tar.bz2复制到ubuntu系统的/opt目录下,并解压安装。解压操作如下。

$ sudo chmod 777 /opt
$ cp   /mnt/hgfs/day02/arm-2009q3.tar.bz2  /opt  
$ cd   /opt
$ sudo tar -jxvf arm-2009q3.tar.bz2

步骤二:编辑用户主目录下的 .bashrc文件,添加PATH 路径设置。

$ cd
$ vim .bashrc
PATH = /opt/arm-2009q3/bin:$PATH:
export  PATH

步骤三:使刚刚设置的PATH起作用可执行如下命令。

$ source   .bashrc

步骤四:测试刚刚安装的工具集,可以输入如下命令。

$ arm-linux-gcc    -v

执行如上命令,终端窗口会有arm-linux-gcc的信息输出,如果提示命令找不到则安装或PATH变量没设置或者PATH没有生效。需要回头查找前述步骤是否正确完成。

步骤五:GNU ARM命令行工具包中包含多个基本命令,现简单介绍一下其使用。

1.编译器arm-linux-gcc。

编译器用来编译源码文件产生可执行文件。可以编写一个打印hello word的C程序文件hello.c,然后将其编译成可执行文件。hello.c示例代码如下:

#include<stdio.h>
int main(void)
{
		printf("\n Hello World!\n");
		return 0;
}

在命令行输入如下命令:

$ arm-linux-gcc hello.c

这样就产生了一个名为a.out的可执行文件,遗憾的是我们不能在x86平台的linux命令行下执行该文件,为什么呢?arm-linux-gcc产生的不是可执行文件吗?可以尝试执行刚刚产生的可执行文件试试。

$ ./a.out

则提示bash: ./a.out: cannot execute binary file,如图-1所示:

图-1

在上图中可以看到,首先用arm-linux-gcc编译hello.c产生a.out可执行文件,然后执行该文件,则提示“bash: ./a.out: cannot execute binary file”。

原因在于arm-linux-gcc是用来为ARM体系结构的硬件平台开发软件的交叉编译器,其产生的可执行文件是要运行在基于 ARM 硬件平台上的Linux系统中,而不能运行在开发主机(X86)系统下的Linux系统中。

可以使用Linux的file命令来查看一下刚刚产生的a.out的文件结构。命令行输入命令:

$ file   a.out

可以看到如图-2所示信息:

图-2

可以看到,刚刚产生的a.out是 LSB(小端格式)的ELF(执行与链接)格式的文件,该文件是能够运行在ARM Linux环境下的可执行程序。

2.汇编器 arm-linux-as。

编写do_sub.s汇编文件,其文件内容如下:

     .text
     .global do_sub
     .global _start
     .code 32

  _start:
do_sub:
mov   r0, #0
mov   r1, #9
mov   r2, #2
sub   r0, r1, r2
b    .

      .end                                        

汇编器用于将汇编源文件汇编成目标文件,示例用法如下:

$ arm-linux-as -o do_sub.o do_sub.s

汇编通过后将生成do_sub.o目标文件。

3.链接器arm-linux-ld。

arm-linux-ld用于链接目标文件和系统库中的函数代码(目标代码),示例用法如下:

$ arm-linux-ld -o do_sub do_sub.o

如果没有错误将产生do_sub文件,该文件是ELF格式文件,可以使用file命令查看其格式,如图-3所示:

$ file   do_sub

图-3

可以看到do_sub是包含ARM指令集的32位ELF格式可执行文件(小端格式)。

4.文件格式转换 arm-linux-objcopy。

用于文件格式转换,将上步生成的do_sub文件转换成二进制文件的示例用法如下:

$arm-linux-objcopy -O  binary  do_sub  do_sub.bin

do_sub.bin就是产生的纯二进制文件。对于裸机软件开发中通常需要将elf格式的可执行文件转换成纯二进制文件下载到TPAD中运行。

5.反汇编 arm-linux-objdump。

arm-linux-objdump用于将目标文件或elf格式可执行文件反汇编成汇编代码文件,示例用法如下:

$ arm-linux-objdump  -S  do_sub.o>do_sub.asm

这样就将do_sub.o文件反汇编成 do_sub.asm汇编文件了,反汇编后得到的do_sub.asm文件内容如图-4所示:

图-4

6.elf格式文件查看:arm-linux-readelf。

arm-linux-readelf可以用来查看elf格式的文件的信息,示例用法如下:

$ arm-linux-readelf -a do_sub

可以输出do_sub文件的文件头信息,以及段信息等等。

7.静态库管理 arm-linux-ar。

可以将产生的目标文件“.o”文件生成或添加到库中,也可以查看库中有的目标文件等。使用的示例代码如下:

$ arm-linux-ar  do_sub.a  do_sub.o

将do_sub.o生成do_sub.a库文件。

8.符号表生成指令 arm-linux-nm。

生成elf文件中的符号,使用示例如下:

$ arm-linux-nm  do_sub>sym

重定向输出的sym文件内容如图-5所示:

图-5

从sym可以看到,程序中的标号的值。

9.去掉elf文件中不需要的信息和代码arm-linux-strip。

arm-linux-strip用于去掉文件中不使用的一些信息,如调试信息等,以减小目标文件的体积。从而节省存储空间或提高加载、执行效率,如图-6所示。

图-6

可以看到do_sub的大小是798,do_sub.o的大小是618,可以执行以下指令:

$ arm-linux-strip  do_sub
$ arm-linux-strip  do_sub.o
$ ls  –l  do_sub*

如图-7所示,可以看到do_sub大小变为320,do_sub.o大小变为376,经比较可以清楚的看到使用arm-linux-strip处理前后do_sub和do_sub.o文件大小的变化。

图-7

2 编写LED驱动代码

2.1 问题

掌握使用C语言编程控制T-PAD上的LED1的闪烁(亮、灭)。

2.2 方案

通过看T-PAD底板原理图,得到LED1的亮灭是由GPC1[3]控制的,GPC1[3]给出高电平LED1亮,GPC1[3]给出低电平LED1灭。

通过阅读s5pv210的手册可以知道要让GPC1[3]输出高电平或者低电平,需要先将其配置为输出口,并禁止其上、下拉电阻,然后通过将GPC1的数据寄存器的BIT3置1使GPC1[3]输出高电平,GPC1的数据寄存器的BIT3清零来使GPC1[3]输出低电平。

C语言中可以使用指针来访问GPC1的寄存器,实现控制T-PAD上的LED1闪烁,程序流程如下,如图-8所示:

图-8

2.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:建立程序头文件。

1. 在当前用户(本例用户为tarena)的主目录下建立led目录,进入目录并建立led.h文件。命令序列如下。

$cd
$mkdir  led
$cd  led
$vim  led.h

2. 使用#define对用到的GPC1寄存器做如下定义。

#define  GPC1CON (*((volatile unsigned int *)0xE0200080))
#define  GPC1DAT (*((volatile unsigned int *)0xE0200084))
#define  GPC1PUD (*((volatile unsigned int *)0xE0200088))

步骤二:建立程序源文件。

1. 在刚刚建立led目录下,建立led.c文件。命令如下。

$vim  led.c

2. 编写寄存器赋值语句实现对灯的控制

使GPC1[3]作为输出口。

GPC1CON =(GPC1CON & 0xFFFF0FFF)|0x00001000;

禁止GPC1[3]的上下拉电阻功能。

GPC1PUD &= ~0xC0;

控制GPC1[3]输出高电平(LED1亮)。

GPC1DAT |= (1<<3);

控制GPC1[3]输出低电平(LED1灭)。

GPC1DAT &= ~8;

步骤三:编译源文件。

从源文件产生了一个名为led.o的目标文件

$arm-linux-gcc -march=armv5te -nostdlib-c -o led.o led.c

注:-march=armv5te 指定生成指令的架构(armv5te);-nostdlib 指定不使用标准库。

步骤四:链接文件。

arm-linux-ld用于链接目标文件生成一个ELF格式可执行程序

$arm-linux-ld -nostartfiles -nostdlib -Ttext=0x20008000 -e led_main -o led led.o

注:-nostartfiles不使用启动文件;-nostdlib不使用标准库;-Ttext指定代码段的起始位置 0x20008000;-e led_main用于指定程序的入口点。

可以使用file 命令查看文件的格式,命令如下:

$file led 

图-9

步骤五:格式转换产生led.bin。

ELF格式的可执行程序需要在有操作系统的情况下才能执行,裸板,无法解析这些格式信息,通过arm-linux-objcopy命令将上步生成的 led文件纯指令拷贝出来。

$arm-linux-objcopy -O binary  led  led.bin

步骤六:复制文件到/tftboot目录下

代码如下所示:

$cp led.bin    /tftboot

步骤七:将led.bin下载到TPAD运行。

1. 用和TPAD配套的串口线连接TPAD和PC机。

2. 启动SecureCRT(参考前例)。

3. 启动TPAD并进入到u-boot主菜单,如图-10所示:

图-10

在上图界面的U-boot提示行输入如下命令,如图-11所示:

tarena# tftp  0x20008000  led.bin

图-11

可以看到新的led.bin文件的大小为264个字节。

步骤八:程序在TPAD上执行

在u-boot提示符下输入:go 0x20008000命令执行程序,这时TPAD上的LED将不停闪烁。代码如下所示:

tarena# go  0x20008000

2.4 完整代码

本案例的完整代码如下所示:

1. led.h主要做函数声明和寄存器定义,内容如下。

#ifndef  __LED_H__
#define  __LED_H__

#define GPC1CON (*((volatile unsigned int *)0xE0200080))
#define GPC1DAT (*((volatile unsigned int *)0xE0200084))
#define GPC1PUD (*((volatile unsigned int *)0xE0200088))

extern void led_init(void);
extern void led_on(void);
extern void led_off(void);

#endif  //__LED_H__

2. LED控制c源程序文件,led.c示例代码如下。

#include "led.h"

//延时函数,LED灯状态保持,以便用户观察
static void delay(unsigned int n);

voidled_main(void)
{
		led_init();
		while (1)
		{
			led_on();
			delay(0x100000);

			led_off();
			delay(0x100000);
		}
}

static void delay(unsigned int n)
{
		unsignedinti;
		for (i = n; i != 0; i--);
}

voidled_init(void)
{
		//1.配置GPC1_3管脚配置为输出功能
		GPC1CON =(GPC1CON & 0xFFFF0FFF)|0x00001000;
		//2.禁止GPC1_3管脚的上下拉电阻
		GPC1PUD &= ~0xC0;
}

voidled_on(void)
{
		//控制GPC1_3管脚输出高电平 操作GPC1DAT bit[3]=1
		GPC1DAT |= 8;
}

voidled_off(void)
{
		//控制GPC1_3管脚输出低电平 操作GPC1DAT bit[3]=0
		GPC1DAT &= ~8;
}