GPIO实验

目标:点亮LED
怎么点亮LED?看原理图,确定LED是怎么接线的。

相关原理图:
这里写图片描述

这里写图片描述

简化的原理图:
这里写图片描述

从原理图中可以看出GPF4=0,灯亮;GPF4=1,灯灭

怎么设置GPF4的值呢?
GPF4既然做为一个管脚,就可以做为输入管脚、也可以做为输出管脚。因此我们需要配置GPF的功能是输入还是输出还是中断(这里应该配置成输出)?配置完以后,需要设置它的值。具体怎么样设置?操作相应的寄存器,需要进一步查看芯片手册找到各自的寄存器(配置寄存器、设置寄存器)。

芯片手册中的GPF控制器说明如下:
这里写图片描述

如图可知要配置成输出引脚,需要将第8-9位设置成01即可。
设置寄存器说明如下:
这里写图片描述

如上图可知只需要将设置设置寄存的第四位即可。

操作原理如下:
这里写图片描述

程序怎么实现:
首先要找到两个寄存的地址,由芯片手册可知,配置寄存器、设置寄存器分别的寄存器地址为:0x56000050、0x56000054。找到寄存器的地址后,只需要向这两个寄存器相应的位写入数据即可。
汇编语言实现代码如下:

@******************************************************************************
@ File:led_on.S
@ 功能:LED点灯程序,点亮LED1
@******************************************************************************       

.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

基中 MOV R1,#0x00000100 表示要往控制器设置的数字值,因为二进制 01000 0000 转成 16进制数据 为:0x00000100。
STR 是一个写内存指令,STR R1,[R0] 表示把R1中的值写到R0(GPF4控制寄存器),执行完这条指令以后,GPF4就被配置成了输出引脚。

同理,设置GPBDAT寄存器的值。

其中

MAIN_LOOP:
            B       MAIN_LOOP

表示一个死循环

汇编程序写完以后,需要进一步编译makefile文件进行编译。makefile文件内容如下:

led_on.bin : led_on.S
    arm-linux-gcc -g -c -o led_on.o led_on.S
    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

-g表示加入一些调试信号;
-c表示编译不链接;程序的编译过程:预处理(语法分析)、编译(.c编译成.s文件)、汇编(.s文件变成.o文件)、链接(把多个.o文件合并一个可执行文件)
这里写图片描述

-c自动将文件汇编成.o文件;
-o 表示输出;
-Ttext 0x0000000:表示代码段的地址为0;
-O binary:表示输出二进制

为什么要把代码段的地址设置为0?
2440有两种启动方式,一种是Nand启动,另一种是NoR启动。这里我们采用Nand启动;以Nand方式启动的时候,硬件上会强制把Nand前面4K的内容拷贝到SARM中,内部的4K的SARM的起始地址为0(即CPU从0地址开始取指执行)。我们的程序是烧写到Nand中的,因此需要设置成0地址。如果Nand放一些乱七八糟的数据,而不是放的程序,系统将不会被启动。
这里写图片描述

NOR启动:
Nand ,NoR的地址是不一样的。
NoR的0地址不在SRAM中,而在NOR,CPU也是从0地址开始执行。
NoR Flash可以像内存一样读数据,但是不能像内存一样写数据。

这里写图片描述

首先编译成elf格式的文件,然后将其转换成bin文件,最后将bin文件烧写到开发板中即可。
然后将 文件上传到服务器编译即可。

由上可知ADS中的开发步骤:编辑、编译、烧定/启动

在Linux下开发步骤:编辑、Makefile、烧定/启动

这里写图片描述

最后烧写bin文件即可。

用C语言点亮LCD:

一般我们在做应用程序开发时,一上来就会写main函数。main函数并没有什么特别,一样会被别人调用。执行完以后,一样会返回。那么由谁来调用main函数,执行完以后,返回到哪里。

我们在主机上开发程序时,一般会由一些系统库 加上 我们的main函数,这些系统库是由系统做好的。

在C函数之前,我们需要写启动文件,启动文件负责调用main,设置栈(函数调用)、设置main函数的返回地址;main返回以后启动文件还要负责一些清理工作。

这里写图片描述

