本文来自于朱有鹏物联网大课堂笔记,总结出了仅供学习使用
九鼎S5PV210开发板上的LED硬件原理图如下:
位置:(X210开发版光盘资料\X210V3S_A\hardware\X210BV3)
查阅原理图,发现开发板上一共有五颗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)
分析: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控制寄存器,用来配置各引脚的工作模式
GPJ0DAT, (GPJ0 data)当引脚配置为input/output模式时,寄存器的相应位和引脚的电平高低相对应。
GPJ0PUD, (pull up down)控制引脚内部弱上拉、下拉
GPJ0DRV, (driver)配置GPIO引脚的驱动能力
GPJ0CONPDN,(记得是低功耗模式下的控制寄存器)
GPJ0PUDPDN (记得是低功耗模式下的上下拉寄存器)
其中GPJ0CONPDN和GPJ0PUDPDN是低功耗模式下使用的。
注:在驱动LED点亮时,应该将GPIO配置为output模式。
实际上真正操控LED的硬件,主要的有:GPJ0CON, GPJ0DAT 这么2个。
**如何点亮LED,编程的步骤是:**
1、操控GPJ0CON寄存器中,选中output模式
2、操控GPJ0DAT寄存器,相应的位设置为0
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 . // .代表当前这一句指令的地址,这个就是高大上的死循环
分析:程序其实就是写了GPJ0CON和GPJ0DAT这2个寄存器而已,功能更改也要从这里下手。
GPJ0CON寄存器不需要修改,GPJ0DAT中设置相应的输出值即可。
直接解法(不使用位运算)和它的弊端
GPJ0DAT = 0x28
/*
* 文件名: 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 . // .代表当前这一句指令的地址,这个就是高大上的死循环
在汇编中实现延时的方法:用一些没有目的的代码来执行消耗时间,达到延时的效果。
汇编编写延时函数的原理,用一个寄存器存放一个数字,然后在循环中每个循环里给数字减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.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 写代码设置寄存器得到想要的效果