ARM裸机学习1——GPIO和LED

本文基本是在复现朱有鹏老师的嵌入式核心课程课件,课件是开源的。做这一项工作的原因有3:
1. 对课件进行简单排版,使观看更方便
2. 我在闲暇之余看这门课,做这个抄录也是在加深自己的印象
3. 取其中我认为的重要内容,总结搭建知识框架
文中使用的开发板为朱老师嵌入式核心课程配套的Study210开发板

ARM裸机学习——GPIO和LED

1、裸机实验体验之usb启动配合dnw工具下载

  1. dnw是一个软件,是三星公司编写的,这个软件的功能是通过USB线连接开发板和电脑主机,然后从主机下载文件镜像到开发板中去烧录系统。

  1. 从usb启动做裸机实验时,因为不需要16字节的校验头,所以直接下载到0xd0020010

  1. usb启动方式主要是用来调试程序的,其实分析S5PV210即可知道,我们这里是把裸机程序当作BL1来使用了

dnw是需要装usb驱动的,Win7 X64版本驱动安装非常麻烦,因为微软启用了USB设备驱动签名政策。

2、裸机实验体验之SD卡下载

  1. 从SD启动时会先从iNand(SD0)启动执行,当iNand启动做校验和时失败才会转为启动SD2。而我们做裸机实验时是通过SD2来提供裸机程序镜像的,因此需要先破坏内部iNand的uboot才可以强迫开发板从SD2启动去执行我们的裸机程序。

3、自己动手安装交叉编译工具链

  1. Windows中装软件的特点:

Windows中装软件使用安装包,安装包解压后有2种情况:

一种是一个安装文件(.exe .msi),双击进行安装,下一步直到安装完毕。安装完毕后会在桌面上生成快捷方式,我们平时使用快捷方式来启动这些程序;

另一种是所谓的绿色软件、免安装软件。这种不用安装,直接解压开里面就有exe可以直接双击执行。

  1. linux中装软件的特点

linux中安装软件比windows中复杂。linux中安装软件一般有以下几种方法:

  1. 第一种:在线安装。譬如ubuntu中使用apt-get install vim来安装vim软件。

  1. 第二种:自己下载安装包来安装。这种方式的缺陷就是你不知道你下载的安装包和你的系统是否匹配。

  1. 第三种:最装逼的一种方式,就是源代码安装

  1. 我们安装交叉编译工具链(arm-linux-gcc)实际采用第二种安装方式

选择交叉编译工具链的原则:和我们所使用的目标平台(给哪款SoC编程)尽量去匹配。

注:linux中的目录管理方法。技术角度来讲,linux中所有目录性质都是一样的,所以技术角度来讲我们把软件安装到哪里都行。但是因为如果胡乱放置,将来程序可能不好找。所以久而久之大家就总结了一个文件放置的一般定义,譬如说
/bin目录放置一些系统自带的用户使用的应用程序;
/sbin目录下存放的是系统自带的系统管理方面的应用程序。
那我们装软件放在哪里?一般都在/usr目录下。我们安装arm-linux-gcc,就在/usr/local/底下创建一个arm文件夹,然后装到里面。

4、配置一些交叉编译工具链的参数

  1. 环境变量的意义

环境变量就是操作系统的全局变量。每一个环境变量对操作系统来说都是唯一的,名字和所代表的意义都是唯一的。linux系统可以有很多个环境变量。其中有一部分是linux系统自带的,还有一些是我们自己来扩充的。我们这里涉及到的一个环境变量是

PATH。PATH这个环境变量是系统自带的,它的含义就是系统在查找可执行程序时会搜索的路径范围

  1. 将工具链导出到环境变量

export PATH=/usr/local/arm/arm-2009q3/bin:$PATH

以上操作在终端中执行时的操作只是针对本终端,以后再打开的终端并未被执行过这个命令所以没导出。

要想添加的路径始终生效,解决方案是:

在~/.bashrc文件中,添加export PATH=/usr/local/arm/arm-2009q3/bin:$PATH 即可

导出这个环境变量是在当前用户,如果你登录时在其他用户下是没用的。
  1. 为工具链创建arm-linux-xxx符号链接

工具链中的各种工具名字太长了,不方便记忆,所以我们给它另外起一个名字,用链接的方式,例如:

ln arm-none-linux-gnueabi-addr2line -s arm-linux-addr2line

以后当我们输入arm-linux-addr2line时就会自动使用arm-none-linux-gnueabi-addr2line来执行命令

5、Makefile

  1. 为什么需要Makefile

Makefile是用来管理工程的。

