This commit is contained in:
bai 2020-05-26 20:13:57 +08:00
parent 753b654ef9
commit 7c56bf812f
24 changed files with 192 additions and 157 deletions

View File

@ -1,16 +1,16 @@
# The Embedded Rust Book
# 嵌入式Rust编程
> Documentation on how to use the Rust Programming Language to develop firmware for bare metal (microcontroller) devices
>有关如何使用Rust编程语言为裸机微控制器设备开发固件的文档
This project is developed and maintained by the [Resources team][team].
此项目由[资源团队] [团队]开发和维护。
See [the issue tracker] for more details. This book is a living document, and is updated continuously.
有关更多详细信息,请参见[问题跟踪器]。这本书是一份有生命的文件,并不断更新。
[the issue tracker]: https://github.com/rust-embedded/book/issues
[问题跟踪器]https://github.com/nkbai/book/issues
## Online Copies of this Book
## 本书的在线副本
This book is located at https://docs.rust-embedded.org/book/
本书位于 https://stevenbai.top/rustbook/book/
## License
@ -32,15 +32,5 @@ Copies of the licenses used by this project may also be found here:
[Apache License v2.0 Hosted]: http://www.apache.org/licenses/LICENSE-2.0
[CC-BY-SA v4.0 Hosted]: https://creativecommons.org/licenses/by-sa/4.0/legalcode
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.
## Code of Conduct
Contribution to this crate is organized under the terms of the [Rust Code of
Conduct][CoC], the maintainer of this crate, the [Resources team][team], promises
to intervene to uphold that code of conduct.
[CoC]: CODE_OF_CONDUCT.md
[team]: https://github.com/rust-embedded/wg#the-resources-team

46
README_en.md Normal file
View File

@ -0,0 +1,46 @@
# The Embedded Rust Book
> Documentation on how to use the Rust Programming Language to develop firmware for bare metal (microcontroller) devices
This project is developed and maintained by the [Resources team][team].
See [the issue tracker] for more details. This book is a living document, and is updated continuously.
[the issue tracker]: https://github.com/rust-embedded/book/issues
## Online Copies of this Book
This book is located at https://docs.rust-embedded.org/book/
## License
The Embedded Rust Book (this project) is distributed under the following licenses:
* The code samples and free-standing Cargo projects contained within this book are licensed under the terms of both the [MIT License] and the [Apache License v2.0].
* The written prose contained within this book is licensed under the terms of the Creative Commons [CC-BY-SA v4.0] license.
Copies of the licenses used by this project may also be found here:
* [MIT License Hosted]
* [Apache License v2.0 Hosted]
* [CC-BY-SA v4.0 Hosted]
[MIT License]: ./LICENSE-MIT
[Apache License v2.0]: ./LICENSE-APACHE
[CC-BY-SA v4.0]: ./LICENSE-CC-BY-SA
[MIT License Hosted]: https://opensource.org/licenses/MIT
[Apache License v2.0 Hosted]: http://www.apache.org/licenses/LICENSE-2.0
[CC-BY-SA v4.0 Hosted]: https://creativecommons.org/licenses/by-sa/4.0/legalcode
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.
## Code of Conduct
Contribution to this crate is organized under the terms of the [Rust Code of
Conduct][CoC], the maintainer of this crate, the [Resources team][team], promises
to intervene to uphold that code of conduct.
[CoC]: CODE_OF_CONDUCT.md
[team]: https://github.com/rust-embedded/wg#the-resources-team

View File

