朱老师课堂学习笔记---一步步玩转led

本文来自于朱有鹏物联网大课堂笔记,总结出了仅供学习使用

首先熟悉开发板硬件工作原理及原理图查阅。

九鼎S5PV210开发板上的LED硬件原理图如下:
位置:(X210开发版光盘资料\X210V3S_A\hardware\X210BV3)
朱老师课堂学习笔记---一步步玩转led_第1张图片
查阅原理图,发现开发板上一共有五颗LED。其中一颗D26的接法是:正极接5V,负极接地。因此这颗LED只要上电就会常亮。因此我们分析这颗LED是电源指示灯。

四颗LED的正极接在了电源正极上,那么只需要控制与这四颗LED负极相接的GPIO为低电平则能点亮这些LED。

这四颗LED的接法是:正极接3.3V,负极接了SoC上的一个引脚(GPIO),具体详细接法是:
D22:GPJ0_3
D23:GPJ0_4
D24:GPJ0_5
D25:PWMTOUT1(GPD0_1)	
原理图无法看出D25的引脚通过查找得到PWMTOUT1的引脚。

路径(X210开发版光盘资料\X210V3S_A\hardware\X210CV3)
PWMTOUT1

分析如何点亮及熄灭LED(GPIO)

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

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

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

查阅三星官方的芯片手册,找到gpj0这一组
路径:(X210开发版光盘资料\X210V3S_A\DataSheet\S5PV210_UM_REV1.1)
在这里插入图片描述
查阅数据手册可知,GPJ0相关的寄存器有以下:
GPJ0CON, (GPJ0 control)GPJ0控制寄存器,用来配置各引脚的工作模式
朱老师课堂学习笔记---一步步玩转led_第2张图片
朱老师课堂学习笔记---一步步玩转led_第3张图片
GPJ0DAT, (GPJ0 data)当引脚配置为input/output模式时,寄存器的相应位和引脚的电平高低相对应。
朱老师课堂学习笔记---一步步玩转led_第4张图片
GPJ0PUD, (pull up down)控制引脚内部弱上拉、下拉
朱老师课堂学习笔记---一步步玩转led_第5张图片
GPJ0DRV, (driver)配置GPIO引脚的驱动能力朱老师课堂学习笔记---一步步玩转led_第6张图片
GPJ0CONPDN,(记得是低功耗模式下的控制寄存器)朱老师课堂学习笔记---一步步玩转led_第7张图片
GPJ0PUDPDN (记得是低功耗模式下的上下拉寄存器)朱老师课堂学习笔记---一步步玩转led_第8张图片
其中GPJ0CONPDN和GPJ0PUDPDN是低功耗模式下使用的。
注:在驱动LED点亮时,应该将GPIO配置为output模式。
实际上真正操控LED的硬件,主要的有:GPJ0CON, GPJ0DAT 这么2个。

**如何点亮LED,编程的步骤是:**
1、操控GPJ0CON寄存器中,选中output模式
2、操控GPJ0DAT寄存器,相应的位设置为0

从零开始手写汇编点亮LED

GPxCON、GPxDAT寄存器分析
GPJ0端口一共有8个引脚,分别记住:GPJ0_0 ~ GPJ0_7,相关重要寄存器就是GPJ0CON和GPJ0DAT
GPJ0CON寄存器中设置8个引脚的工作模式(32/8=4,每个引脚可以分到4位,譬如GPJ0_0对应的bit位为bit0bit3,GPJ0_3对应的位为bit12bit15。工作方法是:给相应的寄存器位写入相应的值,该引脚硬件就会按照相应的模式去工作。譬如给bit12~bit15写入0b0001,GPJ0_3引脚就成为输出模式了)

从零开始写代码操作寄存器需要哪些先决条件才能写呢?
1. 硬件接法和引脚:GPJ0_3 GPJ0_4 GPJ0_5	低电平亮/高电平灭
2. GPJ0CON(0xE0200240)寄存器和GPJ0DAT(0xE0200244)寄存器
3. 工程管理:Makefile等

实验步骤

1:通过地址0xE0200240找到GPJ0COM寄存器 该寄存器就是完成对引脚工作模式的控制0b0001表示输出模式
2:往该寄存器写值写入 0x1111_1111 因为
D22:GPJ0_3
D23:GPJ0_4
D24:GPJ0_5三个引脚有关 但是我们编程的时候可以把整个8个引脚都变成output模式
每个引脚4位 0b0001表示输出模式 则写入0x1111_1111即可
上面两部就是控制了GPIO的工作模式,输出模式
3:通过地址0xE0200244找到GPJ0DAT寄存器,该寄存器8个位分别就是反应了各个引脚的状态。要输出0则直接将该bit为设为0则对应引脚输出0
4;将0x0存入GPJ0DAT寄存器作位引脚输出的状态
5:裸机开发注意要进行死循环 防止cpu跑飞;

/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	这是我们一步步点亮LED教程的自己写的第一个裸机程序
 */
