历史上的今天
返回首页

历史上的今天

今天是:2025年03月11日(星期二)

正在发生

2020年03月11日 | 开发板学习Day7-第一个ARM裸板程序及引申

2020-03-11 来源:eefocus

今天我们来写第一个ARM裸板程序-点亮LED 

我们怎样去点亮一个LED呢? 共分为三步 。


看原理图,确定控制LED的引脚;

看主芯片的芯片手册,确定如何设置控制这个引脚;

写具体的程序来实现;

第001节:硬件知识-LED原理图

点亮LED需要通电源,同时为了保护LED,加个电阻减小电流。 控制LED灯的亮灭,可以手动开关LED,但在电子系统中,不可能让人来控制开关,而是通过编程,利用芯片的引脚去控制开关。 +


如图所示: 

这里写图片描述

LED的驱动方式,常见的有四种:


方式1:使用引脚输出3.3V点亮LED,输出0V熄灭LED。 

方式2:使用引脚拉低到0V点亮LED,输出3.3V熄灭LED。


有的芯片为了省电等原因,其引脚驱动能力不足,这时可以使用三极管驱动。 

方式3:使用引脚输出1.2V点亮LED,输出0V熄灭LED。 

方式4:使用引脚输出0V点亮LED,输出1.2V熄灭LED。

这里写图片描述

由此,主芯片引脚输出高电平/低电平,即可改变LED状态,而无需关注GPIO引脚输出的是3.3V还是1.2V。 所以简称输出1或0:


逻辑1–>高电平

逻辑0–>低电平

第002节:硬件知识-S3C2440启动流程与GPIO操作


查看原理图: 

这里写图片描述 

由图明显可以看出当引脚nLED 1/2/4 输出为低电平时,二极管导通发光。(n表示低电平有效) 


在原理图中,同名的Net表示是连在一起的。 因此我们要找到nLED 1同名net. 

这里写图片描述

由图可知,三个LED灯nLED 1/2/4 分别连接GPF 4/5/6


查看S3C2440芯片手册: 

这里写图片描述

由图可知,GPF输入输出端口共有8组端口,若以nLED_1为例,那我们要操作的就是第四组(即GPF4)。


怎么样令GPF4输出1或0? 

1. 配置为输出引脚; 

2. 设置状态;


首先查看芯片手册:找到GPF的配置寄存器GPFCON。 

这里写图片描述

这里写图片描述

由图可知, 

设置GPFCON[9:8]=0b01,即将GPF4配置为输出; 

设置GPFDAT[4]=1或者0,即输出高电平或低电平;


S3C2440框架: 

这里写图片描述

S3C2440启动流程:


- Nor启动: 

Nor Flash的基地址为0,片内RAM地址为0x4000 0000; 

CPU读出Nor上第1个指令(前4字节),然后执行; 

CPU继续读出其它指令执行。


- Nand启动: 

片内4k RAM基地址为0,Nor Flash不可访问; 

2440硬件把Nand前4K内容复制到片内的RAM,然后CPU从0地址取出第1条指令执行。


第003节:编写第1个程序点亮LED

在开始写第1个程序前,先了解一些概念。 

2440是一个SOC,它里面的CPU有R1、R2、R3……等 寄存器(可以直接通过寄存器名字来访问); 

它里面的GPIO控制器也有很多寄存器,如 GPFCON、GPFDAT。 (需要通过地址访问) 

这两个寄存器是有差异的,在写代码的时候,CPU里面的寄存器可以直接访问,其它的寄存器要以地址进行访问。


然后我们根据芯片手册中的信息可知GPFCON寄存器的地址。


把GPF4配置为输出,需要把GPFCON的第9位和第8位为0b01,即将01 0000 0000=256=0x100写入GPFCON这个寄存器,而这个寄存器只能通过地址来访问,即写到0x5600 0050上;


把GPF4输出1,需要把GPFDAT第4位,即0x10写到GPFDAT寄存器中,GPFDAT寄存器也只能通过地址来访问,其地址为0x5600 0054上; 把GPF4输出0,需要把0x00写到地址0x5600 0054上; 这里的写法会破坏寄存器的其它位,其它位是控制其它引脚的,为了让第一个裸板程序尽可能的简单,才简单粗暴的这样处理。