@ -9,7 +9,7 @@
`core`本身是没有动态内存分配的,但是编译器自带了一个**unstable**的`alloc` crate支持动态内存分配.
如果需要容器,基于堆的实现不是唯一的选择。您还可以使用“固定容量”容器;可以在['heapless`]crate中找到一种这样的实现。
如果需要容器,基于堆的实现不是唯一的选择。您还可以使用“固定容量”容器;可以在[`heapless`]crate中找到一种这样的实现。
[`heapless`]:https://crates.io/crates/heapless

View File

@ -4,7 +4,7 @@
* 中断处理程序,每当相关中断发生时运行,
* 多种形式的多线程,您的微处理器定期在程序的各个部分之间进行交换,
* 在某些系统中是多核微处理器,其中每个核可以同时独立运行程序的不同部分。
* 在多核微处理器,其中每个核可以同时独立运行程序的不同部分。
由于许多嵌入式程序需要处理中断因此并发通常迟早会出现这也是可能会发生许多细微而困难的错误的地方。幸运的是Rust提供了许多抽象和安全保证来帮助我们编写正确的代码。
@ -136,12 +136,12 @@ fn timer() {
这次COUNTER是一个安全的static变量。由于使用了`AtomicUsize`类型,可以从中断处理程序和主线程安全地修改`COUNTER',而无需禁用中断。如果可能,这是一个更好的解决方案-但您的平台可能不支持它。
关于[`Ordering]的注释:这会影响编译器和硬件如何对指令进行重新排序,并对缓存可见性产生影响。假设目标是单核心平台,那么 `Relaxed`就足够了,并且在这种情况下是最有效的选择。更严格的顺序将导致编译器在原子操作前后发出内存屏障。取决于您正在使用原子操作的种类,您可能需要也可能不需要更严格的顺序!原子模型的精确细节非常复杂,在其他地方有最好的描述。
关于[`Ordering`]的注释:这会影响编译器和硬件如何对指令进行重新排序,并对缓存可见性产生影响。假设目标是单核心平台,那么 `Relaxed`就足够了,并且在这种情况下是最有效的选择。更严格的顺序将导致编译器在原子操作前后发出内存屏障。取决于您正在使用原子操作的种类,您可能需要也可能不需要更严格的顺序!原子模型的精确细节非常复杂,在其他地方有最好的描述。
有关原子操作和顺序的更多详细信息,请参见[nomicon]。
[`Ordering`]:https//doc.rust-lang.org/core/sync/atomic/enum.Ordering.html
[nomicon]:https//doc.rust-lang.org/nomicon/atomics.html
[`Ordering`]:https://doc.rust-lang.org/core/sync/atomic/enum.Ordering.html
[nomicon]:https://doc.rust-lang.org/nomicon/atomics.html
## 抽象Send和Sync
@ -265,9 +265,9 @@ fn timer() {
}
```
我们现在使用的是[`Cell`],它与`RefCell`一样用于提供安全的内部可变性。我们已经看到过`UnsafeCell`它是Rust中内部可变性的底层:它允许您获取对其包括的值的多个可变引用,但只能使用不安全的代码。一个`Cell`就像一个`UnsafeCell`一样但是它提供了一个安全的接口它只允许获取当前值的副本或替换当前值而获取不到引用并且由于它不满足Sync因此不能在线程之间共享。这些限制意味着可以安全使用但是我们不能直接在``static`变量中使用它,因为`static`必须为Sync。
我们现在使用的是[`Cell`],它与`RefCell`一样用于提供安全的内部可变性。我们已经看到过`UnsafeCell`它是Rust中内部可变性的基础:它允许您获取对其包括的值的多个可变引用,但只能使用不安全的代码。一个`Cell`就像一个`UnsafeCell`一样但是它提供了一个安全的接口它只允许获取当前值的副本或替换当前值而获取不到引用并且由于它不满足Sync因此不能在线程之间共享。这些限制意味着可以安全使用但是我们不能直接在`static`变量中使用它,因为`static`必须为Sync。
[`Cell`]:https//doc.rust-lang.org/core/cell/struct.Cell.html
[`Cell`]:https://doc.rust-lang.org/core/cell/struct.Cell.html
那么,为什么上面的示例起作用? `Mutex <T>`对要任何实现了`Send`的`T`(比如这里的`Cell`)都实现了`Sync`。它之所以安全,是因为它仅在临界区内允许访问其内容。因此,我们可以实现一个没有任何不安全代码的安全计数器!
@ -351,7 +351,7 @@ static MY_GPIO: Mutex<RefCell<Option<stm32f405::GPIOA>>> =
Mutex::new(RefCell::new(None));
```
现在,我们的共享变量的类型是` Mutex<RefCell<Option<stm32f405::GPIOA>>>`。 `Mutex`可确保我们仅在临界区内具有访问权限,因此就算是`RefCell`不支持`Sync`,变量`MY_GPIO`也能够支持Sync。 `RefCell`为我们提供了带有引用的内部可变性, `Option`使我们可以先将该变量初始化为空稍后才将其实际内容移入。我们不能直接使用static的单例`GPIOA`,所有这一切都是必须的。
现在,我们的共享变量的类型是` Mutex<RefCell<Option<stm32f405::GPIOA>>>`。 `Mutex`可确保我们仅在临界区内具有访问权限,因此就算是`RefCell`不支持`Sync`,变量`MY_GPIO`也能够支持Sync。 `RefCell`为我们提供了带有引用的内部可变性, `Option`使我们可以先将该变量初始化为空稍后才将其实际内容移入。我们不能直接使用static的单例`GPIOA`,所有这一切都是必须的。
```rust , ignore
@ -372,7 +372,7 @@ Finally we use `MY_GPIO` in a safe and concurrent fashion. The critical section
todo 感觉这段话是错的,需要验证.
由于我们无法将`GPIOA`从`Option`中移出,因此我们需要使用`as_ref()`将其转换为`&Option<&GPIOA>`,最后我们可以通过`unwrap()` 获得到`GPIOA`,从而可以修改外设状态。(todo 此处应该是可以访问外设)
由于我们无法将`GPIOA`从`Option`中移出,因此我们需要使用`as_ref()`将其转换为`&Option<&GPIOA>`,最后我们可以通过`unwrap()` 获得到`GPIOA`,从而可以修改外设状态。(todo 此处应该是可以访问外设)
todo &GPIOaA是只读借用啊,在怎么修改?
如果我们需要对共享资源的可变引用,则应该使用`borrow_mut` 和 `deref_mut`。以下代码显示了使用TIM2计时器的示例。
@ -426,7 +426,7 @@ fn timer() {
> version="0.6.0"
> features=["const-fn"]
> ```
>同时,`const-fn`已经在稳定版Rust上工作了一段时间。因此预计这个特性很快会成为`cortex-m`的默认配置,这样以后就不必在Cargo.toml中配置特性了。
>同时,`const-fn`已经在稳定版Rust上工作了一段时间。因此预计这个特性很快会成为`cortex-m`的默认配置,这样以后就不必在Cargo.toml中配置特性了。
>
目前这样虽然安全,但还有点笨拙。我们还有什么可以做的吗?

View File

@ -35,7 +35,7 @@ unsafe fn foo(num: u32) {
[问题# 61]上有我们收集的示例。
[问题# 61]:https//github.com/rust-embedded/book/issues/61
[问题# 61]:https://github.com/rust-embedded/book/issues/61
## 与RTOS的互操作性
@ -44,4 +44,4 @@ unsafe fn foo(num: u32) {
[问题# 62]上有我们收集的示例。
[问题# 62]:https//github.com/rust-embedded/book/issues/62
[问题# 62]:https://github.com/rust-embedded/book/issues/62

View File

@ -4,7 +4,7 @@
使用micro-USB电缆将开发板连接到笔记本电脑/PC。开发板有两个USB接口。请使用位于板边缘中央的标有“USB ST-LINK”的USB接口。
还要检查是否已拔掉ST-LINK跳线。见下图 ST-LINK标头用红色圈出。
还要检查ST-LINK跳线是否连接。见下图; ST-LINK标头用红色圈出。
<p align="center">
<img title="Connected discovery board" src="../../assets/verify.jpeg">
@ -54,7 +54,7 @@ $ openocd -f interface/stlink-v2.cfg -f target/stm32f3x.cfg
如果这两个命令都不能作为普通用户使用请尝试以root权限运行它们例如`sudo openocd ..`)。如果这时可以正常工作,则请检查[udev规则]是否已正确设置。
[udev规则]:linux.md# udev-rules
[udev规则]:linux.md#udev-rules
如果您到了这一步OpenOCD无法正常工作请提交一个[问题],我们将为您提供帮助!

View File

@ -2,7 +2,7 @@
## 寄存器
让我们看一下`SysTick`外设(每个Cortex-M处理器内核随附的简单计时器)。通常,您会在芯片制造商的《技术参考手册》中查找这些信息但是此示例对于所有ARM Cortex-M内核都是通用的因此也可以在[ARM参考手册]中进行查到。我们看到有四个寄存器:
让我们看一下`SysTick`外设(所有Cortex-M处理器都有的简单计时器)。通常您会在芯片制造商的《技术参考手册》中查找这些信息但是此示例对于所有ARM Cortex-M内核都是通用的因此也可以在[ARM参考手册]中查到,我们看到有四个寄存器:
[ARM参考手册]:http://infocenter.arm.com/help/topic/com.arm.doc.dui0553a/Babieigh.html
@ -27,14 +27,14 @@ struct SysTick {
}
```
限定符`#[repr(C)]`告诉Rust编译器像C编译器那样布局此结构体。这非常重要因为Rust允许对结构体字段进行重新排序而C不允许。您可以想象如果编译器以静默方式重新排列了这些字段我们调试起来会有多困难有了此限定符后我们就有四个32位字段它们与上表相对应。但是,当然,这个 `struct` 本身是没有用的-我们需要一个变量。
限定符`#[repr(C)]`告诉Rust编译器像C编译器那样布局此结构体。这非常重要因为Rust允许对结构体字段进行重新排序而C不允许。您可以想象如果编译器以静默方式重新排列了这些字段我们调试起来会有多困难有了此限定符后我们就有四个32位字段它们与上表相对应。当然这个 `struct` 本身无法直接使用--我们需要一个变量。
```rust , ignore
let systick = 0xE000_E010 as *mut SysTick;
let time = unsafe { (*systick).cvr };
```
## 易失性访问
## 易失性(volatile)访问
现在,上述方法存在以下问题:
@ -53,7 +53,7 @@ let time = unsafe { core::ptr::read_volatile(&mut systick.cvr) };
现在,我们已经解决了四个问题之一,但是现在我们有了更多的`unsafe`代码幸运的是第三方crate[ʻvolatile_register`]可以提供帮助。
[`volatile_register`]:https//crates.io/crates/volatile_register
[`volatile_register`]:https://crates.io/crates/volatile_register
```rust , ignore
use volatile_register::{RW, RO};
@ -76,11 +76,11 @@ fn get_time() -> u32 {
}
```
现在,通过`read`和`write`方法会自动执行易失性(volatile)访问。但是执行写入仍然是`unsafe`,公平地说,硬件是一堆易变的状态,编译器无法知道这些写入是否实际上是安全的,因此这是一个很好的默认设置。
现在,通过`read`和`write`方法会自动执行易失性(volatile)访问。但是执行写入仍然是`unsafe`,公平地说,硬件是一堆易变的状态,编译器(todo 难道不应该是volatile_register这个crate么?)无法知道这些写入是否实际上是安全的,因此这是一个很好的默认设置。
## Rust封装
我们需要将此`struct`封装到一个更高层API中以使我们的用户可以安全地调用它。作为驱动程序开发者我们手动验证不安全的代码是否正确然后为我们的用户提供一个安全的API以便他们不必担心它(只要他们相信我们是正确的!)。
我们需要将此`struct`封装到一个更高层API中以使我们的用户可以安全地调用它。作为驱动程序开发者我们手动验证不安全的代码是否正确然后为用户提供一个安全的API以便用户不必担心代码的安全性(只要用户相信我们是正确的!)。
一个示例可能是:
@ -136,4 +136,4 @@ fn thread2() {
}
```
set_reload函数的`mut self`参数确保没有其他对这个特定的`SystemTimer`实例的引用,但是它不会阻止用户创建第二个`SystemTimer`实例,明显它们指向完全相同的外设! 当然如果程序员很努力的避免创建多个实例,则以这种方式编写的代码也可以工作,但是一旦代码分散到不同模块,不同驱动程序,由多个程序员维护,则难免会出现各种错误.
set_reload函数的`mut self`参数只能确保这个实例不存在多个引用,但是它不会阻止用户创建第二个`SystemTimer`实例,明显它们指向完全相同的外设! 当然如果程序员很努力的避免创建多个实例,则以这种方式编写的代码也可以工作,但是一旦代码分散到不同模块,不同驱动程序,由多个程序员维护,则难免会出现各种错误(API的编写者要能确保自己暴露的API在安全代码中不会被错用)

View File

@ -8,12 +8,12 @@
1. 始终使用`volatile`方法读取或写入外围存储器,因为它随时可能发生变化
2. 在软件中,应该允许同时存在对这些外设的任意数量的只读访问
3. 如果某些软件需要对外设的读写访问权限,则它应该有该外设的唯一引用
3. 如果某些软件需要对外设的读写访问权限,则它应该有该外设的唯一引用
## 借用检查器
这些规则中的最后两个听起来和借用检查器的工作机制非常类似!
想像一下我们是否可以放弃这些外设的所有权,或者提供对它们的不变或可变的引用?
想像一下我们是否可以转移这些外设的所有权,或者提供对这些外设的不变或可变的引用?
好吧,我们可以. 但是对于借用检查器,我们需要每个外围设备都只有一个实例以便Rust可以正确处理。 幸运的是,在硬件中,任何给定的外设都只有一个实例,但是如何设计访问接口呢?
好吧,我们可以.我们需要每个外围设备都只有一个实例以便Rust借用检查器可以正确处理。 幸运的是,在硬件中,任何给定的外设都只有一个实例,但是如何设计访问接口呢?

View File

@ -13,7 +13,7 @@
* ROM芯片
* I/O控制器
RAM芯片ROM芯片和I/O控制器(此系统中的外围设备)通过“总线”连接到处理器。处理器通过地址总线选择与哪个设备进行通信,通过数据总线传输数据。在我们的嵌入式微控制器中,原理都是一样的-只是将所有内容包装在一块芯片内。
RAM芯片ROM芯片和I/O控制器(此系统中的外围设备)通过“总线”连接到处理器。处理器通过地址总线选择与哪个设备进行通信,通过数据总线传输数据。在我们的嵌入式微控制器中,原理都是一样的--只是将所有内容包装在一块芯片内。
但是与显卡不同的是,显卡一般提供了像VulkanMetal或OpenGL之类的软件API而嵌入式外设通过内存映射的方式,直接将硬件接口暴露给我们的微控制器。
@ -21,9 +21,9 @@ RAM芯片ROM芯片和I/O控制器(此系统中的外围设备)通过“总线
在微控制器上,将一些数据写入任意地址,例如`0x4000_0000`或`0x0000_0000`,也可能是完全有效的操作。
在台式机系统上,对内存的访问由内存管理单元(MMU)严格控制,MMU有两个主要职责强制执行对内存的访问权限(防止一个进程读取或修改另一进程的内存)并将物理内存的地址重新映射到软件中使用的虚拟内存地址。微控制器通常没有MMU而仅使用实际物理地址。
与嵌入式系统不同,在台式机系统上,对内存的访问由内存管理单元(MMU)严格控制,MMU有两个主要职责强制执行对内存的访问权限(防止一个进程读取或修改另一进程的内存)并将物理内存的地址重新映射到软件中使用的虚拟内存地址。微控制器通常没有MMU而仅使用实际物理地址。
尽管32位微控制器具有从0x0000_0000到0xFFFF_FFFF的物理和线性地址空间但它们通常仅使用该范围的几百K字节作为实际内存。这留下了大量的可用地址空间。在前面的章节中我们讨论了位于地址“0x2000_0000”上的RAM。如果我们的RAM大小为64 KiB(即最大地址为0xFFFF)则地址“0x2000_0000”到“0x2000_FFFF”将对应于我们的RAM。当我们写入位于地址“0x2000_1234”的变量时 某些逻辑检测地址的上半部分(在此示例中为0x2000)然后激活RAM由RAM来处理地址的下半部分(在这种情况下为0x1234)。在Cortex-M上我们将Flash ROM映射到地址“0x0000_0000”到地址“0x0007_FFFF”之间(如果我们有512 KiB Flash ROM)。微控制器设计人员没有忽略这两个区域之间的剩余地址空间,而是将某些内存位置映射给了外设。最终看起来像这样:
尽管32位微控制器具有从0x0000_0000到0xFFFF_FFFF的物理和线性地址空间但它们通常仅使用该范围的几百K字节作为实际内存。这留下了大量的可用地址空间。在前面的章节中我们讨论了位于地址“0x2000_0000”上的RAM。如果我们的RAM大小为64 KiB(即最大地址为0xFFFF)则地址“0x2000_0000”到“0x2000_FFFF”将对应于我们的RAM。当我们写入位于地址“0x2000_1234”的变量时 某些逻辑检测地址的上半部分(在此示例中为0x2000)然后激活RAM控制器由RAM控制器来处理地址的下半部分(在这种情况下为0x1234)。在Cortex-M上我们将Flash ROM映射到地址“0x0000_0000”到地址“0x0007_FFFF”之间(如果我们有512 KiB Flash ROM)。微控制器设计人员没有忽略这两个区域之间的剩余地址空间,而是将某些内存位置映射给了外设。最终看起来像这样:
![](../assets/nrf52-memory-map.png)
@ -31,14 +31,14 @@ RAM芯片ROM芯片和I/O控制器(此系统中的外围设备)通过“总线
## 内存映射的外围设备
乍看之下,与这些外设的交互非常简单-将正确的数据写入正确的地址。例如通过串行端口发送32位字可能与将32位字写入某个内存地址一样直接。然后串行端口外围设备将接管并自动发送数据。
乍看之下,与这些外设的交互非常简单--将正确的数据写入正确的地址。例如通过串行端口发送32位数据可能与将32位数据写入某个内存地址一样直接。写入后串口外设将接管并自动发送数据。
这些外设的配置工作类似。无需调用函数来进行配置一个外设,而是公开了一块用作硬件API的内存区域。比如将“0x8000_0000”写入SPI频率配置寄存器SPI端口将以每秒8兆位的速度发送数据。将“0x0200_0000”写入相同的地址SPI端口将以每秒125 Kilobits的速度发送数据。这些配置寄存器看起来像这样
这些外设的配置工作与内存操作类似。无需调用函数来进行配置一个外设,而是公开了一块用作配置硬件的内存区域。比如将“0x8000_0000”写入SPI频率配置寄存器SPI端口将以每秒8兆位的速度发送数据。将“0x0200_0000”写入相同的地址SPI端口将以每秒125 Kilobits的速度发送数据。这些配置寄存器看起来像这样
![](../assets/nrf52-spi-frequency-register.png)
[Nordic nRF52832手册(pdf)]
无论使用哪种语言无论该语言是AssemblyC还是Rust该接口都是与硬件进行交互的方式
无论使用哪种语言无论该语言是AssemblyC还是Rust都是这样与硬件进行交互。
[Nordic nRF52832手册(pdf)]: http://infocenter.nordicsemi.com/pdf/nRF52832_PS_v1.1.pdf

View File

@ -2,15 +2,14 @@
>在软件工程中,单例模式是一种软件设计模式,它限制类只有一个实例。
>
> *维基百科:[单例模式] *
> *维基百科:[单例模式]*
[单例模式]:https//en.wikipedia.org/wiki/Singleton_pattern
[单例模式]:https://en.wikipedia.org/wiki/Singleton_pattern
## 为什么我们不能直接使用全局变量?
我们可以像这样将所有内容设为公共静态
我们可以像这样将所有外设相关变量设为公共静态
```rust , ignore
static mut THE_SERIAL_PORT: SerialPort = SerialPort;
@ -23,11 +22,11 @@ fn main() {
```
这有一些问题。它是一个可变的全局变量在Rust中与它们进行交互总是不安全的。这些变量在整个程序中也是可见的,这意味着借用检查器无法帮助您跟踪这些变量的引用和所有权。
在Rust中读写全局可变变量都是不安全的。这些变量在整个程序中也是可见的,这意味着借用检查器无法帮助您跟踪这些变量的引用和所有权。
## 我们如何在Rust中做到这一点
## 我们如何在Rust中实现单例
我们不是简单地将外设设为全局变量,而是创建一个全局变量,姑且称为“PERIPHERALS”其中每个外围设备都包含一个“Option <T>
我们不是简单地将外设设为全局变量,而是创建一个全局变量,姑且称为`PERIPHERALS`,其中每个`PERIPHERALS`都包含一个`Option <T>`
```rust , ignore
struct Peripherals {
@ -44,7 +43,7 @@ static mut PERIPHERALS: Peripherals = Peripherals {
};
```
这种结构使我们可以获得外围设备的单个实例。如果我们尝试多次调用`take_serial()`代码将会崩溃
这种结构使我们可以获得外围设备的单个实例。如果我们尝试多次调用`take_serial()`程序将会发生恐慌(panic)
```rust , ignore
fn main() {
@ -54,13 +53,13 @@ fn main() {
}
```
尽管与此结构进行交互是`unsafe`,但一旦取得了它内部的“SerialPort”,我们将不再需要使用`unsafe`或`PERIPHERALS`结构体。
尽管与此结构进行交互是`unsafe`,但一旦取得了它内部的`SerialPort`,我们将不再需要使用`unsafe`或`PERIPHERALS`结构体。
这具有很小的运行时开销,因为我们必须将`SerialPort`结构包装在一个Option中,并且需要调用一次`take_serial()`,但是,这笔小小的前期成本使我们能够在其余所有过程中利用借用检查器检查我们的程序。
我们必须将`SerialPort`结构包装在一个Option中,这具有很小的运行时开销,因为需要调用一次`take_serial()`,但是这笔小小的一次性成本使我们能够在其余所有过程中利用借用检查器检查我们的程序。
## 现有库支持
尽管我们在上面创建了自己的`Peripherals`结构体,但实际上你的代码中无需这么操作。 `cortex_m`crate包含一个名为`singleton!()`的宏,它将为您执行此操作。
尽管我们在上面创建了自己的`Peripherals`结构体,但实际上你的代码中无需这么操作。 `cortex_m`crate包含一个名为[`singleton!()`]((https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html))的宏,它将为您执行此操作。
```rust , ignore
@ -74,9 +73,9 @@ fn main() {
}
```
[cortex_m docs](https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html)
[cortex_m docs] todo 这里需要提交pr
此外,如果您使用`cortex-m-rtfm` 定义和获取这些外围设备的整个过程已经帮您封装好了,您将获得一个`Peripherals`结构,该结构包含非`Option <T>`版本的您定义的所有项目
此外,`cortex-m-rtfm`crate 已经帮您将定义和获取这些外围设备封装好了,您将获得一个`Peripherals`结构体,该结构体没有`Option <T>`并且功能齐全
```rust , ignore
// cortex-m-rtfm v0.3.x
@ -115,9 +114,9 @@ impl SerialPort {
这里有两个重要因素:
* 因为我们使用的是单例,所以只有一种方法可以获得`SerialPort`实例
* 要调用`read_speed()`方法,我们必须对`SerialPort`实例拥有借用或者所有权
* 要调用`read_speed()`方法,我们必须对`SerialPort`实例拥有只读借用或者所有权
这两个因素放在一起,再加上只有满足借用检查器的情况下,才可以访问硬件,这意味着我们绝对不会对同一硬件有多个可变引用!
这两个因素放在一起,再加上Rust的借用规则这意味着我们绝对不会对同一外设有多个可变引用!
```rust , ignore
@ -155,4 +154,4 @@ fn read_button(gpio: &GpioPin) -> bool {
}
```
这使我们能够在编译时(而不是在运行时)限制代码是否应该更改硬件。需要注意的是,这通常仅适用于单个应用程序,但是对于裸机系统,我们的软件被编译到单个应用程序中,因此不是问题。(这里说的是如果存在多个进程,它们可以分别构建单例,但是实际上外设只有一个,还是不安全)
这使我们能够在编译时(而不是在运行时)限制代码是否应该更改硬件。需要注意的是,这通常仅适用于单个应用程序,但是对于裸机系统,我们的软件只能被编译到单个应用程序中,因此不是问题。(这里说的是如果存在多个进程,它们可以分别构建单例,但是实际上外设只有一个,还是不安全)

View File

@ -8,7 +8,7 @@
>
>通过提供对硬件的标准操作系统(OS)调用,从而允许程序员编写与设备无关的高性能应用程序。
>
> *维基百科:[硬件抽象层] *
> *维基百科:[硬件抽象层]*
[硬件抽象层]:https://en.wikipedia.org/wiki/Hardware_abstraction
@ -32,7 +32,7 @@
* 计时器/倒数计数器
* 模拟数字转换
使用**embedded-hal**的Trait和crate的主要原因是为了控制复杂性。如果某个应用程序自己必须独立实现外设的使用方法,独立编写应用程序以及潜在的硬件驱动程序,那么应该很容易看出其代码可重用性非常有限。如果**M**是外设HAL实现的数量而**N**是驱动程序的数量,那么如果我们要为每个应用重新发明轮子,那么最终将得到**M\*N**种实现. 而使用基于**Embedded-hal**提供的Trait的API来实现则只需**M+N**种实现。当然还有其他好处,例如由于定义明确且易于使用的API减少了反复试验。
使用**embedded-hal**的Trait和crate的主要原因是为了控制复杂性。如果某个应用程序自己必须独立实现外设的使用方法,独立编写应用程序以及潜在的硬件驱动程序,那么应该很容易看出其代码可重用性非常有限。如果**M**是外设HAL实现的数量而**N**是驱动程序的数量,那么如果我们要为每个应用重新发明轮子,那么最终将得到**M\*N**种实现. 而使用基于**Embedded-hal**提供的Trait的API来实现则只需**M+N**种实现。当然还有其他好处例如定义明确且易于使用的API减少了反复试验。
## embedded-hal的使用者
@ -47,18 +47,18 @@ HAL实现提供了硬件与HAL trait的用户之间的接口。典型的实现
这样的**HAL实现**可以有多种形式:
* 通过低级别的硬件访问,例如通过寄存器
* 通过低级别的硬件访问,例如寄存器
* 通过操作系统例如在Linux下使用`sysfs`
* 通过适配器,例如模拟单元测试的类型
* 通过硬件适配器的驱动程序例如I2C多路复用器或GPIO扩展器
### 驱动
驱动程序为内部或外部组件实现了一组自定义功能,这些组件连接到实现了嵌入式hal trait的外围设备。这种驱动程序的典型示例包括各种传感器(温度,磁力计,加速度计,光线),显示设备(LED阵列LCD显示屏)和驱动器(电机,发射器)。
驱动程序为内部或外部组件实现了一组自定义功能,这些组件连接到实现了`Embedded-hal` `trait`的外围设备。这种驱动程序的典型示例包括各种传感器(温度,磁力计,加速度计,光线),显示设备(LED阵列LCD显示屏)和执行器(电机,发射器)。
一个驱动程序必须用一个类型实例来初始化该类型实现了Embedded-hal的某个“trait”这是通过特征绑定来确保的并为其自身的类型实例提供一组自定义方法,以允许与被驱动设备进行交互。
一个驱动程序必须用一个实现了`Embedded-hal`的相应`trait`的实例来初始化,并提供一组自定义方法,以允许与被驱动设备进行交互。
A driver has to be initialized with an instance of type that implements a certain `trait` of the embedded-hal which is ensured via trait bound and provides its own type instance with a custom set of methods allowing to interact with the driven device.
### 应用

View File

@ -28,9 +28,9 @@ fn SysTick() {
}
```
如您所知,在函数中使用`static mut`变量使其成为[不可重入](https://en.wikipedia.org/wiki/Reentrancy_(computing))。 从多个异常/中断处理程序或`main`中直接或间接调用不可重入函数是不确定的行为。
如您所知,使用`static mut`变量使函数[不可重入](https://en.wikipedia.org/wiki/Reentrancy_(computing))。 从多个异常/中断处理程序或`main`中直接或间接调用不可重入函数是不确定(UB undefined behavior)的行为。
Safe Rust绝不能导致不确定的行为因此非可重入函数必须标记为 `unsafe`。但是我刚刚却说异常处理程序可以安全地使用`static mut`变量。这怎么可能?这是可能的,因为异常处理程序不能被函数调用,因此无法重入。
Safe Rust绝不能导致不确定的行为因此非可重入函数必须标记为 `unsafe`。但是我刚刚却说异常处理程序可以安全地使用`static mut`变量。这怎么可能?这是可能的,因为异常处理程序不能被其他函数调用,因此不可能发生重入。
## 一个完整的例子
@ -114,7 +114,7 @@ $ cargo run --release
123456789
```
如果在开发板上运行此命令则会在OpenOCD控制台上看到输出。但是当计数达到9时程序将**不**停止。
如果在开发板上运行此命令则会在OpenOCD控制台上看到输出。只不过当计数达到9时程序将**不**停止。
## 默认异常处理程序
@ -137,7 +137,7 @@ fn DefaultHandler(irqn: i16) {
}
```
irqn是正在处理的异常编号。负值表示Cortex-M异常零或正值表示设备特定的异常AKA中断。
irqn是正在处理的异常编号。负值表示Cortex-M异常零或正值表示设备特定的异常即中断。
## 硬故障处理程序
@ -147,7 +147,7 @@ irqn是正在处理的异常编号。负值表示Cortex-M异常零或正值
这是一个执行非法操作的示例:读取不存在的内存位置。
> **注意**该程序在QEMU上不起作用,即不会崩溃,因为`qemu-system-arm -machine lm3s6965evb`不会检查内存读取,并且在读取到无效内存时会很高兴地返回`0`。
> **注意**该程序在QEMU上不会发生崩溃,因为`qemu-system-arm -machine lm3s6965evb`不会检查内存读取,并且在读取到无效内存时会很高兴地返回`0`。
```rust , ignore
@ -214,4 +214,4 @@ ResetTrampoline:
800094c: b #-0x4 <ResetTrampoline+0xa>
```
您可以在反汇编中查找程序计数器`0x0800094a` 的值。您将看到加载操作(`ldr r0[r0]`)引起了异常。 `ExceptionFrame`的`r0`字段将告诉您寄存器r0的值为当时的0x3fff_fffe。
您可以在反汇编中查找程序计数器`0x0800094a` 的值。您将看到加载操作(`ldr r0[r0]`)引起了异常。 `ExceptionFrame`的`r0`字段将告诉您当时寄存器r0的值为0x3fff_fffe。

View File

@ -10,9 +10,9 @@
- ARM内核是否包括FPU Cortex-M4**F**和Cortex-M7**F**内核都有FPU。
-目标设备有多少闪存和RAM例如256 KiB的闪存和32 KiB的RAM。
- 目标设备有多少闪存和RAM例如256 KiB的闪存和32 KiB的RAM。
-闪存和RAM映射的地址空间在哪里例如RAM通常位于地址“0x2000_0000”。
- 闪存和RAM映射的地址空间在哪里例如RAM通常位于地址“0x2000_0000”。
通常您可以在数据手册或设备的参考手册中找到这些信息。
@ -54,7 +54,7 @@ target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU)
```
我们将使用`thumbv7em-none-eabihf`因为它适合Cortex-M4F内核
这次用得是Cortex-M4F内核,所以target使用`thumbv7em-none-eabihf`
第二步是将存储区域信息输入到“memory.x”文件中。
@ -69,7 +69,7 @@ MEMORY
}
```
确保`debug::exit()`调用已被注释掉或删除,因为他仅用于在QEMU中运行
确保`debug::exit()`调用已被注释掉或删除,因为他仅用于QEMU环境
```rust , ignore
#[entry]
@ -84,7 +84,7 @@ fn main() -> ! {
}
```
现在,您可以像以前一样使用`cargo build`交叉编译程序,并使用`cargo-binutils`检查二进制文件。 `cortex-m-rt` crate可处理使您的芯片运行所需的所有魔术,几乎所有Cortex-M CPU都以相同的方式引导。
现在,您可以像以前一样使用`cargo build`交叉编译程序,并使用`cargo-binutils`检查二进制文件。 `cortex-m-rt` crate可处理您的芯片运行所需的所有魔术,几乎所有Cortex-M CPU都以相同的方式引导。
``` console
$ cargo build --example hello
@ -97,11 +97,11 @@ $ cargo build --example hello
和以前一样我们将进行远程调试客户端是GDB进程,服务器将是OpenOCD。
$ cat openocd.cfg
按照[验证]部分的操作将开发板连接到笔记本电脑或者PC并检查是否填充了ST-LINK接头连接器(todo ... check that the ST-LINK header is populated)
按照[验证]部分的操作将开发板连接到笔记本电脑或者PC并检查是否插上了ST-LINK跳线帽
[验证]: ../intro/install/verify.md
在终端上运行“openocd”以连接到开发板上的ST-LINK。从模板的根目录运行此命令;`openocd`会根据`openocd.cfg`文件,找到要使用的接口文件和目标文件。
在终端上,从模板的根目录运行“openocd”以连接到开发板上的ST-LINK。 `openocd`会根据`openocd.cfg`文件,找到要使用的接口文件和目标文件。
``` console
$ cat openocd.cfg
@ -150,7 +150,7 @@ Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints
$ <gdb> -q target/thumbv7em-none-eabihf/debug/examples/hello
```
接下来将GDB连接到OpenOCDOpenOCD正在监听端口3333,等待新的TCP连接
接下来将GDB连接到OpenOCDOpenOCD正在监听端口3333。
``` console
(gdb) target remote :3333
@ -169,7 +169,7 @@ Start address 0x800144e, load size 10380
Transfer rate: 17 KB/sec, 3460 bytes/write.
```
现在程序已加载。该程序使用半主机因此在进行任何半主机调用之前我们必须告诉OpenOCD启用半主机。您可以使用“ monitor”将命令发送到OpenOCD。
现在程序已加载。该程序需要半主机支持因此在进行任何半主机调用之前我们必须告诉OpenOCD启用半主机。您可以使用“monitor”将命令发送到OpenOCD。
``` console
(gdb) monitor arm semihosting enable
@ -222,7 +222,7 @@ Info : halted: PC: 0x08000d70
Info : halted: PC: 0x08000d72
```
发出另一个`next`将使处理器执行`debug::exit`。这充当断点并中止该过程
发出另一个`next`将使处理器执行`debug::exit`。这会像断点一样挂起程序的执行
``` console
(gdb) next
@ -244,7 +244,7 @@ target halted due to breakpoint, current mode: Thread
xPSR: 0x21000000 pc: 0x08000d76 msp: 0x20009fc0, semihosting
```
但是,在微控制器上运行的程尚未终止,您可以使用`continue`或类似命令将其恢复。
但是,在微控制器上运行的程尚未终止,您可以使用`continue`或类似命令将其恢复。
现在,您可以使用“ quit”命令退出GDB。
@ -278,7 +278,7 @@ load
stepi
```
现在运行 `<gdb> -x openocd.gdb $program`将立即将GDB连接到OpenOCD启用半主机加载程序并启动该过程
现在运行 `<gdb> -x openocd.gdb $program`将立即将GDB连接到OpenOCD启用半主机加载程序并开始执行
您也可以将`<gdb> -x openocd.gdb`转换为自定义运行器,这样`cargo run`会自动构建程序并开始GDB会话。该运行器已包含在`.cargo/config`中,只不过现在是被注释掉的状态。

View File

@ -1,5 +1,5 @@
# 入门
在本节中,我们将引导您完成编写,构建闪存和调试嵌入式程序的过程。您将能够在没有任何特殊硬件的情况下尝试大多数示例,因为我们将使用流行的开源硬件仿真器QEMU向您展示基础知识。当然唯一需要硬件的部分是[Hardware](./hardware.md)部分在这里我们使用OpenOCD在[STM32F3DISCOVERY]上编程。
在本节中,我们将引导您完成编写,构建,上传和调试嵌入式程序的过程。其中大多数示例不需要任何特殊硬件,我们将使用流行的开源硬件仿真器QEMU向您展示基础知识。当然唯一需要硬件的部分是[Hardware](./hardware.md)部分在这里我们使用OpenOCD在[STM32F3DISCOVERY]上编程。
[STM32F3DISCOVERY]:http://www.st.com/en/evaluation-tools/stm32f3discovery.html

View File

@ -1,15 +1,15 @@
# 中断
中断在很多方面与异常不同,但是它们的操作和使用在很大程度上相似,并且它们也由同一中断控制器处理。尽管异常是由Cortex-M架构定义的但是中断在命名和功能上始终是特定于供应商(甚至是芯片)的特定实现
中断在很多方面与异常不同,但是它们的操作和使用在很大程度上相似,并且它们也由同一中断控制器处理。异常是由Cortex-M架构统一定义的中断则在命名和功能上随供应商(甚至是芯片)不同而不同
中断确实具有很大的灵活性,在尝试以高级方式使用它们时需要考虑这些灵活性。我们不会在本书中介绍这些用法,但是请牢记以下几点:
* 中断具有可编程的优先级,该优先级确定其处理程序的执行顺序
* 中断可以嵌套和抢占,即中断处理程序的执行可能会被另一个更高优先级的中断中断
* 中断可以嵌套和抢占,即中断处理程序的执行可能会被另一个更高优先级的中断抢占
* 通常需要清除导致中断触发的事件,以防止无限次重新进入中断处理程序
中断的常规初始化步骤始终相同:
* 设置外设以在需要的情况下生成中断请求
* 配置外设,在需要的情况下生成中断请求
* 在中断控制器中设置所需的中断处理程序优先级
* 在中断控制器中启用中断处理程序

View File

@ -1,6 +1,6 @@
# 恐慌(Panicking)
恐慌是Rust语言的核心部分。诸如索引之类的内置操作会在运行时检查内存安全性。当尝试超出索引范围时将导致恐慌。
恐慌是Rust语言的核心部分。诸如索引之类的内置操作会在运行时检查内存安全性。当尝试超出索引范围时将导致恐慌。
在标准库中,恐慌具有确定的行为:恐慌会进行线程栈展开,除非用户选择在恐慌中中止程序。
@ -15,7 +15,7 @@
- [`panic-itm`] 恐慌消息使用ITM(ARM Cortex-M特定的外围设备)记录。
- [`panic-semihosting`] 恐慌消息使用半主机技术记录到主机。
[`panic-abort`]:https//crates.io/crates/panic-abort
[`panic-abort`]:https://crates.io/crates/panic-abort
[`panic-halt`]:https://crates.io/crates/panic-halt
[`panic-itm`]:https://crates.io/crates/panic-itm
[`panic-semihosting`]:https://crates.io/crates/panic-semihosting
@ -42,11 +42,11 @@ extern crate panic_abort;
// ..
```
在此示例中,使用开发人员配置文件(`cargo build`)时,板条箱链接到`panic-halt`板条箱,而当使用发布配置文件构建时,则链接到`panic-abort`板条箱(`cargo build --release `)。
在此示例中,使用开发人员配置文件(`cargo build`)构建时crate链接到`panic-halt`,而当使用发布配置文件构建时,则链接到`panic-abort`crate(`cargo build --release `)。
## 一个例子
这是一个尝试索引超出的示例。该操作导致恐慌。
这是一个尝试索引越界的示例。该操作导致恐慌。
```rust , ignore
#![no_main]
@ -66,7 +66,7 @@ fn main() -> ! {
}
```
本示例选择了`panic-semihosting`恐慌处理行为,该行为将恐慌消息打印到主机控制台。
本示例选择了`panic-semihosting`恐慌处理方式,该方式将恐慌消息打印到主机控制台。
``` console
$ cargo run

View File

@ -5,7 +5,7 @@
[LM3S6965]:http://www.ti.com/product/LM3S6965
**重要**
在本教程中我们将名称“app”用作项目名称。每当您看到“app”一词时都应将其替换为自己的项目名称。或者您也可以将项目命名为“app”以避免替换。
在本教程中我们将名称“app”用作项目名称。每当您看到“app”一词时都应将其替换为自己的项目名称。或者您也可以直接将项目命名为“app”以避免替换。
## 创建一个非标准的Rust程序
@ -37,7 +37,7 @@ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
cd app
```
### 使用`git`
### 使用git
克隆存储库
@ -46,7 +46,7 @@ git clone https://github.com/rust-embedded/cortex-m-quickstart app
cd app
```
And then fill in the placeholders in the `Cargo.toml` file
然后将 `Cargo.toml` 中的`{{authors}}`,`{{project-name}}`替换为你自己的.
```toml
[package]
@ -74,9 +74,9 @@ mv cortex-m-quickstart-master app
cd app
```
或者,您可以浏览到[`cortex-m-quickstart`],单击绿色的“克隆或下载”按钮,然后单击“下载ZIP”。
或者,您可以使用浏览器访问[`cortex-m-quickstart`]单击绿色的“clone or download”按钮然后单击“Download ZIP”。
然后按照“使用git”一节中的第二部分中的操作在Cargo.toml文件中填写占位符
然后按照[`使用git`](#使用git)一节中的第二部分中的操作在Cargo.toml文件中填写自定义内容
## 程序概述
@ -100,22 +100,22 @@ fn main() -> ! {
该程序与标准Rust程序有点不同因此让我们仔细看一下。
`#![no_std]`表示此程序*不会*链接到标准库,而是链接到其子集`core` crate
`#![no_std]`表示此程序*不会*链接到标准库,而是链接到其子集--核心库
`#![no_main]`表示该程序将不使用大多数Rust程序使用的标准`main`接口。使用no_main的主要原因是在no_std上下文中使用main函数需要Rust的nightly版本。
`extern crate panic_halt;`。这个crate提供了一个 `panic_handler`,它定义了程序的恐慌行为。我们将在本书的[Panicking](panicking.md)一章中对此进行详细介绍。
[`#[entry]`][entry]是[`cortex-m-rt`]crate提供的属性用于标记程序的入口。由于我们没有使用标准的“ main”接口因此需要另一种方式来指示程序的入口,即 `#[entry]`
[`#[entry]`][entry]是[`cortex-m-rt`]crate提供的属性用于标记程序的入口。由于我们没有使用标准的“ main”接口因此需要另一种方式来指示程序的入口`#[entry]`
[entry]:https://docs.rs/cortex-m-rt-macros/latest/cortex_m_rt_macros/attr.entry.html
[`cortex-m-rt`]:https://crates.io/crates/cortex-m-rt
注意main函数的签名是`fn main() -> !` ,因为我们的程序是目标硬件上唯一的程序,所以我们不希望它结束​​!我们使用[发散函数](https://doc.rust-lang.org/rust-by-example/fn/diverging.html)(函数签名中的`->`表示没有返回值)来在编译时确保main不会结束。
注意main函数的签名是`fn main() -> !` ,因为我们的程序是目标硬件上唯一的程序,所以我们不希望它结束​​!我们使用[发散函数](https://doc.rust-lang.org/rust-by-example/fn/diverging.html)(函数签名中的`->`表示没有返回值)来在编译时确保main不会结束。
## 交叉编译
下一步是交叉编译针对Cortex-M3架构的程序。如果您知道编译目标($TRIPLE)应该是什么,那就直接运行`cargo build --target $ TRIPLE`。不知道也没关系,模板项目中的.cargo/config里有答案
下一步是针对Cortex-M3架构进行交叉编译。如果您知道编译目标($TRIPLE)应该是什么,那就直接运行`cargo build --target $TRIPLE`。不知道也没关系,模板项目中的.cargo/config里有答案
```console
tail -n6 .cargo/config
@ -148,7 +148,7 @@ cargo readobj --bin app -- -file-headers
```
注意:
*`--bin app`是用于检查``target/$TRIPLE/debug/app`这个二进制文件
*`--bin app`是用于检查`target/$TRIPLE/debug/app`这个二进制文件
*`--bin app`还会在必要时(重新)编译二进制文件
@ -177,9 +177,8 @@ ELF Header:
`cargo-size`可以打印二进制文件的链接器部分的大小。
> **注意**此输出假定已经合并了rust-embedd/cortex-m-rt111
!todo 这句话啥意思啊?
> **注意**此输出假定已经合并了[rust-embedded/cortex-m-rt#111](https://github.com/rust-embedded/cortex-m-rt/pull/111)这个PR
```console
cargo size --bin app --release -- -A
@ -212,10 +211,10 @@ Total 14570
>关于ELF链接器部分的复习
>
>- `.text`包含程序说明
>- `.text`包含程序代码
>- `.rodata`包含常量值,例如字符串
>- `.data`包含静态分配的变量,其初始值为非零
>- `.bss`包含静态分配的变量,其初始值为零
>- `.bss`包含静态分配的变量,其初始值为零
>- `.vector_table`是非标准部分,用于存储中断向量表
>- `.ARM.attributes`和`.debug_ *`部分包含元数据这部分数据不会写入目标开发板的flash上。
@ -227,7 +226,7 @@ Total 14570
cargo objdump --bin app --release -- -disassemble -no-show-raw-insn -print-imm-hex
```
> **注意**此输出在您的系统上可能会有所不同。 不同版本的rustcLLVM和库都会生成不同的程序集。另外,由于空间问题,我们也对内容做了删减。
> **注意**此输出在您的系统上可能会有所不同。 不同版本的rustcLLVM和库都会生成不同的指令。另外,由于空间问题,我们也对内容做了删减。
```text
app: file format ELF32-arm-little
@ -297,7 +296,7 @@ fn main() -> ! {
}
```
该程序使用一种称为半主机(semihosting)的方式将文本打印到*host*控制台。在使用实际硬件时这需要调试会话支持但是在使用QEMU时直接使用就行了。
该程序使用一种称为半主机(semihosting)的方式将文本打印到*主机*控制台。在使用实际硬件时这需要调试会话支持但是在使用QEMU时直接使用就行了。
让我们从编译示例开始:
@ -322,7 +321,7 @@ qemu-system-arm \
Hello, world!
```
打印文本后该命令应成功退出退出代码为0)。在*nix上您可以使用以下命令进行检查
打印文本后该命令应成功退出退出代码为0。在*nix上您可以使用以下命令进行检查
```console
echo $?
@ -334,9 +333,9 @@ echo $?
让我们分解一下QEMU命令
-`qemu-system-arm` 这是QEMU仿真器。QEMU支持很多不同架构的处理。从名字可以看出,这是ARM处理器的完整仿真。
-`qemu-system-arm` 这是QEMU仿真器。QEMU支持很多不同架构。从名字可以看出,这是ARM处理器的完整仿真。
-`-cpu cortex-m3`。这告诉QEMU模拟Cortex-M3 CPU。指定CPU型号可以让我们捕获一些错误编译错误例如运行针对具有硬件FPU的Cortex-M4F编译的程序QEMU将在其运行期间产生错误。
-`-cpu cortex-m3`。这告诉QEMU模拟Cortex-M3 CPU。指定CPU型号可以让我们捕获一些编译参数不当错误例如运行针对具有硬件FPU的Cortex-M4F编译的程序QEMU将在其运行期间产生错误。
-`-machine lm3s6965evb`。这告诉QEMU模拟LM3S6965EVB这是一个包含LM3S6965微控制器的开发板。
@ -379,7 +378,7 @@ Hello, world!
远程调试涉及客户端和服务器。针对QEMU客户端将是GDB(或LLDB)进程而服务器将是运行嵌入式程序的QEMU进程。
在本节中,我们将使用已经编译的“ hello”示例。
在本节中我们将使用已经编译的“hello”示例。
调试的第一步是在调试模式下启动QEMU
@ -421,7 +420,7 @@ Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473
473 pub unsafe extern "C" fn Reset() -> ! {
```
您会看到该进程已停止,并且程序计数器指向了一个名为“ Reset”的函数。那就是重启入口即Cortex-M启动时执行程序的入口。
您会看到该进程已停止并且程序计数器指向了一个名为“Reset”的函数。那就是重启入口即Cortex-M启动时执行程序的入口。
该函数最终将调用我们的main函数。让我们使用断点和`continue`命令一路跳过:

View File

@ -174,8 +174,9 @@ ELF Header:
```
`cargo-size` can print the size of the linker sections of the binary.
todo ! 可以提一个pr
> **NOTE** this output assumes that rust-embedded/cortex-m-rt#111 has been
> **NOTE** this output assumes that [rust-embedded/cortex-m-rt#111](https://github.com/rust-embedded/cortex-m-rt/pull/111) has been
> merged
```console

View File

@ -1,18 +1,18 @@
# 内存映射寄存器
就目前我们所知,嵌入式系统只能执行常规的Rust代码,操作内存中的数据(todo: 什么样的系统不是这样?)。如果我们想获取或者修改系统的任何信息(例如闪烁LED检测到按钮的按下或与某种总线上的外设进行通信),我们将不得不深入了解外设及其“内存映射寄存器”。
到目前为止目前我们学了嵌入式系统如何执行常规的Rust代码,如何操作内存中的数据。如果我们想获取或者修改系统的任何信息(例如闪烁LED检测到按钮的按下或与某种总线上的外设进行通信),我们将不得不深入了解外设及其“内存映射寄存器”。
现在已经存在不少问外设的crate,他们可以大致进行如下分类:
现在已经存在不少访问外设的crate,他们可以大致进行如下分类:
* 处理器架构相关Crate (Micro-architecture Crate) - 这种crate比较通用, 可处理CPU相关的通用例程以及一些通用外设。例如[cortex-m]crate为您提供了启用和禁用中断的功能这些功能对于所有基于Cortex-M的CPU都是相同的。它还使您可以访问所有基于Cortex-M的微控制器附带的时钟外设(SysTick)。
* 外设相关Crate(PAC)-这种crate实际上是对特定CPU型号的内存映射寄存器的一个简单封装。例如[tm4c123x]这个crate是对德州仪器(TI)Tiva-C TM4C123系列CPU的封装[stm32f30x]这个crate是对ST-Micro STM32F30x系列CPU的封装。借助他们您可以按照CPU参考手册中给出的每个外设的操作说明直接与寄存器进行交互。
* 外设相关Crate(PAC) 这种crate实际上是对特定CPU型号的内存映射寄存器的一个简单封装。例如[tm4c123x]这个crate是对德州仪器(TI)Tiva-C TM4C123系列CPU的封装[stm32f30x]这个crate是对ST-Micro STM32F30x系列CPU的封装。借助这些crate您可以按照CPU参考手册中给出的每个外设的操作说明直接与寄存器进行交互。
* HAL crate - 这些crate通过实现[embedded-hal]中定义的一些常见Trait来提供更友好的处理器相关API。例如此crate可能提供一个`Serial`结构体该结构体提供一个构造函数来配置一组GPIO引脚和波特率并提供某种`write_byte`函数来发送数据。有关[embedded-hal]的更多信息,请参见[可移植性]一章。
*开发板相关crate - 通过预先配置各种外设和GPIO引脚以适合特定的开发板例如针对TM32F3DISCOVERY开发板的[F3]crate这些crate相比HAL类crate,更易用。
* 开发板相关crate - 通过预先配置各种外设和GPIO引脚以适合特定的开发板例如针对TM32F3DISCOVERY开发板的[F3]crate这些crate相比HAL类crate,更易用。
[cortex-m]:https//crates.io/crates/cortex-m
[cortex-m]:https://crates.io/crates/cortex-m
[tm4c123x]:https://crates.io/crates/tm4c123x
[stm32f30x]:https://crates.io/crates/stm32f30x
[embedded-hal]:https://crates.io/crates/embedded-hal
@ -22,7 +22,7 @@
## 从底层开始
让我们看一下所有基于Cortex-M的微控制器共有的SysTick外设。我们可以在[cortex-m]crate中找到一个相当低级的API我们可以像这样使用它
让我们看一下SysTick外设, 所有Cortex-M的微控制器都有这个外设。我们可以在[cortex-m] crate中找到一个相当低级的API我们可以像这样使用它
```rust , ignore
use cortex_m::peripheral::{syst, Peripherals};
@ -44,13 +44,13 @@ fn main() -> ! {
}
```
SYST结构上的函数接口与ARM技术参考手册为此外围设备定义的功能非常接近。这个API中没有“延迟X毫秒”这样的函数接口,因此我们必须使用`while`循环来实现它。注意,在调用 `Peripherals::take()`之前,我们无法访问`SYST`结构体-这可确保整个程序中只有一个`SYST`实例。有关更多信息,请参见[外围设备]部分。
SYST结构上的函数接口与ARM技术参考手册为此外围设备定义的功能非常接近。这个API中没有“延迟多少毫秒”这样的函数接口,因此我们必须使用`while`循环来实现它。注意,在调用 `Peripherals::take()`之前,我们无法访问`SYST`结构体--这可确保整个程序中只有一个`SYST`实例。有关更多信息,请参见[外围设备]部分。
[外围设备]:../peripherals/index.md
## 使用外设crate(PAC)
如果我们将自己限制在每个Cortex-M附带的基本外围设备上那么我们在嵌入式软件开发方面就不会走得太远。总有一天我们需要编写一些特定于我们正在使用的特定微控制器的代码。在此示例中,假设我们使用德州仪器(TI)TM4C123这款款处理器(具有256 KiB Flash,80MHz的Cortex-M4)。我们需要[tm4c123x]这个crate以使用此芯片。
如果我们只能操控Cortex-M附带的基本外围设备那么我们在嵌入式软件开发方面就只能是小打小闹。总有一天我们需要编写一些针对我们正在使用的特定微控制器的代码。在此示例中,假设我们使用德州仪器(TI)TM4C123这款款处理器(具有256 KiB Flash,80MHz的Cortex-M4)。需要引入[tm4c123x]crate以使用此芯片。
```rust , ignore
#![no_std]
@ -79,7 +79,7 @@ pub fn init() -> (Delay, Leds) {
```
除了调用`tm4c123x::Peripherals::take()`之外我们访问PWM0外设的方式与之前访问SYST外设的方式完全相同。由于此crate是使用[svd2rust]自动生成的,因此我们寄存器的访问函数采用闭包而不是数字参数。尽管这看起来有很多代码但是Rust编译器可以为我们执行一堆检查以及优化,然后生成与手写汇编代码非常接近的机器代码!自动生成的代码如果无法确定特定访问器函数的参数的所有可能值均有效(例如SVD将寄存器定义为32位整数但实际上只有其中的某些值才有特殊含义,才有意义),则该函数被标记为“不安全”。我们在上面的示例中使用`bits()` 函数设置 `load` 和`compa` 子字段时可以看到这一点。
除了调用`tm4c123x::Peripherals::take()`之外我们访问PWM0外设的方式与之前访问SYST外设的方式完全相同。由于此crate是使用[svd2rust]自动生成的,因此我们寄存器的访问函数采用闭包而不是数字参数。尽管这看起来有点绕但是Rust编译器可以为我们执行很多检查以及优化,然后生成与手写汇编代码非常接近的机器代码!自动生成的代码如果无法确定特定函数的参数的所有可能值均有效(例如SVD将寄存器定义为32位整数但实际上只有其中的某些值才有特殊含义,才有意义),则该函数被标记为“不安全”。我们在上面的示例中使用`bits()` 函数设置 `load` 和`compa` 子字段时可以看到这一点。
### 读访问
@ -94,7 +94,7 @@ if pwm.ctl.read().globalsync0().is_set() {
### 写访问
`write()`函数采用一个带有单个参数的闭包。通常我们将其称为 `w`。根据制造商关于此芯片的SVD文件此参数可对该寄存器内的各个子字段进行读写访问。同样`w`上面定义 所有函数功能,您可以在[tm4c123x文档] [tm4c123x文档W]中找到针对此处理器,此外设的具体寄存器的定义。请注意,我们未设置的所有子字段都将被设置默认值-寄存器中的任何现有内容都将丢失。
`write()`函数的参数是一个带有单个参数的闭包。通常我们将其称为 `w`。根据制造商关于此芯片的SVD文件此参数可对该寄存器内的各个子字段进行读写访问。同样`w`上面定义所有函数功能,您可以在[tm4c123x文档] [tm4c123x文档W]中找到针对此处理器,此外设的具体寄存器的定义。请注意,我们未设置的所有子字段都将被设置默认值--寄存器中的任何现有内容都将丢失。
```rust , ignore
pwm.ctl.write(|w| w.globalsync0().clear_bit());
@ -102,13 +102,13 @@ pwm.ctl.write(|w| w.globalsync0().clear_bit());
### 修改
如果我们只想更改该寄存器中的一个特定子字段,而使其他子字段保持不变,则可以使用`modify`函数。此函数采用带有两个参数的闭包-一个用于读取,一个用于写入。通常,我们分别将它们称为`r`和`w`。 r参数可用于读取寄存器的当前内容w参数可用于修改寄存器的内容。
如果我们只想更改该寄存器中的一个特定子字段,而使其他子字段保持不变,则可以使用`modify`函数。此函数采用带有两个参数的闭包--一个用于读取,一个用于写入。通常,我们分别将它们称为`r`和`w`。 r参数可用于读取寄存器的当前内容w参数可用于修改寄存器的内容。
```rust , ignore
pwm.ctl.modify(|r, w| w.globalsync0().clear_bit());
```
`modify`函数在这里显示了闭包的强大。在C语言中我们必须读入到一些临时值修改特定位上的值然后将其写回。这意味着不小的出错几率
`modify`函数在这里显示了闭包的强大。在C语言中我们必须读入到一些临时值修改特定位上的值然后将其写回,这比较容易不小心出错
```C
uint32_t temp = pwm0.ctl.read();
@ -125,7 +125,7 @@ pwm0.enable.write(temp); // Uh oh! Wrong variable!
## 使用HAL crate
具体芯片的HAL crate一般是通过为PAC crate导出的结构体实现自定义Trait来工作。通常这个自定义crate为单体外设定义一个名为 `constrain()` 的函数为具有多个引脚的GPIO端口之类的外设定义 `split()` 函数。该函数将消耗底层的原始外围设备结构并返回具有更高级别API的新对象。这个API可能还会做一些事情例如让串口`new`函数需要`Clock`结构体的借用这个Clock结构体只能通过调用特定函数来生成,而这个函数会配置PLL并设置时钟频率。这样在没有先配置时钟频率的情况下就不可能创建串口对象, 否则串口对象有可能将波特率误转换为错误的时钟滴答。一些crate甚至为每个GPIO引脚可以处于的状态定义了特殊的Trait要求用户在将引脚传递到外设之前将其置于正确的状态(例如,通过选择适当的可选功能模式)。更重要的是,这些都是零成本抽象!
要想为一个具体的芯片实现HAL,一般是通过为这个芯片的`PAC`中的结构体实现自定义的Trait.通常这个自定义crate为单体外设定义一个名为 `constrain()` 的函数为具有多个引脚的GPIO端口之类的外设定义 `split()` 函数。该函数将消耗底层的原始外围设备结构并返回具有更高级别API的新对象。这个API可能还会做一些事情例如让串口`new`函数需要`Clock`结构体的借用这个Clock结构体只能通过调用特定函数来生成,而这个函数会配置PLL并设置时钟频率。这样在没有先配置时钟频率的情况下就不可能创建串口对象, 否则串口对象有可能将波特率误转换为错误的时钟滴答。一些crate甚至为每个GPIO引脚可以处于的状态定义了特殊的Trait要求用户在将引脚传递到外设之前将其置于正确的状态(例如,通过选择适当的可选功能模式)。更重要的是,这些都是零成本抽象!
让我们来看一个例子:

View File

@ -1,8 +1,8 @@
# 半主机
半主机是这样一种机制它允许嵌入式设备在主机上执行I/O操作主要用于将消息记录到主机控制台。半主机除了需要调试会话之外几乎不需要其他任何操作(不需要额外的接线!),因此使用起来超级方便。缺点是它非常慢:根据您使用的硬件调试器不同(例如ST-Link),每个写入操作可能要花费几毫秒。
半主机是这样一种机制它允许嵌入式设备在主机上执行I/O操作主要用于将消息记录输出到主机控制台。半主机除了需要调试会话之外,几乎不需要其他任何操作(不需要额外的接线!),因此使用起来超级方便。缺点是它非常慢:根据您使用的硬件调试器不同(例如ST-Link),每个写入操作可能要花费几毫秒。
[`cortex-m-semihosting`]crate提供了一个API可以在Cortex-M设备上进行半主机操作。下面的程序是“ Helloworld”的半主机版本
[`cortex-m-semihosting`] crate提供了一个API可以在Cortex-M设备上进行半主机操作。下面的程序是“ Helloworld”的半主机版本
[`cortex-m-semihosting`]:https://crates.io/crates/cortex-m-semihosting

View File

@ -1,12 +1,12 @@
# 静态保证
Rust的类型系统在编译时就防止发生竞争访问(请参阅[`Send`]和[`Sync`]特性)。类型系统还可以用于在编译时检查其他属性在某些情况下,减少了对运行时检查的需求。
Rust的类型系统在编译时就防止发生竞争访问(请参阅[`Send`]和[`Sync`]特性)。类型系统还可以用于在编译时检查其他属性,在某些情况下,减少了对运行时检查的需求。
[`Send`]:https://doc.rust-lang.org/core/marker/trait.Send.html
[`Sync`]:https://doc.rust-lang.org/core/marker/trait.Sync.html
这些**静态检查**在嵌入式程序中还可发挥特殊作用例如可以用来强制完成I/O接口的配置. 可以设计一种API只能先配置好串口所需引脚,然后才能初始化串口对象。
Rust还可以静态检查对外设的配置操作是否允许,例如正确配置后才能将引脚设置为低电平。例如,当引脚是浮动输入模式时,配置引脚的输出状态会产生编译错误。
Rust还可以静态检查是否允许对外设的配置操作,例如,当引脚是浮动输入模式时,配置引脚的输出状态会产生编译错误。
而且,如上一章所述,所有权的概念可以应用于外围设备,以确保只有程序的某些部分才能修改外围设备。与将外围设备视为全局可变状态的方法相比,这种“访问控制”更加合理。

View File

@ -1,8 +1,8 @@
# 外作为状态机
# 外设作为状态机
微控制器的外围设备可以认为是一组状态机。例如,简化的[GPIO引脚]的配置可以表示为以下状态树:
[GPIO引脚]:https://zh.wikipedia.org/wiki/通用输入/输出
[GPIO引脚]:https://en.wikipedia.org/wiki/General-purpose_input/output
* 禁用
* 已启用
@ -14,14 +14,14 @@
* 输入:拉低
* 输入:拉高
如果外围设备以“禁用”模式启动,想要转移至“输入:高阻”模式,我们必须执行以下步骤:
如果外围设备以“禁用”模式启动,想要转移至“输入:高阻”模式,我们必须执行以下步骤:
1. 禁用模式
2. 启用
3. 配置为输入
4. 输入:高电阻
如果要从“输入:高阻”转移至“输入:拉低”,则必须执行以下步骤:
如果要从“输入:高阻”转移至“输入:拉低”,则必须执行以下步骤:
1. 输入:高阻
2. 输入:拉低
@ -35,21 +35,21 @@
## 硬件表示
通常,上面列出的状态是通过将值写入映射到GPIO外设的给定寄存器来设置的。让我们虚拟一个的PIO配置寄存器来说明这一点:
通常,上面列出的状态是通过将修改映射到GPIO外设的寄存器来设置的。让我们虚拟一个的PIO配置寄存器来说明这一点:
|名字 |位号 | 值 |含义 |注意事项 |
| ---: |------------:| ----:|------: | ----: |
|启用| 0 | 0 | 禁用| 禁用GPIO |
| | | 1 |启用 |启用GPIO |
|方向| 1 | 0 |输入|将方向设置为输入|
| | | 1 |输出|将方向设置为输出|
| 输入模式 | 2..3 | 00 | 高电阻 |将输入设置为高阻|
| | | 01 |拉低|输入引脚被拉低|
| | | 10 |拉高|输入引脚被拉高|
| | | 11 |状态无效|不设|
| 输出模式 | 4 | 0 |低|引脚被驱动为低电平|
| | | 1 |高|输出引脚被驱动为高电平
| 输入状态 | 5 | x |输入值|如果输入<1.5v则为0如果输入> = 1.5v则为1 |
|名字 | 位号 | 值 |含义 |注意事项 |
| :------ |------------:| ----:|------: | :---- |
|启用 | 0 | 0 | 禁用 | 禁用GPIO |
| | | 1 |启用 |启用GPIO |
|方向 | 1 | 0 |输入 |将方向设置为输入|
| | | 1 |输出 |将方向设置为输出|
| 输入模式 | 2..3 | 00 | 高电阻 |将输入设置为高阻|
| | | 01 |拉低 |输入引脚被拉低|
| | | 10 |拉高 |输入引脚被拉高|
| | | 11 | 状态无效 |不设|
| 输出模式 | 4 | 0 |低 |引脚被驱动为低电平|
| | | 1 |高 |输出引脚被驱动为高电平
| 输入状态 | 5 | x |输入值 |如果输入<1.5v则为0如果输入> = 1.5v则为1 |
我们可以在Rust中定义以下结构来控制此GPIO:

View File

@ -58,7 +58,7 @@ fn main() {
## 强类型
由于Rust具有[强类型系统],因此没有简单的方法直接创建`Foo`实例,或将`FooBuilder`转换为`Foo`而无需调用`into_foo()`方法。另外调用`into_foo()`方法会消耗原始的`FooBuilder`对象,这意味着如果不创建新实例就无法重用它。
由于Rust具有[强类型系统],因此没有简单的方法直接创建`Foo`实例,或将`FooBuilder`转换为`Foo`而无需调用`into_foo()`方法。另外调用`into_foo()`方法会消耗原始的`FooBuilder`对象,这意味着如果不创建新实例就无法重用它。
[强类型系统]: https://en.wikipedia.org/wiki/Strong_and_weak_typing

View File

@ -34,7 +34,7 @@ pub fn into_input_high_z(self) -> GpioConfig<Enabled, Input, HighZ> {
}
```
我们返回的GpioConfig在运行时永远不会存在。调用此函数实际上就是一条汇编指令-将一个常量写入到寄存器中。这意味着我们开发的类型状态机接口是一种零成本的抽象方法(zero cost abstraction)-它不需要使用CPURAM或代码空间来跟踪`GpioConfig`的状态,最终优化后与手写的直接写寄存器的代码相同。
我们返回的GpioConfig在运行时永远不会存在。调用此函数实际上就是一条汇编指令-将一个常量写入到寄存器中。这意味着我们开发的类型状态机接口是一种零成本的抽象方法(zero cost abstraction)--它不需要使用CPURAM或代码空间来跟踪`GpioConfig`的状态,最终优化后与手写的直接写寄存器的代码相同。
## 嵌套