PRF:20120204 Computer Laboratory - Raspberry Pi- Lesson 4 OK04.md

@qhwdw
This commit is contained in:
Xingyu.Wang 2019-02-10 23:42:45 +08:00
parent c2b32f381e
commit cc71021cf1

View File

@ -1,26 +1,27 @@
[#]: collector: (lujun9972) [#]: collector: (lujun9972)
[#]: translator: (qhwdw) [#]: translator: (qhwdw)
[#]: reviewer: ( ) [#]: reviewer: (wxy)
[#]: publisher: ( ) [#]: publisher: ( )
[#]: url: ( ) [#]: url: ( )
[#]: subject: (Computer Laboratory Raspberry Pi: Lesson 4 OK04) [#]: subject: (Computer Laboratory Raspberry Pi: Lesson 4 OK04)
[#]: via: (https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok04.html) [#]: via: (https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok04.html)
[#]: author: (Robert Mullins http://www.cl.cam.ac.uk/~rdm34) [#]: author: (Robert Mullins http://www.cl.cam.ac.uk/~rdm34)
计算机实验室 树莓派:课程 4 OK04 计算机实验室树莓派:课程 4 OK04
====== ======
OK04 课程在 OK03 的基础上进行构建,它教你如何使用定时器让 `OK``ACT` LED 灯按精确的时间间隔来闪烁。假设你已经有了 [课程 3OK03][1] 的操作系统,我们将以它为基础来构建。 OK04 课程在 OK03 的基础上进行构建,它教你如何使用定时器让 OK 或 ACT LED 灯按精确的时间间隔来闪烁。假设你已经有了 [课程 3OK03][1] 的操作系统,我们将以它为基础来构建。
### 1、一个新设备 ### 1、一个新设备
定时器是树莓派保持时间的唯一方法。大多数计算机都有一个电池供电的时钟,这样当计算机关机后仍然能保持时间。 > 定时器是树莓派保持时间的唯一方法。大多数计算机都有一个电池供电的时钟,这样当计算机关机后仍然能保持时间。
到目前为止,我们仅看了树莓派硬件的一小部分,即 GPIO 控制器。我只是简单地告诉你做什么,然后它会发生什么事情。现在,我们继续看定时器,并继续带你去了解它的工作原理。 到目前为止,我们仅看了树莓派硬件的一小部分,即 GPIO 控制器。我只是简单地告诉你做什么,然后它会发生什么事情。现在,我们继续看定时器,并继续带你去了解它的工作原理。
和 GPIO 控制器一样,定时器也有地址。在本案例中,定时器的基地址在 20003000~16~。阅读手册我们可以找到下面的表: 和 GPIO 控制器一样,定时器也有地址。在本案例中,定时器的基地址在 20003000<sub>16</sub>。阅读手册我们可以找到下面的表:
表 1.1 GPIO 控制器寄存器 表 1.1 GPIO 控制器寄存器
| 地址 | 大小 / 字节 | 名字 | 描述 | 读或写 | | 地址 | 大小 / 字节 | 名字 | 描述 | 读或写 |
| -------- | ------------ | ---------------- | ---------------------------------------------------------- | ---------------- | | -------- | ------------ | ---------------- | ---------------------------------------------------------- | ---------------- |
| 20003000 | 4 | Control / Status | 用于控制和清除定时器通道比较器匹配的寄存器 | RW | | 20003000 | 4 | Control / Status | 用于控制和清除定时器通道比较器匹配的寄存器 | RW |
@ -34,40 +35,33 @@ OK04 课程在 OK03 的基础上进行构建,它教你如何使用定时器让
这个表只告诉我们一部分内容,在手册中描述了更多的字段。手册上解释说,定时器本质上是按每微秒将计数器递增 1 的方式来运行。每次它是这样做的,它将计数器的低 32 位4 字节)与 4 个比较器寄存器进行比较,如果匹配它们中的任何一个,它更新 `Control/Status` 以反映出其中有一个是匹配的。 这个表只告诉我们一部分内容,在手册中描述了更多的字段。手册上解释说,定时器本质上是按每微秒将计数器递增 1 的方式来运行。每次它是这样做的,它将计数器的低 32 位4 字节)与 4 个比较器寄存器进行比较,如果匹配它们中的任何一个,它更新 `Control/Status` 以反映出其中有一个是匹配的。
关于<ruby><rt>bit</rt></ruby>、字节、<ruby>位字段<rt>bit field</rt></ruby>、以及数据大小的更多内容如下: 关于<ruby><rt>bit</rt></ruby><ruby>字节<rt>byte</rt></ruby><ruby>位字段<rt>bit field</rt></ruby>、以及数据大小的更多内容如下:
> >
> 一个位是一个单个的二进制数的名称。你可能还记得,一个单个的二进制数可能是一个 1也可能是一个 0。 > 一个位是一个单个的二进制数的名称。你可能还记得,一个单个的二进制数可能是一个 1也可能是一个 0。
> >
> 一个字节是一个 8 位集合的名称。由于每个位可能是 1 或 0 这两个值的其中之一,因此,一个字节有 2^8^ = 256 个不同的可能值。我们一般解释一个字节为一个介于 0 到 255之间的二进制数。 > 一个字节是一个 8 位集合的名称。由于每个位可能是 1 或 0 这两个值的其中之一,因此,一个字节有 2^8 = 256 个不同的可能值。我们一般解释一个字节为一个介于 0 到 255之间的二进制数。
> >
> ![Diagram of GPIO function select controller register 0.][3] > ![Diagram of GPIO function select controller register 0.][3]
> >
> 一个位字段是解释二进制的另一种方式。二进制可以解释为许多不同的东西,而不仅仅是一个数字。一个位字段可以将二进制看做为一系列的 1 或 0的开关。对于每个小开关我们都有一个意义我们可以使用它们去控制一些东西。我们已经遇到了 GPIO 控制器使用的位字段,使用它设置一个针脚的开或关。位为 1 时 GPIO 针脚将准确地打开或关闭。有时我们需要更多的选项,而不仅仅是开或关,因此我们将几个开关组合到一起,比如 GPIO 控制器的函数设置(如上图),每 3 位为一组控制一个 GPIO 针脚的函数。 > 一个位字段是解释二进制的另一种方式。二进制可以解释为许多不同的东西,而不仅仅是一个数字。一个位字段可以将二进制看做为一系列的 1 或 0的开关。对于每个小开关我们都有一个意义我们可以使用它们去控制一些东西。我们已经遇到了 GPIO 控制器使用的位字段,使用它设置一个针脚的开或关。位为 1 时 GPIO 针脚将准确地打开或关闭。有时我们需要更多的选项,而不仅仅是开或关,因此我们将几个开关组合到一起,比如 GPIO 控制器的函数设置(如上图),每 3 位为一组控制一个 GPIO 针脚的函数。
>
我们的目标是实现一个函数,这个函数能够以一个时间数量为输入来调用它,这个输入的时间数量将作为等待的时间,然后返回。想一想如何去做,想想我们都拥有什么。 我们的目标是实现一个函数,这个函数能够以一个时间数量为输入来调用它,这个输入的时间数量将作为等待的时间,然后返回。想一想如何去做,想想我们都拥有什么。
我认为这将有两个选择: 我认为这将有两个选择:
1. 从计数器中读取一个值,然后保持分支返回到相同的代码,直到计数器的等待时间数量大于它。 1. 从计数器中读取一个值,然后保持分支返回到相同的代码,直到计数器的等待时间数量大于它。
2. 从计数器中读取一个值,加时间数量去等待,保存它到比较器寄存器,然后保持分支返回到相同的代码处,直到 `Control / Status` 寄存器更新。 2. 从计数器中读取一个值,加上要等待的时间数量,将它保存到比较器寄存器,然后保持分支返回到相同的代码处,直到 `Control / Status` 寄存器更新。
```
像这样存在被称为"并发问题"的问题,并且几乎无法解决。
```
这两种策略都工作的很好,但在本教程中,我们将只实现第一个。原因是比较器寄存器更容易出错,因为在增加等待时间并保存它到比较器的寄存器期间,计数器可能已经增加了,并因此可能会不匹配。如果请求的是 1 微秒(或更糟糕的情况是 0 微秒)的等待,这样可能导致非常长的意外延迟。 这两种策略都工作的很好,但在本教程中,我们将只实现第一个。原因是比较器寄存器更容易出错,因为在增加等待时间并保存它到比较器的寄存器期间,计数器可能已经增加了,并因此可能会不匹配。如果请求的是 1 微秒(或更糟糕的情况是 0 微秒)的等待,这样可能导致非常长的意外延迟。
> 像这样存在被称为“并发问题”的问题,并且几乎无法解决。
### 2、实现 ### 2、实现
``` 我将把这个创建完美的等待方法的挑战基本留给你。我建议你将所有与定时器相关的代码都放在一个名为 `systemTimer.s` 的文件中(理由很明显)。关于这个方法的复杂部分是,计数器是一个 8 字节值,而每个寄存器仅能保存 4 字节。所以,计数器值将分到 2 个寄存器中。
大型的操作系统通常使用等待函数来抓住机会在后台执行任务。
```
我将把这个创建完美的等待方法的挑战留给你。我建议你将所有与定时器相关的代码都放在一个名为 `systemTimer.s` 的文件中(理由很明显)。关于这个方法的复杂部分是,计数器是一个 8 字节值,而每个寄存器仅能保存 4 字节。所以,计数器值将分到 2 个寄存器中 > 大型的操作系统通常使用等待函数来抓住机会在后台执行任务。
下列的代码块是一个示例。 下列的代码块是一个示例。
@ -75,13 +69,11 @@ OK04 课程在 OK03 的基础上进行构建,它教你如何使用定时器让
ldrd r0,r1,[r2,#4] ldrd r0,r1,[r2,#4]
``` ```
```assembly > `ldrd regLow,regHigh,[src,#val]``src` 中的数加上 `val` 之和的地址加载 8 字节到寄存器 `regLow``regHigh` 中。
ldrd regLow,regHigh,[src,#val] 从 src 加上 val 数的地址上加载 8 字节到寄存器 regLow 和 regHigh 中。
```
上面的代码中你可以发现一个很有用的指令是 `ldrd`。它从两个寄存器中加载 8 字节的内存。在本案例中,这 8 字节内存从寄存器 `r2` 中的地址开始,将被复制进寄存器 `r0``r1`。这种安排的稍微复杂之处在于 `r1` 实际上只持有了高位 4 字节。换句话说就是,如果如果计数器的值是 999,999,999,999~10~ = 1110100011010100101001010000111111111111~2~ ,那么寄存器 `r1` 中只有 11101000~2~,而寄存器 `r0` 中则是 11010100101001010000111111111111~2~ 上面的代码中你可以发现一个很有用的指令是 `ldrd`。它加载 8 字节的内存到两个寄存器中。在本案例中,这 8 字节内存从寄存器 `r2` 中的地址 + 4 开始,将被复制进寄存器 `r0``r1`。这种安排的稍微复杂之处在于 `r1` 实际上只持有了高位 4 字节。换句话说就是,如果如果计数器的值是 999,999,999,999<sub>10</sub> = 1110100011010100101001010000111111111111<sub>2</sub> ,那么寄存器 `r1` 中只有 11101000<sub>2</sub>,而寄存器 `r0` 中则是 11010100101001010000111111111111<sub>2</sub>
实现它的更明智的方式应该是,去计算当前计数器值与来自方法启动后的那一个值的差,然后将它与要求的等待时间数量进行比较。除非恰好你希望的等待时间是支持 8 字节的,否则上面示例中寄存器 `r1` 中的值将会丢失,而计数器仅需要使用低位 4 字节。 实现它的更明智的方式应该是,去计算当前计数器值与来自方法启动后的那一个值的差,然后将它与要求的等待时间数量进行比较。除非恰好你希望的等待时间是占用 8 字节的,否则上面示例中寄存器 `r1` 中的值将会丢弃,而计数器仅需要使用低位 4 字节。
当等待开始时,你应该总是确保使用大于比较,而不是使用等于比较,因为如果你尝试去等待一个时间,而这个时间正好等于方法开始的时间与结束的时间之差,那么你就错过这个值而永远等待下去。 当等待开始时,你应该总是确保使用大于比较,而不是使用等于比较,因为如果你尝试去等待一个时间,而这个时间正好等于方法开始的时间与结束的时间之差,那么你就错过这个值而永远等待下去。
@ -97,7 +89,7 @@ ldrd regLow,regHigh,[src,#val] 从 src 加上 val 数的地址上加载 8 字节
> mov pc,lr > mov pc,lr
> ``` > ```
> >
> 另一个被证明非常有用的函数是在寄存器 `r0``r1` 中返回当前计数器值: > 另一个被证明非常有用的函数是返回在寄存器 `r0``r1` 中的当前计数器值:
> >
> ```assembly > ```assembly
> .globl GetTimeStamp > .globl GetTimeStamp
@ -110,7 +102,7 @@ ldrd regLow,regHigh,[src,#val] 从 src 加上 val 数的地址上加载 8 字节
> >
> 这个函数简单地使用了 `GetSystemTimerBase` 函数,并像我们前面学过的那样,使用 `ldrd` 去加载当前计数器值。 > 这个函数简单地使用了 `GetSystemTimerBase` 函数,并像我们前面学过的那样,使用 `ldrd` 去加载当前计数器值。
> >
> 现在,我们可以去写我们的等待方法的代码了。首先,在方法启动后,我们需要知道计数器值,当前计数器值我们现在已经可以使用 `GetTimeStamp` 来取得 > 现在,我们可以去写我们的等待方法的代码了。首先,在方法启动后,我们需要知道计数器值,我们可以使用 `GetTimeStamp` 来取得。
> >
> ```assembly > ```assembly
> delay .req r2 > delay .req r2
@ -121,9 +113,9 @@ ldrd regLow,regHigh,[src,#val] 从 src 加上 val 数的地址上加载 8 字节
> mov start,r0 > mov start,r0
> ``` > ```
> >
> 这个代码复制我们的方法的输入,将延迟时间的数量放到寄存器 `r2` 中,然后调用 `GetTimeStamp`,这个函数将会在寄存器 `r0``r1` 中返回当前计数器值。接着复制计数器值的低位 4 字节到寄存器 `r3` 中。 > 这个代码复制我们的方法的输入,将延迟时间的数量放到寄存器 `r2` 中,然后调用 `GetTimeStamp`,这个函数将会返回寄存器 `r0``r1` 中的当前计数器值。接着复制计数器值的低位 4 字节到寄存器 `r3` 中。
> >
> 接下来,我们需要计算当前计数器值与读入的值的差,然后持续这样做,直到它们的差至少是延迟大小为止。 > 接下来,我们需要计算当前计数器值与读入的值的差,然后持续这样做,直到它们的差至少是 `delay`大小为止。
> >
> ```assembly > ```assembly
> loop$: > loop$:
@ -136,7 +128,7 @@ ldrd regLow,regHigh,[src,#val] 从 src 加上 val 数的地址上加载 8 字节
> bls loop$ > bls loop$
> ``` > ```
> >
> 这个代码将一直等待,一直到等待到传递给它的时间数量为止。它从计数器中读取数值,减去最初从计数器中读取的值,然后与要求的延迟时间进行比较。如果过去的时间数量小于要求的延迟,它切换 `loop$` > 这个代码将一直等待,一直到等待到传递给它的时间数量为止。它从计数器中读取数值,减去最初从计数器中读取的值,然后与要求的延迟时间进行比较。如果过去的时间数量小于要求的延迟,它切换 `loop$`
> >
> ```assembly > ```assembly
> .unreq delay > .unreq delay
@ -161,13 +153,13 @@ via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok04.html
作者:[Robert Mullins][a] 作者:[Robert Mullins][a]
选题:[lujun9972][b] 选题:[lujun9972][b]
译者:[qhwdw](https://github.com/qhwdw) 译者:[qhwdw](https://github.com/qhwdw)
校对:[校对者ID](https://github.com/校对者ID) 校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: http://www.cl.cam.ac.uk/~rdm34 [a]: http://www.cl.cam.ac.uk/~rdm34
[b]: https://github.com/lujun9972 [b]: https://github.com/lujun9972
[1]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok03.html [1]: https://linux.cn/article-10519-1.html
[2]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/images/systemTimer.png [2]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/images/systemTimer.png
[3]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/images/gpioControllerFunctionSelect.png [3]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/images/gpioControllerFunctionSelect.png
[4]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok05.html [4]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok05.html