写程序需要用到几条汇编代码:


①LDR (load):读寄存器 

举例:LDR R0,[R1] 

假设R1的值是x,读取以x为首地址的4个连续内存单元的数据(4字节),保存到R0中;


②STR (store):写寄存器 

举例:STR R0,[R1] 

假设R1的值是x,把R0的值写到以x为首地址的4个连续内存单元(4字节);


③B 跳转


④MOV (move)移动,赋值 举例1:MOV R0,R1 把R1的值赋值给R0; 

举例2:MOV R0,#0x100 把0x100赋值给R0,即R0=0x100;


⑤LDR 

举例:LDR R0,=0x12345678 这是一条伪指令,即实际中并不存在这个指令,他会被拆分成几个真正的ARM指令,实现一样的效果。 最后结果是R0=0x12345678。


为什么会引入伪指令?


在ARM的32位指令中,有些字节表示指令,有些字节表示数据,因此表示数据的没有32位,不能表示一个32位的任意值,只能表示一个较小的简单值,这个简单值称为立即数。引入伪指令后,利用LDR可以为R0赋任意大小值,编译器会自动拆分成真正的的指令,实现目的。 

有了前面5个汇编指令的基础,我们就可以写代码了。


第一个程序只能是汇编,以前你们可能写过单片机程序,一上来就写main()函数,那是编译器帮你封装好了。


因此,我们写除了第一个控制led1亮的程序:


/*点亮led1*/

.text

.global _start

_start:

/*将GPF4设置为输出端口:将GPFCON配置寄存器中的第9、8位设为0、1即可,即把0x100送入

GPFCON寄存器的地址

0x56000050中*/

ldr r0,0x56000050

ldr r1,0x100

str r1,[r0]


/*将GPF4输出状态设置为输出低电平0:将GPFDAT寄存器第4位设置输出为低电平0,也就是将

0x00送入GPFDAT寄存器的地址0向6000054中去*/

ldr r0,0x56000054

ldr r1,0x00

str r1,[r0]


/*由于我们不知道在程序代码执行完毕之后,NOR FLASH或NAND FLASH的内容未知,

所以要执行死循环halt*/


halt:

    b halt


将代码上传到服务器, 先编译:


arm-linux-gcc -c -o led_on.o led_on.s ;


再链接:


arm-linux-ld -Ttext 0 led_on.o -o led_on.elf ;


生成bin文件:


arm-linux-objcopy -O binary -S led_on.elf led_on.bin ;


以上的命令,要是我们每次都输入会容易输错,因此我们把他们写到一个文件里,这个文件就叫Makefile. 本次所需的Makefile如下:


all:

    arm-linux-gcc -c -o led_on.o led_on.S

    arm-linux-ld -Ttext 0 led_on.o -o led_on.elf

    arm-linux-objcopy -O binary -S led_on.elf led_on.bin

clean:

    rm *.bin *.o *.elf


以后只需要 使用 make 命令进行编译, make clean 命令进行清理。 

最后烧写到开发板上,即可看到只有一个LED亮,符合我们预期。 

这里写图片描述

第004节_汇编与机器码

前面介绍过伪指令,伪指令是实际不存在的ARM命令,编译器在编译时转换成存在的ARM指令。 

我们可以通过反汇编来查看在实际执行过程中的汇编指令。


在前面的Makefile中加上:


arm-linux-objdump -D led_on.elf > led_on.dis


上传服务器,编译。


生成的led_on.dis就是反汇编文件。led_on.dis如下: 

这里写图片描述

结合我们的程序代码来分析一下:图中第一列为地址,第二列为机器码、第三列为汇编代码。


ldr r0,0x56000050

ldr r1,0x100

str r1,[r0]

/***************/

ldr r0,0x56000054

ldr r1,0x00

str r1,[r0]


为了便于分析,我们先来认识一下CPU内部的寄存器: 

cpu内部共有16个寄存器,其中我们经常用到的有三个:R13R14R15.


