本课时主要在于说明最基本的起步和怎样打开Raspberry Pi上的'OK' LED 灯,灯的位置在RCA和USB接口附近.
1 起步
首先需要你访问 下载 页面获得其中的GNU的工具链.同时在该页面上,也存在一个叫做 OS Template 的文件.请把它下载下来,解压到新建的文件夹内.
2 开始
先解压模板文件,然后在其'source'文件夹中创建一个名称为'main.s'的文件.该文件会包含待开发的操作系统的代码.用文本编辑器打开,准备向其中写入汇编代码.Raspberry Pi使用的是ARMv6的汇编代码,因此,我们也需要根据ARMv6的语法和要求来写代码.
拷贝如下代码:
.section .init .globl _start _start:
目前什么都不会发生,这些代码都是等待给汇编器的指令.汇编器是翻译汇编代码到二进制代码的程序.汇编代码中,每行就是一个新的指令.其中第一行告诉汇编器1放置我们代码的位置.OS模板文件会让 .init 的模块首先输出.这里比较重要,因为我们需要控制具体哪段代码首先执行.不这么做的话,代码根据字典顺序来执行. .section 指令就是告诉汇编器把代码放哪里,代码范围是从该点开始一直到下个 .section 或者文件尾.
后面两行只是为了防止警告信息,没有重要意义.2
3 第一行代码
现在我们开始真正的编写代码.代码的作用是告诉处理器将 0x20200000存入寄存器r0.显然读者肯定会问及两个问题,寄存器是什么和为什么是0x20200000这个数字.
寄存器是处理器内部的一小片存储单元,用于存储处理器目前工作中需要使用的数字.他们的数量很少,且基本都有特定的作用,具体作用稍后再说.共有13个通用寄存器,分别编号为(r0…r12),其不具有特定作用,可以随意使用.例子中,我们按照顺序,就使用了第一个寄存器,不过随便用其中的任何一个也没有关系.当然,随后的代码中使用寄存器就需要保持一致.
0x20200000是个16进制形式的数字.
所以第一个指令就是将数字2020000016 载入r0寄存器.看起来没什么作用,其实不然.在计算机中,有很多设备和内存块.为了能够访问他们,我们就给其中每个都赋予一个地址.如同邮编或者网址,为的是能够让我们识别设备或者内存块的位置.计算机中的地址都是数字编码,所以2020000016 成为GPIO控制器的地址.这其实是硬件制造商设计的地址,制造商可以随意指定地址,只要地址没有冲突就可以.具体的地址信息可以从硬件的手册中获得, 这些地址一般要求没有被别的系统指定,而且往往都是很大的约整数.
4 成功输出
如果读过手册,就会知道我们需要发送两个消息给GPIO控制器.我们必须让控制器能够理解我们的消息,一旦这样,控制器就会按照我们所愿打开OK LED灯.由于这是个十分简单的芯片,所以我们只用很简单的发送几个数字就可以了.
mov r1,#1 lsl r1,#18 str r1,[r0,#4]
以上的代码就能够输出到GPIO的第16个针脚上.首先我们向r1寄存器写入一个数字,然后发给GPIO控制器.前两个命令是为了往r1里写个数字,其实我们也可以用之前用过的那个 ldr 的命令,不过现在这么做是为了方便以后指定任意针脚.直接写值不如通过表达式来指定值来的灵活. OK LED是连接到GPIO的第16个针脚处.所以我们需要发送命令激活第16个针脚.
r1中的数字用来激活LED的针脚.第一行将110 放入r1. mov 指令比 ldr 指令快,因为它不涉及内存操作.不过 mov 只能用来载入确定值3.在ARM的汇编代码中,基本所有指令都由三个字母打头.这种方式被称作助记符,表明该命令实际执行的操作.比如 mov 是 move的简写, ldr 是 load register的简写. mov 指令将第二个参数 #1 移入到第一个参数的 r1.一般来说, # 必须用来指定数字,不过我们已经看到过反例了.
第二条指令是 lsl 其实是逻辑左移运算. 表示将第一个参数的二进制值向左移动第二个参数代表的位数. 在我们这,就是将110(二进制表示12)左移18位(这样就变成了10000000000000000002 = 26214410)
这些命令中固定的数字也是我从硬件说明手册上找到的4.手册上说了,GPIO控制器有一24b的空间,用于决定GPIO的设定.前四个设定前十个GPIO pin, 第五到八设定第十一到二十个GPIO pin,以此类推.总共54个针脚,所以需要6*4个byte,总共24个.在每四个byte中,有三个bits用来指定某一个GPIO pin.如果我们需要第16个pin,那么就要设定第五到八byte.因为我们在处理10-19范围类的pin.我们处理的是3bits中的第6个.所以需要移位18.
最后, str (store register的简写)命令将第一个参数 r1 存储到第二个参数运算结果指定的内存地址中.第二个参数可以直接是寄存器, 在这里是 r0,我们之前用来指定了GPIO控制器的地址,然后加上一个数值,在这里是 #4.所以结果就是我们在GPIO控制器的地址基础上加上4,在这里写入 r1 的值.其实就是第二个4bytes,前面说了6*4bytes的原理.到此为止我们发送了第一个消息给GPIO控制器,告诉它让第十六个pin做好输出准备.
5 让机器动起来
现在,LED已经准备好被打开,但是我们还需要打开它.也即发送一个消息让GPIO控制器将pin 16 关上. 没错,是关上 .芯片制造商决定这么设计的5,当GPIO的pin关闭的时候,LED打开.硬件工程师经常会有这样的决定,让OS开发人员忙活个不停.自己要注意.
mov r1,#1 lsl r1,#16 str r1,[r0,#40]
按说这上面的命令不用多说了,只是参数上稍有不同.第一行和之前一样, r1 赋值为1.第二行左移16位.因为要关闭pin16,所以我们需要在第16bit上置一(同理,其他位置就是操作其他pin了).最后还是GPIO的地址加4010.用来关闭pin(28用来打开pin)
6 收尾工作
到这里工作也就结束了,不过不幸的是,处理器还不知道我们已经完成工作了.实际上,处理器也是不停工作的.只要供电,处理器就不停工作.所以么,我们得给个任务让它一直做下去,否则Raspberry Pi就要崩溃了(在我们课程上其实也没什么关系,因为灯已经打开了,爱崩溃不崩溃吧.)
loop$: b loop$
第一行不是指令,只是一个标签.用于命名接下来的一行为 loop$. 这样的话我们就可以直接通过名称来指定某一行了.这就叫做标签.标签在翻译成二进制后就不再使用,不过对我们来说,通过名字来指定某一行代码要比直接用数字方便的多.按照惯例,我们在标签中加了一个 $ ,表明这个标签只在这段代码里有作用,其他人就会明白,这一标签在更大范围内没有重要意义. b (branch)命令就是让程序执行到标签的位置上去,而不再按照顺序执行.不过,下一行又是 b,又来一遍,结果就是一直循环下去.这样,处理器就可以一直执行这个无限循环,一直到它被安全关掉.
最后的一段空行可不是自己不小心的回车.GNU的工具链希望所有的汇编代码都能够以空行结束.以确保程序已经结束而不是一个大文件被切分后的一部分.如果不放空行,汇编器工作的时候会有警告的.
7 在Pi上执行
完成代码后,我们就得把代码放到Pi上执行了.在你的计算机上打开一个终端,将工作路径改变到源代码目录的上级目录上. 输入命令 make,回车.要是出了错误,请到troubleshooting那一章节去找解决方法.没错的话,应该就生成三个文件了.kernel.img是你的操作系统镜像.kernel.list是你写的汇编代码的清单,是自动生成的,方便将来检查是否生成成功. kernel.map文件是所有的标签的生成的一个对照文件,可以方便我们查找各个值.
安装操作系统,第一步就是要准备好一个已经安装了系统的SD卡.SD卡里,你可以看到一个kernel.img的文件.随便改个名字,比如kernel-linux.img.然后将你自己通过 make 生成的kernel.img拷到SD卡上去.现在就把现有的操作系统替换成你自己的了.最好还是要备份好原先的操作系统,说不定你自己将来还用得上.
插回SD卡,通电.OK LED灯应该已经打开.没有的话还是请去troubleshooting页上找找.如果打开了的话,那么恭喜你,你完成了你的第一个操作系统.去Lesson 2: OK02看看让LED灯闪烁的方法吧.
Footnotes:
其实实际上时告诉链接器.这是另外一个程序,功能是将多个汇编文件链接在一起.不过这个细节并不重要.
其实他们很重要,GNU的工具链主要用于创建程序,因此他需要一个程序的入口,标签名称是 _ start .由于我们在创建一个操作系统, _ start 的标签总是最开始执行的位置,而在这里我们通过 .section 和 .init 命令设定了这一位置.要是我们没有设置这个位置的话,工具链就要报错了.其实第一行的内容就是申明,我们要创建一个 _ start 的符号名称,而这个名称是全局都可见的(所以是globally的),第二行说明, _ start 的地址就是从下一行开始.有关地址,我们很快就会涉及.
这是值是指那些2进制的表示中,只有前8个bit才有1的数值.简而言之就是,8个1bit或者0bit开头,后面的全是0.
这个课程的目的其实是为了减轻你阅读这种手册的痛苦,不过如果你感兴趣,希望进一步深入,可以在 SoC-Peripherals.pdf 这里找到.
一位好心的硬件工程师向我解释了原因:现代芯片使用CMOS来组合,全名是 (Complementary Metal Oxide Semiconductor).Complementary 的意思就是每个信 号都被连接到两个晶体管上,一个是由N-type semiconductor材料制作,用于处理低 电压.一个由P-type材料制作,由于处理高电压.两者只能有一个处于打开状态,否则 就短路了.P型的没有N型导电好,所以P型晶体管要提供相同电流需要比N型大三倍.所 以LED经常是在低电压的时候打开,因为N型的低电压比P型在高电压下电流强. 还有另外一个原因,在二十世纪七十年代,芯片完全由N型材料制作(NMOS).P型的功能由电 阻器来实现.这就是说,在低电压信号的时候芯片要消耗电力而且还越来越热,即使 在芯片什么都不做的时候.如果你的电话放在你口袋什么都不做的时候还越来越热 消耗电池,你就知道这有多糟糕了.所以信号被设计成低电压的时候激活,高电压的 时候关闭,这样就不用浪费电力了.现在我们不再使用NMOS了,但是N型处理低电压还 是要比P型处理高电压要快.一般低电压激活的时候总会在信号名字上面加一横以示 说明,或者写做 SIGNAL _n 或者是/SIGNAL.不过还是容易被混淆,哪怕硬件工程师 也难逃.