/*实验步骤 
1:通过地址0xE0200240找到GPJ0COM寄存器 该寄存器就是完成对引脚工作模式的控制0b0001表示输出模式 
2:往该寄存器写值写入 0x1111_1111 因为 
D22:GPJ0_3 
D23:GPJ0_4 
D24:GPJ0_5三个引脚有关 但是我们编程的时候可以把整个8个引脚都变成output模式 
每个引脚4位 0b0001表示输出模式 则写入0x1111_1111即可 
上面两部就是控制了GPIO的工作模式,输出模式 
3:通过地址0xE0200244找到GPJ0DAT寄存器,该寄存器8个位分别就是反应了各个引脚的状态。要输出0则直接将该bit为设为0则对应引脚输出0 
4;将0x0存入GPJ0DAT寄存器作位引脚输出的状态 
5:裸机开发注意要进行死循环 防止cpu跑飞;
*/
_start:
	// 第一步:把0x11111111写入0xE0200240(GPJ0CON)位置
	ldr r0, =0x11111111			// 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数
	ldr r1, =0xE0200240			// 是合法立即数还是非法立即数。一般写代码都用ldr伪指令
	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跑飞的办法就是在我们整个程序执行完后添加死循环

编译时用我们的工程管理,直接make编译得到led.bin和210.bin

led.bin: led.o 
	arm-linux-ld -Ttext 0x0 -o led.elf $^
	arm-linux-objcopy -O binary led.elf led.bin
	arm-linux-objdump -D led.elf > led_elf.dis
	gcc mkv210_image.c -o mkx210
	./mkx210 led.bin 210.bin
	
%.o : %.S
	arm-linux-gcc -o $@ $< -c

%.o : %.c
	arm-linux-gcc -o $@ $< -c 

clean:
	rm *.o *.elf *.bin *.dis mkx210 -f

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

1. 用宏定义来定义寄存器名字,再来操作。
2. 用 b . 来实现死循环
3. 用.global把_start链接属性改为外部,消除链接时的警告
/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	这是我们一步步点亮LED教程的自己写的第一个裸机程序
 */
 
#define GPJ0CON	0xE0200240
#define GPJ0DAT	0xE0200244

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第一步:把所有引脚都设置为输出模式,代码不变
	ldr r0, =0x11111111			// 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数
	ldr r1, =GPJ0CON			// 是合法立即数还是非法立即数。一般写代码都用ldr伪指令
	str r0, [r1]				// 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去

	// 第二步:把中间LED(GPJ0_4)的输出设置为0,其余两颗设置为1,剩下的其他位不管
	ldr r0, =0x28
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮

	b .							// .代表当前这一句指令的地址,这个就是高大上的死循环

如何只点亮中间1颗(两边是熄灭的)LED

分析:程序其实就是写了GPJ0CON和GPJ0DAT这2个寄存器而已,功能更改也要从这里下手。
GPJ0CON寄存器不需要修改,GPJ0DAT中设置相应的输出值即可。

直接解法(不使用位运算)和它的弊端
GPJ0DAT = 0x28朱老师课堂学习笔记---一步步玩转led_第9张图片

/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	这是我们一步步点亮LED教程的自己写的第一个裸机程序
 */
 
#define GPJ0CON	0xE0200240
#define GPJ0DAT	0xE0200244

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第一步:把所有引脚都设置为输出模式,代码不变
	ldr r0, =0x11111111			// 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数
	ldr r1, =GPJ0CON			// 是合法立即数还是非法立即数。一般写代码都用ldr伪指令
	str r0, [r1]				// 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去

	// 第二步:把中间LED(GPJ0_4)的输出设置为0,其余两颗设置为1,剩下的其他位不管
	ldr r0, =0x28
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮

	b .							// .代表当前这一句指令的地址,这个就是高大上的死循环




总结:1. 这样写可以完成任务。
	  2. 这样写有缺陷。缺陷就是需要人为的去计算这个特定的设置值,而且看代码的也不容易看懂。
解决方案:在写代码时用位运算去让编译器帮我们计算这个特定值。

使用位运算实现功能

1<<3  等于 0b1000
1<<5  等于 0b100000
(1<<3)|(1<<5)  等于 0b101000
/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	这是我们一步步点亮LED教程的自己写的第一个裸机程序
 */
 
#define GPJ0CON	0xE0200240
#define GPJ0DAT	0xE0200244

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第一步:把所有引脚都设置为输出模式,代码不变
	ldr r0, =0x11111111			// 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数
	ldr r1, =GPJ0CON			// 是合法立即数还是非法立即数。一般写代码都用ldr伪指令
	str r0, [r1]				// 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去

	// 第二步:把中间LED(GPJ0_4)的输出设置为0,其余两颗设置为1,剩下的其他位不管
	//ldr r0, =((1<<3) | (1<<5))	// 等效于0b00101000,即0x28
	ldr r0, =((1<<3) | (0<<4) | (1<<5))	// 清清楚楚的看到哪个灭,哪个是亮
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮

	b .							// .代表当前这一句指令的地址,这个就是高大上的死循环

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

延时函数原理

在汇编中实现延时的方法:用一些没有目的的代码来执行消耗时间,达到延时的效果。

汇编编写延时函数

