历史上的今天
返回首页

历史上的今天

今天是:2025年07月22日(星期二)

正在发生

2018年07月22日 | ARM处理器学习之--GPIO操作篇

2018-07-22 来源:eefocus

在上一篇文章中我们详细讲解了ARM开发环境的搭建,我们选择了X86-linux平台交叉编译ARM程序,交叉编译链选用arm-linux-系列。另外,我们还说明了一些开发需要的基础知识。关于以上这些内容,请参见ARM芯片学习内容规划及ARM开发环境的搭建。

我们学习高层应用程序开发的时候,一般第一个程序是经典的”hello world”程序。我们学习嵌入式的开发,主要是根据应用需求,选用合理的电子器件设计硬件电路,然后使用主控芯片控制外设。所以,我们GPIO操作篇的内容选为让电路板上的一个LED灯闪烁。

在讲解实验之前,我还是说明一下。这些基础实验,都是在特定硬件平台上运行的,且现象也是在特定电路板上才能产生的。所以实验中的程序并没有通用的移植性。拿到程序之间编译后下载到您的开发板上不一定能正常执行。需要简单修改。而且,我写这一系列的教程是让大家了解使用一款32bit处理器的基本方法和思路。并不是针对某一硬件平台。之所以所有程序都在一个固定的硬件平台上运行是因为要保证程序及想法的正确性。

 

相信,有些朋友以前就学习过单片机。学习单片机时有一些应该知道的基本问题。同样,学习ARM等其他芯片的使用方法时也一样。下面,我已疑问的形式写在下面:

1.        我通过交叉编译链编译、链接好的程序怎么放到芯片里去?放在什么位置?

2.        芯片加电后从哪里读取第一条指令运行?

3.        交叉编译链编译出的程序地址、下载到芯片里的地址、真正运行时的地址之间到底是什么关系?

4.        ARM芯片有没有中断向量表?在什么位置?

上面这些问题在开发普通PC机程序的时候,你不用考虑,因为这些编译器、操作系统、函数库都帮你做好了,所以你只需要把精力主要放到应用需求上就行了。但现在只有一个ARM芯片,没有任何其他支持,所以,这些问题你就需要理解并掌握。

 

针对第一个问题:LPC2220这款芯片支持在线编程功能,也就是可以通过串口下载你编译好的程序。(其实现方式是芯片内部有固化的loader,这个loader给你提供一些通过串口交互的简单命令。在PC机上一般都有其下载程序的上位机软件)下载到什么位置,要看这款芯片加电后的启动方式。不论从哪个地读取指令并启动,这个地址上应该连接着一种非易失性存储芯片,并且芯片要支持随机读。一般ARM类芯片,支持很多种启动方式,像 norflash启动、nandflash 启动、SD卡启动等等。而且启动方式是可以配置的。我选择的启动方式是norflash启动,这款norflash连接在0X80000000开始的地方。

所以,我们应该把编译、链接好的程序下载到0x8000000开始的地址中去。

 

针对第二个和第四个问题,我们选择了norflash启动方式,那么这款芯片会从物理地址0x8000000地址处开始取出第一条指令并执行。同时,ARM的中断向量表也会指向从0x8000000开始的32字节。见下面的表格:

0x00000000    Reset

0x00000004    Undefined Instruction

0x00000008    Software Interrupt

0x0000000C   Prefetch Abort (instruction fetch memory fault)

0x00000010    Data Abort (data access memory fault)

0x00000014    Reserved *

0x00000018    IRQ

0x0000001C   FIQ

也就是说,当有中断产生时,cpu还是会从上面列出的地址去取指令,只不过这些地址被重新映射到了0x8000000。也即,中断向量表对cpu来说是不变的,但是根据不同的启动方式,将这些逻辑地址重新映射到不同的物理地址上。选择norflash启动模式,就是将中断向量表映射到了物理地址0x8000000。在这种模式下,cpu一上电,还是从0x00000000地址取第一条指令,但是这只是针对cpu来说是逻辑上的0x00000000,其实的真正物理地址是0x80000000。

针对第三个疑问,经过上面的分析我们知道了芯片的启动方式、从哪里取第一条指令。也知道了怎么下载到指定的地址。在说明,下载地址、链接地址、运行地址之间的关系之前,我们先要理解一个问题:Cpu的工作方式。 cpu的工作方式是从指定的地址里取出一条指令进行解析,然后根据解析结果做相应的操作。像前面分析的启动方式,就是从物理地址0x8000000地址开始取指令并执行。我们编译出、链接出来的映像是具有一定格式的,具体格式请参考gnu链接器的说明,这里不做详细说明。我们要知道这样一个事实,我们程序里的code段是顺序存放的,而且我们程序里的跳转指令都要依据我们把第一条指令放在什么地址处。这样说比较晦涩难懂,我们举例说明。如果,我们指定第一条指令的地址为0x8000000,那第二条指令就会接着往后存放。