R13: 又名sp,即Stack Pointer,栈指针,保存的始终是栈顶的地址 

R14: 又名lr, 即Link Register,保存的是返回地址,用于保护断点和现场。 

R15:又名pc,即Program Pointer,保存的是cpu将要执行的下一条指令的地址。由于CPU执行的流水线工作方式,也就是说为了提高cpu执行指令的效率,在CPU执行‘存放在地址A处的指令’的同时,CPU还会对下一条指令(存放在地址为A+4的指令)进行译码,并将之后的第二条指令(存放在地址为A+8的指令)的地址(也就是A+8)送入PC中。


首先我们对反汇编后的第一条代码进行解析:将以pc+20为首地址的四个字节的数据送到R0寄存器中。对于第一条指令,它的地址为0,所以pc=0+8=8,那么pc+20=28=0x1c,反汇编程序的下半部分就是即为对应地址中的数据,所以显然0x1c处的数据为0x56000050,那么这条指令执行完,R0的值就变成了0x56000050.第一条指令的功能就是将GPFCON的地址送入寄存器R0中。


我们接着对第二条指令进行解析:第二条指令将伪指令ldr转换成了mov指令,其功能为将立即数256,也就是0x100送入了寄存器R1中.


第三条指令:由于R0=0x56000050,R1=0x100,第三条指令就是把0x100送入以0x56000050为首地址的四个字节中。就是将GPFCON寄存器的值设为0x100,这样GPF4就被设置成了输出端口。


第四条指令:由于其地址为c,所以,pc=c+8=0x14,则pc+12=0x20。因此此指令便将首地址为0x20的四个字节的数据放入寄存器R0中。所以指令执行后R0的值为0x56000054.


第五条指令:将立即数0x00放入寄存器R1中。


第六条指令:R0=0x00,R1=0x56000054,所以此指令便将0x00放入以0x56000054为首地址的四个字节中,也就是GPFDAT寄存器中。


分析完汇编指令,我们完成一个作业:通过修改汇编代码来点亮led2。 

通过查看芯片手册,我们知道要想点亮led2,首先需要将GPFCON寄存器中的第11、10位设置为01,GPFCON寄存器设置为0x400;然后,经GPFDAT寄存器内容设置为0x00。代码如下:


/*点亮led1*/

.text

.global _start

_start

/*将GPF5设置为输出端口:将GPFCON配置寄存器中的第11、10位设为0、1即可,

即把0x400送入GPFCON寄存器的地址0x56000050中*/

ldr r0,0x56000050

ldr r1,0x140

str r1,[r0]


/*将GPF4输出状态设置为输出低电平0:将GPFDAT寄存器第5位设置输出为低电平0,

也就是将0x00送入GPFDAT寄存器的地址0向6000054中去*/

ldr r0,0x56000054

ldr r1,0x00

str r1,[r0]


/*由于我们不知道在程序代码执行完毕之后,NOR FLASH或NAND FLASH的内容

未知,所以要执行死循环halt*/


halt:

    b halt


接下来解析一下机器码: 

这里写图片描述

上图是四字节机器码的格式,我们先不管其他的只看低16位: 

Rd表示的寄存器,4位可以表示16个寄存器; 

Shift_operand表示操作数,其中12位中的高4位表示rotate_4位,低8位表示immed_8位。表示的立即数为immed_8右移2*rotate_4位后的数值。 

这里写图片描述

如图:第一行机器代码为e5 9f 00 14,其中Rd=0,所以寄存器为R0;rotate_4为0,14表示源操作数中的立即数为0x14,所以立即数为0x14右移2*rotate_4位,结果还是0x14,十进制为20。


第二行中的机器代码为e3 a0 1c 01;其中Rd=1,所以寄存器为R1;rotate_4为c;immed_8为01;所以立即数为0x01右移2*rotate=24位,结果为0x100。


分析完机器代码,我们来修改一下机器代码来修改程序点亮led2: 

将第二行机器代码中的0x100修改为0x400;也就是将rotate_4由c改为b即可。因为0x100要由0x01右移24位,而0x400由0x10右移22位。 

