ARM裸机:一步步点亮LED(汇编)

硬件工作原理及原理图查阅

  • LED物理特性介绍
    LED本身有2个接线点,一个是LED的正极,一个是LED的负极。LED这个硬件的功能就是点亮或者不亮,物理上想要点亮一颗LED只需要给他的正负极上加正电压即可,要熄灭一颗LED只需要去掉电压即可。

  • 查阅原理图了解板载LED硬件接法
    查阅原理图,发现开发板上一共有5颗LED。其中一颗D26的接法是:正极接5V,负极接地。因此这颗LED只要上电就会常亮。因此我们分析这颗LED是电源指示灯。
    剩下4颗LED的接法是:正极接3.3V,负极接了SoC上的一个引脚(GPIO),具体详细接法是:
    D22:GPJ0_3
    D23:GPJ0_4
    D24:GPJ0_5
    D25:PWMTOUT1(GPD0_1)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 分析如何点亮及熄灭LED(GPIO)
    分析:LED点亮的要求是:正极和负极之间有正向电压差。
    思考:在开发板上如何为LED制造这个电压差让它点亮呢?
    解答:因为正极已经定了(3.3V),而负极接在了SoC的引脚上,可以通过SoC中编程来控制负极的电压值,因此我们可以通过程序控制负极输出低电平(0V),这样在正负极上就有了压差,LED即可点亮。

数据手册查阅及相关寄存器浏览

  • GPIO概念的引入
    GPIO:general purpose input output 通用输入输出
    GPIO就是芯片的引脚(芯片上的引脚有些不是GPIO,只有一部分是),作为GPIO的这类引脚,他的功能和特点是可以被编程控制它的工作模式,也可以编程控制他的电压高低等。
    通过之前的分析我们知道,我们设计电路时就把LED接在了一个GPIO上,这样我们就可以通过编程控制GPIO的模式和输入输出值来操控LED亮还是灭;如果你当时设计电路时把LED接在非GPIO上那就不可能了。

  • 阅读数据手册中有关部分
    当我们想要通过编程操控GPIO来操作LED时,我们首先需要通读一下S5PV210的数据手册中有关于GPIO的部分,这部分在数据手册的Section2.2中。
    《S5PV210_UM_REV1.1.pdf》
    在这里插入图片描述

  • GPIO相关的寄存器介绍
    回忆下之前说过的,软件操作硬件的接口是:寄存器。
    我们当前要操作的硬件是LED,但是LED实际是通过GPIO来间接控制的,所以当前我们实际要操作的设备其实是SoC的GPIO。要操作这些GPIO,必须通过设置他们的寄存器。
    在这里插入图片描述

    查阅数据手册可知,GPJ0相关的寄存器有以下:
    GPJ0CON, (GPJ0 control)GPJ0控制寄存器,用来配置各引脚的工作模式
    GPJ0DAT, (GPJ0 data)当引脚配置为input/output模式时,寄存器的相应位和引脚的电平高低相对应。
    GPJ0PUD, (pull up down)控制引脚内部弱上拉、下拉
    GPJ0DRV, (driver)配置GPIO引脚的驱动能力
    GPJ0CONPDN,(记得是低功耗模式下的控制寄存器)
    GPJ0PUDPDN (记得是低功耗模式下的上下拉寄存器)
    注:在驱动LED点亮时,应该将GPIO配置为output模式。

    实际上真正操控LED的硬件,主要的有:GPJ0CON, GPJ0DAT 这么2个。
    如何点亮LED,编程的步骤是:
    1、操控GPJ0CON寄存器中,选中output模式
    2、操控GPJ0DAT寄存器,相应的位设置为0

开始手写汇编点亮LED

D22:GPJ0_3
D23:GPJ0_4
D24:GPJ0_5

在这里插入图片描述
在这里插入图片描述

GPJ0CON(0xE0200240)寄存器和GPJ0DAT(0xE0200244)寄存器

第一步:设置引脚模式为输出模式(向GPxCON寄存器写入0001)
第二步:写入控制的数据(向GPxDAT寄存器写入0输出低电平,LED亮;1输出高电平,LED灭)

文件目录:
在这里插入图片描述

led.S:

