|
注意事项: 1、新建文件led.S 后缀为大写 2、 文件开始加.globl _start 不然报错: warning: cannot find entry symbol _start; defaulting to 00000000
3、删除编译出来文件可以执行:make clean
make clean 指令执行前
make clean 指令执行后
|
|
|
|
|
设置GPJ0CON 8个I/O口全部为输出 如果GPJ0CON设置为输入,那么GPJ0DAT b0到b7对应输入的状态
如果GPJ0CON设置为输出,那么GPJ0DAT b0到b7对应设置输出状态
|
|
|
|
|
|
|
|
|
|
|
|
|
/* *文件名:led.s *作者:宗虎冬 *描述:这是arm裸机点亮led灯第一个程序 * */ .globl _start _start: //第一步:把0x11111111写入到0xE0200240(GPJ0CON)位置 ldr r0, =0x11111111 //从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 ldr r1, =0xE0200240 //是合法立即数还是非法立即数。一般写代码都用ldr伪指令 str r0, [r1] //寄存器间接寻址,功能是把r0的数写入到r1中的数为地址的内存中去 //第二部:把oxE0200244(GPJ0DAT)位置 ldr r0, =0x0 ldr r1, =0xE0200244 ldr r0, =[r1] //把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 flag: //这两行写了一个死循环。以为裸机程序是直接在CPU上运行 b flag //逐行运行裸机程序直到CPU断电关机,如果我们的程序所有 //执行完了CPU就会跑飞(跑飞以后是未定义的,所以千万不能 //跑飞),不让CPU跑飞的方法就是在我们的整个程序执行完成加死循环程序
代码分享:
1.leds_s.zip
解压后打开
|
|
|
|
|
查阅数据手册可知,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 一步步点亮LED3_从零开始手写汇编点亮LED GPxCON、GPxDAT寄存器分析 GPJ0端口一共有8个引脚,分别记住:GPJ0_0 ~ GPJ0_7,相关重要寄存器就是GPJ0CON和GPJ0DAT GPJ0CON寄存器中设置8个引脚的工作模式(32/8=4,每个引脚可以分到4位,譬如GPJ0_0对应的bit位为bit0~bit3,GPJ0_3对应的位为bit12~bit15。工作方法是:给相应的寄存器位写入相应的值,该引脚硬件就会按照相应的模式去工作。譬如给bit12~bit15写入0b0001,GPJ0_3引脚就成为输出模式了) 从零开始写代码操作寄存器 需要哪些先决条件才能写呢? 1. 硬件接法和引脚:GPJ0_3 GPJ0_4 GPJ0_5 低电平亮/高电平灭 2. GPJ0CON(0xE0200240)寄存器和GPJ0DAT(0xE0200244)寄存器 3. 工程管理:Makefile等 根据以上分析,我们就知道代码的写法了,代码所要完成的动作就是: 把相应的配置数据写入相应的寄存器即可。 编译、下载、运行看结果 编译时用我们的工程管理,直接make编译得到led.bin和210.bin 下载运行可以用usb启动dnw下载;也可以用sd卡烧录下载,根据自己的情况用 一般都用usb下载,因为方便。如果电脑主板插上dnw会死机没法解决,那只有sd卡下载启动了。 注意:开发板上按下电源键之后4颗LED默认都是半亮的,当我们下载程序后其中3颗变的很亮,这说明我们的程序已经运行了。 总结和回顾(软件控制硬件思想、寄存器意义、原理图数据手册的作用) 软件到底是怎么控制硬件的?为什么程序一运行硬件就能跟着动? 软件编程控制硬件的接口就是:寄存器
|
|
|
|
|
一步步点亮LED4_使用位运算实现复杂点亮要求 上节回顾:代码写的更漂亮一些 1. 用宏定义来定义寄存器名字,再来操作。 2. 用 b . 来实现死循环 3. 用.global把_start链接属性改为外部,消除链接时的警告
/* *文件名:led.s *作者:宗虎冬 *描述:这是arm裸机点亮led灯第一个程序 * */ .global _start //用.global把_start链接属性改为外部,消除链接时的警告 #define GPJ0CON 0xE0200240 //1. 用宏定义来定义寄存器名字,再来操作。 #define GPJ0DAT 0xE0200244 _start: //第一步:把0x11111111写入到0xE0200240(GPJ0CON)位置 ldr r0, =0x11111111 //从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 ldr r1, =GPJ0CON //是合法立即数还是非法立即数。一般写代码都用ldr伪指令 str r0, [r1] //寄存器间接寻址,功能是把r0的数写入到r1中的数为地址的内存中去 //第二部:把oxE0200244(GPJ0DAT)位置 ldr r0, =0x0 ldr r1, =GPJ0CON ldr r0, =[r1] //把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 b . //.代表当前这一句指令的地址,这个就是高大上的死循环 //2. 用 b . 来实现死循环
|
|
|
|
|
问题提出:如何只点亮中间1颗(两边是熄灭的)LED 分析:程序其实就是写了GPJ0CON和GPJ0DAT这2个寄存器而已,功能更改也要从这里下手。 GPJ0CON寄存器不需要修改,GPJ0DAT中设置相应的输出值即可。 .global _start //把_start链接属性改为外部,这样其他文件就可以看见_start了 #define GPJ0CON 0xE0200240 #define GPJ0DAT 0xE0200244 _start: //第一步:把所有引脚都设置为输出模式,代码不变 ldr r0, =0x11111111 //从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 ldr r1, =GPJ0CON //是合法立即数还是非法立即数。一般写代码都用ldr伪指令 str r0, [r1] //寄存器间接寻址,功能是把r0的数写入到r1中的数为地址的内存中去 //第二部:把中间LED(GPJ0_4)的输出设置为0,其余两颗设置为1,剩下的其他位不管 ldr r0, =0x28 ldr r1, =GPJ0CON ldr r0, =[r1] //把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 b . //.代表当前这一句指令的地址,这个就是高大上的死循环
|
|
|
|
|
直接解法(不使用位运算)和它的弊端 GPJ0DAT = 0x28 代码见<3.led_s> 总结:1. 这样写可以完成任务。 2. 这样写有缺陷。缺陷就是需要人为的去计算这个特定的设置值,而且看代码的也不容易看懂。 解决方案:在写代码时用位运算去让编译器帮我们计算这个特定值。 常用位运算:与、或、非、移位 位与(&) 位或(|) 位非(取反 ~) 移位(左移<< 右移>>) 使用位运算实现功能 1<<3 等于 0b1000 1<<5 等于 0b100000 (1<<3)|(1<<5) 等于 0b101000 扩展一下:如何只熄灭中间1颗而点亮旁边2颗 ldr r0, =((0<<3) | (1<<4) | (0<<5)) ldr r0, =((1<<3) | (1<<5))
//ldr r0, =((1<<3) | (1<<5)) //中间LED(GPJ0_4)亮,其余两颗不亮 ldr r0, =((1<<3) | (0<<4) | (1<<5)) //中间LED(GPJ0_4)亮,其余两颗不亮 //ldr r0, =((0<<3) | (1<<4) | (0<<5)) //中间LED(GPJ0_4)不亮,其余两颗亮
|
|
|
|
|
/* *文件名:led.s *作者:宗虎冬 *描述:这是arm裸机led灯亮灭三次效果 * */ .global _start //把_start链接属性改为外部,这样其他文件就可以看见_start了 #define GPJ0CON 0xE0200240 #define GPJ0DAT 0xE0200244 _start: //第一步:把所有引脚都设置为输出模式,代码不变 ldr r0, =0x11111111 //从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 ldr r1, =GPJ0CON //是合法立即数还是非法立即数。一般写代码都用ldr伪指令 str r0, [r1] //寄存器间接寻址,功能是把r0的数写入到r1中的数为地址的内存中去 //第二部:全部点亮 ldr r0, =((0<<3) | (0<<4) | (0<<5)) ldr r1, =GPJ0CON ldr r0, =[r1] bl delay //第三部:全部灭 ldr r0, =((1<<3) | (1<<4) | (1<<5)) ldr r1, =GPJ0CON ldr r0, =[r1] bl delay //第四部:全部点亮 ldr r0, =((0<<3) | (0<<4) | (0<<5)) ldr r1, =GPJ0CON ldr r0, =[r1] bl delay //第五部:全部灭 ldr r0, =((1<<3) | (1<<4) | (1<<5)) ldr r1, =GPJ0CON ldr r0, =[r1] //第六部:全部点亮 ldr r0, =((0<<3) | (0<<4) | (0<<5)) ldr r1, =GPJ0CON ldr r0, =[r1] bl delay //第七部:全部灭 ldr r0, =((1<<3) | (1<<4) | (1<<5)) ldr r1, =GPJ0CON ldr r0, =[r1] b . //.代表当前这一句指令的地址,这个就是高大上的死循环 delay: ldr r2, =100000 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 //不相等就跳回delay_loop mov pc, lr //函数调用返回
|
|
|
|