计算机实验室之树莓派：课程 5 OK05
树莓派 摩尔斯电码
![](/data/attachment/album/201902/12/122809f73x8z1xl73xhhm7.jpg)


OK05 课程构建于课程 OK04 的基础，使用它来闪烁摩尔斯电码的 SOS 序列（`...---...`）。这里假设你已经有了 [课程 4：OK04](/article-10526-1.html) 操作系统的代码作为基础。


### 1、数据


到目前为止，我们与操作系统有关的所有内容提供的都是指令。然而有时候，指令只是完成了一半的工作。我们的操作系统可能还需要数据。



> 
> 一些早期的操作系统确实只允许特定文件中的特定类型的数据，但是这通常被认为限制太多了。现代方法确实可以使程序变得复杂的多。
> 
> 
> 


通常，数据就是些很重要的值。你可能接受过培训，认为数据就是某种类型的，比如，文本文件包含文本，图像文件包含图片，等等。说实话，这只是你的想法而已。计算机上的全部数据都是二进制数字，重要的是我们选择用什么来解释这些数据。在这个例子中，我们会用一个闪灯序列作为数据保存下来。


在 `main.s` 结束处复制下面的代码：



```
.section .data %定义 .data 段
.align 2 %对齐
pattern: %定义整形变量
.int 0b11111111101010100010001000101010
```


> 
> `.align num` 确保下一行代码的地址是 2<sup> num</sup> 的整数倍。
> 
> 
> `.int val` 输出数值 `val`。
> 
> 
> 


要区分数据和代码，我们将数据都放在 `.data` 区域。我已经将该区域包含在操作系统的内存布局图。我选择将数据放到代码后面。将我们的指令和数据分开保存的原因是，如果最后我们在自己的操作系统上实现一些安全措施，我们就需要知道代码的那些部分是可以执行的，而那些部分是不行的。


我在这里使用了两个新命令 `.align` 和 `.int`。`.align` 保证接下来的数据是按照 2 的乘方对齐的。在这个里，我使用 `.align 2` ，意味着数据最终存放的地址是 2<sup> 2=4</sup> 的整数倍。这个操作是很重要的，因为我们用来读取内存的指令 `ldr` 要求内存地址是 4 的倍数。


命令 `.int` 直接复制它后面的常量到输出。这意味着 11111111101010100010001000101010<sub> 2</sub> 将会被存放到输出，所以该标签模式实际是将这段数据标识为模式。



> 
> 关于数据的一个挑战是寻找一个高效和有用的展示形式。这种保存一个开、关的时间单元的序列的方式，运行起来很容易，但是将很难编辑，因为摩尔斯电码的 `-` 或 `.` 样式丢失了。
> 
> 
> 


如我提到的，数据可以代表你想要的所有东西。在这里我编码了摩尔斯电码的 SOS 序列，对于不熟悉的人，就是 `...---...`。我使用 0 表示一个时间单元的 LED 灭灯，而 1 表示一个时间单元的 LED 亮。这样，我们可以像这样编写一些代码在数据中显示一个序列，然后要显示不同序列，我们所有需要做的就是修改这段数据。下面是一个非常简单的例子，操作系统必须一直执行这段程序，解释和展示数据。


复制下面几行到 `main.s` 中的标记 `loop$` 之前。



```
ptrn .req r4 %重命名 r4 为 ptrn
ldr ptrn,=pattern %加载 pattern 的地址到 ptrn
ldr ptrn,[ptrn] %加载地址 ptrn 所在内存的值
seq .req r5 %重命名 r5 为 seq
mov seq,#0 %seq 赋值为 0
```

这段代码加载 `pattrern` 到寄存器 `r4`，并加载 0 到寄存器 `r5`。`r5` 将是我们的序列位置，所以我们可以追踪我们已经展示了多少个 `pattern`。


如果 `pattern` 的当前位置是 1 且仅有一个 1，下面的代码将非零值放入 `r1`。



```
mov r1,#1 %加载1到 r1
lsl r1,seq %对r1 的值逻辑左移 seq 次
and r1,ptrn %按位与
```

这段代码对你调用 `SetGpio` 很有用，它必须有一个非零值来关掉 LED，而一个 0 值会打开 LED。


现在修改 `main.s` 中你的全部代码，这样代码中每次循环会根据当前的序列数设置 LED，等待 250000 毫秒（或者其他合适的延时），然后增加序列数。当这个序列数到达 32 就需要返回 0。看看你是否能实现这个功能，作为额外的挑战，也可以试着只使用一条指令。


### 2、当你玩得开心时，时间过得很快


你现在准备好在树莓派上实验。应该闪烁一串包含 3 个短脉冲，3 个长脉冲，然后 3 个短脉冲的序列。在一次延时之后，这种模式应该重复。如果这不工作，请查看我们的问题页。


一旦它工作，祝贺你已经抵达 OK 系列教程的结束点。