在一个正式的软件项目中,由很多个.c和.h文件构成,此时如果直接在命令行编译,就会像这样:

gcc a.c b.c c.c d.c e.c f.c g.c -o exe

每次编译都要输入一堆东西很麻烦,这个问题严重影响工作效率!通过编写Makefile文件,只需要在命令行输入make,即可自动实现一系列的编译指令

另一个工具,CMAKE,可以实现自动写Makefile的功能
  1. 一个简单的Makefile示例

led.bin: start.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
这里不对Makefile指令的含义进行具体讲解,有需要的读者可自行百度或听朱老师课程中的讲解
by StarLight
  1. Makefile中的一些基本概念

  1. 目标:目标定格写,后面是冒号(冒号后面是依赖)。

  1. 依赖:依赖是用来产生目标的原材料。

  1. 命令:命令前面一定是Tab,不能是定格,也不能说多个空格。命令就是要生成那个目标需要做的动作。

  1. Makefile的基本工作原理

  1. 当我们执行 make xx 的时候,Makefile会自动执行xx这个目标下面的命令语句。

  1. 当我们make xx的时候,是否执行命令是取决于依赖的。依赖如果成立就会执行命令,否则不执行。

  1. 我们直接执行make 和make 第一个目标 效果是一样的。(第一个目标其实就是默认目标)

  1. 进一步学习Makefile的资料

我们学习Makefile的思路就是:先学会基本的概念和应用,先理解Makefile的概念和使用方法、工作原理。先自己会写简单的Makefile来管理工程。一般先学到这里就可以了,更深入的内容可以随同稍后的课程一起来学习,我们讲到课程的时候会再次提及并且逐步深入。

对于有一定基础的同学,同时还有时间,可以深入学习Makefile,看《跟我一起学Makefile》

这里贴一个链接:[《跟我一起写Makefile》——陈皓]((24条消息) 跟我一起写 Makefile(一)_《跟我一起写makefile》_haoel的博客-优快云博客)

笔者曾经简单浏览过这篇笔记,写得很好,但对新手来说比较复杂,全搞明白需要花时间。建议量力而行。

6、mkv210_image.c文件详解

  1. 裸机程序中的Makefile(实际上真正的项目的Makefile都是这样的)是把程序的编译和链接过程分开的。

  1. 链接器得到led.elf其实就是我们的可执行程序,(如果是在操作系统下,这个led.elf就可以执行了);但是在嵌入式裸机中我们需要的是可以烧写的文件(==可烧写的文件就叫镜像image==),因此我们需要用这个led.elf为原材料来制作镜像,制作工具是交叉编译工具链中的arm-linux-objcopy

  1. 使用arm-linux-objdump(这是经过重命名(链接)后的交叉编译工具链中的工具)工具进行反编译(反汇编),反汇编其实就是把编译后的elf格式的可执行程序给反过来的到对应的汇编程序,的到它的汇编源代码。

我们使用反汇编主要是用来学习,见本部分最后一节。
  1. mkv210_image.c这个程序其实最终不是在开发板上执行的,而是在主机linux中执行的,因此编译这个程序用gcc而不是用arm-linux-gcc。这个.c文件编译后得到一个可执行程序mkmini210,目的是通过执行这个mkmini210程序而由led.bin得到210.bin。

210.bin是通过SD卡启动时的裸机镜像,这个镜像需要由led.bin来加工的到,加工的具体方法和原理要看mkv210_image.c
  1. 汇总

  1. S5PV210的启动过程回顾

分析启动过程可知;210启动后先执行内部iROM中的BL0,BL0执行完后会根据OMpin的配置选择一个外部设备来启动(有很多,我们实际使用的有2个:usb启动和SD卡启动)。

在usb启动时内部BL0读取到BL1后不做校验,直接从BL1的实质内部0xd0020010开始执行,因此usb启动的景象led.bin不需要头信息,因此我们从usb启动时直接将镜像下载到0xd0020010去执行即可,不管头信息了;

从SD启动时,BL0会首先读取sd卡得到完整的镜像(完整指的是led.bin和16字节的头),然后BL0会自己根据你的实际镜像(指led.bin)来计算一个校验和checksum,然后和你完整镜像的头部中的checksum来比对。如果对应则执行BL1,如果不对应则启动失败(会转入执行2st启动,即SD2启动。如果这里已经是2st启动了,这里校验通不过就死定了)。

  1. mkv210_image.c的作用:为BL1添加校验头