假设我们有一小段汇编指令。(假设编译出来的是ARM指令,每条指令32bit)

Start:LDR R0, =0xE0028028      @IO2DIR             

                                       LDR R1,= 0x10000000 

                                       STR R1,[R0]                             @设置P2.28为输出

                                       LDR PC, Start

若我们编译、连接好的程序如下表:

 


链接好的地址

指令内容(编译出的是二进制指令编码,我们使用汇编表示)

0x8000000

LDR R0, =0xE0028028

0x8000004

LDR R1,= 0x10000000

0x8000008

STR R1,[R0]              

0x800000c

LDR PC, Start

 

连接好的映像地址其实并不存储在我们编译、链接出的映像里。这些地址在存储器的地址线上体现,这里这样做表格只是为了好理解。假设,我们把上面的映像文件下载到0x8000000开始的地址处。那么,当执行LDR PC, Start这条指令时,其实这条指令相当于LDR PC, 0x8000000,就是把0x8000000这个地址赋给PC,接着cpu就从PC值指向的地址处开始取指令并执行。这样程序又从头开始执行了。实现了我们的想法。但是,如果我们链接时指定的代码段开始地址是0x8000000,但是我们将这段映像转载到了0x8100000开始的地方,并设置cpu从0x8100000开始取指执行。那么,当我们同样执行到LDR PC, Start指令时,还是把0x8000000这个地址赋值给PC,cpu还会从0x8000000取指令执行,这个时候0x8000000地址处并不是我们想要的内容,导致程序不能正常运行。

通过,上面的例子我们知道了,实际上链接器链接时要求地址是这段映像代码段各个指令位置的一个说明。你要保证程序真正运行的地址和编译器链接要求的地址保持一致,要不然一些绝对跳转指令将不能正常执行。从而导致程序出错。

关于,下载地址,当然可以和真正运行时的地址不同,但你要保证当程序真正运行时所在的地址和链接器要求的地址相一致。实现方法是利用一些和地址无关的指令,将代码在真正运行之前复制到链接器所要求的地址。

好了,这些基本问题搞明白了,我们就可以通过向ARM芯片控制外设了。这里当然是要控制led灯了。

 

 

用主控芯片控制外设的一般方法:

1.         看电路原理图,弄明白主控芯片和外设是怎么连接的。

2.         根据电路连接和需求对主控芯片进行设置。

3.         书写相应代码,实现功能。

 

我的ARM芯片和led灯的连接方式,如下图:

 


我们只控制LED1,通过看电路原理图,我们发现LED1和ARM芯片的P2.28引脚相连。

那下面我们看看怎么设置ARM芯片的P2.28引脚就可以了。很显然,我们让P2.28管脚输出0,LED灯会亮,输出1,LED灯会灭。

使用一个ARM芯片的管脚一般分以下两步:

1.         设置这个管脚的功能。(这个管脚可能有好多功能,根据需要设置)

2.         操作这个管脚

关于配置和操作这个管脚的寄存器地址和方法请参见LPC2220的datasheet。

接着就是写程序、makefile文件,然后在linux主机上编译链接,最后下载到norflash运行。

你看到的现象就是LED1会闪烁。

程序内容如下:

 

 

 

@******************************************************************************

@ 文件名 :control_led.s

@ 功   能:利用P2.28控制led灯闪烁

@

@ 作者    :张连聘

@ 创建时间:2014-06-08

@******************************************************************************

.text

.global _start

 

                            @定义程序中使用到的常量                  

                            .equ   IO2DIR  ,0xE0028028       @控制IO0的输入、输出属性寄存器

                            .equ   IO2SET  ,0xE0028024  @IO2输出1控制寄存器

                            .equ   IO2CLR  ,0xE002802C  @IO2输出0控制寄存器

                            .equ   LEDCON  ,0x10000000  @(1<<28)

                           

                           

_start:

 

                             LDR PC,  ResetAddr

                    

ResetAddr:

.word ResetInit

ResetInit:

                            LDR R0,=IO2DIR      @IO2DIR             

                            LDR R1,=LEDCON 

                            STR R1,[R0]                         @设置P2.28为输出

 

MaiLoop:

                            LDR R0,=IO2CLR 

                            LDR R1,=LEDCON

                            STR R1,[R0]        @P2.28为输出0,熄灭led

                            BL DELAYS                     @调用延时程序

                           

                            LDR R0,=IO2SET

                            LDR R1,=LEDCON                      

                            STR R1,[R0]        @P2.28为输出1,点亮led

                            BL DELAYS                     @调用延时程序

                  

                           

                            B MaiLoop

                           

                           