汇编编写延时函数的原理,用一个寄存器存放一个数字,然后在循环中每个循环里给数字减1,然后再判断这个数字的值是否为0.如果为0则停止循环,如果不为0则继续循环。

汇编编写及调用函数的方式

汇编中整个汇编的主程序是一个死循环,这个死循环是我们汇编程序的主体,类似于C中的main函数。其他函数必须写在这个主死循环程序的后面(死循环外),不然会出错。
汇编编写delay延时函数时,要注意函数的初始化和函数体的位置,不能把初始化写在了循环体内。
汇编中调用函数用bl指令,子函数中最后用mov pc, lr来返回。

/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	这是我们一步步点亮LED教程的自己写的第一个裸机程序
 */
 
#define GPJ0CON	0xE0200240
#define GPJ0DAT	0xE0200244

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第一步:把所有引脚都设置为输出模式,代码不变
	ldr r0, =0x11111111			// 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数
	ldr r1, =GPJ0CON			// 是合法立即数还是非法立即数。一般写代码都用ldr伪指令
	str r0, [r1]				// 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去

	// 第二步:全部点亮
	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]		
	
	// 第五步:延时
	bl delay
	
	// 第六步:点亮
	ldr r0, =((0<<3) | (0<<4) | (0<<5))	// 清清楚楚的看到哪个灭,哪个是亮
	ldr r1, =GPJ0DAT
	str r0, [r1]
	
	// 第七步:全部熄灭
	ldr r0, =((1<<3) | (1<<4) | (1<<5))	// 清清楚楚的看到哪个灭,哪个是亮
	ldr r1, =GPJ0DAT
	str r0, [r1]		
	
	// 第八步:延时
	bl delay
	
	b .							// .代表当前这一句指令的地址,这个就是高大上的死循环

// 延时函数:函数名: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					// 函数调用返回

再难一点的流水灯效果

流水灯原理分析

流水灯又叫跑马灯,实现的效果就是:挨着的LED一次点亮熄灭(同时只有1颗LED亮的)

/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	流水灯
 */
 
#define GPJ0CON	0xE0200240
#define GPJ0DAT	0xE0200244

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第一步:把所有引脚都设置为输出模式,代码不变
	ldr r0, =0x11111111			// 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数
	ldr r1, =GPJ0CON			// 是合法立即数还是非法立即数。一般写代码都用ldr伪指令
	str r0, [r1]				// 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去

	// 要实现流水灯,只要在主循环中实现1圈的流水显示效果即可
flash:
	// 第1步:点亮LED1,其他熄灭
	ldr r0, =((0<<3) | (1<<4) | (1<<5))	// 清清楚楚的看到哪个灭,哪个是亮
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
	// 然后延时
	bl delay					// 使用bl进行函数调用
	
	// 第2步:点亮LED2,其他熄灭
	ldr r0, =((1<<3) | (0<<4) | (1<<5))	// 清清楚楚的看到哪个灭,哪个是亮
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
	// 然后延时
	bl delay	

	// 第3步:点亮LED3,其他熄灭
	ldr r0, =((1<<3) | (1<<4) | (0<<5))	// 清清楚楚的看到哪个灭,哪个是亮
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
	// 然后延时
	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					// 函数调用返回
复杂点的实现

用位取反操作符来轻松愉快的实现单颗LED点亮流水效果朱老师课堂学习笔记---一步步玩转led_第10张图片

/*
 * 文件名:	led.s	
 * 作者:	朱老师
 * 描述:	流水灯
 */
 
#define GPJ0CON	0xE0200240
#define GPJ0DAT	0xE0200244

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第一步:把所有引脚都设置为输出模式,代码不变
	ldr r0, =0x11111111			// 从后面的=可以看出用的是ldr伪指令,因为需要编译器来判断这个数
	ldr r1, =GPJ0CON			// 是合法立即数还是非法立即数。一般写代码都用ldr伪指令
	str r0, [r1]				// 寄存器间接寻址。功能是把r0中的数写入到r1中的数为地址的内存中去

	// 要实现流水灯,只要在主循环中实现1圈的流水显示效果即可
flash:
	// 第1步:点亮LED1,其他熄灭
	//ldr r0, =((0<<3) | (1<<4) | (1<<5))	// 清清楚楚的看到哪个灭,哪个是亮
	ldr r0, =~(1<<3)
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
	// 然后延时
	bl delay					// 使用bl进行函数调用
	
	// 第2步:点亮LED2,其他熄灭	
	ldr r0, =~(1<<4)
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
	// 然后延时
	bl delay					// 使用bl进行函数调用
	
	// 第3步:点亮LED3,其他熄灭	
	ldr r0, =~(1<<5)
	ldr r1, =GPJ0DAT
	str r0, [r1]				// 把0写入到GPJ0DAT寄存器中,引脚即输出低电平,LED点亮
	// 然后延时
	bl delay					// 使用bl进行函数调用
	
	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					// 函数调用返回

编程操控一个硬件的步骤:
1 分析硬件工作原理
2 分析原理图
3 分析数据手册
4 找到相关的SFR
5 写代码设置寄存器得到想要的效果

你可能感兴趣的:(朱老师课堂)