GPIO简介
GPIO(通用输入/输出端口)是相对于芯片而言的,如果在对应的芯片存在GPIO引脚则可以通过读这些引脚来获取引脚的变化(即:引脚的高低电平的变化)。
通过寄存器来访问引脚:
在S3C2410芯片中存在117个I/O端口,共分为A~H共8组分别为GPA~~GPH,在S3C2440中存在130个I/O端口,共分为A~J 9组名分别为GPA~GPJ,配置这些端口相应的寄存器(GPXCON, x=A~H/J),设置引脚是用于输入/输出,或者是用于特殊功能。控制s3c2440的GPIO端口的寄存器有3类,分别是GPxCON、GPxDAT、GPxUP (x=A ~ J):
GPxCON:GPIO控制寄存器,可以设置选定GPIO口的输入输出方式和功能。GPA组的23个端口比较特殊,只能是输出方式。GPACON的每一位对应一个引脚,当某位为0时,对应引脚为输出端口,否则为复用功能。
GPXCON寄存器:
GPXCON(x=A~~H/J)寄存器用于设置相应引脚的功能是输入/输出,还是特殊功能或保留不用。
在功能配置方面PORTA 与 PORTB~PORTH/J 有所不同,GPACON寄存器中每一位对应一个引脚(共23位),当某位被设置为0时,对应该位引脚被设置为输出引脚(可以用于写入),此时我们可以对GPADAT寄存器(用于写引脚)进行写操作,当某位被设置为1时(相应引脚为地址线/或用于控制),此时GONADAT无用.
PORTB~PORTH/J对寄存器操作完全相同,GPXCON每2位对应一个引脚:
00--输入。 01--输出。10--特殊功能。11--保留不用。
GPxDAT: 此引脚用于读写引脚的状态,即端口数据。当引脚配置为输出时,给该寄存器某位写1,则对应引脚输出高电平,写0输出低电平。当引脚配置为输出时,读该寄存器可以得到端口电平状态。
GPXDAT用于读/写引脚,当配置GPXCON寄存器设置某引脚为输入时,读此寄存器可以得知相应引脚的变化,当配置GPXCON寄存器设置某引脚为输出时,通过写此此寄存器可以是相应引脚产生高低电平变化。
GPxUP: 该寄存器可以设置引脚是否使用上拉电阻,某位为0时对应引脚使用内部上拉电阻,某位为1时相应引脚无内部上拉电阻。注意:GPA组没有GPxUP寄存器,即没有上拉电阻。
通过软件访对GPIO的访问:
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPB_OUT (1<<(2*5))
GPBCON = GPB_OUT; //设置GPB5为输出
GPBDAT &= ~(1<<5); //向GPB5输出低电平
注:当需要设计程序访问硬件时可参考芯片提供的电路原理图查找所使用引脚并进一步查找芯片手册中寄存器地址说明才确定所使用的寄存器的具体地址。
设置GPIO寄存器技巧:
以GPF为例
1,设置控制位GPxCON寄存器的控制:
从引脚读数据 将GPxCON设置为输入:
#define GPFx_in ~(3<<(x*2)) 【注释:将GPF端口的第X位设置为输入】 ,
前面说到的寄存器的输入输出控制中:【PORTB~PORTH/J对寄存器操作完全相同,GPXCON每2位对应一个引脚:
00--输入。 01--输出。10--特殊功能。11--保留不用。】
3对应的二进制是11,将3先移位到 要操作的对应位,取反就成了00。
按此思路,如果要将相应的GPxCON设置为输出位,
只需将“01”左移即可: #define GPGx_out (1<<(x*2))
2,对数据寄存器GPxDAT的控制:
【一句话:GPxDAT是用来读写端口数据的。写数据的时候,直接将要写的值赋给GPxDAT即可。要读数据时,先将寄存器置高,然后再读。】
只对寄存器第x位赋0,其余值不变 : GPBDAT &=~(1<
点亮LED灯
S5PV210板上提供了4个可编程用户LED,原理图如下:
LED原理图
在原理图中搜索引脚“LED1”,可得:
LED引脚图 可见,LED1,2,3,4 分别使用的 CPU端口资源为 GPJ2_0,1,2,3。
程序相关讲解
(1). start.S
由原理图可知,点亮S5PV210的4个LED需如下2个步骤:
第一步: 设置寄存器GPJ2CON,使GPJ2_0/1/2/3四个引脚为输出功能;
第二步: 往寄存器GPJ2DAT写0,使GPJ2_0/1/2/3四个引脚输出低电平,4个LED会亮;相反,往寄存器GPJ2DAT写1,使GPJ2_0/1/2/3四个引脚输出高电平,4个LED会灭;
以上两个步骤即为start.S中的核心内容,start.S里面涉及的汇编指令请自行学习GNU汇编指令集。
.globl _start _start: // 设置GPJ2CON的bit[0:15],配置GPJ2_0/1/2/3引脚为输出功能 ldr r1, =0xE0200280 ldr r0, =0x00001111 str r0, [r1] mov r2, #0x1000 led_blink: // 设置GPJ2DAT的bit[0:3],使GPJ2_0/1/2/3引脚输出低电平,LED亮 ldr r1, =0xE0200284 mov r0, #0 str r0, [r1] // 延时 bl delay // 设置GPJ2DAT的bit[0:3],使GPJ2_0/1/2/3引脚输出高电平,LED灭 ldr r1, =0xE0200284 mov r0, #0xf str r0, [r1] // 延时 bl delay sub r2, r2, #1 cmp r2,#0 bne led_blink halt: b halt delay: mov r0, #0x100000 delay_loop: cmp r0, #0 sub r0, r0, #1 bne delay_loop mov pc, lr
(2). Makefile
led.bin: start.o arm-linux-ld -Ttext 0x0 -o led.elf $^ arm-linux-objcopy -O binary led.elf led.bin %.o : %.S arm-linux-gcc -o $@ $< -c %.o : %.c arm-linux-gcc -o $@ $< -c clean: rm -rf *.o *.elf
第一步 执行arm-linux-gcc -o $@ $< -c命令将当前目录下存在的汇编文件和C文件编译成.o文件;
第二步 执行arm-linux-ld -Ttext 0x0 -o led.elf $^将所有.o文件链接成elf文件,-Ttext 0x0表示程序的运行地址是0x0,由于目前我们编写的代码是位置无关码,所以程序能在任何一个地址上运行;
第三步 执行arm-linux-objcopy -O binary led.elf led.bin将elf文件抽取为可在开发板上运行的bin文件;
3、用C语言编写代码
.global _start _start: ldr sp, =0xD0037D80 // 调用c函数,LED闪烁 bl led_blink halt: b halt
#define GPJ2CON (*(volatile unsigned long *) 0xE0200280) #define GPJ2DAT (*(volatile unsigned long *) 0xE0200284) void delay(int r0) // 延时 { volatile int count = r0; while (count--); } void led_blink() // LED闪烁 { GPJ2CON = 0x00001111; // 配置引脚 while(1) { GPJ2DAT = 0; // LED on delay(0x100000); GPJ2DAT = 0xf; // LED off delay(0x100000); } }
undefined reference to `__aeabi_unwind_cpp_pr0' 问题解决办法
问题解决:arm-none-linux-gnueabi-gcc加上-nostdlib选项即可
-nostdlib
不连接系统标准启动文件和标准库文件,只把指定的文件传递给连接器。
这个选项常用于编译内核、bootloader等程序,它们不需要启动文件、标准库文件。
有关volatile unsigned long一些说明:
对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支持独立的IO地址空间,并且是端口映射,就必须使用汇编语言完成实际对设备的控制,因为C语言并没有提供真正的“端口”的概念。如果是内存映射,那就方便的多了。
以 #define IOPIN (*((volatile unsigned long *) 0xE0028000)) 为例:作为一个宏定义语句,define是定义一个变量或常量的伪指令。首先(volatile unsigned long *)的意思是将后面的那个地址强制转换成 volatile unsigned long * ,unsigned long * 是无符号长整形,volatile 是一个类型限定符,如const一样,当使用volatile限定时,表示这个变量是依赖系统实现的,意味着这个变量会被其他程序或者计算机硬件修改,由于地址依赖于硬件,volatile就表示他的值会依赖于硬件。
volatile 类型是其数据确实可能在未知的情况下发生变化。比如,硬件设备的终端更改了它,现在硬件设备往往也有自己的私有内存地址,比如显存,他们一般是通过映象的方式,反映到一段特定的内存地址当中,这样,在某些条件下,程序就可以直接访问这些私有内存了。另外,比如共享的内存地址,多个程序都对它操作的时候。你的程序并不知道,这个内存何时被改变了。如果不加这个voliatile修饰,程序是利用catch当中的数据,那个可能是过时的了,加了 voliatile,就在需要用的时候,程序重新去那个地址去提取,保证是最新的。归纳起来如下:
1. volatile变量可变,允许除了程序之外的比如硬件来修改他的内容
2. 访问该数据任何时候都会直接访问该地址处内容,即通过cache提高访问速度的优化被取消
对于((volatile unsigned long *) 0xE0028000)为随硬件需要定义的一种地址,前面加上“*”指针,为直接指向该地址,整个定义约定符号IOPIN代替,调用的时候直接对指向的地址寄存器写内容既可。这实际上就是内存映射机制的方便性了。其中volatile关键字是嵌入式系统开发的一个重要特点。上述表达式拆开来分析,首先(volatile unsigned long *) 0xE0028000的意思是把0xE0028000强制转换成volatile unsigned long类型的指针,暂记为p,那么就是#define A *p,即A为P指针指向位置的内容了。这里就是通过内存寻址访问到寄存器A,可以读/写操作。
对于(volatile unsigned char *)0x20我们再分析一下,它是由两部分组成:
1)(unsigned char *)0x20,0x20只是个值,前面加(unsigned char *)表示0x20是个地址,而且这个地址类型是 unsigned char ,意思是说读写这个地址时,要写进unsigned char 的值,读出也是unsigned char 。
2)volatile,关键字volatile 确保本条指令不会因C 编译器的优化而被省略,且要求每次直接读值。例如用 while((unsigned char *)0x20)时,有时系统可能不真正去读0x20的值,而是用第一次读出的值,如果这样,那这个循环可能是个死循环。用了volatile 则要求每次都去读0x20的实际值。
那么(volatile unsigned char *)0x20是一个固定的指针,是不可变的,不是变量。而char *u则是个指针变量。
再在前面加"*":*(volatile unsigned char *)0x20则变成了变量(普通的unsigned char变量,不是指针变量),如果#define i (*(volatile unsigned char *)0x20),那么与unsigned char i是一样了,只不过前面的i的地址是固定的。
那么我们的问题就可解答了,(*(volatile unsigned char *)0x20)可看作是一个普通变量,这个变量有固定的地址,指向0x20。而0x20只是个常量,不是指针更不是变量。
代码下载链接:http://download.csdn.net/detail/klcf0220/5508227
喜欢开源,乐意分享的大神们,欢迎加入QQ群:176507146,你值的拥有哦!