烤派宝典第四章之OK04

TurnToJPG -->


#烤派宝典第四章之OK04 OK04 这一章基于OK03,将教会你如何使用定时器(timer)来精确控制’OK’或’ACT’灯的闪烁频率。我们假设你已经拥有了烤派宝典第三章之OK03中的代码和知识储备作为基础。

内容
1 新的设备
2 实现
3 另一个闪烁灯版本

###新的设备 到现在为止,我们已经杰出倒了Raspberrry Pi中的一个硬件,也就是GPIO控制器。在前面的章节中,我只是告诉了你现成的答案,需要做什么,里头的主要原理。现在我们来看看一个新的设备,定时器,这回我将手把手教会你如何从硬件手册中理解其工作方式。

和GPIO控制器一样,定时器也有一个地址。在我们的例子中,定时器的地址在2000300016, 通过阅读手册,我们能找到下面的表格:

定时器是Raspberry Pi上唯一可以保存时间的方式。大多数计算机在主板上都有电池驱动的RTC模块用于在掉电时保持时间。但是Raspberry Pi为了降低成本,去掉了这一部分电路。

  <th>大小 / Bytes</th>

  <th>名字</th>

  <th>描述</th>

  <th>读/写</th>
</tr>
  <td>4</td>

  <td>控制/状态</td>

  <td>用于控制和清除定时器通道比较位
  </td>

  <td>RW</td>
</tr>

<tr>
  <td>20003004</td>

  <td>8</td>

  <td>定时器</td>

  <td>一个以1MHz递加的定时器。</td>

  <td>R</td>
</tr>

<tr>
  <td>2000300C</td>

  <td>4</td>

  <td>比较值 0</td>

  <td>第0个比较值寄存器</td>

  <td>RW</td>
</tr>

<tr>
  <td>20003010</td>

  <td>4</td>

  <td>比较值 1</td>

  <td>第一个比较值寄存器.</td>

  <td>RW</td>
</tr>

<tr>
  <td>20003014</td>

  <td>4</td>

  <td>比较值 2</td>

  <td>第二个比较值寄存器.</td>

  <td>RW</td>
</tr>

<tr>
  <td>20003018</td>

  <td>4</td>

  <td>比较值 3</td>

  <td>第三个比较值寄存器.</td>

  <td>RW</td>
</tr>

timer

表格里包含了很多信息,但是手册里关于每个域的解释更为详尽。手册里的说明显示定时器每一微秒增加1. 每次增加1时,它将比较自身的值和最低的32位(4 byte)中的4个比较寄存器, 如果附和它们中的任何一个,就将更新控制/状态寄存器以表征时间匹配。

更详细的关于bits/bytes/bit field和数据大小的解释如下:

一个bit是单个的二进制数字位,回忆一下,单个二进制位只能有两种取值,0或者1。 一个byte是我们取给8个bit集合的名称。因为每个bit只能有两种取值,一共有2的八次方,一共256个不同的取值。我们经常理解为0到255之间的任一值。 下图是GPIO函数选择控制器0的表征。一个bit域可被理解为二进制位,除了可以被理解为数字外,二进制可以表征更多的事物。比如,我们可以用二进制来表征开关的开(1)/关(0)状态。 我们已经在GPIO控制器中接触到了比特域表征的值,用于描述一个管脚的开/关。 有时候我们需要表征更多的状态,我们就可以将很多个比特位链接起来,如图所示。比如GPIO控制函数的设置,图中所示,在图中每一个3字节的位对应控制一个GPIO的管脚函数。

gpioControllerFunctionSelect

我们的目的是实现一个可以输入等待时间值的函数,函数读取等待时间值后将在等待完相应的时间间隔后而后返回。 在实现前根据我们现有的材料,思考一下该如何实现它。

在我看来有两种方法可以实现:

  1. 从计数器中读入数值,然后通过分支跳转到同样的代码中,等待,直到计数器中的值大于设定的值。
  2. 从计数器中读入数值, 增加已经等待过的时间,将其存放在某一比较寄存器中,而后分支跳转回同样的代码中,直到控制/状态寄存器更新。

两种策略都可以工作得很好,但是在本章中我们只实现第一种方法。原因在于,比较寄存器很容易出错,因为在将等待时间储存到比较寄存器的过程中时,计数器可能会增加值,以至于不能匹配。这可能引发会在请求1微s(microsecond)的等待时间无意中引发出过长的等待间隙(或者,更糟的是,0微s, 0 microsecond的等待时间)。

这种情况被称之为并发行问题,几乎是不可被修复的。

###实现 这里我将实现wait方法的挑战留给你。 我建议你在名为’systemTimer.s'的文件中放入所有你操作定时器的代码。 这个挑战中的难点在于计数器是8byte长度的,但是每个寄存器只能储存4个byte, 因而我们需要把寄存器的值分为两部分。
下列的代码供参考:

	ldrd r0,r1,[r2,#4]

ldrd regLow,regHigh,[src,#val] 从src地址加上val的内存中取出来8个byte,将其分别放入regLow和regHigh中。

上面这条指令对你很有用处。它将一个8byte的值分配到两个寄存器中。在这个例子中,r2寄存器中存储的地址起始的8个byte的内存将被分别拷贝给r0和r1。 有点儿复杂的是这种分配方式中,r1中将存储到高的4个byte,距离来说,如果一个计数器的值是十进制的999,999,999,999 = 也就是二进制的1110100011010100101001010000111111111111, 那么r1中的值会是 111010002, r0 则包含剩下的 110101001010010100001111111111112.

大型操作系统通常在执行等待函数时,在后台运行程序以充分利用CPU。

最明智的方式是计算出当前计数器的值,减掉函数调用时的值,用这个结果和我们设定的值作比较,以确定等待的结束。为了更方便使用,除非我们支持8个byte长度的等待时间,否则我们可以把例子中r1存储的值忽略,只考虑低4个byte。

等待的过程中你应该总是记得比较大于值,而不是相等的值。因为你可能等不到那个刚好的值,错过了那个时间点,你就只能一直等在那里了。

如果你不知道如何编写wait函数代码,请点击一下链接:
waitfunction ###另一个闪烁灯版本 当你觉得自己的等待函数可以工作时,更改’main.s'来使用它。更改r0的值为一个很大的数字(记住这个值表征的是微秒), 然后在你的Raspberry Pi上验证之。如果有任何问题,请参阅troubleshooting页面。
如果你成功了,恭喜你已经掌握了另一种设备,定时器。在下一章,也就是OK系列的最后一章,烤派宝典第五章之OK05中我们将学会如何在LED按预设的模式进行闪烁。