_start:
	//把0x1111 1111 写入 GPJ0CON(0xE0200240)
	//这里 ldr 是Load Register(加载寄存器)的缩写,
	//用于从给定的地址加载数据到处理器寄存器r0。
	//=0x11111111是一个立即数常量,
	//表示存储在内存中的十六进制数值 11111111
	ldr r0, =0x11111111 //ldr伪指令,编译器判断立即数是否合法

	//将立即数 0xE0200240加载到寄存器r1中
	ldr r1, =0xE0200240

	//str是Store Register(存储寄存器)的缩写,
	//它将寄存器r0中的值存储到[r1]所指向的位置。
	//这里的[r1]是对另一个内存地址的操作符,
	//意味着将r0的内容放到r1当前内容指明的那个内存位置
	//将r0的值存储到r1指向的内存地址处
	str r0, [r1] //寄存器间接寻址去

	; //LED灭:将0xff放到GPJ0DAT(0xE0200244)寄存器
	; ldr r0, =0xff
	; ldr r1, =0xE0200244
	; str r0, [r1]

	//LED亮:将 0x0 放到GPJ0DAT(0xE0200244)寄存器
	ldr r0, =0x0
	ldr r1, =0xE0200244
	str r0, [r1]

//结束死循环
falt:
	b falt //直到CPU断电关机

编译结果:
LED常亮

使用位运算实现复杂点亮要求

  • 如何只点亮中间1颗(两边是熄灭的)LED
    //LED亮:将 0xf7 放到GPJ0DAT(0xE0200244)寄存器 亮1颗
    中间一颗:0xEF
    最后一颗:0xdf

  • 常用位运算:与、或、非、移位
    位与(&) 位或(|) 位非(取反 ~) 移位(左移<< 右移>>)

  • 使用位运算实现功能
    1<<3 等于 0b1000
    1<<5 等于 0b100000
    (1<<3)|(1<<5) 等于 0b101000

  • 扩展一下:如何只熄灭中间1颗而点亮旁边2颗
    ldr r0, =((0<<3) | (1<<4) | (0<<5))

汇编编写延时函数并实现LED闪烁效果

延时就是编写一些没有目的的代码,占用CPU的时间。

//延时函数
delay:
	ldr r2, =0x900000
	ldr r3, =0x0
delay_loop:
	cmp r3, r2
	//比较r3 r2  会影响Z标志位  如果r2==r3 则Z=1 下一句当中的ne就会成立
	sub r2, r2, #1 //r2=r2-1
	bne delay_loop //如果r2==r3 就不会执行这句
	mov pc, lr //函数调用返回
#define  GPJ0CON  0xE0200240
#define  GPJ0DAT  0xE0200244
//3、添加链接属性添加
.globl _start //将-start 修改为外部链接属性,其他文件就能找到_start
_start:
	//把0x1111 1111 写入 GPJ0CON(0xE0200240)
	//这里 ldr 是Load Register(加载寄存器)的缩写,
	//用于从给定的地址加载数据到处理器寄存器r0。
	//=0x11111111是一个立即数常量,
	//表示存储在内存中的十六进制数值 11111111
	ldr r0, =0x11111111 //ldr伪指令,编译器判断立即数是否合法

	//将立即数 0xE0200240加载到寄存器r1中
	ldr r1, =GPJ0CON

	//str是Store Register(存储寄存器)的缩写,
	//它将寄存器r0中的值存储到[r1]所指向的位置。
	//这里的[r1]是对另一个内存地址的操作符,
	//意味着将r0的内容放到r1当前内容指明的那个内存位置
	//将r0的值存储到r1指向的内存地址处
	str r0, [r1] //寄存器间接寻址

delay_loop_ok:
	//熄灭中间1颗而点亮旁边2颗
	ldr r0, =((0<<3) | (1<<4) | (0<<5))
	ldr r1, =GPJ0DAT
	str r0, [r1]

	//延时一下:
	bl delay

	ldr r0, =((1<<3) | (0<<4) | (1<<5))
	ldr r1, =GPJ0DAT
	str r0, [r1]

	//延时一下:
	bl delay

	; bne delay_loop_ok
	b delay_loop_ok //死循环

再难一点的流水灯效果

12321的点亮LED

#define  GPJ0CON  0xE0200240
#define  GPJ0DAT  0xE0200244
//3、添加链接属性添加
.globl _start //将-start 修改为外部链接属性,其他文件就能找到_start
_start:
	//把0x1111 1111 写入 GPJ0CON(0xE0200240)
	//这里 ldr 是Load Register(加载寄存器)的缩写,
	//用于从给定的地址加载数据到处理器寄存器r0。
	//=0x11111111是一个立即数常量,
	//表示存储在内存中的十六进制数值 11111111
	ldr r0, =0x11111111 //ldr伪指令,编译器判断立即数是否合法

	//将立即数 0xE0200240加载到寄存器r1中
	ldr r1, =GPJ0CON

	//str是Store Register(存储寄存器)的缩写,
	//它将寄存器r0中的值存储到[r1]所指向的位置。
	//这里的[r1]是对另一个内存地址的操作符,
	//意味着将r0的内容放到r1当前内容指明的那个内存位置
	//将r0的值存储到r1指向的内存地址处
	str r0, [r1] //寄存器间接寻址

