mirror of
https://github.com/LCTT/TranslateProject.git
synced 2024-12-29 21:41:00 +08:00
174 lines
10 KiB
Markdown
174 lines
10 KiB
Markdown
|
[#]: collector: (lujun9972)
|
|||
|
[#]: translator: (qhwdw)
|
|||
|
[#]: reviewer: ( )
|
|||
|
[#]: publisher: ( )
|
|||
|
[#]: url: ( )
|
|||
|
[#]: subject: (Computer Laboratory – Raspberry Pi: Lesson 4 OK04)
|
|||
|
[#]: via: (https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok04.html)
|
|||
|
[#]: author: (Robert Mullins http://www.cl.cam.ac.uk/~rdm34)
|
|||
|
|
|||
|
计算机实验室 – 树莓派:课程 4 OK04
|
|||
|
======
|
|||
|
|
|||
|
OK04 课程在 OK03 的基础上进行构建,它教你如何使用定时器让 `OK` 或 `ACT` LED 灯按精确的时间间隔来闪烁。假设你已经有了 [课程 3:OK03][1] 的操作系统,我们将以它为基础来构建。
|
|||
|
|
|||
|
### 1、一个新设备
|
|||
|
|
|||
|
定时器是树莓派保持时间的唯一方法。大多数计算机都有一个电池供电的时钟,这样当计算机关机后仍然能保持时间。
|
|||
|
|
|||
|
到目前为止,我们仅看了树莓派硬件的一小部分,即 GPIO 控制器。我只是简单地告诉你做什么,然后它会发生什么事情。现在,我们继续看定时器,并继续带你去了解它的工作原理。
|
|||
|
|
|||
|
和 GPIO 控制器一样,定时器也有地址。在本案例中,定时器的基地址在 20003000~16~。阅读手册我们可以找到下面的表:
|
|||
|
|
|||
|
表 1.1 GPIO 控制器寄存器
|
|||
|
| 地址 | 大小 / 字节 | 名字 | 描述 | 读或写 |
|
|||
|
| -------- | ------------ | ---------------- | ---------------------------------------------------------- | ---------------- |
|
|||
|
| 20003000 | 4 | Control / Status | 用于控制和清除定时器通道比较器匹配的寄存器 | RW |
|
|||
|
| 20003004 | 8 | Counter | 按 1 MHz 的频率递增的计数器 | R |
|
|||
|
| 2000300C | 4 | Compare 0 | 0 号比较器寄存器 | RW |
|
|||
|
| 20003010 | 4 | Compare 1 | 1 号比较器寄存器 | RW |
|
|||
|
| 20003014 | 4 | Compare 2 | 2 号比较器寄存器 | RW |
|
|||
|
| 20003018 | 4 | Compare 3 | 3 号比较器寄存器 | RW |
|
|||
|
|
|||
|
![Flowchart of the system timer's operation][2]
|
|||
|
|
|||
|
这个表只告诉我们一部分内容,在手册中描述了更多的字段。手册上解释说,定时器本质上是按每微秒将计数器递增 1 的方式来运行。每次它是这样做的,它将计数器的低 32 位(4 字节)与 4 个比较器寄存器进行比较,如果匹配它们中的任何一个,它更新 `Control/Status` 以反映出其中有一个是匹配的。
|
|||
|
|
|||
|
关于<ruby>位<rt>bit</rt></ruby>、字节、<ruby>位字段<rt>bit field</rt></ruby>、以及数据大小的更多内容如下:
|
|||
|
|
|||
|
>
|
|||
|
> 一个位是一个单个的二进制数的名称。你可能还记得,一个单个的二进制数即可能是一个 1,也可能是一个 0。
|
|||
|
>
|
|||
|
> 一个字节是一个 8 位集合的名称。由于每个位可能是 1 或 0 这两个值的其中之一,因此,一个字节有 2^8^ = 256 个不同的可能值。我们一般解释一个字节为一个介于 0 到 255(含)之间的二进制数。
|
|||
|
>
|
|||
|
> ![Diagram of GPIO function select controller register 0.][3]
|
|||
|
>
|
|||
|
> 一个位字段是解释二进制的另一种方式。二进制可以解释为许多不同的东西,而不仅仅是一个数字。一个位字段可以将二进制看做为一系列的 1(开) 或 0(关)的开关。对于每个小开关,我们都有一个意义,我们可以使用它们去控制一些东西。我们已经遇到了 GPIO 控制器使用的位字段,使用它设置一个针脚的开或关。位为 1 时 GPIO 针脚将准确地打开或关闭。有时我们需要更多的选项,而不仅仅是开或关,因此我们将几个开关组合到一起,比如 GPIO 控制器的函数设置(如上图),每 3 位为一组控制一个 GPIO 针脚的函数。
|
|||
|
>
|
|||
|
|
|||
|
|
|||
|
我们的目标是实现一个函数,这个函数能够以一个时间数量为输入来调用它,这个输入的时间数量将作为等待的时间,然后返回。想一想如何去做,想想我们都拥有什么。
|
|||
|
|
|||
|
我认为这将有两个选择:
|
|||
|
|
|||
|
1. 从计数器中读取一个值,然后保持分支返回到相同的代码,直到计数器的等待时间数量大于它。
|
|||
|
2. 从计数器中读取一个值,加时间数量去等待,保存它到比较器寄存器,然后保持分支返回到相同的代码处,直到 `Control / Status` 寄存器更新。
|
|||
|
|
|||
|
|
|||
|
```
|
|||
|
像这样存在被称为"并发问题"的问题,并且几乎无法解决。
|
|||
|
```
|
|||
|
|
|||
|
这两种策略都工作的很好,但在本教程中,我们将只实现第一个。原因是比较器寄存器更容易出错,因为在增加等待时间并保存它到比较器的寄存器期间,计数器可能已经增加了,并因此可能会不匹配。如果请求的是 1 微秒(或更糟糕的情况是 0 微秒)的等待,这样可能导致非常长的意外延迟。
|
|||
|
|
|||
|
### 2、实现
|
|||
|
|
|||
|
```
|
|||
|
大型的操作系统通常使用等待函数来抓住机会在后台执行任务。
|
|||
|
```
|
|||
|
|
|||
|
我将把这个创建完美的等待方法的挑战留给你。我建议你将所有与定时器相关的代码都放在一个名为 `systemTimer.s` 的文件中(理由很明显)。关于这个方法的复杂部分是,计数器是一个 8 字节值,而每个寄存器仅能保存 4 字节。所以,计数器值将分到 2 个寄存器中。
|
|||
|
|
|||
|
下列的代码块是一个示例。
|
|||
|
|
|||
|
```assembly
|
|||
|
ldrd r0,r1,[r2,#4]
|
|||
|
```
|
|||
|
|
|||
|
```assembly
|
|||
|
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~。
|
|||
|
|
|||
|
实现它的更明智的方式应该是,去计算当前计数器值与来自方法启动后的那一个值的差,然后将它与要求的等待时间数量进行比较。除非恰好你希望的等待时间是支持 8 字节的,否则上面示例中寄存器 `r1` 中的值将会丢失,而计数器仅需要使用低位 4 字节。
|
|||
|
|
|||
|
当等待开始时,你应该总是确保使用大于比较,而不是使用等于比较,因为如果你尝试去等待一个时间,而这个时间正好等于方法开始的时间与结束的时间之差,那么你就错过这个值而永远等待下去。
|
|||
|
|
|||
|
如果你不明白如何编写等待函数的代码,可以参考下面的指南。
|
|||
|
|
|||
|
>
|
|||
|
> 借鉴 GPIO 控制器的创意,第一个函数我们应该去写如何取得系统定时器的地址。示例如下:
|
|||
|
>
|
|||
|
> ```assembly
|
|||
|
> .globl GetSystemTimerBase
|
|||
|
> GetSystemTimerBase:
|
|||
|
> ldr r0,=0x20003000
|
|||
|
> mov pc,lr
|
|||
|
> ```
|
|||
|
>
|
|||
|
> 另一个被证明非常有用的函数是在寄存器 `r0` 和 `r1` 中返回当前计数器值:
|
|||
|
>
|
|||
|
> ```assembly
|
|||
|
> .globl GetTimeStamp
|
|||
|
> GetTimeStamp:
|
|||
|
> push {lr}
|
|||
|
> bl GetSystemTimerBase
|
|||
|
> ldrd r0,r1,[r0,#4]
|
|||
|
> pop {pc}
|
|||
|
> ```
|
|||
|
>
|
|||
|
> 这个函数简单地使用了 `GetSystemTimerBase` 函数,并像我们前面学过的那样,使用 `ldrd` 去加载当前计数器值。
|
|||
|
>
|
|||
|
> 现在,我们可以去写我们的等待方法的代码了。首先,在方法启动后,我们需要知道计数器值,当前计数器值我们现在已经可以使用 `GetTimeStamp` 来取得了。
|
|||
|
>
|
|||
|
> ```assembly
|
|||
|
> delay .req r2
|
|||
|
> mov delay,r0
|
|||
|
> push {lr}
|
|||
|
> bl GetTimeStamp
|
|||
|
> start .req r3
|
|||
|
> mov start,r0
|
|||
|
> ```
|
|||
|
>
|
|||
|
> 这个代码复制我们的方法的输入,将延迟时间的数量放到寄存器 `r2` 中,然后调用 `GetTimeStamp`,这个函数将会在寄存器 `r0` 和 `r1` 中返回当前计数器值。接着复制计数器值的低位 4 字节到寄存器 `r3` 中。
|
|||
|
>
|
|||
|
> 接下来,我们需要计算当前计数器值与读入的值的差,然后持续这样做,直到它们的差至少是延迟大小为止。
|
|||
|
>
|
|||
|
> ```assembly
|
|||
|
> loop$:
|
|||
|
>
|
|||
|
> bl GetTimeStamp
|
|||
|
> elapsed .req r1
|
|||
|
> sub elapsed,r0,start
|
|||
|
> cmp elapsed,delay
|
|||
|
> .unreq elapsed
|
|||
|
> bls loop$
|
|||
|
> ```
|
|||
|
>
|
|||
|
> 这个代码将一直等待,一直到等待到传递给它的时间数量为止。它从计数器中读取数值,减去最初从计数器中读取的值,然后与要求的延迟时间进行比较。如果过去的时间数量小于要求的延迟,它切换到 `loop$`。
|
|||
|
>
|
|||
|
> ```assembly
|
|||
|
> .unreq delay
|
|||
|
> .unreq start
|
|||
|
> pop {pc}
|
|||
|
> ```
|
|||
|
>
|
|||
|
> 代码完成后,函数返回。
|
|||
|
>
|
|||
|
|
|||
|
|
|||
|
### 3、另一个闪灯程序
|
|||
|
|
|||
|
你一旦明白了等待函数的工作原理,修改 `main.s` 去使用它。修改各处 `r0` 的等待设置值为某个很大的数量(记住它的单位是微秒),然后在树莓派上测试。如果函数不能正常工作,请查看我们的排错页面。
|
|||
|
|
|||
|
如果正常工作,恭喜你学会控制另一个设备了,会使用它,则时间由你控制。在下一节课程中,我们将完成 OK 系列课程的最后一节 [课程 5:OK05][4],我们将使用我们已经学习过的知识让 LED 按我们的模式进行闪烁。
|
|||
|
|
|||
|
--------------------------------------------------------------------------------
|
|||
|
|
|||
|
via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok04.html
|
|||
|
|
|||
|
作者:[Robert Mullins][a]
|
|||
|
选题:[lujun9972][b]
|
|||
|
译者:[qhwdw](https://github.com/qhwdw)
|
|||
|
校对:[校对者ID](https://github.com/校对者ID)
|
|||
|
|
|||
|
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
|||
|
|
|||
|
[a]: http://www.cl.cam.ac.uk/~rdm34
|
|||
|
[b]: https://github.com/lujun9972
|
|||
|
[1]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok03.html
|
|||
|
[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
|
|||
|
[4]: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok05.html
|