@******************************************************************************

@ 名   称:DELAYS

@ 功   能:软件延时

@ 入口参数:无

@ 出口参数:无

@ 占用资源:R7

@******************************************************************************

DELAYS:  

                            MOV                  R7,#0x00002000               @延时参数

DELAYS_L1:      SUBS         R7,R7,#1                                  @ R7 = R7-1

                            BNE           DELAYS_L1             @判断R7-1结果是否为0,若不为0则跳转

                            MOV                  PC,LR                                    @返回               

                           

程序里需要说明的:

关于gnu arm汇编请参考相关书籍。这里只说明一些和这里相关的内容。

.text 定义一个代码段的开始。.global _start定义一个全局的标号,一般供链接器链接多个.o文件时使用。

.equ定义一个常量。@后面的内容为注释内容。

                             LDR PC,  ResetAddr

ResetAddr:

.word ResetInit

ResetInit:

这段代码需要说明一下,有些朋友可能有这样的疑问,为什么第一条指针需要跳转指令,还有为什么不能用LDR PC,  ResetInit。

关于这个问题,我们上面分析了,从0x8000000开始的32byte都是中断(异常)向量表,每个中断向量只有四个字节的空间,肯定不能放下中断处理程序,所以放置一条跳转指令。上电后,默认执行Reset,这时候对CPU来说,它认为PC地址为0x00000000,只不过这个地址被其他硬件设备重新映射到了0X80000000地址上了,但cpu并不知道这个硬件设备的存在。而直接LDR PC,  ResetInit只能实现前后32       M空间的跳转,显然不能满足我们的意愿。通过上面的方法定义的ResetAddr,然后再用LDR PC,  ResetAddr可以实现4G范围内跳转。

其他内容都很简单了,那条指令不明白可以查阅ARM指令集。

下面看makefile文件:

control_led.bin:control_led.s

         arm-linux-gcc -g -c -o control_led.o control_led.s

         arm-linux-ld -Ttext  0x80000000 -g  control_led.o -o control_led_elf

         arm-linux-objcopy -O binary -S control_led_elf  control_led.bin

clean:

         rm -f control_led.bin control_led_elf *.o

 

makefile的基本语法和格式,我就不多说了。

嵌入式linux下的arm-linux系列工具,默认链接出来的是在linux内核支持下的映像文件,是ELF格式的映像文件。而我们没有操作系统的支持,所以,我们要利用二进制工具将ELF格式的映像文件转换成纯二进制指令格式映像文件。

我们上面说的链接器链接地址的概念,在arm-linux-ld 的体现就是 arm-linux-ld -Ttext  0x80000000 -g  control_led.o -o control_led_elf,其中-Ttext选项就是指代码段的起始链接地址。这里将地址连接到0x80000000开始的地方。

好了,关于ARM芯片的启动,链接地址,下载地址,执行地址以及第一个点亮LED灯的程序,makefile讲解就到这里吧。

我把这个试验中所涉及到的源码和word文档都打包上传到csdn的下载频道:

下载地址:ARM芯片基础实验之GPIO操作


推荐阅读

史海拾趣

Eurosil Electronics Ltd公司的发展小趣事

作为一家有社会责任感的企业,Eurosil始终关注社会公益事业。公司积极参与各种公益活动,如捐赠教育设施、支持贫困地区发展等。通过这些活动,Eurosil不仅回馈了社会,也提升了企业的社会形象和品牌价值。同时,公司还鼓励员工参与志愿服务活动,培养员工的公益意识和社会责任感。

Fair Rite公司的发展小趣事

随着全球进入数字化、网络化、智能化时代,电子元器件市场发生了深刻的变化。Fair Rite积极应对市场变化,通过技术创新和产品研发,不断推出适应新需求的产品。例如,公司针对EMF/EMI干扰较严重或容易出现导漏流问题的情况,推出了EMI抑制铁芯线圈(SM-BL系列),帮助客户节省PCB空间并降低干扰信号。同时,Fair Rite还注重满足客户的性价比需求,为每个新产品进行定制化设计和质量验证(遵循ISO9001:2008标准),确保其在不同应用场景下稳定可靠。

这些故事展示了Fair Rite在电子行业中不断发展壮大的历程。通过不断创新、拓展市场和提升品质,Fair Rite已经成为电子行业中的佼佼者之一。

Eagle-Picher公司的发展小趣事