我们编译链接时只得到了led.bin,这个210.bin的得到和交叉编译工具链是完全无关的。由led.bin得到210.bin的过程是三星的S5PV210所特有的,因此需要我们自己去完成,为此我们写了mkv210_image.c来完成。

  1. 整个程序工作流分析

整个程序中首先申请一个16KB大小的buffer,然后把所有内容按照各自的位置填充进去,最终把填充好的buffer写入到一个文件(名叫210.bin)就形成了我们想要的镜像。

  1. 代码详解

这一部分建议去看朱老师的课程,这里不详细说明

7、一步步点亮LED1——硬件工作原理及原理图查阅

  1. LED物理特性介绍

LED本身有2个接线点,一个是LED的正极,一个是LED的负极。

LED这个硬件的功能就是点亮或者不亮,

物理上想要点亮一颗LED只需要给他的正负极上加正电压即可,要熄灭一颗LED只需要去掉电压即可。

  1. 查阅原理图了解板载LED硬件接法

查阅原理图,发现开发板上一共有5颗LED。

其中一颗D26的接法是:正极接5V,负极接地。因此这颗LED只要上电就会常亮。因此我们分析这颗LED是电源指示灯。

剩下4颗LED的接法是:正极接3.3V,负极接了SoC上的一个引脚(GPIO),具体详细接法是:

D22:GPJ0_3

D23:GPJ0_4

D24:GPJ0_5

D25:PWMTOUT1(GPD0_1)

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

LED点亮的要求是:正极和负极之间有正向电压差。

因为正极已经定了(3.3V),而负极接在了SoC的引脚上,可以通过SoC中编程来控制负极的电压值,因此我们可以通过程序控制负极输出低电平(0V),这样在正负极上就有了压差,LED即可点亮。

8、一步步点亮LED2_数据手册查阅及相关寄存器浏览

  1. GPIO概念的引入

GPIO: general purpose input output 通用输入输出

GPIO就是芯片的引脚(芯片上的引脚有些不是GPIO,只有一部分是),作为GPIO的这类引脚,他的功能和特点是可以被编程控制它的工作模式,也可以编程控制他的电压高低等

通过之前的分析我们知道,我们设计电路时就把LED接在了一个GPIO上,这样我们就可以通过编程控制GPIO的模式和输入输出值来操控LED亮还是灭;如果你当时设计电路时把LED接在非GPIO上那就不可能了。
  1. 阅读数据手册中有关部分

当我们想要通过编程操控GPIO来操作LED时,我们首先需要通读一下S5PV210的数据手册中有关于GPIO的部分,这部分在数据手册的Section2.2中。

不只是GPIO,我们使用任何外设之前一般都需要先阅读相应的SoC数据手册
  1. GPIO相关的寄存器介绍

软件操作硬件的接口是:寄存器。

我们当前要操作的硬件是LED,但是LED实际是通过GPIO来间接控制的,所以当前我们实际要操作的设备其实是SoC的GPIO要操作这些GPIO,必须通过设置他们的寄存器

不只是GPIO,操作任何外设基本都是通过设置他们的寄存器
查阅数据手册可知,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模式

  1. 操控GPJ0DAT寄存器,相应的位设置为0

9、一步步点亮LED3_从零开始手写汇编点亮LED

  1. 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. 从零开始写代码操作寄存器

需要哪些先决条件才能写呢?

  1. 硬件接法和引脚:GPJ0_3 GPJ0_4 GPJ0_5 低电平亮/高电平灭(查阅数据手册)

  1. GPJ0CON(0xE0200240)寄存器和GPJ0DAT(0xE0200244)寄存器 (查阅数据手册)

  1. 工程管理:Makefile等

根据以上分析,我们就知道代码的写法了,代码所要完成的动作就是

把相应的配置数据写入相应的寄存器即可

  1. 编译、下载、运行看结果

使用Makefile管理工程,直接make编译可得到我们需要的镜像文件:

led.bin:通过USB启动的镜像文件

210.bin:通过SD卡启动的镜像文件

下载运行可以用usb启动dnw下载;也可以用sd卡烧录下载。

一般用usb方式,比较方便,但是要注意使用usb方式时需要一直按着电源。

  1. 总结和回顾(软件控制硬件思想、寄存器意义、原理图数据手册的作用)

至此已经学了很多内容了,提出两个问题自己检查一下掌握程度:

  1. 软件到底是怎么控制硬件的?

  1. 为什么程序一运行硬件就能跟着动?

提示:软件编程控制硬件的接口就是:寄存器
  1. 一步步点亮LED4_使用位运算实现复杂点亮要求

