开发板买了这么久还没有怎么认真的学习,决定花一个月的业余时间看看这个开发板,把学习的体会罗列下来。
我们从最简单的GPIO开始,在我们的开发板上面,有四个LED,我们写一个简单的程序来控制这个LED。
首先看电路图:
LED连上2410的pin
可以发现我们的四个LED分别对应GPB5 GPB6 GPB7 GPB8,然后查找手册,我们可以知道GPB是一个有11引脚的 input/out port,就是说我们的GPB既可以做输入也可以做输出, 在乎你的配置。这里我们复习一下2410的GPIO,
- Port A(GPA): 25-output port
- Port B(GPB): 11-input/out port
- Port C(GPC): 16-input/output port
- Port D(GPD): 16-input/output port
- Port E(GPE): 16-input/output port
- Port F(GPF): 8-input/output port
- Port G(GPG): 16-input/output port
- Port H(GPH): 9-input/output port
- Port J(GPJ): 13-input/output port
具体的功能可以参考手册,注意很多复用的引脚。这里我们只是看看我们的GPB
Port B Selectable Pin Functions
GPB10 Input/output nXDREQ0 – –
GPB9 Input/output nXDACK0 – –
GPB8 Input/output nXDREQ1 – –
GPB7 Input/output nXDACK1 – –
GPB6 Input/output nXBREQ – –
GPB5 Input/output nXBACK – –
GPB4 Input/output TCLK0 – –
GPB3 Input/output TOUT3 – –
GPB2 Input/output TOUT2 – –
GPB1 Input/output TOUT1 – –
GPB0 Input/output TOUT0 – –
这里我们的LED的引脚刚好和DMA的一个Request Source复用了,这里我们先简单的说一下DMA(个人理解),DMA就是不需要cpu参与的数据传输,把CPU空闲下来留给其他的运算。这种数据交换可以是系统总线内部或外围总线之间的数据交互,简单的例子,以外部DMA请求为例:
当需要进行DMA操作的时候,外部DMA请求引脚 XnXDREQ引脚变为低电平,表示CPU已经将控制权交给了DMA控制器,可以进行数据传输。当传输数据完成之后,应答信号XnXDACK置为高电平,通知CPU完成一次DMA操作。
S3C2410支持4通道的DMA,DMA操作可以由软件、内部外设或者外部引脚发起。
a) 源设备和目标都在系统总线AHB上
b) 源设备和目标都在外围总线APB上
c) 源设备在系统总线,而目标设备位于外围总线
d) 源设备在外围总线,而目标设备位于系统总线
2440的DMA和2410差不多,看文档是这样说的,四条通道分别处理四种不同的状态
1、 源和目标都在系统总线上
2、 源在系统总线,目的在外围总线
3、 源在外围总线,目的在系统总线
4、 源和目的都在外围总线
The main advantage of the DMA is that it can transfer the data without CPU intervention. The operation of DMAcan be initiated by software, or requests from internal peripherals or external request pi ns。DMA的优点就是不需要CPU参与的数据传输,DMA的操作可以被软件、内部的总线或者外部的引脚触发。
DMA操作:
Stage-1 初始状态,等待DMA请求,若请求到达,进入Stage-2。此阶段,DMA ACK和INT REQ都为0。
Stage-2 DMA ACK 变为1,计数器CURR_TC从DCON[19:0]加载数值。注意:此时DMA ACK仍然为1,知道它随后在stage-3中被清0。
Stage-3 在此状态,对DMA进行原子操作的sub-FSM(子状态机)被初始化它从源地址读取数据然后写入目的地址(此操作需要考虑数据大小和传输尺寸)。 在此阶段,对DMA进行原子操作的sub-FSM(子状态机)被初始化。Sub-FSM从源地址读取数据,然后写入目的地址。在此操作中,数据大小和传输 大小(单次或者突发)会给予考虑。在Whole service mode中,这种操作会重复至计数器CURR_TC变为0,而在Single service mode,这种操作只会进行一次。在sub-FSM完成一次原子操作,主FSM将CURR_TC减1。另外,当CURR_TC为0,且中断设置 DCON[29]为1时,主FSM会发出INT REQ(中断请求信号)。另外,当下面任何一种情况发生,主FSM清除DMA ACK:
每一次DMA传输都需要等待DMA请求。
这里我们不多说这个DMA,因为我们的程序中没有用到,后面的文章抽时间再看看这个东西。
这里,因为我们只是简单的Led控制,没有涉及到DMA,我们把GPB当做普通的输入输出引脚来看待。
我们可以看到我们GPB的配置寄存器是0x56000010,下面我们看代码,在代码中去实现具体的功能:
我们首先单独的控制GPB5,GPB5对应CON的[11:10]作为输出,这两位应该是01,其他的我们默认的为0,所以我们的控制位应该是0x00000400,[11:10]为 01,其都为零。
我们的最后的代码如下:
@****************
@File : led_On.S
@功能:Led点灯程序,点亮LED1
@****************
.text
.global _start
_start:
LDR R0,=0x56000010
MOV R1,#0x00000400
STR R0,[R0]
LDR R0,=0x56000014
MOV R1,#0x00000000
STR R1,[R0]
MAIN_LOOP:
B MAIN_LOOP
然后就是编译了,因为我的电脑没有并口,使用的JLink,这里所以不能用sjf2410来扫写nand flash,还好,我们可以现在Jlink上面初始化我们的SDRAM,然后把程序通过JLInk直接放在SDRAM上面运行。所以我们的MakeFile应该是:
led_on.bin : led_on.S
arm-linux-gcc -g -c -o led_on.o led_on.S //编译
arm-linux-ld -Ttext 0x3000000 -g led_on.o -o led_on_elf //连接
arm-linux-objcopy -O binary -S led_on_elf led_on.Bin //生成bin
clean:
rm -f led_on.bin led_on_elf *.O
通过LD生成一般是elf文件,还要转化为我们可以运行的机器码 用objcopy命令,通过JLINK命令行把生成的Bin拷贝过去,运行,发现全黑了,没有出现我们应该的一个灯亮,检查代码,发现STR R0,[R0],这种低级错误,把这个换成STR R1,[R0],重新编译。然后拷贝程序到SDRAM,运行OK,发现一个LED亮,其他的熄灭。
上面是纯粹的汇编,但是我们以后写程序一般情况下还是使用的是c,这里我们用C来实现这个功能。Led_on_c
每一个C程序都有一个main函数的入口,但是我们的程序时如何进入这个Main的呢?必须要借助一定得汇编代码。
这里我们写一个基本的接口,crt0.S,负责这个跳转的工作:
@******************************************************************************
@ File:crt0.S
@ 功能:通过它转入C程序
@******************************************************************************
.text
.global _start
_start:
ldr r0, =0x56000010 @ WATCHDOG寄存器地址
mov r1, #0x0
str r1, [r0] @ 写入0,禁止WATCHDOG,否则CPU会不断重启
ldr sp, =1024*4 @ 设置堆栈,注意:一般不能大于4k,如果我 @们的代码是通过Nand启动,就不能大于4K
@但是这里我们是通过SDRAM来实现,这个限 @制可以扩大到64M
@ nand flash中的代码在复位后会移到内部 @ram中,此ram只有4K
bl main @ 调用C程序中的main函数
halt_loop:
b halt_loop
这个就是为什么我们的C程序都是一个main入口函数,我们在window或者Linux下面编程,也会有这样的一个类似功能的crt0的lib,我们的c代码编译的时候都静态的链接这个库,所以我们的程序才能以main为入口。
这里我们只是一一个简化版,只是简单的初始化watch dog,禁用了watch dog,避免CPU的重启。设置了一下堆栈,这里因为这个代码是准备给Nand flash启动来使用的,我们的堆栈是往下生长的,所以这里设置为1024*4,没有什么问题,但是如果是我们的程序,我们的Text段是在0x30000000,这个值就不能是4*1024,而是0x30000000+4*1024,(其实可以更大,这里我们暂时设置这么大)。0x30000000+4*1024 = 0x30001000
所以,将ldr sp, =1024*4改为ldr sp, =0x30001000,这样我们的基本的跳转就好了。下面就是我们的C程序了
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
int main()
{
GPBCON = 0x00000400; // 设置GPB5为输出口, 位[11:10]=0b01
GPBDAT = 0x00000000; // GPB5输出0,LED1点亮
return 0;
}
C代码都是比较简单的,设置寄存器,然后写入对应的值,原来可以参见前面的汇编。
下面就是makefile的编写了,这个有点意思的:
led_on_c.bin : crt0.S led_on_c.c
arm-linux-gcc -g -c -o crt0.o crt0.S //首先编译crt0
arm-linux-gcc -g -c -o led_on_c.o led_on_c.C //然后编译led_on_c
arm-linux-ld -Ttext 0x3000000 -g crt0.o led_on_c.o -o led_on_c_elf //link
arm-linux-objcopy -O binary -S led_on_c_elf led_on_c.Bin //生成bin
arm-linux-objdump -D -m arm led_on_c_elf > led_on_c.Dis //反汇编一下
clean:
rm -f led_on_c.dis led_on_c.bin led_on_c_elf *.O
拷贝程序的时候发现现在开发板上面GPB5还是亮的,我们先写个程序让他熄灭,把C代码中的 GPBDAT = 0x00000000; // GPB5输出0,LED1点亮
改为:
GPBDAT = 0x00000020; // GPB5输出0,LED1点亮 20表示[5]GPB5为1,故此熄灭
编译生成bin,运行,对应的LED熄灭,然后注释掉这一行,改成0x00000000,看会不会亮?
编译运行,拷贝之后运行,发现灯亮。
下面,在深入一点,因为我们的开发板是有几个按键的。K1 K2 K3 K4 K5 K6,有这样的一个功能,对于K1 K2 K3 K4,任意一个按键按下,对应的Led就点亮对应的是Led1、Led2、Led3、Led4,前面查电路图已经知道这四个led分别对应 GPB5 GPB6 GPB7 GPB8,打开电路图看看我们的按键连线。
Key1 key2 key3 key4分别对应的是EINT 8 EINT11 EINT13 EINT14,其中EINT8是GPG0,EINT11是GPG3,EINT13是GPG5 EINT14是GPG6。
这里有一个区别和上面的就是GPG的设置上面
设置为10表示为中断。设置为00表示输入,这里我们没有使用中断,而是使用的是while轮训,所以设置为00,所以就是 ~(3<<2*3),除了Key对应的位为00,其他的都是11。
因此我们还是利用前面的crt0,但是我们的C代码如下
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
/*
* KEY1-4对应GPG0、GPG3、GPG5、GPG6
*/
#define GPG0_in ~(3<<(0*2))
#define GPG3_in ~(3<<(3*2))
#define GPG5_in ~(3<<(5*2))
#define GPG6_in ~(3<<(6*2))
//把对应的地方设置为输入0x11 = 3 取反就是00,表示输入
/*
* LED1-4对应GPB5、GPB6、GPB7、GPB8
*/
#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*2))
int main()
{
unsigned long dwDat;
//LED1-LED4 Pin Config
GPBCON = GPB5_out | GPB6_out | GPB7_out | GPB8_out ;
//Key1-Key4 pin config
GPGCON = GPG0_in & GPG3_in & GPG5_in & GPG6_in;
while(1)
{
dwDat = GPGDAT;
if(dwDat & (1)) //key1没有按下
GPBDAT |= (1<<5);
else
GPBDAT &= ~(1<<5);
if(dwDat & (1<<3)) //key3没有按下
GPBDAT |= (1<<6);
else
GPBDAT &= ~(1<<6);
if(dwDat & (1<<5)) //key3没有按下
GPBDAT |= (1<<7);
else
GPBDAT &= ~(1<<7);
if(dwDat & (1<<6)) //key3没有按下
GPBDAT |= (1<<8);
else
GPBDAT &= ~(1<<8);
}
return 0;
}
这个基本上就是一个轮训的方式,没有使用中断。
我们看看我们的makefile是如何编写的。
key_led.bin : crt0.S key_led.c
arm-linux-gcc -g -c -o crt0.o crt0.S
arm-linux-gcc -g -c -o key_led.o key_led.c
arm-linux-ld -Ttext 0x3000000 -g crt0.o key_led.o -o key_led_elf
arm-linux-objcopy -O binary -S key_led_elf key_led.bin
arm-linux-objdump -D -m arm key_led_elf > key_led.dis
clean:
rm -f key_led.dis key_led.bin key_led_elf *.o
编译,放在开发板上面运行,一切OK,按下键对应的灯亮,放开就熄灭。同时按住两个,对应的两个就亮。
这里只是GPIO的基本操作,下一次我们看看存储器的基本操作。