在这个系列我们学习了汇编代码，GPIO 控制器和系统定时器。我们已经学习了函数和 ABI，以及几个基础的操作系统原理，已经关于数据的知识。


你现在已经可以准备学习下面几个更高级的课程的某一个。


* [Screen](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/screen01.html) 系列是接下来的，会教你如何通过汇编代码使用屏幕。
* [Input](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/input01.html) 系列教授你如何使用键盘和鼠标。


到现在，你已经有了足够的信息来制作操作系统，用其它方法和 GPIO 交互。如果你有任何机器人工具，你可能会想尝试编写一个通过 GPIO 管脚控制的机器人操作系统。




---


via: <https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok05.html>


作者：[Robert Mullins](http://www.cl.cam.ac.uk/%7Erdm34) 选题：[lujun9972](https://github.com/lujun9972) 译者：[ezio](https://github.com/oska874) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Raspberry Pi

# Lesson 5 OK05

The OK05 lesson builds on OK04 using it to flash the SOS Morse Code pattern (...---...).
It is assumed you have the code for the [Lesson 4: OK04](ok04.html) operating
system as a basis.

## 1 Data

So far, all we've had to do with our operating system is provide instructions to be followed. Sometimes however, instructions are only half the story. Our operating systems may need data.

Some early Operating Systems did only allow certain types of data in certain files, but this was generally found to be too restrictive. The modern way does make programs a lot more complicated however.

In general data is just values that are important. You are probably trained to think of data as being of a specific type, e.g. a text file contains text, an image file contains an image, etc. This is, in truth, just an idea. All data on a computer is just binary numbers, how we choose to interpret them is what counts. In this example we're going to store a light flashing sequence as data.

At the end of 'main.s' copy the following code:

.section .data

.align 2

pattern:

.int 0b11111111101010100010001000101010


.align num ensures the address of the next line
is a multiple of 2num
.

.int val outputs the number val .

To differentiate between data and code, we put all the data in the .data. I've included this on the operating system memory layout diagram here. I've just chosen to put the data after the end of the code. The reason for keeping our data and instructions separate is so that if we eventually implement some security on our operating system, we need to know what parts of the code can be executed, and what parts can't.

I've used two new commands here. .align and .int. .align
ensures alignment of the following data to a specified power of 2. In this case
I've used .align 2 which means that this data
will definitely be placed at an address which is a multiple of 22 = 4.
It is really important to do this, because the ldr
instruction we used to read memory only works at addresses that are multiples of
4.

The .int command copies the constant after it
into the output directly. That means that 111111111010101000100010001010102
will be placed into the output, and so the label pattern actually labels this piece
of data as pattern.

One challenge with data is finding an efficient and useful representation. This method of storing the sequence as on and off units of time is easy to run, but would be difficult to edit, as the concept of a Morse - or . is lost.

As I mentioned, data can mean whatever you want. In this case I've encoded the Morse Code SOS sequence, which is ...---... for those unfamiliar. I've used a 0 to represent a unit of time with the LED off, and a 1 to represent a unit of time with the LED on. That way, we can write some code which just displays a sequence in data like this one, and then all we have to do to make it display a different sequence is change the data. This is a very simple example of what operating systems must do all the time; interpret and display data.

Copy the following lines before the loop$ label in 'main.s'.

ptrn .req r4

ldr ptrn,=pattern

ldr ptrn,[ptrn]

seq .req r5

mov seq,#0


This code loads the pattern into r4, and loads 0 into r5. r5 will be our sequence position, so we can keep track of how much of the pattern we have displayed.

The following code puts a non-zero into r1 if and only if there is a 1 in the current part of the pattern.

mov r1,#1

lsl r1,seq

and r1,ptrn


This code is useful for your calls to SetGpio, which must have a non-zero value to turn the LED off, and a value of zero to turn the LED on.

Now modify all of your code in 'main.s' so that each loop the code sets the LED based on the current sequence number, waits for 250000 micro seconds (or any other appropriate delay), and then increments the sequence number. When the sequence number reaches 32, it needs to go back to 0. See if you can implement this, and for an extra challenge, try to do it using only 1 instruction (solution in the download).

## 2 Time Flies When You're Having Fun...

You're now ready to test this on the Raspberry Pi. It should flash out a sequence of 3 short pulses, 3 long pulses and then 3 more short pulses. After a delay, the pattern should repeat. If it doesn't work please see our troubleshooting page.

Once it works, congratulations you have reached the end of the OK series of tutorials.

In this series we've learnt about assembly code, the GPIO controller and the System Timer. We've learnt about functions and the ABI, as well as several basic Operating System concepts, and also about data.

You're now ready to move onto one of the more advanced series.

- The
[Screen](screen01.html)series is next and teaches you how to use the screen with assembly code. - The
[Input](input01.html)series teaches you how to use the keyboard and mouse.

By now you already have enough information to make Operating Systems that interact with the GPIO in other ways. If you have any robot kits, you may want to try writing a robot operating system controlled with the GPIO pins!