1.了解物理特性
点亮LED的关键在于电压差
2.查阅原理图了解板载LED的硬件接法
方法:可以利用PDF文档的搜索功能(搜索LED即可,在底板搜索)
可知:有4颗正极接3.3V,负极分别接SoC上面的引脚(即引脚低电平亮),另外一个开机常亮。
3个SoC引脚是知道的,另外在核心板上查看pwmtout1可知引脚为GPD0_1,如下图
GPD0表示端口号,后面的1是引脚号
3.查阅数据手册
当我们想要编程操控GPIO来操作LED时,需要通读一下数据手册关于GPIO的部分
我们要操作的硬件为LED, 通过GPIO间接控制,实际要操作的设备是GPIO。
3.1、GPJ0相关的寄存器如下
其中,最主要的寄存器有:GPJ0_DAT, GPJ0CON
3.2、以下为GPJ0CON寄存器:
GPJ0这个端口的第一个引脚GPJ0_0对应寄存器GPJ0_CON的bit0~bit3, 点亮LED时应该将这个引脚配置成output模式。
3.3、GPJ0DAT寄存器介绍
GPJ0DAT[7:0]后面的7:0表示GPJ0对应的0~7引脚,后面的BIT[7:0]表示GPJ0DAT寄存器的bit0~bit7,从这里可以知道,
GPJ0端口的每一个引脚分别对应GPJODAT寄存器的每一个bit位。
3.4、总结
根据以上,我们就知道如何点亮LED
(1)操控GPJ0CON寄存器,选中为output模式。
(2)操控GPJ0DAT寄存器,选择相应位为低电平。
4.一步一步点亮le
4.1、点亮led灯
1 _start: 2 // 第一步:把0x11111111写入0xE0200240(GPJ0CON)位置 3 ldr r0, =0x11111111 // 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 4 ldr r1, =0xE0200240 // 是合法立即数还是非法立即数。一般写代码都用ldr伪指令 5 str r0, [r1] // 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去 6 7 // 第二步:把0x0写入0xE0200244(GPJ0DAT)位置 8 ldr r0, =0x0 9 ldr r1, =0xE0200244 10 str r0, [r1] // 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 11 12 flag: // 这两行写了一个死循环。因为裸机程序是直接在CPU上运行的,CPU会 13 b flag // 逐行运行裸机程序直到CPU断电关机。如果我们的程序所有的代码都 14 // 执行完了CPU就会跑飞(跑飞以后是未定义的,所以千万不能让CPU 15 // 跑飞),不让CPU跑飞的办法就是在我们整个程序执行完后添加死循环
4.2、改进代码1
1 #define GPJ0CON 0xE0200240 2 #define GPJ0DAT 0xE0200244 3 4 .global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了 5 _start: 6 // 第一步:把所有引脚都设置为输出模式,代码不变 7 ldr r0, =0x11111111 // 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 8 ldr r1, =GPJ0CON // 是合法立即数还是非法立即数。一般写代码都用ldr伪指令 9 str r0, [r1] // 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去 10 11 // 第二步:把中间LED(GPJ0_4)的输出设置为0,其余两颗设置为1,剩下的其他位不管 12 ldr r0, =0x28 13 ldr r1, =GPJ0DAT 14 str r0, [r1] // 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 15 16 b . // .代表当前这一句指令的地址,这个就是高大上的死循环
上述代码改进:
(1)用宏定义来替换地址的数字
(2)用b .来替换以前的死循环
(3)加上.global _start(将_start的链接属性改为外部链接)
改变:代码把4.1中点亮的led改为点亮中间的led。
4.3、改进代码2
在4.2中的缺陷:需要人为的计算0x28这个特定值,而且可读性不好。
解决方案:在写代码时用位运算让编译器帮我们计算特定值。
1 #define GPJ0CON 0xE0200240 2 #define GPJ0DAT 0xE0200244 3 4 .global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了 5 _start: 6 // 第一步:把所有引脚都设置为输出模式,代码不变 7 ldr r0, =0x11111111 // 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 8 ldr r1, =GPJ0CON // 是合法立即数还是非法立即数。一般写代码都用ldr伪指令 9 str r0, [r1] // 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去 10 11 // 第二步:把中间LED(GPJ0_4)的输出设置为0,其余两颗设置为1,剩下的其他位不管 12 //ldr r0, =((1<<3) | (1<<5)) // 等效于0b00101000,即0x28 13 ldr r0, =((1<<3) | (0<<4) | (1<<5)) // 清清楚楚的看到哪个灭,哪个是亮 14 ldr r1, =GPJ0DAT 15 str r0, [r1] // 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 16 17 b . // .代表当前这一句指令的地址,这个就是高大上的死循环
4.4、改进代码3
加上延时函数和一些功能
1 #define GPJ0CON 0xE0200240 2 #define GPJ0DAT 0xE0200244 3 4 .global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了 5 _start: 6 // 第一步:把所有引脚都设置为输出模式,代码不变 7 ldr r0, =0x11111111 // 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数 8 ldr r1, =GPJ0CON // 是合法立即数还是非法立即数。一般写代码都用ldr伪指令 9 str r0, [r1] // 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去 10 11 // 要实现流水灯,只要在主循环中实现1圈的流水显示效果即可 12 flash: 13 // 第1步:点亮LED1,其他熄灭 14 //ldr r0, =((0<<3) | (1<<4) | (1<<5)) // 清清楚楚的看到哪个灭,哪个是亮 15 ldr r0, =~(1<<3) 16 ldr r1, =GPJ0DAT 17 str r0, [r1] // 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 18 // 然后延时 19 bl delay // 使用bl进行函数调用 20 21 // 第2步:点亮LED2,其他熄灭 22 ldr r0, =~(1<<4) 23 ldr r1, =GPJ0DAT 24 str r0, [r1] // 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 25 // 然后延时 26 bl delay // 使用bl进行函数调用 27 28 // 第3步:点亮LED3,其他熄灭 29 ldr r0, =~(1<<5) 30 ldr r1, =GPJ0DAT 31 str r0, [r1] // 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮 32 // 然后延时 33 bl delay // 使用bl进行函数调用 34 35 b flash 36 37 38 // 延时函数:函数名:delay 39 delay: 40 ldr r2, =9000000 41 ldr r3, =0x0 42 delay_loop: 43 sub r2, r2, #1 //r2 = r2 -1 44 cmp r2, r3 // cmp会影响Z标志位,如果r2等于r3则Z=1,下一句中eq就会成立 45 bne delay_loop 46 mov pc, lr // 函数调用返回
关键知识点:
(1)B常用于不返回的跳转,比如跳到某个标号处,BL则用于子程序跳转(要返回,返回地地存于LR)
(2)在子函数中一般用mov pc, lr 这句指令来用于调用返回
(3)在延时函数中
cmp r2, r3 // cmp会影响Z标志位,如果r2等于r3则Z=1,下一句中eq就会成立
bne delay_loop
ne为条件后缀,成立则执行这条指令
/********************************************************
* 以上知识来自于朱老师物联网大讲堂
*******************************************************/