S5PV210裸板驱动栈与GPIO的使用.docx
- 文档编号:29597528
- 上传时间:2023-07-25
- 格式:DOCX
- 页数:13
- 大小:19.82KB
S5PV210裸板驱动栈与GPIO的使用.docx
《S5PV210裸板驱动栈与GPIO的使用.docx》由会员分享,可在线阅读,更多相关《S5PV210裸板驱动栈与GPIO的使用.docx(13页珍藏版)》请在冰豆网上搜索。
S5PV210裸板驱动栈与GPIO的使用
如果裸板代码都用汇编代码编写的话,那确实是难度大,所以还是选择使用C语言来写,这样的话就会涉及栈了。
接下来慢慢介绍
一。
栈的作用
本想借着这节介绍一下Crotex-A8,的工作模式和寄存器的,但写到一半还是决定放弃,先撇开硬件上的关系,简单介绍一下栈的作用和怎么使用吧,另外的等迟点再说。
栈是在内存上分配的一块内存,当需要存放数据到栈里时,称为入栈,反之称为出栈。
同时栈的数据先进后出,栈是往下延伸的,越后存放的数据,存放数据的地址越小。
我们现在的裸板代码是使用IRAM的,上一章也介绍了BL0会有分配栈空间的操作,由下图看看IRAM是怎么分配的。
这是IRAM内存分布图,之前介绍过,0xD0020000往后的16KB是存放BL0代码的210有8种工作模式,不同工作模式需要分配不同的栈,0xD0037780~0xD0037D80的1.5KB是SVC模式下的栈,0xD0037D80~0xD0037F80的512B是IQR模式下的栈。
目前210工作在SVC模式下。
在代码中,栈需要有一个栈指针SP,用来表示当前可以入栈的地址。
刚开始时,栈里没数据,SP=0xD0037D80,当入栈一个32位数据后,SP地址加4,指向0xD0037D84,如此类推。
那栈是用来干什么的呢,一般常见有三种
1.在调用函数前,要保存当前函数的数据,如一些寄存器里面的值,还有函数的返回地址,不然等调用函数结束后,代码就没办法返回原来的函数和继续操作原来的值
2.存放调用参数,一般代码会默认使用前几个寄存器来作为函数传参,但参数太多的时候,就只能通过存放在栈里面。
因为栈指针的地址是对应同一工作模式下的所有函数是可见的。
3.保存临时变量。
接下来就稍稍修改一下代码,让汇编转跳到C代码执行
1_ARM/1_start/2/start.S
1#defineWTCON0xE2700000
2
3.text
4.align2
5.global_start
6
7_start:
8//closethewatchdog
9ldrr0,=WTCON
10movr1,#0
11strr1,[r0]
12
13ldrsp,=0xD0037D80
14
15blmain
16
17loop:
18bloop
在原来的基础上添加了一句 ldrsp,=0xD0037D80 ,指定栈空间的地址。
其实关开门狗和设置栈空间在这里都是多此一举的,因为在BL0的固化代码里面已经有这样的操作了,这里只是我在代码上强调一下而已。
接着就要实现main函数了。
1_ARM/1_start/2/main.c
1intmain(void)
2{
3return0;
4}
什么操作都没做。
编译后这个代码是没有实质操作的,我只是想让大家看看编译后的反汇编代码。
1_ARM/1_start/2/start.dis
1900000020
2020:
e52db004push{fp};(strfp,[sp,#-4]!
)
2124:
e28db000addfp,sp,#0
2228:
e3a03000movr3,#0
232c:
e1a00003movr0,r3
2430:
e28bd000addsp,fp,#0
2534:
e8bd0800pop{fp}
2638:
e12fff1ebxlr
27
在进入main函数后,因为接下来的操作会更改fp寄存器,所以第一步的操作就是把fp原来的值入栈,函数执行完后出栈。
二.GPIO的使用
到目前为止,我们已经知道了芯片启动的过程,并且已经跳转到C代码,接下来我们就开始用C语言来编写裸板代码。
我们先从GPIO入手。
最简单的寄存器操作需要两步:
1。
配置GPIO的功能。
GPIO可以配置成输入,输出,中断或者其他的特定的功能,如配置成串口功能
2。
设置GPIO的值
如果配置成输出,我们就要设置GPIO输出的值,是1还是0
如果配置成输入,我们就要想办法获取输入的值是多少
当然,除了这两步外,还有其他的操作,譬如配置上拉电阻,配置驱动能力等。
接下来我们尝试通过GPIO管脚点亮LED灯。
我们先看看原理图
我们尝试让D22这个LED闪烁,这是通过管脚GPJ0_3控制。
VDD_IO是供电,想要LED点亮,GPJ0_3必须输出低电平,反之,LED灭。
所以我们要做的事情就是:
1。
配置GPJ0_3为输出管脚
2。
使GPJ3_0重复输出高低电平
先介绍一下GPJ0的相关寄存器
GPJ0CON:
配置寄存器,配置管脚的功能,如输出,输入以及其他功能
GPJ0DAT:
数据寄存器,管脚配置成输出时,可以修改这个寄存器的值来修改管脚的输出,管脚配置成输入时,可以读取寄存器的值来知道管脚的输入。
GPJ0PUD:
上/下拉寄存器,配置管脚上的上下拉电阻。
GPJ0DRV:
驱动能力寄存器,配置管脚的驱动能力。
GPJ0CONPDN:
powerdown模式下的配置寄存器。
GPJ0PUDPDN:
powerdown模式下的上下拉配置寄存器。
我们下面的代码只需要配置前两个寄存器,后面的使用默认值就可以了。
在S5PV210中,每一个寄存器都有分配一个地址,对应的地址上存放这寄存器的值。
如寄存器GPJ0CON,它的地址是0xE0200240,寄存器的默认值就是0x00000000。
如果我们需要往GPJ0CON寄存器写入0x11111111。
需要怎么做呢?
1.定义地址0xE0200240位unsignedint类型的指针
(unsignedint*)0xE0200240)
2.获取0xE0200240的值
*(unsignedint*)0xE0200240)
3.赋值
*(unsignedint*)0xE0200240)=0x11111111
4添加volatile,防止编译优化。
(*(volatileunsignedint*)0xE0200240))=0x11111111
在原理图可以看到,GPJ0有8个GPIO,分别是GPJ0_0~~GPJ0_7。
先看配置寄存器GPJ0CON。
我们需要修改的是GPJ0_3,在寄存器GPJ0CON的12~15bit,默认值是0000
0000是配置成输入,0001是配置成输出,1111配置成中断,以及其他的功能。
现在我们要配置成输出,即0001,其他的管脚保持为0000。
所以GPJ0CON的值二进制表示为:
0b0000000000000000 0001 000000000000
也可以用代码表示为:
(1<<12)
再看看数据寄存器GPJ0DAT
GPJ0DAT有8bit,每一个bit,对应一个GPIO。
如果我们配置了GPJ0_3管脚为输出,现在需要输出高电平的话,需要给GPJ0DAT的bit3赋1。
(*(volatileunsignedint*)0xE0200244))=(1<<3)
而上下拉电阻寄存器GPJ0UP
GPJ0UP是有16个bit,每两个bit对应一个GPIO
GPJ0UP的默认值是0x55555555,代表每个GPIO都配置成下拉有效,即01。
我要将GPJ0_3配置成不需要上下拉电阻,即00。
(*(volatileunsignedint*)0xE0200248))=(0 << 6)
GPJ0DRV寄存器我们就可以不需要配置了,使用默认值就可以。
代码修改如下:
1_ARM/2_gpio/1/main.c
1#defineGPJ0CON(*(volatileunsignedint*)0xE0200240)
2#defineGPJ0DAT(*(volatileunsignedint*)0xE0200244)
3#defineGPJ0PUD(*(volatileunsignedint*)0xE0200248)
4
5voiddelay(unsignedintcount)
6{
7for(;count>0;count--);
8}
9
10intmain(void)
11{
12GPJ0CON=(1<<12);
13GPJ0PUD=(0<<6);
14
15while
(1){
16GPJ0DAT=(1<<3);
17delay(100000);
18GPJ0DAT=(0<<3);
19delay(10000);
20}
21}
编译后,将代码烧入开发板,当按下电源开关,就会看到对应的LED不停闪烁。
但是,只要我们松开开关,电源就断开了,这样可不行啊,总不能每次跑程序我都要死死按住不放吧。
所以待会还要改进一下开发板的启动函数。
接下来先优化一下代码。
上面的操作,是直接给整个寄存器赋值,如果我只要单独赋值的话该如何做呢?
以操作GPJ0DAT为例:
置1:
GPJ0DAT|=(1<<3);
置零:
GPJ0DAT&=~(1<<3);
如果每次修改寄存器都按上面的方法操作,那该多麻烦啊,所以我定义了几个宏:
1_ARM/2_gpio/2/common.h
4#definevi*(volatileunsignedint*)
5
6#defineSET_ZERO(addr,bit)((viaddr)&=(~(1<<(bit))))
7#defineSET_ONE(addr,bit)((viaddr)|=(1<<(bit)))
8
9#defineSET_BIT(addr,bit,val)((viaddr)=((viaddr)&=(~(1<<(bit))))|((val)<<(bit)))
10
11#defineSET_2BIT(addr,bit,val)((viaddr)=((viaddr)&(~(3<<(bit))))|((val)<<(bit)))
12
13#defineSET_NBIT(addr,bit,len,val)\
14((viaddr)=(((viaddr)&(~((((1<<(len))-1))<<(bit))))|((val)<<(bit))))
15
16#defineGET_BIT(addr,bit)(((viaddr)&(1<<(bit)))>0)
17
18#defineGET_VAL(addr,val)((val)=viaddr)
19#defineSET_VAL(addr,val)((viaddr)=(val))
20#defineOR_VAL(addr,val)((viaddr)|=(val))
文件实现了一下简单的给指定某个寄存器的哪几位赋值的操作,具体不一一细说。
另外我还修改了Makefile
1CC=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-gcc
2LD=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-ld
3OBJCOPY=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-objcopy
4OBJDUMP=/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-objdump
5
6START_OBJ=start.omain.ocommon.o
7
8start.bin:
$(START_OBJ)
9$(LD)-Ttext0x0000000-g$(START_OBJ)-ostart
10$(OBJCOPY)-Obinary-Sstart$@
11$(OBJDUMP)-D-marmstart>start.dis
12gccmkv210_image.c-omkmini210
13./mkmini210$@210.bin
14cp210.bin/fastboot
15
16%.o:
%.S
17$(CC)-g-nostdlib-c-o$@$<
18
19%.o:
%.c
20$(CC)-g-nostdlib-c-o$@$<
21
22clean:
23rmstart*.o*.bin*.dismkmini210
1~4行定义的编译工具的简写
第6行指定最终打包成的bin文件需要的.o文件,后续需要添加C文件的话只需要在此处添加就可以
8~14行指定了最终编出来的start.bin的编译方式
16~17行指定了汇编代码的编译规则
19~20行指定了C代码的编译规则
原来的代码也修改如下:
/1_ARM/2_gpio/2/main.c
1#include"common.h"
2
3#defineGPJ0CON0xE0200240
4#defineGPJ0DAT0xE0200244
5#defineGPJ0PUD0xE0200248
6
7intmain(void)
8{
9SET_2BIT(GPJ0CON,12,1);
10SET_VAL(GPJ0PUD,0);//这里GPJ0PUD全置0,因为默认值配置了下拉
11//电阻,会让其他LED也发光
12while
(1){
13SET_ONE(GPJ0DAT,3);
14delay(100000);
15SET_ZERO(GPJ0DAT,3);
16delay(100000);
17}
18}
接下来就要解决另一个问题,开机后怎么样才能不需要一直按住按键。
先来个原理图:
为了让开发板工作,必须要让MOS管APM2305导通,这样VDD_IN才能输入到右侧元件的管脚7。
现在区域1,当SWI被按下时,区域1与VDD_IN形成了通路,使APM2305导通,所以开发板就上电工作了,一旦我们松开按键,开发板就会断电。
要想松开手开发板还能工作,那就需要区域2的电路了。
只要区域2或者区域1有一处与VDD_IN接通就可以了。
而区域2上有一个二极管,只要往二极管的管脚1输入高电平,就能让管脚2,3导通。
所以我们接下来就是要在开机后让EINT0输出高电平即可,而EINT0对应管脚GPH0_0。
代码如下:
3#defineGPJ0CON0xE0200240
4#defineGPJ0DAT0xE0200244
5#defineGPJ0PUD0xE0200248
6#defineGPH0CON0xE0200C00
7#defineGPH0DAT0xE0200C04
8#defineGPH0PUD0xE0200C08
9
10intmain(void)
11{
12SET_2BIT(GPJ0CON,12,1);
13SET_VAL(GPJ0PUD,0);
14SET_2BIT(GPH0CON,0,1);
15SET_VAL(GPH0PUD,0);
16
17SET_ONE(GPH0DAT,0);
18while
(1){
19SET_ONE(GPJ0DAT,3);
20delay(100000);
21SET_ZERO(GPJ0DAT,3);
22delay(100000);
23}
24}
最后我们要介绍一下GPIO作为输入的问题。
需要使用GPIO的管脚功能时,需要两个步骤:
1:
通过CON寄存器配置管脚为输入功能
2:
通过读DAT寄存器的值来判断当前管脚电平的高低
另外,如果有需要,通过PUD寄存器配置上拉电阻
以按键SW5为例,当按键没有按下是,管脚EINT2检测到的是高电平,当按键按下时,管脚EINT2检测到的是低电平,从原理图看EINT2链接的是GPH2_0。
接下来我们就要实现:
当SW5按下,LEDD22点亮,SW5松开,LEDD22熄灭
代码如下:
1_ARM/2_gpio/4/main.c
13intmain(void)
14{
15SET_NBIT(GPJ0CON,12,4,1);
16SET_VAL(GPJ0PUD,0);
17
18SET_NBIT(GPH0CON,0,4,1);
19SET_VAL(GPH0PUD,0);
20SET_ONE(GPH0DAT,0);
21
22SET_NBIT(GPH2CON,0,4,0);//配置GPH2_0为输入管脚
23SET_2BIT(GPH2PUD,0,2);//配置GPH2_0使能上拉电阻
24
25while
(1){
26if(0==GET_BIT(GPH2DAT,0))//如果检测到低电平,表示按键按下
27SET_ZERO(GPJ0DAT,3);
28elseif(1==GET_BIT(GPH2DAT,0))//反之按键松开
29SET_ONE(GPJ0DAT,3);
30
31delay(10000);
32}
33}
总结:
到现在为止,所有的内容都介绍问了,主要有两部分:
1:
汇编转跳C语言的方法以及堆栈的分配
2:
GPIO输入输出的配置和使用
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- S5PV210 驱动 GPIO 使用