zoukankan      html  css  js  c++  java
  • 第8课.第一个ARM裸板程序(点亮led)及申引

    1.原理图


    2.芯片手册

    3.几条汇编代码

    1.ldr:读内存
        ldr R0, [R1]
        假设R1的值是x,读取地址x上的数据(4字节),保存到R0中
        ldr R0, =0x12345678            (4字节)
        R0 = 0x12345678    此语句是伪指令,它会被分为几条真正的ARM指令
    2.str:写内存命令
        str R0, [R1]
        假设R1的值是x,把R0的值写到地址x(4字节)
    3.b:跳转
    4.mov:
        mov R0, R1            把R1的值赋给R0,R0 = R1
        mov R0, #0x100        R0 = 0x100    (#0x100立即数)
        注意:
            mov R0, =0x12345678    不可,mov只能表示简单值(被称为立即数)
            而ldr R0, =任意值
    5.
        add r0, r1, #4        r0 = r1 + 4
        sub r0, r1, #4        r0 = r1 - 4
        sub r0, r1, r2        r0 = r1 - r2
    6.bl:跳转
        bl xxx
        跳转到xxx,把返回地址保存在lr寄存器(下一条指令的地址)
    7.ldm:读内存,写入多个寄存器
      stm:把多个寄存器的值写入内存
        ldm:    ia:内存过后增加    ib:内存预先增加
        stm:    da:内存过后减少    db:内存预先减少
    
        stmdb sp!, {fp, ip, lr, pc}        假设:sp = 4096
                    R11 R12 R14 R15        高编号在高地址
        解析:
            !:被修改的sp不为原始值,为加减后的值
    

        ldmia sp, {fp, sp, pc}        假设:sp = 4080
        解析:
            无!,修改后的地址值不存入sp中
    

    4.汇编代码

    /*
     *	点亮led
     */
    
    .text
    .global _start
    _start:
    
    	
     
    /*	配置gpf4为输出引脚
     *	把0x100写到地址0x56000050
     */
     	ldr r1, = 0x0x56000050
    	ldr r0, = 0x100
    	str r0, [r1]
    
    /* 设置gpf4输出高电平
     * 把0x10写到地址0x56000054
     */
    	ldr r1, = 0x56000054
    	ldr r0, = 0x10
    	str r0, [r1]
    
    /*死循环*/
    halt:
    	b halt
    

    解析:

    1:.text部分是处理器开始执行代码的地方,指定了后续编译出来的内容放在代码段【可执行】,是arm-gcc编译器的关键字
    2:.global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用;告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可能是函数名】
    3:.global _start让_start符号成为可见符号,这样链接器就知道跳转到程序的什么地方并开始执行
    4:_start是默认起始地址,也是编译,链接后程序的起始地址,由于程序是通过加载器来加载的,必须要找到_start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,供其它程序【如加载器】寻找到
    

    Makefile

    all:
    	arm-linux-gcc -c -o led_on.o led_on.S
    	arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
    	arm_linux-objcopy -O binary -S led_on.elf led_on.bin
    
    clean:
    	rm *.bin *.o *.elf
    

    5.寄存器

    cup:可直接访问的寄存器

    r0-r3:参数结果寄存器。可以用来传参数
    r4-r11:可以参与程序的操作。如果使用到了它们,则需要在函数的入口保存它们,在函数的出口恢复它们
    sp:栈指针
    lr:用来保存返回地址
    pc:程序计数器。当把一个地址写到pc时,cpu会跳到地址去执行。pc的值为当前地址 + 8 
    

    其他寄存器需要通过地址来访问

    6.ARM三级流水线

    PC=当前指令+8
    流水线结构:

    当前执行地址A的地址
    已经在对地址A + 4的指令进行译码了
    已经在读取地址A + 8的指令(pc的值)
    

    7.2440启动流程

    NOR启动

    nor启动的时候,nor flash自身地址为0,片内4KRAM为0x4000 0000
    程序直接在nor上读取,运行
    nor能够像内存一样的读取,但不能直接写
    

    NAND启动

    片内4KRAM的地址为0x0000 0000,nor flash不可见
    自动复制Nand前4K的程序到片内RAM中运行
    

    堆栈设置:(栈区用来保存寄存器和局部变量)

    NOR启动

     sp = 0x40000000 + 4096
    

    NAND启动

    sp = 4096
    

    自动判断NOR启动还是NAND启动

    先读出0地址的值,在写0到0地址后,读出0地址中的值。
    如果读出来的值和写入的值不一样。表示0地址上的值被修改了,它对应ram地址为0x0000 0000,为NAND启动。否则为NOR启动
    最后要把0地址的值复原
    

    8.C语言代码

    start.S

    .text
    .global _start
    _start:
    
    	/*	设置内存: sp栈	*/
    	ldr sp, =4096		/*	NAND启动	*/
    	//ldr sp, =0x40000000 + 4096	/*	NOR启动	*/
    
    	/*	调用main函数	*/
    	bl main
    
    halt:
    	b halt
    

    led.c

    int main()
    {
    	unsigned int* pGPFCON = (unsigned int*)0x56000050;
    	unsigned int* pGPFDAT = (unsigned int*)0x56000054;
    
    	/*	配置GPF4为输出引脚	*/
    	*pGPFCON = 0x100;
    
    	/*	配置GPF4为输出为0	*/
    	*pGPFDAT = 0;
    
    	return 0;
    }
    

    Makefile

    all:
    	arm-linux-gcc -c -o led.o led.c
    	arm-linux-gcc -c -o start.o start.S
    	arm_linux-ld -Ttext 0 start.o led.o -o led.elf
    	arm-linux-objcopy -O binary -S led.elf led.bin
    	arm-linux-objdump -D led.elf > led.dis
    
    clean:
    	rm *.o *.elf *.bin *.dis
    

    解析:

    arm-linux-objdump -D led.elf > led.dis
    

    这句意义是生成反汇编文件,用于查看和分析

    9.ATPCS规则

    这里直接引用别人的文章
    https://www.cnblogs.com/zongzi10010/p/10023531.html

    10.栈区存储示意图

    栈顶存储寄存器和局部变量
    栈底存储代码段(未重定义前,代码都存储在RAM中只能接受4k的代码)

    11.延时点亮LED

    start.S

    .text
    .global _start
    _start:
    
    	ldr r0, = 4096		/*	NAND 启动	*/
    
    	mov r0, #4
    	bl led_on
    
    	ldr r0, =10000
    	bl delay
    
    	mov r0, #5
    	bl led_on
    
    halt:
    	b halt
    

    解析:

    这里使用到了r0-r3可以传递参数的功能
    

    led.c

    void delay(int i)
    {
    	while(i--);
    }
    
    int led_on(int which)
    {
    	/*	配置地址	*/
    
    	unsigned int *pGPFCON = (unsigned int *)0x56000050;
    	unsigned int *pGPFDAT = (unsigned int *)0x56000054;
    
    	if(which == 4)
    		{
    		
    		/*	配置GPF4为输出	*/
    		
    			*pGPFCON = 0x100;
    		}
    	else if(which == 5)
    		{
    		
    		/*	配置GPF5为输出	*/
    		
    			*pGPFCON = 0x500;
    		}
    	/*	配置GPF4和GPF5输出为0	*/
    
    	*pGPFDAT = 0;
    
    	return 0;
    
    }
    

    12.看门狗

    汇编代码

    /*    关闭看门狗    */
    ldr r0, =0x53000000
    ldr r1, =0
    str r1, [r0]
    

    13.自动判断NOR启动还是NAND启动

    汇编代码

    /* 设置内存: sp 栈 */
    /* 分辨是nor/nand启动
     * 写0到0地址, 再读出来
     * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
     * 否则就是nor启动
     */
    mov r1, #0
    ldr r0, [r1]        /* 读出原来的值备份 */
    str r1, [r1]        /* 0->[0] */ 
    ldr r2, [r1]        /* r2=[0] */
    cmp r1, r2            /* r1==r2? 如果相等表示是NAND启动 */
    ldr sp, =0x40000000 + 4096        /* 先假设是nor启动 */
    moveq sp, #4096            /* nand启动 */
    streq r0, [r1]             /* 恢复原来的值 */
    

    解析:

    moveq:cmp r1, r2相等时执行
    streq:cmp r1, r2相等时执行
    

    14.按键控制LED

    原理图

    led.c

    #include "s3c2440_soc.h"
    
    void delay(volatile int d)
    {
    	while(i--);
    }
    
    int main(void)
    {
    	int val1, val2;
    
    	/* 设置GPFCON让GPF4/5/6配置为输出引脚 */
    	GPFCON &= ~((3<<8) | (3<<10) | (3<<12));	// 清零
    	GPFCON |=  ((1<<8) | (1<<10) | (1<<12));
    
    	/* 配置3个按键引脚为输入引脚:
    	 * GPF0(S2),GPF2(S3),GPG3(S4)
    	 */
    	GPFCON &= ~((3<<0) | (3<<4));  /* gpf0,2 */
    	GPGCON &= ~((3<<6));  /* gpg3 */
    
    	/* 循环点亮 */
    	while (1)
    	{
    		val1 = GPFDAT;
    		val2 = GPGDAT;
    
    		if (val1 & (1<<0)) /* s2 --> gpf6 */
    		{
    			/* 松开 */
    			GPFDAT |= (1<<6);
    		}
    		else
    		{
    			/* 按下 */
    			GPFDAT &= ~(1<<6);
    		}
    
    		if (val1 & (1<<2)) /* s3 --> gpf5 */
    		{
    			/* 松开 */
    			GPFDAT |= (1<<5);
    		}
    		else
    		{
    			/* 按下 */
    			GPFDAT &= ~(1<<5);
    		}
    
    		if (val2 & (1<<3)) /* s4 --> gpf4 */
    		{
    			/* 松开 */
    			GPFDAT |= (1<<4);
    		}
    		else
    		{
    			/* 按下 */
    			GPFDAT &= ~(1<<4);
    		}
    
    		
    	}
    
    	return 0;	
    }
    

    ARM指令集百度云文档:
    https://pan.baidu.com/s/1E2JhzBlJHgLbZ7hqZWmXIw

  • 相关阅读:
    xcode构建webdriverAgent时报错Messaging unqualified id的解决办法
    ubuntu18.0安装RabbitMQ
    python中*的用法
    Jenkins构建项目
    Jenkins安装与配置
    git_仓库
    六、 Shell数组应用
    五、 Shell函数应用
    三、 Shell流程控制
    二、 Shell变量定义
  • 原文地址:https://www.cnblogs.com/huangdengtao/p/12084817.html
Copyright © 2011-2022 走看看