第2课 GPIO实验

volatile关键词的作用

是为了改善编译器的优化能力,例如,可以将一个指针指向某个硬件位置,其中包含了来自窗口的时间或者信息。

在这种情况下,硬件而不是程序可能修改其中的内容,例如,假如编译器发现,程序在几条语句中两次使用了某个变量的值,比如*p,则编译器可能不是

让程序去查找这个值两次,而是把这个值缓存到寄存器中,这种优化假设变量的值在这两次使用之间不会变化。如果不将变量声明为volatile,则编译器将

进行这种优化,使用后,将告诉编译器,不要进行这种优化;


volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFDATA;;

不要对p中的内容即*p 进行优化



1.用汇编语言点亮一盏灯.  全过程涉及到2个寄存器

.global _start

_start::

LDR R0,=0x56000050 @RO为GPFCON寄存器。配置GPIO口的功能

MOV R1,#0x00000100

STR R1,【R0】

LDR R0,=0x56000054

MOV  R1,#0x00000000 //立即数不能超过8位

STR  R1,【R0】

MAIN_LOOP:

B MAIN_LOOP  @主循环


对应的Makefile文件

led_on.bin:led_on.S

arm-linux-gcc -g -c -o led_on,o led_on.S     //-g   Add some debug imformation 

arm-linux-ld  -Ttext0x0000000 -g led_on.o  -o led_on_elf   //  -Ttext 0x0000000 代表代码段的地址是0  elf文件为UNIX系统二进制文件接口

arm-linux-objcopy -O binary -S led_on_elf

clean:

rm -f  led_on.bin led_on_elf   *.o

预处理

编译 .c ->.S

链接 多个.o合并成一个执行文件.bin


-S 

--strip-all 

不从源文件拷贝符号信息和relocation信息。 


通过指定输入目标为而进制文件(例如-O binary),objcopy可以生成原始格式的二进制文件。当objcopy生成一个原始格式的二进制文件的时候,它会生成输入的目标文件的基本内存拷贝,然后所有的标号和可重定位信息都会被去掉。内存拷贝开始于最低段的加载地址,拷贝到输出文件。 


.text .global 是arm-gcc编译器的关键词。
.text 指定了后续编译出来的内容放在代码段【可执行】;

.global 告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】;

在本例中,_start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,供其它程序【如加载器】寻找到。



另外还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。
ldr伪指令可以在立即数(这里个人觉得常数更适合)前加上=,以表示把一个地址写到某寄存器中,比如:
ldr r0, =0x12345678
这样,就把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的。只不过mov指令限制了立即数的长度为8位,也就是不能超过512。
而ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。

ldr伪指令和ldr指令不是一个同东西。


MOV可以把寄存器内容,立即数传入一个寄存器里(注意,这里绝对不可以是内存),该立即数必须可以通过一个8位数循环移位偶数次得到。


LDR   r0,=label     用于加载立即数或一个地址值到指定寄存器中

如果label是立即数:  LDR r0,=0X123   ;将0X123存入r0中   

如果name是个标识符:  LDR   r0,=label_1   ;将label_1所指向的地址值存入r0中 

LDR       r0,[r1]   ;将R1中的值存到r0中    

LDR       r1,[r2,#16]    ;将(r2+16)地址中的内容存到r1中 

LDR       r1,[r2],#4    ;将r2地址中的内容存到r1中,同时r2=r2+4

STR   r1,[r2]        ; 将r1中的值存到r2所指定的地址中 

STR   r1,[r2,#4]   ;将r1中的值存到r2+4所指定的地址中 

STR   r1,[r2],#4  ;将r1中的值存到r2所指定的地址中, 同时r2=r2+4



2.通过c语言点亮一盏灯

需要自己编写

启动文件包括硬件初始化,软件初始化

1.设置栈  SP->某块内存  2440为SRAM 自动完成初始化。假如为外挂SDRAM,则需要手动初始化。硬件方面的初始化:

关看门狗,

初始化时钟,(2440最快400M,上电默认12M)

初始化SDRAM。没有不需要

2.设置返回地址

3.调用main

4.清理工作

对应的汇编启动文件

file:crt0.S

.text

.global_start

_start:

ldr r0,=0x53000000 @看门狗寄存器地址

mov r1,#0x0

str r1,[r0]

ldr sp,=1024*4

bl main @call main

halt_loop:

b halt_loop


lr为调用子程序命令,halt_loop:为一个自定义的标号

B指令(Branch)表示无条件跳转.

   B main ;跳转到标号为main地代码处

 

BL指令(Branch with Link)表示带返回值的跳转.

   BL比B多做一步,在跳转前,BL会把当前位置保存在R14(即LR寄存器),当跳转代码结束后,用MOV PC,LR指令跳回来,这实际上就是C语言执行函数的用法,

   汇编里调子程序都用BL,执行完子函数后,可以用MOV PC,LR跳回来.

   BL delay ;执行子函数或代码段delay ,delay可以为C函数.



上电时NAND前4K内容复制到片内SRAM,PC->0x0地址,初始化时让SP->最高地址,因为底部地址已经被使用。让栈向下增长。



For instance: 按键控制LED,按键按下LED亮,松开LED灭。

启动文件包括硬件初始化,软件初始化

1.设置栈  SP->某块内存  2440为SRAM 自动完成初始化。假如为外挂SDRAM,则需要手动初始化。硬件方面的初始化:

关看门狗,

初始化时钟,(2440最快400M,上电默认12M)

初始化SDRAM。没有不需要

2.设置返回地址

3.调用main

4.清理工作


crt0.S 启动文件

.text

.global _start

_start:

ldrr0,=0x53000000

movr1,#0x0

strr1,[r0]  //close Watch dog

ldr  sp,=1024*4

bl main

halt_loop:

b halt_loop


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



//********* Important

这种位操作很常见。

条件位 |

掩码位 &

对应一种状态

思想:先屏蔽掉某些位对其置0,然后按位或确定状态 |。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值