这里写图片描述

第005节编程知识进制(略)

第006节编程知识字节序_位操作

字节序: 

假设int a = 0x12345678; 

前面说了16进制每位是4个字节,在内存中,是以8个字节作为1byte进行存储的,因此0x12345678中每两位作为1byte,其中0x78是低位,0x12是高位。 

在内存中的存储方式有两种: 

这里写图片描述

0x12345678的低位(0x78)存在低地址,即方式1,叫做小字节序(Little endian); 

0x12345678的高位(0x12)存在低地址,即方式2,叫做大字节序(Big endian); 

一般的arm芯片都是小字节序,对于2440可以设置某个寄存器,让整个系统使用大字节序或小字节序,它默认使用小字节序。


置位- 把a的bit7、8置位(变为1) 

int a = 0x123; int b = a|(1<<7)|(1<


清位 -把a的bit7、8清位(变为0) 

int a = 0x123; int b = (a& ~(1<<7))&(~(1<

置位和清位在后面寄存器的操作中,会经常使用。


第007节_编写C程序控制LED

接下来,我们来编写C程序来控制led。


我们写出了main函数,以下有几个问题需要我们仔细考虑一下: 

a, 谁来调用它? 

b. main函数中变量保存在内存中, 这个内存地址是多少?


答: 我们还需要写一个汇编代码, 给main函数设置内存, 调用main函数


led.c源码:


int main()

{

    unsigned int *pGPFCON = (unsigned int *)0x56000050;

    unsigned int *pGPFDAT = (unsigned int *)0x56000054;


    /*配置GPF4为输出引脚*/

    *pGPFCON = 0x100;


    /*配置GPF4输出0*/

    *pGPFDAT = 0;


    return 0;

}


start.S源码:


.text

.global _start

_start:

    /*设置内存:sp栈*/

    ldr sp,=4096 /*nand启动*/

//  ldr sp, =0x40000000 /*nor启动*/


    /*调用main*/

    bl main

halt:

    b halt


Makefile源码:


all:

    arm-linux-gcc -c -o led.o led.c

    arm-linux-gcc -c -o start.o start.S

    arm-linux-ld -Ttext 0 start.o led.o -o led.elf

    arm-linux-objcopy -O binary -S led.elf led.bin

    arm-linux-objdump -D led.elf > led.dis

clean:

    rm *.bin *.o *.elf *.dis


最后将上面三个文件放入Ubuntu主机编译,然后烧写到开发板即可


第008节_几条汇编指令_bl_add_sub_ldm_stm

1,add


add r0,r1,#4          

1

功能:r0=r1+4


2,sub


sub r0,r1,#4

1

功能:r0=r1-4


3,bl (branch and link)


bl xxx

1

功能:第一步,将返回地址保存到 lr (r14)寄存器中;第二步,跳转到xxx


4,ldm 和 stm


ldmia  sp, {fp,sp,pc}

1

功能:读内存,将值写入到多个寄存器中。


stmdb  sp!, {fp,ip,lr,pc}

1

功能:把多个寄存器的内容写入到内存中。


我们把两条指令放到一起来讲: 

指令有4种前缀,分别为 

ia:Increment After过后增加 

ib:Increment Before预先增加 

da:Increment Before过后减少 

db:Decrement Before预先减少


并且,对于两条指令而言都是高地址内存写入高编号寄存器、高编号寄存器写入高编号地址。


针对以上两条举例来讲解:

这里写图片描述 

其中,sp!表示sp的最终值为修改后的值。

这里写图片描述 

其中,sp表示sp的值保持不变。


009节_解析C程序的内部机制

上面我们共写了三段代码,下面进行分析。


1,start.S 

start.S中,实际上做了两件事情; 

-设置栈 

-调用了main函数,并把返回地址存入lr寄存器。


2,led.c 

led.c中,实际上也做了一下三件事情: 

-定义两个局部变量 

-设置变量 

-return 0


由此我们可能要问一下几个问题: 

1)为何要设置栈? 

因为C函数要用。


2)怎么使用栈? 

一是存放局部变量,二是存放lr等寄存器的值。


3)调用者如何传参数给被调用者?


