- 一步步点亮LED硬件工作原理
- 原理图:
开发板上有五颗LED。其中一颗D26:正极接5V,负极接地。
这颗LED上电就会长亮,也就是电源指示灯。
剩下的4颗LED的接法是:正极接3.3V,负极接了Soc上的一个引脚(GPIO),详细接法:
D22: GPJ0_3
D23:GPJ0_4
D24:GPJ0_5
D25:PWMTOUT1(GPD0_1)
- 如何点亮及熄灭LED(GPIO)
正极的电压已经定了(3.3V),而负极接在了Soc的引脚上,可以通过Soc中编程控制负极的电压值,因此我们通过程序控制负极输出低电平,正负极就有了压差,LED当然就亮了。
--------------当下内容开始就与软件关联很大了-----------------------
2.一步步点亮LED_数据手册查阅及相关寄存器
- GPIO概念:general purpose input output
GPIO就是芯片的引脚,可以被编程控制。
- GPIO相关的寄存器介绍
软件操作硬件的接口:寄存器
目前想操作LED,而LED实际是通过GPIO来间接控制,所以当前我们实际操作的设备是Soc的GPIO,要操作这些GPIO,必须通过设置他们的寄存器
根据数据手册,GPJ0相关的寄存器如下:
GPJ0CON(GPJ0 control)GPJ0控制寄存器,用来配置个引脚的工作模式
GPJDAT(GPJ0 data)控制高低电平
GPJ0PUD
GPJ0DRV
GPJ0CONPDN
GPJ0PUDPDN
实际上真正操控LED的硬件,主要的有:GPJ0CON,GPJ0DAT两个
如何点亮LED,编程步骤:
- 操控GPJ0CON寄存器中,选中output模式(输出)
- 操控GPJ0DAT寄存器中,相应的位设置为0(低电平)
3.一步步点亮LED_从零开始手写汇编点亮LED
--------------正式开始写代码啦--------------
- GPxCON、GPxDAT寄存器分析
每个寄存器都是32位的,
GPJ0CON寄存器中设置8个引脚的工作模式(32/8=4,每个引脚可以分到4位,譬如GPJ0_0对应的bit位为bit0~bit3,工作方法:给相应的寄存器写入相应的值,该引脚硬件就会按照相应的模式去工作。譬如给bit12~bit15写入0b0001,GPJ0_3引脚就成为输出模式了)
- 从零开始写代码操作寄存器
需要哪些先决条件?
- GPJ0_3 GPJ0_4 GPJ0_5 低电平亮,高电平灭
- GPJ0CON(0xE0200240)寄存和GPJ0DAT(0xE0200244)寄存器
- 工程管理:Makefile等
根据以上分析,我们知道代码的写法了,就是将相应的内容写相关的寄存器,其他寄存器不用理会。
- 编译下载运行看结果
编译时用我们的工程管理,直接make编译得到led.bin,210.bin
下载运行可以用usb启动dnw下载:也可以用sd卡烧录下载
第一个裸机程序成功!
/*
*文件名: led.s
*作者: 刺客
*描述: 这是一步步点亮LED的第一个裸机程序
*/
_strat:
//第一步:把0x11111111写入0xE200240(GPJ0CON)位置
ldr r0, =0x11111111 //ldr伪指令,从后面的“=”可以看出用的是ldr伪指令
ldr r1, =0xE0200240 //因为需要编译器来判断这个数是合法还是非法立即数。一般写代码都用lsr伪指令
str r0, [r1] //寄存器间接寻址,功能是把r0中的数写入到r1中的数为地址的内存去
//第二步:把0x0写入0xE0200244(GPJ0DAT)位置
ldr r0, =0x0
ldr r1, =0xE0200244
str r0, [r1] //把0写入GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
flag: //这两行写了一个死循环。因为逻辑程序时直接在CPU上运行的,CPU会
b flag //逐行运行裸机程序知道CPU断电关机。如果我们程序所有的代码
//都执行完了,CPU就会跑飞(跑飞以后是未定义的,所以千万不要让CPU跑飞),
//不让CPU跑飞的办法就是在整个程序执行完后添加死循环
//"0" he "o" 的细小差别!!!
- 使代码更加漂亮点
- 宏定义来定义寄存器地址,再来操作
- 用 b . 来实现死循环
- 用 .global 把_start链接属性改为外部,消除警告
总结,
软件到底是怎么控制硬件的?
通过寄存器控制
为什么程序一运行硬件就跟着动?
程序运行,影响寄存器的输出状态,从而控制寄存器的表现,寄存器在通过电平等影响硬件端口状态从而达到”控制“一说。
软件编程控制硬件的接口就是寄存器。
4.一步步点亮LED_使用位运算实现复杂点亮要求
- 问题:只让中间那颗亮。
分析:实际上程序就是写了GPJ0CON和GPJ0DAT两个寄存器,功能更改就从这两个下手
GPJ0CON寄存器不用修改,GOJ0DAT设置相应的输出值即可
- 直接解法
GPJ0DAT = 0x28
0
0
1
0
1
0
0
0
7
6
5
4
3
2
1
0
总结:
- 这样写可以解决问题
- 缺陷是需要人为的去计算这个特定的设置值,而且代码也容易晦涩难懂
解决方案:在写代码时用位运算去让编译器帮我们计算这个特定值
- 常用位运算:与、或、非、移位
位与(&)位或(|)位非(取反 ~)移位(左移 <<右移>>)
使用位运算实现需求:
1<<3 等于 0b1000
1<<5 等于 0b100000
(1<<3)|(1<<5)等于 0b101000
扩展:两边亮,中间不亮
Ldr r0, =(0<<3) | (1<<4) | (0<<5)
5.一步步点亮LED_汇编编写延时函数并实现LED闪烁效果
- 闪烁效果原理
闪烁 =亮 +延时 +灭 +延时 +亮 +延时….
- 延时函数原理
在汇编中实现延时的方法:用一些没有目的的代码来执行消耗时间,达到延时的效果
- 汇编编写延时函数,用一个寄存器存放一个数字,然后在循环中每个循环里给数字减1,然后判断这个数字的值是否为0,如果为0则停止,如果不为0则继续循环
- 汇编编写及调用函数的方式
汇编中整个汇编的主程序是一个死循环,这个死循环是我们汇编程序的主体,类似于C中的main函数。其他汇编函数必须写在主死循环程序的后面(死循环外面),不然会出错。
汇编中调用函数用bl指令,子函数中最后用mov pc,lr来返回。
- /*
- *文件名: led.s
- *作者: 刺客
- *描述: 这是一步步点亮LED的第一个裸机程序
- */
- #define GPJ0CON 0xE0200240
- #define GPJ0DAT 0xE0200244
- .global _start //把_strat链接属性改为外部,这样其他文件就可以看见_strat了
- _start:
- //第一步:把所有引脚都设置为输出模式,代码不变
- ldr r0, =0x11111111 //ldr伪指令,从后面的“=”可以看出用的是ldr伪指令
- ldr r1, =GPJ0CON //因为需要编译器来判断这个数是合法还是非法立即数。一般写代码都用lsr伪指令
- str r0, [r1] //寄存器间接寻址,功能是把r0中的数写入到r1中的数为地址的内存去
- flash:
- //第二步:所有都点亮
- //ldr r0, =(1<<3)|(1<<5) //等效于0b00101000,即0x28
- ldr r0, =(0<<3)|(0<<4)|(0<<5) //让两边亮,中间不亮
- ldr r1, =GPJ0DAT
- str r0, [r1] //把0写入GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
- //第三步:延时
- bl delay //使用bl进行函数调用
- //第四步:所有都熄灭
- ldr r0, =(1<<3)|(1<<4)|(1<<5) //让两边亮,中间不亮
- ldr r1, =GPJ0DAT
- str r0, [r1] //把0写入GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
- //第五步:延时
- bl delay
- b flash
- //第六步:所有都点亮
- //ldr r0, =(0<<3)|(0<<4)|(0<<5) //让两边亮,中间不亮
- //ldr r1, =GPJ0DAT
- //str r0, [r1] //把0写入GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
- // flag: //这两行写了一个死循环。因为逻辑程序时直接在CPU上运行的,CPU会
- // b flag //逐行运行裸机程序知道CPU断电关机。如果我们程序所有的代码
- //都执行完了,CPU就会跑飞(跑飞以后是未定义的,所以千万不要让CPU跑飞),
- //不让CPU跑飞的办法就是在整个程序执行完后添加死循环
- // b . // . 代表当前指令的地址,高大上的死循环~
- //延时函数:函数名:delay
- delay:
- ldr r2, =9999999
- ldr r3, =0x0
- delay_loop:
- sub r2, r2, #1 //r2 = r2 - 1
- cmp r2, r3 //cmp会影响Z标志位,如果r2等于r3则Z=1,下一句eq就会成立
- bne delay_loop
- mov pc, lr //函数调用返回
6.一步步点亮LED_流水灯效果
- 流水灯原理
实现效果:挨着的LED一次点亮熄灭(同时只有1颗LED亮的)
LED3亮延时+LED4亮延时+LED5亮延时
用位运算取反实现更加简洁的实现方式:
ldr r0, =(0<<3)|(1<<4)|(1<<5)
等于
ldr r0, =~(1<<3)
编程操控一个硬件的步骤:1.分析硬件工作原理 2.分析原理图 3.分析数据手册 4.找到相关的SFR 5.写代码设置寄存器得到想要的效果
任务:
将第四颗LED点亮,然后实现四颗流水灯。
点亮成功!
/*
*文件名: led.S
*作者: 刺客
*描述: 流水灯
*/
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define GPD0CON 0xE02000A0
#define GPD0DAT 0xE02000A4
.global _start
_start:
//GPD0CON
ldr r0, =0x11111111
ldr r1, =GPD0CON
str r0, [r1]
//GPJ0CON
ldr r0, =0x1111111
ldr r2, =GPJ0CON
str r0, [r2]
flash:
//GPJ0DAT
ldr r0, =~(1<<3)
ldr r3, =GPJ0DAT
str r0, [r3]
bl delay
//GPJ0DAT
ldr r0, =~(1<<4)
ldr r3, =GPJ0DAT
str r0, [r3]
bl delay
//GPJ0DAT
ldr r0, =~(1<<5)
ldr r3, =GPJ0DAT
str r0, [r3]
bl delay
//GPDODAT
ldr r0, =~(1<<2)
ldr r1, =GPD0DAT
str r0, [r1]
bl delay
b flash
//延时函数:
delay:
ldr r4, =9999999
ldr r5, =0x0
delay_loop:
sub r4, r4, #1
cmp r4, r5
bne delay_loop
mov pc, lr
GPD0_0 LED并没有实现流水灯效果,在第一次的短暂亮灯后,就熄灭了。。
原因不明。
7.反汇编工具objdump的使用简介
- 反汇编的原理及用途
arm-linux-objdump -D led.elf > led_elf.dis
objdump是gcc工具链中的反汇编工具,作用是由编译链接接好的elf格式的可执行程序反过来得到汇编源代码
-D表示反汇编 > 左边的是elf的可执行程序(反汇编时的原材料),>右边的是反汇编生成的反汇编程序
反汇编的原因:
- 逆向破解
- 调试程序时,反汇编代码可以帮助我们理解程序,用来学习理解,尤其在链接脚本、链接地址等概念
- 把C语言源代码编译链接生成的可执行程序反汇编后得到对应的汇编代码,可以帮助理解C语言和汇编语言之间的对应关系。非常有助于深入理解C语言。
- 反汇编文件的格式和看法
汇编 assembly 反汇编 dissembly
标号地址、标号名字、指令地址、指令机器码、指令机器码反汇编到的指令
扩展:ARM汇编中用地址池方式来实现非法立即数
- 初始指令地址
下载烧录执行的bin文件,内部其实是一条一条的指令机器码。这些指令每一条都有一个指令地址,这个地址
是连接的时候ld给指定的(ld根据我们写的链接脚本来指定)
反汇编的时候得到的指令地址是链接器考虑了链接脚本之后得到的地址,而我们写代码时通过指定链接脚本来让链接器给我们链接合适的地址。
但是有时候我们写的链接脚本有误,或者我们不知道这个链接脚本会怎么样,这时候可以通过看反汇编文件来分析这个链接脚本的效果,看是不是我们想要的,如果不是可以改了再看
总结:今天的学习较昨天有趣多了,能够自己动手写些代码了,虽然有些难懂
- 汇编语言的大量运用,有些基础应该补补
- 由于长时间未编程写代码,导致总是犯些小错误比如把“0”写成了“o”
- 还是需要汇编的语言的熟悉!
- 在位运算中,对C高级的需求凸显。