一、GPIO
gpio(general purpose ports)通用输入/输出端口
gpio的操作是所有硬件操作的基础,这是底层开发人员必须掌握的
以三星公司的s3c2410/s3c2440为例做一下简要说明
s3c2410有117个i/o 分为A~H 8组
s3c2440有130个i/o 分为A~J 9组
二、通过操作寄存器来控制gpio,说明如下
寄存器类别 中文名称 功能描述
GPxCON 配置寄存器 配置端口的功能,如作为输入口(input)、输出口(output)以及特殊功能口(如中断信号)
GPxUP 上拉寄存器 上、下拉电阻的使能/失能(注:上拉电阻作用为当I/O PORTS被定义为input口时,为了避免信号干 扰产生不正确的值,通常会使用上拉电阻。)
GPxDAT 数据寄存器 只作为普通i/o时用于交互数据
GPxDRV 驱动能级寄存器 配置引脚的推拉电流能力
注:不同的处理器一般由不同的位数来控制一个pin,这需要查看数据手册才能正确配置,
关于GPxDRV寄存器有的处理器是没有这组寄存器的,关于GPA一般用于接外部存储器而不作为
普通的i/o,使用时应当注意这几点。
三、上、下拉电阻的作用
1、钳位电平
2、抗干扰
3、提高引脚的推拉电流能力
四、引脚的操作步骤
引脚的操作无外乎3种:输出高低电平,检测引脚状态,中断。
操作步骤概括如下:
1、查看引脚对应寄存器地址
2、配置引脚的功能及上下拉电阻
3、操作数据寄存器(只限用作普通i/o时)
注:有的引脚会通过缓冲器与外设相连,缓冲器的作用主要是提高驱动能力,隔离前后级信号。
五、裸机程序的设计步骤
编写源程序、生成可执行文件、烧写程序、运行测试
这个过程中要注意一些问题:
1、C语言程序在编译器编译生成可执行程序的时会在其中加入几个启动文件代码---ctrl.o、crti.o、
crtend.o crtn.o等,它们是标准库文件,用来设置堆栈,然后调用main函数,它们依赖于操作系
统,在裸机中这些代码无法执行,所以需要自己写一个启动代码。
2、在这个启动代码中需要做以下几件事
@1 关看门狗,防止cpu一直自动重启
@2 设置栈指针,设置sp时候注意其值不能大于内部RAM(IRAM)的空间大小
@3 调用c程序中的main函数
3、没有操作系统的裸板上运行程序,只能通过JTAG烧写
六、实验实例:实验平台mini2440单板
1、汇编程序(led_on.S):点亮GPB5~8的四个led灯
.text @代码段从此处开始
.global _start @将_start标号定义为全局可见
_start: @程序入口
LDR R0,=0x56000010 @将GPBCON寄存器地址值装入R0
MOV R1,#0x00015400 @配置5、6、7、8管脚为输出
STR R1,[R0]
LDR R0,=0x56000014 @将GPBDAT寄存器地址值装入R0
MOV R1,#0x00000000 @让5、6、7、8管脚为输出低电平
STR R1,[R0]
MAIN_LOOP:
B MAIN_LOOP
工程Makefile
led_on.bin : led_on.S
arm-linux-gcc -g -c led_on.S -o led_on.o
arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf
arm-linux-objcopy -O binary -S led_on_elf led_on.bin
clean:
rm -f led_on.bin led_on_elf *.o
2、C语言程序(led_on.c)点亮GPB5、7、8的三个led灯
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPBCON_5678_OUTPUT 0x1<<5*2 | 0x1<<6*2 | 0x1<<7*2 | 0x1<<8*2
#define GPBDAT_5678_LOW 0x0<<5*1 | 0x0<<6*1 | 0x0<<7*1 | 0x0<<8*1
int main(int argc, char *argv[])
{
GPBCON &= ~(0x3<<5*2 | 0x3<<6*2 | 0x3<<7*2 | 0x3<<8*2);
GPBCON |= GPBCON_5678_OUTPUT;
GPBDAT &= ~(0x1<<5*1 | 0x1<<6*1 | 0x1<<7*1 | 0x1<<8*1);
GPBDAT |= GPBDAT_5678_LOW;
GPBDAT |= 0x1<<6*1;
return 0;
}
由于前面说过需要自己编写启动代码,若是没有启动代码,交叉编译器会报告缺少
_start入口,所以必须自己编写一个启动代码
.text
.global _start
_start:
halt:
b halt
???这不是一个死循环吗?坑爹!这样led灯肯定不亮啊!!!
好吧,先不管那么多,我们先用下面的Makefile编译,试试看灯到底亮不?
led_on_test.bin:start.S led_on.c
arm-linux-gcc -g -c start.S -o start.o
arm-linux-gcc -g -c led_on.c -o led_on.o
arm-linux-ld -Ttext 0x00000000 -g led_on.o start.o -o led_test_elf #@要点1
arm-linux-objcopy -O binary -S led_test_elf led_test.bin
arm-linux-objdump -D -m arm led_test_elf > led_on.dis
clean:
@rm *.o *.dis *elf *.bin
二进制文件生成了,先不管把led_test.bin烧入板子,重启板子。看一看,我靠狗日的,
竟然led灯神奇的亮了,更加坑爹了啊?!什么原因,且待后面继续探索。
下面是另一个正确的启动代码,按部就班的操作吧!
.text
.global _start
_start:
ldr r0, =0x53000000
ldr r1, =0x0
str r1, [r0]
ldr sp, =1024*4
bl main
halt:
b halt
下面是Makefile
led_on.bin:start.S led_on.c
arm-linux-gcc -g -c start.S -o start.o
arm-linux-gcc -g -c led_on.c -o led_on.o
arm-linux-ld -Ttext 0x00000000 -g start.o led_on.o -o led_on_elf #@要点2
arm-linux-objcopy -O binary -S led_on_elf led_on.bin
arm-linux-objdump -D -m arm led_on_elf > led_on.dis
clean:
@rm *.o *.dis *elf *.bin
我们先使用make执行第一个目标生成led_on.bin ,烧入led_on.bin,然后重启板子,
看看吧!发现可以正常点亮led灯。靠!当然这不用怀疑了,正确的东西怎能不行呢!
可是为什么前面的那个坑爹的启动代码竟然可以点亮led灯呢???这的确有点让人费解
仔细观察下:
!!!请注意Makefile中的标注要点1 和 要点2 不同点在于链接时候顺序不一样呗!
链接器在链接时候是以一种从左到右的顺序链接的。就算是这样又怎么了,不是说程序启动时候
的入口应该都是_start吗???
要不改下上面的链接顺序, 重新试一下吧!发现还真是这回上面的错误启动代码,不能将led点亮了
,虽然现象是正常了(与我们的理论推测结果一致),可是为什么呢?要不看看两次生成的反
汇编代码吧!
为交换链接顺序时错误的启动代码情况时反汇编如下(只看核心代码部分):
00000000 <main>:
0: e52db004 push {fp} ; (str fp, [sp, #-4]!)
4: e28db000 add fp, sp, #0 ; 0x0
8: e24dd00c sub sp, sp, #12 ; 0xc
c: e50b0008 str r0, [fp, #-8]
10: e50b100c str r1, [fp, #-12]
14: e3a02456 mov r2, #1442840576 ; 0x56000000
18: e2822010 add r2, r2, #16 ; 0x10
1c: e3a03456 mov r3, #1442840576 ; 0x56000000
20: e2833010 add r3, r3, #16 ; 0x10
24: e5933000 ldr r3, [r3]
28: e3c33bff bic r3, r3, #261120 ; 0x3fc00
2c: e5823000 str r3, [r2]
30: e3a02456 mov r2, #1442840576 ; 0x56000000
34: e2822010 add r2, r2, #16 ; 0x10
…………………………中间省略n行………………………………
a0: e1a00003 mov r0, r3
a4: e28bd000 add sp, fp, #0 ; 0x0
a8: e8bd0800 pop {fp}
ac: e12fff1e bx lr
000000b0 <_start>:
b0: eafffffe b b0 <_start>
交换链接顺序后错误的启动代码情况时反汇编如下:
00000000 <_start>:
0: eafffffe b 0 <_start>
00000004 <aa>:
4: e52db004 push {fp} ; (str fp, [sp, #-4]!)
8: e28db000 add fp, sp, #0 ; 0x0
c: e24dd00c sub sp, sp, #12 ; 0xc
10: e50b0008 str r0, [fp, #-8]
14: e50b100c str r1, [fp, #-12]
18: e3a02456 mov r2, #1442840576 ; 0x56000000
1c: e2822010 add r2, r2, #16 ; 0x10
20: e3a03456 mov r3, #1442840576 ; 0x56000000
24: e2833010 add r3, r3, #16 ; 0x10
28: e5933000 ldr r3, [r3]
2c: e3c33bff bic r3, r3, #261120 ; 0x3fc00
30: e5823000 str r3, [r2]
34: e3a02456 mov r2, #1442840576 ; 0x56000000
…………………………中间省略n行………………………………
a0: e3a03000 mov r3, #0 ; 0x0
a4: e1a00003 mov r0, r3
a8: e28bd000 add sp, fp, #0 ; 0x0
ac: e8bd0800 pop {fp}
b0: e12fff1e bx lr
反汇编代码中依次含义如下
0: e52db004 push {fp}
地址 机器码 指令助记码
其中地址指的是这条指令的存储位置,指令十六进制码(机器码)是这条指令在计算
机中实际存储的数据形式, 指令助记码其实是指令十六进制码一种通俗一点的
表达符号,目的是让程序员更容易看懂,试想一直让你看机器码,你是不是会疯掉啊。
比较两者的反汇编代码不难知道,原来是“eafffffe b 0 <_start>”这一条指令
被存放在了不同的地址,才导致截然不同的结果,程序在运行时候依次由低地址往
高地址依次取指令顺序顺序执行,所以前者在没有进入_start入口直接进入了main函数
执行所以才出现了令人诧异的结果。
总结后一定牢记:链接器在链接时候是以一种从左到右的顺序链接的,最好把启动代码
作为第一个链接对象放在最左边,使其最先链接。
gpio(general purpose ports)通用输入/输出端口
gpio的操作是所有硬件操作的基础,这是底层开发人员必须掌握的
以三星公司的s3c2410/s3c2440为例做一下简要说明
s3c2410有117个i/o 分为A~H 8组
s3c2440有130个i/o 分为A~J 9组
二、通过操作寄存器来控制gpio,说明如下
寄存器类别 中文名称 功能描述
GPxCON 配置寄存器 配置端口的功能,如作为输入口(input)、输出口(output)以及特殊功能口(如中断信号)
GPxUP 上拉寄存器 上、下拉电阻的使能/失能(注:上拉电阻作用为当I/O PORTS被定义为input口时,为了避免信号干 扰产生不正确的值,通常会使用上拉电阻。)
GPxDAT 数据寄存器 只作为普通i/o时用于交互数据
GPxDRV 驱动能级寄存器 配置引脚的推拉电流能力
注:不同的处理器一般由不同的位数来控制一个pin,这需要查看数据手册才能正确配置,
关于GPxDRV寄存器有的处理器是没有这组寄存器的,关于GPA一般用于接外部存储器而不作为
普通的i/o,使用时应当注意这几点。
三、上、下拉电阻的作用
1、钳位电平
2、抗干扰
3、提高引脚的推拉电流能力
四、引脚的操作步骤
引脚的操作无外乎3种:输出高低电平,检测引脚状态,中断。
操作步骤概括如下:
1、查看引脚对应寄存器地址
2、配置引脚的功能及上下拉电阻
3、操作数据寄存器(只限用作普通i/o时)
注:有的引脚会通过缓冲器与外设相连,缓冲器的作用主要是提高驱动能力,隔离前后级信号。
五、裸机程序的设计步骤
编写源程序、生成可执行文件、烧写程序、运行测试
这个过程中要注意一些问题:
1、C语言程序在编译器编译生成可执行程序的时会在其中加入几个启动文件代码---ctrl.o、crti.o、
crtend.o crtn.o等,它们是标准库文件,用来设置堆栈,然后调用main函数,它们依赖于操作系
统,在裸机中这些代码无法执行,所以需要自己写一个启动代码。
2、在这个启动代码中需要做以下几件事
@1 关看门狗,防止cpu一直自动重启
@2 设置栈指针,设置sp时候注意其值不能大于内部RAM(IRAM)的空间大小
@3 调用c程序中的main函数
3、没有操作系统的裸板上运行程序,只能通过JTAG烧写
六、实验实例:实验平台mini2440单板
1、汇编程序(led_on.S):点亮GPB5~8的四个led灯
.text @代码段从此处开始
.global _start @将_start标号定义为全局可见
_start: @程序入口
LDR R0,=0x56000010 @将GPBCON寄存器地址值装入R0
MOV R1,#0x00015400 @配置5、6、7、8管脚为输出
STR R1,[R0]
LDR R0,=0x56000014 @将GPBDAT寄存器地址值装入R0
MOV R1,#0x00000000 @让5、6、7、8管脚为输出低电平
STR R1,[R0]
MAIN_LOOP:
B MAIN_LOOP
工程Makefile
led_on.bin : led_on.S
arm-linux-gcc -g -c led_on.S -o led_on.o
arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf
arm-linux-objcopy -O binary -S led_on_elf led_on.bin
clean:
rm -f led_on.bin led_on_elf *.o
2、C语言程序(led_on.c)点亮GPB5、7、8的三个led灯
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPBCON_5678_OUTPUT 0x1<<5*2 | 0x1<<6*2 | 0x1<<7*2 | 0x1<<8*2
#define GPBDAT_5678_LOW 0x0<<5*1 | 0x0<<6*1 | 0x0<<7*1 | 0x0<<8*1
int main(int argc, char *argv[])
{
GPBCON &= ~(0x3<<5*2 | 0x3<<6*2 | 0x3<<7*2 | 0x3<<8*2);
GPBCON |= GPBCON_5678_OUTPUT;
GPBDAT &= ~(0x1<<5*1 | 0x1<<6*1 | 0x1<<7*1 | 0x1<<8*1);
GPBDAT |= GPBDAT_5678_LOW;
GPBDAT |= 0x1<<6*1;
return 0;
}
由于前面说过需要自己编写启动代码,若是没有启动代码,交叉编译器会报告缺少
_start入口,所以必须自己编写一个启动代码
.text
.global _start
_start:
halt:
b halt
???这不是一个死循环吗?坑爹!这样led灯肯定不亮啊!!!
好吧,先不管那么多,我们先用下面的Makefile编译,试试看灯到底亮不?
led_on_test.bin:start.S led_on.c
arm-linux-gcc -g -c start.S -o start.o
arm-linux-gcc -g -c led_on.c -o led_on.o
arm-linux-ld -Ttext 0x00000000 -g led_on.o start.o -o led_test_elf #@要点1
arm-linux-objcopy -O binary -S led_test_elf led_test.bin
arm-linux-objdump -D -m arm led_test_elf > led_on.dis
clean:
@rm *.o *.dis *elf *.bin
二进制文件生成了,先不管把led_test.bin烧入板子,重启板子。看一看,我靠狗日的,
竟然led灯神奇的亮了,更加坑爹了啊?!什么原因,且待后面继续探索。
下面是另一个正确的启动代码,按部就班的操作吧!
.text
.global _start
_start:
ldr r0, =0x53000000
ldr r1, =0x0
str r1, [r0]
ldr sp, =1024*4
bl main
halt:
b halt
下面是Makefile
led_on.bin:start.S led_on.c
arm-linux-gcc -g -c start.S -o start.o
arm-linux-gcc -g -c led_on.c -o led_on.o
arm-linux-ld -Ttext 0x00000000 -g start.o led_on.o -o led_on_elf #@要点2
arm-linux-objcopy -O binary -S led_on_elf led_on.bin
arm-linux-objdump -D -m arm led_on_elf > led_on.dis
clean:
@rm *.o *.dis *elf *.bin
我们先使用make执行第一个目标生成led_on.bin ,烧入led_on.bin,然后重启板子,
看看吧!发现可以正常点亮led灯。靠!当然这不用怀疑了,正确的东西怎能不行呢!
可是为什么前面的那个坑爹的启动代码竟然可以点亮led灯呢???这的确有点让人费解
仔细观察下:
!!!请注意Makefile中的标注要点1 和 要点2 不同点在于链接时候顺序不一样呗!
链接器在链接时候是以一种从左到右的顺序链接的。就算是这样又怎么了,不是说程序启动时候
的入口应该都是_start吗???
要不改下上面的链接顺序, 重新试一下吧!发现还真是这回上面的错误启动代码,不能将led点亮了
,虽然现象是正常了(与我们的理论推测结果一致),可是为什么呢?要不看看两次生成的反
汇编代码吧!
为交换链接顺序时错误的启动代码情况时反汇编如下(只看核心代码部分):
00000000 <main>:
0: e52db004 push {fp} ; (str fp, [sp, #-4]!)
4: e28db000 add fp, sp, #0 ; 0x0
8: e24dd00c sub sp, sp, #12 ; 0xc
c: e50b0008 str r0, [fp, #-8]
10: e50b100c str r1, [fp, #-12]
14: e3a02456 mov r2, #1442840576 ; 0x56000000
18: e2822010 add r2, r2, #16 ; 0x10
1c: e3a03456 mov r3, #1442840576 ; 0x56000000
20: e2833010 add r3, r3, #16 ; 0x10
24: e5933000 ldr r3, [r3]
28: e3c33bff bic r3, r3, #261120 ; 0x3fc00
2c: e5823000 str r3, [r2]
30: e3a02456 mov r2, #1442840576 ; 0x56000000
34: e2822010 add r2, r2, #16 ; 0x10
…………………………中间省略n行………………………………
a0: e1a00003 mov r0, r3
a4: e28bd000 add sp, fp, #0 ; 0x0
a8: e8bd0800 pop {fp}
ac: e12fff1e bx lr
000000b0 <_start>:
b0: eafffffe b b0 <_start>
交换链接顺序后错误的启动代码情况时反汇编如下:
00000000 <_start>:
0: eafffffe b 0 <_start>
00000004 <aa>:
4: e52db004 push {fp} ; (str fp, [sp, #-4]!)
8: e28db000 add fp, sp, #0 ; 0x0
c: e24dd00c sub sp, sp, #12 ; 0xc
10: e50b0008 str r0, [fp, #-8]
14: e50b100c str r1, [fp, #-12]
18: e3a02456 mov r2, #1442840576 ; 0x56000000
1c: e2822010 add r2, r2, #16 ; 0x10
20: e3a03456 mov r3, #1442840576 ; 0x56000000
24: e2833010 add r3, r3, #16 ; 0x10
28: e5933000 ldr r3, [r3]
2c: e3c33bff bic r3, r3, #261120 ; 0x3fc00
30: e5823000 str r3, [r2]
34: e3a02456 mov r2, #1442840576 ; 0x56000000
…………………………中间省略n行………………………………
a0: e3a03000 mov r3, #0 ; 0x0
a4: e1a00003 mov r0, r3
a8: e28bd000 add sp, fp, #0 ; 0x0
ac: e8bd0800 pop {fp}
b0: e12fff1e bx lr
反汇编代码中依次含义如下
0: e52db004 push {fp}
地址 机器码 指令助记码
其中地址指的是这条指令的存储位置,指令十六进制码(机器码)是这条指令在计算
机中实际存储的数据形式, 指令助记码其实是指令十六进制码一种通俗一点的
表达符号,目的是让程序员更容易看懂,试想一直让你看机器码,你是不是会疯掉啊。
比较两者的反汇编代码不难知道,原来是“eafffffe b 0 <_start>”这一条指令
被存放在了不同的地址,才导致截然不同的结果,程序在运行时候依次由低地址往
高地址依次取指令顺序顺序执行,所以前者在没有进入_start入口直接进入了main函数
执行所以才出现了令人诧异的结果。
总结后一定牢记:链接器在链接时候是以一种从左到右的顺序链接的,最好把启动代码
作为第一个链接对象放在最左边,使其最先链接。