进入21世纪后,Eagle-Picher公司迎来了新的发展机遇。2017年,OMGroup斥资1.7亿美元收购了Eagle-Picher公司,这一举措为Eagle-Picher注入了新的资金和资源。在新的资本支持下,Eagle-Picher加大了在电池技术领域的研发投入,不断推出新的产品和解决方案。同时,公司也积极拓展国际市场,与全球多家知名企业建立了合作关系。这些新的发展机遇为Eagle-Picher的未来发展奠定了坚实的基础。

Festo公司的发展小趣事

作为一家以创新驱动的公司,Festo始终将创新作为公司发展的核心动力。近年来,Festo在研发领域的投资不断增加,推出了一系列具有颠覆性的新产品和解决方案。同时,Festo还积极响应全球可持续发展的趋势,致力于减少碳排放和提高能源效率。通过引入先进的能效措施和扩大光伏(PV)的使用,Festo成功实现了碳中和的目标,为电子行业的可持续发展做出了积极的贡献。

赛微(Cellwise)公司的发展小趣事

随着公司业务的不断发展和技术实力的不断提升,赛微开始积极拓展国际市场。公司坚持“以人为本”的理念,积极引进国际化人才,加强与国际知名企业的合作与交流。通过多年的努力,赛微已经成功打入国际市场,与众多国际知名企业建立了长期稳定的合作关系,实现了业务的国际化拓展。

Hengstler GmbH公司的发展小趣事

作为一家有着高度社会责任感的企业,赛微始终关注社会发展和环境保护。公司积极参与公益事业和慈善活动,为社会做出积极贡献。同时,公司还注重环境保护和资源节约,通过引进先进的生产设备和工艺技术,降低生产过程中的能耗和排放,实现绿色生产和可持续发展。

请注意,以上故事框架仅为概述,具体细节和数据可能需要根据实际情况进行调整和完善。

问答坊 | AI 解惑

ZT:一个技术人员悟到的管理秘诀

我在原来的公司做的时候,就注意观察公司在管理上的成功和失败的经验教训,并在网络上找很多关于管理的文章。     管理上有很多故事,让我领悟到管理就是设计一个合理的机制。          故事之一:分粥          分粥的故 ...…

查看全部问答>

STM32菜鸟程序!

拿到STM32开发板三天了!写了几个程序!程序简单但对入门还是有点帮助的!我自己下次调试成功了的! 上传供一起刚入门的朋友分享下! 第一天学习:MDK工程建立和GPIO 第二天学习:RCC和按键程序 第三天学习:EXTI程序(一个中断按键程序) 每 ...…

查看全部问答>

stm32f103ze的I2C不行

程序代码如下: 初始化后寄存器如下:起始地址是0x40005800 00000001     00000024  00004000 00000000 00000000    00000000   00000000 00000708 00000035 我不喜欢用提供的函数, ...…

查看全部问答>

电风扇模拟控制系统设计 求助 !!真的不会啊!!

电风扇模拟控制系统设计1.用4个LED显示电风扇的工作状态(1,2,3,4四档风力),显示风类:“自然风”、 “常风”和“睡眠风”。2.设计 “自然风”、 “常风”和“睡眠风” 三个风类键用于设置风类; 设计一个“摇头” 键用于控制电机摇头。 &nb ...…

查看全部问答>

新手提问--想学ARM但是不知道先学ARM7或ARM9好还是先学STM32好--有51和avr经验

想学ARM但是不知道先学ARM7或ARM9好还是先学STM32好。我现在一直在用的是51和AVR。…

查看全部问答>

趣味实验

家里有一个上小学的女孩子,有一天兴冲冲的跟我说,要做一个伟大的实验给我看,并且不许我偷看。看着她神秘的样子,我想小孩子搞什么名堂。大概过了半个多小时,她跑进来把我屋里面的灯给关了,然后又匆匆忙忙的跑出去。不一会儿,她小心翼翼的端着 ...…

查看全部问答>

液晶显示图片

在执行以下函数的时候 用仿真发现 i 只能加到14,然后就归零(也就是程序退不出第一个for循环),为什么?按道理说不该这样啊 显示上半屏函数: Write_Command(0x34); //  *******显示上半屏内容设置    for(i=0;i…

查看全部问答>

请大家帮忙看一看这是什么品牌的电感呢

大家帮看看这是什么品牌的电感呢?非常感谢! …

查看全部问答>

关于DM8127中的codec engine的接口问题

完成ARM对接口的调用和初始化,和DSP之间数据的传输和处理。 修改buildutils/platform.xs文件来确定codec对应的平台信息。 Var allDevices=new Array(); allDevices[‘OMAP3530’]={platforms: ti.platforms.evm3530 请问我选用的OMAP3530是对 ...…

查看全部问答>