这里不把程序怎么一步步写漂亮的步骤慢慢展示了,感兴趣的读者可以去听朱老师的讲解,这里贴一个最后总的使用汇编语言控制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 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					// 函数调用返回
这里对本文点亮LED程序中涉及到的思想进行一些解释:
1. 常用位运算:与、或、非、移位
位与(&) 位或(|) 位非(取反 ~) 移位(左移<< 右移>>)
2. 使用位运算实现功能(0b代表二进制)
1<<3 等于 0b1000
1<<5 等于 0b100000
(1<<3)|(1<<5) 等于 0b101000
3. 闪烁效果原理分析
闪烁 = 亮 + 延时 + 灭 + 延时 + 亮 + 延时 ······
4. 延时函数原理
在汇编中 实现延时的方法: 用一些没有目的的代码来执行消耗时间,达到延时的效果
5. 汇编编写及调用函数的方式
1. 汇编中整个汇编的主程序是一个死循环,这个死循环是我们汇编程序的主体,类似于C中的main函数。其他函数必须写在这个主死循环程序的后面(死循环外),不然会出错。
2. 汇编编写delay延时函数时,要注意函数的初始化和函数体的位置,不能把初始化写在了循环体内。
3. 汇编中调用函数用bl指令,子函数中最后用mov pc, lr来返回。
6. 流水灯原理分析
流水灯又叫跑马灯,实现的效果就是:挨着的LED一次点亮熄灭(每一时间段只有1颗LED亮)
7. 流水灯编写(使用循环)
LED1亮延时 + LED2亮延时 + LED3亮延时 + 循环
2023/3/25补充,完整掌握汇编点亮LED: ARM汇编指令学习——通过点亮LED的方式_StarLight~的博客-优快云博客
  1. 总结

从一步一步点亮LED1开始到上面贴的代码,朱老师写了8个示例代码,一步步的实现了更复杂的效果,其间夹杂使用了位运算来给LED赋值,以实现想要的点亮效果。如果按部就班实际上非常简单(前提是对其中用到的arm汇编指令熟悉)。

总之,编程控制一个硬件的步骤可以总结如下:

  1. 分析硬件工作原理

  1. 分析原理图(找到外围设备涉及到的芯片引脚)

  1. 分析数据手册

  1. 找到相关的SFR(Special Function Register)特殊功能寄存器

  1. 写代码设置寄存器得到想要的效果

  1. 作业

大家可以用以上所学内容控制LED达到任何自己想要的功能

10、反汇编工具objdump的使用简介(补充项)

  1. 反汇编的原理&为什么要反汇编

arm-linux-objdump -D led.elf > led_elf.dis

objdump是gcc工具链中的反汇编工具,作用是由编译链接好的elf格式的可执行程序反过来得到汇编源代码

-D表示反汇编 > 左边的是elf的可执行程序(反汇编时的原材料),>右边的是反汇编生成的反汇编程序

反汇编原理:

  1. 逆向破解。

  1. 调试程序时,反汇编代码可以帮助我们理解程序(我们学习时使用objdump主要目的是这个),尤其是在理解链接脚本、链接地址等概念时。

  1. 把C语言源代码编译链接生成的可执行程序反汇编后得到对应的汇编代码,可以帮助我们理解C语言和汇编语言之间的对应关系。非常有助于深入理解C语言。

  1. 展望

反汇编工具帮助我们分析链接脚本

目前来说不是很重要的注释:
指令地址:下载烧录执行的bin文件,内部其实是一条一条的指令机器码。这些指令每一条都有一个指令地址,这个地址是连接的时候ld给指定的(ld根据我们写的链接脚本来指定)
反汇编的时候得到的指令地址是链接器考虑了链接脚本之后得到的地址,而我们写代码时通过指定连接脚本来让链接器给我们链接合适的地址。
但是有时候我们写的链接脚本有误(或者我们不知道这个链接脚本会怎么样),这时候可以通过看反汇编文件来分析这个链接脚本的效果,看是不是我们想要的,如果不是可以改了再看。
  1. 总结

反汇编这个知识点不是目前的重点,了解以上的思想就ok。

全文总结

OK,到目前为止,我们对GPIO这个外设以及LED这个外围设备,还有程序的编写流程有了基本的认识。值得说明的是,如果仅仅想通过这篇文章理解全部知识点是不行的,因为里面很多东西需要自己动手去实操,去学习,比如查数据手册,比如理解用到的汇编指令等。

当然,因为这只是自己学习过程中的记录,难免会有总结不到位的地方,欢迎大伙的指正。希望本文能对大家有所帮助。

2023/3/23 by StarLight
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值