所谓的设置栈,就是把栈指针 sp 指向某块内存;这里的这块内存正好是片内的SRAM,不需要初始化就可以使用,如果这块内存是SDRAM,需要先对它进行初始化。
硬件方面的初始化:
1,关看门狗,所谓的看门狗其实是一个定时器,2440默认一电,这个定时器就会自启动,并且倒计时。如果3秒之内没有关闭这个定时器,它就会重启整个系统。
2,初始化时钟,2440最快能跑到400MB HZ,当一上电的话时候只有12MB HZ,如果 我们想程序跑的更快,需要对其修改。
3,初始化SDARM.

启动文件可以称之为软件相关的初始化。
硬件初始化+软件初始化 = 启动文件

这里写图片描述

先看一下本LED的启动文件代码:

@******************************************************************************
@ File:crt0.S
@ 功能:通过它转入C程序
@******************************************************************************       

.text
.global _start
_start:
            ldr     r0, =0x53000000     @ WATCHDOG寄存器地址
            mov     r1, #0x0                     
            str     r1, [r0]            @ 写入0,禁止WATCHDOG,否则CPU会不断重启

            ldr     sp, =1024*4         @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K
                                        @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
            bl      main                @ 调用C程序中的main函数
halt_loop:
            b       halt_loop

首先需要关看门狗,只需要向想就的寄存器写入0即可,这样系统就不会不断的重启。代码如下:

            ldr     r0, =0x53000000     @ WATCHDOG寄存器地址
            mov     r1, #0x0                     
            str     r1, [r0]            @ 写入0,禁止WATCHDOG,否则CPU会不断重启

接着设置栈,让sp 指向内存。

            ldr     sp, =1024*4         @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K
                                        @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K

在片内的内存从0地址开始。

设置返回地址:
bl 指令会跳转到main函数,并且把main的返回地址存在lr寄存器里面

 bl      main                @ 调用C程序中的main函数

清理工作:
这里没有做任何的工作,只是不断的循环。

halt_loop:
            b       halt_loop

然后看一下C 语言的main函数代码:

#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;
}

关于其中定义的两个宏的理解,先看以下关于指针的代码,如果想让a = 0, 有两种方法;

这里写图片描述

volatile表示让编译器不要去优化它,编译器有时候发现这个变量没有用到,可能不有什么意义,编译器会自做主张对其进行优化。

在C语言代码中,其中

GPFCON = 0x00000100;    // 设置GPF4为输出口, 位[9:8]=0b01

表示设置控制器为输出寄存器。

然后将想关代码上传到服务器进行编译,编译后把bin文件烧写到开发板上面。

用oflash烧写 bin方法:
这里写图片描述

用C语言实现轮流点亮三个LECD:

看原理图,看原理图的方法同第一个LECD的方法一样。

初始化代码如下:

@******************************************************************************
@ File:crt0.S
@ 功能:通过它转入C程序
@******************************************************************************       

.text
.global _start
_start:
            ldr     r0, =0x53000000     @ WATCHDOG寄存器地址
            mov     r1, #0x0                     
            str   r1, [r0]              @ 写入0,禁止WATCHDOG,否则CPU会不断重启

            ldr     sp, =1024*4         @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K
                                        @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
            bl      main                @ 调用C程序中的main函数
halt_loop:
            b       halt_loop

代码同点亮第一个LED的原理一样。

Main函数代码如下:


#define GPFCON      (*(volatile unsigned long *)0x56000050)
#define GPFDAT      (*(volatile unsigned long *)0x56000054)

#define GPF4_out    (1<<(4*2))
#define GPF5_out    (1<<(5*2))
#define GPF6_out    (1<<(6*2))

void  wait(volatile unsigned long dly)
{
    for(; dly > 0; dly--);
}

int main(void)
{
    unsigned long i = 0;

    GPFCON = GPF4_out|GPF5_out|GPF6_out;        // 将LED1,2,4对应的GPF4/5/6三个引脚设为输出

    while(1){
        wait(30000);
        GPFDAT = (~(i<<4));        // 根据i的值,点亮LED1,2,4
        if(++i == 8)
            i = 0;
    }

    return 0;
}

关于GPF4_out、GPF5_out的设置理由如下:
这里写图片描述

GPFDAT = (~(i<<4)); 4,5,6寄存器,i左移4位,刚好是寄存器4开始。