delay_loop_ok:

	ldr r0, =((0<<3) | (1<<4) | (1<<5))
	ldr r1, =GPJ0DAT
	str r0, [r1]

	//延时一下:
	bl delay

	ldr r0, =((1<<3) | (0<<4) | (1<<5))
	ldr r1, =GPJ0DAT
	str r0, [r1]

	//延时一下:
	bl delay

	ldr r0, =((1<<3) | (1<<4) | (0<<5))
	ldr r1, =GPJ0DAT
	str r0, [r1]


	//延时一下:
	bl delay

	ldr r0, =((1<<3) | (0<<4) | (1<<5))
	ldr r1, =GPJ0DAT
	str r0, [r1]

	//延时一下:
	bl delay
	
	; bne delay_loop_ok
	b delay_loop_ok //死循环



//结束死循环 2、高级点的死循环
	b .  //直到CPU断电关机 .:当前指令的地址


//延时函数
delay:
	ldr r2, =0x900000
	ldr r3, =0x0
delay_loop:
	cmp r3, r2
	//比较r3 r2  会影响Z标志位  如果r2==r3 则Z=1 下一句当中的ne就会成立
	sub r2, r2, #1 //r2=r2-1
	bne delay_loop //如果r2==r3 就不会执行这句
	mov pc, lr //函数调用返回

位取反操作:

//1、使用宏定义
#define  GPJ0CON  0xE0200240
#define  GPJ0DAT  0xE0200244
//3、添加链接属性添加
.globl _start //将-start 修改为外部链接属性,其他文件就能找到_start
_start:
	//把0x1111 1111 写入 GPJ0CON(0xE0200240)
	//这里 ldr 是Load Register(加载寄存器)的缩写,
	//用于从给定的地址加载数据到处理器寄存器r0。
	//=0x11111111是一个立即数常量,
	//表示存储在内存中的十六进制数值 11111111
	ldr r0, =0x11111111 //ldr伪指令,编译器判断立即数是否合法

	//将立即数 0xE0200240加载到寄存器r1中
	ldr r1, =GPJ0CON

	//str是Store Register(存储寄存器)的缩写,
	//它将寄存器r0中的值存储到[r1]所指向的位置。
	//这里的[r1]是对另一个内存地址的操作符,
	//意味着将r0的内容放到r1当前内容指明的那个内存位置
	//将r0的值存储到r1指向的内存地址处
	str r0, [r1] //寄存器间接寻址

delay_loop_ok:

	ldr r0, =~(1<<3)
	ldr r1, =GPJ0DAT
	str r0, [r1]
	//延时一下:
	bl delay

	ldr r0, =~(1<<4)
	ldr r1, =GPJ0DAT
	str r0, [r1]
	//延时一下:
	bl delay

	ldr r0, =~(1<<5)
	ldr r1, =GPJ0DAT
	str r0, [r1]
	//延时一下:
	bl delay


	; bne delay_loop_ok
	b delay_loop_ok //死循环



//结束死循环 2、高级点的死循环
	b .  //直到CPU断电关机 .:当前指令的地址


//延时函数
delay:
	ldr r2, =0x900000
	ldr r3, =0x0
delay_loop:
	cmp r3, r2
	//比较r3 r2  会影响Z标志位  如果r2==r3 则Z=1 下一句当中的ne就会成立
	sub r2, r2, #1 //r2=r2-1
	bne delay_loop //如果r2==r3 就不会执行这句
	mov pc, lr //函数调用返回

反汇编工具objdump的使用简介

反汇编的原理&为什么要反汇编
arm-linux-objdump -D led.elf > led_elf.dis
objdump是gcc工具链中的反汇编工具,作用是由编译链接好的elf格式的可执行程序反过来得到汇编源代码
-D表示反汇编 > 左边的是elf的可执行程序(反汇编时的原材料),>右边的是反汇编生成的反汇编程序

  • 反汇编的原因
    1、逆向破解
    2、调试,理解程序链接脚本、链接地址
    3、理解C语言和汇编语言的关系

(汇编 assembly 反汇编 Disassembly)
标号的实质是地址
指令被转换为机器码
地址池实现非法立即数
在这里插入图片描述

bin文件内部是机器码,机器码会有指定的指令地址,使用ld链接在一起

总结

1、知道LED点亮原理
2、查看原理图知道接线方式
3、查看数据手册知道寄存器地址
4、开始编程
5、编译可执行文件
6、下载到设备上
7、添加延时达到流水灯
8、位操作增加可读性,同时比较简略
9、反汇编工具objdump可以帮助理解程序

学习记录,侵权联系删除。
来源:朱老师物联网大课堂

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

li星野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值