|
复杂点的实现 用位取反操作符来轻松愉快的实现单颗LED点亮流水效果 总结:一步步写,根本不难 从一步一步点亮LED1开始到6,写了8个示例代码,一步步的实现了更复杂的效果,其间夹杂使用了位运算来给LED赋值,以实现想要的点亮效果。如果按部就班实际上非常简单。 编程操控一个硬件的步骤:1 分析硬件工作原理 2 分析原理图 3 分析数据手册 4 找到相关的SFR 5 写代码设置寄存器得到想要的效果
|
|
|
|
|
/* *文件名: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中的数为地址的内存中去 mov r4, #3 flash:
//第一步:点亮第一颗led灯 ldr r0, =~(1<<r4) ldr r1, =GPJ0CON ldr r0, [r1] //延时 bl delay add r4, r4, #1 cmp r4, #6 //如果等于6就返回循环 bne flash //否则r4传入3再返回循环 mov r4, #3 b flash // 延时函数:函数名:delay delay: ldr r2, =9000000 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 // 函数调用返回
|
|
|
|
|
在linux下复制,在windows下打开 /* *文件名: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中的数为地址的内存中去
flash: //第一步:点亮第一颗led灯 ldr r0, =((0<<3) | (1<<4) | (1<<5)) ldr r1, =GPJ0CON ldr r0, [r1] //延时 bl delay //第二步:点亮第二颗led灯 ldr r0, =((1<<3) | (0<<4) | (0<<5)) ldr r1, =GPJ0CON ldr r0, [r1] //延时 bl delay //第三步:点亮第三颗led灯 ldr r0, =((1<<3) | (1<<4) | (0<<5)) ldr r1, =GPJ0CON ldr r0, [r1] //延时 bl delay //返回循环 b flash // 延时函数:函数名:delay delay: ldr r2, =9000000 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 // 函数调用返回
|
|
|
|
|
一步步点亮LED5_汇编编写延时函数并实现LED闪烁效果 闪烁效果原理分析 闪烁 = 亮 + 延时 + 灭 + 延时 + 亮 + 延时 ······ 延时函数原理 在汇编中实现延时的方法:用一些没有目的的代码来执行消耗时间,达到延时的效果。 汇编编写延时函数 汇编编写延时函数的原理,用一个寄存器存放一个数字,然后在循环中每个循环里给数字减1,然后再判断这个数字的值是否为0.如果为0则停止循环,如果不为0则继续循环。 汇编编写及调用函数的方式 汇编中整个汇编的主程序是一个死循环,这个死循环是我们汇编程序的主体,类似于C中的main函数。其他函数必须写在这个主死循环程序的后面(死循环外),不然会出错。 汇编编写delay延时函数时,要注意函数的初始化和函数体的位置,不能把初始化写在了循环体内。 汇编中调用函数用bl指令,子函数中最后用mov pc, lr来返回。
一步步点亮LED6_再难一点的流水灯效果 流水灯原理分析 流水灯又叫跑马灯,实现的效果就是:挨着的LED一次点亮熄灭(同时只有1颗LED亮的) 流水灯编写(使用循环) LED1亮延时 + LED2亮延时 + LED3亮延时 + 循环
|
|
|
|
|
b .是一个空的死循环
以下是一个非空的死循环 flash: XXX
b flash
|
|
|
|
|
/* *文件名: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中的数为地址的内存中去
flash: //全部点亮 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 //返回循环 b flash 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 //函数调用返回
如果用c可以这样写 while(1); //空的死循环
while(1) //非空的死循环 { XXX
}
|
|
|
|
|
/* *文件名: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 //函数调用返回
|
|
|
|
|
直接解法(不使用位运算)和它的弊端 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)不亮,其余两颗亮
|
|
|
|
|
问题提出:如何只点亮中间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 . //.代表当前这一句指令的地址,这个就是高大上的死循环
|
|
|
|
|
一步步点亮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 . 来实现死循环
|
|
|
|