看一下makefile 文件代码:

CFLAGS  := -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -ffreestanding
leds.bin : crt0.S  leds.c
    arm-linux-gcc $(CFLAGS) -c -o crt0.o crt0.S
    arm-linux-gcc $(CFLAGS) -c -o leds.o leds.c
    arm-linux-ld -Ttext 0x0000000 crt0.o leds.o -o leds_elf
#   arm-linux-ld -Tleds.lds  crt0.o leds.o -o leds_elf
    arm-linux-objcopy -O binary -S leds_elf leds.bin
    arm-linux-objdump -D -m arm  leds_elf > leds.dis
clean:
    rm -f   leds.dis leds.bin leds_elf *.o

用按键控制LED:

这里写图片描述

简化版的原理图

这里写图片描述

怎么做?
判断按钮状态(松开还是按下);点/灭LED;
配置按钮为输入管脚,LED为输出;
读按钮状态,根据读到的值来设置LED;

启动文件的代码跟上面的一样,该处忽略;

看一下C代码:

#define GPFCON      (*(volatile unsigned long *)0x56000050)
#define GPFDAT      (*(volatile unsigned long *)0x56000054)

#define GPGCON      (*(volatile unsigned long *)0x56000060)
#define GPGDAT      (*(volatile unsigned long *)0x56000064)

/*
 * LED1,LED2,LED4对应GPF4、GPF5、GPF6
 */
#define GPF4_out    (1<<(4*2))
#define GPF5_out    (1<<(5*2))
#define GPF6_out    (1<<(6*2))

#define GPF4_msk    (3<<(4*2))
#define GPF5_msk    (3<<(5*2))
#define GPF6_msk    (3<<(6*2))

/*
 * S2,S3,S4对应GPF0、GPF2、GPG3
 */
#define GPF0_in     (0<<(0*2))
#define GPF2_in     (0<<(2*2))
#define GPG3_in     (0<<(3*2))

#define GPF0_msk    (3<<(0*2))
#define GPF2_msk    (3<<(2*2))
#define GPG3_msk    (3<<(3*2))

int main()
{
        unsigned long dwDat;
        // LED1,LED2,LED4对应的3根引脚设为输出
        GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk);
        GPFCON |= GPF4_out | GPF5_out | GPF6_out;

        // S2,S3对应的2根引脚设为输入
        GPFCON &= ~(GPF0_msk | GPF2_msk);
        GPFCON |= GPF0_in | GPF2_in;

        // S4对应的引脚设为输入
        GPGCON &= ~GPG3_msk;
        GPGCON |= GPG3_in;

        while(1){
            //若Kn为0(表示按下),则令LEDn为0(表示点亮)
            dwDat = GPFDAT;             // 读取GPF管脚电平状态

            if (dwDat & (1<<0))        // S2没有按下
                GPFDAT |= (1<<4);       // LED1熄灭
            else    
                GPFDAT &= ~(1<<4);      // LED1点亮

            if (dwDat & (1<<2))         // S3没有按下
                GPFDAT |= (1<<5);       // LED2熄灭
            else    
                GPFDAT &= ~(1<<5);      // LED2点亮

            dwDat = GPGDAT;             // 读取GPG管脚电平状态

            if (dwDat & (1<<3))         // S4没有按下
                GPFDAT |= (1<<6);       // LED3熄灭
            else    
                GPFDAT &= ~(1<<6);      // LED3点亮
    }

    return 0;
}

位操作的原因:不要影响不要别的位;以前的代码是直接设置整个寄存的值,这样可能会影响别的位。
这里写图片描述

其中

 GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk);

表示这个三个寄存器对应的位先全部清0;

这里写图片描述

其中GPFCON |= GPF4_out | GPF5_out | GPF6_out;设置相应寄存器的值;

读取GPF寄存器

 dwDat = GPFDAT;             // 读取GPF管脚电平状态
 dwDat = GPGDAT;             // 读取GPG管脚电平状态

这里读取是整个寄存的值,需要根据寄存器的某一位来判断相应管脚的状态。

至此,整个通过按键控制LED的代码分析完毕。

最后总结一下按位操作:

如果想对某一位清0操作?位与
如果 想对某一位置1? 位或

这里写图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值