4)被调用者如何传返回值给调用者?


5)怎么从栈中恢复那些寄存器?


接下来,以上节编写的代码为例来分析整个过程:


Disassembly of section .text:


00000000

   0:   e3a0da01    mov sp, #4096   ; 0x1000`

   4:   eb000000    bl  c


00000008

   8:   eafffffe    b   8


0000000c

   c:   e1a0c00d    mov ip, sp

  10:   e92dd800    stmdb   sp!, {fp, ip, lr, pc}

  14:   e24cb004    sub fp, ip, #4  ; 0x4

  18:   e24dd008    sub sp, sp, #8  ; 0x8

  1c:   e3a03456    mov r3, #1442840576 ; 0x56000000

  20:   e2833050    add r3, r3, #80 ; 0x50

  24:   e50b3010    str r3, [fp, #-16]

  28:   e3a03456    mov r3, #1442840576 ; 0x56000000

  2c:   e2833054    add r3, r3, #84 ; 0x54

  30:   e50b3014    str r3, [fp, #-20]

  34:   e51b2010    ldr r2, [fp, #-16]

  38:   e3a03c01    mov r3, #256    ; 0x100

  3c:   e5823000    str r3, [r2]

  40:   e51b2014    ldr r2, [fp, #-20]

  44:   e3a03000    mov r3, #0  ; 0x0

  48:   e5823000    str r3, [r2]

  4c:   e3a03000    mov r3, #0  ; 0x0

  50:   e1a00003    mov r0, r3

  54:   e24bd00c    sub sp, fp, #12 ; 0xc

  58:   e89da800    ldmia   sp, {fp, sp, pc}

Disassembly of section .comment:


00000000

   0:   43434700    cmpmi   r3, #0  ; 0x0

   4:   4728203a    undefined

   8:   2029554e    eorcs   r5, r9, lr, asr #10

   c:   2e342e33    mrccs   14, 1, r2, cr4, cr3, {1}

  10:   Address 0x10 is out of bounds.


上面是我们将led.bin文件进行反汇编后的文件。结合图示来分析; 

机器代码会被复制到cpu中的4K的SDRAM中,如下图所示: 

片内4KRAM: 

这里写图片描述

我们根据汇编指令行号来一行一行的分析:


00000000

   0:   e3a0da01    mov sp, #4096   ; 0x1000    //sp=4096

推荐阅读

史海拾趣

CONTRINEX公司的发展小趣事

面对海洋环境的独特挑战,Contrinex推出了海洋系列传感器。这些传感器采用全金属技术,外壳材质为V4A/AISI 316L不锈钢,防护等级高达IP68/IP69K,能够抵御恶劣的海洋环境。同时,该系列传感器还具备卓越的EMC防护性能,能够满足各种海洋应用需求。这一创新产品的推出,进一步巩固了Contrinex在传感器行业的领先地位。

Array Microsystems Inc公司的发展小趣事

在取得初步成功的基础上,Array Microsystems Inc深知技术创新是企业持续发展的核心动力。因此,公司加大了对研发的投入力度,不断推出新的阵列传感器产品和技术。这些新产品不仅具有更高的性能和更低的成本,还满足了市场不断变化的需求。通过持续的创新和研发,Array Microsystems Inc在电子行业中保持了领先地位。

Amphenol Aerospace公司的发展小趣事

为了支持公司的快速发展和技术创新,Array Microsystems Inc高度重视人才引进和团队建设。公司积极招聘业内优秀的技术人才和管理人才,为他们提供良好的工作环境和福利待遇。同时,公司还注重员工的培训和晋升机制,鼓励员工不断学习和成长。这些措施使得Array Microsystems Inc拥有了一支高素质、专业化的团队,为公司的发展提供了有力的人才保障。

西安航天民芯公司的发展小趣事

随着新能源汽车市场的蓬勃发展,西安航天民芯敏锐地捕捉到了这一机遇。公司投入大量资源进行新能源汽车BMS管理芯片的研发,并成功推出了国内首款新能源汽车专用BMS管理芯片。这一创新产品填补了国内空白,为公司在新能源汽车领域赢得了先机。

Crystalfontz America Inc公司的发展小趣事

为了保证产品质量和客户满意度,Crystalfontz America Inc公司建立了完善的质量管理体系。公司从原材料采购、生产制造到产品出厂等各个环节都严格把控质量,确保产品符合相关标准和客户要求。同时,公司还不断优化生产流程和管理制度,提高生产效率和产品质量稳定性。这些措施为公司赢得了良好的市场声誉和客户口碑。

Fascomp公司的发展小趣事

Fascomp公司起源于一间小型的电子产品研发工作室。创始人李明和他的团队在资金紧张、技术条件有限的情况下,坚持自主研发一款高性能的芯片。经过无数次的失败和试验,他们最终成功开发出了一款具有竞争力的芯片,赢得了市场的初步认可。这个过程中,团队成员的坚持和对技术的执着追求成为了公司后续发展的基石。

问答坊 | AI 解惑

诚聘欲在桂林工作的嵌入式软件开发、测试工程师 待遇好

2007年秋桂林市核子软件有限公司招聘信息 2007年桂林市核子软件开发有限公司招聘信息公司简介: 桂林市核子软件开发有限公司主要从事于嵌入式软件及网络新技术的研究和开发,主要由深圳市双赢伟业科技有限公司投资兴办。公司主要客户是双赢伟 ...…

查看全部问答>

电子技术新手资料

电子技术新手资料,适合入门者…

查看全部问答>

AVR入门必读《实战AVR单片机C语言》

自学AVR单片机少不了动手实践!…

查看全部问答>

红外可视距离与红外距离之间的区别

红外距离指的是红外光所达到的距离,从几米到几百米,甚至是数公里。而夜间可视距离是通过监视器所能够看到的清晰有效最大距离,是由红外灯的发光距离、摄像机的感红外程度、现场反射红外情况、供电电源的质量及镜头的匹配情况来决定的。它们之间相 ...…

查看全部问答>

向你请教 WINCE + 2410 + I2C + AT24C02 的问题!

我想用 2410 开发板,WinCE 4.2 操作系统,I2C 总线读写 AT24C02 EEPROM。 我已经看懂了 2410 的 I2C 寄存器和 AT24C02 的读写方法;也看懂了 ADS 下的纯 C 语言程序。 因为我以前一直做桌面的程序,底子比较差;就是对 WINCE 下的 I2C 流驱动没 ...…

查看全部问答>

cypress的gpif波形问题,求教。

如题,我用cypress的芯片控制nand flash,型号是k9f1g08u0a,gpif波形图如下,现在我换了一个flash,时序上不一样,没弄过这些,不知道这些图形都代表什么,盼高手指点。 d:\\flash\\gpif\\1.jpg d:\\flash\\gpif\\2.jpg d:\\flash\\gpif\\3.jpg ...…

查看全部问答>

电路里三极管怎么分析他们的作用

本信息来自合作QQ群:arm linux HTTP://WWW.1YQ.COM (49900581) 群管理员在坛子里的ID:wangkj…

查看全部问答>

大家觉得今年的控制题目会怎么出?

本帖最后由 paulhyde 于 2014-9-15 09:46 编辑 自由谈论,希望大家发挥想象各抒己见  …

查看全部问答>

正负5V电源输出

今天,我用L7805CV和L7905CV做了个正负5V电源,正端输出是5.01V,负端输出是负6.26V,不是负5V左右,开始的时候,我以为可能是L7905CV芯片的问题,就换了一块L7905CV芯片,结果负端还是负5.89V,另外,我测试了下L7805CV和L7905CV的输入端分别是正 ...…

查看全部问答>

求一份低频信号分析仪设计 急!!!求教高手!!!

本帖最后由 paulhyde 于 2014-9-15 03:51 编辑 做一个低频信号分析仪要求 基本要求 在输入交流信号电压峰峰值Vpp为500m V~5V,频率范围为50Hz~50kHz条件下,完成以下参数测试; 正弦波,三角波和方波频率测试,测量误差小于1%; 正弦波 ...…

查看全部问答>