参考自韦东山《嵌入式linux应用开发》第五章
1.关于GPIO硬件
根据笔者在单片机上付出的带价。所谓的ARM9实际上是把51单片机各种直接用代码操作的方式封装在一起。用户可以通过ARM上的寄存器操作实现响应硬件操作,这是对开发者透明的。51单片机基本要代码一行一行实现操作,印象中的I2C协议就是如此。晚点回忆单片机再写好了,这里不影响理解。
GPIO,就是通用IO端口,说白就是个端口。一个端口有什么用啊!?说白了就是输出和读取作用了,既然计算机是二进制的,那肯定就是两个状态了。高电平/低电平(卧槽,数字电路!噩梦啊)。所以GPIO在硬件上就是做这四件事情的,(输出/输入)X(高/低电平)。
那么想想应该要几个寄存器操作他们啊。显然本来输入输出可以用不同端口,然而万恶资本主义家们为了节省成本,压榨设计人员剩余价值。当然要一个端口同时做这四种事情了。(我吹的)
那一个端口起码要有两个寄存器来控制了,一个控制输出还是输入,一个控制高低电平。当然输入是不需要控制高低电平的。但当然还是可以读寄存器的数据来查看高低电平的。有点语无伦次直接看别人怎么搞的吧。
先看GPF组端口。(实际上是从GPA组端口一路排下来的。作用大同小异,有1输入和2输出,还有一个是3自己独特用法,还有一个4保留。具体一个组的一个端口是由两位BIT控制,00、01、10、11.就是代表上面四个用法)
1.1GPFCON、GPFDAT、GPFUP
1.11GPFUP(不是重点)
这里居然多出了一个寄存器,GPFUP。查了一下,实际上用于引脚上拉电阻。什么叫上拉电阻,说白了就是确定一个高阻态的东西保持恒定电压。(什么是高阻态?应该就是你拿一根导线接入空中,然后你不能像炮姐一样发出电磁炮,导致端口另一端是个诡异的状态。我瞎掰的。操作GPFUP寄存器为0时候就相当于在下图B点接个高电压。)
为什么要保持一个恒定电压。撇开B点,比如下面A点在端口用于读取时候究竟是高电平还是低电平。因为A点另一端接的相当于是空气,所以高阻,高阻不能确定电平。但是B点如果是高电平,明显,开关闭合就是低电平,开关打开就是高电平。
一般而言这东西是不用管的,因为默认就是0,就是接上拉电阻的意思。至少这里不涉及,后面我也没用过不接上啦电阻的状态。计算机是二进制的,高阻态的意义应该就是告诉我们,在高阻态基础上加上拉电阻可以消除不确定因素,就是高低电平的意思。
1.12GPFCON
可以把最开头那个表格拿来看,那是叫做芯片手册的东西。
GPFCON就是用于用来设定端口是输入还是输出的,至于为什么能实现,这个在开发上透明,不用管。
有没有发现,CPFCON可以设置为EINT,中断的意思,以后说。
CON就是Configure配置的意思了
这寄存器怎么访问?
看到Address,0x56000050。在看到第二个表格,BIT是0~15.说明有16位。
每两位控制一个GPF端口。说明GPF端口有GPF0~GPF7总共8条对外的端口(线)。
即在0x56000050这个地址,写入16位的二进制数字,就可以操作一共8条端口的4个用法。
1.13GPFDAT
看到DAT显然就是数据的意思了。
作用两个,一个是在GPFCON输出状态时候,往这个寄存器里写,可以输出高低电平。
另一个则是在GPFCON输入状态时候,读取这个寄存器,可以知道究竟输入是高电平还是低电平。
看那鸡肠,翻译过来就是。
输出时候,写1高电平,写0低电平。
输入时候,读到1或0就是输入高电平或低电平。
GPFUP是不用管的。
补充:CPU操作寄存器实际上是对于寄存器所在的地址写数据。读同理。
那有的人会问,内存不是正是地址吗?确实是的
但是在硬件上内存地址跟寄存器的地址是在不同地方的。但都统一硬件编址。
如S3C2440对于外设(内存、NOR FLASH,网卡等等)是用地址线寻址的。一共27根地址线+8根片选就是(2^27)*8=1G的地址空间。这1G空间是用来访问外设的。0x00000000~0x4000_0000正好1G用于给外设地址。其中内存在0x3000_0000开始,下节再说。
但是S3C2440是一个32位CPU,所以理论可以寻址4G,那么剩下的地址就用来分配给跟CPU封装一块的寄存器了。比如上面的CPFXX就是寄存器地址了,寄存器是从0x4800_0000~0x5FFF_FFFF。0x4000_0000~0x4800_0000地址就不知道是什么了,先别管了。没准是保留了。
2.接下来看看硬件结构
如图,看到第一副,点亮LED1,显然是要nLED_1处是个低电平,导通二极管。那么nLED_1就应该是输出低电平(不是输入,不能理解为电流流进去云云。)
而nLED_1的端口是GPF4。
上面一直说的GPF4.
那么代码就是把GPF4设置为输出低电平即可。GPFUP默认就是接入上拉电阻,所以不用管GPFUP。
顺便说一句,那个Externa截了一半的就是CPU,LED相当于接入引脚的灯了。
3.代码
3.1汇编代码
首先说说这汇编代码的是怎样滚到内存里面的。
我们知道,一份代码是被编译器加工,最后成为一个二进制文件,载入内存执行的。
所以代码怎么处理就是编译器的事情了。
我们这代码是运行在开发板的内存中的,显然是不能用普通的编译器的。普通编译器是编译和运行都是一台电脑上的。
比如这里使用arm-linux-gcc。而我另一个地方则使用i386-linux-gnu。他们有个共同点就是都在linux平台上编译,运行在arm或者i386架构上。
我们这里只讨论,arm-linux-gcc。
所以是用交叉编译器来编译这条汇编的。(交叉编译器,顾名思义就是跨平台的,window上写,另外一个平台执行,这个点我希望在另外一个系列写,这里简单就是这样)
我们这里只需要知道这不是一般编译器处理的程序就可以。
3.2汇编到可执行文件(简述,详述有时间在linux分类下看)
arm-linux-as :汇编->目标文件A
arm-linux-ld :(目标文件B、C、D,也可以叫库,交叉编译器的库提供)+目标文件A=可执行文件E(像胶水一样粘起来)。ld来执行这一过程。对于编译内核bootloader是不要这些库的。
在直接用arm-linux-gcc时候可以用-nostartfiles、-nostdlib来表明不要这些库。
当然这里分步了,具体告诉ld究竟需要什么,所以就不会链接这些库
但是ld还是会找_start为入口就对了,这个写在汇编里面,所以执行汇编。
而目标文件B会提供_start这样的入口,这样就可以使得你的目标文件A可以是由C语言写,入口是main(),helloworld相信大家都有印象。
写汇编,不要库,所以要指定_start入口
可执行文件(elf格式)经过加载就变成可以躺在响应内存上执行的机器码
而且目标文件A B C D在linux都是一个文件格式,就是elf文件格式(elf文件详情打算在linux板块讲)
可执行文件E也是elf格式的
简单说可执行文件E里面包含了,这是一个在什么平台上的代码,如何放到内存上的信息等。具体放内存哪个地址。
最后是由加载器把可执行文件E加载内存。
这里有个差别,一个是有操作系统的加载内存,另一个是裸板加载内存。
有操作系统加载当然交给操作系统。(linux分类再写),elf文件会被处理后载入内存
像我们现在这种裸板,要多执行一步。因为elf文件有些信息对硬件是无效的。不可能直接烧到开发板执行
用arm-linux-objcopy就可以把elf转换为二进制文件,这个文件可以直接烧写执行。
3.3代码分析.text
.global _start
_start:
LDR R0,=0x56000050 @ R0设为GPFCON寄存器。此寄存器
@ 用于选择端口B各引脚的功能:
@ 是输出、是输入、还是其他
MOV R1,#0x00000100
STR R1,[R0] @ 设置GPF4为输出口, 位[8:7]=0b01
LDR R0,=0x56000054 @ R0设为GPBDAT寄存器。此寄存器
@ 用于读/写端口B各引脚的数据
MOV R1,#0x00000000 @ 此值改为0x00000010,
@ 可让LED1熄灭
STR R1,[R0] @ GPF4输出0,LED1点亮
MAIN_LOOP:
B MAIN_LOOP
---摘自韦东山书籍
为什么没有关开门狗呢,实际上是要关的,程序其实不断重启。
但重启太快,肉眼分辨不出这个LED1点亮的状况,所以就忽略,后面的程序都需要的。
这是arm上的汇编语法,当然通过arm-linux-gcc进行编译了。
“ARM是RISC结构数据从内存到CPU之间的移动只能通过L/S指令来完成,也就是ldr/str指令“
注释非常清楚了,这里用的LDR是它伪指令用法,因为有等号LDR R0,=0x56000050
这里大家自己查语法
这里R0充当了代表地址的作用
开头的.text说明是加载到内存.text段,另外讲。
.gobal _start是告诉编译器,_start在这个位置
而arm-linux-ld是以这个_start作为执行文件的入口的。这就是为什么要声明_start了
makefile就省略,语法也不多介绍
大家有空查一查
3.4----------------C语言实现--------------
裸板是不能执行目标文件BCD的。他们依赖操作系统
但是这样我们就写不了C语言实现了。
所以我们要自己写一个不依赖操作系统的汇编写的目标文件B,然后用他来转入C语言程序
.text
.global _start
_start:
ldr r0, =0x53000000
mov r1, #0x0
str r1, [r0]
ldr sp, =1024*4
bl main
halt_loop:
b halt_loop
这里只需要了解
bl main
即可
剩下的arm-linux-ld会解决。
所以这里makefile跟用汇编写的makefile有点不一样。
不一样就是在arm-linux-ld会加上这个汇编代码和C语言代码。
C语言代码毕竟开发快,可读。
不可能像求伯君先生那样用汇编来写,这逆天。
C语言代码如下:
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
int main()
{
GPFCON = 0x00000100; // 设置GPF4为输出口, 位[9:8]=0b01
GPFDAT = 0x00000000; // GPF4输出0,LED1点亮
return 0;
}
显然简洁很多。
重点是没有include之类的东西。
volatile 是为了不让编译器优化这个地址
比如你写
CPFCON = 1;
函数A;
GPFCON = 2;
函数B;
GPFCON = 3;
编译器只会执行最后一个,但是
这是不符合我们硬件逻辑的。
我们很显然想在这个地址依次写入1,2,3。
万一这地址在写入1后,使用函数A
写入2后,使用函数B
正好这两个函数是要读取这个寄存器呢
。。。。。
当然这里代码没有体现。
yoghourt_man
yoghourt_man