From b529295a84b05d8832b5c3e87f6a1f4c6abe70ec Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 9 Apr 2022 15:00:26 +0800 Subject: [PATCH 001/137] minor --- README.md | 3 +-- src/intro/index.md | 50 +++++++++++++--------------------------------- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 2ed54e3b..a25146c6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # The Embedded Rust Book - -> 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]. diff --git a/src/intro/index.md b/src/intro/index.md index 512ffa18..0fffba9d 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -1,45 +1,23 @@ -# Introduction +# 引言 +欢迎阅读The Embedded Rust Book:一本关于在裸板(比如,微处理器)上使用Rust编程语言的引导书籍 -Welcome to The Embedded Rust Book: An introductory book about using the Rust -Programming Language on "Bare Metal" embedded systems, such as Microcontrollers. +## Embedded Rust 是为谁准备的 +Embedded Rust是为了那些想要进行嵌入式编程且又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(See also [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) -## Who Embedded Rust is For -Embedded Rust is for everyone who wants to do embedded programming while taking advantage of the higher-level concepts and safety guarantees the Rust language provides. -(See also [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) +## 本书范围 +这本书的目的是: ++ 使开发者快速上手嵌入式Rust开发,比如,如何设置一个开发环境。 ++ 分享那些关于使用Rust进行嵌入式开发的,现存的,最好的实践经验,比如,如何最大程度上地利用好Rust语言的特性去写更正确的嵌入式软件 ++ 某种程度下作为工具书,比如,如何将C和Rust混进一个单一的项目里 -## Scope +这本书尽可能地尝试变得通用,但是为了使读者和作者更容易理解,在所有的例子中它都使用了ARM Cortex-M架构。然而,这本书并不假设读者熟悉这个特定的架构,并在需要时解释这个架构的特定细节 -The goals of this book are: +## 这本书是为谁准备的 +这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对嵌入式Rust编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”章节,从书中获取知识补充缺失的知识,提高你的阅读体验。你可以看看“其它资源”章节,以找到你关心的主题的资源。 -* Get developers up to speed with embedded Rust development. i.e. How to set - up a development environment. +### 要求和前提 -* Share *current* best practices about using Rust for embedded development. i.e. - How to best use Rust language features to write more correct embedded - software. - -* Serve as a cookbook in some cases. e.g. How do I do mix C and Rust in a single - project? - -This book tries to be as general as possible but to make things easier for both -the readers and the writers it uses the ARM Cortex-M architecture in all its -examples. However, the book doesn't assume that the reader is familiar with this -particular architecture and explains details particular to this architecture -where required. - -## Who This Book is For -This book caters towards people with either some embedded background or some Rust background, however we believe -everybody curious about embedded Rust programming can get something out of this book. For those without any prior knowledge -we suggest you read the "Assumptions and Prerequisites" section and catch up on missing knowledge to get more out of the book -and improve your reading experience. You can check out the "Other Resources" section to find resources on topics -you might want to catch up on. - -### Assumptions and Prerequisites - -* You are comfortable using the Rust Programming Language, and have written, - run, and debugged Rust applications on a desktop environment. You should also - be familiar with the idioms of the [2018 edition] as this book targets - Rust 2018. ++ 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你也应该熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 [2018 edition]: https://doc.rust-lang.org/edition-guide/ From a441afa2675919bdd169a342e3b3a45a1460d7e9 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 11 Apr 2022 00:03:37 +0800 Subject: [PATCH 002/137] minor --- src/intro/hardware.md | 29 +++---- src/intro/index.md | 46 ++++------- src/intro/install.md | 30 +++----- src/intro/install/linux.md | 3 +- src/intro/no-std.md | 56 ++++---------- src/intro/tooling.md | 77 +++++-------------- src/start/index.md | 10 +-- src/start/qemu.md | 152 +++++++++++++------------------------ 8 files changed, 122 insertions(+), 281 deletions(-) diff --git a/src/intro/hardware.md b/src/intro/hardware.md index b458aa19..2c6996e4 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -1,6 +1,6 @@ -# Meet Your Hardware +# 熟悉你的硬件 -Let's get familiar with the hardware we'll be working with. +让我们来熟悉下我们将使用的硬件。 ## STM32F3DISCOVERY (the "F3") @@ -8,23 +8,16 @@ Let's get familiar with the hardware we'll be working with.

-What does this board contain? +这个板子包含什么? -- A [STM32F303VCT6](https://www.st.com/en/microcontrollers/stm32f303vc.html) microcontroller. This microcontroller has - - A single-core ARM Cortex-M4F processor with hardware support for single-precision floating point - operations and a maximum clock frequency of 72 MHz. - - - 256 KiB of "Flash" memory. (1 KiB = 10**24** bytes) - - - 48 KiB of RAM. - - - A variety of integrated peripherals such as timers, I2C, SPI and USART. - - - General purpose Input Output (GPIO) and other types of pins accessible through the two rows of headers along side the board. - - - A USB interface accessible through the USB port labeled "USB USER". - -- An [accelerometer](https://en.wikipedia.org/wiki/Accelerometer) as part of the [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html) chip. ++ 一个[STM32F303VCT6](https://www.st.com/en/microcontrollers/stm32f303vc.html)微控制器。这个微控制器包含 + + 一个单核的ARM Cortex-M4F 处理器,支持单精度浮点运算,72MHz的最大时钟频率。 + + 256 KiB的"Flash"存储。 + + 48 KiB的RAM + + 多种多样的外设,比如计时器,I2C,SPI和USART + + 通用GPIO和板子两侧的其它类型引脚 + + 通过被标记为“USB USER”的USB端口访问的一个USB接口 ++ 作为[LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片部分的一个[加速度计](https://en.wikipedia.org/wiki/Accelerometer) - A [magnetometer](https://en.wikipedia.org/wiki/Magnetometer) as part of the [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html) chip. diff --git a/src/intro/index.md b/src/intro/index.md index 0fffba9d..fd5daf1b 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -20,24 +20,22 @@ Embedded Rust是为了那些想要进行嵌入式编程且又想使用Rust语言 + 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你也应该熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 [2018 edition]: https://doc.rust-lang.org/edition-guide/ ++ 你可以轻松地使用其它语言,比如C,C++或者Ada开发和调试嵌入式系统,且熟悉如下的概念: + + 交叉编译 + + 存储映射外设(Memory Mapped Peripherals) + + 中断 + + I2C,SPI,串口等等常见接口 -* You are comfortable developing and debugging embedded systems in another - language such as C, C++, or Ada, and are familiar with concepts such as: - * Cross Compilation - * Memory Mapped Peripherals - * Interrupts - * Common interfaces such as I2C, SPI, Serial, etc. - -### Other Resources -If you are unfamiliar with anything mentioned above or if you want more information about a specific topic mentioned in this book you might find some of these resources helpful. +### 其它资源 +如果你不熟悉上面提到的东西或者你对这本书中提到的某个特定主题关心,你可能可以从这些资源中找到有用的信息。 | Topic | Resource | Description | |--------------|----------|-------------| -| Rust | [Rust Book](https://doc.rust-lang.org/book/) | If you are not yet comfortable with Rust, we highly suggest reading this book. | -| Rust, Embedded | [Discovery Book](https://docs.rust-embedded.org/discovery/) | If you have never done any embedded programming, this book might be a better start | -| Rust, Embedded | [Embedded Rust Bookshelf](https://docs.rust-embedded.org) | Here you can find several other resources provided by Rust's Embedded Working Group. | -| Rust, Embedded | [Embedonomicon](https://docs.rust-embedded.org/embedonomicon/) | The nitty gritty details when doing embedded programming in Rust. | -| Rust, Embedded | [embedded FAQ](https://docs.rust-embedded.org/faq.html) | Frequently asked questions about Rust in an embedded context. | +| Rust | [Rust Book](https://doc.rust-lang.org/book/) | 如果你还不能轻松地使用Rust,我们高度地建议读这本书。| +| Rust, Embedded | [Discovery Book](https://docs.rust-embedded.org/discovery/) | 如果你从来没有做过嵌入式编程,这本书可能是个更好的开始。 | +| Rust, Embedded | [Embedded Rust Bookshelf](https://docs.rust-embedded.org) | 这里你能找到许多Rust's Embedded Working Group提供的额外资源。| +| Rust, Embedded | [Embedonomicon](https://docs.rust-embedded.org/embedonomicon/) | 在Rust中进行嵌入式编程的细节。 | +| Rust, Embedded | [embedded FAQ](https://docs.rust-embedded.org/faq.html) | 关于嵌入式环境中的Rust的常见问题。| | Interrupts | [Interrupt](https://en.wikipedia.org/wiki/Interrupt) | - | | Memory-mapped IO/Peripherals | [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O) | - | | SPI, UART, RS232, USB, I2C, TTL | [Stack Exchange about SPI, UART, and other interfaces](https://electronics.stackexchange.com/questions/37814/usart-uart-rs232-usb-spi-i2c-ttl-etc-what-are-all-of-these-and-how-do-th) | - | @@ -50,22 +48,10 @@ translation listed here, please open a PR to add it. * [Japanese](https://tomoyuki-nakabayashi.github.io/book/) ([repository](/~https://github.com/tomoyuki-nakabayashi/book)) -## How to Use This Book - -This book generally assumes that you’re reading it front-to-back. Later -chapters build on concepts in earlier chapters, and earlier chapters may -not dig into details on a topic, revisiting the topic in a later chapter. - -This book will be using the [STM32F3DISCOVERY] development board from -STMicroelectronics for the majority of the examples contained within. This board -is based on the ARM Cortex-M architecture, and while basic functionality is -the same across most CPUs based on this architecture, peripherals and other -implementation details of Microcontrollers are different between different -vendors, and often even different between Microcontroller families from the same -vendor. - -For this reason, we suggest purchasing the [STM32F3DISCOVERY] development board -for the purpose of following the examples in this book. +## 如何使用这本书 +这本书通常假设你是前后阅读的。之后章节是建立在先前章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,在随后的章节将会再次重温这个主题。 +这本书将在大多数案例中使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的供应商之间是不同的,甚至来自同一个供应商的不同处理器家族也是不同的。 +因为这个理由,我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。(译者注:我使用[renode](https://renode.io/about/)来测试大多数例子) [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html diff --git a/src/intro/install.md b/src/intro/install.md index 79d030fa..775886f2 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -1,23 +1,16 @@ -# Installing the tools +# 安装工具 +这一页包含某些工具的操作系统无关的安装指令: -This page contains OS-agnostic installation instructions for a few of the tools: +### Rust 工具链 +跟着[https://rustup.rs](https://rustup.rs)的指令安装rustup。 -### Rust Toolchain - -Install rustup by following the instructions at [https://rustup.rs](https://rustup.rs). - -**NOTE** Make sure you have a compiler version equal to or newer than `1.31`. `rustc --V` should return a date newer than the one shown below. +**NOTE** 确保你的编译器版本等于或者大于`1.31`版本。`rustc -V`应该返回一个比下列日期更新的日期。 ``` text $ rustc -V rustc 1.31.1 (b6c32da9b 2018-12-18) ``` - -For bandwidth and disk usage concerns the default installation only supports -native compilation. To add cross compilation support for the ARM Cortex-M -architectures choose one of the following compilation targets. For the STM32F3DISCOVERY -board used for the examples in this book, use the `thumbv7em-none-eabihf` target. +考虑到带宽和磁盘的使用量,默认安装只支持本地环境的编译。为了添加对ARM Cortex-M架构交叉编译的支持,从下列编译目标选择一个。对于这本书里使用的STM32F3DISCOVERY板子,使用`thumbv7em-none-eabihf`目标。 Cortex-M0, M0+, and M1 (ARMv6-M architecture): ``` console @@ -64,18 +57,15 @@ rustup component add llvm-tools-preview ``` ### `cargo-generate` - -We'll use this later to generate a project from a template. +我们随后将使用这个来从模板生成一个项目。 ``` console cargo install cargo-generate ``` +注意:在某些Linux发行版上(e.g. Ubuntu) 在安装cargo-generate之前,你可能需要安装`libssl-dev`和`pkg-config` -Note: on some Linux distros (e.g. Ubuntu) you may need to install the packages `libssl-dev` and `pkg-config` prior to installing cargo-generate. - -### OS-Specific Instructions - -Now follow the instructions specific to the OS you are using: +### 操作系统特定指令 +现在根据你使用的操作系统,来执行对应的指令: - [Linux](install/linux.md) - [Windows](install/windows.md) diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index f8684d0a..145fe464 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -1,6 +1,5 @@ # Linux - -Here are the installation commands for a few Linux distributions. +这部分是关于某些Linux发行版的安装指令。 ## Packages diff --git a/src/intro/no-std.md b/src/intro/no-std.md index 35bfc1ad..02dc2736 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -1,48 +1,20 @@ -# A `no_std` Rust Environment +# 一个 `no_std` Rust环境 +嵌入式编程这个词被广泛用于许多不同的编程场景中。小到RAM和ROM只有KB的8位机(像是[ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)),大到具有一个32/64位4核Cortex-A53和1GB的RAM像是树莓派的系统([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications))。当编写代码时,取决于你有的目标和用例,将会有不同的限制和局限。
+通常嵌入式编程有两类: -The term Embedded Programming is used for a wide range of different classes of programming. -Ranging from programming 8-Bit MCUs (like the [ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)) -with just a few KB of RAM and ROM, up to systems like the Raspberry Pi -([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications)) which has a 32/64-bit -4-core Cortex-A53 @ 1.4 GHz and 1GB of RAM. Different restrictions/limitations will apply when writing code -depending on what kind of target and use case you have. +## 主机环境 +这类环境接近一个通常的PC环境。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。你可能在RAM/ROM的使用上有一些sysroot和限制,可能还有一些特别的硬件或者I/O。总之感觉像是在一个特定目的的PC环境上编程一样。 -There are two general Embedded Programming classifications: +## 裸板环境 +在一个裸板环境中,先于你的程序被加载前,不存在代码。没有系统提供的软件,我们不能加载标准库。相反地,该程序,以及它使用的crates只能使用硬件(裸板)去运行。使用`no-std`可以防止rust读取标准库。标准库中与平台无关的部分可以通过[libcore](https://doc.rust-lang.org/core/)获得。libcore也排除那些在一个嵌入式环境中非必要的东西。比如用于动态分配的内存分配器。如果你需要这些或者其它那些功能,通常会有提供这些功能的crates。 -## Hosted Environments -These kinds of environments are close to a normal PC environment. -What this means is that you are provided with a System Interface [E.G. POSIX](https://en.wikipedia.org/wiki/POSIX) -that provides you with primitives to interact with various systems, such as file systems, networking, memory management, threads, etc. -Standard libraries in turn usually depend on these primitives to implement their functionality. -You may also have some sort of sysroot and restrictions on RAM/ROM-usage, and perhaps some -special HW or I/Os. Overall it feels like coding on a special-purpose PC environment. +### libstd运行时 +就像之前提到的,使用[libstd](https://doc.rust-lang.org/std/)需要一些系统集成,这不仅是因为[libstd](https://doc.rust-lang.org/std/)只是提供了一个公共的方法访问OS抽象,它也提供了一个运行时环境。这个运行时环境,负责设置堆栈溢出保护,处理命令行参数,在一个程序主函数被激活前启动一个主线程。在一个`no_std`环境中,这个运行时环境也是不可用的。 -## Bare Metal Environments -In a bare metal environment no code has been loaded before your program. -Without the software provided by an OS we can not load the standard library. -Instead the program, along with the crates it uses, can only use the hardware (bare metal) to run. -To prevent rust from loading the standard library use `no_std`. -The platform-agnostic parts of the standard library are available through [libcore](https://doc.rust-lang.org/core/). -libcore also excludes things which are not always desirable in an embedded environment. -One of these things is a memory allocator for dynamic memory allocation. -If you require this or any other functionalities there are often crates which provide these. +## 总结 +`#![no_std]`是一个crate-level属性,它说明crate将连接至core-crate而不是std-crate。[libcore](https://doc.rust-lang.org/core/) crate是std crate一个平台无关的子集,它对程序将要运行的系统没有做要求。比如,它提供了像是floats,strings和切片的APIs,暴露了像是原子操作和SIMD指令的处理器特性相关的APIs。然而,它缺少涉及到平台集成的那些APIs。由于这些特性,no_std和[libcore](https://doc.rust-lang.org/core/)代码可以用于任何引导程序(stage 0)像是bootloaders,固件或者内核。 -### The libstd Runtime -As mentioned before using [libstd](https://doc.rust-lang.org/std/) requires some sort of system integration, but this is not only because -[libstd](https://doc.rust-lang.org/std/) is just providing a common way of accessing OS abstractions, it also provides a runtime. -This runtime, among other things, takes care of setting up stack overflow protection, processing command line arguments, -and spawning the main thread before a program's main function is invoked. This runtime also won't be available in a `no_std` environment. - -## Summary -`#![no_std]` is a crate-level attribute that indicates that the crate will link to the core-crate instead of the std-crate. -The [libcore](https://doc.rust-lang.org/core/) crate in turn is a platform-agnostic subset of the std crate -which makes no assumptions about the system the program will run on. -As such, it provides APIs for language primitives like floats, strings and slices, as well as APIs that expose processor features -like atomic operations and SIMD instructions. However it lacks APIs for anything that involves platform integration. -Because of these properties no\_std and [libcore](https://doc.rust-lang.org/core/) code can be used for any kind of -bootstrapping (stage 0) code like bootloaders, firmware or kernels. - -### Overview +### 概述 | feature | no\_std | std | |-----------------------------------------------------------|--------|-----| @@ -54,9 +26,9 @@ bootstrapping (stage 0) code like bootloaders, firmware or kernels. | libcore available | ✓ | ✓ | | writing firmware, kernel, or bootloader code | ✓ | ✘ | -\* Only if you use the `alloc` crate and use a suitable allocator like [alloc-cortex-m]. +\* 只有在你使用 `alloc` crate 和一个适合的分配器,比如[alloc-cortex-m]时有效。 -\** Only if you use the `collections` crate and configure a global default allocator. +\** 只有在你使用 `collections` crate 和配置一个全局默认的分配器时有效。 [alloc-cortex-m]: /~https://github.com/rust-embedded/alloc-cortex-m diff --git a/src/intro/tooling.md b/src/intro/tooling.md index 88adc2d1..fcbdacd0 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -1,13 +1,5 @@ -# Tooling - -Dealing with microcontrollers involves using several different tools as we'll be -dealing with an architecture different than your laptop's and we'll have to run -and debug programs on a *remote* device. - -We'll use all the tools listed below. Any recent version should work when a -minimum version is not specified, but we have listed the versions we have -tested. - +# 工具 +与微控制器打交道需要使用几种不同的工具,因为我们要处理的架构与笔记本电脑不同,我们必须在*远程*设备上运行和调试程序。我们将使用下面列举出来的工具。当一个最小版本没有被指定时,最近的版本应该可以工作,但是我们已经列出我们已经测过的那些版本。 - Rust 1.31, 1.31-beta, or a newer toolchain PLUS ARM Cortex-M compilation support. - [`cargo-binutils`](/~https://github.com/rust-embedded/cargo-binutils) ~0.1.4 @@ -16,36 +8,16 @@ tested. - GDB with ARM support. Version 7.12 or newer highly recommended. Tested versions: 7.10, 7.11, 7.12 and 8.1 - [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) or `git`. - These tools are optional but will make it easier to follow along with the book. -The text below explains why we are using these tools. Installation instructions -can be found on the next page. +这些工具都是可选的,但是它们随书使用更容易。下面的文档解释我们为什么使用这些工具。安装指令可以在下一页找到。 ## `cargo-generate` OR `git` - -Bare metal programs are non-standard (`no_std`) Rust programs that require some -adjustments to the linking process in order to get the memory layout of the program -right. This requires some additional files (like linker scripts) and -settings (like linker flags). We have packaged those for you in a template -such that you only need to fill in the missing information (such as the project name and the -characteristics of your target hardware). - -Our template is compatible with `cargo-generate`: a Cargo subcommand for -creating new Cargo projects from templates. You can also download the -template using `git`, `curl`, `wget`, or your web browser. +裸板编程是non-strandard Rust编程,为了获取正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进一个模板里,你只需要补充缺失的信息(比如项目名和你的目标硬件的特性)。
+我们的模板兼容`cargo-generate`:一个用来从模板生成新的Cargo项目的Cargo子命令。你也能使用`git`,`curl`,`wget`,或者你的网页浏览器下载模板。 ## `cargo-binutils` - -`cargo-binutils` is a collection of Cargo subcommands that make it easy to use -the LLVM tools that are shipped with the Rust toolchain. These tools include the -LLVM versions of `objdump`, `nm` and `size` and are used for inspecting -binaries. - -The advantage of using these tools over GNU binutils is that (a) installing the -LLVM tools is the same one-command installation (`rustup component add -llvm-tools-preview`) regardless of your OS and (b) tools like `objdump` support -all the architectures that `rustc` supports -- from ARM to x86_64 -- because -they both share the same LLVM backend. +`cargo-binutils`是一个Cargo子命令集合,它使得能轻松使用Rust工具链带来的LLVM工具。这些工具包括LLVM版本的`objdump`,`nm`和`size`,用来查看二进制文件。
+在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工具是相同的一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 ## `qemu-system-arm` @@ -54,31 +26,16 @@ systems. We use QEMU to run embedded programs on the host. Thanks to this you can follow some parts of this book even if you don't have any hardware with you! ## GDB - -A debugger is a very important component of embedded development as you may not -always have the luxury to log stuff to the host console. In some cases, you may -not even have LEDs to blink on your hardware! - -In general, LLDB works as well as GDB when it comes to debugging but we haven't -found an LLDB counterpart to GDB's `load` command, which uploads the program to -the target hardware, so currently we recommend that you use GDB. +调试器是嵌入式开发的一个非常重要的组件,你可能不总是有机会将内容记录到主机控制台。在某些情况里,你可能甚至没有LEDs点亮你的硬件!
+通常,提到调试,LLDB和GDB的功能一样,但是我们还没找到与GDB的`load`命令对应的LLDB命令,它将程序上传到目标硬件,因此我们现在推荐你使用GDB。 ## OpenOCD - -GDB isn't able to communicate directly with the ST-Link debugging hardware on -your STM32F3DISCOVERY development board. It needs a translator and the Open -On-Chip Debugger, OpenOCD, is that translator. OpenOCD is a program that runs -on your laptop/PC and translates between GDB's TCP/IP based remote debug -protocol and ST-Link's USB based protocol. - -OpenOCD also performs other important work as part of its translation for the -debugging of the ARM Cortex-M based microcontroller on your STM32F3DISCOVERY -development board: -* It knows how to interact with the memory mapped registers used by the ARM - CoreSight debug peripheral. It is these CoreSight registers that allow for: - * Breakpoint/Watchpoint manipulation - * Reading and writing of the CPU registers - * Detecting when the CPU has been halted for a debug event - * Continuing CPU execution after a debug event has been encountered +GDB不能直接和你的STM32F3DISCOVERY开发板上的ST-Link调试硬件通信。它需要一个转译者,the Open On-Chip Debugger,OpenOCD就是这样的转译者。OpenOCD是一个运行在你的笔记本/个人电脑上的程序,它在基于远程调试的TCP/IP协议和ST-Link的USB协议间进行转译。 +OpenOCD也执行其它一些翻译中重要的工作,用于调试你的STM32FDISCOVERY开发板上的ARM Cortex-M微控制器: +* 他知道如何与ARM CoreSight调试外设使用的内存映射寄存器交互。这些CoreSight寄存器的功能有: + * 断点操作 + * 读取和写入CPU寄存器 + * 发现CPU什么时候因为一个调试事件被挂载 + * 在遇到一个调试事件后继续CPU的执行。 * etc. -* It also knows how to erase and write to the microcontroller's FLASH +* 它也知道如何擦除和写入微控制器的FLASH diff --git a/src/start/index.md b/src/start/index.md index 2684955d..927d3d04 100644 --- a/src/start/index.md +++ b/src/start/index.md @@ -1,10 +1,4 @@ -# Getting Started - -In this section we'll walk you through the process of writing, building, -flashing and debugging embedded programs. You will be able to try most of the -examples without any special hardware as we will show you the basics using -QEMU, a popular open-source hardware emulator. The only section where hardware -is required is, naturally enough, the [Hardware](./hardware.md) section, -where we use OpenOCD to program an [STM32F3DISCOVERY]. +# 开始 +在这部分里,你将会经历编写,编译,烧录和调试嵌入式程序的过程。你能够尝试大多数案例,而不需要特定的硬件,因为我们将要向你展示QEMU的基本使用,一个开源硬件仿真器。唯一需要硬件的部分,自然是,[硬件](./hardware.md)部分,我们使用OpenOCD去编程一个[STM32F3DISCOVERY]。 [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html diff --git a/src/start/qemu.md b/src/start/qemu.md index d4dfc361..c3d3f5b8 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -1,34 +1,22 @@ # QEMU - -We'll start writing a program for the [LM3S6965], a Cortex-M3 microcontroller. -We have chosen this as our initial target because it [can be emulated](https://wiki.qemu.org/Documentation/Platforms/ARM#Supported_in_qemu-system-arm) using QEMU -so you don't need to fiddle with hardware in this section and we can focus on -the tooling and the development process. +我们将开始为[LM3S6965]编写程序,一个Cortex-M3微控制器。我们选择这个作为我们的第一个目标办,因为他能使用[QEMU仿真](https://wiki.qemu.org/Documentation/Platforms/ARM#Supported_in_qemu-system-arm),因此本节中,你不需要摆弄硬件,我们注意力可以集中在工具和开发过程上。 [LM3S6965]: http://www.ti.com/product/LM3S6965 -**IMPORTANT** -We'll use the name "app" for the project name in this tutorial. -Whenever you see the word "app" you should replace it with the name you selected -for your project. Or, you could also name your project "app" and avoid the -substitutions. - -## Creating a non standard Rust program +**重要** +在这个引导里,我们将使用"app"这个名字来代指项目名。无论何时你看到单词"app"你应该用你选择的项目名来替代它。或者你也能命名你的项目为"app",避免替代。 -We'll use the [`cortex-m-quickstart`] project template to generate a new -project from it. The created project will contain a barebone application: a good -starting point for a new embedded rust application. In addition, the project will -contain an `examples` directory, with several separate applications, highlighting -some of the key embedded rust functionality. +## 生成一个非标准的 Rust program +我们将使用[`cortex-m-quickstart`]项目模板来生成一个新项目。生成的项目将包含一个最基本的应用:对于一个新的嵌入式rust应用来说,是一个很好的起点。另外,项目将包含一个`example`文件夹,文件夹中有许多独立的应用,突出一些关键的嵌入式rust功能。 [`cortex-m-quickstart`]: /~https://github.com/rust-embedded/cortex-m-quickstart -### Using `cargo-generate` -First install cargo-generate +### 使用 `cargo-generate` +首先安装 cargo-generate ```console cargo install cargo-generate ``` -Then generate a new project +然后生成一个新项目 ```console cargo generate --git /~https://github.com/rust-embedded/cortex-m-quickstart ``` @@ -45,14 +33,14 @@ cd app ### Using `git` -Clone the repository +克隆仓库 ```console 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`文件中的占位符 ```toml [package] @@ -86,9 +74,9 @@ download" button and then click "Download ZIP". Then fill in the placeholders in the `Cargo.toml` file as done in the second part of the "Using `git`" version. -## Program Overview +## 项目概览 -For convenience here are the most important parts of the source code in `src/main.rs`: +为了便利,这是`src/main.rs`中源码最重要的部分。 ```rust,ignore #![no_std] @@ -106,44 +94,26 @@ fn main() -> ! { } ``` -This program is a bit different from a standard Rust program so let's take a -closer look. +这个程序与标准Rust程序有一点不同,因此让我们走进点看看。 -`#![no_std]` indicates that this program will *not* link to the standard crate, -`std`. Instead it will link to its subset: the `core` crate. +`#![no_std]`指出这个程序将*不会*链接标准crate`std`。反而它将会链接到它的子集: `core`crate。 -`#![no_main]` indicates that this program won't use the standard `main` -interface that most Rust programs use. The main (no pun intended) reason to go -with `no_main` is that using the `main` interface in `no_std` context requires -nightly. +`#![no_main]`指出这个程序将不会使用标准的大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是在`no_std`上下文中使用`main`接口要求nightly(译者注:原文是`requires nightly`,不知道有什么合适的翻译,主要的理由是`main`接口对程序的运行环境有要求,比如,它假设命令行参数存在,这不适合`no_std`环境)。 -`use panic_halt as _;`. This crate provides a `panic_handler` that defines -the panicking behavior of the program. We will cover this in more detail in the -[Panicking](panicking.md) chapter of the book. +`use panic_halt as _;`。这个crate提供了一个`panic_handler`,它定义了程序陷入`panic`时的行为。我们将会在这本书的[Panicking](panicking.md)章节中覆盖更多的细节。 -[`#[entry]`][entry] is an attribute provided by the [`cortex-m-rt`] crate that's used -to mark the entry point of the program. As we are not using the standard `main` -interface we need another way to indicate the entry point of the program and -that'd be `#[entry]`. +[`#[entry]`][entry] 是一个由[`cortex-m-rt`]提供的属性,它用来标记程序的入口。当我们不使用标准的`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 -`fn main() -> !`. Our program will be the *only* process running on the target -hardware so we don't want it to end! We use a [divergent function](https://doc.rust-lang.org/rust-by-example/fn/diverging.html) (the `-> !` -bit in the function signature) to ensure at compile time that'll be the case. - -## Cross compiling - -The next step is to *cross* compile the program for the Cortex-M3 architecture. -That's as simple as running `cargo build --target $TRIPLE` if you know what the -compilation target (`$TRIPLE`) should be. Luckily, the `.cargo/config.toml` in the -template has the answer: +`fn main() -> !`。我们的程序将会是运行在目标板子上的*唯一*的进程,因此我们不想要它结束!我们使用一个[divergent function](https://doc.rust-lang.org/rust-by-example/fn/diverging.html) (函数签名中的 `-> !` 位)确保在编译时就是这么回事儿。 +## 交叉编译 +下一步是位Cortex-M3架构*交叉*编译程序。如果你知道编译目标(`$TRIPLE`)应该是什么,那就和运行`cargo build --target $TRIPLE`一样简单。幸运地,模板中的`.cargo/config.toml`有这个答案: ```console tail -n6 .cargo/config.toml ``` - ```toml [build] # Pick ONE of these compilation targets @@ -152,38 +122,29 @@ target = "thumbv7m-none-eabi" # Cortex-M3 # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) # target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) ``` - -To cross compile for the Cortex-M3 architecture we have to use -`thumbv7m-none-eabi`. That target is not automatically installed when installing -the Rust toolchain, it would now be a good time to add that target to the toolchain, -if you haven't done it yet: +为了交叉编译Cortex-M3架构我们不得不使用`thumbv7m-none-eabi`。当安装Rust工具时,target不会自动被安装,如果你还没有做,现在是个好时机添加那个target到工具链上。 ``` console rustup target add thumbv7m-none-eabi ``` - Since the `thumbv7m-none-eabi` compilation target has been set as the default in - your `.cargo/config.toml` file, the two commands below do the same: - +因为`thumbv7m-none-eabi`编译目标在你的`.cargo/config.toml`中被设置成默认值,下面的两个命令是一样的效果: ```console cargo build --target thumbv7m-none-eabi cargo build ``` -## Inspecting +## 检查 -Now we have a non-native ELF binary in `target/thumbv7m-none-eabi/debug/app`. We -can inspect it using `cargo-binutils`. +现在我们在`target/thumbv7m-none-eabi/debug/app`中有一个非原生的ELF二进制文件。我们能使用`cargo-binutils`检查它。 -With `cargo-readobj` we can print the ELF headers to confirm that this is an ARM -binary. +使用`cargo-readobj`我们能打印ELF头,确认这是一个ARM二进制。 -``` console +```console cargo readobj --bin app -- --file-headers ``` -Note that: -* `--bin app` is sugar for inspect the binary at `target/$TRIPLE/debug/app` -* `--bin app` will also (re)compile the binary, if necessary - +注意: +* `--bin app` 是一个用来检查`target/$TRIPLE/debug/app`的语法糖 +* `--bin app` 如果需要,将也会重新编译二进制。 ``` text ELF Header: @@ -208,13 +169,13 @@ ELF Header: Section header string table index: 18 ``` -`cargo-size` can print the size of the linker sections of the binary. - +`cargo-size` 能打印二进制文件的linker部分的大小。 ```console cargo size --bin app --release -- -A ``` -we use `--release` to inspect the optimized version + +我们使用`--release`检查优化的版本 ``` text app : @@ -238,36 +199,26 @@ section size addr Total 14570 ``` -> A refresher on ELF linker sections +> ELF linker sections的新手 > -> - `.text` contains the program instructions -> - `.rodata` contains constant values like strings -> - `.data` contains statically allocated variables whose initial values are -> *not* zero -> - `.bss` also contains statically allocated variables whose initial values -> *are* zero -> - `.vector_table` is a *non*-standard section that we use to store the vector -> (interrupt) table -> - `.ARM.attributes` and the `.debug_*` sections contain metadata and will -> *not* be loaded onto the target when flashing the binary. - -**IMPORTANT**: ELF files contain metadata like debug information so their *size -on disk* does *not* accurately reflect the space the program will occupy when -flashed on a device. *Always* use `cargo-size` to check how big a binary really -is. - -`cargo-objdump` can be used to disassemble the binary. +> - `.text` 包含程序指令 +> - `.rodata` 包含像是字符串这样的常量 +> - `.data` 包含静态分配的初始值*非*零的变量 +> - `.bss` 也包含静态分配的初始值*是*零的变量 +> - `.vector_table` 是一个我们用来存储向量(中断)表的*非*标准的section +> - `.ARM.attributes` 和 `.debug_*` sections包含元数据,当烧录二进制文件时,其将不会被加载到目标上的。 + +**重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*不是正确地反应了程序当被烧录到设备上时将占据的空间的大小。*总是*使用`cargo-size`检查一个二进制文件有多大。 + +`cargo-objdump` 能用来反编译二进制文件。 ```console cargo objdump --bin app --release -- --disassemble --no-show-raw-insn --print-imm-hex ``` -> **NOTE** if the above command complains about `Unknown command line argument` see -> the following bug report: /~https://github.com/rust-embedded/book/issues/269 +> **注意** 如果上面的命令抱怨 `Unknown command line argument` 看下面的bug报告:/~https://github.com/rust-embedded/book/issues/269 -> **NOTE** this output can differ on your system. New versions of rustc, LLVM -> and libraries can generate different assembly. We truncated some of the instructions -> to keep the snippet small. +> **注意** 这个输出可能在你的系统上不同。rustc, LLVM 和库的新版本能产出不同的汇编。我们截取了一些指令 ```text app: file format ELF32-arm-little @@ -308,15 +259,14 @@ HardFault: 663: ``` -## Running +## 运行 -Next, let's see how to run an embedded program on QEMU! This time we'll use the -`hello` example which actually does something. +接下来,让我们看一个嵌入式程序是如何在QEMU上运行!这时我们将使用 `hello` 例子,来做些真正的事。 -For convenience here's the source code of `examples/hello.rs`: +为了方便起见,这是`examples/hello.rs`的源码: ```rust,ignore -//! Prints "Hello, world!" on the host console using semihosting +//! 在主机调试台上打印 "Hello, world!" #![no_main] #![no_std] @@ -330,8 +280,8 @@ use cortex_m_semihosting::{debug, hprintln}; fn main() -> ! { hprintln!("Hello, world!").unwrap(); - // exit QEMU - // NOTE do not run this on hardware; it can corrupt OpenOCD state + // 退出 QEMU + // NOTE 不要在硬件上运行这个;它会打破OpenOCD状态 debug::exit(debug::EXIT_SUCCESS); loop {} From eb96dd3fab17c1f83fc1044826a4474955bd00e2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 11 Apr 2022 00:29:39 +0800 Subject: [PATCH 003/137] minor --- src/start/qemu.md | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/start/qemu.md b/src/start/qemu.md index c3d3f5b8..f785d1f8 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -266,7 +266,7 @@ HardFault: 为了方便起见,这是`examples/hello.rs`的源码: ```rust,ignore -//! 在主机调试台上打印 "Hello, world!" +//! 使用semihosting在主机调试台上打印 "Hello, world!" #![no_main] #![no_std] @@ -288,20 +288,17 @@ fn main() -> ! { } ``` -This program uses something called semihosting to print text to the *host* -console. When using real hardware this requires a debug session but when using -QEMU this Just Works. +这个程序使用一些被叫做semihosting的东西去打印文本到主机调试台上。当使用真实的硬件时,这要求一个调试对话但是当使用QEMU时它可以工作。 -Let's start by compiling the example: +通过编译例子,让我们开始 ```console cargo build --example hello ``` -The output binary will be located at -`target/thumbv7m-none-eabi/debug/examples/hello`. +输出的二进制文件将位于`target/thumbv7m-none-eabi/debug/examples/hello`。 -To run this binary on QEMU run the following command: +为了在QEMU上运行这个二进制文件,执行下列的命令: ```console qemu-system-arm \ @@ -316,9 +313,7 @@ qemu-system-arm \ Hello, world! ``` -The command should successfully exit (exit code = 0) after printing the text. On -*nix you can check that with the following command: - +这个命令应该打印文本之后成功地退出 (exit code = 0)。你能使用下列的指令检查这个: ```console echo $? ``` @@ -327,12 +322,11 @@ echo $? 0 ``` -Let's break down that QEMU command: +让我们看看QEMU命令: -- `qemu-system-arm`. This is the QEMU emulator. There are a few variants of - these QEMU binaries; this one does full *system* emulation of *ARM* machines - hence the name. ++ `qemu-system-arm`。这是QEMU仿真器。这些QEMU二进制有一些变种;因为名字这个仿真器能做ARM机器的全系统仿真。 ++ `-cpu cortex-m3`。这告诉QEMU去仿真一个Cortex-M3 CPU。指定 - `-cpu cortex-m3`. This tells QEMU to emulate a Cortex-M3 CPU. Specifying the CPU model lets us catch some miscompilation errors: for example, running a program compiled for the Cortex-M4F, which has a hardware FPU, will make QEMU From 9ad6fdfa327b79f8cd5efe996c1892ba2e9b0c50 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 17 May 2022 01:02:35 +0800 Subject: [PATCH 004/137] minor --- src/start/qemu.md | 84 +++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 62 deletions(-) diff --git a/src/start/qemu.md b/src/start/qemu.md index f785d1f8..9f90619d 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -326,46 +326,25 @@ echo $? + `qemu-system-arm`。这是QEMU仿真器。这些QEMU二进制有一些变种;因为名字这个仿真器能做ARM机器的全系统仿真。 -+ `-cpu cortex-m3`。这告诉QEMU去仿真一个Cortex-M3 CPU。指定 -- `-cpu cortex-m3`. This tells QEMU to emulate a Cortex-M3 CPU. Specifying the - CPU model lets us catch some miscompilation errors: for example, running a - program compiled for the Cortex-M4F, which has a hardware FPU, will make QEMU - error during its execution. - -- `-machine lm3s6965evb`. This tells QEMU to emulate the LM3S6965EVB, a - evaluation board that contains a LM3S6965 microcontroller. - -- `-nographic`. This tells QEMU to not launch its GUI. - -- `-semihosting-config (..)`. This tells QEMU to enable semihosting. Semihosting - lets the emulated device, among other things, use the host stdout, stderr and - stdin and create files on the host. - -- `-kernel $file`. This tells QEMU which binary to load and run on the emulated - machine. - -Typing out that long QEMU command is too much work! We can set a custom runner -to simplify the process. `.cargo/config.toml` has a commented out runner that invokes -QEMU; let's uncomment it: ++ `-cpu cortex-m3`。这告诉QEMU去仿真一个Cortex-M3 CPU。指定CPU模型会让我们捕捉到一些误编译错误:比如,运行一个为Cortex-M4F编译的程序,它具有一个硬件FPU,在执行时将会使QEMU报错。 ++ `-machine lm3s6965evb`。这告诉QEMU去仿真 LM3S6965EVB,一个包含LM3S6965微控制器的评估板。 ++ `-nographic`。这告诉QEMU不要启动它的GUI。 ++ `-semihosting-config (..)`。这告诉QEMU使能半主机模式。半主机模式允许被仿真的设备,使用主机的stdout,stderr,和stdin,并在主机上创建文件。 ++ `-kernel $file`。这告诉QEMU在仿真机器上加载和运行哪个二进制文件。 +输入这么长的QEMU命令太费功夫了!我们可以设置一个自定义运行器(runner)简化步骤。`.cargo/config.toml` 有一个被注释掉的,可以调用QEMU的运行器。让我们去掉注释。 ```console head -n3 .cargo/config.toml ``` - ```toml [target.thumbv7m-none-eabi] # uncomment this to make `cargo run` execute programs on QEMU runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" ``` - -This runner only applies to the `thumbv7m-none-eabi` target, which is our -default compilation target. Now `cargo run` will compile the program and run it -on QEMU: - +这个运行器只会应用于 `thumbv7m-none-eabi` 目标,它是我们的默认编译目标。现在 `cargo run` 将会编译程序且在QEMU上运行它。 ```console cargo run --example hello --release ``` - ```text Compiling app v0.1.0 (file:///tmp/app) Finished release [optimized + debuginfo] target(s) in 0.26s @@ -373,21 +352,16 @@ cargo run --example hello --release Hello, world! ``` -## Debugging - -Debugging is critical to embedded development. Let's see how it's done. +## 调试 +对于嵌入式开发来说,调试非常重要。让我们来看下如何完成它。 -Debugging an embedded device involves *remote* debugging as the program that we -want to debug won't be running on the machine that's running the debugger -program (GDB or LLDB). +因为我们想要调试的程序运行的机器上,并没有运行一个调试器程序(GDB或者LLDB),所以调试一个嵌入式设备就涉及到了*远程*调试 -Remote debugging involves a client and a server. In a QEMU setup, the client -will be a GDB (or LLDB) process and the server will be the QEMU process that's -also running the embedded program. +远程调试涉及一个客户端和一个服务器。在QEMU的情况中,客户端将是一个GDB(或者LLDM)进程且服务器将会是运行着嵌入式程序的QEMU进程。 -In this section we'll use the `hello` example we already compiled. +在这部分,我们将使用我们已经编译的 `hello` 示例。 -The first debugging step is to launch QEMU in debugging mode: +调试的第一步是在调试模式中启动QEMU: ```console qemu-system-arm \ @@ -400,30 +374,18 @@ qemu-system-arm \ -kernel target/thumbv7m-none-eabi/debug/examples/hello ``` -This command won't print anything to the console and will block the terminal. We -have passed two extra flags this time: - -- `-gdb tcp::3333`. This tells QEMU to wait for a GDB connection on TCP - port 3333. - -- `-S`. This tells QEMU to freeze the machine at startup. Without this the - program would have reached the end of main before we had a chance to launch - the debugger! - -Next we launch GDB in another terminal and tell it to load the debug symbols of -the example: +这个命令将不打印任何东西到调试台上,且将会阻塞住终端。此刻我们还传递了两个额外的标志。 ++ `-gdb tcp::3333`。这告诉QEMU在3333的TCP端口上等待一个GDB连接。 ++ `-S`。这告诉QEMU在启动时,冻结机器。没有这个,在我们有机会启动调试器之前,程序可能已经到达了主程序的底部了! +接下来我们在另一个终端启动GDB,且告诉它去加载示例的调试符号。 ```console gdb-multiarch -q target/thumbv7m-none-eabi/debug/examples/hello ``` -**NOTE**: you might need another version of gdb instead of `gdb-multiarch` depending -on which one you installed in the installation chapter. This could also be -`arm-none-eabi-gdb` or just `gdb`. - -Then within the GDB shell we connect to QEMU, which is waiting for a connection -on TCP port 3333. +**注意**: 你可能需要另一个gdb版本而不是 `gdb-multiarch`,取决于你在安装章节中安装了哪个。这个可能是 `arm-none-eabi-gdb` 或者只是 `gdb`。 +然后在GDB shell中,我们连接QEMU,QEMU正在等待一个在3333 TCP端口上的连接。 ```console target remote :3333 ``` @@ -434,16 +396,14 @@ Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473 473 pub unsafe extern "C" fn Reset() -> ! { ``` +你将看到,进程被挂起了,程序计数器正指向一个名为 `Reset` 的函数。那是 reset 句柄:Cortex-M核在启动时执行的中断函数。 -You'll see that the process is halted and that the program counter is pointing -to a function named `Reset`. That is the reset handler: what Cortex-M cores -execute upon booting. - -> Note that on some setup, instead of displaying the line `Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473` as shown above, gdb may print some warnings like : +> 注意在一些配置中,可能不会像上面一样,展示那行`Reset() at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473`,gdb可能打印一些警告,比如: > >`core::num::bignum::Big32x40::mul_small () at src/libcore/num/bignum.rs:254` > ` src/libcore/num/bignum.rs: No such file or directory.` > +> > That's a known glitch. You can safely ignore those warnings, you're most likely at Reset(). From 200f81b4aeed94f42d6b2ff2a570f361771fd7b1 Mon Sep 17 00:00:00 2001 From: chang xu Date: Mon, 16 May 2022 13:08:34 -0400 Subject: [PATCH 005/137] Update install.md --- src/intro/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intro/install.md b/src/intro/install.md index 775886f2..c54e0b5e 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -1,5 +1,5 @@ # 安装工具 -这一页包含某些工具的操作系统无关的安装指令: +这一页包含与操作系统无关的那些工具的安装指令: ### Rust 工具链 跟着[https://rustup.rs](https://rustup.rs)的指令安装rustup。 From e8a50ab4f48cab47bc3e6e778d4bc07d4ca77295 Mon Sep 17 00:00:00 2001 From: chang xu Date: Mon, 16 May 2022 13:11:09 -0400 Subject: [PATCH 006/137] Update install.md --- src/intro/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intro/install.md b/src/intro/install.md index c54e0b5e..8ac36e22 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -1,5 +1,5 @@ # 安装工具 -这一页包含与操作系统无关的那些工具的安装指令: +这一页包含与操作系统无关的工具安装指令: ### Rust 工具链 跟着[https://rustup.rs](https://rustup.rs)的指令安装rustup。 From 0a4efb0518d22695beb0d0e5eeedc5fe800bb2a6 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 17 May 2022 21:24:25 +0800 Subject: [PATCH 007/137] minor --- src/start/qemu.md | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/start/qemu.md b/src/start/qemu.md index 9f90619d..7980906f 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -402,19 +402,15 @@ Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473 > >`core::num::bignum::Big32x40::mul_small () at src/libcore/num/bignum.rs:254` > ` src/libcore/num/bignum.rs: No such file or directory.` -> -> -> That's a known glitch. You can safely ignore those warnings, you're most likely at Reset(). - +> +> 那是一个已知的小bug,你可以安全地忽略这些警告,you're most likely at Reset()。 -This reset handler will eventually call our main function. Let's skip all the -way there using a breakpoint and the `continue` command. To set the breakpoint, let's first take a look where we would like to break in our code, with the `list` command. +这个reset句柄最终将调用我们的主函数,让我们使用一个断点和`continue`命令跳过所有的步骤。为了设置断点,让我们首先看下我们想要在我们代码哪里打断点,使用`list`指令 ```console list main ``` -This will show the source code, from the file examples/hello.rs. - +这将从examples/hello.rs文件显示源代码。 ```text 6 use panic_halt as _; 7 @@ -427,12 +423,13 @@ This will show the source code, from the file examples/hello.rs. 14 15 // exit QEMU ``` -We would like to add a breakpoint just before the "Hello, world!", which is on line 13. We do that with the `break` command: +我们想要在"Hello, world!"之前添加一个断点,在13行那里。我们可以使用`break`命令 ```console break 13 ``` -We can now instruct gdb to run up to our main function, with the `continue` command: + +我们现在能使用`continue`命令指示gdb运行到我们的主函数。 ```console continue @@ -445,8 +442,7 @@ Breakpoint 1, hello::__cortex_m_rt_main () at examples\hello.rs:13 13 hprintln!("Hello, world!").unwrap(); ``` -We are now close to the code that prints "Hello, world!". Let's move forward -using the `next` command. +我们现在靠近打印"Hello, world!"的代码。让我们使用`next`命令继续前进。 ``` console next @@ -456,15 +452,14 @@ next 16 debug::exit(debug::EXIT_SUCCESS); ``` -At this point you should see "Hello, world!" printed on the terminal that's -running `qemu-system-arm`. +在这里,你应该看到 "Hello, world!" 被打印到正在运行 `qemu-system-arm` 的终端上。 ```text $ qemu-system-arm (..) Hello, world! ``` -Calling `next` again will terminate the QEMU process. +再次调用`next`将会终止QEMU进程。 ```console next @@ -474,7 +469,7 @@ next [Inferior 1 (Remote target) exited normally] ``` -You can now exit the GDB session. +你现在能退出GDB对话了。 ``` console quit From b081df8ee81670726fb733193c44084d59fc3851 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 17 May 2022 22:04:29 +0800 Subject: [PATCH 008/137] minor --- src/start/hardware.md | 75 +++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 49 deletions(-) diff --git a/src/start/hardware.md b/src/start/hardware.md index 5af7804a..5523404f 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -1,44 +1,27 @@ -# Hardware +# 硬件 -By now you should be somewhat familiar with the tooling and the development -process. In this section we'll switch to real hardware; the process will remain -largely the same. Let's dive in. +现在你应该有点熟悉工具和开发过程了。这部分我们将转换到真正的硬件上;步骤非常相似。让我们深入进去。 -## Know your hardware +## 认识你的硬件 -Before we begin you need to identify some characteristics of the target device -as these will be used to configure the project: +在我们开始之前,你需要认识你的目标设备的一些特性,因为它们将被用于配置项目: +- ARM 核心。e.g. Cortex-M3 。 +- ARM 核心包括一个FPU吗?Cortex-M4**F**和Cortex-M7**F**有。 +- 目标设备有多少Flash和RAM?e.g. 256KiB的Flash和32KiB的RAM。 +- Flash和RAM映射在地址空间什么位置?e.g. RAM通常位于 `0x2000_0000` 地址处。 -- The ARM core. e.g. Cortex-M3. +你可以在你的设备的数据手册和参考手册上找到这些信息。 -- Does the ARM core include an FPU? Cortex-M4**F** and Cortex-M7**F** cores do. +这部分,我们会用我们的参考硬件,STM32F3DISCOVERY。这个板子包含一个STM32F303VCT6微控制器。这个微控制器拥有: +- 一个Cortex-M4F核心,它包含一个单精度FPU。 +- 位于 0x0800_0000 地址的256KiB的Flash。 +- 位于 0x2000_0000 地址的40KiB的RAM。(这里还有其它的RAM区域,但是为了简便,我们将忽略它)。 -- How much Flash memory and RAM does the target device have? e.g. 256 KiB of - Flash and 32 KiB of RAM. +## 配置 -- Where are Flash memory and RAM mapped in the address space? e.g. RAM is - commonly located at address `0x2000_0000`. +我们将从一个新的模板实例开始。参考[先前的QEMU]章节,了解如何在没有`cargo-generate`的情况下完成它。 -You can find this information in the data sheet or the reference manual of your -device. - -In this section we'll be using our reference hardware, the STM32F3DISCOVERY. -This board contains an STM32F303VCT6 microcontroller. This microcontroller has: - -- A Cortex-M4F core that includes a single precision FPU - -- 256 KiB of Flash located at address 0x0800_0000. - -- 40 KiB of RAM located at address 0x2000_0000. (There's another RAM region but - for simplicity we'll ignore it). - -## Configuring - -We'll start from scratch with a fresh template instance. Refer to the -[previous section on QEMU] for a refresher on how to do this without -`cargo-generate`. - -[previous section on QEMU]: qemu.md +[先前的QEMU]: qemu.md ``` text $ cargo generate --git /~https://github.com/rust-embedded/cortex-m-quickstart @@ -49,7 +32,7 @@ $ cargo generate --git /~https://github.com/rust-embedded/cortex-m-quickstart $ cd app ``` -Step number one is to set a default compilation target in `.cargo/config.toml`. +第一步是在`.cargo/config.toml`中设置一个默认编译目标。 ``` console tail -n5 .cargo/config.toml @@ -63,10 +46,9 @@ tail -n5 .cargo/config.toml target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) ``` -We'll use `thumbv7em-none-eabihf` as that covers the Cortex-M4F core. +我们将使用`thumbv7em-none-eabihf`,因为它覆盖Cortex-M4F核。 -The second step is to enter the memory region information into the `memory.x` -file. +第二步是将存储区域信息(memory region information)输入`memory.x`。 ``` text $ cat memory.x @@ -78,15 +60,11 @@ MEMORY RAM : ORIGIN = 0x20000000, LENGTH = 40K } ``` -> **NOTE**: If you for some reason changed the `memory.x` file after you had made -> the first build of a specific build target, then do `cargo clean` before -> `cargo build`, because `cargo build` may not track updates of `memory.x`. +> **注意**:如果你因为一些理由,在对一个特定构建目标第一次构建后,改变了`memory.x`文件,需要在`cargo build`之前执行`cargo clean`。因为`cargo build`可能不会跟踪`memory.x`的更新。 -We'll start with the hello example again, but first we have to make a small -change. +我们将使用hello示例再次开始,但是首先我们必须做一个小改变。 -In `examples/hello.rs`, make sure the `debug::exit()` call is commented out or -removed. It is used only for running in QEMU. +在`examples/hello.rs`中,确保`debug::exit()`调用被注释掉了或者移除。它只能用于在QEMU中运行时。 ```rust,ignore #[entry] @@ -101,16 +79,15 @@ fn main() -> ! { } ``` -You can now cross compile programs using `cargo build` -and inspect the binaries using `cargo-binutils` as you did before. The -`cortex-m-rt` crate handles all the magic required to get your chip running, -as helpfully, pretty much all Cortex-M CPUs boot in the same fashion. +你能像你之前做的一样,使用`cargo build`检查编译程序,使用`cargo-binutils`观察二进制文件。`cortex-m-rt`库可以处理所有运行芯片所需的魔法,同样有用的是,几乎所有的Cortex-M CPUs都按同样的方式启动。 ``` console cargo build --example hello ``` -## Debugging +## 调试 + +调试将看起来有点不同。事实上,第一步 Debugging will look a bit different. In fact, the first steps can look different depending on the target device. In this section we'll show the steps required to From 026fd46632db76d1068c68ff8e48183bb09acd15 Mon Sep 17 00:00:00 2001 From: chang xu Date: Tue, 17 May 2022 22:23:41 +0800 Subject: [PATCH 009/137] Set theme jekyll-theme-cayman --- docs/_config.yml | 1 + docs/index.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 docs/_config.yml create mode 100644 docs/index.md diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 00000000..c4192631 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..38ececf0 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,37 @@ +## Welcome to GitHub Pages + +You can use the [editor on GitHub](/~https://github.com/XxChang/book/edit/master/docs/index.md) to maintain and preview the content for your website in Markdown files. + +Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files. + +### Markdown + +Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for + +```markdown +Syntax highlighted code block + +# Header 1 +## Header 2 +### Header 3 + +- Bulleted +- List + +1. Numbered +2. List + +**Bold** and _Italic_ and `Code` text + +[Link](url) and ![Image](src) +``` + +For more details see [Basic writing and formatting syntax](https://docs.github.com/en/github/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). + +### Jekyll Themes + +Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](/~https://github.com/XxChang/book/settings/pages). The name of this theme is saved in the Jekyll `_config.yml` configuration file. + +### Support or Contact + +Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://support.github.com/contact) and we’ll help you sort it out. From d21f2be77fc6fe0e31b06958766697f6fc89d41e Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 17 May 2022 23:17:11 +0800 Subject: [PATCH 010/137] minor --- src/SUMMARY.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f62fa633..14f2d9cd 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -9,26 +9,26 @@ more information and coordination --> -- [Introduction](./intro/index.md) - - [Hardware](./intro/hardware.md) +- [引言](./intro/index.md) + - [硬件](./intro/hardware.md) - [`no_std`](./intro/no-std.md) - - [Tooling](./intro/tooling.md) - - [Installation](./intro/install.md) + - [工具](./intro/tooling.md) + - [安装](./intro/install.md) - [Linux](./intro/install/linux.md) - [MacOS](./intro/install/macos.md) - [Windows](./intro/install/windows.md) - - [Verify Installation](./intro/install/verify.md) -- [Getting started](./start/index.md) + - [安装验证](./intro/install/verify.md) +- [开始](./start/index.md) - [QEMU](./start/qemu.md) - - [Hardware](./start/hardware.md) + - [硬件](./start/hardware.md) - [Memory-mapped Registers](./start/registers.md) - - [Semihosting](./start/semihosting.md) + - [半主机](./start/semihosting.md) - [Panicking](./start/panicking.md) - - [Exceptions](./start/exceptions.md) - - [Interrupts](./start/interrupts.md) + - [异常](./start/exceptions.md) + - [中断](./start/interrupts.md) - [IO](./start/io.md) -- [Peripherals](./peripherals/index.md) - - [A first attempt in Rust](./peripherals/a-first-attempt.md) +- [外设](./peripherals/index.md) + - [第一次尝试Rust](./peripherals/a-first-attempt.md) - [The Borrow Checker](./peripherals/borrowck.md) - [Singletons](./peripherals/singletons.md) - [Static Guarantees](./static-guarantees/index.md) @@ -36,10 +36,10 @@ more information and coordination - [Peripherals as State Machines](./static-guarantees/state-machines.md) - [Design Contracts](./static-guarantees/design-contracts.md) - [Zero Cost Abstractions](./static-guarantees/zero-cost-abstractions.md) -- [Portability](./portability/index.md) -- [Concurrency](./concurrency/index.md) +- [可移植性](./portability/index.md) +- [并发](./concurrency/index.md) - [Collections](./collections/index.md) -- [Design Patterns](./design-patterns/index.md) +- [设计模式](./design-patterns/index.md) - [HALs](./design-patterns/hal/index.md) - [Checklist](./design-patterns/hal/checklist.md) - [Naming](./design-patterns/hal/naming.md) From c4e7f6406cd7caff7ab254cbd7a5ebdc57abe7aa Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 18 May 2022 10:21:16 +0800 Subject: [PATCH 011/137] minor --- src/interoperability/index.md | 19 +++++-------------- src/intro/install/linux.md | 25 +++++++++++-------------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/interoperability/index.md b/src/interoperability/index.md index 4ff5fd9d..b51880ed 100644 --- a/src/interoperability/index.md +++ b/src/interoperability/index.md @@ -1,21 +1,12 @@ -# Interoperability +# 互用性 -Interoperability between Rust and C code is always dependent -on transforming data between the two languages. -For this purposes there are two dedicated modules -in the `stdlib` called -[`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html) and -[`std::os::raw`](https://doc.rust-lang.org/std/os/raw/index.html). +Rust和C代码间的互用性始终取决于两种语言间的数据转换。为了实现它,在`stdlib`中,有两个专用模块,叫做[`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html)和[`std::os::raw`](https://doc.rust-lang.org/std/os/raw/index.html) 。 -`std::os::raw` deals with low-level primitive types that can -be converted implicitly by the compiler -because the memory layout between Rust and C -is similar enough or the same. +`std::os::raw`处理底层基本类型,这些类型可以被编译器隐式地转换,因为Rust和C之间的内存布局足够相似或相同。 -`std::ffi` provides some utility for converting more complex -types such as Strings, mapping both `&str` and `String` -to C-types that are easier and safer to handle. +`std::ffi`提供了一些工具去转换更复杂的类型,比如Strings,将`&str`和`String`映射成更容易和安全处理的C类型。 +这两个模块在`core`中都不可用,但是你可以找到一个 Neither of these modules are available in `core`, but you can find a `#![no_std]` compatible version of `std::ffi::{CStr,CString}` in the [`cstr_core`] crate, and most of the `std::os::raw` types in the [`cty`] crate. diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index 145fe464..543b0168 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -1,12 +1,12 @@ # Linux -这部分是关于某些Linux发行版的安装指令。 + +这部分是在某些Linux发行版环境下下的安装指令。 ## Packages -- Ubuntu 18.04 or newer / Debian stretch or newer +- Ubuntu 18.04 或者更新的版本 / Debian stretch 或者更新的版本 -> **NOTE** `gdb-multiarch` is the GDB command you'll use to debug your ARM -> Cortex-M programs +> **注意** `gdb-multiarch` 是你将用来调试你的ARM Cortex-M程序的GDB命令 @@ -24,8 +24,7 @@ sudo apt install gdb-multiarch openocd qemu-system-arm - Ubuntu 14.04 and 16.04 -> **NOTE** `arm-none-eabi-gdb` is the GDB command you'll use to debug your ARM -> Cortex-M programs +> **注意** `arm-none-eabi-gdb` 是你将用来调试你的ARM Cortex-M程序的GDB命令 @@ -36,10 +35,9 @@ sudo apt install gdb-multiarch openocd qemu-system-arm sudo apt install gdb-arm-none-eabi openocd qemu-system-arm ``` -- Fedora 27 or newer +- Fedora 27 或者更新的版本 -> **NOTE** `arm-none-eabi-gdb` is the GDB command you'll use to debug your ARM -> Cortex-M programs +> **注意** `arm-none-eabi-gdb` 是你将用来调试你的ARM Cortex-M程序的GDB命令 @@ -52,18 +50,17 @@ sudo dnf install arm-none-eabi-gdb openocd qemu-system-arm - Arch Linux -> **NOTE** `arm-none-eabi-gdb` is the GDB command you'll use to debug ARM -> Cortex-M programs +> **注意** `arm-none-eabi-gdb` 是你将用来调试你的ARM Cortex-M程序的GDB命令 ``` console sudo pacman -S arm-none-eabi-gdb qemu-arch-extra openocd ``` -## udev rules +## udev 规则 -This rule lets you use OpenOCD with the Discovery board without root privilege. +这个规则可以让你在不需要超级用户权限的情况下,使用OpenOCD和Discovery开发板。 -Create the file `/etc/udev/rules.d/70-st-link.rules` with the contents shown below. +生成包含下列内容的 `/etc/udev/rules.d/70-st-link.rules` 文件 ``` text # STM32F3DISCOVERY rev A/B - ST-LINK/V2 From ca0b51472c6a5de562f29f50775a9131b2dc8c89 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 18 May 2022 10:23:41 +0800 Subject: [PATCH 012/137] minor --- src/SUMMARY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 14f2d9cd..be880a74 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -43,12 +43,12 @@ more information and coordination - [HALs](./design-patterns/hal/index.md) - [Checklist](./design-patterns/hal/checklist.md) - [Naming](./design-patterns/hal/naming.md) - - [Interoperability](./design-patterns/hal/interoperability.md) + - [互用性](./design-patterns/hal/interoperability.md) - [Predictability](./design-patterns/hal/predictability.md) - [GPIO](./design-patterns/hal/gpio.md) - [Tips for embedded C developers](./c-tips/index.md) -- [Interoperability](./interoperability/index.md) +- [互用性](./interoperability/index.md) - [A little C with your Rust](./interoperability/c-with-rust.md) - [A little Rust with your C](./interoperability/rust-with-c.md) - [Unsorted topics](./unsorted/index.md) From 5c2bad6861e64d607ae330d30dedbd0f681cc9a2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 05:57:23 +0800 Subject: [PATCH 013/137] minor --- src/SUMMARY.md | 2 +- src/peripherals/borrowck.md | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index be880a74..92281df5 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -29,7 +29,7 @@ more information and coordination - [IO](./start/io.md) - [外设](./peripherals/index.md) - [第一次尝试Rust](./peripherals/a-first-attempt.md) - - [The Borrow Checker](./peripherals/borrowck.md) + - [借用检查器](./peripherals/borrowck.md) - [Singletons](./peripherals/singletons.md) - [Static Guarantees](./static-guarantees/index.md) - [Typestate Programming](./static-guarantees/typestate-programming.md) diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index fb817b2d..af42a3d2 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -1,19 +1,21 @@ -## Mutable Global State +## 可变的全局状态 -Unfortunately, hardware is basically nothing but mutable global state, which can feel very frightening for a Rust developer. Hardware exists independently from the structures of the code we write, and can be modified at any time by the real world. +不幸地是,硬件本质上是个可变的全局状态,对于Rust开发者来说可能感到很害怕。硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 -## What should our rules be? +## 我们应该遵循什么规则? -How can we reliably interact with these peripherals? +我们如何才能做到可靠地与这些外设交互? -1. Always use `volatile` methods to read or write to peripheral memory, as it can change at any time -2. In software, we should be able to share any number of read-only accesses to these peripherals -3. If some software should have read-write access to a peripheral, it should hold the only reference to that peripheral +1. 总是使用 `volatile` 方法去读或者写外设存储器。因为它可以随时改变。(译者注:对于存在多级缓存的MCU,这方法更为重要) +2. 在软件中,我们应该能共享任何数量的 对这些外设的只读访问 +3. 如果一些软件对一个外设应该可以读写访问,它应该保有对那个外设的唯一引用。 ## The Borrow Checker +## 借用检查器 -The last two of these rules sound suspiciously similar to what the Borrow Checker does already! +这些规则最后两个听起来与借用检查器已经做的事相似。 -Imagine if we could pass around ownership of these peripherals, or offer immutable or mutable references to them? +思考一下,我们是否可以分发这些外设的所有权,或者引用它们? +我们当然可以,但是对于借用检查器,每个外设我们都需要一个实例。 Well, we can, but for the Borrow Checker, we need to have exactly one instance of each peripheral, so Rust can handle this correctly. Well, luckily in the hardware, there is only one instance of any given peripheral, but how can we expose that in the structure of our code? From 3c632a776768145d02afc4ba0c0d626a9a2bbfb5 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 06:04:35 +0800 Subject: [PATCH 014/137] minor --- src/interoperability/c-with-rust.md | 9 +++++---- src/peripherals/index.md | 4 ++-- src/start/index.md | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index f7cfb959..04cfa502 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -1,9 +1,10 @@ -# A little C with your Rust +# Rust配点C -Using C or C++ inside of a Rust project consists of two major parts: +在一个Rust项目中使用C或者C++,由两个主要部分组成: + ++ 使用Rust封装要暴露的C API来用 ++ 编译要和Rust代码集成的C或者C++代码 -- Wrapping the exposed C API for use with Rust -- Building your C or C++ code to be integrated with the Rust code As C++ does not have a stable ABI for the Rust compiler to target, it is recommended to use the `C` ABI when combining Rust with C or C++. diff --git a/src/peripherals/index.md b/src/peripherals/index.md index 23731c56..9662617b 100644 --- a/src/peripherals/index.md +++ b/src/peripherals/index.md @@ -1,6 +1,6 @@ -# Peripherals +# 外设 -## What are Peripherals? +## 什么是外设? Most Microcontrollers have more than just a CPU, RAM, or Flash Memory - they contain sections of silicon which are used for interacting with systems outside of the microcontroller, as well as directly and indirectly interacting with their surroundings in the world via sensors, motor controllers, or human interfaces such as a display or keyboard. These components are collectively known as Peripherals. diff --git a/src/start/index.md b/src/start/index.md index 927d3d04..18de458d 100644 --- a/src/start/index.md +++ b/src/start/index.md @@ -1,4 +1,4 @@ # 开始 -在这部分里,你将会经历编写,编译,烧录和调试嵌入式程序的过程。你能够尝试大多数案例,而不需要特定的硬件,因为我们将要向你展示QEMU的基本使用,一个开源硬件仿真器。唯一需要硬件的部分,自然是,[硬件](./hardware.md)部分,我们使用OpenOCD去编程一个[STM32F3DISCOVERY]。 +在这部分里,你将会经历编写,编译,烧录和调试嵌入式程序的过程。大多数的例子都不需要特定的硬件,你就可以试试,因为我们将要向你展示一个开源硬件仿真器,QEMU的基本使用。唯一需要硬件的部分,自然是,[硬件](./hardware.md)章节,我们会使用OpenOCD去编程一个[STM32F3DISCOVERY]。 [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html From 94364ffe2d86abb9d2bb9cc9a5cd8d1edfa3a59d Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 06:12:24 +0800 Subject: [PATCH 015/137] minor --- src/intro/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/intro/index.md b/src/intro/index.md index fd5daf1b..af379daf 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -1,8 +1,8 @@ # 引言 -欢迎阅读The Embedded Rust Book:一本关于在裸板(比如,微处理器)上使用Rust编程语言的引导书籍 +欢迎阅读The Embedded Rust Book:一本关于在裸板(比如,微处理器)上使用Rust编程语言的引导书籍。 ## Embedded Rust 是为谁准备的 -Embedded Rust是为了那些想要进行嵌入式编程且又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(See also [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) +Embedded Rust是为了那些想要进行嵌入式编程且,又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(See also [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) ## 本书范围 这本书的目的是: @@ -13,7 +13,7 @@ Embedded Rust是为了那些想要进行嵌入式编程且又想使用Rust语言 这本书尽可能地尝试变得通用,但是为了使读者和作者更容易理解,在所有的例子中它都使用了ARM Cortex-M架构。然而,这本书并不假设读者熟悉这个特定的架构,并在需要时解释这个架构的特定细节 ## 这本书是为谁准备的 -这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对嵌入式Rust编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”章节,从书中获取知识补充缺失的知识,提高你的阅读体验。你可以看看“其它资源”章节,以找到你关心的主题的资源。 +这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对嵌入式Rust编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”章节,从书中获取、补充缺失的知识,提高你的阅读体验。你可以看看“其它资源”章节,以找到你关心的主题的资源。 ### 要求和前提 From 662bdc7f651edfb997a8fa7acb6e9e1d49fe7af5 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 06:38:25 +0800 Subject: [PATCH 016/137] minor --- src/intro/hardware.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/intro/hardware.md b/src/intro/hardware.md index 2c6996e4..67d338a6 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -16,14 +16,15 @@ + 48 KiB的RAM + 多种多样的外设,比如计时器,I2C,SPI和USART + 通用GPIO和板子两侧的其它类型引脚 - + 通过被标记为“USB USER”的USB端口访问的一个USB接口 -+ 作为[LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片部分的一个[加速度计](https://en.wikipedia.org/wiki/Accelerometer) + + 一个写着“USB USER”的USB接口 ++ [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片上的一个[加速度计](https://en.wikipedia.org/wiki/Accelerometer)。 -- A [magnetometer](https://en.wikipedia.org/wiki/Magnetometer) as part of the [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html) chip. ++ [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片上的一个[磁力计](https://en.wikipedia.org/wiki/Magnetometer)。 -- A [gyroscope](https://en.wikipedia.org/wiki/Gyroscope) as part of the [L3GD20](https://www.pololu.com/file/0J563/L3GD20.pdf) chip. ++ [L3GD20](https://www.pololu.com/file/0J563/L3GD20.pdf)芯片上的一个[陀螺仪](https://en.wikipedia.org/wiki/Gyroscope). + ++ 摆得像一个指南针形状样的8个用户LEDs。 -- 8 user LEDs arranged in the shape of a compass. - A second microcontroller: a [STM32F103](https://www.st.com/en/microcontrollers/stm32f103cb.html). This microcontroller is actually part of an on-board programmer / debugger and is connected to the USB port named "USB ST-LINK". From c562f85f0f6cf18d6033fa83eb5c6ba6fe55b10d Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 06:52:33 +0800 Subject: [PATCH 017/137] minor --- src/intro/hardware.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/intro/hardware.md b/src/intro/hardware.md index 67d338a6..77fdcfdf 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -25,9 +25,8 @@ + 摆得像一个指南针形状样的8个用户LEDs。 ++ 一个二级微控制器: [STM32F103](https://www.st.com/en/microcontrollers/stm32f103cb.html)。这个微控制器实际上是一个板载编程器/调试器的一部分,与名为“USB ST-LINK”的USB端口相连。 -- A second microcontroller: a [STM32F103](https://www.st.com/en/microcontrollers/stm32f103cb.html). This microcontroller is actually part of an on-board programmer / debugger and is connected to the USB port named "USB ST-LINK". +关于所列举的特性的更多细节,和板子的更多规范请看向[STMicroelectronics](https://www.st.com/en/evaluation-tools/stm32f3discovery.html)网站。 -For a more detailed list of features and further specifications of the board take a look at the [STMicroelectronics](https://www.st.com/en/evaluation-tools/stm32f3discovery.html) website. - -A word of caution: be careful if you want to apply external signals to the board. The microcontroller STM32F303VCT6 pins take a nominal voltage of 3.3 volts. For further information consult the [6.2 Absolute maximum ratings section in the manual](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf) +提醒一句: 如果你想要为板子提供外部信号,请小心。微控制器STM32F303VCT6引脚的标称电压是3.3伏。更多信息请查看[6.2 Absolute maximum ratings section in the manual](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf)。 From 6282ee3a952895b57c1962c179d13663cb4eff5b Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 10:15:43 +0800 Subject: [PATCH 018/137] minor --- src/intro/tooling.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/intro/tooling.md b/src/intro/tooling.md index fcbdacd0..a9a18e29 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -1,15 +1,13 @@ # 工具 与微控制器打交道需要使用几种不同的工具,因为我们要处理的架构与笔记本电脑不同,我们必须在*远程*设备上运行和调试程序。我们将使用下面列举出来的工具。当一个最小版本没有被指定时,最近的版本应该可以工作,但是我们已经列出我们已经测过的那些版本。 -- Rust 1.31, 1.31-beta, or a newer toolchain PLUS ARM Cortex-M compilation - support. +- Rust 1.31, 1.31-beta, 或者一个更新的,支持ARM Cortex-M编译的工具链。 - [`cargo-binutils`](/~https://github.com/rust-embedded/cargo-binutils) ~0.1.4 -- [`qemu-system-arm`](https://www.qemu.org/). Tested versions: 3.0.0 -- OpenOCD >=0.8. Tested versions: v0.9.0 and v0.10.0 -- GDB with ARM support. Version 7.12 or newer highly recommended. Tested - versions: 7.10, 7.11, 7.12 and 8.1 -- [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) or `git`. +- [`qemu-system-arm`](https://www.qemu.org/). 测试的版本: 3.0.0 +- OpenOCD >=0.8. 测试的版本: v0.9.0 and v0.10.0 +- 有ARM支持的GDB。高度建议7.12或者更新的版本。测试版本: 7.10, 7.11 和 8.1 +- [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) 或者 `git` -这些工具都是可选的,但是它们随书使用更容易。下面的文档解释我们为什么使用这些工具。安装指令可以在下一页找到。 +这些工具都是可选的,但是跟着书来使用它们,会更容易。下面的文档解释我们为什么使用这些工具。安装指令可以在下一页找到。 ## `cargo-generate` OR `git` 裸板编程是non-strandard Rust编程,为了获取正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进一个模板里,你只需要补充缺失的信息(比如项目名和你的目标硬件的特性)。
@@ -21,9 +19,7 @@ ## `qemu-system-arm` -QEMU is an emulator. In this case we use the variant that can fully emulate ARM -systems. We use QEMU to run embedded programs on the host. Thanks to this you -can follow some parts of this book even if you don't have any hardware with you! +QEMU是一个仿真器。在这个例子里,我们使用能完全仿真ARM系统的变种版。我们使用QEMU在主机上运行嵌入式程序。多亏了它,你可以在没有任何硬件的情况下,尝试这本书的部分例子。 ## GDB 调试器是嵌入式开发的一个非常重要的组件,你可能不总是有机会将内容记录到主机控制台。在某些情况里,你可能甚至没有LEDs点亮你的硬件!
From e699e790200cd4a3e7321ab6619673d754ae2a2b Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 10:26:32 +0800 Subject: [PATCH 019/137] minor --- src/intro/install/linux.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index 543b0168..46ceb387 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -70,21 +70,21 @@ ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", TAG+="uaccess" ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", TAG+="uaccess" ``` -Then reload all the udev rules with: +然后重新加载所有的udev规则 ``` console sudo udevadm control --reload-rules ``` -If you had the board plugged to your laptop, unplug it and then plug it again. +如果开发板已经被插入了你的笔记本,拔下它然后再插上它。 -You can check the permissions by running this command: +你可以通过运行这个命令检查权限: ``` console lsusb ``` -Which should show something like +终端显示的东西看起来像是 ```text (..) @@ -92,8 +92,7 @@ Bus 001 Device 018: ID 0483:374b STMicroelectronics ST-LINK/V2.1 (..) ``` -Take note of the bus and device numbers. Use those numbers to create a path like -`/dev/bus/usb//`. Then use this path like so: +记住bus和device号,使用这些数字创造一个像是 `/dev/bus/usb//` 这样的路径。然后像这样使用这个路径: ``` console ls -l /dev/bus/usb/001/018 @@ -112,10 +111,11 @@ user::rw- user:you:rw- ``` + The `+` appended to permissions indicates the existence of an extended permission. The `getfacl` command tells the user `you` can make use of this device. -Now, go to the [next section]. +现在,去往[下个章节]. -[next section]: verify.md +[下个章节]: verify.md From dee773b7d2c8c526fb899c7c2cec188cf1af8030 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 11:07:03 +0800 Subject: [PATCH 020/137] minor --- src/intro/install/linux.md | 5 +---- src/intro/install/verify.md | 29 +++++++++++------------------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index 46ceb387..4f7a6c2d 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -111,10 +111,7 @@ user::rw- user:you:rw- ``` - -The `+` appended to permissions indicates the existence of an extended -permission. The `getfacl` command tells the user `you` can make use of -this device. +权限后的 `+` 指出存在一个扩展权限。`getfacl` 命令显示,`user`也就是`你`能使用这个设备。 现在,去往[下个章节]. diff --git a/src/intro/install/verify.md b/src/intro/install/verify.md index 921db8cc..5a435209 100644 --- a/src/intro/install/verify.md +++ b/src/intro/install/verify.md @@ -1,30 +1,25 @@ -# Verify Installation +# 安装验证 -In this section we check that some of the required tools / drivers have been -correctly installed and configured. +在这个章节中我们将检查工具和驱动是否已经被正确地安装和配置了。 -Connect your laptop / PC to the discovery board using a micro USB cable. The -discovery board has two USB connectors; use the one labeled "USB ST-LINK" that -sits on the center of the edge of the board. +使用一个micro USB线缆将你的笔记本/个人电脑连接到discovery开发板上。discovery开发板有两个USB连接器;使用标记着"USB ST-LINK"的那个,它位于开发板边缘的中间位置。 -Also check that the ST-LINK header is populated. See the picture below; the -ST-LINK header is circled in red. +也要检查下ST-LINK的短路帽被安装了。看下面的图;ST-LINK短路帽用红色圈起来了。

-Now run the following command: +现在运行下面的命令: ``` console openocd -f interface/stlink.cfg -f target/stm32f3x.cfg ``` -> **NOTE**: Old versions of openocd, including the 0.10.0 release from 2017, do -> not contain the new (and preferable) `interface/stlink.cfg` file; instead you -> may need to use `interface/stlink-v2.cfg` or `interface/stlink-v2-1.cfg`. +> **注意**: 旧版的openocd, 包括从2017发布的0.10.0, 不包含新的(且更适合的)`interface/stlink.cfg`文件; 你需要使用`interface/stlink-v2.cfg` 或者 `interface/stlink-v2-1.cfg`。 + -You should get the following output and the program should block the console: +你应该看到下列的输出,且程序应该阻塞住了控制台: ``` text Open On-Chip Debugger 0.10.0 @@ -45,13 +40,11 @@ Info : Target voltage: 2.919881 Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints ``` -The contents may not match exactly but you should get the last line about -breakpoints and watchpoints. If you got it then terminate the OpenOCD process -and move to the [next section]. +内容可能并不是一模一样,但是在最后一行,你应该看到了breakpoints和watchpoints,如果你看到了,那就终止OpenOCD进程然后进入[下个章节] -[next section]: ../../start/index.md +[下个章节]: ../../start/index.md -If you didn't get the "breakpoints" line then try one of the following commands. +如果你没看到"breakpoints"这行,尝试下下列命令中的某一个。 ``` console openocd -f interface/stlink-v2.cfg -f target/stm32f3x.cfg From b2d23ae6cdccfadd6ef9371863f050c553bda74e Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 11:21:36 +0800 Subject: [PATCH 021/137] minor --- src/start/hardware.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/start/hardware.md b/src/start/hardware.md index 5523404f..1dcc5149 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -87,17 +87,12 @@ cargo build --example hello ## 调试 -调试将看起来有点不同。事实上,第一步 +调试将看起来有点不同。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试的,特定的信息,可以看[the +Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 -Debugging will look a bit different. In fact, the first steps can look different -depending on the target device. In this section we'll show the steps required to -debug a program running on the STM32F3DISCOVERY. This is meant to serve as a -reference; for device specific information about debugging check out [the -Debugonomicon](/~https://github.com/rust-embedded/debugonomicon). - -As before we'll do remote debugging and the client will be a GDB process. This -time, however, the server will be OpenOCD. +像之前一样,我们将进行远程调试,客户端将是一个GDB进程。不同的是,OpenOCD将是服务器。 +就像之前在 As done during the [verify] section connect the discovery board to your laptop / PC and check that the ST-LINK header is populated. From 76366e995f7ae1eeb7262391a50c2e7724adc30e Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 22 May 2022 11:26:53 +0800 Subject: [PATCH 022/137] minor --- src/interoperability/c-with-rust.md | 5 ++--- src/intro/install/verify.md | 14 ++++---------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 04cfa502..89534397 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -5,10 +5,9 @@ + 使用Rust封装要暴露的C API来用 + 编译要和Rust代码集成的C或者C++代码 +因为对于目标的Rust编译器,C++没有一个稳定的ABI,当将Rust和C或者C++结合时,建议使用`C`。 -As C++ does not have a stable ABI for the Rust compiler to target, it is recommended to use the `C` ABI when combining Rust with C or C++. - -## Defining the interface +## 定义接口 Before consuming C or C++ code from Rust, it is necessary to define (in Rust) what data types and function signatures exist in the linked code. In C or C++, you would include a header (`.h` or `.hpp`) file which defines this data. In Rust, it is necessary to either manually translate these definitions to Rust, or use a tool to generate these definitions. diff --git a/src/intro/install/verify.md b/src/intro/install/verify.md index 5a435209..ea90d375 100644 --- a/src/intro/install/verify.md +++ b/src/intro/install/verify.md @@ -54,18 +54,12 @@ openocd -f interface/stlink-v2.cfg -f target/stm32f3x.cfg openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg ``` -If one of those commands works it means you got an old hardware revision of the -discovery board. That won't be a problem but commit that fact to memory as -you'll need to configure things a bit differently later on. You can move to the -[next section]. +如果这些命令的某条起作用了,那意味着你使用的discovery开发板是一个旧的版本。那也不成问题,但是你要记住这件事,因为随后你可能需要有点不同的配置。你可以移到[下个章节]了。 -If none of the commands work as a normal user then try to run them with root -permission (e.g. `sudo openocd ..`). If the commands do work with root -permission then check that the [udev rules] have been correctly set. +如果这些命令在normal user模式下都没用,尝试下使用root模式运行它们(e.g. `sudo openocd ..`)。如果命令在root模式下起作用,要检查下[udev rules]是否被正确地设置了。 [udev rules]: linux.md#udev-rules -If you have reached this point and OpenOCD is not working please open [an issue] -and we'll help you out! +如果这些都试了,OpenOCD还不工作,请打开一个[issue],我们将帮助你! -[an issue]: /~https://github.com/rust-embedded/book/issues +[issue]: /~https://github.com/rust-embedded/book/issues From 1a254932b93d15cffb94722df3f4ce5dc401cb6e Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 26 May 2022 09:47:14 +0800 Subject: [PATCH 023/137] minor --- src/start/interrupts.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/start/interrupts.md b/src/start/interrupts.md index a898708b..10dd4349 100644 --- a/src/start/interrupts.md +++ b/src/start/interrupts.md @@ -1,4 +1,6 @@ -# Interrupts +# 中断 + +虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎一样,且它们也能被同一个中断控制器处理。 Interrupts differ from exceptions in a variety of ways but their operation and use is largely similar and they are also handled by the same interrupt From 25969a14ffd080a290cd16508eb2430ae7a783ae Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 26 May 2022 15:39:16 +0800 Subject: [PATCH 024/137] minor --- src/intro/index.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/intro/index.md b/src/intro/index.md index af379daf..757fa530 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -1,19 +1,20 @@ # 引言 -欢迎阅读The Embedded Rust Book:一本关于在裸板(比如,微处理器)上使用Rust编程语言的引导书籍。 +欢迎阅读The Embedded Rust Book:一本关于在裸机(比如,微处理器)上使用Rust编程语言的引导书籍。 ## Embedded Rust 是为谁准备的 -Embedded Rust是为了那些想要进行嵌入式编程且,又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(See also [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) +Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(See also [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) ## 本书范围 这本书的目的是: -+ 使开发者快速上手嵌入式Rust开发,比如,如何设置一个开发环境。 ++ 让开发者快速上手嵌入式Rust开发,比如,如何设置一个开发环境。 + 分享那些关于使用Rust进行嵌入式开发的,现存的,最好的实践经验,比如,如何最大程度上地利用好Rust语言的特性去写更正确的嵌入式软件 -+ 某种程度下作为工具书,比如,如何将C和Rust混进一个单一的项目里 ++ 某种程度下作为工具书,比如,如何在一个项目里将C和Rust混合进去 -这本书尽可能地尝试变得通用,但是为了使读者和作者更容易理解,在所有的例子中它都使用了ARM Cortex-M架构。然而,这本书并不假设读者熟悉这个特定的架构,并在需要时解释这个架构的特定细节 +这本书尽可能地尝试变得适用于所有场景,但是为了使读者和作者更容易理解,在所有的例子中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个特定的架构,并在需要时解释这个架构的特定细节。 ## 这本书是为谁准备的 -这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对嵌入式Rust编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”章节,从书中获取、补充缺失的知识,提高你的阅读体验。你可以看看“其它资源”章节,以找到你关心的主题的资源。 + +这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对嵌入式Rust编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”部分,从书中获取、补充缺失的知识,这样能提高你的阅读体验。你可以看看“其它资源”章节,以找到你感兴趣的主题的资源。 ### 要求和前提 @@ -27,7 +28,8 @@ Embedded Rust是为了那些想要进行嵌入式编程且,又想使用Rust语 + I2C,SPI,串口等等常见接口 ### 其它资源 -如果你不熟悉上面提到的东西或者你对这本书中提到的某个特定主题关心,你可能可以从这些资源中找到有用的信息。 + +如果你不熟悉上面提到的东西或者你对这本书中提到的某个特定主题感兴趣,你可能可以从这些资源中找到有用的信息。 | Topic | Resource | Description | |--------------|----------|-------------| @@ -40,36 +42,35 @@ Embedded Rust是为了那些想要进行嵌入式编程且,又想使用Rust语 | Memory-mapped IO/Peripherals | [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O) | - | | SPI, UART, RS232, USB, I2C, TTL | [Stack Exchange about SPI, UART, and other interfaces](https://electronics.stackexchange.com/questions/37814/usart-uart-rs232-usb-spi-i2c-ttl-etc-what-are-all-of-these-and-how-do-th) | - | -### Translations +### 翻译 -This book has been translated by generous volunteers. If you would like your -translation listed here, please open a PR to add it. +这本书是已经被一些慷慨的志愿者翻译了。如果你想要将你的翻译列在这里,请打开一个PR去添加它。 * [Japanese](https://tomoyuki-nakabayashi.github.io/book/) ([repository](/~https://github.com/tomoyuki-nakabayashi/book)) +* [Chinese](https://xxchang.github.io/book/) + ([repository](/~https://github.com/xxchang/book)) + ## 如何使用这本书 -这本书通常假设你是前后阅读的。之后章节是建立在先前章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,在随后的章节将会再次重温这个主题。 +这本书通常假设你是前后阅读的。之后章节是建立在先前章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,因为在随后的章节将会再次重温这个主题。 这本书将在大多数案例中使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的供应商之间是不同的,甚至来自同一个供应商的不同处理器家族也是不同的。 因为这个理由,我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。(译者注:我使用[renode](https://renode.io/about/)来测试大多数例子) [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html -## Contributing to This Book +## 贡献 -The work on this book is coordinated in [this repository] and is mainly -developed by the [resources team]. +这本书的工作主要在[这个仓库]里管理,且主要由[resouces team]开发。 -[this repository]: /~https://github.com/rust-embedded/book -[resources team]: /~https://github.com/rust-embedded/wg#the-resources-team +[这个仓库]: /~https://github.com/rust-embedded/book +[resouces team]: /~https://github.com/rust-embedded/wg#the-resources-team -If you have trouble following the instructions in this book or find that some -section of the book is not clear enough or hard to follow then that's a bug and -it should be reported in [the issue tracker] of this book. +如果你按着这本书的操作遇到了什么麻烦,或者这本书的一些部分不够清楚,或者很难进行下去,那这本书就是有个bug,它应该被报道给这本书的[the issue tracker] 。 [the issue tracker]: /~https://github.com/rust-embedded/book/issues/ -Pull requests fixing typos and adding new content are very welcome! +修改拼写错误和添加新内容的Pull requests非常欢迎! ## Re-using this material From ad2c7de581c1cc2f44615d113b16669580a6e928 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 26 May 2022 15:45:24 +0800 Subject: [PATCH 025/137] minor --- src/intro/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/intro/index.md b/src/intro/index.md index 757fa530..f1b4822c 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -72,9 +72,9 @@ Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言 修改拼写错误和添加新内容的Pull requests非常欢迎! -## Re-using this material +## 二次使用这个材料 -This book 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, pictures and diagrams contained within this book are licensed under the terms of the Creative Commons [CC-BY-SA v4.0] license. From 7585700c27b18ff5122de7b5a6de4548ba8d1fbc Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 26 May 2022 16:02:44 +0800 Subject: [PATCH 026/137] minor --- src/intro/no-std.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/intro/no-std.md b/src/intro/no-std.md index 02dc2736..f16460cc 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -1,15 +1,18 @@ # 一个 `no_std` Rust环境 -嵌入式编程这个词被广泛用于许多不同的编程场景中。小到RAM和ROM只有KB的8位机(像是[ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)),大到具有一个32/64位4核Cortex-A53和1GB的RAM像是树莓派的系统([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications))。当编写代码时,取决于你有的目标和用例,将会有不同的限制和局限。
+嵌入式编程这个词被广泛用于许多不同的编程场景中。小到RAM和ROM只有KB的8位机(像是[ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)),大到一个具有32/64位4核Cortex-A53和1GB的RAM的系统,比如树莓派([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications))。当编写代码时,取决于你的目标环境和用例,将会有不同的限制和局限。
通常嵌入式编程有两类: ## 主机环境 -这类环境接近一个通常的PC环境。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。你可能在RAM/ROM的使用上有一些sysroot和限制,可能还有一些特别的硬件或者I/O。总之感觉像是在一个特定目的的PC环境上编程一样。 -## 裸板环境 -在一个裸板环境中,先于你的程序被加载前,不存在代码。没有系统提供的软件,我们不能加载标准库。相反地,该程序,以及它使用的crates只能使用硬件(裸板)去运行。使用`no-std`可以防止rust读取标准库。标准库中与平台无关的部分可以通过[libcore](https://doc.rust-lang.org/core/)获得。libcore也排除那些在一个嵌入式环境中非必要的东西。比如用于动态分配的内存分配器。如果你需要这些或者其它那些功能,通常会有提供这些功能的crates。 +这类环境类似一个常见的PC环境。意味着有向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。你可能在RAM/ROM的使用上有一些sysroot的限制,可能还有一些特别的硬件或者I/O。总之感觉像是在一个特定目的的PC环境上编程一样。 + +## 裸机环境 + +在一个裸机环境中,先于你的程序被加载前,不存在代码。没有系统提供的软件,我们不能加载标准库。相反地,该程序,以及它使用的crates只能使用硬件(裸机)去运行。使用`no-std`可以防止rust读取标准库。标准库中与平台无关的部分在[libcore](https://doc.rust-lang.org/core/)中。libcore剔除了那些在一个嵌入式环境中非必要的东西。比如用于动态分配的内存分配器。如果你需要这些或者其它的那些功能,通常会有提供这些功能的crates。 ### libstd运行时 -就像之前提到的,使用[libstd](https://doc.rust-lang.org/std/)需要一些系统集成,这不仅是因为[libstd](https://doc.rust-lang.org/std/)只是提供了一个公共的方法访问OS抽象,它也提供了一个运行时环境。这个运行时环境,负责设置堆栈溢出保护,处理命令行参数,在一个程序主函数被激活前启动一个主线程。在一个`no_std`环境中,这个运行时环境也是不可用的。 + +就像之前提到的,使用[libstd](https://doc.rust-lang.org/std/)需要一些系统集成,这不仅仅是因为[libstd](https://doc.rust-lang.org/std/)提供了一个公共的方法访问操作系统,它也提供了一个运行时环境。这个运行时环境,负责设置堆栈溢出保护,处理命令行参数,在一个程序主函数被激活前启动一个主线程。在一个`no_std`环境中,这个运行时环境也是不可用的。 ## 总结 `#![no_std]`是一个crate-level属性,它说明crate将连接至core-crate而不是std-crate。[libcore](https://doc.rust-lang.org/core/) crate是std crate一个平台无关的子集,它对程序将要运行的系统没有做要求。比如,它提供了像是floats,strings和切片的APIs,暴露了像是原子操作和SIMD指令的处理器特性相关的APIs。然而,它缺少涉及到平台集成的那些APIs。由于这些特性,no_std和[libcore](https://doc.rust-lang.org/core/)代码可以用于任何引导程序(stage 0)像是bootloaders,固件或者内核。 From 9e801728af3729a197000d5f884b29327e691cf0 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 26 May 2022 16:03:36 +0800 Subject: [PATCH 027/137] minor --- src/intro/no-std.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intro/no-std.md b/src/intro/no-std.md index f16460cc..8a16b826 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -15,7 +15,7 @@ 就像之前提到的,使用[libstd](https://doc.rust-lang.org/std/)需要一些系统集成,这不仅仅是因为[libstd](https://doc.rust-lang.org/std/)提供了一个公共的方法访问操作系统,它也提供了一个运行时环境。这个运行时环境,负责设置堆栈溢出保护,处理命令行参数,在一个程序主函数被激活前启动一个主线程。在一个`no_std`环境中,这个运行时环境也是不可用的。 ## 总结 -`#![no_std]`是一个crate-level属性,它说明crate将连接至core-crate而不是std-crate。[libcore](https://doc.rust-lang.org/core/) crate是std crate一个平台无关的子集,它对程序将要运行的系统没有做要求。比如,它提供了像是floats,strings和切片的APIs,暴露了像是原子操作和SIMD指令的处理器特性相关的APIs。然而,它缺少涉及到平台集成的那些APIs。由于这些特性,no_std和[libcore](https://doc.rust-lang.org/core/)代码可以用于任何引导程序(stage 0)像是bootloaders,固件或者内核。 +`#![no_std]`是一个crate-level属性,它说明crate将连接至core-crate而不是std-crate。[libcore](https://doc.rust-lang.org/core/) crate是std crate的一个平台无关的子集,它对程序将要运行的系统没有做要求。比如,它提供了像是floats,strings和切片的APIs,暴露了像是原子操作和SIMD指令的处理器特性相关的APIs。然而,它缺少涉及到平台集成的那些APIs。由于这些特性,no_std和[libcore](https://doc.rust-lang.org/core/)代码可以用于任何引导程序(stage 0)像是bootloaders,固件或者内核。 ### 概述 From f86445b7fc2fc5b2e4cc9888e263d998ef453a76 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 26 May 2022 16:08:01 +0800 Subject: [PATCH 028/137] minor --- src/intro/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intro/install.md b/src/intro/install.md index 8ac36e22..ea6a1f8f 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -1,5 +1,5 @@ # 安装工具 -这一页包含与操作系统无关的工具安装指令: +这一页包含那些与操作系统无关的工具安装指令: ### Rust 工具链 跟着[https://rustup.rs](https://rustup.rs)的指令安装rustup。 From 3e4a24d40689ea99ca1e49028766d4de4463c0bd Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 28 May 2022 00:25:07 +0800 Subject: [PATCH 029/137] minor --- src/SUMMARY.md | 4 +-- src/intro/install/linux.md | 2 +- src/start/hardware.md | 63 +++++++++++++------------------------- src/start/qemu.md | 12 +++----- 4 files changed, 29 insertions(+), 52 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 92281df5..94c79d78 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -21,7 +21,7 @@ more information and coordination - [开始](./start/index.md) - [QEMU](./start/qemu.md) - [硬件](./start/hardware.md) - - [Memory-mapped Registers](./start/registers.md) + - [存储映射寄存器](./start/registers.md) - [半主机](./start/semihosting.md) - [Panicking](./start/panicking.md) - [异常](./start/exceptions.md) @@ -30,7 +30,7 @@ more information and coordination - [外设](./peripherals/index.md) - [第一次尝试Rust](./peripherals/a-first-attempt.md) - [借用检查器](./peripherals/borrowck.md) - - [Singletons](./peripherals/singletons.md) + - [单例](./peripherals/singletons.md) - [Static Guarantees](./static-guarantees/index.md) - [Typestate Programming](./static-guarantees/typestate-programming.md) - [Peripherals as State Machines](./static-guarantees/state-machines.md) diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index 4f7a6c2d..ee8101a0 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -1,6 +1,6 @@ # Linux -这部分是在某些Linux发行版环境下下的安装指令。 +这部分是在某些Linux发行版下的安装指令。 ## Packages diff --git a/src/start/hardware.md b/src/start/hardware.md index 1dcc5149..f86c02c8 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -87,20 +87,15 @@ cargo build --example hello ## 调试 -调试将看起来有点不同。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试的,特定的信息,可以看[the -Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 +调试将看起来有点不同。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试的,特定的信息,可以看[the Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 像之前一样,我们将进行远程调试,客户端将是一个GDB进程。不同的是,OpenOCD将是服务器。 -就像之前在 -As done during the [verify] section connect the discovery board to your laptop / -PC and check that the ST-LINK header is populated. +像是在[安装验证]中做的那样,把你的笔记本/个人电脑和discovery开发板连接起来,检查ST-LINK的短路帽是否被安装了。 -[verify]: ../intro/install/verify.md +[安装验证]: ../intro/install/verify.md -On a terminal run `openocd` to connect to the ST-LINK on the discovery board. -Run this command from the root of the template; `openocd` will pick up the -`openocd.cfg` file which indicates which interface file and target file to use. +在一个终端上运行 `openocd` 连接到你开发板上的 ST-LINK 。从模板的根目录运行这个命令;`openocd` 将会选择 `openocd.cfg` 文件,它指出了所使用的接口文件(interface file)和目标文件(target file)。 ``` console cat openocd.cfg @@ -121,9 +116,7 @@ source [find interface/stlink.cfg] source [find target/stm32f3x.cfg] ``` -> **NOTE** If you found out that you have an older revision of the discovery -> board during the [verify] section then you should modify the `openocd.cfg` -> file at this point to use `interface/stlink-v2.cfg`. +> **注意** 如果你在[安装验证]章节中,发现你的discovery开发板是一个更旧的版本,那么你应该修改你的 `openocd.cfg` 文件,让它去使用 `interface/stlink-v2.cfg` 。注释掉 `interface/stlink.cfg` ``` text $ openocd @@ -145,13 +138,13 @@ Info : Target voltage: 2.913879 Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints ``` -On another terminal run GDB, also from the root of the template. +在另一个终端,也是从模板的根目录,运行GDB。 ``` text $ -q target/thumbv7em-none-eabihf/debug/examples/hello ``` -Next connect GDB to OpenOCD, which is waiting for a TCP connection on port 3333. +接下来连接GDB和OpenOCD,OpenOCD在3333端口上正在等待一个TCP连接。 ``` console (gdb) target remote :3333 @@ -159,8 +152,7 @@ Remote debugging using :3333 0x00000000 in ?? () ``` -Now proceed to *flash* (load) the program onto the microcontroller using the -`load` command. +接下来使用`load`命令,继续 *flash*(加载) 程序到微控制器上。 ``` console (gdb) load @@ -171,19 +163,16 @@ Start address 0x800144e, load size 10380 Transfer rate: 17 KB/sec, 3460 bytes/write. ``` -The program is now loaded. This program uses semihosting so before we do any -semihosting call we have to tell OpenOCD to enable semihosting. You can send -commands to OpenOCD using the `monitor` command. +程序现在被加载了。这个程序使用半主机模式,因此在我们调用半主机模式之前,我们必须告诉OpenOCD使能半主机。你可以使用 `monitor` 命令,发送命令给OpenOCD 。 ``` console (gdb) monitor arm semihosting enable semihosting is enabled ``` -> You can see all the OpenOCD commands by invoking the `monitor help` command. +> 通过调用 `monitor help` 命令,你能看到所有的OpenOCD命令。 -Like before we can skip all the way to `main` using a breakpoint and the -`continue` command. +像我们之前一样,使用一个断点和 `continue` 命令我们可以跳过所有的步骤到 `main` 。 ``` console (gdb) break main @@ -197,12 +186,9 @@ Breakpoint 1, main () at examples/hello.rs:15 15 let mut stdout = hio::hstdout().unwrap(); ``` -> **NOTE** If GDB blocks the terminal instead of hitting the breakpoint after -> you issue the `continue` command above, you might want to double check that -> the memory region information in the `memory.x` file is correctly set up -> for your device (both the starts *and* lengths). +> **注意** 如果在你使用了上面的`continue`命令后,GDB阻塞住了终端而不是停在了断点处,你可能需要检查下`memory.x`文件中的存储分区的信息,对于你的设备来说是否被正确的设置了 (both the starts *and* lengths) 。 -Advancing the program with `next` should produce the same results as before. +使用 `next` 让程序继续,应该像之前一样,产生一样的结果。 ``` console (gdb) next @@ -212,8 +198,7 @@ Advancing the program with `next` should produce the same results as before. 19 debug::exit(debug::EXIT_SUCCESS); ``` -At this point you should see "Hello, world!" printed on the OpenOCD console, -among other stuff. +这时,你应该看到 "Hello, world!" 被打印到了OpenOCD控制台上。 ``` text $ openocd @@ -229,8 +214,7 @@ Info : halted: PC: 0x08000d70 Info : halted: PC: 0x08000d72 ``` -Issuing another `next` will make the processor execute `debug::exit`. This acts -as a breakpoint and halts the process: +使用另一个 `next` 将会让处理器执行 `debug::exit`。这个函数和断点的作用一样,会悬挂其进程。 ``` console (gdb) next @@ -239,7 +223,7 @@ Program received signal SIGTRAP, Trace/breakpoint trap. 0x0800141a in __syscall () ``` -It also causes this to be printed to the OpenOCD console: +它也会导致这个东西被打印到OpenOCD控制台: ``` text $ openocd @@ -252,17 +236,15 @@ target halted due to breakpoint, current mode: Thread xPSR: 0x21000000 pc: 0x08000d76 msp: 0x20009fc0, semihosting ``` -However, the process running on the microcontroller has not terminated and you -can resume it using `continue` or a similar command. +然而,运行在微控制器上的进程还没有被终止,使用 `continue` 或者一个相同的命令,你能重新启动它。 -You can now exit GDB using the `quit` command. +使用 `quit` 命令,你现在可以退出 GDB 了。 ``` console (gdb) quit ``` -Debugging now requires a few more steps so we have packed all those steps into a -single GDB script named `openocd.gdb`. The file was created during the `cargo generate` step, and should work without any modifications. Let's have a peak: +现在调试比之前多了点步骤,因此我们已经把所有步骤打包进一个名为 `openocd.gdb` 的GDB脚本中。这个文件在 `cargo generate` 步骤中被生成,因此不需要任何修改了。让我们看一下: ``` console cat openocd.gdb @@ -287,12 +269,9 @@ load stepi ``` -Now running ` -x openocd.gdb target/thumbv7em-none-eabihf/debug/examples/hello` will immediately connect GDB to -OpenOCD, enable semihosting, load the program and start the process. +现在运行 ` -x openocd.gdb target/thumbv7em-none-eabihf/debug/examples/hello` 将会立即把GDB和OpenOCD连接起来,使能半主机,加载程序和启动进程。 -Alternatively, you can turn ` -x openocd.gdb` into a custom runner to make -`cargo run` build a program *and* start a GDB session. This runner is included -in `.cargo/config.toml` but it's commented out. +另外,你能将 ` -x openocd.gdb` 放进一个自定义的 runner 中,使 `cargo run` 能编译程序并启动一个GDB会话。这个 runner 在 `.cargo/config.toml` 中,但是它被注释掉了。 ``` console head -n10 .cargo/config.toml diff --git a/src/start/qemu.md b/src/start/qemu.md index 7980906f..5c77e376 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -31,7 +31,7 @@ cargo generate --git /~https://github.com/rust-embedded/cortex-m-quickstart cd app ``` -### Using `git` +### 使用 `git` 克隆仓库 @@ -57,9 +57,9 @@ test = false bench = false ``` -### Using neither +### 要么使用 -Grab the latest snapshot of the `cortex-m-quickstart` template and extract it. +抓取最新的 `cortex-m-quickstart` 模板,解压它。 ```console curl -LO /~https://github.com/rust-embedded/cortex-m-quickstart/archive/master.zip @@ -68,11 +68,9 @@ mv cortex-m-quickstart-master app cd app ``` -Or you can browse to [`cortex-m-quickstart`], click the green "Clone or -download" button and then click "Download ZIP". +或者你可以浏览[`cortex-m-quickstart`],点击绿色的 "Clone or download" 按钮,然后点击 "Download ZIP" 。 -Then fill in the placeholders in the `Cargo.toml` file as done in the second -part of the "Using `git`" version. +然后像在 “使用 `git`” 那里的第二部分那样填充 `Cargo.toml` 。 ## 项目概览 From 697e2fc7fbe0b59dd8bd80761d967827824b54e2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 28 May 2022 09:08:55 +0800 Subject: [PATCH 030/137] minor --- src/SUMMARY.md | 4 ++-- src/intro/index.md | 2 +- src/start/registers.md | 8 +++++--- src/start/semihosting.md | 31 +++++++++---------------------- 4 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 94c79d78..0a9fc87a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -21,8 +21,8 @@ more information and coordination - [开始](./start/index.md) - [QEMU](./start/qemu.md) - [硬件](./start/hardware.md) - - [存储映射寄存器](./start/registers.md) - - [半主机](./start/semihosting.md) + - [存储映射的寄存器](./start/registers.md) + - [半主机模式](./start/semihosting.md) - [Panicking](./start/panicking.md) - [异常](./start/exceptions.md) - [中断](./start/interrupts.md) diff --git a/src/intro/index.md b/src/intro/index.md index f1b4822c..f3b2e8a2 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -23,7 +23,7 @@ Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言 [2018 edition]: https://doc.rust-lang.org/edition-guide/ + 你可以轻松地使用其它语言,比如C,C++或者Ada开发和调试嵌入式系统,且熟悉如下的概念: + 交叉编译 - + 存储映射外设(Memory Mapped Peripherals) + + 存储映射的外设(Memory Mapped Peripherals) + 中断 + I2C,SPI,串口等等常见接口 diff --git a/src/start/registers.md b/src/start/registers.md index cbf88509..1d88d028 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,12 +1,14 @@ -# Memory Mapped Registers +# 存储映射的寄存器 -Embedded systems can only get so far by executing normal Rust code and moving data around in RAM. If we want to get any information into or out of our system (be that blinking an LED, detecting a button press or communicating with an off-chip peripheral on some sort of bus) we're going to have to dip into the world of Peripherals and their 'memory mapped registers'. +嵌入式系统只能通过执行普通的Rust代码和在RAM间移动数据来运行下去。如果我们想要获取或者发出信息系统外的信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 +你可能发现,需要用来访问你的微控制器的外设的代码已经写了, You may well find that the code you need to access the peripherals in your micro-controller has already been written, at one of the following levels:

+* 微架构Crate - 这种Crate处理任何有用的 * Micro-architecture Crate - This sort of crate handles any useful routines common to the processor core your microcontroller is using, as well as any peripherals that are common to all micro-controllers that use that particular type of processor core. For example the [cortex-m] crate gives you functions to enable and disable interrupts, which are the same for all Cortex-M based micro-controllers. It also gives you access to the 'SysTick' peripheral included with all Cortex-M based micro-controllers. * Peripheral Access Crate (PAC) - This sort of crate is a thin wrapper over the various memory-wrapper registers defined for your particular part-number of micro-controller you are using. For example, [tm4c123x] for the Texas Instruments Tiva-C TM4C123 series, or [stm32f30x] for the ST-Micro STM32F30x series. Here, you'll be interacting with the registers directly, following each peripheral's operating instructions given in your micro-controller's Technical Reference Manual. * HAL Crate - These crates offer a more user-friendly API for your particular processor, often by implementing some common traits defined in [embedded-hal]. For example, this crate might offer a `Serial` struct, with a constructor that takes an appropriate set of GPIO pins and a baud rate, and offers some sort of `write_byte` function for sending data. See the chapter on [Portability] for more information on [embedded-hal]. @@ -136,7 +138,7 @@ pwm0.enable.write(temp); // Uh oh! Wrong variable! The HAL crate for a chip typically works by implementing a custom Trait for the raw structures exposed by the PAC. Often this trait will define a function called `constrain()` for single peripherals or `split()` for things like GPIO ports with multiple pins. This function will consume the underlying raw peripheral structure and return a new object with a higher-level API. This API may also do things like have the Serial port `new` function require a borrow on some `Clock` structure, which can only be generated by calling the function which configures the PLLs and sets up all the clock frequencies. In this way, it is statically impossible to create a Serial port object without first having configured the clock rates, or for the Serial port object to mis-convert the baud rate into clock ticks. Some crates even define special traits for the states each GPIO pin can be in, requiring the user to put a pin into the correct state (say, by selecting the appropriate Alternate Function Mode) before passing the pin into Peripheral. All with no run-time cost! -Let's see an example: +让我们看一个例子: ```rust,ignore #![no_std] diff --git a/src/start/semihosting.md b/src/start/semihosting.md index cf4626f4..903f6cde 100644 --- a/src/start/semihosting.md +++ b/src/start/semihosting.md @@ -1,14 +1,8 @@ -# Semihosting +# 半主机模式 -Semihosting is a mechanism that lets embedded devices do I/O on the host and is -mainly used to log messages to the host console. Semihosting requires a debug -session and pretty much nothing else (no extra wires!) so it's super convenient -to use. The downside is that it's super slow: each write operation can take -several milliseconds depending on the hardware debugger (e.g. ST-Link) you use. +半主机模式是一种让嵌入式设备在主机上进行I/O操作的的机制,主要被用来记录信息到主机控制台上。半主机模式需要一个debug会话,除此之外几乎没有其它要求了,因此它非常易于使用。缺点是它非常慢:取决于你的硬件调试器(e.g. ST-LINK),每个写操作需要几毫秒的时间。 -The [`cortex-m-semihosting`] crate provides an API to do semihosting operations -on Cortex-M devices. The program below is the semihosting version of "Hello, -world!": +[`cortex-m-semihosting`] crate 提供一个API去在Cortex-M设备上执行半主机操作。下面的程序是"Hello, world!"的半主机版本。 [`cortex-m-semihosting`]: https://crates.io/crates/cortex-m-semihosting @@ -29,8 +23,7 @@ fn main() -> ! { } ``` -If you run this program on hardware you'll see the "Hello, world!" message -within the OpenOCD logs. +如果你在硬件上运行这个程序,你将会在OpenOCD的logs中看到"Hello, world!"信息。 ``` text $ openocd @@ -39,17 +32,13 @@ Hello, world! (..) ``` -You do need to enable semihosting in OpenOCD from GDB first: +你首先需要从GDB使能OpenOCD中的半主机模式。 ``` console (gdb) monitor arm semihosting enable semihosting is enabled ``` -QEMU understands semihosting operations so the above program will also work with -`qemu-system-arm` without having to start a debug session. Note that you'll -need to pass the `-semihosting-config` flag to QEMU to enable semihosting -support; these flags are already included in the `.cargo/config.toml` file of the -template. +QEMU理解半主机操作,因此上面的程序不需要启动一个debug会话,也能在`qemu-system-arm`中工作。注意你需要传递`-semihosting-config`标志给QEMU去使能半主机支持;这些标识已经被包括在模板的`.cargo/config.toml`文件中了。 ``` text $ # this program will block the terminal @@ -58,10 +47,7 @@ $ cargo run Hello, world! ``` -There's also an `exit` semihosting operation that can be used to terminate the -QEMU process. Important: do **not** use `debug::exit` on hardware; this function -can corrupt your OpenOCD session and you will not be able to debug more programs -until you restart it. +`exit`半主机操作也能被用于终止QEMU进程。重要:**不要**在硬件上使用`debug::exit`;这个函数会关闭你的OpenOCD对话,除了重启它,你将不能执行更多的程序调试操作。 ```rust,ignore #![no_main] @@ -94,6 +80,7 @@ $ echo $? 1 ``` +最后一个提醒:你将 One last tip: you can set the panicking behavior to `exit(EXIT_FAILURE)`. This will let you write `no_std` run-pass tests that you can run on QEMU. @@ -131,7 +118,7 @@ $ echo $? 1 ``` -**NOTE**: To enable this feature on `panic-semihosting`, edit your +**注意**: To enable this feature on `panic-semihosting`, edit your `Cargo.toml` dependencies section where `panic-semihosting` is specified with: ``` toml From 2c571db1c7d82cf775a6a8170020a14182a4b06c Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 28 May 2022 10:01:31 +0800 Subject: [PATCH 031/137] minor --- src/start/registers.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 1d88d028..961857e4 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,18 +1,17 @@ # 存储映射的寄存器 -嵌入式系统只能通过执行普通的Rust代码和在RAM间移动数据来运行下去。如果我们想要获取或者发出信息系统外的信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 +嵌入式系统只能通过执行普通的Rust代码和在RAM间移动数据来运行下去。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 + +你可能发现,访问你的微控制器外设所需要的代码已经写进了下面某个抽象层中。 -你可能发现,需要用来访问你的微控制器的外设的代码已经写了, -You may well find that the code you need to access the peripherals in your micro-controller has already been written, at one of the following levels:

-* 微架构Crate - 这种Crate处理任何有用的 -* Micro-architecture Crate - This sort of crate handles any useful routines common to the processor core your microcontroller is using, as well as any peripherals that are common to all micro-controllers that use that particular type of processor core. For example the [cortex-m] crate gives you functions to enable and disable interrupts, which are the same for all Cortex-M based micro-controllers. It also gives you access to the 'SysTick' peripheral included with all Cortex-M based micro-controllers. -* Peripheral Access Crate (PAC) - This sort of crate is a thin wrapper over the various memory-wrapper registers defined for your particular part-number of micro-controller you are using. For example, [tm4c123x] for the Texas Instruments Tiva-C TM4C123 series, or [stm32f30x] for the ST-Micro STM32F30x series. Here, you'll be interacting with the registers directly, following each peripheral's operating instructions given in your micro-controller's Technical Reference Manual. -* HAL Crate - These crates offer a more user-friendly API for your particular processor, often by implementing some common traits defined in [embedded-hal]. For example, this crate might offer a `Serial` struct, with a constructor that takes an appropriate set of GPIO pins and a baud rate, and offers some sort of `write_byte` function for sending data. See the chapter on [Portability] for more information on [embedded-hal]. -* Board Crate - These crates go one step further than a HAL Crate by pre-configuring various peripherals and GPIO pins to suit the specific developer kit or board you are using, such as [stm32f3-discovery] for the STM32F3DISCOVERY board. +* Micro-architecture Crate(微架构Crate) - 这种Crate提供对你正在使用的微控制器的处理器核心,和任何使用这个特定类型处理器核心的微控制器的常见外设来说,有用的程序。比如 [cortex-m] crate提供你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个能力。 +* Peripheral Access Crate(PAC)(外设访问Crate) - 这种crate是在不同存储器封装的寄存器上再进行的一次封装,其由你正在使用的微控制器的产品号定义的。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,你将根据你的微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 +* HAL Crate - 这些crates为你的处理器提供了一个更友好的API,通常是通过实现在[embedded-hal]中定义的一些常用的traits来实现的。比如,这个crate可能提供一个`Serial`结构体,它的构造函数获取一组合适的GPIO口和一个波特率,它为了发送数据提供一些 `write_byte` 函数。查看 [Portability] 可以看到更多关于 [embedded-hal] 的信息。 +* Board Crate(开发板crate) - 这个通过预先配置的不同的外设和GPIO管脚再进行了一层抽象去适配你正在使用的特定的开发者工具或者开发板,比如对于STM32F3DISCOVERY开发板来说,是[stm32f3-discovery] [cortex-m]: https://crates.io/crates/cortex-m [tm4c123x]: https://crates.io/crates/tm4c123x @@ -22,7 +21,7 @@ You may well find that the code you need to access the peripherals in your micro [stm32f3-discovery]: https://crates.io/crates/stm32f3-discovery [Discovery]: https://rust-embedded.github.io/discovery/ -## Board Crate +## 开发板Crate (Board Crate) A board crate is the perfect starting point, if you're new to embedded Rust. They nicely abstract the HW details that might be overwelming when starting studying this subject, and makes standard tasks easy, like turning a LED on or off. The functionality they exposes varies a lot between boards. Since this book aims at staying hardware agnostic, the board crates won't be covered by this book. From 68fe3b584edc9a6bf5be78d6064aa60c28fce106 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 28 May 2022 10:03:42 +0800 Subject: [PATCH 032/137] minor --- src/start/registers.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 961857e4..b3b38347 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -23,15 +23,15 @@ ## 开发板Crate (Board Crate) -A board crate is the perfect starting point, if you're new to embedded Rust. They nicely abstract the HW details that might be overwelming when starting studying this subject, and makes standard tasks easy, like turning a LED on or off. The functionality they exposes varies a lot between boards. Since this book aims at staying hardware agnostic, the board crates won't be covered by this book. +如果你是嵌入式Rust新手,board crate是一个完美的起点。它们很好地抽象出了,在开始学习这个项目时,需要耗费心力了解的硬件细节,使得标准工作变得简单,像是打开或者关闭LED。不同的板子间,它们提供的功能变化很大。因为这本书是不假设我们使用的是何种板子,所以board crate不会被这本书涉及。 -If you want to experiment with the STM32F3DISCOVERY board, it is highly recommmand to take a look at the [stm32f3-discovery] board crate, which provides functionality to blink the board LEDs, access its compass, bluetooth and more. The [Discovery] book offers a great introduction to the use of a board crate. +如果你想要用STM32F3DISCOVERY开发板做实验,强烈建议看一下[stm32f3-discovery]开发板crate,它提供了功能点亮LEDs,访问它的指南针,蓝牙和其它的。[Discovery]书对于一个board crate的用法提供一个很好介绍。 -But if you're working on a system that doesn't yet have dedicated board crate, or you need functionality not provided by existing crates, read on as we start from the bottom, with the micro-architecture crates. +但是如果你正在使用一个还没有提供专用的board crate的系统,或者你需要一些功能现存的crates不提供,那我们需要从底层的微架构crates开始。 ## Micro-architecture crate -Let's look at the SysTick peripheral that's common to all Cortex-M based micro-controllers. We can find a pretty low-level API in the [cortex-m] crate, and we can use it like this: +让我们看一下对于所有的Cortex-M微控制器来说很常见的SysTick外设。我们能在[cortex-m] crate中找到一个相当底层的API,我们能像这样使用它: ```rust,ignore #![no_std] From 3905f7a1511af8175bde908d675c41acda7d7b1c Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 28 May 2022 10:13:35 +0800 Subject: [PATCH 033/137] minor --- src/start/registers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index b3b38347..59e7cfc2 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -55,9 +55,9 @@ fn main() -> ! { loop {} } ``` -The functions on the `SYST` struct map pretty closely to the functionality defined by the ARM Technical Reference Manual for this peripheral. There's nothing in this API about 'delaying for X milliseconds' - we have to crudely implement that ourselves using a `while` loop. Note that we can't access our `SYST` struct until we have called `Peripherals::take()` - this is a special routine that guarantees that there is only one `SYST` structure in our entire program. For more on that, see the [Peripherals] section. +`SYST`结构体上的功能,相当接近ARM技术手册为这个外设定义的功能。在这个API中没有关于 '延迟X毫秒' 的功能 - 我们不得不使用一个 `while` 循环自己粗略地实现它。注意,我们调用了`Peripherals::take()`才能访问我们的`SYST`结构体 - 这是一个特别的程序,保障了在我们的整个程序中只存在一个`SYST`结构体实例,更多的信息可以看[外设]部分。 -[Peripherals]: ../peripherals/index.md +[外设]: ../peripherals/index.md ## Using a Peripheral Access Crate (PAC) From f614105612282793e5db28e377051b94143553a2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 28 May 2022 10:59:13 +0800 Subject: [PATCH 034/137] minor --- src/start/registers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 59e7cfc2..2551e8d1 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -59,9 +59,9 @@ fn main() -> ! { [外设]: ../peripherals/index.md -## Using a Peripheral Access Crate (PAC) +## 使用一个外设访问Crate (PAC) -We won't get very far with our embedded software development if we restrict ourselves to only the basic peripherals included with every Cortex-M. At some point, we're going to need to write some code that's specific to the particular micro-controller we're using. In this example, let's assume we have an Texas Instruments TM4C123 - a middling 80MHz Cortex-M4 with 256 KiB of Flash. We're going to pull in the [tm4c123x] crate to make use of this chip. +如果我们把自己只局限于每个Cortex-M拥有的基本外设,那我们的嵌入式软件开发将不会走得太远。我们准备需要写一些特定于我们正在使用的微控制器的代码。在这个例子里,让我们假设我们有一个TI的TM4C123 - 一个有256KiB Flash的中等规模的80MHz的Cortex-M4。我们用[tm4c123x] crate去使用这个芯片。 ```rust,ignore #![no_std] From 3f96b865c6dc36df47e0ec0ed24d0be8520cc134 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 30 May 2022 12:04:46 +0800 Subject: [PATCH 035/137] minor --- src/start/registers.md | 48 +++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 2551e8d1..25d76d96 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -90,11 +90,11 @@ pub fn init() -> (Delay, Leds) { ``` -We've accessed the `PWM0` peripheral in exactly the same way as we accessed the `SYST` peripheral earlier, except we called `tm4c123x::Peripherals::take()`. As this crate was auto-generated using [svd2rust], the access functions for our register fields take a closure, rather than a numeric argument. While this looks like a lot of code, the Rust compiler can use it to perform a bunch of checks for us, but then generate machine-code which is pretty close to hand-written assembler! Where the auto-generated code isn't able to determine that all possible arguments to a particular accessor function are valid (for example, if the SVD defines the register as 32-bit but doesn't say if some of those 32-bit values have a special meaning), then the function is marked as `unsafe`. We can see this in the example above when setting the `load` and `compa` sub-fields using the `bits()` function. +我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,对我们寄存器位域的访问函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是很多代码,但是Rust编译器能使用它为我们执行一批检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保一个特定的访问器函数的所有可能的参数是否有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。当使用 `bits()` 函数设置 `load` 和 `compa` 子域的时候,我们能在上面看到这个例子。 -### Reading +### 读取 -The `read()` function returns an object which gives read-only access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. You can find all the functions available on special `R` return type for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation][tm4c123x documentation R]. +`read()` 函数返回一个对象,这个对象提供了这个寄存器中不同子域的只读访问,由制造商的关于这个芯片的SVD文件定义。在 [tm4c123x documentation][tm4c123x documentation R] 中你能找到所有,与这个特定芯片上,特定外设中,这个特定寄存器相关的特别 `R` 返回类型上所有可用的函数。 ```rust,ignore if pwm.ctl.read().globalsync0().is_set() { @@ -102,23 +102,24 @@ if pwm.ctl.read().globalsync0().is_set() { } ``` -### Writing +### 写入 + +`write()`函数使用一个只有一个参数的闭包。通常我们把这个参数叫做 `w`。这个参数然后提供对这个寄存器中不同的子域的读写访问,由制造商关于这个芯片的SVD文件提供。再一次,在 [tm4c123x documentation][tm4c123x documentation W] 中你能找到所有,与这个特定芯片上,特定外设中,这个特定寄存器相关的 `w` 上所有可用的函数。注意所有我们没有设置的子域将会被设置一个默认值 - 任何在这个寄存器中现存的内容将会丢失。 -The `write()` function takes a closure with a single argument. Typically we call this `w`. This argument then gives read-write access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. Again, you can find all the functions available on the 'w' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation][tm4c123x Documentation W]. Note that all of the sub-fields that we do not set will be set to a default value for us - any existing content in the register will be lost. ```rust,ignore pwm.ctl.write(|w| w.globalsync0().clear_bit()); ``` -### Modifying +### 修改 -If we wish to change only one particular sub-field in this register and leave the other sub-fields unchanged, we can use the `modify` function. This function takes a closure with two arguments - one for reading and one for writing. Typically we call these `r` and `w` respectively. The `r` argument can be used to inspect the current contents of the register, and the `w` argument can be used to modify the register contents. +如果我们希望只改变这个寄存器中某个特定子域且让其它子域不改变,我们能使用`modify`函数。这个函数使用一个具有两个参数的闭包 - 一个用来读取,一个用来写入。通常我们分别称它们为 `r` 和 `w` 。 `r` 参数能被用来查看这个寄存器现在的内容,`w` 参数能被用来修改寄存器的内容。 ```rust,ignore pwm.ctl.modify(|r, w| w.globalsync0().clear_bit()); ``` -The `modify` function really shows the power of closures here. In C, we'd have to read into some temporary value, modify the correct bits and then write the value back. This means there's considerable scope for error: +`modify` 函数在这里真正展示了闭包的能量。在C中,我们不得不读取一些临时值,修改成正确的位,然后把值写回。这意味着出现错误的范围非常大。 ```C uint32_t temp = pwm0.ctl.read(); @@ -133,9 +134,9 @@ pwm0.enable.write(temp); // Uh oh! Wrong variable! [tm4c123x documentation R]: https://docs.rs/tm4c123x/0.7.0/tm4c123x/pwm0/ctl/struct.R.html [tm4c123x documentation W]: https://docs.rs/tm4c123x/0.7.0/tm4c123x/pwm0/ctl/struct.W.html -## Using a HAL crate +## 使用一个HAL crate -The HAL crate for a chip typically works by implementing a custom Trait for the raw structures exposed by the PAC. Often this trait will define a function called `constrain()` for single peripherals or `split()` for things like GPIO ports with multiple pins. This function will consume the underlying raw peripheral structure and return a new object with a higher-level API. This API may also do things like have the Serial port `new` function require a borrow on some `Clock` structure, which can only be generated by calling the function which configures the PLLs and sets up all the clock frequencies. In this way, it is statically impossible to create a Serial port object without first having configured the clock rates, or for the Serial port object to mis-convert the baud rate into clock ticks. Some crates even define special traits for the states each GPIO pin can be in, requiring the user to put a pin into the correct state (say, by selecting the appropriate Alternate Function Mode) before passing the pin into Peripheral. All with no run-time cost! +一个芯片的HAL crate通常通过为PAC暴露的原始结构体们实现一个自定义Trait来发挥作用。经常这个trait将会为某个外设定义一个被称作 `constrain()` 的函数或者为像是有多个管脚的GPIO端口定义一个`split()`函数。这个函数将会使用原始外设结构体,然后返回一个具有更高抽象的API的新对象。这个API还可以做一些事,像是让Serial port的 `new` 函数变成需要某些`Clock`结构体的函数,这个结构体只能通过调用配置PLLs和设置所有的时钟频率的函数来生成。在这时,生成一个Serial port对象而不先配置时钟速率是不可能的,对于Serial port对象来说错误地将波特率转换为时钟滴答数也是不可能发生的。一些crates甚至为每个GPIO管脚的状态定义了特定的 traits,在把管脚传递进外设前,要求用户去把一个管脚设置成正确的状态(通过选择Alternate Function模式) 。所有这些都没有运行时消耗! 让我们看一个例子: @@ -156,42 +157,41 @@ fn main() -> ! { let p = hal::Peripherals::take().unwrap(); let cp = hal::CorePeripherals::take().unwrap(); - // Wrap up the SYSCTL struct into an object with a higher-layer API + // 将SYSCTL结构体封装成一个有更高抽象API的对象 let mut sc = p.SYSCTL.constrain(); - // Pick our oscillation settings + // 选择我们的晶振配置 sc.clock_setup.oscillator = sysctl::Oscillator::Main( sysctl::CrystalFrequency::_16mhz, sysctl::SystemClock::UsePll(sysctl::PllOutputFrequency::_80_00mhz), ); - // Configure the PLL with those settings + // 把PLL设置成那些配置 let clocks = sc.clock_setup.freeze(); - // Wrap up the GPIO_PORTA struct into an object with a higher-layer API. - // Note it needs to borrow `sc.power_control` so it can power up the GPIO - // peripheral automatically. + // 把GPIO_PORTA结构体封装成一个有更高抽象API的对象 + // 注意它需要借用 `sc.power_control` 因此它能自动开启GPIO外设。 let mut porta = p.GPIO_PORTA.split(&sc.power_control); - // Activate the UART. + // 激活UART let uart = Serial::uart0( p.UART0, - // The transmit pin + // 传送管脚 porta .pa1 .into_af_push_pull::(&mut porta.control), - // The receive pin + // 接收管脚 porta .pa0 .into_af_push_pull::(&mut porta.control), - // No RTS or CTS required + // 不需要RTS或者CTS (), (), - // The baud rate + // 波特率 115200_u32.bps(), - // Output handling + // 输出处理 NewlineMode::SwapLFtoCRLF, - // We need the clock rates to calculate the baud rate divisors + // 我们需要时钟频率去计算波特率除法器(divisors) &clocks, - // We need this to power up the UART peripheral + // 我们需要这个去启动UART外设 &sc.power_control, ); From 357f1756a5962c35b200a937a50002aca4f45c80 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 31 May 2022 08:31:04 +0800 Subject: [PATCH 036/137] minor --- src/intro/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intro/index.md b/src/intro/index.md index f3b2e8a2..9eb33a4e 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -10,7 +10,7 @@ Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言 + 分享那些关于使用Rust进行嵌入式开发的,现存的,最好的实践经验,比如,如何最大程度上地利用好Rust语言的特性去写更正确的嵌入式软件 + 某种程度下作为工具书,比如,如何在一个项目里将C和Rust混合进去 -这本书尽可能地尝试变得适用于所有场景,但是为了使读者和作者更容易理解,在所有的例子中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个特定的架构,并在需要时解释这个架构的特定细节。 +这本书尽可能地尝试变得适用于所有场景,但是为了使读者和作者更容易理解,在所有的例子中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个特定的架构,书中会在需要时解释这个架构的特定细节。 ## 这本书是为谁准备的 From fe33b7e7a62da95b0acd205816121be10efdaf78 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 31 May 2022 09:23:56 +0800 Subject: [PATCH 037/137] minor --- src/intro/index.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/intro/index.md b/src/intro/index.md index 9eb33a4e..6f997ffb 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -76,17 +76,18 @@ Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言 这本书根据以下许可证发布: -* 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, pictures and diagrams contained within this book are licensed under the terms of the Creative Commons [CC-BY-SA v4.0] license. +* 本书中包含的代码示例和独立的Cargo项目均根据[MIT License]和[Apache License v2.0]发放许可的。 +* 本书中包含的written prose(?),图片和表格均根据[CC-BY-SA v4.0]发放许可的。 [MIT License]: https://opensource.org/licenses/MIT [Apache License v2.0]: http://www.apache.org/licenses/LICENSE-2.0 [CC-BY-SA v4.0]: https://creativecommons.org/licenses/by-sa/4.0/legalcode -TL;DR: If you want to use our text or images in your work, you need to: +总之:如果你想在你的工作中使用我们的文本或者图片,你需要: -* Give the appropriate credit (i.e. mention this book on your slide, and provide a link to the relevant page) -* Provide a link to the [CC-BY-SA v4.0] licence -* Indicate if you have changed the material in any way, and make any changes to our material available under the same licence ++ 提供合适的授信 (i.e. 在你的幻灯片中提到本书,提供相关页面的连接) ++ 提供[CC-BY-SA v4.0]的许可证的连接 ++ 指出你是否改变了材料的内容,在同一个许可证下,可以对材料进行任何改变 + +也请告诉我这本书对你是否有帮助! -Also, please do let us know if you find this book useful! From ff9eb3ebf5a577bff6f84e5f4f33b1f83f2d6b16 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 1 Jun 2022 08:54:14 +0800 Subject: [PATCH 038/137] minor --- src/intro/install.md | 16 ++++++++-------- src/intro/tooling.md | 2 +- src/start/exceptions.md | 22 +++++++++------------- src/start/semihosting.md | 14 ++++---------- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/intro/install.md b/src/intro/install.md index ea6a1f8f..e1ef576e 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -4,7 +4,7 @@ ### Rust 工具链 跟着[https://rustup.rs](https://rustup.rs)的指令安装rustup。 -**NOTE** 确保你的编译器版本等于或者大于`1.31`版本。`rustc -V`应该返回一个比下列日期更新的日期。 +**注意** 确保你的编译器版本等于或者大于`1.31`版本。`rustc -V`应该返回一个比下列日期更新的日期。 ``` text $ rustc -V @@ -12,37 +12,37 @@ rustc 1.31.1 (b6c32da9b 2018-12-18) ``` 考虑到带宽和磁盘的使用量,默认安装只支持本地环境的编译。为了添加对ARM Cortex-M架构交叉编译的支持,从下列编译目标选择一个。对于这本书里使用的STM32F3DISCOVERY板子,使用`thumbv7em-none-eabihf`目标。 -Cortex-M0, M0+, and M1 (ARMv6-M architecture): +Cortex-M0, M0+, and M1 (ARMv6-M 架构): ``` console rustup target add thumbv6m-none-eabi ``` -Cortex-M3 (ARMv7-M architecture): +Cortex-M3 (ARMv7-M 架构): ``` console rustup target add thumbv7m-none-eabi ``` -Cortex-M4 and M7 without hardware floating point (ARMv7E-M architecture): +没有硬件浮点单元的Cortex-M4和M7 (ARMv7E-M架构) ``` console rustup target add thumbv7em-none-eabi ``` -Cortex-M4F and M7F with hardware floating point (ARMv7E-M architecture): +具有硬件浮点单元的Cortex-M4F和M7F (ARMv7E-M架构) ``` console rustup target add thumbv7em-none-eabihf ``` -Cortex-M23 (ARMv8-M architecture): +Cortex-M23 (ARMv8-M架构): ``` console rustup target add thumbv8m.base-none-eabi ``` -Cortex-M33 and M35P (ARMv8-M architecture): +Cortex-M33和M35P (ARMv8-M架构): ``` console rustup target add thumbv8m.main-none-eabi ``` -Cortex-M33F and M35PF with hardware floating point (ARMv8-M architecture): +具有硬件浮点单元的Cortex-M33F和M35PF (ARMv8-M架构): ``` console rustup target add thumbv8m.main-none-eabihf ``` diff --git a/src/intro/tooling.md b/src/intro/tooling.md index a9a18e29..e9eadfc3 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -9,7 +9,7 @@ 这些工具都是可选的,但是跟着书来使用它们,会更容易。下面的文档解释我们为什么使用这些工具。安装指令可以在下一页找到。 -## `cargo-generate` OR `git` +## `cargo-generate` 或者 `git` 裸板编程是non-strandard Rust编程,为了获取正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进一个模板里,你只需要补充缺失的信息(比如项目名和你的目标硬件的特性)。
我们的模板兼容`cargo-generate`:一个用来从模板生成新的Cargo项目的Cargo子命令。你也能使用`git`,`curl`,`wget`,或者你的网页浏览器下载模板。 diff --git a/src/start/exceptions.md b/src/start/exceptions.md index b15717da..36f29829 100644 --- a/src/start/exceptions.md +++ b/src/start/exceptions.md @@ -1,27 +1,22 @@ -# Exceptions +# 异常 -Exceptions, and interrupts, are a hardware mechanism by which the processor -handles asynchronous events and fatal errors (e.g. executing an invalid -instruction). Exceptions imply preemption and involve exception handlers, -subroutines executed in response to the signal that triggered the event. +异常和中断,是处理器用来处理异步事件和致命错误的一种硬件机制(e.g. 执行一个无效的指令)。异常意味着抢占并涉及异常处理程序,即响应触发事件的信号的子例程。 -The `cortex-m-rt` crate provides an [`exception`] attribute to declare exception -handlers. +`cortex-m-rt` crate提供了一个 [`exception`] 属性去声明异常处理程序。 [`exception`]: https://docs.rs/cortex-m-rt-macros/latest/cortex_m_rt_macros/attr.exception.html ``` rust,ignore -// Exception handler for the SysTick (System Timer) exception +// SysTick (System计时器)异常的异常处理函数 #[exception] fn SysTick() { // .. } ``` -Other than the `exception` attribute exception handlers look like plain -functions but there's one more difference: `exception` handlers can *not* be -called by software. Following the previous example, the statement `SysTick();` -would result in a compilation error. +除了 `exception` 属性,异常处理函数看起来和普通函数一样,但是有一个很大的不同: `exception` 处理函数*不能*被软件调用。按照先前的例子,语句 `SysTick();` 将会导致一个编译错误。 + + This behavior is pretty much intended and it's required to provide a feature: `static mut` variables declared *inside* `exception` handlers are *safe* to use. @@ -36,6 +31,7 @@ fn SysTick() { } ``` +就像我们知道的那样,在一个函数里使用`static mut`变量 As you may know, using `static mut` variables in a function makes it [*non-reentrant*](https://en.wikipedia.org/wiki/Reentrancy_(computing)). It's undefined behavior to call a non-reentrant function, directly or indirectly, from more than one exception / interrupt handler or from @@ -53,7 +49,7 @@ possible. > Thus we can derefence the reference via `*` to access the values of the variables without > needing to wrap them in an `unsafe` block. -## A complete example +## 一个复杂的例子 Here's an example that uses the system timer to raise a `SysTick` exception roughly every second. The `SysTick` exception handler keeps track of how many diff --git a/src/start/semihosting.md b/src/start/semihosting.md index 903f6cde..44920fdd 100644 --- a/src/start/semihosting.md +++ b/src/start/semihosting.md @@ -80,13 +80,9 @@ $ echo $? 1 ``` -最后一个提醒:你将 -One last tip: you can set the panicking behavior to `exit(EXIT_FAILURE)`. This -will let you write `no_std` run-pass tests that you can run on QEMU. +最后一个提示:你可以将运行时恐慌(panicking)的行为设置成 `exit(EXIT_FAILURE)`。这将允许你编写可以在QEMU上运行通过的 `no_std` 测试。 -For convenience, the `panic-semihosting` crate has an "exit" feature that when -enabled invokes `exit(EXIT_FAILURE)` after logging the panic message to the host -stderr. +为了方便,`panic-semihosting`crate有一个 "exit" 特性。当它使能的时候,在主机stderr上打印恐慌(painc)信息后会调用 `exit(EXIT_FAILURE)` 。 ```rust,ignore #![no_main] @@ -118,15 +114,13 @@ $ echo $? 1 ``` -**注意**: To enable this feature on `panic-semihosting`, edit your -`Cargo.toml` dependencies section where `panic-semihosting` is specified with: +**注意**: 为了在`panic-semihosting`上使能这个特性,编辑你的`Cargo.toml`依赖,`panic-semihosting`改写成: ``` toml panic-semihosting = { version = "VERSION", features = ["exit"] } ``` -where `VERSION` is the version desired. For more information on dependencies -features check the [`specifying dependencies`] section of the Cargo book. +`VERSION`是想要的版本。关于依赖特性的更多信息查看Cargo book的[`specifying dependencies`]部分。 [`specifying dependencies`]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html From 6fc0afcd36c77b4dfbdd7584a7e23fc6873a3652 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 1 Jun 2022 08:57:46 +0800 Subject: [PATCH 039/137] minor --- src/SUMMARY.md | 2 +- src/start/panicking.md | 2 +- src/start/qemu.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 0a9fc87a..fd9f2a73 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -23,7 +23,7 @@ more information and coordination - [硬件](./start/hardware.md) - [存储映射的寄存器](./start/registers.md) - [半主机模式](./start/semihosting.md) - - [Panicking](./start/panicking.md) + - [运行时恐慌(Panicking)](./start/panicking.md) - [异常](./start/exceptions.md) - [中断](./start/interrupts.md) - [IO](./start/io.md) diff --git a/src/start/panicking.md b/src/start/panicking.md index 10f7318b..9e8ecd08 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -1,4 +1,4 @@ -# Panicking +# 运行时恐慌(Panicking) Panicking is a core part of the Rust language. Built-in operations like indexing are runtime checked for memory safety. When out of bounds indexing is attempted diff --git a/src/start/qemu.md b/src/start/qemu.md index 5c77e376..39692542 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -98,7 +98,7 @@ fn main() -> ! { `#![no_main]`指出这个程序将不会使用标准的大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是在`no_std`上下文中使用`main`接口要求nightly(译者注:原文是`requires nightly`,不知道有什么合适的翻译,主要的理由是`main`接口对程序的运行环境有要求,比如,它假设命令行参数存在,这不适合`no_std`环境)。 -`use panic_halt as _;`。这个crate提供了一个`panic_handler`,它定义了程序陷入`panic`时的行为。我们将会在这本书的[Panicking](panicking.md)章节中覆盖更多的细节。 +`use panic_halt as _;`。这个crate提供了一个`panic_handler`,它定义了程序陷入`panic`时的行为。我们将会在这本书的[运行时恐慌(Panicking)](panicking.md)章节中覆盖更多的细节。 [`#[entry]`][entry] 是一个由[`cortex-m-rt`]提供的属性,它用来标记程序的入口。当我们不使用标准的`main`接口时,我们需要其它方法来指示程序的入口,那就是`#[entry]`。 From af5f256b3b3345df4b7fc9f662f4b1e3ad1d65fb Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 1 Jun 2022 23:52:23 +0800 Subject: [PATCH 040/137] minor --- src/portability/index.md | 13 ++++---- src/start/panicking.md | 67 ++++++++++++---------------------------- 2 files changed, 27 insertions(+), 53 deletions(-) diff --git a/src/portability/index.md b/src/portability/index.md index 1e1f03bc..b9790043 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,9 +1,10 @@ -# Portability +# 可移植性 -In embedded environments portability is a very important topic: Every vendor and even each family from a single manufacturer offers different peripherals and capabilities and similarly the ways to interact with the peripherals will vary. +在嵌入式环境中,可移植性是一个非常重要的主题: 来自单个厂家的每个供应商,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式将会不一样。 -A common way to equalize such differences is via a layer called Hardware Abstraction layer or **HAL**. +通过一个被叫做硬件抽象层或者**HAL**的层去均等化这种差异是一种常见的方法。 +> > Hardware abstractions are sets of routines in software that emulate some platform-specific details, giving programs direct access to the hardware resources. > > They often allow programmers to write device-independent, high performance applications by providing standard operating system (OS) calls to hardware. @@ -38,7 +39,7 @@ The main reason for having the **embedded-hal** traits and crates implementing a As said above there are three main users of the HAL: -### HAL implementation +### HAL实现 A HAL implementation provides the interfacing between the hardware and the users of the HAL traits. Typical implementations consist of three parts: * One or more hardware specific types @@ -51,12 +52,12 @@ Such a **HAL implementation** can come in various flavours: * Via adapter, e.g. a mock of types for unit testing * Via driver for hardware adapters, e.g. I2C multiplexer or GPIO expander -### Driver +### 驱动 A driver implements a set of custom functionality for an internal or external component, connected to a peripheral implementing the embedded-hal traits. Typical examples for such drivers include various sensors (temperature, magnetometer, accelerometer, light), display devices (LED arrays, LCD displays) and actuators (motors, transmitters). 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. -### Application +### 应用 The application binds the various parts together and ensures that the desired functionality is achieved. When porting between different systems, this is the part which requires the most adaptation efforts, since the application needs to correctly initialize the real hardware via the HAL implementation and the initialisation of different hardware differs, sometimes drastically so. Also the user choice often plays a big role, since components can be physically connected to different terminals, hardware buses sometimes need external hardware to match the configuration or there are different trade-offs to be made in the use of internal peripherals (e.g. multiple timers with different capabilities are available or peripherals conflict with others). diff --git a/src/start/panicking.md b/src/start/panicking.md index 9e8ecd08..baf799cb 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -1,80 +1,55 @@ # 运行时恐慌(Panicking) -Panicking is a core part of the Rust language. Built-in operations like indexing -are runtime checked for memory safety. When out of bounds indexing is attempted -this results in a panic. +运行时恐慌是Rust语言的一个核心部分。像是索引这样的內建的操作为了存储安全性是运行时检查的。当尝试越界索引时,这会导致运行时恐慌(panic)。 -In the standard library panicking has a defined behavior: it unwinds the stack -of the panicking thread, unless the user opted for aborting the program on -panics. +在标准库中,运行时恐慌有一个被定义了的行为:它展开(unwinds)恐慌的线程的栈,除非用户选择在恐慌时终止程序。 -In programs without standard library, however, the panicking behavior is left -undefined. A behavior can be chosen by declaring a `#[panic_handler]` function. -This function must appear exactly *once* in the dependency graph of a program, -and must have the following signature: `fn(&PanicInfo) -> !`, where [`PanicInfo`] -is a struct containing information about the location of the panic. +然而在没有标准库的程序中,运行时恐慌的行为是未被定义了的。通过声明一个 `#[painc_handler]` 函数可以选择一个运行时恐慌的行为。 + +这个函数必须在一个程序的依赖图中只出现一次,且必须有这样的签名: `fn(&PanicInfo) -> !`,`PanicInfo`是一个包含关于运行时恐慌位置的信息的结构体。 [`PanicInfo`]: https://doc.rust-lang.org/core/panic/struct.PanicInfo.html -Given that embedded systems range from user facing to safety critical (cannot -crash) there's no one size fits all panicking behavior but there are plenty of -commonly used behaviors. These common behaviors have been packaged into crates -that define the `#[panic_handler]` function. Some examples include: +鉴于嵌入式系统的范围从面向用户的系统到安全关键系统,没有一个运行时恐慌行为能满足所有场景,但是有许多常用的行为。这些常用的行为已经被打包进了一些crates中,这些crates定义了 `#[panic_handler]`函数。比如: -- [`panic-abort`]. A panic causes the abort instruction to be executed. -- [`panic-halt`]. A panic causes the program, or the current thread, to halt by - entering an infinite loop. -- [`panic-itm`]. The panicking message is logged using the ITM, an ARM Cortex-M - specific peripheral. -- [`panic-semihosting`]. The panicking message is logged to the host using the - semihosting technique. +- [`panic-abort`]. 这个运行时恐慌会导致终止指令被执行。 +- [`panic-halt`]. 这个运行时恐慌会导致程序,或者现在的线程,通过进入一个无限循环中而挂起。 +- [`panic-itm`]. 运行时恐慌的信息会被ITM记录,ITM是一个ARM Cortex-M的特殊的外设。 +- [`panic-semihosting`]. 使用半主机技术,运行时恐慌的信息被记录到主机上。 [`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 -You may be able to find even more crates searching for the [`panic-handler`] -keyword on crates.io. +在 crates.io 上搜索 [`panic-handler`],你甚至能找到更多的crates。 [`panic-handler`]: https://crates.io/keywords/panic-handler -A program can pick one of these behaviors simply by linking to the corresponding -crate. The fact that the panicking behavior is expressed in the source of -an application as a single line of code is not only useful as documentation but -can also be used to change the panicking behavior according to the compilation -profile. For example: +仅仅通过连接到相关的crate,一个程序就可以从这些行为中选择一个行为。将运行时恐慌的行为作为一行代码放进一个应用的源码中,不仅仅是可以作为文档使用,而且能根据编译配置改变运行时恐慌的行为。比如: ``` rust,ignore #![no_main] #![no_std] -// dev profile: easier to debug panics; can put a breakpoint on `rust_begin_unwind` +// dev配置: 更容易调试运行时恐慌; 可以在 `rust_begin_unwind` 上放一个断点 #[cfg(debug_assertions)] use panic_halt as _; -// release profile: minimize the binary size of the application +// release配置: 最小化应用的二进制文件的大小 #[cfg(not(debug_assertions))] use panic_abort as _; // .. ``` -In this example the crate links to the `panic-halt` crate when built with the -dev profile (`cargo build`), but links to the `panic-abort` crate when built -with the release profile (`cargo build --release`). +在这个例子里,当使用dev配置编译的时候(`cargo build`),crate链接到 `panic-halt` crate上,但是当使用release配置编译时(`cargo build --release`),crate链接到`panic-abort` crate上。 -> The `use panic_abort as _;` form of the `use` statement is used to ensure the `panic_abort` panic handler is -> included in our final executable while making it clear to the compiler that we won't explicitly use anything from -> the crate. Without the `as _` rename, the compiler would warn that we have an unused import. -> Sometimes you might see `extern crate panic_abort` instead, which is an older style used before the -> 2018 edition of Rust, and should now only be used for "sysroot" crates (those distributed with Rust itself) such -> as `proc_macro`, `alloc`, `std`, and `test`. +> `use panic_abort as _` 形式的 `use` 语句被用来确保 `panic_abort` 运行时恐慌函数被包含进我们最终的可执行程序里,同时让编译器清除地知道我们不会从这个crate显式地使用任何东西。没有 `_` 重命名,编译器将会警告我们有一个未使用的导入。反而,有时候你可能看到的是 `extern crate panic_abort`,这是Rust 2018之前的版本使用的更旧的写法,现在应该只被用于 "sysroot" crates (与Rust一起发布的crates),比如 `proc_macro`,`alloc`,`std` 和 `test` 。 -## An example +## 一个例子 -Here's an example that tries to index an array beyond its length. The operation -results in a panic. +这里有一个尝试越界访问数组的例子。操作的结果导致了一个运行时恐慌(panic)。 ```rust,ignore #![no_main] @@ -94,8 +69,7 @@ fn main() -> ! { } ``` -This example chose the `panic-semihosting` behavior which prints the panic -message to the host console using semihosting. +这个例子选择了`panic-semihosting`行为,运行时恐慌的信息会被打印至使用了半主机模式的主机控制台上。 ``` text $ cargo run @@ -103,5 +77,4 @@ $ cargo run panicked at 'index out of bounds: the len is 3 but the index is 4', src/main.rs:12:13 ``` -You can try changing the behavior to `panic-halt` and confirm that no message is -printed in that case. +你可以尝试将行为改成`panic-halt`,确定在这个案例里没有信息被打印。 From 2a59529f408d72b5c565da474bec35dbcbc77829 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 3 Jun 2022 04:17:28 +0800 Subject: [PATCH 041/137] minor --- src/start/exceptions.md | 104 ++++++++++++---------------------------- 1 file changed, 30 insertions(+), 74 deletions(-) diff --git a/src/start/exceptions.md b/src/start/exceptions.md index 36f29829..00d7c7c7 100644 --- a/src/start/exceptions.md +++ b/src/start/exceptions.md @@ -16,48 +16,29 @@ fn SysTick() { 除了 `exception` 属性,异常处理函数看起来和普通函数一样,但是有一个很大的不同: `exception` 处理函数*不能*被软件调用。按照先前的例子,语句 `SysTick();` 将会导致一个编译错误。 - - -This behavior is pretty much intended and it's required to provide a feature: -`static mut` variables declared *inside* `exception` handlers are *safe* to use. +这么做是有目的的,因为异常处理函数被要求具有一个特性: 在异常处理函数中被声明`static mut`的变量能被安全(safe)地使用。 ``` rust,ignore #[exception] fn SysTick() { static mut COUNT: u32 = 0; - // `COUNT` has transformed to type `&mut u32` and it's safe to use + // `COUNT` 被转换到了 `&mut u32` 类型且它用起来是安全的 *COUNT += 1; } ``` -就像我们知道的那样,在一个函数里使用`static mut`变量 -As you may know, using `static mut` variables in a function makes it -[*non-reentrant*](https://en.wikipedia.org/wiki/Reentrancy_(computing)). It's undefined behavior to call a non-reentrant function, -directly or indirectly, from more than one exception / interrupt handler or from -`main` and one or more exception / interrupt handlers. +就像你可能已经知道的那样,在一个函数里使用`static mut`变量,会让函数变成[*非可重入函数(non-reentrancy)*](https://en.wikipedia.org/wiki/Reentrancy_(computing))。从多个异常/中断处理函数,或者从`main`函数同多个异常/中断处理函数中直接或者间接地调用一个非可重入(non-reentrancy)函数是未定义的行为。 -Safe Rust must never result in undefined behavior so non-reentrant functions -must be marked as `unsafe`. Yet I just told that `exception` handlers can safely -use `static mut` variables. How is this possible? This is possible because -`exception` handlers can *not* be called by software thus reentrancy is not -possible. +安全的Rust必须从不会造成未定义的行为,所以非可重入函数必须被标记为 `unsafe`。然而,我刚说了`exception`处理函数能安全地使用`static mut`变量。这怎么可能?因为`exception`处理函数*不*能被软件调用因此重入(reentrancy)不会发生,所以这才变得可能。 -> Note that the `exception` attribute transforms definitions of static variables -> inside the function by wrapping them into `unsafe` blocks and providing us -> with new appropriate variables of type `&mut` of the same name. -> Thus we can derefence the reference via `*` to access the values of the variables without -> needing to wrap them in an `unsafe` block. +> 注意,`exception`属性,通过将静态变量封装进`unsafe`块中,转换了函数中静态变量的定义,为我们提供了同个名字的,类型为 `&mut` 的,新的合适的变量。因此我们可以通过 `*` 解引用访问变量的值而不需要将它们打包进一个 `unsafe` 块中。 ## 一个复杂的例子 -Here's an example that uses the system timer to raise a `SysTick` exception -roughly every second. The `SysTick` exception handler keeps track of how many -times it has been called in the `COUNT` variable and then prints the value of -`COUNT` to the host console using semihosting. +这里有个例子,使用系统计时器大概每秒都会抛出一个 `SysTick` 异常。异常处理函数使用 `COUNT` 变量追踪它自己被调用了多少次,然后使用半主机模式(semihosting)打印 `COUNT` 的值到主机控制台上。 -> **NOTE**: You can run this example on any Cortex-M device; you can also run it -> on QEMU +> **注意**: 你能在任何Cortex-M设备上运行这个例子;你也能在QEMU运行它。 ```rust,ignore #![deny(unsafe_code)] @@ -80,9 +61,9 @@ fn main() -> ! { let p = cortex_m::Peripherals::take().unwrap(); let mut syst = p.SYST; - // configures the system timer to trigger a SysTick exception every second + // 配置系统的计时器每秒去触发一个SysTick异常 syst.set_clock_source(SystClkSource::Core); - // this is configured for the LM3S6965 which has a default CPU clock of 12 MHz + // 这是关于LM3S6965的配置,其有一个12MHz的默认CPU时钟 syst.set_reload(12_000_000); syst.clear_current(); syst.enable_counter(); @@ -98,7 +79,7 @@ fn SysTick() { *COUNT += 1; - // Lazy initialization + // 惰性初始化(Lazy initialization) if STDOUT.is_none() { *STDOUT = hio::hstdout().ok(); } @@ -107,10 +88,10 @@ fn SysTick() { write!(hstdout, "{}", *COUNT).ok(); } - // IMPORTANT omit this `if` block if running on real hardware or your - // debugger will end in an inconsistent state + // 重要信息 如果运行在真正的硬件上,去掉这个 `if` 块, + // 否则你的调试器将会以一种不一致的状态结束 if *COUNT == 9 { - // This will terminate the QEMU process + // 这将终结QEMU进程 debug::exit(debug::EXIT_SUCCESS); } } @@ -134,15 +115,11 @@ $ cargo run --release 123456789 ``` -If you run this on the Discovery board you'll see the output on the OpenOCD -console. Also, the program will *not* stop when the count reaches 9. +如果你在Discovery开发板上运行这个例子,你将会在OpenOCD控制太上看到输出。还有,当计数到达9的时候,程序将 *不会* 停止。 -## The default exception handler +## 默认异常处理函数 -What the `exception` attribute actually does is *override* the default exception -handler for a specific exception. If you don't override the handler for a -particular exception it will be handled by the `DefaultHandler` function, which -defaults to: +`exception` 属性真正做的是,*覆盖* 了一个特定异常的默认异常处理函数。如果你不覆盖一个特定异常的处理函数,它将会被 `DefaultHandler` 函数处理,其默认的是: ``` rust,ignore fn DefaultHandler() { @@ -150,43 +127,28 @@ fn DefaultHandler() { } ``` -This function is provided by the `cortex-m-rt` crate and marked as -`#[no_mangle]` so you can put a breakpoint on "DefaultHandler" and catch -*unhandled* exceptions. +这个函数是 `cortex-m-rt` crate提供的,且被标记为 `#[no_mangle]` 因此你能在 "DefaultHandler" 上放置一个断点和捕获 *unhandled* 异常。 -It's possible to override this `DefaultHandler` using the `exception` attribute: +可以使用 `exception` 属性覆盖这个 `DefaultHandler`: ``` rust,ignore #[exception] fn DefaultHandler(irqn: i16) { - // custom default handler + // 自定义默认处理函数 } ``` -The `irqn` argument indicates which exception is being serviced. A negative -value indicates that a Cortex-M exception is being serviced; and zero or a -positive value indicate that a device specific exception, AKA interrupt, is -being serviced. +`irqn` 参数指出了被服务的是哪个异常。一个负数值指出了被服务的是一个Cortex-M异常;0或者一个正数值指出了被服务的是一个设备特定的异常,也就是中断。 -## The hard fault handler +## 硬错误(Hard Fault)处理函数 -The `HardFault` exception is a bit special. This exception is fired when the -program enters an invalid state so its handler can *not* return as that could -result in undefined behavior. Also, the runtime crate does a bit of work before -the user defined `HardFault` handler is invoked to improve debuggability. +`HardFault`异常有点特别。当程序进入一个无效的状态,这个异常被触发,因此它的处理函数 *不能* 返回,因为这么做可能导致一个未定义的行为。在用户定义的 `HardFault` 处理函数被调用之前,运行时crate还做了一些工作去提供可调试性。 -The result is that the `HardFault` handler must have the following signature: -`fn(&ExceptionFrame) -> !`. The argument of the handler is a pointer to -registers that were pushed into the stack by the exception. These registers are -a snapshot of the processor state at the moment the exception was triggered and -are useful to diagnose a hard fault. +结果是,`HardFault`处理函数必须有下列的签名: `fn(&ExceptionFrame) -> !` 。处理函数的参数是一个指针,它指向被异常推入栈中的寄存器。这些寄存器是异常被触发那刻,处理器状态的一个记录,能被用来分析一个硬错误。 -Here's an example that performs an illegal operation: a read to a nonexistent -memory location. +这里有个执行一个不合法操作的例子: 读取一个不存在的存储位置。 -> **NOTE**: This program won't work, i.e. it won't crash, on QEMU because -> `qemu-system-arm -machine lm3s6965evb` doesn't check memory loads and will -> happily return `0 `on reads to invalid memory. +> **注意**: 这个程序在QEMU上将不会工作,i.e. 它将不会崩溃,因为 `qemu-system-arm -machine lm3s6965evb` 不检查存储加载,且读取无效存储时将会开心地返回 `0`。 ```rust,ignore #![no_main] @@ -202,7 +164,7 @@ use cortex_m_semihosting::hio; #[entry] fn main() -> ! { - // read a nonexistent memory location + // 读取一个无效的存储位置 unsafe { ptr::read_volatile(0x3FFF_FFFE as *const u32); } @@ -220,8 +182,7 @@ fn HardFault(ef: &ExceptionFrame) -> ! { } ``` -The `HardFault` handler prints the `ExceptionFrame` value. If you run this -you'll see something like this on the OpenOCD console. +`HardFault`处理函数打印了`ExceptionFrame`值。如果你运行这个,你将会看到下面的东西打印到OpenOCD控制台上。 ``` text $ openocd @@ -238,11 +199,9 @@ ExceptionFrame { } ``` -The `pc` value is the value of the Program Counter at the time of the exception -and it points to the instruction that triggered the exception. - -If you look at the disassembly of the program: +`pc`值是异常时,程序计数器(Program Counter)的值,它指向触发了异常的指令。 +如果你看向程序的反汇编: ``` text $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex @@ -254,7 +213,4 @@ ResetTrampoline: 800094c: b #-0x4 ``` -You can lookup the value of the program counter `0x0800094a` in the dissassembly. -You'll see that a load operation (`ldr r0, [r0]` ) caused the exception. -The `r0` field of `ExceptionFrame` will tell you the value of register `r0` -was `0x3fff_fffe` at that time. +你可以在反汇编中搜索程序计数器`0x0800094a`的值。你将会看到一个读取操作(`ldr r0, [r0]`)导致了异常。`ExceptionFrame`的`r0`字段将告诉你,那时寄存器`r0`的值是`0x3fff_fffe` 。 From dc11a048c437f73b79aab17dd4786c65e3f861ce Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 3 Jun 2022 10:01:24 +0800 Subject: [PATCH 042/137] minor --- src/start/interrupts.md | 52 ++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/src/start/interrupts.md b/src/start/interrupts.md index 10dd4349..b3154137 100644 --- a/src/start/interrupts.md +++ b/src/start/interrupts.md @@ -1,62 +1,44 @@ # 中断 -虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎一样,且它们也能被同一个中断控制器处理。 +虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎一样,且它们也能被同一个中断控制器处理。然而异常是由Cortex-M微架构定义的,中断在命名和功能上总是供应商(经常甚至是芯片)特定的实现。 -Interrupts differ from exceptions in a variety of ways but their operation and -use is largely similar and they are also handled by the same interrupt -controller. Whereas exceptions are defined by the Cortex-M architecture, -interrupts are always vendor (and often even chip) specific implementations, -both in naming and functionality. +中断提供了很多灵活性,当尝试用一种高级的方法使用它们时这种灵活性需要被解释。我们将不会在这本书里涵盖那些使用,然而把下面的东西记在心里是个不错的想法: +* 中断有可以编程的优先级,其决定了它们的处理函数的执行顺序。 +* 中断能嵌套且抢占,i.e. 一个中断处理函数的执行可以被其它更高优先级的中断中断。 +* 通常导致中断被触发的原因需要被清除,避免无限地再次进入中断处理函数。 -Interrupts do allow for a lot of flexibility which needs to be accounted for -when attempting to use them in an advanced way. We will not cover those uses in -this book, however it is a good idea to keep the following in mind: +在运行时的常规初始化步骤始终相同: +* 设置外设在遇到想要的时候产生中断请求 +* 在中断控制器中设置需要的中断处理函数的优先级 +* 在中断控制器中使能中断处理函数 -* Interrupts have programmable priorities which determine their handlers' execution order -* Interrupts can nest and preempt, i.e. execution of an interrupt handler might be interrupted by another higher-priority interrupt -* In general the reason causing the interrupt to trigger needs to be cleared to prevent re-entering the interrupt handler endlessly - -The general initialization steps at runtime are always the same: -* Setup the peripheral(s) to generate interrupts requests at the desired occasions -* Set the desired priority of the interrupt handler in the interrupt controller -* Enable the interrupt handler in the interrupt controller - -Similarly to exceptions, the `cortex-m-rt` crate provides an [`interrupt`] -attribute to declare interrupt handlers. The available interrupts (and -their position in the interrupt handler table) are usually automatically -generated via `svd2rust` from a SVD description. +与异常相似,`cortex-m-rt` crate提供了一个[`interrupt`]属性去声明中断处理函数。可用的中断(及它们在中断向量表中的位置)通常由`svd2rust`从一个SVD描述文件自动地生成。 [`interrupt`]: https://docs.rs/cortex-m-rt-macros/0.1.5/cortex_m_rt_macros/attr.interrupt.html ``` rust,ignore -// Interrupt handler for the Timer2 interrupt +// Timer2中断的中断处理函数 #[interrupt] fn TIM2() { // .. - // Clear reason for the generated interrupt request + // 清除生成中断请求的原因 } ``` -Interrupt handlers look like plain functions (except for the lack of arguments) -similar to exception handlers. However they can not be called directly by other -parts of the firmware due to the special calling conventions. It is however -possible to generate interrupt requests in software to trigger a diversion to -the interrupt handler. +中断处理函数和异常处理函数一样看起来像是普通的函数(除了没有入参)。然而由于特殊的调用约定,它不能被固件的其它部分直接调用。但是,可以在软件中生成中断请求去触发一个转移,进入中断处理函数。 -Similar to exception handlers it is also possible to declare `static mut` -variables inside the interrupt handlers for *safe* state keeping. +与异常处理函数一样,它也能在中断处理函数中声明`static mut`变量且保持 *safe* 状态。 ``` rust,ignore #[interrupt] fn TIM2() { static mut COUNT: u32 = 0; - // `COUNT` has type `&mut u32` and it's safe to use + // `COUNT` 的类型是 `&mut u32` 且它用起来安全 *COUNT += 1; } ``` -For a more detailed description about the mechanisms demonstrated here please -refer to the [exceptions section]. +关于这里说的机制的更多细节描述,请参考[异常章节]。 -[exceptions section]: ./exceptions.md +[异常章节]: ./exceptions.md From f50fe249e430eae158e366a682fe9ee5220a4fb8 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 3 Jun 2022 19:29:46 +0800 Subject: [PATCH 043/137] minor --- src/SUMMARY.md | 2 +- src/peripherals/index.md | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index fd9f2a73..ba5507fb 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -28,7 +28,7 @@ more information and coordination - [中断](./start/interrupts.md) - [IO](./start/io.md) - [外设](./peripherals/index.md) - - [第一次尝试Rust](./peripherals/a-first-attempt.md) + - [首次尝试](./peripherals/a-first-attempt.md) - [借用检查器](./peripherals/borrowck.md) - [单例](./peripherals/singletons.md) - [Static Guarantees](./static-guarantees/index.md) diff --git a/src/peripherals/index.md b/src/peripherals/index.md index 9662617b..c52a5d4b 100644 --- a/src/peripherals/index.md +++ b/src/peripherals/index.md @@ -2,23 +2,24 @@ ## 什么是外设? -Most Microcontrollers have more than just a CPU, RAM, or Flash Memory - they contain sections of silicon which are used for interacting with systems outside of the microcontroller, as well as directly and indirectly interacting with their surroundings in the world via sensors, motor controllers, or human interfaces such as a display or keyboard. These components are collectively known as Peripherals. +大多数微处理器不仅有一个CPU,RAM,或者Flash存储器 - 它们包含硅片被用来与微处理器的外部系统交互的部分,通过传感器,电机控制器,或者人机接口比如一个显示器或者键盘直接和间接地与它们周围的世界交互。这些组件统称为外设。 -These peripherals are useful because they allow a developer to offload processing to them, avoiding having to handle everything in software. Similar to how a desktop developer would offload graphics processing to a video card, embedded developers can offload some tasks to peripherals allowing the CPU to spend its time doing something else important, or doing nothing in order to save power. +这些外设很有用因为它们允许一个开发者将处理工作给它们来做,避免必须在软件中处理每件事。就像一个桌面开发者如何将图形处理工作让给一个显卡那样,嵌入式开发者能将一些任务让给外设去做,让CPU可以把时间放在做其它更重要的事上,或者为了省电啥事也不做。 -If you look at the main circuit board in an old-fashioned home computer from the 1970s or 1980s (and actually, the desktop PCs of yesterday are not so far removed from the embedded systems of today) you would expect to see: +如果你看向从1970s或者1980s的旧型号的家庭电脑的主板(其实,昨日的桌面PCs与今日的嵌入式系统没太大区别),你将看到: -* A processor -* A RAM chip -* A ROM chip -* An I/O controller +* 一个处理器 +* 一个RAM芯片 +* 一个ROM芯片 +* 一个I/O控制器 -The RAM chip, ROM chip and I/O controller (the peripheral in this system) would be joined to the processor through a series of parallel traces known as a 'bus'. This bus carries address information, which selects which device on the bus the processor wishes to communicate with, and a data bus which carries the actual data. In our embedded microcontrollers, the same principles apply - it's just that everything is packed on to a single piece of silicon. +RAM芯片,ROM芯片和I/O控制器(这个系统中的外设)将会通过一系列并行的迹(traces)又被称为一个"总线"被加进处理器中。这个总线搬运地址信息,其用来选择处理器希望跟总线上哪个设备通信,还有一个用来搬运实际数据的数据总线。在我们的嵌入式微控制器中,应用了相同的概念 - 只是所有的东西被打包到一片硅片上。 -However, unlike graphics cards, which typically have a Software API like Vulkan, Metal, or OpenGL, peripherals are exposed to our Microcontroller with a hardware interface, which is mapped to a chunk of the memory. +然而,不像显卡,显卡通常有像是Vulkan,Metal,或者OpenGL这样的一个软件API。外设暴露给微控制器的是一个硬件接口,其被映射到一块存储区域。 -## Linear and Real Memory Space +## 线性的真实存储空间 +在一个微控制器上,往一些任意别的地址写一些数据,比如 `0x4000_0000` 或者 `0x0000_0000`, On a microcontroller, writing some data to some other arbitrary address, such as `0x4000_0000` or `0x0000_0000`, may also be a completely valid action. On a desktop system, access to memory is tightly controlled by the MMU, or Memory Management Unit. This component has two major responsibilities: enforcing access permission to sections of memory (preventing one process from reading or modifying the memory of another process); and re-mapping segments of the physical memory to virtual memory ranges used in software. Microcontrollers do not typically have an MMU, and instead only use real physical addresses in software. From 5b76e736a02a35ad12f5c008f4d7e8ca6d99660d Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 3 Jun 2022 20:37:20 +0800 Subject: [PATCH 044/137] minor --- src/peripherals/index.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/peripherals/index.md b/src/peripherals/index.md index c52a5d4b..3bd71a12 100644 --- a/src/peripherals/index.md +++ b/src/peripherals/index.md @@ -19,27 +19,26 @@ RAM芯片,ROM芯片和I/O控制器(这个系统中的外设)将会通过一系 ## 线性的真实存储空间 -在一个微控制器上,往一些任意别的地址写一些数据,比如 `0x4000_0000` 或者 `0x0000_0000`, -On a microcontroller, writing some data to some other arbitrary address, such as `0x4000_0000` or `0x0000_0000`, may also be a completely valid action. +在一个微控制器上,往一些任意别的地址写一些数据,比如 `0x4000_0000` 或者 `0x0000_0000`,可能也是一个完全有效的动作。 -On a desktop system, access to memory is tightly controlled by the MMU, or Memory Management Unit. This component has two major responsibilities: enforcing access permission to sections of memory (preventing one process from reading or modifying the memory of another process); and re-mapping segments of the physical memory to virtual memory ranges used in software. Microcontrollers do not typically have an MMU, and instead only use real physical addresses in software. +在一个桌面系统上,访问内存被MMU,或者内存管理单元紧紧地控制着。这个组件有两个主要责任: 对部分内存加入访问权限(防止一个进程读取或者修改另一个进程的内存);重映射物理内存的段到软件中使用的虚拟内存范围上。微控制器通常没有一个MMU,反而在软件中只使用真实的物理地址。 -Although 32 bit microcontrollers have a real and linear address space from `0x0000_0000`, and `0xFFFF_FFFF`, they generally only use a few hundred kilobytes of that range for actual memory. This leaves a significant amount of address space remaining. In earlier chapters, we were talking about RAM being located at address `0x2000_0000`. If our RAM was 64 KiB long (i.e. with a maximum address of 0xFFFF) then addresses `0x2000_0000` to `0x2000_FFFF` would correspond to our RAM. When we write to a variable which lives at address `0x2000_1234`, what happens internally is that some logic detects the upper portion of the address (0x2000 in this example) and then activates the RAM so that it can act upon the lower portion of the address (0x1234 in this case). On a Cortex-M we also have our Flash ROM mapped in at address `0x0000_0000` up to, say, address `0x0007_FFFF` (if we have a 512 KiB Flash ROM). Rather than ignore all remaining space between these two regions, Microcontroller designers instead mapped the interface for peripherals in certain memory locations. This ends up looking something like this: +虽然32位微控制器有一个从`0x0000_0000`到`0xFFFF_FFFF`的真实的线性的地址空间,但是它们通常只使用几百KiB的实际内存。有相当大部分的地址空间遗留。在早期的章节中,我们说到RAM被放置在地址`0x2000_0000`处。如果我们的RAM是64KiB大小(i.e. 最大地址为0xFFFF),那么地址 `0x2000_0000`到`0x2000_FFFF`与我们的RAM有关。当我们写入一个位于地址`0x2000_1234`的变量时,内部发生的是,一些逻辑发现了地址的上部(这个例子里是0x2000),然后激活RAM,因此它能对地址的下部(这个例子里是0x1234)起作用。在一个Cortex-M上,我们也把我们的Flash ROM映射进地址 `0x000_0000` 到地址 `0x0007_FFFF` 上 (如果我们有一个512KiB Flash ROM)。微控制器设计者没有忽略这两个区域间的所有剩余空间,反而将外设的接口映射到某些地址上。最后看起来像这样: ![](../assets/nrf52-memory-map.png) [Nordic nRF52832 Datasheet (pdf)] -## Memory Mapped Peripherals +## 存储映射的外设 -Interaction with these peripherals is simple at a first glance - write the right data to the correct address. For example, sending a 32 bit word over a serial port could be as direct as writing that 32 bit word to a certain memory address. The Serial Port Peripheral would then take over and send out the data automatically. +乍一看,与这些外设交互很简单 - 将正确的数据写入正确的地址。比如,在一个串行端口上发送一个32位字(32 bit word),可以像把那个32位字写入某些存储地址一样直接。串行端口外设然后能自动获取和发出数据。 -Configuration of these peripherals works similarly. Instead of calling a function to configure a peripheral, a chunk of memory is exposed which serves as the hardware API. Write `0x8000_0000` to a SPI Frequency Configuration Register, and the SPI port will send data at 8 Megabits per second. Write `0x0200_0000` to the same address, and the SPI port will send data at 125 Kilobits per second. These configuration registers look a little bit like this: +这些外设的配置工作相似。不是调用一个函数去配置一个外设,而是暴露一块地址空间作为硬件API。向一个SPI频率控制寄存器写入 `0x8000_0000`,SPI端口将会按照每秒8MB的速度发送数据。像同个地址写入 `0x0200_0000`,SPI端口将会按照每秒125KiB的速度发送数据。这些配置寄存器看起来有点像这个: ![](../assets/nrf52-spi-frequency-register.png) [Nordic nRF52832 Datasheet (pdf)] -This interface is how interactions with the hardware are made, no matter what language is used, whether that language is Assembly, C, or Rust. +这个接口是关于如何与硬件交互的,其与被使用的语言无关,无论这个语言是汇编,C,或者Rust。 [Nordic nRF52832 Datasheet (pdf)]: http://infocenter.nordicsemi.com/pdf/nRF52832_PS_v1.1.pdf From a10f6fa6577c04c804c3718ec8aefafc0e123e83 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 6 Jun 2022 08:43:46 +0800 Subject: [PATCH 045/137] minor --- src/peripherals/a-first-attempt.md | 34 ++++++++++++++---------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 59b65608..d6b59034 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -1,10 +1,10 @@ -# A First Attempt +# 首次尝试 -## The Registers +## 寄存器 -Let's look at the 'SysTick' peripheral - a simple timer which comes with every Cortex-M processor core. Typically you'll be looking these up in the chip manufacturer's data sheet or *Technical Reference Manual*, but this example is common to all ARM Cortex-M cores, let's look in the [ARM reference manual]. We see there are four registers: +让我们看向 'SysTick' 外设 - 一个简单的计时器,其在每个Cortex-M处理器内核中都有。通常你能在芯片制造商的数据手册或者*技术参考手册*中看到它们,但是这个例子对所有ARM Cortex-M核心都是通用的,让我们看下[ARM参考手册]。我们能看到这里有四个寄存器: -[ARM reference manual]: http://infocenter.arm.com/help/topic/com.arm.doc.dui0553a/Babieigh.html +[ARM参考手册]: http://infocenter.arm.com/help/topic/com.arm.doc.dui0553a/Babieigh.html | Offset | Name | Description | Width | |--------|-------------|-----------------------------|--------| @@ -13,9 +13,9 @@ Let's look at the 'SysTick' peripheral - a simple timer which comes with every C | 0x08 | SYST_CVR | Current Value Register | 32 bits| | 0x0C | SYST_CALIB | Calibration Value Register | 32 bits| -## The C Approach +## C语言风格的方法(The C Approach) -In Rust, we can represent a collection of registers in exactly the same way as we do in C - with a `struct`. +在Rust中,我们可以像我们在C语言中做的那样,用一个 `struct` 表征一组寄存器。 ```rust,ignore #[repr(C)] @@ -26,32 +26,29 @@ struct SysTick { pub calib: u32, } ``` - -The qualifier `#[repr(C)]` tells the Rust compiler to lay this structure out like a C compiler would. That's very important, as Rust allows structure fields to be re-ordered, while C does not. You can imagine the debugging we'd have to do if these fields were silently re-arranged by the compiler! With this qualifier in place, we have our four 32-bit fields which correspond to the table above. But of course, this `struct` is of no use by itself - we need a variable. +限定符 `#[repr(C)]` 告诉Rust编译器像C编译器一样去布局这个结构体。那是非常重要的,因为Rust允许结构体字段被重新排序,而C语言不允许。你可以想象下如果这些字段被编译器悄悄地重新排序,我们将不得不进行的调试!有了这个限定符,我们就有了与上表对应的四个32位的字段。但当然,这个 `struct` 本身没什么用处 - 我们需要一个变量。 ```rust,ignore let systick = 0xE000_E010 as *mut SysTick; let time = unsafe { (*systick).cvr }; ``` -## Volatile Accesses +## 易变的访问(Volatile Accesses) -Now, there are a couple of problems with the approach above. +现在,上面的方法有一堆问题。 -1. We have to use unsafe every time we want to access our Peripheral. -2. We've got no way of specifying which registers are read-only or read-write. -3. Any piece of code anywhere in your program could access the hardware - through this structure. -4. Most importantly, it doesn't actually work... +1. 每次我们想要访问我们的外设,我们不得不使用unsafe 。 +2. 我们无法指定哪个寄存器是只读的或者读写的。 +3. 你程序中任何地方的任何一段代码都可以通过这个结构体访问硬件。 +4. 最重要的是,实际上它并不能工作。 -Now, the problem is that compilers are clever. If you make two writes to the same piece of RAM, one after the other, the compiler can notice this and just skip the first write entirely. In C, we can mark variables as `volatile` to ensure that every read or write occurs as intended. In Rust, we instead mark the *accesses* as volatile, not the variable. +现在,问题是编译器很聪明。如果你往RAM同个地方写两次,一个接着一个,编译器会注意到这个且完全跳过第一个写入操作。在C语言中,我们能标记变量为`volatile`去确保每个读或写操作按预期发生。在Rust中,我们将*访问* 标记为易变的(volatile),而不是变量。 ```rust,ignore let systick = unsafe { &mut *(0xE000_E010 as *mut SysTick) }; let time = unsafe { core::ptr::read_volatile(&mut systick.cvr) }; ``` - -So, we've fixed one of our four problems, but now we have even more `unsafe` code! Fortunately, there's a third party crate which can help - [`volatile_register`]. +因此,我们已经修复了我们四个问题中的一个,但是现在我们有了更多的 `unsafe` 代码!幸运的是,有个第三方的crate可以帮助到我们 - [`volatile_register`] [`volatile_register`]: https://crates.io/crates/volatile_register @@ -76,6 +73,7 @@ fn get_time() -> u32 { } ``` +现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件 Now, the volatile accesses are performed automatically through the `read` and `write` methods. It's still `unsafe` to perform writes, but to be fair, hardware is a bunch of mutable state and there's no way for the compiler to know whether these writes are actually safe, so this is a good default position. ## The Rusty Wrapper From bc39bf9ff6e19af875249d880f7bfebf64944402 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 6 Jun 2022 09:52:33 +0800 Subject: [PATCH 046/137] minor --- src/peripherals/a-first-attempt.md | 13 ++++++------- src/peripherals/borrowck.md | 10 ++++------ src/peripherals/singletons.md | 16 ++++++++-------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index d6b59034..46ea02d5 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -73,14 +73,13 @@ fn get_time() -> u32 { } ``` -现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件 -Now, the volatile accesses are performed automatically through the `read` and `write` methods. It's still `unsafe` to perform writes, but to be fair, hardware is a bunch of mutable state and there's no way for the compiler to know whether these writes are actually safe, so this is a good default position. +现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件有一堆可替换的状态,对于编译器来说没有方法去知道是否这些写操作是真正安全的,因此默认就这样是个不错的选择。 -## The Rusty Wrapper +## Rusty封装 -We need to wrap this `struct` up into a higher-layer API that is safe for our users to call. As the driver author, we manually verify the unsafe code is correct, and then present a safe API for our users so they don't have to worry about it (provided they trust us to get it right!). +我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于我们用户来说,可以安全地被调用。作为驱动的作者,我们手动地验证不安全的代码是否正确,然后为我们的用户提供一个安全的API,因此他们不必担心它(让他们相信我们做对了!)。 -One example might be: +一个可能的例子是: ```rust,ignore use volatile_register::{RW, RO}; @@ -120,7 +119,7 @@ pub fn example_usage() -> String { } ``` -Now, the problem with this approach is that the following code is perfectly acceptable to the compiler: +现在,这种方法带来的问题是下列的代码完全可以被编译器接受: ```rust,ignore fn thread1() { @@ -134,4 +133,4 @@ fn thread2() { } ``` -Our `&mut self` argument to the `set_reload` function checks that there are no other references to *that* particular `SystemTimer` struct, but they don't stop the user creating a second `SystemTimer` which points to the exact same peripheral! Code written in this fashion will work if the author is diligent enough to spot all of these 'duplicate' driver instances, but once the code is spread out over multiple modules, drivers, developers, and days, it gets easier and easier to make these kinds of mistakes. +虽然 `set_reload` 函数的 `&mut self` 参数保证了对某个`SystemTimer`结构体的引用只有一个,但是他们不能阻止用户去创造第二个`SystemTimer`,其指向同个外设!如果作者足够尽力,能发现所有这些'重复的'驱动实例,按这种方式写的代码将可以工作,但是一旦代码被散播几天,散播到多个模块,驱动,开发者,它会越来越容易犯此类错误。 diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index af42a3d2..16062409 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -1,21 +1,19 @@ -## 可变的全局状态 +## 可替换的全局状态 -不幸地是,硬件本质上是个可变的全局状态,对于Rust开发者来说可能感到很害怕。硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 +不幸地是,硬件本质上是个可替换的全局状态,对于Rust开发者来说可能感到很害怕。硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 ## 我们应该遵循什么规则? 我们如何才能做到可靠地与这些外设交互? -1. 总是使用 `volatile` 方法去读或者写外设存储器。因为它可以随时改变。(译者注:对于存在多级缓存的MCU,这方法更为重要) +1. 总是使用 `volatile` 方法去读或者写外设存储器。因为它可以随时改变。 2. 在软件中,我们应该能共享任何数量的 对这些外设的只读访问 3. 如果一些软件对一个外设应该可以读写访问,它应该保有对那个外设的唯一引用。 -## The Borrow Checker ## 借用检查器 这些规则最后两个听起来与借用检查器已经做的事相似。 思考一下,我们是否可以分发这些外设的所有权,或者引用它们? -我们当然可以,但是对于借用检查器,每个外设我们都需要一个实例。 -Well, we can, but for the Borrow Checker, we need to have exactly one instance of each peripheral, so Rust can handle this correctly. Well, luckily in the hardware, there is only one instance of any given peripheral, but how can we expose that in the structure of our code? +我们当然可以,但是对于借用检查器,每个外设我们都只需要一个实例,因此Rust可以正确地处理这个实例。在硬件中幸运的是,任何给定的外设,只有一个实例,但是我们该如何暴露它在我们代码的结构体中? diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 840a9866..96024899 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -1,15 +1,15 @@ -# Singletons +# 单例 -> In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. +> 在软件工程中,单例模式是一个软件设计模式,其限制了一个类到一个对象的实例化。 > > *Wikipedia: [Singleton Pattern]* [Singleton Pattern]: https://en.wikipedia.org/wiki/Singleton_pattern -## But why can't we just use global variable(s)? +## 但是为什么我们不能只是使用全局变量呢? -We could make everything a public static, like this +可以像这样,我们可以使每个东西都变成公共静态的(public static): ```rust,ignore static mut THE_SERIAL_PORT: SerialPort = SerialPort; @@ -21,11 +21,11 @@ fn main() { } ``` -But this has a few problems. It is a mutable global variable, and in Rust, these are always unsafe to interact with. These variables are also visible across your whole program, which means the borrow checker is unable to help you track references and ownership of these variables. +但是这个带来了一些问题。它是一个可替换的全局变量,在Rust,与这些交互总是不安全的。这些变量在你的整个程序间也是可见的,意味着借用检查器不能帮你跟踪引用和这些变量的所有者。 -## How do we do this in Rust? +## 我们在Rust中要怎么做? -Instead of just making our peripheral a global variable, we might instead decide to make a global variable, in this case called `PERIPHERALS`, which contains an `Option` for each of our peripherals. +与其只是让我们的外设变成一个全局变量,我们不如决定使用一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,我们的每个外设其包含一个`Option`。 ```rust,ignore struct Peripherals { @@ -42,7 +42,7 @@ static mut PERIPHERALS: Peripherals = Peripherals { }; ``` -This structure allows us to obtain a single instance of our peripheral. If we try to call `take_serial()` more than once, our code will panic! +这个结构体允许我们获得我们外设的一个实例。如果我们尝试调用`take_serial()`获得多个实例,我们的代码将会抛出运行时恐慌(panic)! ```rust,ignore fn main() { From df7a0c09da6d9876411521212db8c0a202241a36 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 8 Jun 2022 16:06:38 +0800 Subject: [PATCH 047/137] minor --- src/peripherals/singletons.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 96024899..99cafa60 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -110,6 +110,7 @@ impl SerialPort { } ``` + There are two important factors in play here: * Because we are using a singleton, there is only one way or place to obtain a `SerialPort` structure From 1e22ff4eac1399d8746d9e75517e1fbe740407a8 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 9 Jun 2022 18:13:09 +0800 Subject: [PATCH 048/137] minor --- src/SUMMARY.md | 2 +- src/intro/install/linux.md | 1 + src/peripherals/a-first-attempt.md | 2 +- src/peripherals/borrowck.md | 6 +-- src/peripherals/singletons.md | 49 ++++++++++--------- src/static-guarantees/index.md | 21 +++----- src/static-guarantees/state-machines.md | 4 +- .../typestate-programming.md | 23 ++++----- 8 files changed, 52 insertions(+), 56 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ba5507fb..7add6a41 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -31,7 +31,7 @@ more information and coordination - [首次尝试](./peripherals/a-first-attempt.md) - [借用检查器](./peripherals/borrowck.md) - [单例](./peripherals/singletons.md) -- [Static Guarantees](./static-guarantees/index.md) +- [静态保障(static guarantees)](./static-guarantees/index.md) - [Typestate Programming](./static-guarantees/typestate-programming.md) - [Peripherals as State Machines](./static-guarantees/state-machines.md) - [Design Contracts](./static-guarantees/design-contracts.md) diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index ee8101a0..77a43648 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -18,6 +18,7 @@ + ``` console sudo apt install gdb-multiarch openocd qemu-system-arm ``` diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 46ea02d5..6a3c3295 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -73,7 +73,7 @@ fn get_time() -> u32 { } ``` -现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件有一堆可替换的状态,对于编译器来说没有方法去知道是否这些写操作是真正安全的,因此默认就这样是个不错的选择。 +现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件有一堆可变的状态,对于编译器来说没有方法去知道是否这些写操作是真正安全的,因此默认就这样是个不错的选择。 ## Rusty封装 diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index 16062409..7e4303f7 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -1,6 +1,6 @@ -## 可替换的全局状态 +## 可变的全局状态 -不幸地是,硬件本质上是个可替换的全局状态,对于Rust开发者来说可能感到很害怕。硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 +不幸地是,硬件本质上是个可变的全局状态,对于Rust开发者来说可能感到很害怕。因为硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 ## 我们应该遵循什么规则? @@ -16,4 +16,4 @@ 思考一下,我们是否可以分发这些外设的所有权,或者引用它们? -我们当然可以,但是对于借用检查器,每个外设我们都只需要一个实例,因此Rust可以正确地处理这个实例。在硬件中幸运的是,任何给定的外设,只有一个实例,但是我们该如何暴露它在我们代码的结构体中? +我们当然可以,但是对于借用检查器来说,每个外设我们都只需要一个实例,这样Rust才可以正确地处理这个实例。在硬件中幸运的是,任何给定的外设,只有一个实例,但是我们该如何暴露它在我们代码的结构体中? diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 99cafa60..f1f28057 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -7,7 +7,7 @@ [Singleton Pattern]: https://en.wikipedia.org/wiki/Singleton_pattern -## 但是为什么我们不能只是使用全局变量呢? +## 但是为什么我们不可以使用全局变量呢? 可以像这样,我们可以使每个东西都变成公共静态的(public static): @@ -21,11 +21,11 @@ fn main() { } ``` -但是这个带来了一些问题。它是一个可替换的全局变量,在Rust,与这些交互总是不安全的。这些变量在你的整个程序间也是可见的,意味着借用检查器不能帮你跟踪引用和这些变量的所有者。 +但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是不安全的。这些变量在你的整个程序间也是可见的,意味着借用检查器不能帮你跟踪引用和这些变量的所有者。 ## 我们在Rust中要怎么做? -与其只是让我们的外设变成一个全局变量,我们不如决定使用一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,我们的每个外设其包含一个`Option`。 +与其只是让我们的外设变成一个全局变量,我们不如决定使用一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,这个全局变量对于我们的每个外设,它都有一个与之对应的 `Option` 。 ```rust,ignore struct Peripherals { @@ -47,25 +47,25 @@ static mut PERIPHERALS: Peripherals = Peripherals { ```rust,ignore fn main() { let serial_1 = unsafe { PERIPHERALS.take_serial() }; - // This panics! + // 这里造成运行时恐慌! // let serial_2 = unsafe { PERIPHERALS.take_serial() }; } ``` -Although interacting with this structure is `unsafe`, once we have the `SerialPort` it contained, we no longer need to use `unsafe`, or the `PERIPHERALS` structure at all. +虽然与这个结构体交互是`unsafe`,然而一旦我们获得了它包含的 `SerialPort`,我们将不再需要使用`unsafe`,或者`PERIPHERALS`结构体。 -This has a small runtime overhead because we must wrap the `SerialPort` structure in an option, and we'll need to call `take_serial()` once, however this small up-front cost allows us to leverage the borrow checker throughout the rest of our program. +这个带来了少量的运行时消耗,因为我们必须打包 `SerialPort` 结构体进一个option中,且我们将需要调用一次 `take_serial()`,但是这种少量的前期成本,能使我们在接下来的程序中使用借用检查器(borrow checker) 。 -## Existing library support +## 已存的库支持 -Although we created our own `Peripherals` structure above, it is not necessary to do this for your code. the `cortex_m` crate contains a macro called `singleton!()` that will perform this action for you. +虽然我们在上面创造了我们自己的 `Peripherals` 结构体,但这并不是必须的。`cortex_m` crate 包含一个被叫做 `singleton!()` 的宏,其将为你执行这个任务。 ```rust,ignore #[macro_use(singleton)] extern crate cortex_m; fn main() { - // OK if `main` is executed only once + // OK 如果 `main` 只被执行一次 let x: &'static mut bool = singleton!(: bool = false).unwrap(); } @@ -73,7 +73,7 @@ fn main() { [cortex_m docs](https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html) -Additionally, if you use [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic), the entire process of defining and obtaining these peripherals are abstracted for you, and you are instead handed a `Peripherals` structure that contains a non-`Option` version of all of the items you define. +另外,如果你使用 [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic),获取和定义这些外设的整个过程为你做了抽象,你获得了一个`Peripherals`结构体,其包含一个所有你定义了的项的非 `Option` 的版本。 ```rust,ignore // cortex-m-rtic v0.5.x @@ -92,16 +92,16 @@ const APP: () = { } ``` -## But why? +## 但是为什么? -But how do these Singletons make a noticeable difference in how our Rust code works? +但是这些单例模式是如何使我们的Rust代码在工作方式上产生很大不同的? ```rust,ignore impl SerialPort { const SER_PORT_SPEED_REG: *mut u32 = 0x4000_1000 as _; fn read_speed( - &self // <------ This is really, really important + &self // <------ 这个真的真的很重要 ) -> u32 { unsafe { ptr::read_volatile(Self::SER_PORT_SPEED_REG) @@ -111,30 +111,30 @@ impl SerialPort { ``` -There are two important factors in play here: +这里有两个重要因素: -* Because we are using a singleton, there is only one way or place to obtain a `SerialPort` structure -* To call the `read_speed()` method, we must have ownership or a reference to a `SerialPort` structure +* 因为我们正在使用一个单例模式,所以我们只有一种方法或者地方去获得一个 `SerialPort` 结构体。 +* 为了调用 `read_speed()` 方法,我们必须拥有一个 `SerialPort` 结构体的所有权或者一个引用。 -These two factors put together means that it is only possible to access the hardware if we have appropriately satisfied the borrow checker, meaning that at no point do we have multiple mutable references to the same hardware! +这两个因素放在一起意味着,只有当我们满足了借用检查器的条件时,我们才有可能访问硬件,也意味着我们在任何时候不可能有多个对同一个硬件的可变引用(&mut)! ```rust,ignore fn main() { - // missing reference to `self`! Won't work. + // 缺少对`self`的引用!将不会工作。 // SerialPort::read_speed(); let serial_1 = unsafe { PERIPHERALS.take_serial() }; - // you can only read what you have access to + // 你只能读取你有权访问的内容 let _ = serial_1.read_speed(); } ``` -## Treat your hardware like data +## 像数据一样对待你的硬件 -Additionally, because some references are mutable, and some are immutable, it becomes possible to see whether a function or method could potentially modify the state of the hardware. For example, +另外,因为一些引用是可变的,一些是不可变的,观察一个函数或者方法是否会潜在修改硬件的状态变得可能了。比如, -This is allowed to change hardware settings: +这个函数允许改变硬件的配置: ```rust,ignore fn setup_spi_port( @@ -145,7 +145,7 @@ fn setup_spi_port( } ``` -This isn't: +这个不行: ```rust,ignore fn read_button(gpio: &GpioPin) -> bool { @@ -153,4 +153,5 @@ fn read_button(gpio: &GpioPin) -> bool { } ``` -This allows us to enforce whether code should or should not make changes to hardware at **compile time**, rather than at runtime. As a note, this generally only works across one application, but for bare metal systems, our software will be compiled into a single application, so this is not usually a restriction. +这允许我们强制代码是否应该或者不应该在**编译时**而不是运行时对硬件进行改变,这通常在只有一个应用的情况下起作用,但是对于裸机系统来说,我们的软件将被编译进一个单一应用中,因此它通常是不受限的。 + diff --git a/src/static-guarantees/index.md b/src/static-guarantees/index.md index a5bb8fa3..fc44d30f 100644 --- a/src/static-guarantees/index.md +++ b/src/static-guarantees/index.md @@ -1,23 +1,14 @@ # Static Guarantees +# 静态保障 -Rust's type system prevents data races at compile time (see [`Send`] and -[`Sync`] traits). The type system can also be used to check other properties at -compile time; reducing the need for runtime checks in some cases. +Rust的类型系统可防止编译时的数据竞争(看[`Send`]和[`Sync`]特性(traits))。类型系统也能被用来在编译时检查其它属性;减少某些案例中运行时检查的需要。 [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html -When applied to embedded programs these *static checks* can be used, for -example, to enforce that configuration of I/O interfaces is done properly. For -instance, one can design an API where it is only possible to initialize a serial -interface by first configuring the pins that will be used by the interface. +当应用到嵌入式程序时,这些*静态检查*能被用来,比如,强制I/O接口被恰当地配置了。例如,可以设计一个API来初始化一个串行接口,这个API只能通过先配置接口需要的管脚才能被正确地使用。 -One can also statically check that operations, like setting a pin low, can only -be performed on correctly configured peripherals. For example, trying to change -the output state of a pin configured in floating input mode would raise a -compile error. +也可以静态检查操作是否被执行在正确配置了的外设上,像是拉低一个管脚。例如尝试修改一个被配置成浮空输入模式的管脚的输出状态时,将会出发一个编译时错误。 + +且,像是在前面章节看到的,所有权的概念能被应用到外设上确保只有一个程序的某些部分可以修改一个外设。与将这个外设当做全局可变的状态相比,*访问控制*(assess control)使得软件更容易推理。 -And, as seen in the previous chapter, the concept of ownership can be applied -to peripherals to ensure that only certain parts of a program can modify a -peripheral. This *access control* makes software easier to reason about -compared to the alternative of treating peripherals as global mutable state. diff --git a/src/static-guarantees/state-machines.md b/src/static-guarantees/state-machines.md index 84207911..5ee5af45 100644 --- a/src/static-guarantees/state-machines.md +++ b/src/static-guarantees/state-machines.md @@ -1,4 +1,6 @@ -# Peripherals as State Machines +# 作为状态机的外设 + +一个微控制器的外设可以被想成是一组状态机。比如, The peripherals of a microcontroller can be thought of as set of state machines. For example, the configuration of a simplified [GPIO pin] could be represented as the following tree of states: diff --git a/src/static-guarantees/typestate-programming.md b/src/static-guarantees/typestate-programming.md index 9bd70f40..51748f86 100644 --- a/src/static-guarantees/typestate-programming.md +++ b/src/static-guarantees/typestate-programming.md @@ -1,9 +1,9 @@ -# Typestate Programming +# 类型状态编程(Typestate Programming) -The concept of [typestates] describes the encoding of information about the current state of an object into the type of that object. Although this can sound a little arcane, if you have used the [Builder Pattern] in Rust, you have already started using Typestate Programming! +[typestates]的概念描述了将有关对象当前状态的信息编码为该对象的类型中。虽然这听起来有点神秘,如果你在Rust中使用了[建造者模式],你就已经开始使用类型状态编程了! [typestates]: https://en.wikipedia.org/wiki/Typestate_analysis -[Builder Pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html +[建造者模式]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html ```rust pub mod foo_module { @@ -49,17 +49,18 @@ fn main() { } ``` -In this example, there is no direct way to create a `Foo` object. We must create a `FooBuilder`, and properly initialize it before we can obtain the `Foo` object we want. +在这个例子里,不能直接生成一个`Foo`对象。我们必须创造一个`FooBuilder`,在我们获取我们需要的`Foo`对象之前恰当地初始化它。 -This minimal example encodes two states: +这个最小的例子编码了两个状态: -* `FooBuilder`, which represents an "unconfigured", or "configuration in process" state -* `Foo`, which represents a "configured", or "ready to use" state. +* `FooBuilder`,其表征了一个"没有被配置",或者"正在配置"状态 +* `Foo`,其表征了一个"被配置",或者"可以使用"状态。 -## Strong Types +## 强类型 -Because Rust has a [Strong Type System], there is no easy way to magically create an instance of `Foo`, or to turn a `FooBuilder` into a `Foo` without calling the `into_foo()` method. Additionally, calling the `into_foo()` method consumes the original `FooBuilder` structure, meaning it can not be reused without the creation of a new instance. +因为Rust有一个[强类型系统],没有一个简单的,魔法般地创造一个`Foo`实例或者不用调用`into_foo()`方法把一个`FooBuilder`变成一个`Foo`的方法。另外,调用`into_foo()`方法消费了最初的`FooBuilder`结构体,意味着不创造一个新的实例它就不能被再次使用。 -[Strong Type System]: https://en.wikipedia.org/wiki/Strong_and_weak_typing +[强类型系统]: https://en.wikipedia.org/wiki/Strong_and_weak_typing + +这允许我们去将我们系统的状态表示成类型,把状态转换必须的动作包括进交换两个类型的方法中。通过创造一个 `FooBuilder`,与一个 `Foo` 对象交换,我们已经使用了一个基本状态机。 -This allows us to represent the states of our system as types, and to include the necessary actions for state transitions into the methods that exchange one type for another. By creating a `FooBuilder`, and exchanging it for a `Foo` object, we have walked through the steps of a basic state machine. From eede76c99b1f723bb1ddf77299d9a781a47bc2de Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 10 Jun 2022 09:45:16 +0800 Subject: [PATCH 049/137] minor --- src/SUMMARY.md | 8 +- src/static-guarantees/design-contracts.md | 62 +++++++------ src/static-guarantees/state-machines.md | 93 +++++++++---------- .../zero-cost-abstractions.md | 2 +- 4 files changed, 82 insertions(+), 83 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 7add6a41..b43bbf7a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -32,10 +32,10 @@ more information and coordination - [借用检查器](./peripherals/borrowck.md) - [单例](./peripherals/singletons.md) - [静态保障(static guarantees)](./static-guarantees/index.md) - - [Typestate Programming](./static-guarantees/typestate-programming.md) - - [Peripherals as State Machines](./static-guarantees/state-machines.md) - - [Design Contracts](./static-guarantees/design-contracts.md) - - [Zero Cost Abstractions](./static-guarantees/zero-cost-abstractions.md) + - [类型状态编程](./static-guarantees/typestate-programming.md) + - [作为状态机的外设](./static-guarantees/state-machines.md) + - [设计协约](./static-guarantees/design-contracts.md) + - [零成本抽象](./static-guarantees/zero-cost-abstractions.md) - [可移植性](./portability/index.md) - [并发](./concurrency/index.md) - [Collections](./collections/index.md) diff --git a/src/static-guarantees/design-contracts.md b/src/static-guarantees/design-contracts.md index 56fec45b..568abcdf 100644 --- a/src/static-guarantees/design-contracts.md +++ b/src/static-guarantees/design-contracts.md @@ -1,27 +1,29 @@ -# Design Contracts +# 设计协约 -In our last chapter, we wrote an interface that *didn't* enforce design contracts. Let's take another look at our imaginary GPIO configuration register: +在我们的上个章节中,我们写了一个接口,其没有强制执行设计协约。让我们再看下我们假想的GPIO配置寄存器: -| Name | Bit Number(s) | Value | Meaning | Notes | + +| 名字 | 位数(s) | 值 | 含义 | 注释 | | ---: | ------------: | ----: | ------: | ----: | -| enable | 0 | 0 | disabled | Disables the GPIO | -| | | 1 | enabled | Enables the GPIO | -| direction | 1 | 0 | input | Sets the direction to Input | -| | | 1 | output | Sets the direction to Output | -| input_mode | 2..3 | 00 | hi-z | Sets the input as high resistance | -| | | 01 | pull-low | Input pin is pulled low | -| | | 10 | pull-high | Input pin is pulled high | -| | | 11 | n/a | Invalid state. Do not set | -| output_mode | 4 | 0 | set-low | Output pin is driven low | -| | | 1 | set-high | Output pin is driven high | -| input_status | 5 | x | in-val | 0 if input is < 1.5v, 1 if input >= 1.5v | - -If we instead checked the state before making use of the underlying hardware, enforcing our design contracts at runtime, we might write code that looks like this instead: +| 使能 | 0 | 0 | 关闭 | 关闭GPIO | +| | | 1 | 使能 | 使能GPIO | +| 方向 | 1 | 0 | 输入 | 方向设置成输入 | +| | | 1 | 输出 | 方向设置成输出 | +| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻抗 | +| | | 01 | 下拉 | 下拉输入管脚 | +| | | 10 | 上拉 | 上拉输入管脚 | +| | | 11 | n/a | 无效模式。不要设置 | +| 输出模式 | 4 | 0 | 拉低 | 拉低输出管脚 | +| | | 1 | 拉高 | 拉高输出管脚 | +| 输入状态 | 5 | x | in-val | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 | + + +如果我们在使用底层硬件之前检查状态,在运行时强制遵守我们的设计协约,我们写的代码可能像这一样: ```rust,ignore -/// GPIO interface +/// GPIO接口 struct GpioConfig { - /// GPIO Configuration structure generated by svd2rust + /// 由svd2rust生成的GPIO配制结构体 periph: GPIO_CONFIG, } @@ -34,7 +36,7 @@ impl GpioConfig { pub fn set_direction(&mut self, is_output: bool) -> Result<(), ()> { if self.periph.read().enable().bit_is_clear() { - // Must be enabled to set direction + // 必须被使能配置方向 return Err(()); } @@ -47,12 +49,12 @@ impl GpioConfig { pub fn set_input_mode(&mut self, variant: InputMode) -> Result<(), ()> { if self.periph.read().enable().bit_is_clear() { - // Must be enabled to set input mode + // 必须被使能配置输入模式 return Err(()); } if self.periph.read().direction().bit_is_set() { - // Direction must be input + // 方向必须被设置成输入 return Err(()); } @@ -65,12 +67,12 @@ impl GpioConfig { pub fn set_output_status(&mut self, is_high: bool) -> Result<(), ()> { if self.periph.read().enable().bit_is_clear() { - // Must be enabled to set output status + // 设置输出状态必须被使能 return Err(()); } if self.periph.read().direction().bit_is_clear() { - // Direction must be output + // 方向必须是输出 return Err(()); } @@ -83,12 +85,12 @@ impl GpioConfig { pub fn get_input_status(&self) -> Result { if self.periph.read().enable().bit_is_clear() { - // Must be enabled to get status + // 获取状态必须被使能 return Err(()); } if self.periph.read().direction().bit_is_set() { - // Direction must be input + // 方向必须是输入 return Err(()); } @@ -97,16 +99,16 @@ impl GpioConfig { } ``` -Because we need to enforce the restrictions on the hardware, we end up doing a lot of runtime checking which wastes time and resources, and this code will be much less pleasant for the developer to use. +因为我们不需要强制遵守硬件上的限制,所以我们最后做了很多运行时检查,它浪费了我们很多时间和资源,对于开发者来说,这个代码用起来就没那么愉快了。 -## Type States +## 类型状态(Type states) -But what if instead, we used Rust's type system to enforce the state transition rules? Take this example: +但是,如果我们使用Rust的类型系统去强制状态转换的规则会怎样?看下这个例子: ```rust,ignore -/// GPIO interface +/// GPIO接口 struct GpioConfig { - /// GPIO Configuration structure generated by svd2rust + /// 由svd2rust产生的GPIO配置结构体 periph: GPIO_CONFIG, enabled: ENABLED, direction: DIRECTION, diff --git a/src/static-guarantees/state-machines.md b/src/static-guarantees/state-machines.md index 5ee5af45..63a3d250 100644 --- a/src/static-guarantees/state-machines.md +++ b/src/static-guarantees/state-machines.md @@ -1,64 +1,61 @@ # 作为状态机的外设 -一个微控制器的外设可以被想成是一组状态机。比如, +一个微控制器的外设可以被想成是一组状态机。比如,一个简化的[GPIO管脚]的配置可以被表达成下列的状态树: -The peripherals of a microcontroller can be thought of as set of state machines. For example, the configuration of a simplified [GPIO pin] could be represented as the following tree of states: +[GPIO管脚]: https://en.wikipedia.org/wiki/General-purpose_input/output -[GPIO pin]: https://en.wikipedia.org/wiki/General-purpose_input/output +* 关闭 +* 使能 + * 配置成输出 + * 输出: 高 + * 输出: 低 + * 配置成输入 + * 输入: 高阻抗 + * 输入: 下拉 + * 输入: 上拉 -* Disabled -* Enabled - * Configured as Output - * Output: High - * Output: Low - * Configured as Input - * Input: High Resistance - * Input: Pulled Low - * Input: Pulled High +如果外设开始于`关闭`模式,切换到`输入: 高阻抗`模式,我们必须执行下面的步骤: -If the peripheral starts in the `Disabled` mode, to move to the `Input: High Resistance` mode, we must perform the following steps: +1. 关闭 +2. 使能 +3. 配置成输入 +4. 输入: 高阻抗 -1. Disabled -2. Enabled -3. Configured as Input -4. Input: High Resistance +如果我们想要从`输入: 高阻抗`切换到`输入: 下拉`,我们必须执行下列的步骤: -If we wanted to move from `Input: High Resistance` to `Input: Pulled Low`, we must perform the following steps: +1. 输入: 高阻抗 +2. 输入: 下拉 -1. Input: High Resistance -2. Input: Pulled Low +同样地,如果我们想要把一个GPIO管脚从`输入: 下拉`切换到`输出: 高`,我们必须执行下列的步骤: +1. 输入: 下拉 +2. 配置成输入 +3. 配置成输出 +4. 输出: 高 -Similarly, if we want to move a GPIO pin from configured as `Input: Pulled Low` to `Output: High`, we must perform the following steps: +## 硬件表征(Hardware Representation) -1. Input: Pulled Low -2. Configured as Input -3. Configured as Output -4. Output: High +通常,上面列的状态通过向指定的映射到一个GPIO外设的寄存器写入值来配置。让我们定义一个假想的GPIO配置寄存器来解释它: -## Hardware Representation - -Typically the states listed above are set by writing values to given registers mapped to a GPIO peripheral. Let's define an imaginary GPIO Configuration Register to illustrate this: - -| Name | Bit Number(s) | Value | Meaning | Notes | +| 名字 | 位数(s) | 值 | 含义 | 注释 | | ---: | ------------: | ----: | ------: | ----: | -| enable | 0 | 0 | disabled | Disables the GPIO | -| | | 1 | enabled | Enables the GPIO | -| direction | 1 | 0 | input | Sets the direction to Input | -| | | 1 | output | Sets the direction to Output | -| input_mode | 2..3 | 00 | hi-z | Sets the input as high resistance | -| | | 01 | pull-low | Input pin is pulled low | -| | | 10 | pull-high | Input pin is pulled high | -| | | 11 | n/a | Invalid state. Do not set | -| output_mode | 4 | 0 | set-low | Output pin is driven low | -| | | 1 | set-high | Output pin is driven high | -| input_status | 5 | x | in-val | 0 if input is < 1.5v, 1 if input >= 1.5v | - -We _could_ expose the following structure in Rust to control this GPIO: +| 使能 | 0 | 0 | 关闭 | 关闭GPIO | +| | | 1 | 使能 | 使能GPIO | +| 方向 | 1 | 0 | 输入 | 方向设置成输入 | +| | | 1 | 输出 | 方向设置成输出 | +| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻抗 | +| | | 01 | 下拉 | 下拉输入管脚 | +| | | 10 | 上拉 | 上拉输入管脚 | +| | | 11 | n/a | 无效模式。不要设置 | +| 输出模式 | 4 | 0 | 拉低 | 拉低输出管脚 | +| | | 1 | 拉高 | 拉高输出管脚 | +| 输入状态 | 5 | x | in-val | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 | + +我们 _能_ 在Rust中暴露下列的结构体来控制这个GPIO: ```rust,ignore -/// GPIO interface +/// GPIO接口 struct GpioConfig { - /// GPIO Configuration structure generated by svd2rust + /// 由svd2rust生成的GPIO配置结构体 periph: GPIO_CONFIG, } @@ -93,8 +90,8 @@ impl GpioConfig { } ``` -However, this would allow us to modify certain registers that do not make sense. For example, what happens if we set the `output_mode` field when our GPIO is configured as an input? +然而,这将会允许我们修改某些没有意义的寄存器。比如,如果当我们的GPIO被配置为GPIO时我们设置`output_mode`字段,将会发生什么? -In general, use of this structure would allow us to reach states not defined by our state machine above: e.g. an output that is pulled low, or an input that is set high. For some hardware, this may not matter. On other hardware, it could cause unexpected or undefined behavior! +通常,使用这个结构体将会允许我们访问我们上面的状态机没有定义的状态: e.g. 一个被上拉的输出,或者一个被拉高的输入。对于一些硬件,这并没有关系。在另外一些硬件,这将会导致不可预期或者没有定义的行为! -Although this interface is convenient to write, it doesn't enforce the design contracts set out by our hardware implementation. +虽然这个接口很方便写入,但是它没有强制执行我们的硬件实现所规定的设计协约。 diff --git a/src/static-guarantees/zero-cost-abstractions.md b/src/static-guarantees/zero-cost-abstractions.md index f7defd18..08584eac 100644 --- a/src/static-guarantees/zero-cost-abstractions.md +++ b/src/static-guarantees/zero-cost-abstractions.md @@ -1,4 +1,4 @@ -# Zero Cost Abstractions +# 零成本抽象 Type states are also an excellent example of Zero Cost Abstractions - the ability to move certain behaviors to compile time execution or analysis. These type states contain no actual data, and are instead used as markers. Since they contain no data, they have no actual representation in memory at runtime: From 1763f15e42053c6bf519233b00861896617a6d6a Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 10 Jun 2022 21:19:23 +0800 Subject: [PATCH 050/137] minor --- src/static-guarantees/design-contracts.md | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/static-guarantees/design-contracts.md b/src/static-guarantees/design-contracts.md index 568abcdf..3524c480 100644 --- a/src/static-guarantees/design-contracts.md +++ b/src/static-guarantees/design-contracts.md @@ -115,7 +115,7 @@ struct GpioConfig { mode: MODE, } -// Type states for MODE in GpioConfig +// GpioConfig中MODE的类型状态 struct Disabled; struct Enabled; struct Output; @@ -125,7 +125,7 @@ struct PulledHigh; struct HighZ; struct DontCare; -/// These functions may be used on any GPIO Pin +/// 这些函数可能被用于所有的GPIO管脚 impl GpioConfig { pub fn into_disabled(self) -> GpioConfig { self.periph.modify(|_r, w| w.enable.disabled()); @@ -166,14 +166,14 @@ impl GpioConfig { } } -/// This function may be used on an Output Pin +/// 这个函数可能被用于一个输出管脚 impl GpioConfig { pub fn set_bit(&mut self, set_high: bool) { self.periph.modify(|_r, w| w.output_mode.set_bit(set_high)); } } -/// These methods may be used on any enabled input GPIO +/// 这些方法可能被用于使能一个输入GPIO impl GpioConfig { pub fn bit_is_set(&self) -> bool { self.periph.read().input_status.bit_is_set() @@ -211,46 +211,46 @@ impl GpioConfig { } ``` -Now let's see what the code using this would look like: +现在让我们看下代码如何用这个API: ```rust,ignore /* - * Example 1: Unconfigured to High-Z input + * 案例 1: Unconfigured to High-Z input */ let pin: GpioConfig = get_gpio(); -// Can't do this, pin isn't enabled! +// 不能这么做,pin没有被使能 // pin.into_input_pull_down(); -// Now turn the pin from unconfigured to a high-z input +// 现在从unconfigured to a high-z input打开管脚 let input_pin = pin.into_enabled_input(); -// Read from the pin +// 从管脚读取 let pin_state = input_pin.bit_is_set(); -// Can't do this, input pins don't have this interface! +// 不能这么做,输入管脚没有这个接口 // input_pin.set_bit(true); /* - * Example 2: High-Z input to Pulled Low input + * 案例 2: High-Z 输入到下拉输入 */ let pulled_low = input_pin.into_input_pull_down(); let pin_state = pulled_low.bit_is_set(); /* - * Example 3: Pulled Low input to Output, set high + * 案例 3: 下拉输入到输出, 拉高 */ let output_pin = pulled_low.into_enabled_output(); output_pin.set_bit(true); -// Can't do this, output pins don't have this interface! +// 不能这么做,输出管脚没有这个接口 // output_pin.into_input_pull_down(); ``` -This is definitely a convenient way to store the state of the pin, but why do it this way? Why is this better than storing the state as an `enum` inside of our `GpioConfig` structure? +这绝对是存储管脚状态的便捷方法,但是为什么这么做?为什么这比把状态当成一个`enum`存在我们的`CpioConfig`结构体中更好? -## Compile Time Functional Safety +## 编译时功能安全(Functional Safety) -Because we are enforcing our design constraints entirely at compile time, this incurs no runtime cost. It is impossible to set an output mode when you have a pin in an input mode. Instead, you must walk through the states by converting it to an output pin, and then setting the output mode. Because of this, there is no runtime penalty due to checking the current state before executing a function. +因为我们在编译时完全强制执行我们的设计约定,这造成了没有运行时消耗。当你有一个在输入模式的管脚时,是不可能去设置一个输出模式的。你必须先把它设置成一个输出管脚,然后在设置输出模式。因为在执行一个函数前会检查现在的状态,因此没有运行时消耗。 -Also, because these states are enforced by the type system, there is no longer room for errors by consumers of this interface. If they try to perform an illegal state transition, the code will not compile! +也因为这些状态被类型系统强制执行,因此没有为这个接口的使用者留太多的犯错空间。如果它们尝试执行一个非法的状态转换,代码将不会编译! From ca230853f47476ab39c966ecfc70396f987c5edd Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 10 Jun 2022 21:42:47 +0800 Subject: [PATCH 051/137] minor --- src/static-guarantees/zero-cost-abstractions.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/static-guarantees/zero-cost-abstractions.md b/src/static-guarantees/zero-cost-abstractions.md index 08584eac..7be659c9 100644 --- a/src/static-guarantees/zero-cost-abstractions.md +++ b/src/static-guarantees/zero-cost-abstractions.md @@ -1,6 +1,6 @@ # 零成本抽象 -Type states are also an excellent example of Zero Cost Abstractions - the ability to move certain behaviors to compile time execution or analysis. These type states contain no actual data, and are instead used as markers. Since they contain no data, they have no actual representation in memory at runtime: +类型状态是一个零成本抽象的杰出案例 - 把某些行为移到编译时执行或者分析的能力。这些类型状态不包含真实的数据,只用来作为标记。因为它们不包含数据,在运行时它们在内存中不存在实际的表征。 ```rust,ignore use core::mem::size_of; @@ -11,15 +11,15 @@ let _ = size_of::(); // == 0 let _ = size_of::>(); // == 0 ``` -## Zero Sized Types +## 零大小的类型(Zero Sized Types) ```rust,ignore struct Enabled; ``` -Structures defined like this are called Zero Sized Types, as they contain no actual data. Although these types act "real" at compile time - you can copy them, move them, take references to them, etc., however the optimizer will completely strip them away. +像这样定义的结构体被称为零大小的类型,因为它们不包含实际数据。虽然这些类型在编译时像是"真的"(real) - 你可以拷贝它们,移动它们,引用它们,etc.,然而优化器将会完全跳过它们。 -In this snippet of code: +在这个代码片段里: ```rust,ignore pub fn into_input_high_z(self) -> GpioConfig { @@ -33,10 +33,10 @@ pub fn into_input_high_z(self) -> GpioConfig { } ``` -The GpioConfig we return never exists at runtime. Calling this function will generally boil down to a single assembly instruction - storing a constant register value to a register location. This means that the type state interface we've developed is a zero cost abstraction - it uses no more CPU, RAM, or code space tracking the state of `GpioConfig`, and renders to the same machine code as a direct register access. +我们返回的CpioConfig在运行时并不存在。调用这个函数通常将会总结为一个单一的汇编指令 - 保存一个常量寄存器值进一个寄存器里。这意味着我们开发的类型状态接口是一个零成本抽象 - 它不会用更多的CPU,RAM,或者代码空间去跟踪`GpioConfig`的状态,会渲染成和直接访问寄存器一样的机器码。 -## Nesting +## 嵌套 -In general, these abstractions may be nested as deeply as you would like. As long as all components used are zero sized types, the whole structure will not exist at runtime. +通常,你可能会把这些抽象深深地嵌套起来。一旦所有的被使用的组件是零大小类型,整个结构体将不会在运行时存在。 -For complex or deeply nested structures, it may be tedious to define all possible combinations of state. In these cases, macros may be used to generate all implementations. +对于复杂或者深度嵌套的结构体,定义所有可能的状态组合可能很乏味。在这些案例中,宏大概能被用来产生所有的实现。 From 695627fb94787a939bfe5af1676826fefb48811d Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 12 Jun 2022 01:10:38 +0800 Subject: [PATCH 052/137] minor --- src/portability/index.md | 52 +++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/portability/index.md b/src/portability/index.md index b9790043..56704589 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,31 +1,30 @@ # 可移植性 -在嵌入式环境中,可移植性是一个非常重要的主题: 来自单个厂家的每个供应商,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式将会不一样。 +在嵌入式环境中,可移植性是一个非常重要的主题: 来自单个厂家的每个供应链,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式将会不一样。 通过一个被叫做硬件抽象层或者**HAL**的层去均等化这种差异是一种常见的方法。 -> -> Hardware abstractions are sets of routines in software that emulate some platform-specific details, giving programs direct access to the hardware resources. -> -> They often allow programmers to write device-independent, high performance applications by providing standard operating system (OS) calls to hardware. +> 在软件中硬件抽象是一组函数,模仿了一些平台特定的细节,让程序可以直接访问硬件资源。 +> 它们经常通过提供标准的对硬件的操作系统(OS)调用来让程序员可以编写独立于设备的,高性能应用。 > > *Wikipedia: [Hardware Abstraction Layer]* [Hardware Abstraction Layer]: https://en.wikipedia.org/wiki/Hardware_abstraction -Embedded systems are a bit special in this regard since we typically do not have operating systems and user installable software but firmware images which are compiled as a whole as well as a number of other constraints. So while the traditional approach as defined by Wikipedia could potentially work it is likely not the most productive approach to ensure portability. +在这方面嵌入式系统有点特别,因为我们通常没有操作系统和用户可安装的软件,而是只有固件镜像,其作为一个整体被编译且伴着许多约束。因此虽然维基百科定义的传统方法可能有效,但是它不是确保可移植性最有效的方法。 + +在Rust中我们要怎么实现这个目标?让我们进入**embedded-hal**... -How do we do this in Rust? Enter **embedded-hal**... +## 什么是embedded-hal? -## What is embedded-hal? +简而言之,它是一组特性(traits),其定义了**HAL实现**,**驱动**,**应用(或者固件)** 之间的实现约定。那些约定包括功能(即约定,如果某个类型实现了某个特性,其**HAL实现**就提供了某个功能)和方法(即,如果你构建了一个实现了某个trait的类型,约定保障你肯定有在trait中指定的方法)。 -In a nutshell it is a set of traits which define implementation contracts between **HAL implementations**, **drivers** and **applications (or firmwares)**. Those contracts include both capabilities (i.e. if a trait is implemented for a certain type, the **HAL implementation** provides a certain capability) and methods (i.e. if you can construct a type implementing a trait it is guaranteed that you have the methods specified in the trait available). -A typical layering might look like this: +典型的分层可能如下所示: ![](../assets/rust_layers.svg) -Some of the defined traits in **embedded-hal** are: +一些在**embedded-hal**中被定义的traits是: * GPIO (input and output pins) * Serial communication * I2C @@ -33,31 +32,34 @@ Some of the defined traits in **embedded-hal** are: * Timers/Countdowns * Analog Digital Conversion -The main reason for having the **embedded-hal** traits and crates implementing and using them is to keep complexity in check. If you consider that an application might have to implement the use of the peripheral in the hardware as well as the application and potentially drivers for additional hardware components, then it should be easy to see that the re-usability is very limited. Expressed mathematically, if **M** is the number of peripheral HAL implementations and **N** the number of drivers then if we were to reinvent the wheel for every application then we would end up with **M*N** implementations while by using the *API* provided by the **embedded-hal** traits will make the implementation complexity approach **M+N**. Of course there're additional benefits to be had, such as less trial-and-error due to a well-defined and ready-to-use APIs. +使用**embedded-hal**traits和实现且使用了它们的crates的主要理由是为了控制复杂性。如果你发现一个应用可能必须要去实现硬件中的外设的接口和潜在的要被其他硬件组件使用的驱动,那么其应该很容易被看作是可复用性有限。用数学语言来说就是,如果**M**是外设HAL实现的数量,**N**是驱动的数量,那么如果我们要为每个应用重新发明轮子我们最终会有**M*N**个实现,然而通过使用**embedded-hal**的traits提供的 *API* 将会使实现复杂性变成**M+N** 。当然还有其它好处,比如由于API定义良好,开箱即用,导致的试错减少。 -## Users of the embedded-hal +## embedded-hal的用户 + +像上面说的,HAL有三个主要用户: As said above there are three main users of the HAL: ### HAL实现 -A HAL implementation provides the interfacing between the hardware and the users of the HAL traits. Typical implementations consist of three parts: -* One or more hardware specific types -* Functions to create and initialize such a type, often providing various configuration options (speed, operation mode, use pins, etc.) -* one or more `trait` `impl` of **embedded-hal** traits for that type +一个HAL实现提供了硬件和HAL traits的用户之间的接口。典型的实现由三部分组成: + +* 一个或者多个特定于硬件的类型 +* 生成和初始化这个类型的函数,经常伴随着不同的配置选项(速度,操作模式,使用的管脚,etc 。) +* 与那个类型有关的一个或者更多**embedded-hal** traits 的 `trait` `impl` -Such a **HAL implementation** can come in various flavours: -* Via low-level hardware access, e.g. via registers -* Via operating system, e.g. by using the `sysfs` under Linux -* Via adapter, e.g. a mock of types for unit testing -* Via driver for hardware adapters, e.g. I2C multiplexer or GPIO expander +这样的一个 **HAL实现** 可以有各种风格: +* 通过低级硬件访问,e.g. 通过寄存器。 +* 通过操作系统,e.g. 通过使用Linux下的 `sysfs` +* 通过适配器,e.g. 单元测试相关的类型的一个模仿 +* 通过硬件适配器的驱动,e.g. I2C多路复用器或者GPIO扩展器(I2C multiplexer or GPIO expander) ### 驱动 -A driver implements a set of custom functionality for an internal or external component, connected to a peripheral implementing the embedded-hal traits. Typical examples for such drivers include various sensors (temperature, magnetometer, accelerometer, light), display devices (LED arrays, LCD displays) and actuators (motors, transmitters). +一个驱动为一个外部或者内部组件实现了一组自定义的功能,被连接到一个实现了embedded-hal traits的外设上。这种驱动的典型的例子包括多个传感器(温度计,磁力计,加速度计,光照计),显示设备(LED阵列,LCD显示屏)和执行器(电机,发送器)。 -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. +必须使用一个实现了embedded-hal的某个`trait`的类型的实例来初始化一个驱动,这是通过trait bound来确保的,驱动也提供了它自己的类型实例,这个实例具有一组自定义的方法,这些方法允许与被驱动的设备交互。 ### 应用 -The application binds the various parts together and ensures that the desired functionality is achieved. When porting between different systems, this is the part which requires the most adaptation efforts, since the application needs to correctly initialize the real hardware via the HAL implementation and the initialisation of different hardware differs, sometimes drastically so. Also the user choice often plays a big role, since components can be physically connected to different terminals, hardware buses sometimes need external hardware to match the configuration or there are different trade-offs to be made in the use of internal peripherals (e.g. multiple timers with different capabilities are available or peripherals conflict with others). +应用把多个部分结合在一起并确保需要的功能被实现。当在不同的系统间移植时,这部分的适配是花费最多精力的地方,因为应用需要通过HAL实现正确地初始化真实的硬件且不同硬件的初始化也不相同,甚至有时候差别非常大。用户的选择也在其中扮演了非常重大的角色,因为组件能被物理连接到不同的端口,硬件总线有时候需要外部硬件去配合,或者在内部外设的使用上有不同的考量(e.g. 多个计时器被赋予了不同的功能或者外设与其它外设冲突) From 48f1b53bbb012b0464275dc6d6b25c3f0ef76452 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 12 Jun 2022 15:12:04 +0800 Subject: [PATCH 053/137] minor --- src/concurrency/index.md | 74 ++++++++++++---------------------------- 1 file changed, 22 insertions(+), 52 deletions(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index fe30e235..0ade5c5a 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -1,26 +1,16 @@ -# Concurrency +# 并发 -Concurrency happens whenever different parts of your program might execute -at different times or out of order. In an embedded context, this includes: +无论何时你程序的不同部分可能会在不同时刻执行或者不按顺序执行,那并发就发生了。在一个嵌入式环境中,这包括: -* interrupt handlers, which run whenever the associated interrupt happens, -* various forms of multithreading, where your microprocessor regularly swaps - between parts of your program, -* and in some systems, multiple-core microprocessors, where each core can be - independently running a different part of your program at the same time. +* 中断处理函数,无论何时相关的中断发生时,其会运行, +* 不同的多线程形式,在这块,你的微处理器通常会在你的程序的不同部分间进行切换, +* 在一些多核微处理器系统中,每个核可以同时独立地运行你的程序的不同部分。 -Since many embedded programs need to deal with interrupts, concurrency will -usually come up sooner or later, and it's also where many subtle and difficult -bugs can occur. Luckily, Rust provides a number of abstractions and safety -guarantees to help us write correct code. +因为许多嵌入式程序需要处理中断,因此并发迟早会出现,这也是许多微妙和困难的bugs会出现的地方。幸运地是,Rust提供了许多抽象和安全保障去帮助我们写正确的代码。 -## No Concurrency +## 没有并发 -The simplest concurrency for an embedded program is no concurrency: your -software consists of a single main loop which just keeps running, and there -are no interrupts at all. Sometimes this is perfectly suited to the problem -at hand! Typically your loop will read some inputs, perform some processing, -and write some outputs. +对于一个嵌入式程序来说最简单的并发是没有并发: 你的软件由单个保持运行的main循环组成,一点中断也没有。有时候这非常适合手边的问题! 通常你的循环将会读取一些输入,执行一些处理,且写入一些输出。 ```rust,ignore #[entry] @@ -34,29 +24,17 @@ fn main() { } ``` -Since there's no concurrency, there's no need to worry about sharing data -between parts of your program or synchronising access to peripherals. If -you can get away with such a simple approach this can be a great solution. +因为这里没有并发,因此不需要担心程序不同部分间的共享数据或者同步对外设的访问。如果你可以使用一个简单的方法来解决问题,这种方法是个不错的选择。 -## Global Mutable Data +## 全局可变数据 -Unlike non-embedded Rust, we will not usually have the luxury of creating -heap allocations and passing references to that data into a newly-created -thread. Instead, our interrupt handlers might be called at any time and must -know how to access whatever shared memory we are using. At the lowest level, -this means we must have _statically allocated_ mutable memory, which -both the interrupt handler and the main code can refer to. +不像非嵌入式Rust,我们通常不能分配堆和将对那个数据的引用传递进一个新创造的线程。反而,我们的中断处理函数可能在任何时间被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _static allocated_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 -In Rust, such [`static mut`] variables are always unsafe to read or write, -because without taking special care, you might trigger a race condition, -where your access to the variable is interrupted halfway through by an -interrupt which also accesses that variable. +在Rust中,[`static mut`]这样的变量读取或者写入总是不安全的,因为不特别关注它们的话,你可能会触发一个竞态条件,你对变量的访问在中途就被一个也访问那个变量的中断打断。 [`static mut`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable -For an example of how this behaviour can cause subtle errors in your code, -consider an embedded program which counts rising edges of some input signal -in each one-second period (a frequency counter): +为了举例这种行为如何在你的代码中导致了微妙的错误,思考一个嵌入式程序,这个程序在每个一秒的周期内计数一些输入信号的上升沿(一个频率计数器): ```rust,ignore static mut COUNTER: u32 = 0; @@ -68,7 +46,7 @@ fn main() -> ! { loop { let state = read_signal_level(); if state && !last_state { - // DANGER - Not actually safe! Could cause data races. + // 危险 - 实际不安全! 可能导致数据竞争。 unsafe { COUNTER += 1 }; } last_state = state; @@ -81,19 +59,11 @@ fn timer() { } ``` -Each second, the timer interrupt sets the counter back to 0. Meanwhile, the -main loop continually measures the signal, and incremements the counter when -it sees a change from low to high. We've had to use `unsafe` to access -`COUNTER`, as it's `static mut`, and that means we're promising the compiler -we won't cause any undefined behaviour. Can you spot the race condition? The -increment on `COUNTER` is _not_ guaranteed to be atomic — in fact, on most -embedded platforms, it will be split into a load, then the increment, then -a store. If the interrupt fired after the load but before the store, the -reset back to 0 would be ignored after the interrupt returns — and we would -count twice as many transitions for that period. +每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当它看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在读取之后但是写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那段时间,一些变化我们会计算两次。 ## Critical Sections +因此,关于数据竞争我们能做些什么?一个简单的方法是使用 _critical sections_ 。 So, what can we do about data races? A simple approach is to use _critical sections_, a context where interrupts are disabled. By wrapping the access to `COUNTER` in `main` in a critical section, we can be sure the timer interrupt @@ -146,7 +116,7 @@ other core could be happily accessing the same memory as your core, even without interrupts. You will need stronger synchronisation primitives if you are using multiple cores. -## Atomic Access +## 原子访问 On some platforms, special atomic instructions are available, which provide guarantees about read-modify-write operations. Specifically for Cortex-M: `thumbv6` @@ -203,7 +173,7 @@ For more details on atomics and ordering, see the [nomicon]. [nomicon]: https://doc.rust-lang.org/nomicon/atomics.html -## Abstractions, Send, and Sync +## 抽象,Send和Sync None of the above solutions are especially satisfactory. They require `unsafe` blocks which must be very carefully checked and are not ergonomic. Surely we @@ -305,7 +275,7 @@ to share between threads, we implement the Sync trait explicitly. As with the previous use of critical sections, this is only safe on single-core platforms: with multiple cores, you would need to go to greater lengths to ensure safety. -## Mutexes +## 互斥量(Mutexs) We've created a useful abstraction specific to our counter problem, but there are many common abstractions used for concurrency. @@ -387,7 +357,7 @@ more complex types which are not Copy? An extremely common example in an embedded context is a peripheral struct, which generally is not Copy. For that, we can turn to `RefCell`. -## Sharing Peripherals +## 共享外设 Device crates generated using `svd2rust` and similar abstractions provide safe access to peripherals by enforcing that only one instance of the @@ -574,7 +544,7 @@ documentation] for more information! [the documentation]: https://rtic.rs -## Real Time Operating Systems +## 实时操作系统 Another common model for embedded concurrency is the real-time operating system (RTOS). While currently less well explored in Rust, they are widely used in @@ -591,7 +561,7 @@ primitives, and often interoperate with hardware features such as DMA engines. At the time of writing, there are not many Rust RTOS examples to point to, but it's an interesting area so watch this space! -## Multiple Cores +## 多个核心 It is becoming more common to have two or more cores in embedded processors, which adds an extra layer of complexity to concurrency. All the examples using From ce31b636c3af3a88392f86f90063957f4015ba24 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 12 Jun 2022 15:35:44 +0800 Subject: [PATCH 054/137] minor --- src/concurrency/index.md | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index 0ade5c5a..20f7beba 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -61,13 +61,9 @@ fn timer() { 每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当它看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在读取之后但是写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那段时间,一些变化我们会计算两次。 -## Critical Sections +## 临界区(Critical Sections) -因此,关于数据竞争我们能做些什么?一个简单的方法是使用 _critical sections_ 。 -So, what can we do about data races? A simple approach is to use _critical -sections_, a context where interrupts are disabled. By wrapping the access to -`COUNTER` in `main` in a critical section, we can be sure the timer interrupt -will not fire until we're finished incrementing `COUNTER`: +因此,关于数据竞争我们能做些什么?一个简单的方法是使用 _临界区(critical sections)_ ,在临界区的上下文中中断被关闭了。通过把对`main`中的`COUNTER`访问封装进一个临界区,我们能确保计时器中断将不会激活,直到我们完成了增加`COUNTER`的操作: ```rust,ignore static mut COUNTER: u32 = 0; @@ -79,7 +75,7 @@ fn main() -> ! { loop { let state = read_signal_level(); if state && !last_state { - // New critical section ensures synchronised access to COUNTER + // 新的临界区确保对COUNTER的同步访问 cortex_m::interrupt::free(|_| { unsafe { COUNTER += 1 }; }); @@ -94,27 +90,18 @@ fn timer() { } ``` -In this example, we use `cortex_m::interrupt::free`, but other platforms will -have similar mechanisms for executing code in a critical section. This is also -the same as disabling interrupts, running some code, and then re-enabling -interrupts. +在这个例子里,我们使用 `cortex_m::interrupt::free`,但是其它平台将会有更简单的机制在一个临界区中执行代码。它们都有一样的逻辑,关闭中断,运行一些代码,然后重新使能中断。 -Note we didn't need to put a critical section inside the timer interrupt, -for two reasons: +注意,有两个理由,我们不需要把一个临界区放进计时器中断中: - * Writing 0 to `COUNTER` can't be affected by a race since we don't read it - * It will never be interrupted by the `main` thread anyway + * 向`COUNTER`写入0不会被一个竞争影响,因为我们不需要读取它 + * 无论如何,它永远不会被`main`线程中断 -If `COUNTER` was being shared by multiple interrupt handlers that might -_preempt_ each other, then each one might require a critical section as well. +如果`COUNTER`被多个可能相互 _抢占_ 的中断处理函数共享,那么每一个也需要一个临界区。 -This solves our immediate problem, but we're still left writing a lot of unsafe code which we need to carefully reason about, and we might be using critical sections needlessly. Since each critical section temporarily pauses interrupt processing, there is an associated cost of some extra code size and higher interrupt latency and jitter (interrupts may take longer to be processed, and the time until they are processed will be more variable). Whether this is a problem depends on your system, but in general, we'd like to avoid it. +这解决了我们的眼前问题,但是我们仍然要编写许多不安全的代码,我们需要仔细推敲这些代码,有些我们可能不需要使用临界区。因为每个临界区暂时地暂停了中断处理,就会出现一些消耗,其与一些额外的代码大小和更高的中断延迟和抖动有关(中断可能花费很长时间去处理,等待被处理的时间变化非常大)。这是否是个问题取决于你的系统,但是通常,我们想要避免它。 -It's worth noting that while a critical section guarantees no interrupts will -fire, it does not provide an exclusivity guarantee on multi-core systems! The -other core could be happily accessing the same memory as your core, even -without interrupts. You will need stronger synchronisation primitives if you -are using multiple cores. +值得注意的是,虽然一个临界区保障了没有中断将会发生,但是它在多核系统上不提供一个排他性保证(exclusivity guarantee)!其它核可能很开心访问与你的核一样的内存区域,设置不用中断。如果你正在使用多核,你将需要更强的同步原语(synchronisation primitives)。 ## 原子访问 From 6a6f32c145680e9552a6008238d9c821066cf98c Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 12 Jun 2022 22:35:44 +0800 Subject: [PATCH 055/137] minor --- src/concurrency/index.md | 53 ++++++++++++---------------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index 20f7beba..d7c27037 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -105,15 +105,7 @@ fn timer() { ## 原子访问 -On some platforms, special atomic instructions are available, which provide -guarantees about read-modify-write operations. Specifically for Cortex-M: `thumbv6` -(Cortex-M0, Cortex-M0+) only provide atomic load and store instructions, -while `thumbv7` (Cortex-M3 and above) provide full Compare and Swap (CAS) -instructions. These CAS instructions give an alternative to the heavy-handed -disabling of all interrupts: we can attempt the increment, it will succeed most -of the time, but if it was interrupted it will automatically retry the entire -increment operation. These atomic operations are safe even across multiple -cores. +在一些平台上,可以使用特定的原子指令,它保障了读取-修改-写回操作。针对Cortex-M: `thumbv6`(Cortex-M0,Cortex-M0+)只提供原子读取和存取指令,而`thumv7`(Cortex-M3和其上)提供完全的比较和交换(CAS)指令。这些CAS指令提供了一种对所有中断禁用的替代方法: 我们可以尝试执行增加操作,它在大多数情况下都会成功,但是如果它被中断了它将会自动重试完整的增加操作。这些原子操作甚至在多核间也是安全的。 ```rust,ignore use core::sync::atomic::{AtomicUsize, Ordering}; @@ -141,20 +133,11 @@ fn timer() { } ``` -This time `COUNTER` is a safe `static` variable. Thanks to the `AtomicUsize` -type `COUNTER` can be safely modified from both the interrupt handler and the -main thread without disabling interrupts. When possible, this is a better -solution — but it may not be supported on your platform. +这时,`COUNTER`是一个安全的`static`变量。多亏了`AtomicUsize`类型,不需要禁用中断,`COUNTER`能从中断处理函数和main线程被安全地修改。当可以这么做时,这是一个更好的解决方案 - 然而你的平台上可能不支持。 -A note on [`Ordering`]: this affects how the compiler and hardware may reorder -instructions, and also has consequences on cache visibility. Assuming that the -target is a single core platform `Relaxed` is sufficient and the most efficient -choice in this particular case. Stricter ordering will cause the compiler to -emit memory barriers around the atomic operations; depending on what you're -using atomics for you may or may not need this! The precise details of the -atomic model are complicated and best described elsewhere. +一个关于[`Ordering`]的提示: 这影响编译器和硬件可能如何重新排序指令,也会影响缓存可见性。假设目标是个单核平台,在这个案例里`Relaxed`是充足和最有效的选择。更严格的排序将导致编译器在原子操作周围产生内存屏障(Memory Barriers);取决于你做什么原子操作,你可能需要或者不需要这个!原子模型的精确细节是复杂的,最好写在其它地方。 -For more details on atomics and ordering, see the [nomicon]. +关于原子操作和排序的更多细节,可以看这里[nomicon]。 [`Ordering`]: https://doc.rust-lang.org/core/sync/atomic/enum.Ordering.html [nomicon]: https://doc.rust-lang.org/nomicon/atomics.html @@ -162,31 +145,25 @@ For more details on atomics and ordering, see the [nomicon]. ## 抽象,Send和Sync -None of the above solutions are especially satisfactory. They require `unsafe` -blocks which must be very carefully checked and are not ergonomic. Surely we -can do better in Rust! +上面的解决方案都不是特别令人满意。它们需要`unsafe`块,`unsafe`块必须要被十分小心地检查且符合人体工程学。确实,我们在Rust中可以做得更好! -We can abstract our counter into a safe interface which can be safely used -anywhere else in our code. For this example, we'll use the critical-section -counter, but you could do something very similar with atomics. +我们可以把我们的计数器抽象进一个安全的接口,其可以在我们代码的其它地方被安全地使用。对于这个例子,我们将使用临界区的(cirtical-section)计数器,但是你可以用原子操作做一些非常类似的事情。 ```rust,ignore use core::cell::UnsafeCell; use cortex_m::interrupt; -// Our counter is just a wrapper around UnsafeCell, which is the heart -// of interior mutability in Rust. By using interior mutability, we can have -// COUNTER be `static` instead of `static mut`, but still able to mutate -// its counter value. +// 我们的计数器只是包围UnsafeCell的一个封装,它是Rust中内部可变性 +// (interior mutability)的关键。通过使用内部可变性,我们能让COUNTER +// 变成`static`而不是`static mut`,但是仍能改变它的计数器值。 struct CSCounter(UnsafeCell); const CS_COUNTER_INIT: CSCounter = CSCounter(UnsafeCell::new(0)); impl CSCounter { pub fn reset(&self, _cs: &interrupt::CriticalSection) { - // By requiring a CriticalSection be passed in, we know we must - // be operating inside a CriticalSection, and so can confidently - // use this unsafe block (required to call UnsafeCell::get). + // 通过要求一个CriticalSection被传递进来,我们知道我们肯定正在一个 + // CriticalSection中操作,且因此可以自信地使用这个unsafe块(调用UnsafeCell::get的前提)。 unsafe { *self.0.get() = 0 }; } @@ -195,11 +172,11 @@ impl CSCounter { } } -// Required to allow static CSCounter. See explanation below. +// 允许静态CSCounter的前提。看下面的解释。 unsafe impl Sync for CSCounter {} -// COUNTER is no longer `mut` as it uses interior mutability; -// therefore it also no longer requires unsafe blocks to access. +// COUNTER不再是`mut`的因为它使用内部可变性; +// 因此访问它也不再需要unsafe块。 static COUNTER: CSCounter = CS_COUNTER_INIT; #[entry] @@ -209,7 +186,7 @@ fn main() -> ! { loop { let state = read_signal_level(); if state && !last_state { - // No unsafe here! + // 这里不会unsafe! interrupt::free(|cs| COUNTER.increment(cs)); } last_state = state; From 03fc46ce03c0ec18cd06f81e48ac82473caed89a Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 12 Jun 2022 23:22:28 +0800 Subject: [PATCH 056/137] minor --- src/concurrency/index.md | 77 ++++++++++------------------------------ src/intro/no-std.md | 2 +- 2 files changed, 20 insertions(+), 59 deletions(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index d7c27037..8faf94a9 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -186,7 +186,7 @@ fn main() -> ! { loop { let state = read_signal_level(); if state && !last_state { - // 这里不会unsafe! + // 这里不用unsafe! interrupt::free(|cs| COUNTER.increment(cs)); } last_state = state; @@ -195,82 +195,43 @@ fn main() -> ! { #[interrupt] fn timer() { - // We do need to enter a critical section here just to obtain a valid - // cs token, even though we know no other interrupt could pre-empt - // this one. + // 这里我们需要进入一个临界区,只是为了传递进一个有效的cs token,尽管我们知道 + // 没有其它中断可以抢占这个中断。 interrupt::free(|cs| COUNTER.reset(cs)); - // We could use unsafe code to generate a fake CriticalSection if we - // really wanted to, avoiding the overhead: + // 如果我们真的需要,我们可以使用unsafe代码去生成一个假CriticalSection, + // 避免消耗: // let cs = unsafe { interrupt::CriticalSection::new() }; } ``` -We've moved our `unsafe` code to inside our carefully-planned abstraction, -and now our application code does not contain any `unsafe` blocks. - -This design requires that the application pass a `CriticalSection` token in: -these tokens are only safely generated by `interrupt::free`, so by requiring -one be passed in, we ensure we are operating inside a critical section, without -having to actually do the lock ourselves. This guarantee is provided statically -by the compiler: there won't be any runtime overhead associated with `cs`. -If we had multiple counters, they could all be given the same `cs`, without -requiring multiple nested critical sections. - -This also brings up an important topic for concurrency in Rust: the -[`Send` and `Sync`] traits. To summarise the Rust book, a type is Send -when it can safely be moved to another thread, while it is Sync when -it can be safely shared between multiple threads. In an embedded context, -we consider interrupts to be executing in a separate thread to the application -code, so variables accessed by both an interrupt and the main code must be -Sync. +我们已经把我们的`unsafe`代码移进了精心安排的抽象中,现在我们的应用代码不包含任何`unsafe`块。 + +这个设计要求应用传递一个`CriticalSection` token进来: 这些tokens仅由`interrupt::free`安全地产生,因此通过要求传递进一个`CriticalSection` token,我们确保我们正在一个临界区中操作,不用自己动手锁起来。这个保障由编译器静态地提供: 这将不会有任何与`cs`有关的运行时消耗。如果我们由多个计数器,它们都可以被指定同一个`cs`,而不用要求多个嵌套的临界区。 + +这也带来了Rust中关于并发的一个重要主题: [`Send` and `Sync`] traits。总结一下Rust book,当一个类型能够安全地被移动到另一个线程,它是Send,当一个类型能被安全地在多个线程间共享的时候,它是Sync。在一个嵌入式上下文中,我们认为中断是在应用代码的一个独立线程中执行的,因此在一个中断和main代码中都被访问的变量必须是Sync。 [`Send` and `Sync`]: https://doc.rust-lang.org/nomicon/send-and-sync.html -For most types in Rust, both of these traits are automatically derived for you -by the compiler. However, because `CSCounter` contains an [`UnsafeCell`], it is -not Sync, and therefore we could not make a `static CSCounter`: `static` -variables _must_ be Sync, since they can be accessed by multiple threads. +在Rust中的大多数类型,这两个traits都会由你的编译器为你自动地产生。然而,因为`CSCounter`包含了一个[`UnsafeCell`],它不是Sync,因此我们不能使用一个`static CSCounter`: `static` 变量 _必须_ 是Sync,因此它们能被多个线程访问。 [`UnsafeCell`]: https://doc.rust-lang.org/core/cell/struct.UnsafeCell.html -To tell the compiler we have taken care that the `CSCounter` is in fact safe -to share between threads, we implement the Sync trait explicitly. As with the -previous use of critical sections, this is only safe on single-core platforms: -with multiple cores, you would need to go to greater lengths to ensure safety. +为了告诉编译器我们已经注意到`CSCounter`事实上在线程间共享是安全的,我们显式地实现了Sync trait。与之前使用的临界区一样,这只在单核平台上是安全的: 对于多核,你需要做更多的事来确保安全。 ## 互斥量(Mutexs) -We've created a useful abstraction specific to our counter problem, but -there are many common abstractions used for concurrency. +我们已经为我们的计数器问题创造了一个有用的抽象,但是关于并发这里存在许多通用的抽象。 -One such _synchronisation primitive_ is a mutex, short for mutual exclusion. -These constructs ensure exclusive access to a variable, such as our counter. A -thread can attempt to _lock_ (or _acquire_) the mutex, and either succeeds -immediately, or blocks waiting for the lock to be acquired, or returns an error -that the mutex could not be locked. While that thread holds the lock, it is -granted access to the protected data. When the thread is done, it _unlocks_ (or -_releases_) the mutex, allowing another thread to lock it. In Rust, we would -usually implement the unlock using the [`Drop`] trait to ensure it is always -released when the mutex goes out of scope. +一个这样的 _同步原语_ 是一个互斥量(mutex),互斥(mutual exclusion)的缩写。这些构造确保了对一个变量的排他访问,比如我们的计数器。一个线程尝试 _lock_ (或者 _acquire_) 互斥量,或者当互斥量不能被锁住时返回一个错误。当线程持有锁时,它有权访问被保护的数据,当线程工作完成了,它 _unlocks_ (或者 _releases_) 互斥量,允许其它线程锁住它。在Rust中,我们通常使用[`Drop`] trait实现unlock去确保当互斥量超出作用域时它总是被释放。 [`Drop`]: https://doc.rust-lang.org/core/ops/trait.Drop.html -Using a mutex with interrupt handlers can be tricky: it is not normally -acceptable for the interrupt handler to block, and it would be especially -disastrous for it to block waiting for the main thread to release a lock, -since we would then _deadlock_ (the main thread will never release the lock -because execution stays in the interrupt handler). Deadlocking is not -considered unsafe: it is possible even in safe Rust. - -To avoid this behaviour entirely, we could implement a mutex which requires -a critical section to lock, just like our counter example. So long as the -critical section must last as long as the lock, we can be sure we have -exclusive access to the wrapped variable without even needing to track -the lock/unlock state of the mutex. - -This is in fact done for us in the `cortex_m` crate! We could have written -our counter using it: +将中断处理函数与一个互斥量一起使用可能有点棘手: 阻塞中断处理函数通常是不可接受的,如果它阻塞等待main线程去释放一个锁,那将是一场灾难。因为我们会 _死锁_ (因为执行停留在中断处理函数中,主线程将永远不会释放锁)。死锁被认为是不安全的: 即使在安全的Rust中这也是可能发生的。 + +为了完全避免这个行为,我们可以实现一个要求临界区的互斥量去锁住,就像我们的计数器例子一样。临界区的存在时间必须和锁存在的时间一样长,我们能确保我们对被封装的变量有排他式访问,甚至不需要跟踪互斥量的 lock/unlock 状态。 + +实际上我们在 `cortex_m` crate中就是这么做的!我们可以用它来写入我们的计数器: ```rust,ignore use core::cell::Cell; diff --git a/src/intro/no-std.md b/src/intro/no-std.md index 8a16b826..eb530285 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -4,7 +4,7 @@ ## 主机环境 -这类环境类似一个常见的PC环境。意味着有向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。你可能在RAM/ROM的使用上有一些sysroot的限制,可能还有一些特别的硬件或者I/O。总之感觉像是在一个特定目的的PC环境上编程一样。 +这类环境类似一个常见的PC环境。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。你可能在RAM/ROM的使用上有一些sysroot的限制,可能还有一些特别的硬件或者I/O。总之感觉像是在一个特定目的的PC环境上编程一样。 ## 裸机环境 From 693b28ef12b81817781065cc47f5b2c3e05c4424 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 12 Jun 2022 23:33:36 +0800 Subject: [PATCH 057/137] minor --- src/concurrency/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index 8faf94a9..8774a5c3 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -255,11 +255,12 @@ fn main() -> ! { #[interrupt] fn timer() { - // We still need to enter a critical section here to satisfy the Mutex. + // 这里我们仍然需要进入一个临界区去满足互斥量。 interrupt::free(|cs| COUNTER.borrow(cs).set(0)); } ``` +我们现在使用了[`Cell`], We're now using [`Cell`], which along with its sibling `RefCell` is used to provide safe interior mutability. We've already seen `UnsafeCell` which is the bottom layer of interior mutability in Rust: it allows you to obtain From fe1beb9589705f522e1caec2c2ac198737b498ca Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 13 Jun 2022 10:24:59 +0800 Subject: [PATCH 058/137] minor --- src/concurrency/index.md | 11 +---------- src/design-patterns/hal/checklist.md | 26 +++++++++++++------------- src/design-patterns/hal/index.md | 17 +++++++---------- src/design-patterns/index.md | 4 ++-- src/interoperability/index.md | 26 +++++++++----------------- 5 files changed, 32 insertions(+), 52 deletions(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index 8774a5c3..fc590dff 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -260,16 +260,7 @@ fn timer() { } ``` -我们现在使用了[`Cell`], -We're now using [`Cell`], which along with its sibling `RefCell` is used to -provide safe interior mutability. We've already seen `UnsafeCell` which is -the bottom layer of interior mutability in Rust: it allows you to obtain -multiple mutable references to its value, but only with unsafe code. A `Cell` -is like an `UnsafeCell` but it provides a safe interface: it only permits -taking a copy of the current value or replacing it, not taking a reference, -and since it is not Sync, it cannot be shared between threads. These -constraints mean it's safe to use, but we couldn't use it directly in a -`static` variable as a `static` must be Sync. +我们现在使用了[`Cell`],它与它的兄弟`RefCell`一起被用于提供安全的内部可变性。我们已经见过`UnsafeCell`了,在Rust中它是内部可变性的底层: 它允许你去获得多个对值的可变引用,但是只能与不安全的代码一起工作。一个`Cell`像一个`UnsafeCell`一样但是它提供了一个安全的接口: 它只允许拷贝现在的值或者替换它,不允许获取一个引用,因此它不是Sync,它不能被在线程间共享。这些限制意味着它用起来是安全的,但是我们不能直接将它用于`static`变量因为一个`static`必须是Sync。 [`Cell`]: https://doc.rust-lang.org/core/cell/struct.Cell.html diff --git a/src/design-patterns/hal/checklist.md b/src/design-patterns/hal/checklist.md index 6fdf98b7..966dd99e 100644 --- a/src/design-patterns/hal/checklist.md +++ b/src/design-patterns/hal/checklist.md @@ -1,17 +1,17 @@ -# HAL Design Patterns Checklist +# HAL设计模式清单 -- **Naming** *(crate aligns with Rust naming conventions)* - - [ ] The crate is named appropriately ([C-CRATE-NAME]) -- **Interoperability** *(crate interacts nicely with other library functionality)* - - [ ] Wrapper types provide a destructor method ([C-FREE]) - - [ ] HALs reexport their register access crate ([C-REEXPORT-PAC]) - - [ ] Types implement the `embedded-hal` traits ([C-HAL-TRAITS]) -- **Predictability** *(crate enables legible code that acts how it looks)* - - [ ] Constructors are used instead of extension traits ([C-CTOR]) -- **GPIO Interfaces** *(GPIO Interfaces follow a common pattern)* - - [ ] Pin types are zero-sized by default ([C-ZST-PIN]) - - [ ] Pin types provide methods to erase pin and port ([C-ERASED-PIN]) - - [ ] Pin state should be encoded as type parameters ([C-PIN-STATE]) +- **命名** *(crate符合Rust命名规则)* + - [ ] crate被恰当地命名 ([C-CRATE-NAME]) +- **互用性** *(crate很好地与其它的库功能交互)* + - [ ] 封装类型提供一种解构方法 ([C-FREE]) + - [ ] HALs重新导出了它们的寄存器访问crate ([C-REEXPORT-PAC]) + - [ ] 类型实现了 `embedded-hal` traits ([C-HAL-TRAITS]) +- **可预见性** *(crate使清晰的代码工作起来像它们看起来一样)* + - [ ] 使用构造函数而不是扩展traies ([C-CTOR]) +- **GPIO接口** *(GPIO接口遵循一个常见的模式)* + - [ ] Pin类型默认是零大小类型 ([C-ZST-PIN]) + - [ ] Pin类型提供擦除管脚和端口的方法 ([C-ERASED-PIN]) + - [ ] Pin状态应该被编码为类型参数 ([C-PIN-STATE]) [C-CRATE-NAME]: naming.html#c-crate-name diff --git a/src/design-patterns/hal/index.md b/src/design-patterns/hal/index.md index c493a873..d15d1fe0 100644 --- a/src/design-patterns/hal/index.md +++ b/src/design-patterns/hal/index.md @@ -1,15 +1,12 @@ -# HAL Design Patterns +# HAL设计模式 -This is a set of common and recommended patterns for writing hardware -abstraction layers (HALs) for microcontrollers in Rust. These patterns are -intended to be used in addition to the existing [Rust API Guidelines] when -writing HALs for microcontrollers. +这是一组关于使用Rust为微控制器写硬件抽象层的常见的和推荐的模式。当为微控制器编写HALs时,除了现有的 [Rust API 指南] 外,还打算使用这些模式。 -[Rust API Guidelines]: https://rust-lang.github.io/api-guidelines/ +[Rust API 指南]: https://rust-lang.github.io/api-guidelines/ -[Checklist](checklist.md) +[清单](checklist.md) -- [Naming](naming.md) -- [Interoperability](interoperability.md) -- [Predictability](predictability.md) +- [命名](naming.md) +- [互用性](interoperability.md) +- [可预见性](predictability.md) - [GPIO](gpio.md) diff --git a/src/design-patterns/index.md b/src/design-patterns/index.md index 32153faf..1222d89d 100644 --- a/src/design-patterns/index.md +++ b/src/design-patterns/index.md @@ -1,3 +1,3 @@ -# Design Patterns +# 设计模式 -This chapter aims to collect various useful design patterns for embedded Rust. +这个章节的目标是为嵌入式Rust收集不同的有用的设计模式。 diff --git a/src/interoperability/index.md b/src/interoperability/index.md index b51880ed..1bda977c 100644 --- a/src/interoperability/index.md +++ b/src/interoperability/index.md @@ -6,15 +6,12 @@ Rust和C代码间的互用性始终取决于两种语言间的数据转换。为 `std::ffi`提供了一些工具去转换更复杂的类型,比如Strings,将`&str`和`String`映射成更容易和安全处理的C类型。 -这两个模块在`core`中都不可用,但是你可以找到一个 -Neither of these modules are available in `core`, but you can find a `#![no_std]` -compatible version of `std::ffi::{CStr,CString}` in the [`cstr_core`] crate, and -most of the `std::os::raw` types in the [`cty`] crate. +这两个模块在`core`中都没有,但是你可以在[`cstr_core`] crate中,找到一个`std::ffi::{CStr,CString}` 的 `#![no_std]`兼容版本,大多数的`std::os::raw`类型在[`cty`] crate中。 [`cstr_core`]: https://crates.io/crates/cstr_core [`cty`]: https://crates.io/crates/cty -| Rust type | Intermediate | C type | +| Rust 类型 | 中间类型 | C type | |------------|--------------|--------------| | String | CString | *char | | &str | CStr | *const char | @@ -22,8 +19,7 @@ most of the `std::os::raw` types in the [`cty`] crate. | u32 or u64 | c_uint | unsigned int | | etc | ... | ... | -As mentioned above, primitive types can be converted -by the compiler implicitly. +像上面提到的,基本类型能被编译器隐式地转换。 ```rust,ignore unsafe fn foo(num: u32) { @@ -32,23 +28,19 @@ unsafe fn foo(num: u32) { } ``` -## Interoperability with other build systems +## 与其它编译系统的互用性 -A common requirement for including Rust in your embedded project is combining -Cargo with your existing build system, such as make or cmake. +把Rust包含进你的嵌入式项目的一个常见需求是,把Cargo结合进你现存的编译系统中,比如make或者cmake。 -We are collecting examples and use cases for this on our issue tracker in -[issue #61]. +在[issue #61]中我们的issue tracker上,我们正在为这个需求收集例子和用例。 [issue #61]: /~https://github.com/rust-embedded/book/issues/61 -## Interoperability with RTOSs +## 与RTOSs的互用性 -Integrating Rust with an RTOS such as FreeRTOS or ChibiOS is still a work in -progress; especially calling RTOS functions from Rust can be tricky. +将Rust和一个RTOS集成在一起,比如FreeRTOS或者ChibiOS仍然在进行中; 尤其是从Rust调用RTOS函数可能很棘手。 -We are collecting examples and use cases for this on our issue tracker in -[issue #62]. +在[issue #62]中我们的issue tracker上,我们正为这件事收集例子和用例。 [issue #62]: /~https://github.com/rust-embedded/book/issues/62 From 0ffc12cf87198ada444c97049bdc75e9b9f0d446 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 14 Jun 2022 10:03:50 +0800 Subject: [PATCH 059/137] minor --- src/collections/index.md | 13 ++-- src/concurrency/index.md | 135 +++++++++++---------------------------- 2 files changed, 40 insertions(+), 108 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index 7319a397..dfa1ffab 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -1,17 +1,12 @@ -# Collections - -Eventually you'll want to use dynamic data structures (AKA collections) in your -program. `std` provides a set of common collections: [`Vec`], [`String`], -[`HashMap`], etc. All the collections implemented in `std` use a global dynamic -memory allocator (AKA the heap). +# 集合 +最终,你将希望在你的程序里使用动态数据结构(也称为集合)。`std` 提供了一组常见的集合: [`Vec`],[`String`],[`HashMap`],等等。所有这些在`std`中被实现的集合都使用一个全局动态分配器(也称为堆)。 + [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html [`String`]: https://doc.rust-lang.org/std/string/struct.String.html [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html -As `core` is, by definition, free of memory allocations these implementations -are not available there, but they can be found in the `alloc` crate -that's shipped with the compiler. +因为`core`的定义是没有内存分配的,所以这些实现在`core`中是没有的,但是我们可以在编译器附带的`alloc` crate中找到。 If you need collections, a heap allocated implementation is not your only option. You can also use *fixed capacity* collections; one such implementation diff --git a/src/concurrency/index.md b/src/concurrency/index.md index fc590dff..d7a884f2 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -264,36 +264,19 @@ fn timer() { [`Cell`]: https://doc.rust-lang.org/core/cell/struct.Cell.html -So why does the example above work? The `Mutex` implements Sync for any -`T` which is Send — such as a `Cell`. It can do this safely because it only -gives access to its contents during a critical section. We're therefore able -to get a safe counter with no unsafe code at all! +因此为什么上面的例子可以工作?`Mutex`对于任何是Send的`T`实现了Sync - 比如一个`Cell`。因为它只能在临界区对它的内容进行访问,所以它这么做是安全的。因此我们可以即使没有一点unsafe代码我们也能获取一个safe的计数器! -This is great for simple types like the `u32` of our counter, but what about -more complex types which are not Copy? An extremely common example in an -embedded context is a peripheral struct, which generally is not Copy. -For that, we can turn to `RefCell`. +对于我们的简单类型,像是我们的计数器的`u32`,来说是很棒的,但是对于更复杂的不能拷贝的类型呢?在一个嵌入式上下文中一个极度常见的例子是一个外设结构体,通常它们不是Copy。针对那种情况,我们可以使用`RefCell`。 ## 共享外设 -Device crates generated using `svd2rust` and similar abstractions provide -safe access to peripherals by enforcing that only one instance of the -peripheral struct can exist at a time. This ensures safety, but makes it -difficult to access a peripheral from both the main thread and an interrupt -handler. +使用`svd2rust`生成的设备crates和相似的抽象,通过强制要求同时只能存在一个外设结构体的实例,提供了对外设的安全的访问。这个确保了安全性,但是使得它很难从main线程和一个中断处理函数一起访问一个外设。 -To safely share peripheral access, we can use the `Mutex` we saw before. We'll -also need to use [`RefCell`], which uses a runtime check to ensure only one -reference to a peripheral is given out at a time. This has more overhead than -the plain `Cell`, but since we are giving out references rather than copies, -we must be sure only one exists at a time. +为了安全地共享对外设的访问,我们能使用我们之前看到的`Mutex`。我们也将需要使用[`RefCell`],它使用一个运行时检查去确保对一个外设m每次只有一个引用被给出。这个比纯`Cell`消耗更多,但是因为我们正给出引用而不是拷贝,我们必须确保每次只有一个引用存在。 [`RefCell`]: https://doc.rust-lang.org/core/cell/struct.RefCell.html -Finally, we'll also have to account for somehow moving the peripheral into -the shared variable after it has been initialised in the main code. To do -this we can use the `Option` type, initialised to `None` and later set to -the instance of the peripheral. +最终,我们也必须考虑在main代码中初始化外设后,如何将外设移到共享变量中。为了做这个,我们使用`Option`类型,初始成`None`,之后设置成外设的实例。 ```rust,ignore use core::cell::RefCell; @@ -305,35 +288,31 @@ static MY_GPIO: Mutex>> = #[entry] fn main() -> ! { - // Obtain the peripheral singletons and configure it. - // This example is from an svd2rust-generated crate, but - // most embedded device crates will be similar. + // 获得外设的单例并配置它。这个例子来自一个svd2rust生成的crate, + // 但是大多数的嵌入式设备crates都相似。 let dp = stm32f405::Peripherals::take().unwrap(); let gpioa = &dp.GPIOA; - // Some sort of configuration function. - // Assume it sets PA0 to an input and PA1 to an output. + // 某个配置函数。假设它把PA0设置成一个输入和把PA1设置成一个输出。 configure_gpio(gpioa); - // Store the GPIOA in the mutex, moving it. + // 把GPIOA存进互斥量中,移动它。 interrupt::free(|cs| MY_GPIO.borrow(cs).replace(Some(dp.GPIOA))); - // We can no longer use `gpioa` or `dp.GPIOA`, and instead have to - // access it via the mutex. + // 我可以不再用`gpioa`或者`dp.GPIOA`,反而必须通过互斥量访问它。 - // Be careful to enable the interrupt only after setting MY_GPIO: - // otherwise the interrupt might fire while it still contains None, - // and as-written (with `unwrap()`), it would panic. + // 请注意,只有在设置MY_GPIO后才能使能中断: 要不然当MY_GPIO还是包含None的时候, + // 中断可能会发生,然后像上面写的那样操作(使用`unwrap()`),它将发生运行时恐慌。 set_timer_1hz(); let mut last_state = false; loop { - // We'll now read state as a digital input, via the mutex + // 我们现在将通过互斥量,读取作为数字输入时的状态。 let state = interrupt::free(|cs| { let gpioa = MY_GPIO.borrow(cs).borrow(); gpioa.as_ref().unwrap().idr.read().idr0().bit_is_set() }); if state && !last_state { - // Set PA1 high if we've seen a rising edge on PA0. + // 如果我们在PA0上已经看到了一个上升沿,拉高PA1。 interrupt::free(|cs| { let gpioa = MY_GPIO.borrow(cs).borrow(); gpioa.as_ref().unwrap().odr.modify(|_, w| w.odr1().set_bit()); @@ -345,40 +324,30 @@ fn main() -> ! { #[interrupt] fn timer() { - // This time in the interrupt we'll just clear PA0. + // 这次在中断中,我们将清除PA0。 interrupt::free(|cs| { - // We can use `unwrap()` because we know the interrupt wasn't enabled - // until after MY_GPIO was set; otherwise we should handle the potential - // for a None value. + // 我们可以使用`unwrap()` 因为我们知道直到MY_GPIO被设置后,中断都是禁用的; + // 否则我应该处理会出现一个None值的潜在可能 let gpioa = MY_GPIO.borrow(cs).borrow(); gpioa.as_ref().unwrap().odr.modify(|_, w| w.odr1().clear_bit()); }); } ``` -That's quite a lot to take in, so let's break down the important lines. +这需要理解的内容很多,所以让我们把重要的内容分解一下。 ```rust,ignore static MY_GPIO: Mutex>> = Mutex::new(RefCell::new(None)); ``` -Our shared variable is now a `Mutex` around a `RefCell` which contains an -`Option`. The `Mutex` ensures we only have access during a critical section, -and therefore makes the variable Sync, even though a plain `RefCell` would not -be Sync. The `RefCell` gives us interior mutability with references, which -we'll need to use our `GPIOA`. The `Option` lets us initialise this variable -to something empty, and only later actually move the variable in. We cannot -access the peripheral singleton statically, only at runtime, so this is -required. +我们的共享变量现在是一个包围了一个`RefCell`的`Mutex`,`RefCell`包含一个`Option`。`Mutex`确保只在一个临界区的时候可以访问,因此使变量变成Sync,设置即使一个纯`RefCell`不是Sync。`RefCell`赋予了我们引用的内部可变性,我们将需要使用我们的`GPIOA`。`Option`让我们初始化这个变量成空的东西,只在随后实际移动变量进来。我们不能静态地访问外设单例,只有在运行时,因此这是需要的。 ```rust,ignore interrupt::free(|cs| MY_GPIO.borrow(cs).replace(Some(dp.GPIOA))); ``` -Inside a critical section we can call `borrow()` on the mutex, which gives us -a reference to the `RefCell`. We then call `replace()` to move our new value -into the `RefCell`. +在一个临界区中,我们可以在互斥量上调用`borrow()`,其给了我们一个指向`RefCell`的引用。然后我们调用`replace()`去移动我们的新值进来`RefCell`。 ```rust,ignore interrupt::free(|cs| { @@ -387,18 +356,11 @@ interrupt::free(|cs| { }); ``` -Finally, we use `MY_GPIO` in a safe and concurrent fashion. The critical section -prevents the interrupt firing as usual, and lets us borrow the mutex. The -`RefCell` then gives us an `&Option`, and tracks how long it remains -borrowed - once that reference goes out of scope, the `RefCell` will be updated -to indicate it is no longer borrowed. +最终,我们用一种安全和并发的方式使用`MY_GPIO`。临界区禁止了中断像往常一样发生,让我们借用互斥量。`RefCell`然后给了我们一个`&Option`,追踪它还要借用多久 - 一旦引用超出作用域,`RefCell`将会被更新去指出引用不再被借用。 -Since we can't move the `GPIOA` out of the `&Option`, we need to convert it to -an `&Option<&GPIOA>` with `as_ref()`, which we can finally `unwrap()` to obtain -the `&GPIOA` which lets us modify the peripheral. +因为我不能把`GPIOA`移出`&Option`,我们需要用`as_ref()`将它转换成一个`&Option<&GPIOA>`,最终我们能使用`unwrap()`获得`&GPIOA`,其让我们可以修改外设。 -If we need a mutable reference to a shared resource, then `borrow_mut` and `deref_mut` -should be used instead. The following code shows an example using the TIM2 timer. +如果我们需要一个共享的资源的可变引用,那么`borrow_mut`和`deref_mut`应该被使用。下面的代码展示了一个使用TIM2计时器的例子。 ```rust,ignore use core::cell::RefCell; @@ -415,9 +377,8 @@ fn main() -> ! { let mut cp = cm::Peripherals::take().unwrap(); let dp = stm32f405::Peripherals::take().unwrap(); - // Some sort of timer configuration function. - // Assume it configures the TIM2 timer, its NVIC interrupt, - // and finally starts the timer. + // 某个计时器配置函数。假设它配置了TIM2计时器和它的NVIC中断, + // 最终启动计时器。 let tim = configure_timer_interrupt(&mut cp, dp); interrupt::free(|cs| { @@ -432,7 +393,7 @@ fn main() -> ! { #[interrupt] fn timer() { interrupt::free(|cs| { - if let Some(ref mut tim)) = G_TIM.borrow(cs).borrow_mut().deref_mut() { + if let Some(ref mut tim) = G_TIM.borrow(cs).borrow_mut().deref_mut() { tim.start(1.hz()); } }); @@ -440,55 +401,31 @@ fn timer() { ``` -Whew! This is safe, but it is also a little unwieldy. Is there anything else -we can do? +呼!这是安全的,但也有点笨拙。我们还能做些什么吗? ## RTIC -One alternative is the [RTIC framework], short for Real Time Interrupt-driven Concurrency. It -enforces static priorities and tracks accesses to `static mut` variables -("resources") to statically ensure that shared resources are always accessed -safely, without requiring the overhead of always entering critical sections and -using reference counting (as in `RefCell`). This has a number of advantages such -as guaranteeing no deadlocks and giving extremely low time and memory overhead. +另一个方法是使用[RTIC框架],Real Time Interrupt-driven Concurrency的缩写。它强制执行静态优先级并追踪对`static mut`变量("资源")的访问去确保共享资源总是能被安全地访问,而不需要总是进入临界区且使用引用计数带来的消耗(如`RefCell`中所示)。这有许多好处,比如保证没有死锁且时间和内存的消耗极度低。 -[RTIC framework]: /~https://github.com/rtic-rs/cortex-m-rtic +[RTIC框架]: /~https://github.com/rtic-rs/cortex-m-rtic -The framework also includes other features like message passing, which reduces -the need for explicit shared state, and the ability to schedule tasks to run at -a given time, which can be used to implement periodic tasks. Check out [the -documentation] for more information! +这个框架也包括了其它的特性,像是消息传递(message passing),消息传递减少了对显式共享状态的需要,还提供了在一个给定时间调度任务去运行的功能,这功能能被用来实现周期性的任务。看下[文档]可以知道更多的信息! -[the documentation]: https://rtic.rs +[文档]: https://rtic.rs ## 实时操作系统 -Another common model for embedded concurrency is the real-time operating system -(RTOS). While currently less well explored in Rust, they are widely used in -traditional embedded development. Open source examples include [FreeRTOS] and -[ChibiOS]. These RTOSs provide support for running multiple application threads -which the CPU swaps between, either when the threads yield control (called -cooperative multitasking) or based on a regular timer or interrupts (preemptive -multitasking). The RTOS typically provide mutexes and other synchronisation -primitives, and often interoperate with hardware features such as DMA engines. +与嵌入式并发有关的另一个模型是实时操作系统(RTOS)。虽然现在在Rust中的研究较少,但是它们被广泛用于传统的嵌入式开发。开源的例子包括[FreeRTOS]和[ChibiOS]。这些RTOSs提供对运行多个应用线程的支持,CPU在这些线程间进行切换,切换要么发生在当线程让出控制权的时候(被称为非抢占式多任务),要么是基于一个常规计时器或者中断(抢占式多任务)。RTOS通常提供互斥量或者其它的同步原语,经常与硬件特性互相操作,比如DMA引擎。 [FreeRTOS]: https://freertos.org/ [ChibiOS]: http://chibios.org/ -At the time of writing, there are not many Rust RTOS examples to point to, -but it's an interesting area so watch this space! +在撰写本文时,没有太多的Rust RTOS示例可供参考,但这是一个有趣的领域,所以请关注这块! ## 多个核心 -It is becoming more common to have two or more cores in embedded processors, -which adds an extra layer of complexity to concurrency. All the examples using -a critical section (including the `cortex_m::interrupt::Mutex`) assume the only -other execution thread is the interrupt thread, but on a multi-core system -that's no longer true. Instead, we'll need synchronisation primitives designed -for multiple cores (also called SMP, for symmetric multi-processing). +在嵌入式处理器中有两个或者多个核心很正常,其为并发添加了额外一层复杂性。所有使用临界区的例子(包括`cortex_m::interrupt::Mutex`)都假设了另一个执行的线程仅是中断线程,但是在一个多核系统中,这不再是正确的假设。反而,我们将需要为多核设计的同步原语(也被叫做SMP,symmetric multi-processing的缩写)。 -These typically use the atomic instructions we saw earlier, since the -processing system will ensure that atomicity is maintained over all cores. +我们之前看到的,这些通常使用原子指令,因为处理系统将确保原子性在所有的核中都保持着。 -Covering these topics in detail is currently beyond the scope of this book, -but the general patterns are the same as for the single-core case. +覆盖这些主题的细节已经超出了本书的范围,但是常规的模式与单核的相似。 From bcf969fe6c78ec9b9e3de6cc52a61cace79db466 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 09:18:01 +0800 Subject: [PATCH 060/137] minor --- src/SUMMARY.md | 2 +- src/collections/index.md | 28 +++++++++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index b43bbf7a..0e274e47 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -38,7 +38,7 @@ more information and coordination - [零成本抽象](./static-guarantees/zero-cost-abstractions.md) - [可移植性](./portability/index.md) - [并发](./concurrency/index.md) -- [Collections](./collections/index.md) +- [集合](./collections/index.md) - [设计模式](./design-patterns/index.md) - [HALs](./design-patterns/hal/index.md) - [Checklist](./design-patterns/hal/checklist.md) diff --git a/src/collections/index.md b/src/collections/index.md index dfa1ffab..bfd26cdf 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -8,19 +8,15 @@ 因为`core`的定义是没有内存分配的,所以这些实现在`core`中是没有的,但是我们可以在编译器附带的`alloc` crate中找到。 -If you need collections, a heap allocated implementation is not your only -option. You can also use *fixed capacity* collections; one such implementation -can be found in the [`heapless`] crate. +如果你需要集合,一个堆分配的实现不是你唯一的选择。你也可以使用 *fixed capacity* 集合; 一个这样的实现可以在 [`heapless`] crate中被找到。 [`heapless`]: https://crates.io/crates/heapless -In this section, we'll explore and compare these two implementations. +在这部分,我们将研究和比较这两个实现。 -## Using `alloc` +## 使用 `alloc` -The `alloc` crate is shipped with the standard Rust distribution. To import the -crate you can directly `use` it *without* declaring it as a dependency in your -`Cargo.toml` file. +`alloc` crate与标准的Rust发行版在一起。为了导入这个crate,你可以直接 `use` 它而不需要在你的`Cargo.toml`文件中把它声明为一个依赖。 ``` rust,ignore #![feature(alloc)] @@ -30,19 +26,14 @@ extern crate alloc; use alloc::vec::Vec; ``` -To be able to use any collection you'll first need use the `global_allocator` -attribute to declare the global allocator your program will use. It's required -that the allocator you select implements the [`GlobalAlloc`] trait. +为了能使用集合,你首先需要使用`global_allocator`属性去声明你程序将使用的全局的分配器。它要求你选择的分配器实现了[`GlobalAlloc`] trait 。 [`GlobalAlloc`]: https://doc.rust-lang.org/core/alloc/trait.GlobalAlloc.html -For completeness and to keep this section as self-contained as possible we'll -implement a simple bump pointer allocator and use that as the global allocator. -However, we *strongly* suggest you use a battle tested allocator from crates.io -in your program instead of this allocator. +为了完整性和尽可能保持本节的自包含性,我们将实现一个简单线性指针分配器且用它作为全局分配器。然而,我们 *强烈地* 建议你在你的程序中使用一个来自crates.io的久经战斗测试的分配器而不是这个分配器。 ``` rust,ignore -// Bump pointer allocator implementation +// 线性指针分配器实现 extern crate cortex_m; @@ -51,7 +42,7 @@ use core::ptr; use cortex_m::interrupt; -// Bump pointer allocator for *single* core systems +// 用于单核系统的线性指针分配器 struct BumpPointerAlloc { head: UnsafeCell, end: usize, @@ -61,8 +52,7 @@ unsafe impl Sync for BumpPointerAlloc {} unsafe impl GlobalAlloc for BumpPointerAlloc { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - // `interrupt::free` is a critical section that makes our allocator safe - // to use from within interrupts + // `interrupt::free`是一个临界区,临界区让我们的分配器在中断中用起来安全 interrupt::free(|_| { let head = self.head.get(); let size = layout.size(); From 29679b9440374be6e3d5017eb269f7ae0929f975 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 09:46:38 +0800 Subject: [PATCH 061/137] minor --- src/collections/index.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index bfd26cdf..0204ad64 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -59,11 +59,11 @@ unsafe impl GlobalAlloc for BumpPointerAlloc { let align = layout.align(); let align_mask = !(align - 1); - // move start up to the next alignment boundary + // 将start移至下一个对齐边界。 let start = (*head + align - 1) & align_mask; if start + size > self.end { - // a null pointer signal an Out Of Memory condition + // 一个空指针通知内存不足 ptr::null_mut() } else { *head = start + size; @@ -73,13 +73,13 @@ unsafe impl GlobalAlloc for BumpPointerAlloc { } unsafe fn dealloc(&self, _: *mut u8, _: Layout) { - // this allocator never deallocates memory + // 这个分配器从不释放内存 } } -// Declaration of the global memory allocator -// NOTE the user must ensure that the memory region `[0x2000_0100, 0x2000_0200]` -// is not used by other parts of the program +// 全局内存分配器的声明 +// 注意 用户必须确保`[0x2000_0100, 0x2000_0200]`内存区域 +// 没有被程序的其它部分使用 #[global_allocator] static HEAP: BumpPointerAlloc = BumpPointerAlloc { head: UnsafeCell::new(0x2000_0100), @@ -87,9 +87,7 @@ static HEAP: BumpPointerAlloc = BumpPointerAlloc { }; ``` -Apart from selecting a global allocator the user will also have to define how -Out Of Memory (OOM) errors are handled using the *unstable* -`alloc_error_handler` attribute. +除了选择一个全局分配器,用户也将必须定义如何使用*不稳定的*`alloc_error_handler`属性来处理内存溢出错误。 ``` rust,ignore #![feature(alloc_error_handler)] @@ -104,7 +102,7 @@ fn on_oom(_layout: Layout) -> ! { } ``` -Once all that is in place, the user can finally use the collections in `alloc`. +一旦一切都满足了,用户最终可以在`alloc`中使用集合。 ```rust,ignore #[entry] @@ -120,13 +118,11 @@ fn main() -> ! { } ``` -If you have used the collections in the `std` crate then these will be familiar -as they are exact same implementation. +如果你已经使用了`std` crate中的集合,那么这些对你来说将非常熟悉,因为他们的实现一样。 -## Using `heapless` +## 使用 `heapless` -`heapless` requires no setup as its collections don't depend on a global memory -allocator. Just `use` its collections and proceed to instantiate them: +`heapless`无需设置因为它的集合不依赖一个全局内存分配器。只是`use`它的集合然后实例化它们: ```rust,ignore extern crate heapless; // v0.4.x From d2afb21661a764f5688de0a4874416f120a55f40 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 10:22:46 +0800 Subject: [PATCH 062/137] minor --- src/collections/index.md | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index 0204ad64..72dc3238 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -139,27 +139,15 @@ fn main() -> ! { } ``` -You'll note two differences between these collections and the ones in `alloc`. +你会注意到这些集合与`alloc`中的集合有两个不一样的地方。 -First, you have to declare upfront the capacity of the collection. `heapless` -collections never reallocate and have fixed capacities; this capacity is part of -the type signature of the collection. In this case we have declared that `xs` -has a capacity of 8 elements that is the vector can, at most, hold 8 elements. -This is indicated by the `U8` (see [`typenum`]) in the type signature. +第一,你必须预先声明集合的容量。`heapless`集合从来不会发生重分配且具有固定的容量;这个容量是集合的类型签名的一部分。在这个例子里,我们已经声明了`xs`的容量为8个元素,也就是说,这个vector最多只能有八个元素。这是通过类型签名中的`U8` (看[`typenum`])来指定的。 [`typenum`]: https://crates.io/crates/typenum -Second, the `push` method, and many other methods, return a `Result`. Since the -`heapless` collections have fixed capacity all operations that insert elements -into the collection can potentially fail. The API reflects this problem by -returning a `Result` indicating whether the operation succeeded or not. In -contrast, `alloc` collections will reallocate themselves on the heap to increase -their capacity. +第二,`push`方法和许多其它方法返回的是一个`Result`。因为`heapless`集合有一个固定的容量,所以所有插入的操作可能会失败。通过返回一个`Result`,API反应了这个问题,指出操作是否成功还是失败。相反,`alloc`集合自己将会在堆上重新分配去增加它的容量。 -As of version v0.4.x all `heapless` collections store all their elements inline. -This means that an operation like `let x = heapless::Vec::new();` will allocate -the collection on the stack, but it's also possible to allocate the collection -on a `static` variable, or even on the heap (`Box>`). +自v0.4.x版本起,所有的`heapless`集合将它们所有的元素内联地存储起来了。这意味着像是`let x = heapless::Vec::new()`这样的一个操作将会在栈上分配集合,但是它也能够在一个`static`变量上分配集合,或者甚至在堆上(`Box>`)。 ## Trade-offs @@ -231,11 +219,12 @@ On the other hand fixed capacity collections never reallocate so all operations have a predictable execution time. For example, `heapless::Vec.push` executes in constant time. -### Ease of use +### 易用性 + +`alloc`要求配置一个全局分配器而`heapless`不需要。然而,`heapless`要求你去选择你要实例化的每一个集合的容量。 + +`alloc` API几乎为每一个Rust开发者所熟知。`heapless` API尝试模仿`alloc` API,但是它 -`alloc` requires setting up a global allocator whereas `heapless` does not. -However, `heapless` requires you to pick the capacity of each collection that -you instantiate. The `alloc` API will be familiar to virtually every Rust developer. The `heapless` API tries to closely mimic the `alloc` API but it will never be From 9a9439b7b242d65828a41f806e53898633019415 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 10:38:55 +0800 Subject: [PATCH 063/137] minor --- src/collections/index.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index 72dc3238..2970e7de 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -149,19 +149,16 @@ fn main() -> ! { 自v0.4.x版本起,所有的`heapless`集合将它们所有的元素内联地存储起来了。这意味着像是`let x = heapless::Vec::new()`这样的一个操作将会在栈上分配集合,但是它也能够在一个`static`变量上分配集合,或者甚至在堆上(`Box>`)。 -## Trade-offs +## 取舍 -Keep these in mind when choosing between heap allocated, relocatable collections -and fixed capacity collections. +当在堆分配的可重定位的集合和固定容量的集合间进行选择的时候,记住这些内容。 -### Out Of Memory and error handling +### 内存溢出和错误处理 + +使用堆分配,内存溢出总是有可能出现的且会发生在任何一个集合需要增长的地方: 比如,所有的 `alloc::Vec.push` 调用会潜在地产生一个OOM(Out of Memory)条件。因此一些操作可能会*隐式地*失败。一些`alloc`集合暴露了`try_reserve`方法,可以当增加集合时让你检查潜在的OOM条件,但是你需要主动地使用它们。 + +如果你排他地使用`heapless`集合且不为其它任何东西使用一个内存分配器,那么一个OOM条件不可能出现。相反, -With heap allocations Out Of Memory is always a possibility and can occur in -any place where a collection may need to grow: for example, all -`alloc::Vec.push` invocations can potentially generate an OOM condition. Thus -some operations can *implicitly* fail. Some `alloc` collections expose -`try_reserve` methods that let you check for potential OOM conditions when -growing the collection but you need be proactive about using them. If you exclusively use `heapless` collections and you don't use a memory allocator for anything else then an OOM condition is impossible. Instead, you'll From bfc282482fa7ec64d2314a8a550811e5a391b477 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 10:41:47 +0800 Subject: [PATCH 064/137] minor --- src/collections/index.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index 2970e7de..627fe381 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -220,10 +220,4 @@ constant time. `alloc`要求配置一个全局分配器而`heapless`不需要。然而,`heapless`要求你去选择你要实例化的每一个集合的容量。 -`alloc` API几乎为每一个Rust开发者所熟知。`heapless` API尝试模仿`alloc` API,但是它 - - -The `alloc` API will be familiar to virtually every Rust developer. The -`heapless` API tries to closely mimic the `alloc` API but it will never be -exactly the same due to its explicit error handling -- some developers may feel -the explicit error handling is excessive or too cumbersome. +`alloc` API几乎为每一个Rust开发者所熟知。`heapless` API尝试模仿`alloc` API,但是因为`heapless`的显式错误处理,它们不可能会一模一样 -- 一些开发者可能会觉得显式的错误处理过多或太麻烦。 From d4d5dd06d26ce9420f21b00893862c8df2e3c272 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 11:03:04 +0800 Subject: [PATCH 065/137] minor --- src/collections/index.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index 627fe381..6ef94a52 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -157,14 +157,10 @@ fn main() -> ! { 使用堆分配,内存溢出总是有可能出现的且会发生在任何一个集合需要增长的地方: 比如,所有的 `alloc::Vec.push` 调用会潜在地产生一个OOM(Out of Memory)条件。因此一些操作可能会*隐式地*失败。一些`alloc`集合暴露了`try_reserve`方法,可以当增加集合时让你检查潜在的OOM条件,但是你需要主动地使用它们。 -如果你排他地使用`heapless`集合且不为其它任何东西使用一个内存分配器,那么一个OOM条件不可能出现。相反, +如果你只使用`heapless`集合,而不使用内存分配器,那么一个OOM条件不可能出现。反而,你必须逐个处理容量不足的集合。也就是你必须处理*所有*的`Result`,其由像是`Vec.push`这样的方法返回的。 +OOM失败会比 -If you exclusively use `heapless` collections and you don't use a memory -allocator for anything else then an OOM condition is impossible. Instead, you'll -have to deal with collections running out of capacity on a case by case basis. -That is you'll have deal with *all* the `Result`s returned by methods like -`Vec.push`. OOM failures can be harder to debug than say `unwrap`-ing on all `Result`s returned by `heapless::Vec.push` because the observed location of failure may @@ -173,7 +169,10 @@ returned by `heapless::Vec.push` because the observed location of failure may some other collection was leaking memory (memory leaks are possible in safe Rust). -### Memory usage +### 内存使用 + +推理堆分配集合的内存使用是很难的因为长期使用的集合的大小会在运行时改变。一些操作可能隐式地重分配 + Reasoning about memory usage of heap allocated collections is hard because the capacity of long lived collections can change at runtime. Some operations may From e8c80976ff3a852489b2587120abfb7ed93c9d90 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 12:54:45 +0800 Subject: [PATCH 066/137] minor --- src/collections/index.md | 51 +++++++--------------------------------- 1 file changed, 9 insertions(+), 42 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index 6ef94a52..5516b092 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -159,61 +159,28 @@ fn main() -> ! { 如果你只使用`heapless`集合,而不使用内存分配器,那么一个OOM条件不可能出现。反而,你必须逐个处理容量不足的集合。也就是你必须处理*所有*的`Result`,其由像是`Vec.push`这样的方法返回的。 -OOM失败会比 - - -OOM failures can be harder to debug than say `unwrap`-ing on all `Result`s -returned by `heapless::Vec.push` because the observed location of failure may -*not* match with the location of the cause of the problem. For example, even -`vec.reserve(1)` can trigger an OOM if the allocator is nearly exhausted because -some other collection was leaking memory (memory leaks are possible in safe -Rust). +与在所有由`heapless::Vec.push`返回的`Result`上调用`unwrap`相比,OOM错误更难调试,因为错误被发现的位置可能与导致问题的位置*不*一致。比如,甚至如果分配器接近消耗完`vec.reserve(1)`都能触发一个OOM,因为一些其它的集合正在泄露内存(内存泄露在安全的Rust是会发生的)。 ### 内存使用 -推理堆分配集合的内存使用是很难的因为长期使用的集合的大小会在运行时改变。一些操作可能隐式地重分配 - - -Reasoning about memory usage of heap allocated collections is hard because the -capacity of long lived collections can change at runtime. Some operations may -implicitly reallocate the collection increasing its memory usage, and some -collections expose methods like `shrink_to_fit` that can potentially reduce the -memory used by the collection -- ultimately, it's up to the allocator to decide -whether to actually shrink the memory allocation or not. Additionally, the -allocator may have to deal with memory fragmentation which can increase the -*apparent* memory usage. +推理堆分配集合的内存使用是很难的因为长期使用的集合的大小会在运行时改变。一些操作可能隐式地重分配集合,增加了它的内存使用,一些集合暴露的方法,像是`shrink_to_fit`,会潜在地减少集合使用的内存 -- 最终,它由分配器去决定是否确定减小内存的分配或者不。另外,分配器可能不得不处理内存碎片,它会*明显*增加内存的使用。 -On the other hand if you exclusively use fixed capacity collections, store -most of them in `static` variables and set a maximum size for the call stack -then the linker will detect if you try to use more memory than what's physically -available. +另一方面,如果你只使用固定容量的集合,请把大多数的数据保存在`static`变量中,并为调用栈设置一个最大尺寸,随后如果你尝试使用大于可用的物理内存的内存大小连接器会发现它。 -Furthermore, fixed capacity collections allocated on the stack will be reported -by [`-Z emit-stack-sizes`] flag which means that tools that analyze stack usage -(like [`stack-sizes`]) will include them in their analysis. +另外,在栈上分配的固定容量的集合可以通过[`-Z emit-stack-sizes`]标识来报告,其意味着用来分析栈使用的工具(像是[`stack-sizes`])将会把在栈上分配的集合包含进它们的分析中。 [`-Z emit-stack-sizes`]: https://doc.rust-lang.org/beta/unstable-book/compiler-flags/emit-stack-sizes.html [`stack-sizes`]: https://crates.io/crates/stack-sizes -However, fixed capacity collections can *not* be shrunk which can result in -lower load factors (the ratio between the size of the collection and its -capacity) than what relocatable collections can achieve. +然而,固定容量的集合*不*能被减少,与可重定位集合所能达到的负载系数(集合的大小和它的容量之间的比值)相比,它能产生更低的负载系数。 -### Worst Case Execution Time (WCET) +### 最坏执行时间 (WCET) -If you are building time sensitive applications or hard real time applications -then you care, maybe a lot, about the worst case execution time of the different -parts of your program. +如果你正在搭建时间敏感型应用或者硬实时应用,那么你可能更关心你程序的不同部分的最坏执行时间。 -The `alloc` collections can reallocate so the WCET of operations that may grow -the collection will also include the time it takes to reallocate the collection, -which itself depends on the *runtime* capacity of the collection. This makes it -hard to determine the WCET of, for example, the `alloc::Vec.push` operation as -it depends on both the allocator being used and its runtime capacity. +`alloc`集合能重分配,所以操作的WCET可能会增加,集合也将包括它用来重分配集合所需的时间,它取决于集合的*运行时*容量。这使得它更难去决定操作,比如`alloc::Vec.push`,的WCET,因为它依赖被使用的分配器和它的运行时容量。 -On the other hand fixed capacity collections never reallocate so all operations -have a predictable execution time. For example, `heapless::Vec.push` executes in -constant time. +另一方面固定容量的集合不会重分配,因此所有的操作有个可预期的执行时间。比如,`heapless::Vec.push`以固定时间执行。 ### 易用性 From 975a9e2259797489bc2e2fcd5a829613ce164efa Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 12:57:29 +0800 Subject: [PATCH 067/137] minor --- src/SUMMARY.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 0e274e47..751400e8 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -41,17 +41,17 @@ more information and coordination - [集合](./collections/index.md) - [设计模式](./design-patterns/index.md) - [HALs](./design-patterns/hal/index.md) - - [Checklist](./design-patterns/hal/checklist.md) - - [Naming](./design-patterns/hal/naming.md) + - [列表](./design-patterns/hal/checklist.md) + - [命名](./design-patterns/hal/naming.md) - [互用性](./design-patterns/hal/interoperability.md) - - [Predictability](./design-patterns/hal/predictability.md) + - [可预见性](./design-patterns/hal/predictability.md) - [GPIO](./design-patterns/hal/gpio.md) -- [Tips for embedded C developers](./c-tips/index.md) +- [给嵌入式C开发者的技巧](./c-tips/index.md) - [互用性](./interoperability/index.md) - [A little C with your Rust](./interoperability/c-with-rust.md) - [A little Rust with your C](./interoperability/rust-with-c.md) -- [Unsorted topics](./unsorted/index.md) +- [没有排序的主题](./unsorted/index.md) - [Optimizations: The speed size tradeoff](./unsorted/speed-vs-size.md) - [Performing Math Functionality](./unsorted/math.md) From 9dd9f837b104f75a3e916aebcf85e05621bc3bb9 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 16 Jun 2022 20:17:12 +0800 Subject: [PATCH 068/137] minor --- src/SUMMARY.md | 2 +- src/design-patterns/hal/predictability.md | 11 +++++---- src/interoperability/c-with-rust.md | 20 ++++++++--------- src/interoperability/rust-with-c.md | 27 +++++++++-------------- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 751400e8..6146e6eb 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -46,7 +46,7 @@ more information and coordination - [互用性](./design-patterns/hal/interoperability.md) - [可预见性](./design-patterns/hal/predictability.md) - [GPIO](./design-patterns/hal/gpio.md) -- [给嵌入式C开发者的技巧](./c-tips/index.md) +- [给嵌入式C开发者的贴士](./c-tips/index.md) - [互用性](./interoperability/index.md) - [A little C with your Rust](./interoperability/c-with-rust.md) diff --git a/src/design-patterns/hal/predictability.md b/src/design-patterns/hal/predictability.md index e3181856..4c1dfe52 100644 --- a/src/design-patterns/hal/predictability.md +++ b/src/design-patterns/hal/predictability.md @@ -1,16 +1,15 @@ -# Predictability +# 可预见性 -## Constructors are used instead of extension traits (C-CTOR) +## 使用构造函数而不是扩展traits -All peripherals to which the HAL adds functionality should be wrapped in a new -type, even if no additional fields are required for that functionality. +所有由HAL添加功能的外设应该被封装进一个新类型,即使该功能不需要额外的字段。 -Extension traits implemented for the raw peripheral should be avoided. +应该避免为原始外设扩展traits。 -## Methods are decorated with `#[inline]` where appropriate (C-INLINE) +## 方法在适当的地方用`#[inline`修饰 The Rust compiler does not by default perform full inlining across crate boundaries. As embedded applications are sensitive to unexpected code size diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 89534397..9cf50d59 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -9,16 +9,16 @@ ## 定义接口 -Before consuming C or C++ code from Rust, it is necessary to define (in Rust) what data types and function signatures exist in the linked code. In C or C++, you would include a header (`.h` or `.hpp`) file which defines this data. In Rust, it is necessary to either manually translate these definitions to Rust, or use a tool to generate these definitions. +从Rust消费C或者C++代码之前,必须定义(在Rust中)在被链接的代码中存在什么数据类型和函数签名。在C或者C++中,你要包括一个头文件(`.h`或者`.hpp`),其定义了这个数据。在Rsut中,必须手动地将这些定义翻译成Rust,或者使用一个工具去生成这些定义。 -First, we will cover manually translating these definitions from C/C++ to Rust. +首先,我们将介绍如何将这些定义从C/C++手动转换为Rust。 -### Wrapping C functions and Datatypes +### 封装C函数和数据类型 -Typically, libraries written in C or C++ will provide a header file defining all types and functions used in public interfaces. An example file may look like this: +通常,用C或者C++写的库会提供一个头文件,头文件定义了所有的类型和用于公共接口的函数。一个示例文件可能如下所示: ```C -/* File: cool.h */ +/* 文件: cool.h */ typedef struct CoolStruct { int x; int y; @@ -27,7 +27,7 @@ typedef struct CoolStruct { void cool_function(int i, char c, CoolStruct* cs); ``` -When translated to Rust, this interface would look as such: +当翻译成Rust时,这个接口将看起来像是: ```rust,ignore /* File: cool_bindings.rs */ @@ -44,21 +44,21 @@ pub extern "C" fn cool_function( ); ``` -Let's take a look at this definition one piece at a time, to explain each of the parts. +让我们一次看一个定义,来解释每个部分。 ```rust,ignore #[repr(C)] pub struct CoolStruct { ... } ``` -By default, Rust does not guarantee order, padding, or the size of data included in a `struct`. In order to guarantee compatibility with C code, we include the `#[repr(C)]` attribute, which instructs the Rust compiler to always use the same rules C does for organizing data within a struct. +默认,Rust不会保证包含在一个`struct`中的数据的大小,padding,或者顺序。为了保证与C代码兼容,我们使用`#[repr(C)]`属性,它指示Rust编译器总是使用和C一样的规则去组织一个结构体中的数据。 ```rust,ignore pub x: cty::c_int, pub y: cty::c_int, ``` -Due to the flexibility of how C or C++ defines an `int` or `char`, it is recommended to use primitive data types defined in `cty`, which will map types from C to types in Rust. +由于C或者C++定义一个`int`或者`char`的方式很灵活,所以建议使用在`cty`中定义的基础类型,它将类型从C映射到Rust中的类型。 ```rust,ignore pub extern "C" fn cool_function( ... ); @@ -114,7 +114,7 @@ While your crate may be targeting a `no_std` embedded platform, your `build.rs` [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html -### Building C/C++ code with the `cc` crate +### 使用`cc` crate构建C/C++代码 For projects with limited dependencies or complexity, or for projects where it is difficult to modify the build system to produce a static library (rather than a final binary or executable), it may be easier to instead utilize the [`cc` crate], which provides an idiomatic Rust interface to the compiler provided by the host. diff --git a/src/interoperability/rust-with-c.md b/src/interoperability/rust-with-c.md index 5e596f02..e5fd481b 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -1,31 +1,26 @@ # A little Rust with your C -Using Rust code inside a C or C++ project mostly consists of two parts. +在C或者C++中使用Rust代码通常由两部分组成。 -- Creating a C-friendly API in Rust -- Embedding your Rust project into an external build system +- 用Rust创造一个C友好的API +- 将你的Rust项目嵌入一个外部的编译系统 -Apart from `cargo` and `meson`, most build systems don't have native Rust support. -So you're most likely best off just using `cargo` for compiling your crate and -any dependencies. +除了`cargo`和`meson`,大多数编译系统没有原生Rust支持。因此你最好只用`cargo`编译你的crate和依赖。 -## Setting up a project +## 设置一个项目 -Create a new `cargo` project as usual. - -There are flags to tell `cargo` to emit a systems library, instead of -its regular rust target. -This also allows you to set a different output name for your library, -if you want it to differ from the rest of your crate. +像往常一样创建一个新的`cargo`项目。有一些标志可以告诉`cargo`去生成一个系统库,而不是常规的rust目标文件。如果你想要它与你的crate的其它部分不一样,这也允许你为你的库设置一个不同的输出名。 ```toml [lib] name = "your_crate" -crate-type = ["cdylib"] # Creates dynamic lib -# crate-type = ["staticlib"] # Creates static lib +crate-type = ["cdylib"] # 生成动态链接库 +# crate-type = ["staticlib"] # 生成静态链接库 ``` -## Building a `C` API +## 构建一个`C` API + +因为C++没有稳定的ABI Because C++ has no stable ABI for the Rust compiler to target, we use `C` for any interoperability between different languages. This is no exception when using Rust From 7c44739ee5c97350e73ccc7dc3d6fea6ddb2855e Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 18 Jun 2022 20:15:17 +0800 Subject: [PATCH 069/137] minor --- src/design-patterns/hal/gpio.md | 44 ++++++++++------------- src/design-patterns/hal/predictability.md | 15 +++----- 2 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/design-patterns/hal/gpio.md b/src/design-patterns/hal/gpio.md index 6582070c..b88d10e7 100644 --- a/src/design-patterns/hal/gpio.md +++ b/src/design-patterns/hal/gpio.md @@ -1,16 +1,13 @@ -# Recommendations for GPIO Interfaces +# 关于GPIO接口的建议 -## Pin types are zero-sized by default (C-ZST-PIN) +## Pin类型默认是零大小的(C-ZST-PIN) -GPIO Interfaces exposed by the HAL should provide dedicated zero-sized types for -each pin on every interface or port, resulting in a zero-cost GPIO abstraction -when all pin assignments are statically known. +由HAL暴露的GPIO接口应该为所有接口或者端口上的每一个管脚提供一个专用的零大小类型,从而当所有的管脚分配静态已知时,提供一个零开销抽象。 -Each GPIO Interface or Port should implement a `split` method returning a -struct with every pin. +每个GPIO接口或者端口应该实现一个`split`方法,它返回一个有所有管脚的结构体。 -Example: +案例: ```rust pub struct PA0; @@ -37,15 +34,14 @@ pub struct PortAPins { ``` -## Pin types provide methods to erase pin and port (C-ERASED-PIN) +## 管脚类型提供方法去擦除管脚和端口(C-ERASED-PIN) -Pins should provide type erasure methods that move their properties from -compile time to runtime, and allow more flexibility in applications. +管脚应该提供类型擦除方法去将它们的属性从编译时移到运行时,允许在应用中有更多的灵活性。 -Example: +案例: ```rust -/// Port A, pin 0. +/// 端口 A, 管脚 0。 pub struct PA0; impl PA0 { @@ -54,9 +50,9 @@ impl PA0 { } } -/// A pin on port A. +/// 端口A上的A管脚。 pub struct PA { - /// The pin number. + /// 管脚号。 pin: u8, } @@ -72,7 +68,8 @@ impl PA { pub struct Pin { port: Port, pin: u8, - // (these fields can be packed to reduce the memory footprint) + /// (这些字段) + /// (这些字段可以打包以减少内存占用) } enum Port { @@ -84,18 +81,15 @@ enum Port { ``` -## Pin state should be encoded as type parameters (C-PIN-STATE) +## 管脚状态应该被编码成类型参数 (C-PIN-STATE) -Pins may be configured as input or output with different characteristics -depending on the chip or family. This state should be encoded in the type system -to prevent use of pins in incorrect states. +取决于芯片或者芯片家族,管家可能被配置为具有不同特性的输出或者输入。这个状态应该在类型系统中被编码去避免在错误的状态中使用管脚。 -Additional, chip-specific state (eg. drive strength) may also be encoded in this -way, using additional type parameters. +另外,芯片特定的状态(eg. 驱动强度)可能也用这个办法被编码,使用额外的类型参数。 -Methods for changing the pin state should be provided as `into_input` and -`into_output` methods. +用来改变管脚状态的方法应该被实现成`into_input`和`into_output`方法。 +另外,`with_{input,output}_state`方法应该 Additionally, `with_{input,output}_state` methods should be provided that temporarily reconfigure a pin in a different state without moving it. @@ -124,7 +118,7 @@ Pin state should be bounded by sealed traits. Users of the HAL should have no need to add their own state. The traits can provide HAL-specific methods required to implement the pin state API. -Example: +案例: ```rust # use std::marker::PhantomData; diff --git a/src/design-patterns/hal/predictability.md b/src/design-patterns/hal/predictability.md index 4c1dfe52..1b57f59a 100644 --- a/src/design-patterns/hal/predictability.md +++ b/src/design-patterns/hal/predictability.md @@ -9,15 +9,8 @@ 应该避免为原始外设扩展traits。 -## 方法在适当的地方用`#[inline`修饰 +## 方法在适当的地方用`#[inline]`修饰 -The Rust compiler does not by default perform full inlining across crate -boundaries. As embedded applications are sensitive to unexpected code size -increases, `#[inline]` should be used to guide the compiler as follows: - -* All "small" functions should be marked `#[inline]`. What qualifies as "small" - is subjective, but generally all functions that are expected to compile down - to single-digit instruction sequences qualify as small. -* Functions that are very likely to take constant values as parameters should be - marked as `#[inline]`. This enables the compiler to compute even complicated - initialization logic at compile time, provided the function inputs are known. +Rust编译器默认不会越过crate边界执行完全内联。因为嵌入式应用对于不可预期的代码大小的增加很敏感,`#[inline]`应该如下所示用来指导编译器: +* 所有的"小"函数应该被标记`#[inline]`。什么是"小"是主观的,但是通常所有有希望被编译成一位数的指令序列(single-digit instruction sequences)都可以被视为"小"。 +* 非常有可能把一个常量数值作为你参数的函数应该被标记为`#[inline]`。这让编译器在编译时就可以进行计算甚至是复杂的初始化逻辑,前提是函数输入是已知的。 From 11c3e9d75e712a9be107437bca078f18b2c144b2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 18 Jun 2022 20:25:46 +0800 Subject: [PATCH 070/137] minor --- src/design-patterns/hal/gpio.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/design-patterns/hal/gpio.md b/src/design-patterns/hal/gpio.md index b88d10e7..94021bef 100644 --- a/src/design-patterns/hal/gpio.md +++ b/src/design-patterns/hal/gpio.md @@ -89,12 +89,9 @@ enum Port { 用来改变管脚状态的方法应该被实现成`into_input`和`into_output`方法。 -另外,`with_{input,output}_state`方法应该 -Additionally, `with_{input,output}_state` methods should be provided that -temporarily reconfigure a pin in a different state without moving it. +另外,应该提供`with_{input,output}_state`方法,在一个不同的状态中临时配置一个管脚而不是移动它。 -The following methods should be provided for every pin type (that is, both -erased and non-erased pin types should provide the same API): +应该为每个的管脚类型提供下列的方法(也就是说,已擦除和未擦除的管脚类型应该提供一样的API): * `pub fn into_input(self, input: N) -> Pin` * `pub fn into_output(self, output: N) -> Pin` @@ -113,10 +110,7 @@ erased and non-erased pin types should provide the same API): ) -> R ``` - -Pin state should be bounded by sealed traits. Users of the HAL should have no -need to add their own state. The traits can provide HAL-specific methods -required to implement the pin state API. +管脚状态应该用sealed traits来绑定。HAL的用户应该不需要添加它们自己的状态。这个traits能提供HAL特定的方法,实现管脚状态API需要这些方法。 案例: @@ -195,5 +189,5 @@ impl PA1 { } } -// Same for `PA` and `Pin`, and other pin types. +// 对于`PA`和`Pin`一样的,对于其它管脚类型来说也是。 ``` From b386b49d9dd51c39a70c1487fe724d0c6273ea51 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 18 Jun 2022 20:29:00 +0800 Subject: [PATCH 071/137] minor --- src/design-patterns/hal/gpio.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/design-patterns/hal/gpio.md b/src/design-patterns/hal/gpio.md index 94021bef..c4897b2f 100644 --- a/src/design-patterns/hal/gpio.md +++ b/src/design-patterns/hal/gpio.md @@ -68,8 +68,8 @@ impl PA { pub struct Pin { port: Port, pin: u8, - /// (这些字段) - /// (这些字段可以打包以减少内存占用) + // (这些字段) + // (这些字段可以打包以减少内存占用) } enum Port { From 2a5bc86e4b4e8fca605ce39e299f222010813080 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 18 Jun 2022 20:51:02 +0800 Subject: [PATCH 072/137] minor --- src/design-patterns/hal/interoperability.md | 25 ++++++++------------- src/design-patterns/hal/naming.md | 7 +++--- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/design-patterns/hal/interoperability.md b/src/design-patterns/hal/interoperability.md index 12049020..1e34ec08 100644 --- a/src/design-patterns/hal/interoperability.md +++ b/src/design-patterns/hal/interoperability.md @@ -1,22 +1,16 @@ -# Interoperability +# 互用性 -## Wrapper types provide a destructor method (C-FREE) +## 封装类型提供一个析构方法 (C-FREE) -Any non-`Copy` wrapper type provided by the HAL should provide a `free` method -that consumes the wrapper and returns back the raw peripheral (and possibly -other objects) it was created from. +任何由HAL提供的非`Copy`封装类型应该提供一个`free`方法,这个方法消费封装类且返回最初生成它的外设(可能是其它对象)。 -The method should shut down and reset the peripheral if necessary. Calling `new` -with the raw peripheral returned by `free` should not fail due to an unexpected -state of the peripheral. +如果有必要方法应该关闭和重置外设。使用由`free`返回的原始外设调用`new`不应该由于设备的意外状态而失败, -If the HAL type requires other non-`Copy` objects to be constructed (for example -I/O pins), any such object should be released and returned by `free` as well. -`free` should return a tuple in that case. +如果HAL类型要求构造其它的非`Copy`对象(比如 I/O 管脚),任何这样的对象应该也由`free`返回和释放。`free`应该返回一个元组。 -For example: +比如: ```rust # pub struct TIMER0; @@ -34,12 +28,11 @@ impl Timer { ``` -## HALs reexport their register access crate (C-REEXPORT-PAC) +## HALs重新导出它们的寄存器访问cra(C-REEXPORT-PAC) -HALs can be written on top of [svd2rust]-generated PACs, or on top of other -crates that provide raw register access. HALs should always reexport the -register access crate they are based on in their crate root. +HALs能被编写在[svd2rust]生成的PACs之上,或在其它提供纯寄存器访问的crates之上。HALs应该总是能在它们的crate root中重新导出它们所基于的寄存器访问crate +一个PAC应该 A PAC should be reexported under the name `pac`, regardless of the actual name of the crate, as the name of the HAL should already make it clear what PAC is being accessed. diff --git a/src/design-patterns/hal/naming.md b/src/design-patterns/hal/naming.md index 65877d3e..8cd562c1 100644 --- a/src/design-patterns/hal/naming.md +++ b/src/design-patterns/hal/naming.md @@ -1,9 +1,8 @@ -# Naming +# 命名 ## The crate is named appropriately (C-CRATE-NAME) +## crate被恰当地命名(C-CRATE-NAME) -HAL crates should be named after the chip or family of chips they aim to -support. Their name should end with `-hal` to distinguish them from register -access crates. The name should not contain underscores (use dashes instead). +HAL crates应该在它目标支持的芯片或者芯片家族之后被命名。它们的名字应该以`-hal`结尾,为了将它们与寄存器访问crates区分开来。名字不应该包含下划线(请改用破折号)。 From ac1e9a75a17854cddd35480a708502524f9b776f Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 19 Jun 2022 19:37:09 +0800 Subject: [PATCH 073/137] minor --- book.toml | 3 +- src/c-tips/index.md | 55 +++++++-------------- src/design-patterns/hal/interoperability.md | 12 ++--- src/design-patterns/hal/naming.md | 1 - 4 files changed, 25 insertions(+), 46 deletions(-) diff --git a/book.toml b/book.toml index fa5b7e3a..4ed695de 100644 --- a/book.toml +++ b/book.toml @@ -5,4 +5,5 @@ src = "src" title = "The Embedded Rust Book" [output.html] -git-repository-url = "/~https://github.com/rust-embedded/book" +# git-repository-url = "/~https://github.com/rust-embedded/book" +git-repository-url = "/~https://github.com/xxchang/book" diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 8afd0b92..0845fff7 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -1,37 +1,24 @@ -# Tips for embedded C developers +# 给嵌入式C开发者的贴士 -This chapter collects a variety of tips that might be useful to experienced -embedded C developers looking to start writing Rust. It will especially -highlight how things you might already be used to in C are different in Rust. +这个章节收集了可能对于正在寻求开始编写Rust有经验的嵌入式C开发者有用的各种各样的贴士。它将解释你在C中可能已经用到的那些东西与Rust中有多不同。 -## Preprocessor +## 预处理器 -In embedded C it is very common to use the preprocessor for a variety of -purposes, such as: +在嵌入式C中,为了各种各样的目的使用预处理器是很常见的,比如: -* Compile-time selection of code blocks with `#ifdef` -* Compile-time array sizes and computations -* Macros to simplify common patterns (to avoid function call overhead) +* 使用`#ifdef`编译时选择代码块 +* 编译时的数组大小和计算 +* 用来简化常见模式的宏(避免函数调用的开销) -In Rust there is no preprocessor, and so many of these use cases are addressed -differently. In the rest of this section we cover various alternatives to -using the preprocessor. +在Rust中没有预处理器,所以许多用例有不同的处理方法。本章节剩下的部分,我们将介绍使用预处理器的各种替代方法。 -### Compile-Time Code Selection +### 编译时的代码选择 -The closest match to `#ifdef ... #endif` in Rust are [Cargo features]. These -are a little more formal than the C preprocessor: all possible features are -explicitly listed per crate, and can only be either on or off. Features are -turned on when you list a crate as a dependency, and are additive: if any crate -in your dependency tree enables a feature for another crate, that feature will -be enabled for all users of that crate. +Rust中最接近`#ifdef ... #endif`的是[Cargo features]。这些比C预处理器更正式一点: 每个crate显式列举的所有可能的features只能是关了的或者打开了的。当你把一个crate列为依赖项时,Features被打开,且是可添加的: 如果你依赖树中的任何crate为另一个crate打开了一个feature,那么这个feature将为所有那个crate的用户而打开。 [Cargo features]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section -For example, you might have a crate which provides a library of signal -processing primitives. Each one might take some extra time to compile or -declare some large table of constants which you'd like to avoid. You could -declare a Cargo feature for each component in your `Cargo.toml`: +比如,你可能有一个crate,其提供一个信号处理的原语库(library of signal processing primitives)。每个原语可能带来一些额外的时间去编译大量的常量,你想要躲开这些常量。你可以为你的`Cargo.toml`中每个组件声明一个Cargo feature。 ```toml [features] @@ -39,11 +26,10 @@ FIR = [] IIR = [] ``` -Then, in your code, use `#[cfg(feature="FIR")]` to control what is included. +然后,在你的代码中,使用`#[cfg(feature="FIR")]`去控制什么东西应该被包含。 ```rust -/// In your top-level lib.rs - +/// 在你的顶层的lib.rs中 #[cfg(feature="FIR")] pub mod fir; @@ -51,19 +37,16 @@ pub mod fir; pub mod iir; ``` -You can similarly include code blocks only if a feature is _not_ enabled, or if -any combination of features are or are not enabled. +同样地,你可以控制,只有当某个feature _没有_ 被打开时,包含代码块,或者某些features的组合被打开或者被关闭时。 -Additionally, Rust provides a number of automatically-set conditions you can -use, such as `target_arch` to select different code based on architecture. For -full details of the conditional compilation support, refer to the -[conditional compilation] chapter of the Rust reference. +另外,Rust提供许多你可以使用的自动配置了的条件,比如`target_arch`用来选择不同的代码所基于的架构。对于条件编译的全部细节,可以参看the Rust reference的[conditional compilation]章节。 [conditional compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html -The conditional compilation will only apply to the next statement or block. If -a block can not be used in the current scope then the `cfg` attribute will -need to be used multiple times. It's worth noting that most of the time it is +条件编译将只应用于下一条语句或者块。如果一个块不能在现在的作用域中被使用,那么`cfg`属性将需要被多次使用。值得注意的是 + + + It's worth noting that most of the time it is better to simply include all the code and allow the compiler to remove dead code when optimising: it's simpler for you and your users, and in general the compiler will do a good job of removing unused code. diff --git a/src/design-patterns/hal/interoperability.md b/src/design-patterns/hal/interoperability.md index 1e34ec08..87cad5e9 100644 --- a/src/design-patterns/hal/interoperability.md +++ b/src/design-patterns/hal/interoperability.md @@ -32,19 +32,15 @@ impl Timer { HALs能被编写在[svd2rust]生成的PACs之上,或在其它提供纯寄存器访问的crates之上。HALs应该总是能在它们的crate root中重新导出它们所基于的寄存器访问crate -一个PAC应该 -A PAC should be reexported under the name `pac`, regardless of the actual name -of the crate, as the name of the HAL should already make it clear what PAC is -being accessed. +一个PAC应该被重新导出在名字`pac`下,无论这个crate实际的名字是什么,因为HAL的名字应该已经明确了正被访问的是什么PAC 。 [svd2rust]: /~https://github.com/rust-embedded/svd2rust -## Types implement the `embedded-hal` traits (C-HAL-TRAITS) +## 类型实现`embedded-hal` traits (C-HAL-TRAITS) -Types provided by the HAL should implement all applicable traits provided by the -[`embedded-hal`] crate. +HAL提供的类型应该实现所有的由[`embedded-hal`] crate提供的能用的traits。 -Multiple traits may be implemented for the same type. +同个类型可能实现多个traits。 [`embedded-hal`]: /~https://github.com/rust-embedded/embedded-hal diff --git a/src/design-patterns/hal/naming.md b/src/design-patterns/hal/naming.md index 8cd562c1..6057cbef 100644 --- a/src/design-patterns/hal/naming.md +++ b/src/design-patterns/hal/naming.md @@ -2,7 +2,6 @@ -## The crate is named appropriately (C-CRATE-NAME) ## crate被恰当地命名(C-CRATE-NAME) HAL crates应该在它目标支持的芯片或者芯片家族之后被命名。它们的名字应该以`-hal`结尾,为了将它们与寄存器访问crates区分开来。名字不应该包含下划线(请改用破折号)。 From e90cc880ff800d72b721f2b1b6d2bd43aed5fe2a Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 19 Jun 2022 20:25:27 +0800 Subject: [PATCH 074/137] minor --- src/SUMMARY.md | 4 ++-- src/c-tips/index.md | 30 +++++++++++------------------- src/unsorted/index.md | 2 +- src/unsorted/math.md | 19 +++++++------------ src/unsorted/speed-vs-size.md | 2 +- 5 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 6146e6eb..1122e5f5 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -52,8 +52,8 @@ more information and coordination - [A little C with your Rust](./interoperability/c-with-rust.md) - [A little Rust with your C](./interoperability/rust-with-c.md) - [没有排序的主题](./unsorted/index.md) - - [Optimizations: The speed size tradeoff](./unsorted/speed-vs-size.md) - - [Performing Math Functionality](./unsorted/math.md) + - [优化: 速度与大小间的均衡](./unsorted/speed-vs-size.md) + - [执行数学运算](./unsorted/math.md) --- diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 0845fff7..22bbb643 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -43,20 +43,12 @@ pub mod iir; [conditional compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html -条件编译将只应用于下一条语句或者块。如果一个块不能在现在的作用域中被使用,那么`cfg`属性将需要被多次使用。值得注意的是 - - - It's worth noting that most of the time it is -better to simply include all the code and allow the compiler to remove dead -code when optimising: it's simpler for you and your users, and in general the -compiler will do a good job of removing unused code. +条件编译将只应用于下一条语句或者块。如果一个块不能在现在的作用域中被使用,那么`cfg`属性将需要被多次使用。值得注意的是大多数时间,仅是包含所有的代码,让编译器在优化时去删除死代码(dead code)更好,通常,在移除不使用的代码方面的工作,编译器做得很好。 ### Compile-Time Sizes and Computation +### 编译时大小和计算 -Rust supports `const fn`, functions which are guaranteed to be evaluable at -compile-time and can therefore be used where constants are required, such as -in the size of arrays. This can be used alongside features mentioned above, -for example: +Rust支持`const fn`,`const fn`是在编译时可以被计算的函数,因此可以被用在需要常量的地方,比如在数组的大小中。这个能与上述的features一起使用,比如: ```rust const fn array_size() -> usize { @@ -69,12 +61,12 @@ const fn array_size() -> usize { static BUF: [u32; array_size()] = [0u32; array_size()]; ``` -These are new to stable Rust as of 1.31, so documentation is still sparse. The -functionality available to `const fn` is also very limited at the time of -writing; in future Rust releases it is expected to expand on what is permitted -in a `const fn`. +这些对于stable版本的Rust来说是新的特性,从1.31开始引入,因此文档依然很少。在写这篇文章的时候`const fn`可用的功能也非常有限; 在未来的Rust release版本中,我们可以期望`const fn`将带来更多的功能。 + +### 宏 + +Rust提供一个极度强大的[宏系统]。虽然C预处理器 -### Macros Rust provides an extremely powerful [macro system]. While the C preprocessor operates almost directly on the text of your source code, the Rust macro system @@ -85,7 +77,7 @@ item, or pattern. Procedural macros are more complex but permit extremely powerful additions to the Rust language: they can transform arbitrary Rust syntax into new Rust syntax. -[macro system]: https://doc.rust-lang.org/book/ch19-06-macros.html +[宏系统]: https://doc.rust-lang.org/book/ch19-06-macros.html In general, where you might have used a C preprocessor macro, you probably want to see if a macro-by-example can do the job instead. They can be defined in @@ -385,9 +377,9 @@ Rust Reference. [type layout]: https://doc.rust-lang.org/reference/type-layout.html -## Other Resources +## 其它资源 -* In this book: +* 在这本书中: * [A little C with your Rust](../interoperability/c-with-rust.md) * [A little Rust with your C](../interoperability/rust-with-c.md) * [The Rust Embedded FAQs](https://docs.rust-embedded.org/faq.html) diff --git a/src/unsorted/index.md b/src/unsorted/index.md index 4be66549..cb8f7a4e 100644 --- a/src/unsorted/index.md +++ b/src/unsorted/index.md @@ -1 +1 @@ -# Unsorted topics +# 没有排序的主题 diff --git a/src/unsorted/math.md b/src/unsorted/math.md index 91271710..60101c78 100644 --- a/src/unsorted/math.md +++ b/src/unsorted/math.md @@ -1,11 +1,9 @@ -# Performing math functionality with `#[no_std]` +# 在`#[no_std]`下执行数学运算 -If you want to perform math related functionality like calculating the squareroot or -the exponential of a number and you have the full standard library available, your code -might look like this: +如果你想要执行数学相关的功能,像是计算平方根或者一个数的指数且你有完整的标准库支持,你的代码可能看起来像这个: ```rs -//! Some mathematical functions with standard support available +//! 可用一些标准支持的数学函数 fn main() { let float: f32 = 4.82832; @@ -26,9 +24,7 @@ fn main() { } ``` -Without standard library support, these functions are not available. -An external crate like [`libm`](https://crates.io/crates/libm) can be used instead. The example code -would then look like this: +没有标准库支持的时候,这些函数不可用。反而像是[`libm`](https://crates.io/crates/libm)这样一个外部库可以被使用。示例的代码将会看起来像这个: ```rs #![no_main] @@ -58,16 +54,15 @@ fn main() -> ! { exponential_of_four ) .unwrap(); - // exit QEMU - // NOTE do not run this on hardware; it can corrupt OpenOCD state + // 退出QEMU + // 注意不要在硬件上允许这个; 它能破坏OpenOCD的状态 // debug::exit(debug::EXIT_SUCCESS); loop {} } ``` -If you need to perform more complex operations like DSP signal processing or advanced linear -algebra on your MCU, the following crates might help you +如果你需要在你的MCU上执行更复杂的操作,像是DSP信号处理或者更高级的线性代数,下列的crates可能可以帮助你 - [CMSIS DSP library binding](/~https://github.com/jacobrosenthal/cmsis-dsp-sys) - [`micromath`](/~https://github.com/tarcieri/micromath) diff --git a/src/unsorted/speed-vs-size.md b/src/unsorted/speed-vs-size.md index 6538d94b..306ad41f 100644 --- a/src/unsorted/speed-vs-size.md +++ b/src/unsorted/speed-vs-size.md @@ -1,5 +1,5 @@ # Optimizations: the speed size tradeoff - +# 优化: 速度与大小之间的均衡 Everyone wants their program to be super fast and super small but it's usually not possible to have both characteristics. This section discusses the different optimization levels that `rustc` provides and how they affect the From 13ef608f886b9fed47aa059957c12d00127344c9 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 12:22:27 +0800 Subject: [PATCH 075/137] minor --- src/c-tips/index.md | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 22bbb643..c23bc6c7 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -65,27 +65,13 @@ static BUF: [u32; array_size()] = [0u32; array_size()]; ### 宏 -Rust提供一个极度强大的[宏系统]。虽然C预处理器 - - -Rust provides an extremely powerful [macro system]. While the C preprocessor -operates almost directly on the text of your source code, the Rust macro system -operates at a higher level. There are two varieties of Rust macro: _macros by -example_ and _procedural macros_. The former are simpler and most common; they -look like function calls and can expand to a complete expression, statement, -item, or pattern. Procedural macros are more complex but permit extremely -powerful additions to the Rust language: they can transform arbitrary Rust -syntax into new Rust syntax. +Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在你的源代码之上进行操作,但是Rust宏系统可以在一个更高的级别上操作。存在两种C宏: _声明宏_ 和 _过程宏_ 。前者更简单也最常见; 它们看起来像是函数调用,且能扩展成一个完整的表达式,语句,项目,或者模式。过程宏更复杂但是却能让Rust更强大: 它们可以把任一条Rust语法变成一个新的Rust语法。 [宏系统]: https://doc.rust-lang.org/book/ch19-06-macros.html -In general, where you might have used a C preprocessor macro, you probably want -to see if a macro-by-example can do the job instead. They can be defined in -your crate and easily used by your own crate or exported for other users. Be -aware that since they must expand to complete expressions, statements, items, -or patterns, some use cases of C preprocessor macros will not work, for example -a macro that expands to part of a variable name or an incomplete set of items -in a list. +通常,你可能想知道在那些你可能使用一个C预处理器宏的地方,能否使用一个声明宏做同样的工作。你能在你的crate中定义它们,且在你的crate中轻松使用它们或者导出给其他人用。但是请注意,因为它们必须扩展成完整的表达式,语句,项或者模式,因此C预处理器的某些用例将无法工作,比如扩展成一个变量名的一部分或者一个列表中不完整的项目集。 + +和Cargo features一样,值得考虑下你是否真的需要宏。在一些例子中一个常规的函数更容易去理解且将被内联成和一个宏一样的代码。`#[inline]`和`#[inline(always)]` [attributes] 能让你更深入控制这个过程,虽然这里也要小心 - As with Cargo features, it is worth considering if you even need the macro. In many cases a regular function is easier to understand and will be inlined to @@ -97,10 +83,9 @@ to decreased performance. [attributes]: https://doc.rust-lang.org/reference/attributes.html#inline-attribute -Explaining the entire Rust macro system is out of scope for this tips page, so -you are encouraged to consult the Rust documentation for full details. +研究完整的Rust宏系统超出了本节内容,因此我们鼓励你去查阅Rust文档了解完整的细节。 -## Build System +## 编译系统 Most Rust crates are built using Cargo (although it is not required). This takes care of many difficult problems with traditional build systems. However, From c1e65652aa8207d38143bd2aea4f740567feb069 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 14:29:44 +0800 Subject: [PATCH 076/137] minor --- src/c-tips/index.md | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index c23bc6c7..b4fa86d2 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -71,15 +71,7 @@ Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在 通常,你可能想知道在那些你可能使用一个C预处理器宏的地方,能否使用一个声明宏做同样的工作。你能在你的crate中定义它们,且在你的crate中轻松使用它们或者导出给其他人用。但是请注意,因为它们必须扩展成完整的表达式,语句,项或者模式,因此C预处理器的某些用例将无法工作,比如扩展成一个变量名的一部分或者一个列表中不完整的项目集。 -和Cargo features一样,值得考虑下你是否真的需要宏。在一些例子中一个常规的函数更容易去理解且将被内联成和一个宏一样的代码。`#[inline]`和`#[inline(always)]` [attributes] 能让你更深入控制这个过程,虽然这里也要小心 - - -As with Cargo features, it is worth considering if you even need the macro. In -many cases a regular function is easier to understand and will be inlined to -the same code as a macro. The `#[inline]` and `#[inline(always)]` [attributes] -give you further control over this process, although care should be taken here -as well — the compiler will automatically inline functions from the same crate -where appropriate, so forcing it to do so inappropriately might actually lead -to decreased performance. +和Cargo features一样,值得考虑下你是否真的需要宏。在一些例子中一个常规的函数更容易被理解,它也能被内联成和一个和宏一样的代码。`#[inline]`和`#[inline(always)]` [attributes] 能让你更深入控制这个过程,这里也要小心 - 编译器将自动地从同一个crate的合适的的地方内联函数,因此不恰当地强迫它内联函数实际可能会导致性能下降。 [attributes]: https://doc.rust-lang.org/reference/attributes.html#inline-attribute @@ -87,28 +79,22 @@ to decreased performance. ## 编译系统 -Most Rust crates are built using Cargo (although it is not required). This -takes care of many difficult problems with traditional build systems. However, -you may wish to customise the build process. Cargo provides [`build.rs` -scripts] for this purpose. They are Rust scripts which can interact with the -Cargo build system as required. +大多数Rust crates使用Cargo编译 (即使它不是必须的)。这解决了传统编译系统带来的许多难题。然而,你可能希望自定义编译过程。为了实现这个目的,Cargo提供了[`build.rs`脚本]。它们是可以根据需要与Cargo编译系统进行交互的Rust脚本。 -[`build.rs` scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html +[`build.rs`脚本]: https://doc.rust-lang.org/cargo/reference/build-scripts.html -Common use cases for build scripts include: +与编译脚本有关的常见用例包括: -* provide build-time information, for example statically embedding the build - date or Git commit hash into your executable -* generate linker scripts at build time depending on selected features or other - logic -* change the Cargo build configuration -* add extra static libraries to link against +* 提供编译时信息,比如静态嵌入编译日期或者Git commit hash进你的可执行文件中 +* 根据被选择的features或者其它逻辑在编译时生成链接脚本 +* 改变Cargo的编译配置 +* 添加额外的静态链接库以进行链接 At present there is no support for post-build scripts, which you might traditionally have used for tasks like automatic generation of binaries from the build objects or printing build information. -### Cross-Compiling +### 交叉编译 Using Cargo for your build system also simplifies cross-compiling. In most cases it suffices to tell Cargo `--target thumbv6m-none-eabi` and find a From ba5e6a6bfa6cef0d0aedfb74c3e262afdc1c5a70 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 15:27:16 +0800 Subject: [PATCH 077/137] minor --- src/c-tips/index.md | 66 +++++++++++++-------------------------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index b4fa86d2..102003c9 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -90,25 +90,19 @@ Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在 * 改变Cargo的编译配置 * 添加额外的静态链接库以进行链接 -At present there is no support for post-build scripts, which you might -traditionally have used for tasks like automatic generation of binaries from -the build objects or printing build information. +现在还不支持post-build脚本,你通常将它用于像是从编译的对象自动生生成二进制文件或者打印编译信息这类任务。 ### 交叉编译 -Using Cargo for your build system also simplifies cross-compiling. In most -cases it suffices to tell Cargo `--target thumbv6m-none-eabi` and find a -suitable executable in `target/thumbv6m-none-eabi/debug/myapp`. +为你的编译系统使用Cargo也能简化交叉编译。在大多数例子里,告诉Cargo `--target thumbv6m-none-eabi`就行了,它会在`target/thumbv6m-none-eabi/debug/myapp`找到一个合适的可执行文件。 -For platforms not natively supported by Rust, you will need to build `libcore` -for that target yourself. On such platforms, [Xargo] can be used as a stand-in -for Cargo which automatically builds `libcore` for you. +对于那些并不是Rust原生支持的平台,你将需要自己为那个目标平台编译`libcore`。遇到这样的平台,[Xargo]可以作为Cargo的替代来使用,它可以自动地为你编译`libcore`。 [Xargo]: /~https://github.com/japaric/xargo -## Iterators vs Array Access +## 迭代器与数组访问 -In C you are probably used to accessing arrays directly by their index: +在C中,你可能习惯于通过索引直接访问数组: ```c int16_t arr[16]; @@ -118,13 +112,9 @@ for(i=0; i Date: Mon, 20 Jun 2022 15:34:55 +0800 Subject: [PATCH 078/137] minor --- src/c-tips/index.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 102003c9..20b92547 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -65,7 +65,7 @@ static BUF: [u32; array_size()] = [0u32; array_size()]; ### 宏 -Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在你的源代码之上进行操作,但是Rust宏系统可以在一个更高的级别上操作。存在两种C宏: _声明宏_ 和 _过程宏_ 。前者更简单也最常见; 它们看起来像是函数调用,且能扩展成一个完整的表达式,语句,项目,或者模式。过程宏更复杂但是却能让Rust更强大: 它们可以把任一条Rust语法变成一个新的Rust语法。 +Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在你的源代码之上进行操作,但是Rust宏系统可以在一个更高的级别上操作。存在两种Rust宏: _声明宏_ 和 _过程宏_ 。前者更简单也最常见; 它们看起来像是函数调用,且能扩展成一个完整的表达式,语句,项目,或者模式。过程宏更复杂但是却能让Rust更强大: 它们可以把任一条Rust语法变成一个新的Rust语法。 [宏系统]: https://doc.rust-lang.org/book/ch19-06-macros.html @@ -151,53 +151,55 @@ for element in arr.iter() { [`core::ptr::read_volatile`]: https://doc.rust-lang.org/core/ptr/fn.read_volatile.html [`core::ptr::write_volatile`]: https://doc.rust-lang.org/core/ptr/fn.write_volatile.html -For example, in C you might write: +比如,在C中你可能这样写: ```c volatile bool signalled = false; void ISR() { - // Signal that the interrupt has occurred + // 提醒中断已经发生了 signalled = true; } void driver() { while(true) { - // Sleep until signalled + // 睡眠直到信号来了 while(!signalled) { WFI(); } - // Reset signalled indicator + // 重置信号提示符 signalled = false; - // Perform some task that was waiting for the interrupt + // 执行一些正在等待这个中断的任务 run_task(); } } ``` -The equivalent in Rust would use volatile methods on each access: +在Rust中对每个访问使用volatile方法能达到相同的效果: ```rust,ignore static mut SIGNALLED: bool = false; #[interrupt] fn ISR() { - // Signal that the interrupt has occurred - // (In real code, you should consider a higher level primitive, - // such as an atomic type). + // 提醒中断已经发生 + // (在正在的代码中,你应该考虑一个更高级的基本类型, + // 比如一个原子类型) unsafe { core::ptr::write_volatile(&mut SIGNALLED, true) }; } fn driver() { loop { - // Sleep until signalled + // 睡眠直到信号来了 while unsafe { !core::ptr::read_volatile(&SIGNALLED) } {} - // Reset signalled indicator + // 重置信号指示符 unsafe { core::ptr::write_volatile(&mut SIGNALLED, false) }; - // Perform some task that was waiting for the interrupt + // 执行一些正在等待中断的任务 run_task(); } } ``` +在示例代码中有些事情值得注意: +* 我们可以 A few things are worth noting in the code sample: * We can pass `&mut SIGNALLED` into the function requiring `*mut T`, since `&mut T` automatically converts to a `*mut T` (and the same for `*const T`) From af864599f4be2117ffe21e8f4fdb20de09a1655d Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 15:48:20 +0800 Subject: [PATCH 079/137] minor --- src/SUMMARY.md | 2 +- src/appendix/glossary.md | 16 ++++++---------- src/c-tips/index.md | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 1122e5f5..764d081b 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -57,4 +57,4 @@ more information and coordination --- -[Appendix A: Glossary](./appendix/glossary.md) +[附录A: 词汇表](./appendix/glossary.md) diff --git a/src/appendix/glossary.md b/src/appendix/glossary.md index 6a898b49..2eb5fb0f 100644 --- a/src/appendix/glossary.md +++ b/src/appendix/glossary.md @@ -1,8 +1,6 @@ -# Appendix A: Glossary +# 附录A: 词汇表 -The embedded ecosystem is full of different protocols, hardware components and -vendor-specific things that use their own terms and abbreviations. This Glossary -attempts to list them with pointers for understanding them better. +嵌入式生态系统充满了不同的协议,硬件组件,还有许多与生产商相关的东西,它们都使用自己的缩写和项目名。这个词汇表尝试列出它们以便更高理解它们。 ### BSP @@ -41,24 +39,22 @@ or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). ### SPI -Serial Peripheral Interface. See [here][spi] for more information. +串行外设接口。看[这里][spi]获取更多信息。 [spi]: https://en.wikipedia.org/wiki/Serial_peripheral_interface ### SVD -System View Description is an XML file format used to describe the programmers view of a -microcontroller device. You can read more about it on -[the ARM CMSIS documentation site](https://www.keil.com/pack/doc/CMSIS/SVD/html/index.html). +系统视图描述文件(System View Description)是一个XML文件格式,被用来描述一个微控制器设备的程序员视角。你能在[the ARM CMSIS documentation site](https://www.keil.com/pack/doc/CMSIS/SVD/html/index.html)上获取更多信息。 ### UART -Universal asynchronous receiver-transmitter. See [here][uart] for more information. +通用异步收发器。看[这里][uart]获取更多信息。 [uart]: https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter ### USART -Universal synchronous and asynchronous receiver-transmitter. See [here][usart] for more information. +通用同步异步收发器。看[这里][usart]获取更多信息。 [usart]: https://en.wikipedia.org/wiki/Universal_synchronous_and_asynchronous_receiver-transmitter diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 20b92547..2e529d98 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -79,7 +79,7 @@ Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在 ## 编译系统 -大多数Rust crates使用Cargo编译 (即使它不是必须的)。这解决了传统编译系统带来的许多难题。然而,你可能希望自定义编译过程。为了实现这个目的,Cargo提供了[`build.rs`脚本]。它们是可以根据需要与Cargo编译系统进行交互的Rust脚本。 +大多数Rust crates使用Cargo编译 (即使这不是必须的)。这解决了传统编译系统带来的许多难题。然而,你可能希望自定义编译过程。为了实现这个目的,Cargo提供了[`build.rs`脚本]。它们是可以根据需要与Cargo编译系统进行交互的Rust脚本。 [`build.rs`脚本]: https://doc.rust-lang.org/cargo/reference/build-scripts.html From 467dbc59b47662ff9afd5dbf27242e8b86a9d424 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 16:59:44 +0800 Subject: [PATCH 080/137] minor --- src/c-tips/index.md | 66 ++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 2e529d98..d4ec99ee 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -199,33 +199,18 @@ fn driver() { ``` 在示例代码中有些事情值得注意: -* 我们可以 -A few things are worth noting in the code sample: - * We can pass `&mut SIGNALLED` into the function requiring `*mut T`, since - `&mut T` automatically converts to a `*mut T` (and the same for `*const T`) - * We need `unsafe` blocks for the `read_volatile`/`write_volatile` methods, - since they are `unsafe` functions. It is the programmer's responsibility - to ensure safe use: see the methods' documentation for further details. - -It is rare to require these functions directly in your code, as they will -usually be taken care of for you by higher-level libraries. For memory mapped -peripherals, the peripheral access crates will implement volatile access -automatically, while for concurrency primitives there are better abstractions -available (see the [Concurrency chapter]). - -[Concurrency chapter]: ../concurrency/index.md - -## Packed and Aligned Types - -In embedded C it is common to tell the compiler a variable must have a certain -alignment or a struct must be packed rather than aligned, usually to meet -specific hardware or protocol requirements. - -In Rust this is controlled by the `repr` attribute on a struct or union. The -default representation provides no guarantees of layout, so should not be used -for code that interoperates with hardware or C. The compiler may re-order -struct members or insert padding and the behaviour may change with future -versions of Rust. + * 我们可以把`&mut SIGNALLED`传递给要求`*mut T`的函数中,因为`&mut T`会自动转换成一个`*mut T` (对于`*const T`来说是一样的) + * 我们需要为`read_volatile`/`write_volatile`方法使用`unsafe`块,因为它们是`unsafe`的函数。确保操作安全变成了程序员的责任: 看方法的文档获得更多细节。 + +在你的代码中直接使用这些函数是很少见的,因为它们通常由更高级的库封装起来为你提供服务。对于存储映射的外设,提供外设访问的crates将自动实现volatile访问,而对于并发的基本类型,存在更好的抽象可用。(看[并发章节]) + +[并发章节]: ../concurrency/index.md + +## 填充和对齐类型 + +在嵌入式C中,告诉编译器一个变量必须遵守某个对齐或者一个结构体必须被填充而不是对齐,是很常见的行为,通常是为了满足特定的硬件或者协议要求。 + +在Rust中,这由一个结构体或者联合体上的`repr`属性来控制。默认的表示(representation)不保障布局,因此不应该被用于与硬件或者C互用的代码。编译器可能会对结构体成员重新排序或者插入填充,且这种行为可能在未来的Rust版本中改变。 ```rust struct Foo { @@ -240,10 +225,10 @@ fn main() { } // 0x7ffecb3511d0 0x7ffecb3511d4 0x7ffecb3511d2 -// Note ordering has been changed to x, z, y to improve packing. +// 注意为了改进填充,顺序已经被变成了x, z, y ``` -To ensure layouts that are interoperable with C, use `repr(C)`: +使用`repr(C)`可以确保布局可以与C互用。 ```rust #[repr(C)] @@ -259,8 +244,8 @@ fn main() { } // 0x7fffd0d84c60 0x7fffd0d84c62 0x7fffd0d84c64 -// Ordering is preserved and the layout will not change over time. -// `z` is two-byte aligned so a byte of padding exists between `y` and `z`. +// 顺序被保留了,布局将不会随着时间而改变 +// `z`是两个字节对齐,因此在`y`和`z`之间填充了一个字节。 ``` To ensure a packed representation, use `repr(packed)`: @@ -280,13 +265,12 @@ fn main() { } // 0x7ffd33598490 0x7ffd33598492 0x7ffd33598493 -// No padding has been inserted between `y` and `z`, so now `z` is unaligned. +// 在`y`和`z`没有填充被插入,因此现在`z`没有被对齐。 ``` -Note that using `repr(packed)` also sets the alignment of the type to `1`. +注意使用`repr(packed)`也会将类型的对齐设置成`1` 。 -Finally, to specify a specific alignment, use `repr(align(n))`, where `n` is -the number of bytes to align to (and must be a power of two): +最终,为了指定一个特定的对齐,使用`repr(align(n))`,`n`是要对齐的字节数(必须是2的幂): ```rust #[repr(C)] @@ -306,17 +290,13 @@ fn main() { // 0x7ffec909a000 0x7ffec909a002 0x7ffec909a004 // 0x7ffec909b000 0x7ffec909b002 0x7ffec909b004 -// The two instances `u` and `v` have been placed on 4096-byte alignments, -// evidenced by the `000` at the end of their addresses. +// `u`和`v`两个实例已经被放置在4096字节的对齐上。 +// 它们地址结尾处的`000`证明了这件事。 ``` -Note we can combine `repr(C)` with `repr(align(n))` to obtain an aligned and -C-compatible layout. It is not permissible to combine `repr(align(n))` with -`repr(packed)`, since `repr(packed)` sets the alignment to `1`. It is also not -permissible for a `repr(packed)` type to contain a `repr(align(n))` type. +注意我们可以结合`repr(C)`和`repr(align(n))`来获取一个对齐的c兼容的布局。不允许将`repr(align(n))`和`repr(packed)`一起使用,因为`repr(packed)`将对齐设置为`1`。也不允许一个`repr(packed)`类型包含一个`repr(align(n))`类型。 -For further details on type layouts, refer to the [type layout] chapter of the -Rust Reference. +关于类型布局更多的细节,参考the Rust Reference的[type layout]章节。 [type layout]: https://doc.rust-lang.org/reference/type-layout.html From 72a67ca1e01048ebf0c9465e511605dbbbd7aed2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 17:33:26 +0800 Subject: [PATCH 081/137] minor --- src/c-tips/index.md | 1 - src/unsorted/speed-vs-size.md | 53 +++++++++++++++-------------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index d4ec99ee..0df30ae2 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -45,7 +45,6 @@ pub mod iir; 条件编译将只应用于下一条语句或者块。如果一个块不能在现在的作用域中被使用,那么`cfg`属性将需要被多次使用。值得注意的是大多数时间,仅是包含所有的代码,让编译器在优化时去删除死代码(dead code)更好,通常,在移除不使用的代码方面的工作,编译器做得很好。 -### Compile-Time Sizes and Computation ### 编译时大小和计算 Rust支持`const fn`,`const fn`是在编译时可以被计算的函数,因此可以被用在需要常量的地方,比如在数组的大小中。这个能与上述的features一起使用,比如: diff --git a/src/unsorted/speed-vs-size.md b/src/unsorted/speed-vs-size.md index 306ad41f..70854a65 100644 --- a/src/unsorted/speed-vs-size.md +++ b/src/unsorted/speed-vs-size.md @@ -1,28 +1,20 @@ -# Optimizations: the speed size tradeoff # 优化: 速度与大小之间的均衡 -Everyone wants their program to be super fast and super small but it's usually -not possible to have both characteristics. This section discusses the -different optimization levels that `rustc` provides and how they affect the -execution time and binary size of a program. -## No optimizations +每个人都想要它们的程序变得超级快且超级小,但是同时满足这两个条件是不可能的。这部分讨论`rustc`提供的不同的优化等级,和它们是如何影响执行时间和一个程序的二进制文件大小。 -This is the default. When you call `cargo build` you use the development (AKA -`dev`) profile. This profile is optimized for debugging so it enables debug -information and does *not* enable any optimizations, i.e. it uses `-C opt-level -= 0`. +## 没有优化 -At least for bare metal development, debuginfo is zero cost in the sense that it -won't occupy space in Flash / ROM so we actually recommend that you enable -debuginfo in the release profile -- it is disabled by default. That will let you -use breakpoints when debugging release builds. +这是默认的。当你调用`cargo build`时,你使用的是development(又叫`dev`)配置。这个配置优化的目的是为了调试,因此它使能了调试信息且*关闭*了所有优化,i.e. 它使用 `-C opt-level = 0` 。 + +至少对于裸机开发来说,调试信息不会占用Flash/ROM中的空间,意味着在这种情况下,调试信息是零开销的,因此实际上我们推荐你在release配置中使能调试信息 -- 默认它被关闭了。那可以让你调试release编译时,使用断点。 ``` toml [profile.release] -# symbols are nice and they don't increase the size on Flash +# 符号很好且它们不会增加Flash上的大小 debug = true ``` +没有优化对于调试来说是最好的,因为沿着代码单步调试 No optimizations is great for debugging because stepping through the code feels like you are executing the program statement by statement, plus you can `print` stack variables and function arguments in GDB. When the code is optimized, trying @@ -33,18 +25,15 @@ huge and slow. The size is usually more of a problem because unoptimized binaries can occupy dozens of KiB of Flash, which your target device may not have -- the result: your unoptimized binary doesn't fit in your device! -Can we have smaller, debugger friendly binaries? Yes, there's a trick. +我们可以有更小的,调试友好的二进制文件吗?是的,这里有一个技巧。 -### Optimizing dependencies +### 优化依赖 -There's a Cargo feature named [`profile-overrides`] that lets you -override the optimization level of dependencies. You can use that feature to -optimize all dependencies for size while keeping the top crate unoptimized and -debugger friendly. +这里有个名为[`profile-overrides`]的Cargo feature,其可以让你覆盖依赖的优化等级。你能使用那个feature去优化所有依赖的大小,而保持顶层的crate没有被优化且调试起来友好。 [`profile-overrides`]: https://doc.rust-lang.org/cargo/reference/profiles.html#overrides -Here's an example: +这是一个示例: ``` toml # Cargo.toml @@ -56,7 +45,7 @@ name = "app" opt-level = "z" # + ``` -Without the override: +没有覆盖: ``` text $ cargo size --bin app -- -A @@ -69,7 +58,7 @@ section size addr .bss 4 0x20000000 ``` -With the override: +有覆盖: ``` text $ cargo size --bin app -- -A @@ -82,8 +71,10 @@ section size addr .bss 4 0x20000000 ``` -That's a 6 KiB reduction in Flash usage without any loss in the debuggability of -the top crate. If you step into a dependency then you'll start seeing those +在Flash的使用上减少了6KiB,而不会损害顶层crate的可调试性。如果你步进一个依赖,然后你将开始再次看到那些``信息但是 + + +、 If you step into a dependency then you'll start seeing those `` messages again but it's usually the case that you want to debug the top crate and not the dependencies. And if you *do* need to debug a dependency then you can use the `profile-overrides` feature to exclude a @@ -165,9 +156,9 @@ different optimization levels use][inline-threshold]: [inline-threshold]: /~https://github.com/rust-lang/rust/blob/1.29.0/src/librustc_codegen_llvm/back/write.rs#L2105-L2122 -- `opt-level = 3` uses 275 -- `opt-level = 2` uses 225 -- `opt-level = "s"` uses 75 -- `opt-level = "z"` uses 25 +- `opt-level = 3` 使用 275 +- `opt-level = 2` 使用 225 +- `opt-level = "s"` 使用 75 +- `opt-level = "z"` 使用 25 -You should try `225` and `275` when optimizing for size. +当为了大小进行优化时,你应该尝试`225`和`275` 。 From 48d74671b18f20af0da41496b39c0762947893c2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 17:49:53 +0800 Subject: [PATCH 082/137] minor --- src/appendix/glossary.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/appendix/glossary.md b/src/appendix/glossary.md index 2eb5fb0f..8eaafbbe 100644 --- a/src/appendix/glossary.md +++ b/src/appendix/glossary.md @@ -4,17 +4,16 @@ ### BSP -A Board Support Crate provides a high level interface configured for a specific -board. It usually depends on a [HAL](#hal) crate. -There is a more detailed description on the [memory-mapped registers page](../start/registers.md) -or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). +一个板级支持Crate(Board Support Crate)提供一个为某个特定板子配置的高级接口。它通常依赖一个[HAL](#hal) crate 。在[存储映射的寄存器那页](../start/registers.md)有更多细节的描述或者看[这个视频](https://youtu.be/vLYit_HHPaY)来获取一个更广泛的概述。 ### FPU -Floating-point Unit. A 'math processor' running only operations on floating-point numbers. +浮点单元(Floating-Point Unit)。一个只运行在浮点数上的'数学处理器'。 ### HAL +一个硬件抽象层(Hardware Abstraction Layer) crate提供了一个 + A Hardware Abstraction Layer crate provides a developer friendly interface to a microcontroller's features and peripherals. It is usually implemented on top of a [Peripheral Access Crate (PAC)](#pac). It may also implement traits from the [`embedded-hal`](https://crates.io/crates/embedded-hal) crate. @@ -23,8 +22,7 @@ or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). ### I2C -Sometimes referred to as `I²C` or Inter-IC. It is a protocol meant for hardware communication -within a single integrated circuit. See [here][i2c] for more details +有时又被称为 `I²C` 或者 Intere-IC 。它是一种用于在单个集成电路中进行硬件通信的协议。看[这里][i2c]来获取更多细节。 [i2c]: https://en.wikipedia.org/wiki/I2c From e42f769762c9c9788b0e26a2b1f8bbb070f89946 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 20 Jun 2022 18:12:43 +0800 Subject: [PATCH 083/137] minor --- src/appendix/glossary.md | 6 +++--- src/interoperability/c-with-rust.md | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/appendix/glossary.md b/src/appendix/glossary.md index 8eaafbbe..e1e947b7 100644 --- a/src/appendix/glossary.md +++ b/src/appendix/glossary.md @@ -28,9 +28,9 @@ or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). ### PAC -A Peripheral Access Crate provides access to a microcontroller's peripherals. It is one of -the lower level crates and is usually generated directly from the provided [SVD](#svd), often -using [svd2rust](/~https://github.com/rust-embedded/svd2rust/). The [Hardware Abstraction Layer](#hal) +一个外设访问 Crate (Peripheral Access Crate)提供了对一个微控制器的外设的访问。它是一个底层的crates且通常从提供的[SVD](#svd)被直接生成,经常使用[svd2rust](/~https://github.com/rust-embedded/svd2rust/)。 + + The [Hardware Abstraction Layer](#hal) would usually depend on this crate. There is a more detailed description on the [memory-mapped registers page](../start/registers.md) or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 9cf50d59..0e7cf3ac 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -64,7 +64,7 @@ pub y: cty::c_int, pub extern "C" fn cool_function( ... ); ``` -This statement defines the signature of a function that uses the C ABI, called `cool_function`. By defining the signature without defining the body of the function, the definition of this function will need to be provided elsewhere, or linked into the final library or binary from a static library. +这个语句定义了一个使用C ABI的函数的签名,被叫做`cool_function`。通过定义签名而不定义函数的主体,这个函数的定义将需要在其它地方定义,或者从一个静态库链接进最终的库或者一个二进制文件中 ```rust,ignore i: cty::c_int, @@ -72,7 +72,8 @@ This statement defines the signature of a function that uses the C ABI, called ` cs: *mut CoolStruct ``` -Similar to our datatype above, we define the datatypes of the function arguments using C-compatible definitions. We also retain the same argument names, for clarity. +与我们上面的数据类型一样,我们使用C兼容的定义去定义函数参数的数据类型。为了清晰可见,我们还保留了相同的参数名。 + We have one new type here, `*mut CoolStruct`. As C does not have a concept of Rust's references, which would look like this: `&mut CoolStruct`, we instead have a raw pointer. As dereferencing this pointer is `unsafe`, and the pointer may in fact be a `null` pointer, care must be taken to ensure the guarantees typical of Rust when interacting with C or C++ code. From 9cfb9a01e0228f15314eebab7e726d8d9409f3c2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 21 Jun 2022 09:10:38 +0800 Subject: [PATCH 084/137] minor --- src/SUMMARY.md | 4 ++-- src/appendix/glossary.md | 15 ++------------- src/interoperability/c-with-rust.md | 11 ++++++++--- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 764d081b..ad6fa66f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -49,8 +49,8 @@ more information and coordination - [给嵌入式C开发者的贴士](./c-tips/index.md) - [互用性](./interoperability/index.md) - - [A little C with your Rust](./interoperability/c-with-rust.md) - - [A little Rust with your C](./interoperability/rust-with-c.md) + - [你的Rust配上一点C](./interoperability/c-with-rust.md) + - [你的C配上一点Rust](./interoperability/rust-with-c.md) - [没有排序的主题](./unsorted/index.md) - [优化: 速度与大小间的均衡](./unsorted/speed-vs-size.md) - [执行数学运算](./unsorted/math.md) diff --git a/src/appendix/glossary.md b/src/appendix/glossary.md index e1e947b7..a18d9f3b 100644 --- a/src/appendix/glossary.md +++ b/src/appendix/glossary.md @@ -12,13 +12,7 @@ ### HAL -一个硬件抽象层(Hardware Abstraction Layer) crate提供了一个 - -A Hardware Abstraction Layer crate provides a developer friendly interface to a microcontroller's -features and peripherals. It is usually implemented on top of a [Peripheral Access Crate (PAC)](#pac). -It may also implement traits from the [`embedded-hal`](https://crates.io/crates/embedded-hal) crate. -There is a more detailed description on the [memory-mapped registers page](../start/registers.md) -or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). +一个硬件抽象层(Hardware Abstraction Layer) crate为一个微控制器的功能和外设提供一个开发者友好的接口。它通常在[Peripheral Access Crate (PAC)](#pac)之上被实现。它可能也会实现来自[`embedded-hal`](https://crates.io/crates/embedded-hal) crate的traits 。在[存储映射的寄存器那页](../start/registers.md)上有更多的细节或者看[这个视频](https://youtu.be/vLYit_HHPaY)获取一个更广泛的概述。 ### I2C @@ -28,12 +22,7 @@ or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). ### PAC -一个外设访问 Crate (Peripheral Access Crate)提供了对一个微控制器的外设的访问。它是一个底层的crates且通常从提供的[SVD](#svd)被直接生成,经常使用[svd2rust](/~https://github.com/rust-embedded/svd2rust/)。 - - The [Hardware Abstraction Layer](#hal) -would usually depend on this crate. -There is a more detailed description on the [memory-mapped registers page](../start/registers.md) -or for a broader overview see [this video](https://youtu.be/vLYit_HHPaY). +一个外设访问 Crate (Peripheral Access Crate)提供了对一个微控制器的外设的访问。它是一个底层的crates且通常从提供的[SVD](#svd)被直接生成,经常使用[svd2rust](/~https://github.com/rust-embedded/svd2rust/)。[硬件抽象层](#hal)应该依赖这个crate。在[存储映射的寄存器那页](../start/registers.md)有更细节的描述或者看[这个视频](https://youtu.be/vLYit_HHPaY)获取一个更广泛的概述。 ### SPI diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 0e7cf3ac..21a5b52e 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -74,6 +74,8 @@ pub extern "C" fn cool_function( ... ); 与我们上面的数据类型一样,我们使用C兼容的定义去定义函数参数的数据类型。为了清晰可见,我们还保留了相同的参数名。 +这里我们有个新类型,`*mut CoolStruct` 。 + We have one new type here, `*mut CoolStruct`. As C does not have a concept of Rust's references, which would look like this: `&mut CoolStruct`, we instead have a raw pointer. As dereferencing this pointer is `unsafe`, and the pointer may in fact be a `null` pointer, care must be taken to ensure the guarantees typical of Rust when interacting with C or C++ code. @@ -91,7 +93,7 @@ Rather than manually generating these interfaces, which may be tedious and error [bindgen]: /~https://github.com/rust-lang/rust-bindgen [bindgen user's manual]: https://rust-lang.github.io/rust-bindgen/ -## Building your C/C++ code +## 编译你的 C/C++ 代码 As the Rust compiler does not directly know how to compile C or C++ code (or code from any other language, which presents a C interface), it is necessary to compile your non-Rust code ahead of time. @@ -101,13 +103,16 @@ If the library you would like to use is already distributed as a static archive, If your code exists as a source project, it will be necessary to compile your C/C++ code to a static library, either by triggering your existing build system (such as `make`, `CMake`, etc.), or by porting the necessary compilation steps to use a tool called the `cc` crate. For both of these steps, it is necessary to use a `build.rs` script. -### Rust `build.rs` build scripts +### Rust的 `build.rs` 编译脚本 + +一个 `build.rs` 脚本是一个用Rust语法编写的文件,它被运行在你的编译机器上, + A `build.rs` script is a file written in Rust syntax, that is executed on your compilation machine, AFTER dependencies of your project have been built, but BEFORE your project is built. The full reference may be found [here](https://doc.rust-lang.org/cargo/reference/build-scripts.html). `build.rs` scripts are useful for generating code (such as via [bindgen]), calling out to external build systems such as `Make`, or directly compiling C/C++ through use of the `cc` crate. -### Triggering external build systems +### 使用外部编译系统 For projects with complex external projects or build systems, it may be easiest to use [`std::process::Command`] to "shell out" to your other build systems by traversing relative paths, calling a fixed command (such as `make library`), and then copying the resulting static library to the proper location in the `target` build directory. From dace7b6a9608b9c59c38990e13f65ccc63ca5e04 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 21 Jun 2022 09:34:01 +0800 Subject: [PATCH 085/137] minor --- src/unsorted/speed-vs-size.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/unsorted/speed-vs-size.md b/src/unsorted/speed-vs-size.md index 70854a65..e5caaf6a 100644 --- a/src/unsorted/speed-vs-size.md +++ b/src/unsorted/speed-vs-size.md @@ -95,7 +95,7 @@ opt-level = "z" Now the top crate and `cortex-m-rt` are debugger friendly! -## Optimize for speed +## 优化速度 As of 2018-09-18 `rustc` supports three "optimize for speed" levels: `opt-level = 1`, `2` and `3`. When you run `cargo build --release` you are using the release @@ -112,15 +112,11 @@ enough). Currently there's no way to disable loop unrolling in `opt-level = 2` and `3` so if you can't afford its cost you should optimize your program for size. -## Optimize for size +## 优化尺寸 -As of 2018-09-18 `rustc` supports two "optimize for size" levels: `opt-level = -"s"` and `"z"`. These names were inherited from clang / LLVM and are not too -descriptive but `"z"` is meant to give the idea that it produces smaller -binaries than `"s"`. +自2018-09-18开始`rustc`支持两个"优化尺寸"的等级: `opt-level = "s"` 和 `"z"` 。这些名字传承自 clang / LLVM 且不具有描述性,但是`"z"`意味着它产生的二进制文件比`"s"`更小。 -If you want your release binaries to be optimized for size then change the -`profile.release.opt-level` setting in `Cargo.toml` as shown below. +如果你想要发布一个优化了尺寸的二进制文件,那么改变下面展示的`Cargo.toml`中的`profile.release.opt-level`配置。 ``` toml [profile.release] @@ -128,6 +124,9 @@ If you want your release binaries to be optimized for size then change the opt-level = "s" ``` +这两个优化等级 + + These two optimization levels greatly reduce LLVM's inline threshold, a metric used to decide whether to inline a function or not. One of Rust principles are zero cost abstractions; these abstractions tend to use a lot of newtypes and @@ -143,7 +142,7 @@ rustflags in `.cargo/config.toml`. ``` toml # .cargo/config.toml -# this assumes that you are using the cortex-m-quickstart template +# 这里假设你正在使用cortex-m-quickstart模板 [target.'cfg(all(target_arch = "arm", target_os = "none"))'] rustflags = [ # .. @@ -151,8 +150,7 @@ rustflags = [ ] ``` -What value to use? [As of 1.29.0 these are the inline thresholds that the -different optimization levels use][inline-threshold]: +用什么值?[从1.29.0开始,这些是不同优化级别使用的内联阈值][inline-threshold]: [inline-threshold]: /~https://github.com/rust-lang/rust/blob/1.29.0/src/librustc_codegen_llvm/back/write.rs#L2105-L2122 @@ -161,4 +159,4 @@ different optimization levels use][inline-threshold]: - `opt-level = "s"` 使用 75 - `opt-level = "z"` 使用 25 -当为了大小进行优化时,你应该尝试`225`和`275` 。 +当优化尺寸时,你应该尝试`225`和`275` 。 From b3e6d1686615b5714e272ae454c39ff25d510789 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 21 Jun 2022 09:34:15 +0800 Subject: [PATCH 086/137] minor --- src/c-tips/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 0df30ae2..5b93d9e0 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -247,7 +247,7 @@ fn main() { // `z`是两个字节对齐,因此在`y`和`z`之间填充了一个字节。 ``` -To ensure a packed representation, use `repr(packed)`: +使用`repr(packed)`去确保表示(representation)被填充了: ```rust #[repr(packed)] @@ -259,7 +259,7 @@ struct Foo { fn main() { let v = Foo { x: 0, y: 0, z: 0 }; - // Unsafe is required to borrow a field of a packed struct. + // 借用一个被填充的结构体的一个字段要求Unsafe操作。 unsafe { println!("{:p} {:p} {:p}", &v.x, &v.y, &v.z) }; } From f3850e114a44e2ca71f0e9d1bd7918619b4b1f0d Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 21 Jun 2022 09:39:36 +0800 Subject: [PATCH 087/137] minor --- src/interoperability/rust-with-c.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/interoperability/rust-with-c.md b/src/interoperability/rust-with-c.md index e5fd481b..f6c69c87 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -20,11 +20,7 @@ crate-type = ["cdylib"] # 生成动态链接库 ## 构建一个`C` API -因为C++没有稳定的ABI - -Because C++ has no stable ABI for the Rust compiler to target, we use `C` for -any interoperability between different languages. This is no exception when using Rust -inside of C and C++ code. +因为对于Rust编译器来说,C++没有稳定的ABI,因此我们使用`C`表示不同语言间的互用性。在C和C++代码的内部使用Rust时也不例外。 ### `#[no_mangle]` From c82dd3c22cd6210495b4d64ea61c33361a9914ca Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 21 Jun 2022 10:08:53 +0800 Subject: [PATCH 088/137] minor --- src/interoperability/rust-with-c.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/interoperability/rust-with-c.md b/src/interoperability/rust-with-c.md index f6c69c87..af6a4a66 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -24,23 +24,17 @@ crate-type = ["cdylib"] # 生成动态链接库 ### `#[no_mangle]` -The Rust compiler mangles symbol names differently than native code linkers expect. -As such, any function that Rust exports to be used outside of Rust needs to be told -not to be mangled by the compiler. +Rust对符号名的修饰与本机的代码链接器所期望的不同。因此,需要告知任何被Rust导出到Rust外部去使用的函数不要被编译器修饰。 ### `extern "C"` -By default, any function you write in Rust will use the -Rust ABI (which is also not stabilized). -Instead, when building outwards facing FFI APIs we need to -tell the compiler to use the system ABI. +默认,任何用Rust写的函数将使用Rust ABI(这也不稳定)。相反,当编译面向外部的FFI APIs,我们需要告诉编译器去使用系统ABI 。 -Depending on your platform, you might want to target a specific ABI version, which are -documented [here](https://doc.rust-lang.org/reference/items/external-blocks.html). +取决于你的平台,你可能需要针对一个特定的ABI版本,其记录在[这里](https://doc.rust-lang.org/reference/items/external-blocks.html)。 --- -Putting these parts together, you get a function that looks roughly like this. +把这些部分放在一起,你得到一个函数,其粗略看起来像是这个。 ```rust,ignore #[no_mangle] From f80c40ff5689bc675a7a39456f7b5893ebc48e34 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 21 Jun 2022 10:49:01 +0800 Subject: [PATCH 089/137] minor --- src/interoperability/rust-with-c.md | 34 ++++++++++++----------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/interoperability/rust-with-c.md b/src/interoperability/rust-with-c.md index af6a4a66..163dbc71 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -1,4 +1,4 @@ -# A little Rust with your C +# 你的C配点Rust 在C或者C++中使用Rust代码通常由两部分组成。 @@ -43,46 +43,40 @@ pub extern "C" fn rust_function() { } ``` -Just as when using `C` code in your Rust project you now need to transform data -from and to a form that the rest of the application will understand. +就像在你的Rust项目中使用`C`代码时那样,你现在将需要把数据转换为应用其它部分可以理解的形式。 -## Linking and greater project context. +## 链接和更大的项目上下文 -So then, that's one half of the problem solved. -How do you use this now? +问题只解决了一半。 -**This very much depends on your project and/or build system** +你现在要如何使用它? -`cargo` will create a `my_lib.so`/`my_lib.dll` or `my_lib.a` file, -depending on your platform and settings. This library can simply be linked -by your build system. +**这很大程度上取决于你的项目或者编译系统** -However, calling a Rust function from C requires a header file to declare -the function signatures. +`cargo`将生成一个`my_lib.so`/`my_lib.dll`或者`my_lib.a`文件,取决于你的平台和配置。可以通过编译系统简单地链接这个库。 -Every function in your Rust-ffi API needs to have a corresponding header function. +然而,从C调用一个Rust函数要求一个头文件去声明函数的签名。 + +在你的Rust-ffi API中的每个函数需要有一个相关的头文件函数。 ```rust,ignore #[no_mangle] pub extern "C" fn rust_function() {} ``` -would then become +将会变成 ```C void rust_function(); ``` -etc. +等等。 -There is a tool to automate this process, -called [cbindgen] which analyses your Rust code -and then generates headers for your C and C++ projects from it. +这里有个工具可以自动化这个过程,被叫做[cbindgen],其会分析你的Rust代码然后从它为你的C和C++项目生成头文件。 [cbindgen]: /~https://github.com/eqrion/cbindgen -At this point, using the Rust functions from C -is as simple as including the header and calling them! +此时从C中使用Rust函数非常简单,只需包含头文件和调用它们! ```C #include "my-rust-project.h" From baea4913ee14048d39cfa3d31aa1f322dd72f155 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 21 Jun 2022 23:43:24 +0800 Subject: [PATCH 090/137] minor --- src/interoperability/c-with-rust.md | 22 +++++----- src/unsorted/speed-vs-size.md | 63 +++++++---------------------- 2 files changed, 24 insertions(+), 61 deletions(-) diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 21a5b52e..e48976d9 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -64,7 +64,7 @@ pub y: cty::c_int, pub extern "C" fn cool_function( ... ); ``` -这个语句定义了一个使用C ABI的函数的签名,被叫做`cool_function`。通过定义签名而不定义函数的主体,这个函数的定义将需要在其它地方定义,或者从一个静态库链接进最终的库或者一个二进制文件中 +这个语句定义了一个使用C ABI的函数的签名,被叫做`cool_function`。通过定义签名而不定义函数的主体,这个函数的定义将需要在其它地方定义,或者从一个静态库链接进最终的库或者一个二进制文件中。 ```rust,ignore i: cty::c_int, @@ -74,18 +74,16 @@ pub extern "C" fn cool_function( ... ); 与我们上面的数据类型一样,我们使用C兼容的定义去定义函数参数的数据类型。为了清晰可见,我们还保留了相同的参数名。 -这里我们有个新类型,`*mut CoolStruct` 。 +这里我们有个新类型,`*mut CoolStruct` 。因为C没有Rust中的引用的概念,其看起来像是这个: `&mut CoolStruct`,所以我们使用一个裸指针。因为解引用这个指针是`unsafe`的,且实际上指针可能是一个`null`指针,因此当与C或者C++代码交互时必须要小心对待那些Rust做出的安全保证。 +### 自动产生接口 -We have one new type here, `*mut CoolStruct`. As C does not have a concept of Rust's references, which would look like this: `&mut CoolStruct`, we instead have a raw pointer. As dereferencing this pointer is `unsafe`, and the pointer may in fact be a `null` pointer, care must be taken to ensure the guarantees typical of Rust when interacting with C or C++ code. +有一个叫做[bindgen]的工具,它可以自动执行这些转换,而不用手动生成这些接口那么繁琐且容易出错。关于[bindgen]使用的指令,请参考[bindgen user's manual],然而经典的过程有下面几步: -### Automatically generating the interface - -Rather than manually generating these interfaces, which may be tedious and error prone, there is a tool called [bindgen] which will perform these conversions automatically. For instructions of the usage of [bindgen], please refer to the [bindgen user's manual], however the typical process consists of the following: - -1. Gather all C or C++ headers defining interfaces or datatypes you would like to use with Rust. -2. Write a `bindings.h` file, which `#include "..."`'s each of the files you gathered in step one. -3. Feed this `bindings.h` file, along with any compilation flags used to compile +1. 收集所有定义了你可能在Rust中用到的数据类型或者接口的C或者C++头文件。 +2. 写一个`bindings.h`文件,其`#include "..."`每一个你在步骤一中收集的文件。 +3. +4. Feed this `bindings.h` file, along with any compilation flags used to compile your code into `bindgen`. Tip: use `Builder.ctypes_prefix("cty")` / `--ctypes-prefix=cty` and `Builder.use_core()` / `--use-core` to make the generated code `#![no_std]` compatible. 4. `bindgen` will produce the generated Rust code to the output of the terminal window. This file may be piped to a file in your project, such as `bindings.rs`. You may use this file in your Rust project to interact with C/C++ code compiled and linked as an external library. Tip: don't forget to use the [`cty`](https://crates.io/crates/cty) crate if your types in the generated bindings are prefixed with `cty`. @@ -122,11 +120,11 @@ While your crate may be targeting a `no_std` embedded platform, your `build.rs` ### 使用`cc` crate构建C/C++代码 -For projects with limited dependencies or complexity, or for projects where it is difficult to modify the build system to produce a static library (rather than a final binary or executable), it may be easier to instead utilize the [`cc` crate], which provides an idiomatic Rust interface to the compiler provided by the host. +对于具有有限的依赖项或者复杂度的项目,或者对于那些难以修改编译系统去生成一个静态库(而不是一个二进制文件或者可执行文件)的项目,使用[`cc` crate]可能更容易,它提供了一个符合Rust语法的接口,这个接口是关于主机提供的编译器的。 [`cc` crate]: /~https://github.com/alexcrichton/cc-rs -In the simplest case of compiling a single C file as a dependency to a static library, an example `build.rs` script using the [`cc` crate] would look like this: +在把一个C文件编译成一个静态库的依赖项的最简单的场景下,可以使用[`cc` crate],示例`build.rs`脚本看起来像这样: ```rust,ignore extern crate cc; diff --git a/src/unsorted/speed-vs-size.md b/src/unsorted/speed-vs-size.md index e5caaf6a..7d0854fc 100644 --- a/src/unsorted/speed-vs-size.md +++ b/src/unsorted/speed-vs-size.md @@ -2,11 +2,11 @@ 每个人都想要它们的程序变得超级快且超级小,但是同时满足这两个条件是不可能的。这部分讨论`rustc`提供的不同的优化等级,和它们是如何影响执行时间和一个程序的二进制文件大小。 -## 没有优化 +## 不优化 这是默认的。当你调用`cargo build`时,你使用的是development(又叫`dev`)配置。这个配置优化的目的是为了调试,因此它使能了调试信息且*关闭*了所有优化,i.e. 它使用 `-C opt-level = 0` 。 -至少对于裸机开发来说,调试信息不会占用Flash/ROM中的空间,意味着在这种情况下,调试信息是零开销的,因此实际上我们推荐你在release配置中使能调试信息 -- 默认它被关闭了。那可以让你调试release编译时,使用断点。 +至少对于裸机开发来说,调试信息不会占用Flash/ROM中的空间,意味着在这种情况下,调试信息是零开销的,因此实际上我们推荐你在release配置中使能调试信息 -- 默认它被关闭了。那让你调试release版本的固件时可以使用断点。 ``` toml [profile.release] @@ -14,22 +14,15 @@ debug = true ``` -没有优化对于调试来说是最好的,因为沿着代码单步调试 -No optimizations is great for debugging because stepping through the code feels -like you are executing the program statement by statement, plus you can `print` -stack variables and function arguments in GDB. When the code is optimized, trying -to print variables results in `$0 = ` being printed. +不优化对于调试来说是最好的,因为单步调试代码感觉像是你正在逐条语句地执行程序,且你能在GDB中`print`栈变量和函数参数。当代码被优化了,尝试打印变量会导致`$0 = `被打印出来。 -The biggest downside of the `dev` profile is that the resulting binary will be -huge and slow. The size is usually more of a problem because unoptimized -binaries can occupy dozens of KiB of Flash, which your target device may not -have -- the result: your unoptimized binary doesn't fit in your device! +`dev`配置最大的缺点就是最终的二进制文件将会变得巨大且缓慢。大小通常是一个更大的问题,因为未优化的二进制文件会占据大量KiB的Flash,你的目标设备可能没这么多Flash -- 结果: 你未优化的二进制文件无法烧录进你的设备中! 我们可以有更小的,调试友好的二进制文件吗?是的,这里有一个技巧。 ### 优化依赖 -这里有个名为[`profile-overrides`]的Cargo feature,其可以让你覆盖依赖的优化等级。你能使用那个feature去优化所有依赖的大小,而保持顶层的crate没有被优化且调试起来友好。 +这里有个名为[`profile-overrides`]的Cargo feature,其可以让你覆盖依赖项的优化等级。你能使用这个feature去优化所有依赖的大小,而保持顶层的crate没有被优化以致调试起来友好。 [`profile-overrides`]: https://doc.rust-lang.org/cargo/reference/profiles.html#overrides @@ -71,46 +64,30 @@ section size addr .bss 4 0x20000000 ``` -在Flash的使用上减少了6KiB,而不会损害顶层crate的可调试性。如果你步进一个依赖,然后你将开始再次看到那些``信息但是 - - -、 If you step into a dependency then you'll start seeing those -`` messages again but it's usually the case that you want -to debug the top crate and not the dependencies. And if you *do* need to debug a -dependency then you can use the `profile-overrides` feature to exclude a -particular dependency from being optimized. See example below: +在Flash的使用上减少了6KiB,而不会损害顶层crate的可调试性。如果你步进一个依赖项,然后你将开始再次看到那些``信息,但是通常的情况下你只想调试顶层的crate而不是依赖项。如果你 *需要* 调试一个依赖项,那么你可以使用`profile-overrides` feature去防止一个特定的依赖项被优化。看下面的例子: ``` toml # .. -# don't optimize the `cortex-m-rt` crate +# 不要优化`cortex-m-rt` crate [profile.dev.package.cortex-m-rt] # + opt-level = 0 # + -# but do optimize all the other dependencies +# 但是优化所有其它依赖项 [profile.dev.package."*"] codegen-units = 1 # better optimizations opt-level = "z" ``` -Now the top crate and `cortex-m-rt` are debugger friendly! +现在顶层的crate和`cortex-m-rt`对调试器很友好! ## 优化速度 -As of 2018-09-18 `rustc` supports three "optimize for speed" levels: `opt-level -= 1`, `2` and `3`. When you run `cargo build --release` you are using the release -profile which defaults to `opt-level = 3`. +自2018-09-18开始 `rustc` 支持三个 "优化速度" 的等级: `opt-level = 1`, `2` 和 `3` 。当你运行 `cargo build --release` 时,你正在使用的是release配置,其默认是 `opt-level = 3` 。 -Both `opt-level = 2` and `3` optimize for speed at the expense of binary size, -but level `3` does more vectorization and inlining than level `2`. In -particular, you'll see that at `opt-level` equal to or greater than `2` LLVM will -unroll loops. Loop unrolling has a rather high cost in terms of Flash / ROM -(e.g. from 26 bytes to 194 for a zero this array loop) but can also halve the -execution time given the right conditions (e.g. number of iterations is big -enough). +`opt-level = 2` 和 `3` 都以二进制文件大小为代价优化速度,但是等级`3`比等级`2`做了更多的向量化和内联。特别是,你将看到在`opt-level`等于或者大于`2`时LLVM将展开循环。循环展开在 Flash / ROM 方面的成本相当高(e.g. from 26 bytes to 194 for a zero this array loop)但是如果条件合适(迭代次数足够大),也可以将执行时间减半。 -Currently there's no way to disable loop unrolling in `opt-level = 2` and `3` so -if you can't afford its cost you should optimize your program for size. +现在还没有办法在`opt-level = 2`和`3`的情况下关闭循环展开,因此如果你不能接受它的开销,你应该优化你程序的尺寸。 ## 优化尺寸 @@ -124,21 +101,9 @@ if you can't afford its cost you should optimize your program for size. opt-level = "s" ``` -这两个优化等级 - - -These two optimization levels greatly reduce LLVM's inline threshold, a metric -used to decide whether to inline a function or not. One of Rust principles are -zero cost abstractions; these abstractions tend to use a lot of newtypes and -small functions to hold invariants (e.g. functions that borrow an inner value -like `deref`, `as_ref`) so a low inline threshold can make LLVM miss -optimization opportunities (e.g. eliminate dead branches, inline calls to -closures). +这两个优化等级极大地减少了LLVM的内联阈值,一个用来决定是否内联或者不内联一个函数的度量。Rust其中一个概念是零成本抽象;这些抽象趋向于去使用许多新类型和小函数去保持不变量(e.g. 像是`deref`,`as_ref`这样借用内部值的函数)因此一个低内联阈值会使LLVM失去优化的机会(e.g. 去掉死分支(dead branches),内联对闭包的调用)。 -When optimizing for size you may want to try increasing the inline threshold to -see if that has any effect on the binary size. The recommended way to change the -inline threshold is to append the `-C inline-threshold` flag to the other -rustflags in `.cargo/config.toml`. +当优化尺寸时,你可能想要尝试增加增加内联阈值去观察是否会对你的二进制文件的大小有影响。推荐的改变内联阈值的方法是在`.cargo/config.toml`中往其它rustflags后插入`-C inline-threshold` 。 ``` toml # .cargo/config.toml From 1d3f80d0b65aa47819a2bc3e8978cef63e829800 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 10:21:40 +0800 Subject: [PATCH 091/137] minor --- src/SUMMARY.md | 4 ++-- src/c-tips/index.md | 4 ++-- src/interoperability/c-with-rust.md | 26 ++++++++++---------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ad6fa66f..c5a302fa 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -49,8 +49,8 @@ more information and coordination - [给嵌入式C开发者的贴士](./c-tips/index.md) - [互用性](./interoperability/index.md) - - [你的Rust配上一点C](./interoperability/c-with-rust.md) - - [你的C配上一点Rust](./interoperability/rust-with-c.md) + - [给Rust配上一点C](./interoperability/c-with-rust.md) + - [给C配上一点Rust](./interoperability/rust-with-c.md) - [没有排序的主题](./unsorted/index.md) - [优化: 速度与大小间的均衡](./unsorted/speed-vs-size.md) - [执行数学运算](./unsorted/math.md) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 5b93d9e0..9c50681e 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -302,8 +302,8 @@ fn main() { ## 其它资源 * 在这本书中: - * [A little C with your Rust](../interoperability/c-with-rust.md) - * [A little Rust with your C](../interoperability/rust-with-c.md) + * [给Rust配点C](../interoperability/c-with-rust.md) + * [给C配点Rust](../interoperability/rust-with-c.md) * [The Rust Embedded FAQs](https://docs.rust-embedded.org/faq.html) * [Rust Pointers for C Programmers](http://blahg.josefsipek.net/?p=580) * [I used to use pointers - now what?](/~https://github.com/diwic/reffers-rs/blob/master/docs/Pointers.md) diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index e48976d9..3c230b80 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -82,39 +82,33 @@ pub extern "C" fn cool_function( ... ); 1. 收集所有定义了你可能在Rust中用到的数据类型或者接口的C或者C++头文件。 2. 写一个`bindings.h`文件,其`#include "..."`每一个你在步骤一中收集的文件。 -3. -4. Feed this `bindings.h` file, along with any compilation flags used to compile - your code into `bindgen`. Tip: use `Builder.ctypes_prefix("cty")` / - `--ctypes-prefix=cty` and `Builder.use_core()` / `--use-core` to make the generated code `#![no_std]` compatible. -4. `bindgen` will produce the generated Rust code to the output of the terminal window. This file may be piped to a file in your project, such as `bindings.rs`. You may use this file in your Rust project to interact with C/C++ code compiled and linked as an external library. Tip: don't forget to use the [`cty`](https://crates.io/crates/cty) crate if your types in the generated bindings are prefixed with `cty`. +3. 将这个`bindings.h`文件和任何用来编译你代码的编译标识发给`bindgen`。贴士: 使用`Builder.ctypes_prefix("cty")` / `--ctypes-prefix=cty` 和 `Builder.use_core()` / `--use-core` 去使生成的代码兼容`#![no_std]` +4. `bindgen`将会在终端窗口输出生成的Rust代码。这个文件可能会被通过管道发送给你项目中的一个文件,比如`bindings.rs` 。你可能要在你的Rust项目中使用这个文件来与被编译和链接成一个外部库的C/C++代码交互。贴士: 如果你的类型在生成的绑定中被前缀了`cty`,不要忘记使用[`cty`](https://crates.io/crates/cty) crate 。 [bindgen]: /~https://github.com/rust-lang/rust-bindgen [bindgen user's manual]: https://rust-lang.github.io/rust-bindgen/ ## 编译你的 C/C++ 代码 -As the Rust compiler does not directly know how to compile C or C++ code (or code from any other language, which presents a C interface), it is necessary to compile your non-Rust code ahead of time. +因为Rust编译器并不直接知道如何编译C或者C++代码(或者从其它语言来的代码,其提供了一个C接口),所以必须要静态编译你的非Rust代码。 -For embedded projects, this most commonly means compiling the C/C++ code to a static archive (such as `cool-library.a`), which can then be combined with your Rust code at the final linking step. +对于嵌入式项目,这通常意味着把C/C++代码编译成一个静态库文档(比如 `cool-library.a`),然后其能在最后链接阶段与你的Rust代码组合起来。 -If the library you would like to use is already distributed as a static archive, it is not necessary to rebuild your code. Just convert the provided interface header file as described above, and include the static archive at compile/link time. +如果你要使用的库已经作为一个静态库文档被发布,那就没必要重新编译你的代码。只需按照上面所述转换提供的接口头文件,且在编译/链接时包含静态库文档。 -If your code exists as a source project, it will be necessary to compile your C/C++ code to a static library, either by triggering your existing build system (such as `make`, `CMake`, etc.), or by porting the necessary compilation steps to use a tool called the `cc` crate. For both of these steps, it is necessary to use a `build.rs` script. +如果你的代码作为一个源项目存在,将你的C/C++代码编译成一个静态库将是必须的,要么通过使用你现存的编译系统(比如 `make`,`CMake`,等等),要么通过使用一个被叫做`cc` crate的工具移植必要的编译步骤。关于这两个,都必须使用一个`build.rs`脚本。 ### Rust的 `build.rs` 编译脚本 -一个 `build.rs` 脚本是一个用Rust语法编写的文件,它被运行在你的编译机器上, +一个 `build.rs` 脚本是一个用Rust语法编写的文件,它被运行在你的编译机器上,发生在你项目的依赖项被编译**之后**,但是在你的项目被编译**之前** 。 - -A `build.rs` script is a file written in Rust syntax, that is executed on your compilation machine, AFTER dependencies of your project have been built, but BEFORE your project is built. - -The full reference may be found [here](https://doc.rust-lang.org/cargo/reference/build-scripts.html). `build.rs` scripts are useful for generating code (such as via [bindgen]), calling out to external build systems such as `Make`, or directly compiling C/C++ through use of the `cc` crate. +可能能在[这里](https://doc.rust-lang.org/cargo/reference/build-scripts.html)发现完整的参考。`build.rs` 脚本能用来生成代码(比如通过[bindgen]),调用外部编译系统,比如`Make`,或者直接通过使用`cc` crate直接编译C/C++ 。 ### 使用外部编译系统 -For projects with complex external projects or build systems, it may be easiest to use [`std::process::Command`] to "shell out" to your other build systems by traversing relative paths, calling a fixed command (such as `make library`), and then copying the resulting static library to the proper location in the `target` build directory. +对于有复杂的外部项或者编译系统的项目,使用[`std::process::Command`]通过遍历相对路径来向其它编译系统"输出",调用一个固定的命令(比如 `make library`),然后拷贝最终的静态库到`target`编译文件夹中恰当的位置,可能是最简单的方法。 -While your crate may be targeting a `no_std` embedded platform, your `build.rs` executes only on machines compiling your crate. This means you may use any Rust crates which will run on your compilation host. +虽然你的crate目标可能是一个`no_std`嵌入式平台,但你的`build.rs`只运行在负责编译你的crate的机器上。这意味着你能使用任何Rust crates,其将运行在你的编译主机上。 [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html From b1c9ccdd9df0c4b423fba2ea2f642d2b3aa33dd9 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 10:40:04 +0800 Subject: [PATCH 092/137] minor --- src/intro/index.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/intro/index.md b/src/intro/index.md index 6f997ffb..24e7a6d7 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -1,24 +1,24 @@ # 引言 -欢迎阅读The Embedded Rust Book:一本关于在裸机(比如,微处理器)上使用Rust编程语言的引导书籍。 +欢迎阅读The Embedded Rust Book:一本介绍如何在裸机(比如,微处理器)上使用Rust编程语言的书籍。 ## Embedded Rust 是为谁准备的 -Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(See also [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) +Embedded Rust是为了那些即想要进行嵌入式编程,又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(也可以看 [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) ## 本书范围 这本书的目的是: -+ 让开发者快速上手嵌入式Rust开发,比如,如何设置一个开发环境。 ++ 让开发者快速上手Rust嵌入式开发,比如,如何设置一个开发环境。 + 分享那些关于使用Rust进行嵌入式开发的,现存的,最好的实践经验,比如,如何最大程度上地利用好Rust语言的特性去写更正确的嵌入式软件 -+ 某种程度下作为工具书,比如,如何在一个项目里将C和Rust混合进去 ++ 某种程度下作为工具书,比如,如何在一个项目里将C和Rust混合在一起使用 -这本书尽可能地尝试变得适用于所有场景,但是为了使读者和作者更容易理解,在所有的例子中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个特定的架构,书中会在需要时解释这个架构的特定细节。 +这本书尽可能地尝试变得适用于所有场景,但是为了使读者和作者更容易理解,在所有的案例中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个架构,书中会在需要时解释这个架构的特定细节。 ## 这本书是为谁准备的 -这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对嵌入式Rust编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”部分,从书中获取、补充缺失的知识,这样能提高你的阅读体验。你可以看看“其它资源”章节,以找到你感兴趣的主题的资源。 +这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对Rust嵌入式编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”部分。从其它资料中获取、补充缺失的知识,这样能提高你的阅读体验。你可以看看“其它资源”部分,以找到你感兴趣的主题的资源。 ### 要求和前提 -+ 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你也应该熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 ++ 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你也应该要熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 [2018 edition]: https://doc.rust-lang.org/edition-guide/ + 你可以轻松地使用其它语言,比如C,C++或者Ada开发和调试嵌入式系统,且熟悉如下的概念: @@ -33,11 +33,11 @@ Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言 | Topic | Resource | Description | |--------------|----------|-------------| -| Rust | [Rust Book](https://doc.rust-lang.org/book/) | 如果你还不能轻松地使用Rust,我们高度地建议读这本书。| +| Rust | [Rust Book](https://doc.rust-lang.org/book/) | 如果你还不能轻松地使用Rust,我们强烈地建议读这本书。| | Rust, Embedded | [Discovery Book](https://docs.rust-embedded.org/discovery/) | 如果你从来没有做过嵌入式编程,这本书可能是个更好的开始。 | | Rust, Embedded | [Embedded Rust Bookshelf](https://docs.rust-embedded.org) | 这里你能找到许多Rust's Embedded Working Group提供的额外资源。| | Rust, Embedded | [Embedonomicon](https://docs.rust-embedded.org/embedonomicon/) | 在Rust中进行嵌入式编程的细节。 | -| Rust, Embedded | [embedded FAQ](https://docs.rust-embedded.org/faq.html) | 关于嵌入式环境中的Rust的常见问题。| +| Rust, Embedded | [embedded FAQ](https://docs.rust-embedded.org/faq.html) | 关于嵌入式环境下的Rust会遇到的常见问题。| | Interrupts | [Interrupt](https://en.wikipedia.org/wiki/Interrupt) | - | | Memory-mapped IO/Peripherals | [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O) | - | | SPI, UART, RS232, USB, I2C, TTL | [Stack Exchange about SPI, UART, and other interfaces](https://electronics.stackexchange.com/questions/37814/usart-uart-rs232-usb-spi-i2c-ttl-etc-what-are-all-of-these-and-how-do-th) | - | @@ -54,7 +54,7 @@ Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言 ## 如何使用这本书 这本书通常假设你是前后阅读的。之后章节是建立在先前章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,因为在随后的章节将会再次重温这个主题。 -这本书将在大多数案例中使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的供应商之间是不同的,甚至来自同一个供应商的不同处理器家族也是不同的。 +在大多数案例中这本书将使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的厂家之间是不同的,甚至来自同一个厂家,不同处理器系列之间也是不同的。 因为这个理由,我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。(译者注:我使用[renode](https://renode.io/about/)来测试大多数例子) [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html @@ -77,16 +77,16 @@ Embedded Rust是为了那些想要进行嵌入式编程,又想使用Rust语言 这本书根据以下许可证发布: * 本书中包含的代码示例和独立的Cargo项目均根据[MIT License]和[Apache License v2.0]发放许可的。 -* 本书中包含的written prose(?),图片和表格均根据[CC-BY-SA v4.0]发放许可的。 +* 本书中包含的文档,图片和表格均根据[CC-BY-SA v4.0]发放许可的。 [MIT License]: https://opensource.org/licenses/MIT [Apache License v2.0]: http://www.apache.org/licenses/LICENSE-2.0 [CC-BY-SA v4.0]: https://creativecommons.org/licenses/by-sa/4.0/legalcode -总之:如果你想在你的工作中使用我们的文本或者图片,你需要: +总之:如果你想在你的工作中使用我们的文档或者图片,你需要: + 提供合适的授信 (i.e. 在你的幻灯片中提到本书,提供相关页面的连接) -+ 提供[CC-BY-SA v4.0]的许可证的连接 ++ 提供[CC-BY-SA v4.0]的许可证的链接 + 指出你是否改变了材料的内容,在同一个许可证下,可以对材料进行任何改变 也请告诉我这本书对你是否有帮助! From e345ef582aea4613f37cda23ae21cf45c070cb70 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 11:11:13 +0800 Subject: [PATCH 093/137] minor --- src/intro/hardware.md | 12 ++++++------ src/intro/no-std.md | 13 +++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/intro/hardware.md b/src/intro/hardware.md index 77fdcfdf..b0d2d59a 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -17,16 +17,16 @@ + 多种多样的外设,比如计时器,I2C,SPI和USART + 通用GPIO和板子两侧的其它类型引脚 + 一个写着“USB USER”的USB接口 -+ [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片上的一个[加速度计](https://en.wikipedia.org/wiki/Accelerometer)。 ++ 一个位于[LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片上的[加速度计](https://en.wikipedia.org/wiki/Accelerometer)。 -+ [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片上的一个[磁力计](https://en.wikipedia.org/wiki/Magnetometer)。 ++ 一个位于[LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片上的[磁力计](https://en.wikipedia.org/wiki/Magnetometer)。 -+ [L3GD20](https://www.pololu.com/file/0J563/L3GD20.pdf)芯片上的一个[陀螺仪](https://en.wikipedia.org/wiki/Gyroscope). ++ 一个位于[L3GD20](https://www.pololu.com/file/0J563/L3GD20.pdf)芯片上的[陀螺仪](https://en.wikipedia.org/wiki/Gyroscope). -+ 摆得像一个指南针形状样的8个用户LEDs。 ++ 8个摆得像一个指南针形状的用户LEDs。 + 一个二级微控制器: [STM32F103](https://www.st.com/en/microcontrollers/stm32f103cb.html)。这个微控制器实际上是一个板载编程器/调试器的一部分,与名为“USB ST-LINK”的USB端口相连。 -关于所列举的特性的更多细节,和板子的更多规范请看向[STMicroelectronics](https://www.st.com/en/evaluation-tools/stm32f3discovery.html)网站。 +关于所列举的功能的更多细节和开发板的更多规格请看向[STMicroelectronics](https://www.st.com/en/evaluation-tools/stm32f3discovery.html)网站。 -提醒一句: 如果你想要为板子提供外部信号,请小心。微控制器STM32F303VCT6引脚的标称电压是3.3伏。更多信息请查看[6.2 Absolute maximum ratings section in the manual](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf)。 +提醒一句: 如果你想要为板子提供外部信号,请小心。微控制器STM32F303VCT6管脚的标称电压是3.3伏。更多信息请查看[6.2 Absolute maximum ratings section in the manual](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf)。 diff --git a/src/intro/no-std.md b/src/intro/no-std.md index eb530285..040d6ad0 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -1,21 +1,22 @@ # 一个 `no_std` Rust环境 + 嵌入式编程这个词被广泛用于许多不同的编程场景中。小到RAM和ROM只有KB的8位机(像是[ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)),大到一个具有32/64位4核Cortex-A53和1GB的RAM的系统,比如树莓派([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications))。当编写代码时,取决于你的目标环境和用例,将会有不同的限制和局限。
通常嵌入式编程有两类: ## 主机环境 -这类环境类似一个常见的PC环境。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。你可能在RAM/ROM的使用上有一些sysroot的限制,可能还有一些特别的硬件或者I/O。总之感觉像是在一个特定目的的PC环境上编程一样。 +这类环境类似一个常见的PC环境。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。sysroot可能限制你使用RAM/ROM,可能还有一些特别的硬件或者I/O。总之感觉像是在专用的PC环境上编程一样。 ## 裸机环境 -在一个裸机环境中,先于你的程序被加载前,不存在代码。没有系统提供的软件,我们不能加载标准库。相反地,该程序,以及它使用的crates只能使用硬件(裸机)去运行。使用`no-std`可以防止rust读取标准库。标准库中与平台无关的部分在[libcore](https://doc.rust-lang.org/core/)中。libcore剔除了那些在一个嵌入式环境中非必要的东西。比如用于动态分配的内存分配器。如果你需要这些或者其它的那些功能,通常会有提供这些功能的crates。 +在一个裸机环境中,你的程序被加载前,不存在代码。没有系统提供的软件,我们不能加载标准库。相反地,该程序,以及它使用的crates只能使用硬件(裸机)去运行。使用`no-std`可以防止rust读取标准库。标准库中与平台无关的部分在[libcore](https://doc.rust-lang.org/core/)中。libcore剔除了那些在一个嵌入式环境中非必要的东西。比如用于动态分配的内存分配器。如果你需要这些或者其它的某些功能,通常会有提供这些功能的crates。 ### libstd运行时 -就像之前提到的,使用[libstd](https://doc.rust-lang.org/std/)需要一些系统集成,这不仅仅是因为[libstd](https://doc.rust-lang.org/std/)提供了一个公共的方法访问操作系统,它也提供了一个运行时环境。这个运行时环境,负责设置堆栈溢出保护,处理命令行参数,在一个程序主函数被激活前启动一个主线程。在一个`no_std`环境中,这个运行时环境也是不可用的。 +就像之前提到的,使用[libstd](https://doc.rust-lang.org/std/)需要一些系统集成,这不仅仅是因为[libstd](https://doc.rust-lang.org/std/)使用了一个公共的方法访问操作系统,它也提供了一个运行时环境。这个运行时环境,负责设置堆栈溢出保护,处理命令行参数,并在一个程序的主函数被激活前启动一个主线程。在一个`no_std`环境中,这个运行时环境也是不可用的。 ## 总结 -`#![no_std]`是一个crate-level属性,它说明crate将连接至core-crate而不是std-crate。[libcore](https://doc.rust-lang.org/core/) crate是std crate的一个平台无关的子集,它对程序将要运行的系统没有做要求。比如,它提供了像是floats,strings和切片的APIs,暴露了像是原子操作和SIMD指令的处理器特性相关的APIs。然而,它缺少涉及到平台集成的那些APIs。由于这些特性,no_std和[libcore](https://doc.rust-lang.org/core/)代码可以用于任何引导程序(stage 0)像是bootloaders,固件或者内核。 +`#![no_std]`是一个crate层级的属性,它说明crate将连接至core-crate而不是std-crate。[libcore](https://doc.rust-lang.org/core/) crate是std crate的一个的子集,其与平台无关,它对程序将要运行的系统没有做要求。比如,它提供了像是floats,strings和切片的APIs,暴露了像是与原子操作和SIMD指令相关的处理器功能的APIs。然而,它缺少涉及到平台集成的那些APIs。由于这些特性,no_std和[libcore](https://doc.rust-lang.org/core/)代码可以用于任何引导程序(stage 0)像是bootloaders,固件或者内核。 ### 概述 @@ -29,9 +30,9 @@ | libcore available | ✓ | ✓ | | writing firmware, kernel, or bootloader code | ✓ | ✘ | -\* 只有在你使用 `alloc` crate 和一个适合的分配器,比如[alloc-cortex-m]时有效。 +\* 只有在你使用了 `alloc` crate 和一个适合的分配器,比如[alloc-cortex-m]时有效。 -\** 只有在你使用 `collections` crate 和配置一个全局默认的分配器时有效。 +\** 只有在你使用了 `collections` crate 和配置了一个全局默认的分配器时有效。 [alloc-cortex-m]: /~https://github.com/rust-embedded/alloc-cortex-m From 2cf4ac1cd26268bec0af8525690c22b3a36c1582 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 11:24:36 +0800 Subject: [PATCH 094/137] minor --- src/intro/tooling.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/intro/tooling.md b/src/intro/tooling.md index e9eadfc3..1a5b2f98 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -1,34 +1,34 @@ # 工具 -与微控制器打交道需要使用几种不同的工具,因为我们要处理的架构与笔记本电脑不同,我们必须在*远程*设备上运行和调试程序。我们将使用下面列举出来的工具。当一个最小版本没有被指定时,最近的版本应该可以工作,但是我们已经列出我们已经测过的那些版本。 +与微控制器打交道需要使用几种不同的工具,因为我们要处理的架构与笔记本电脑不同,我们必须在 *远程* 设备上运行和调试程序。我们将使用下面列举出来的工具。当没有指定一个最小版本时,最新的版本应该可以工作,但是我们已经列出了我们已经测过的那些版本。 - Rust 1.31, 1.31-beta, 或者一个更新的,支持ARM Cortex-M编译的工具链。 - [`cargo-binutils`](/~https://github.com/rust-embedded/cargo-binutils) ~0.1.4 - [`qemu-system-arm`](https://www.qemu.org/). 测试的版本: 3.0.0 - OpenOCD >=0.8. 测试的版本: v0.9.0 and v0.10.0 -- 有ARM支持的GDB。高度建议7.12或者更新的版本。测试版本: 7.10, 7.11 和 8.1 +- 有ARM支持的GDB。强烈建议7.12或者更新的版本。测试版本: 7.10, 7.11 和 8.1 - [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) 或者 `git` -这些工具都是可选的,但是跟着书来使用它们,会更容易。下面的文档解释我们为什么使用这些工具。安装指令可以在下一页找到。 +这些工具都是可选的,但是跟着书来使用它们,会更容易。下面的文档将解释我们为什么使用这些工具。安装指令可以在下一页找到。 ## `cargo-generate` 或者 `git` -裸板编程是non-strandard Rust编程,为了获取正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进一个模板里,你只需要补充缺失的信息(比如项目名和你的目标硬件的特性)。
+裸机编程是非标准 Rust 编程,为了得到正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进了一个模板里,你只需要补充缺失的信息(比如项目名和你的目标硬件的特性)。
我们的模板兼容`cargo-generate`:一个用来从模板生成新的Cargo项目的Cargo子命令。你也能使用`git`,`curl`,`wget`,或者你的网页浏览器下载模板。 ## `cargo-binutils` -`cargo-binutils`是一个Cargo子命令集合,它使得能轻松使用Rust工具链带来的LLVM工具。这些工具包括LLVM版本的`objdump`,`nm`和`size`,用来查看二进制文件。
+`cargo-binutils`是一个Cargo命令的子集,它让我们能轻松使用Rust工具链带来的LLVM工具。这些工具包括LLVM版本的`objdump`,`nm`和`size`,用来查看二进制文件。
在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工具是相同的一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 ## `qemu-system-arm` -QEMU是一个仿真器。在这个例子里,我们使用能完全仿真ARM系统的变种版。我们使用QEMU在主机上运行嵌入式程序。多亏了它,你可以在没有任何硬件的情况下,尝试这本书的部分例子。 +QEMU是一个仿真器。在这个例子里,我们使用能完全仿真ARM系统的改良版。我们使用QEMU在主机上运行嵌入式程序。多亏了它,你可以在没有任何硬件的情况下,尝试这本书的部分示例。 ## GDB -调试器是嵌入式开发的一个非常重要的组件,你可能不总是有机会将内容记录到主机控制台。在某些情况里,你可能甚至没有LEDs点亮你的硬件!
-通常,提到调试,LLDB和GDB的功能一样,但是我们还没找到与GDB的`load`命令对应的LLDB命令,它将程序上传到目标硬件,因此我们现在推荐你使用GDB。 +调试器是嵌入式开发的一个非常重要的组件,你不可能总是有机会将内容记录到主机控制台上。在某些情况里,你可能甚至没有LEDs去点亮你的硬件!
+通常,提到调试,LLDB和GDB的功能一样,但是我们还没找到与GDB的`load`命令对应的LLDB命令,这条命令负责将程序上传到目标硬件,因此我们现在推荐你使用GDB。 ## OpenOCD GDB不能直接和你的STM32F3DISCOVERY开发板上的ST-Link调试硬件通信。它需要一个转译者,the Open On-Chip Debugger,OpenOCD就是这样的转译者。OpenOCD是一个运行在你的笔记本/个人电脑上的程序,它在基于远程调试的TCP/IP协议和ST-Link的USB协议间进行转译。 -OpenOCD也执行其它一些翻译中重要的工作,用于调试你的STM32FDISCOVERY开发板上的ARM Cortex-M微控制器: -* 他知道如何与ARM CoreSight调试外设使用的内存映射寄存器交互。这些CoreSight寄存器的功能有: +OpenOCD也执行其它一些转译中的重要的工作,用于调试你的STM32FDISCOVERY开发板上的ARM Cortex-M微控制器: +* 它知道如何与ARM CoreSight调试外设使用的内存映射的寄存器进行交互。这些CoreSight寄存器的功能有: * 断点操作 * 读取和写入CPU寄存器 * 发现CPU什么时候因为一个调试事件被挂载 From 0a7d73f27576388656cd6d4293fc4585be381a76 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 11:32:21 +0800 Subject: [PATCH 095/137] minor --- src/intro/install.md | 5 +++-- src/intro/install/verify.md | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/intro/install.md b/src/intro/install.md index e1ef576e..6d3e3989 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -1,5 +1,5 @@ # 安装工具 -这一页包含那些与操作系统无关的工具安装指令: +这一页包含的工具安装指令与操作系统无关: ### Rust 工具链 跟着[https://rustup.rs](https://rustup.rs)的指令安装rustup。 @@ -64,7 +64,8 @@ cargo install cargo-generate ``` 注意:在某些Linux发行版上(e.g. Ubuntu) 在安装cargo-generate之前,你可能需要安装`libssl-dev`和`pkg-config` -### 操作系统特定指令 +### 特定于操作系统的指令 + 现在根据你使用的操作系统,来执行对应的指令: - [Linux](install/linux.md) diff --git a/src/intro/install/verify.md b/src/intro/install/verify.md index ea90d375..037e4df3 100644 --- a/src/intro/install/verify.md +++ b/src/intro/install/verify.md @@ -2,7 +2,7 @@ 在这个章节中我们将检查工具和驱动是否已经被正确地安装和配置了。 -使用一个micro USB线缆将你的笔记本/个人电脑连接到discovery开发板上。discovery开发板有两个USB连接器;使用标记着"USB ST-LINK"的那个,它位于开发板边缘的中间位置。 +使用一个micro USB线缆将你的笔记本/个人电脑连接到discovery开发板上。discovery开发板有两个USB连接器;使用标记着"USB ST-LINK"的那个,它位于开发板边缘的中间位置。 也要检查下ST-LINK的短路帽被安装了。看下面的图;ST-LINK短路帽用红色圈起来了。 @@ -54,7 +54,7 @@ openocd -f interface/stlink-v2.cfg -f target/stm32f3x.cfg openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg ``` -如果这些命令的某条起作用了,那意味着你使用的discovery开发板是一个旧的版本。那也不成问题,但是你要记住这件事,因为随后你可能需要有点不同的配置。你可以移到[下个章节]了。 +如果这些命令的某条起作用了,那意味着你使用的discovery开发板是一个旧的版本。那也不成问题,但是你要记住这件事,因为随后你可能需要点不同的配置。你可以移到[下个章节]了。 如果这些命令在normal user模式下都没用,尝试下使用root模式运行它们(e.g. `sudo openocd ..`)。如果命令在root模式下起作用,要检查下[udev rules]是否被正确地设置了。 From 9f5855ccd89a94edd0b959c44dea1bf4a8dae612 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 12:27:55 +0800 Subject: [PATCH 096/137] minor --- src/start/hardware.md | 20 ++++++++-------- src/start/qemu.md | 52 ++++++++++++++++++++++-------------------- src/start/registers.md | 16 ++++++------- 3 files changed, 45 insertions(+), 43 deletions(-) diff --git a/src/start/hardware.md b/src/start/hardware.md index f86c02c8..1742dda2 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -4,9 +4,9 @@ ## 认识你的硬件 -在我们开始之前,你需要认识你的目标设备的一些特性,因为它们将被用于配置项目: -- ARM 核心。e.g. Cortex-M3 。 -- ARM 核心包括一个FPU吗?Cortex-M4**F**和Cortex-M7**F**有。 +在我们开始之前,你需要了解下你的目标设备的一些特性,因为它们将被用于配置项目: +- ARM 内核。e.g. Cortex-M3 。 +- ARM 内核包括一个FPU吗?Cortex-M4**F**和Cortex-M7**F**有。 - 目标设备有多少Flash和RAM?e.g. 256KiB的Flash和32KiB的RAM。 - Flash和RAM映射在地址空间什么位置?e.g. RAM通常位于 `0x2000_0000` 地址处。 @@ -15,7 +15,7 @@ 这部分,我们会用我们的参考硬件,STM32F3DISCOVERY。这个板子包含一个STM32F303VCT6微控制器。这个微控制器拥有: - 一个Cortex-M4F核心,它包含一个单精度FPU。 - 位于 0x0800_0000 地址的256KiB的Flash。 -- 位于 0x2000_0000 地址的40KiB的RAM。(这里还有其它的RAM区域,但是为了简便,我们将忽略它)。 +- 位于 0x2000_0000 地址的40KiB的RAM。(这里还有其它的RAM区域,但是为了方便起见,我们将忽略它)。 ## 配置 @@ -60,11 +60,11 @@ MEMORY RAM : ORIGIN = 0x20000000, LENGTH = 40K } ``` -> **注意**:如果你因为一些理由,在对一个特定构建目标第一次构建后,改变了`memory.x`文件,需要在`cargo build`之前执行`cargo clean`。因为`cargo build`可能不会跟踪`memory.x`的更新。 +> **注意**:如果你因为一些理由,在对某个编译目标第一次编译后,改变了`memory.x`文件,需要在`cargo build`之前执行`cargo clean`。因为`cargo build`可能不会追踪`memory.x`的更新。 我们将使用hello示例再次开始,但是首先我们必须做一个小改变。 -在`examples/hello.rs`中,确保`debug::exit()`调用被注释掉了或者移除。它只能用于在QEMU中运行时。 +在`examples/hello.rs`中,确保`debug::exit()`调用被注释掉了或者移除。它只能用于在QEMU中运行的情况。 ```rust,ignore #[entry] @@ -79,7 +79,7 @@ fn main() -> ! { } ``` -你能像你之前做的一样,使用`cargo build`检查编译程序,使用`cargo-binutils`观察二进制文件。`cortex-m-rt`库可以处理所有运行芯片所需的魔法,同样有用的是,几乎所有的Cortex-M CPUs都按同样的方式启动。 +你能像你之前做的一样,使用`cargo build`检查编译程序,使用`cargo-binutils`观察二进制文件。`cortex-m-rt`库可以处理所有运行芯片所需的魔法,几乎所有的Cortex-M CPUs都按同样的方式启动,这同样有用。 ``` console cargo build --example hello @@ -87,7 +87,7 @@ cargo build --example hello ## 调试 -调试将看起来有点不同。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试的,特定的信息,可以看[the Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 +调试将看起来有点不同。事实上,取决于不同的目标设备第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试的特定的信息,可以看[the Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 像之前一样,我们将进行远程调试,客户端将是一个GDB进程。不同的是,OpenOCD将是服务器。 @@ -95,7 +95,7 @@ cargo build --example hello [安装验证]: ../intro/install/verify.md -在一个终端上运行 `openocd` 连接到你开发板上的 ST-LINK 。从模板的根目录运行这个命令;`openocd` 将会选择 `openocd.cfg` 文件,它指出了所使用的接口文件(interface file)和目标文件(target file)。 +在一个终端上运行 `openocd` 连接到你的开发板上的 ST-LINK 。从模板的根目录运行这个命令;`openocd` 将会选择 `openocd.cfg` 文件,它指出了所使用的接口文件(interface file)和目标文件(target file)。 ``` console cat openocd.cfg @@ -186,7 +186,7 @@ Breakpoint 1, main () at examples/hello.rs:15 15 let mut stdout = hio::hstdout().unwrap(); ``` -> **注意** 如果在你使用了上面的`continue`命令后,GDB阻塞住了终端而不是停在了断点处,你可能需要检查下`memory.x`文件中的存储分区的信息,对于你的设备来说是否被正确的设置了 (both the starts *and* lengths) 。 +> **注意** 如果在你使用了上面的`continue`命令后,GDB阻塞住了终端而不是停在了断点处,你可能需要检查下`memory.x`文件中的存储分区的信息,对于你的设备来说是否被正确的设置了起始位置**和**大小 。 使用 `next` 让程序继续,应该像之前一样,产生一样的结果。 diff --git a/src/start/qemu.md b/src/start/qemu.md index 39692542..a0083ccf 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -1,13 +1,13 @@ # QEMU -我们将开始为[LM3S6965]编写程序,一个Cortex-M3微控制器。我们选择这个作为我们的第一个目标办,因为他能使用[QEMU仿真](https://wiki.qemu.org/Documentation/Platforms/ARM#Supported_in_qemu-system-arm),因此本节中,你不需要摆弄硬件,我们注意力可以集中在工具和开发过程上。 +我们将开始为[LM3S6965]编写程序,一个Cortex-M3微控制器。我们选择这个作为我们的第一个目标,因为它能使用[QEMU仿真](https://wiki.qemu.org/Documentation/Platforms/ARM#Supported_in_qemu-system-arm),因此本节中,你不需要摆弄硬件,我们注意力可以集中在工具和开发过程上。 [LM3S6965]: http://www.ti.com/product/LM3S6965 **重要** -在这个引导里,我们将使用"app"这个名字来代指项目名。无论何时你看到单词"app"你应该用你选择的项目名来替代它。或者你也能命名你的项目为"app",避免替代。 +在这个引导里,我们将使用"app"这个名字来代指项目名。无论何时你看到单词"app",你应该用你选择的项目名来替代"app"。或者你也可以选择把你的项目命名为"app",避免替代。 ## 生成一个非标准的 Rust program -我们将使用[`cortex-m-quickstart`]项目模板来生成一个新项目。生成的项目将包含一个最基本的应用:对于一个新的嵌入式rust应用来说,是一个很好的起点。另外,项目将包含一个`example`文件夹,文件夹中有许多独立的应用,突出一些关键的嵌入式rust功能。 +我们将使用[`cortex-m-quickstart`]项目模板来生成一个新项目。生成的项目将包含一个最基本的应用:对于一个新的嵌入式rust应用来说,是一个很好的起点。另外,项目将包含一个`example`文件夹,文件夹中有许多独立的应用,突出了一些关键的嵌入式rust的功能。 [`cortex-m-quickstart`]: /~https://github.com/rust-embedded/cortex-m-quickstart @@ -70,7 +70,7 @@ cd app 或者你可以浏览[`cortex-m-quickstart`],点击绿色的 "Clone or download" 按钮,然后点击 "Download ZIP" 。 -然后像在 “使用 `git`” 那里的第二部分那样填充 `Cargo.toml` 。 +然后像在 “使用 `git`” 那里的第二部分写的那样填充 `Cargo.toml` 。 ## 项目概览 @@ -92,11 +92,11 @@ fn main() -> ! { } ``` -这个程序与标准Rust程序有一点不同,因此让我们走进点看看。 +这个程序与标准Rust程序有一点不同,因此让我们走近点看看。 -`#![no_std]`指出这个程序将*不会*链接标准crate`std`。反而它将会链接到它的子集: `core`crate。 +`#![no_std]`指出这个程序将 *不会* 链接标准crate`std`。反而它将会链接到它的子集: `core` crate。 -`#![no_main]`指出这个程序将不会使用标准的大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是在`no_std`上下文中使用`main`接口要求nightly(译者注:原文是`requires nightly`,不知道有什么合适的翻译,主要的理由是`main`接口对程序的运行环境有要求,比如,它假设命令行参数存在,这不适合`no_std`环境)。 +`#![no_main]`指出这个程序将不会使用标准的且被大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是,因为在`no_std`上下文中使用`main`接口要求nightly rust(译者注:`main`接口对程序的运行环境有要求,比如,它假设命令行参数存在,这不适合`no_std`环境)。 `use panic_halt as _;`。这个crate提供了一个`panic_handler`,它定义了程序陷入`panic`时的行为。我们将会在这本书的[运行时恐慌(Panicking)](panicking.md)章节中覆盖更多的细节。 @@ -105,10 +105,11 @@ fn main() -> ! { [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 -`fn main() -> !`。我们的程序将会是运行在目标板子上的*唯一*的进程,因此我们不想要它结束!我们使用一个[divergent function](https://doc.rust-lang.org/rust-by-example/fn/diverging.html) (函数签名中的 `-> !` 位)确保在编译时就是这么回事儿。 +`fn main() -> !`。我们的程序将会是运行在目标板子上的 *唯一* 的进程,因此我们不想要它结束!我们使用一个[divergent function](https://doc.rust-lang.org/rust-by-example/fn/diverging.html) (函数签名中的 `-> !` )来确保在编译时就是这么回事儿。 ## 交叉编译 -下一步是位Cortex-M3架构*交叉*编译程序。如果你知道编译目标(`$TRIPLE`)应该是什么,那就和运行`cargo build --target $TRIPLE`一样简单。幸运地,模板中的`.cargo/config.toml`有这个答案: + +下一步是为Cortex-M3架构*交叉*编译程序。如果你知道编译目标(`$TRIPLE`)应该是什么,那就和运行`cargo build --target $TRIPLE`一样简单。幸运地,模板中的`.cargo/config.toml`有这个答案: ```console tail -n6 .cargo/config.toml ``` @@ -120,7 +121,7 @@ target = "thumbv7m-none-eabi" # Cortex-M3 # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) # target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) ``` -为了交叉编译Cortex-M3架构我们不得不使用`thumbv7m-none-eabi`。当安装Rust工具时,target不会自动被安装,如果你还没有做,现在是个好时机添加那个target到工具链上。 +为了交叉编译Cortex-M3架构我们不得不使用`thumbv7m-none-eabi`。当安装Rust工具时,target不会自动被安装,如果你还没有做,现在是个好时机,去添加那个target到工具链上。 ``` console rustup target add thumbv7m-none-eabi ``` @@ -132,7 +133,7 @@ cargo build ## 检查 -现在我们在`target/thumbv7m-none-eabi/debug/app`中有一个非原生的ELF二进制文件。我们能使用`cargo-binutils`检查它。 +现在我们在`target/thumbv7m-none-eabi/debug/app`中有一个非主机环境的ELF二进制文件。我们能使用`cargo-binutils`检查它。 使用`cargo-readobj`我们能打印ELF头,确认这是一个ARM二进制。 @@ -204,9 +205,9 @@ Total 14570 > - `.data` 包含静态分配的初始值*非*零的变量 > - `.bss` 也包含静态分配的初始值*是*零的变量 > - `.vector_table` 是一个我们用来存储向量(中断)表的*非*标准的section -> - `.ARM.attributes` 和 `.debug_*` sections包含元数据,当烧录二进制文件时,其将不会被加载到目标上的。 +> - `.ARM.attributes` 和 `.debug_*` sections包含元数据,当烧录二进制文件时,它们不会被加载到目标上的。 -**重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*不是正确地反应了程序当被烧录到设备上时将占据的空间的大小。*总是*使用`cargo-size`检查一个二进制文件有多大。 +**重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*没有正确地反应了程序被烧录到设备上时将占据的空间的大小。*一直*使用`cargo-size`检查一个二进制文件有多大。 `cargo-objdump` 能用来反编译二进制文件。 @@ -216,7 +217,7 @@ cargo objdump --bin app --release -- --disassemble --no-show-raw-insn --print-im > **注意** 如果上面的命令抱怨 `Unknown command line argument` 看下面的bug报告:/~https://github.com/rust-embedded/book/issues/269 -> **注意** 这个输出可能在你的系统上不同。rustc, LLVM 和库的新版本能产出不同的汇编。我们截取了一些指令 +> **注意** 在你的系统上这个输出可能不一样。rustc, LLVM 和库的新版本能产出不同的汇编。我们截取了一些指令 ```text app: file format ELF32-arm-little @@ -259,7 +260,7 @@ HardFault: ## 运行 -接下来,让我们看一个嵌入式程序是如何在QEMU上运行!这时我们将使用 `hello` 例子,来做些真正的事。 +接下来,让我们看一个嵌入式程序是如何在QEMU上运行的!此刻我们将使用 `hello` 示例,来做些真正的事。 为了方便起见,这是`examples/hello.rs`的源码: @@ -286,9 +287,9 @@ fn main() -> ! { } ``` -这个程序使用一些被叫做semihosting的东西去打印文本到主机调试台上。当使用真实的硬件时,这要求一个调试对话但是当使用QEMU时它可以工作。 +这个程序使用一些被叫做semihosting的东西去打印文本到主机调试台上。当使用真实的硬件时,这要求一个调试对话,但是当使用QEMU时这样就可以工作了。 -通过编译例子,让我们开始 +通过编译示例,让我们开始 ```console cargo build --example hello @@ -311,7 +312,8 @@ qemu-system-arm \ Hello, world! ``` -这个命令应该打印文本之后成功地退出 (exit code = 0)。你能使用下列的指令检查这个: +这个命令应该打印文本之后成功地退出 (exit code = 0)。你能使用下列的指令检查下: + ```console echo $? ``` @@ -322,7 +324,7 @@ echo $? 让我们看看QEMU命令: -+ `qemu-system-arm`。这是QEMU仿真器。这些QEMU二进制有一些变种;因为名字这个仿真器能做ARM机器的全系统仿真。 ++ `qemu-system-arm`。这是QEMU仿真器。这些QEMU有一些改良版的二进制文件;这个仿真器能做ARM机器的全系统仿真。 + `-cpu cortex-m3`。这告诉QEMU去仿真一个Cortex-M3 CPU。指定CPU模型会让我们捕捉到一些误编译错误:比如,运行一个为Cortex-M4F编译的程序,它具有一个硬件FPU,在执行时将会使QEMU报错。 + `-machine lm3s6965evb`。这告诉QEMU去仿真 LM3S6965EVB,一个包含LM3S6965微控制器的评估板。 @@ -353,7 +355,7 @@ Hello, world! ## 调试 对于嵌入式开发来说,调试非常重要。让我们来看下如何完成它。 -因为我们想要调试的程序运行的机器上,并没有运行一个调试器程序(GDB或者LLDB),所以调试一个嵌入式设备就涉及到了*远程*调试 +因为我们想要调试的程序所运行的机器上并没有运行一个调试器程序(GDB或者LLDB),所以调试一个嵌入式设备就涉及到了 *远程* 调试 远程调试涉及一个客户端和一个服务器。在QEMU的情况中,客户端将是一个GDB(或者LLDM)进程且服务器将会是运行着嵌入式程序的QEMU进程。 @@ -394,21 +396,21 @@ Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473 473 pub unsafe extern "C" fn Reset() -> ! { ``` -你将看到,进程被挂起了,程序计数器正指向一个名为 `Reset` 的函数。那是 reset 句柄:Cortex-M核在启动时执行的中断函数。 +你将看到,进程被挂起了,程序计数器正指向一个名为 `Reset` 的函数。那是 reset 句柄:Cortex-M 内核在启动时执行的中断函数。 -> 注意在一些配置中,可能不会像上面一样,展示那行`Reset() at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473`,gdb可能打印一些警告,比如: +> 注意在一些配置中,可能不会像上面一样,显示`Reset() at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473`,gdb可能打印一些警告,比如: > >`core::num::bignum::Big32x40::mul_small () at src/libcore/num/bignum.rs:254` > ` src/libcore/num/bignum.rs: No such file or directory.` > -> 那是一个已知的小bug,你可以安全地忽略这些警告,you're most likely at Reset()。 +> 那是一个已知的小bug,你可以安全地忽略这些警告,你非常大可能已经Reset()了。 这个reset句柄最终将调用我们的主函数,让我们使用一个断点和`continue`命令跳过所有的步骤。为了设置断点,让我们首先看下我们想要在我们代码哪里打断点,使用`list`指令 ```console list main ``` -这将从examples/hello.rs文件显示源代码。 +这将显示从examples/hello.rs文件来的源代码。 ```text 6 use panic_halt as _; 7 @@ -467,7 +469,7 @@ next [Inferior 1 (Remote target) exited normally] ``` -你现在能退出GDB对话了。 +你现在能退出GDB的会话了。 ``` console quit diff --git a/src/start/registers.md b/src/start/registers.md index 25d76d96..6c4cdf02 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -2,22 +2,22 @@ 嵌入式系统只能通过执行普通的Rust代码和在RAM间移动数据来运行下去。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 -你可能发现,访问你的微控制器外设所需要的代码已经写进了下面某个抽象层中。 +你可能发现,访问你的微控制器外设所需要的代码,已经被写进了下面的某个抽象层中。

-* Micro-architecture Crate(微架构Crate) - 这种Crate提供对你正在使用的微控制器的处理器核心,和任何使用这个特定类型处理器核心的微控制器的常见外设来说,有用的程序。比如 [cortex-m] crate提供你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个能力。 -* Peripheral Access Crate(PAC)(外设访问Crate) - 这种crate是在不同存储器封装的寄存器上再进行的一次封装,其由你正在使用的微控制器的产品号定义的。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,你将根据你的微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 -* HAL Crate - 这些crates为你的处理器提供了一个更友好的API,通常是通过实现在[embedded-hal]中定义的一些常用的traits来实现的。比如,这个crate可能提供一个`Serial`结构体,它的构造函数获取一组合适的GPIO口和一个波特率,它为了发送数据提供一些 `write_byte` 函数。查看 [Portability] 可以看到更多关于 [embedded-hal] 的信息。 +* Micro-architecture Crate(微架构Crate) - 这个Crate提供那些对你正在使用的微控制器的处理器内核和任何使用这个处理器内核的微控制器的那些常见外设,有用的程序。比如 [cortex-m] crate提供给你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个能力。 +* Peripheral Access Crate(PAC)(外设访问Crate) - 这种crate是在被不同存储器封装的寄存器上再进行的一次封装,其由你正在使用的微控制器的产品号定义的。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,你将根据你的微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 +* HAL Crate - 这些crates为你的处理器提供了一个更友好的API,通常是通过实现在[embedded-hal]中定义的一些常用的traits来实现的。比如,这个crate可能提供一个`Serial`结构体,它的构造函数需要一组合适的GPIO端口和一个波特率,它为了发送数据提供一些 `write_byte` 函数。查看 [可移植性] 可以看到更多关于 [embedded-hal] 的信息。 * Board Crate(开发板crate) - 这个通过预先配置的不同的外设和GPIO管脚再进行了一层抽象去适配你正在使用的特定的开发者工具或者开发板,比如对于STM32F3DISCOVERY开发板来说,是[stm32f3-discovery] [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 -[Portability]: ../portability/index.md +[可移植性]: ../portability/index.md [stm32f3-discovery]: https://crates.io/crates/stm32f3-discovery [Discovery]: https://rust-embedded.github.io/discovery/ @@ -25,9 +25,9 @@ 如果你是嵌入式Rust新手,board crate是一个完美的起点。它们很好地抽象出了,在开始学习这个项目时,需要耗费心力了解的硬件细节,使得标准工作变得简单,像是打开或者关闭LED。不同的板子间,它们提供的功能变化很大。因为这本书是不假设我们使用的是何种板子,所以board crate不会被这本书涉及。 -如果你想要用STM32F3DISCOVERY开发板做实验,强烈建议看一下[stm32f3-discovery]开发板crate,它提供了功能点亮LEDs,访问它的指南针,蓝牙和其它的。[Discovery]书对于一个board crate的用法提供一个很好介绍。 +如果你想要用STM32F3DISCOVERY开发板做实验,强烈建议看一下[stm32f3-discovery]开发板crate,它提供了点亮LEDs,访问它的指南针,蓝牙和其它的功能。[Discovery]书对于一个board crate的用法提供一个很好的介绍。 -但是如果你正在使用一个还没有提供专用的board crate的系统,或者你需要一些功能现存的crates不提供,那我们需要从底层的微架构crates开始。 +但是如果你正在使用一个还没有提供专用的board crate的系统,或者你需要的一些功能,现存的crates不提供,那我们需要从底层的微架构crates开始。 ## Micro-architecture crate @@ -90,7 +90,7 @@ pub fn init() -> (Delay, Leds) { ``` -我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,对我们寄存器位域的访问函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是很多代码,但是Rust编译器能使用它为我们执行一批检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保一个特定的访问器函数的所有可能的参数是否有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。当使用 `bits()` 函数设置 `load` 和 `compa` 子域的时候,我们能在上面看到这个例子。 +我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器位域的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是很多代码,但是Rust编译器能使用它为我们执行一批检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保一个特定的访问器函数的所有可能的参数是否有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。当使用 `bits()` 函数设置 `load` 和 `compa` 子域的时候,我们能在上面看到这个例子。 ### 读取 From c1cf8faf67683833595f6f8061bd01b873726a0e Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 15:28:03 +0800 Subject: [PATCH 097/137] minor --- src/start/registers.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 6c4cdf02..2a48d1c4 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -90,11 +90,11 @@ pub fn init() -> (Delay, Leds) { ``` -我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器位域的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是很多代码,但是Rust编译器能使用它为我们执行一批检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保一个特定的访问器函数的所有可能的参数是否有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。当使用 `bits()` 函数设置 `load` 和 `compa` 子域的时候,我们能在上面看到这个例子。 +我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器位域的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是更多代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数,其所有可能的参数都有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 ### 读取 -`read()` 函数返回一个对象,这个对象提供了这个寄存器中不同子域的只读访问,由制造商的关于这个芯片的SVD文件定义。在 [tm4c123x documentation][tm4c123x documentation R] 中你能找到所有,与这个特定芯片上,特定外设中,这个特定寄存器相关的特别 `R` 返回类型上所有可用的函数。 +`read()` 函数返回一个对象,这个对象提供了对这个寄存器中不同子域的只读访问,由厂商提供的这个芯片的SVD文件定义。在 [tm4c123x documentation][tm4c123x documentation R] 中你能找到在这个特别的返回类型 `R` 上所有可用的函数,其与特定芯片中的特定外设的特定寄存器有关。 ```rust,ignore if pwm.ctl.read().globalsync0().is_set() { @@ -104,7 +104,7 @@ if pwm.ctl.read().globalsync0().is_set() { ### 写入 -`write()`函数使用一个只有一个参数的闭包。通常我们把这个参数叫做 `w`。这个参数然后提供对这个寄存器中不同的子域的读写访问,由制造商关于这个芯片的SVD文件提供。再一次,在 [tm4c123x documentation][tm4c123x documentation W] 中你能找到所有,与这个特定芯片上,特定外设中,这个特定寄存器相关的 `w` 上所有可用的函数。注意所有我们没有设置的子域将会被设置一个默认值 - 任何在这个寄存器中现存的内容将会丢失。 +`write()`函数使用一个只有一个参数的闭包。通常我们把这个参数叫做 `w`。然后这个参数提供对这个寄存器中不同的子域的读写访问,由厂商关于这个芯片的SVD文件提供。再一次,在 [tm4c123x documentation][tm4c123x documentation W] 中你能找到 `W` 所有可用的函数,其与特定芯片中的特定外设的特定寄存器有关。注意所有我们没有设置的子域将会被设置一个默认值 - 任何在这个寄存器中的现存的内容将会丢失。 ```rust,ignore @@ -113,13 +113,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(); @@ -127,7 +127,7 @@ temp |= PWM0_CTL_GLOBALSYNC0; pwm0.ctl.write(temp); uint32_t temp2 = pwm0.enable.read(); temp2 |= PWM0_ENABLE_PWM4EN; -pwm0.enable.write(temp); // Uh oh! Wrong variable! +pwm0.enable.write(temp); // 哦 不! 错误的变量! ``` [svd2rust]: https://crates.io/crates/svd2rust @@ -136,7 +136,7 @@ pwm0.enable.write(temp); // Uh oh! Wrong variable! ## 使用一个HAL crate -一个芯片的HAL crate通常通过为PAC暴露的原始结构体们实现一个自定义Trait来发挥作用。经常这个trait将会为某个外设定义一个被称作 `constrain()` 的函数或者为像是有多个管脚的GPIO端口定义一个`split()`函数。这个函数将会使用原始外设结构体,然后返回一个具有更高抽象的API的新对象。这个API还可以做一些事,像是让Serial port的 `new` 函数变成需要某些`Clock`结构体的函数,这个结构体只能通过调用配置PLLs和设置所有的时钟频率的函数来生成。在这时,生成一个Serial port对象而不先配置时钟速率是不可能的,对于Serial port对象来说错误地将波特率转换为时钟滴答数也是不可能发生的。一些crates甚至为每个GPIO管脚的状态定义了特定的 traits,在把管脚传递进外设前,要求用户去把一个管脚设置成正确的状态(通过选择Alternate Function模式) 。所有这些都没有运行时消耗! +一个芯片的HAL crate通常通过为PAC暴露的基础结构体们实现一个自定义Trait来发挥作用。经常这个trait将会为某个外设定义一个被称作 `constrain()` 的函数或者为像是有多个管脚的GPIO端口定义一个`split()`函数。这个函数将会使用基础外设结构体,然后返回一个具有更高抽象的API的新对象。这个API还可以做一些事,像是让Serial port的 `new` 函数变成需要某些`Clock`结构体的函数,这个结构体只能通过调用配置PLLs和设置所有的时钟频率的函数来生成。在这时,生成一个Serial port对象而不先配置时钟速率是不可能的,对于Serial port对象来说错误地将波特率转换为时钟滴答数也是不可能发生的。一些crates甚至为每个GPIO管脚的状态定义了特定的 traits,在把管脚传递进外设前,要求用户去把一个管脚设置成正确的状态(通过选择Alternate Function模式) 。所有这些都没有运行时开销的! 让我们看一个例子: @@ -164,7 +164,7 @@ fn main() -> ! { sysctl::CrystalFrequency::_16mhz, sysctl::SystemClock::UsePll(sysctl::PllOutputFrequency::_80_00mhz), ); - // 把PLL设置成那些配置 + // 设置PLL let clocks = sc.clock_setup.freeze(); // 把GPIO_PORTA结构体封装成一个有更高抽象API的对象 From 6359737e400b8cf4609d5054ec2a0ddef07acecf Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 17:52:01 +0800 Subject: [PATCH 098/137] minor --- src/start/exceptions.md | 26 +++++++++++++------------- src/start/interrupts.md | 14 +++++++------- src/start/panicking.md | 6 +++--- src/start/semihosting.md | 12 ++++++------ 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/start/exceptions.md b/src/start/exceptions.md index 00d7c7c7..0c11c86b 100644 --- a/src/start/exceptions.md +++ b/src/start/exceptions.md @@ -1,6 +1,6 @@ # 异常 -异常和中断,是处理器用来处理异步事件和致命错误的一种硬件机制(e.g. 执行一个无效的指令)。异常意味着抢占并涉及异常处理程序,即响应触发事件的信号的子例程。 +异常和中断,是处理器用来处理异步事件和致命错误(e.g. 执行一个无效的指令)的一种硬件机制。异常意味着抢占且涉及到异常处理程序,即响应触发事件的信号的子例程。 `cortex-m-rt` crate提供了一个 [`exception`] 属性去声明异常处理程序。 @@ -14,9 +14,9 @@ fn SysTick() { } ``` -除了 `exception` 属性,异常处理函数看起来和普通函数一样,但是有一个很大的不同: `exception` 处理函数*不能*被软件调用。按照先前的例子,语句 `SysTick();` 将会导致一个编译错误。 +除了 `exception` 属性,异常处理函数看起来和普通函数一样,但是有一个很大的不同: `exception` 处理函数 *不能* 被软件调用。在先前的例子中,语句 `SysTick();` 将会导致一个编译错误。 -这么做是有目的的,因为异常处理函数被要求具有一个特性: 在异常处理函数中被声明`static mut`的变量能被安全(safe)地使用。 +这么做是有目的的,因为异常处理函数必须具有一个特性: 在异常处理函数中被声明`static mut`的变量能被安全(safe)地使用。 ``` rust,ignore #[exception] @@ -28,15 +28,15 @@ fn SysTick() { } ``` -就像你可能已经知道的那样,在一个函数里使用`static mut`变量,会让函数变成[*非可重入函数(non-reentrancy)*](https://en.wikipedia.org/wiki/Reentrancy_(computing))。从多个异常/中断处理函数,或者从`main`函数同多个异常/中断处理函数中直接或者间接地调用一个非可重入(non-reentrancy)函数是未定义的行为。 +就像你可能已经知道的那样,在一个函数里使用`static mut`变量,会让函数变成[*非可重入函数(non-reentrancy)*](https://en.wikipedia.org/wiki/Reentrancy_(computing))。从多个异常/中断处理函数,或者从`main`函数同多个异常/中断处理函数中,直接或者间接地调用一个非可重入(non-reentrancy)函数是未定义的行为。 -安全的Rust必须从不会造成未定义的行为,所以非可重入函数必须被标记为 `unsafe`。然而,我刚说了`exception`处理函数能安全地使用`static mut`变量。这怎么可能?因为`exception`处理函数*不*能被软件调用因此重入(reentrancy)不会发生,所以这才变得可能。 +安全的Rust不能导致未定义的行为出现,所以非可重入函数必须被标记为 `unsafe`。然而,我刚说了`exception`处理函数能安全地使用`static mut`变量。这怎么可能?因为`exception`处理函数 *不* 能被软件调用因此重入(reentrancy)不会发生,所以这才变得可能。 -> 注意,`exception`属性,通过将静态变量封装进`unsafe`块中,转换了函数中静态变量的定义,为我们提供了同个名字的,类型为 `&mut` 的,新的合适的变量。因此我们可以通过 `*` 解引用访问变量的值而不需要将它们打包进一个 `unsafe` 块中。 +> 注意,`exception`属性,通过将静态变量封装进`unsafe`块中且为我们提供了名字相同的,类型为 `&mut` 的,新的合适的变量,转换了函数中静态变量的定义。因此我们可以通过 `*` 解引用访问变量的值而不需要将它们打包进一个 `unsafe` 块中。 ## 一个复杂的例子 -这里有个例子,使用系统计时器大概每秒都会抛出一个 `SysTick` 异常。异常处理函数使用 `COUNT` 变量追踪它自己被调用了多少次,然后使用半主机模式(semihosting)打印 `COUNT` 的值到主机控制台上。 +这里有个例子,使用系统计时器大概每秒会抛出一个 `SysTick` 异常。异常处理函数使用 `COUNT` 变量追踪它自己被调用了多少次,然后使用半主机模式(semihosting)打印 `COUNT` 的值到主机控制台上。 > **注意**: 你能在任何Cortex-M设备上运行这个例子;你也能在QEMU运行它。 @@ -89,7 +89,7 @@ fn SysTick() { } // 重要信息 如果运行在真正的硬件上,去掉这个 `if` 块, - // 否则你的调试器将会以一种不一致的状态结束 + // 否则你的调试器将会以一种不一样的状态结束 if *COUNT == 9 { // 这将终结QEMU进程 debug::exit(debug::EXIT_SUCCESS); @@ -115,7 +115,7 @@ $ cargo run --release 123456789 ``` -如果你在Discovery开发板上运行这个例子,你将会在OpenOCD控制太上看到输出。还有,当计数到达9的时候,程序将 *不会* 停止。 +如果你在Discovery开发板上运行这个例子,你将会在OpenOCD控制台上看到输出。还有,当计数到达9的时候,程序将 *会* 停止。 ## 默认异常处理函数 @@ -142,13 +142,13 @@ fn DefaultHandler(irqn: i16) { ## 硬错误(Hard Fault)处理函数 -`HardFault`异常有点特别。当程序进入一个无效的状态,这个异常被触发,因此它的处理函数 *不能* 返回,因为这么做可能导致一个未定义的行为。在用户定义的 `HardFault` 处理函数被调用之前,运行时crate还做了一些工作去提供可调试性。 +`HardFault`异常有点特别。当程序进入一个无法工作的状态时,这个异常被触发,因此它的处理函数 *不能* 返回,因为这么做可能导致一个未定义的行为。在用户定义的 `HardFault` 处理函数被调用之前,运行时crate还做了一些工作去提供可调试性。 结果是,`HardFault`处理函数必须有下列的签名: `fn(&ExceptionFrame) -> !` 。处理函数的参数是一个指针,它指向被异常推入栈中的寄存器。这些寄存器是异常被触发那刻,处理器状态的一个记录,能被用来分析一个硬错误。 -这里有个执行一个不合法操作的例子: 读取一个不存在的存储位置。 +这里有个执行不合法操作的案例: 读取一个不存在的存储位置。 -> **注意**: 这个程序在QEMU上将不会工作,i.e. 它将不会崩溃,因为 `qemu-system-arm -machine lm3s6965evb` 不检查存储加载,且读取无效存储时将会开心地返回 `0`。 +> **注意**: 这个程序在QEMU上将不会工作,i.e. 它将不会崩溃,因为 `qemu-system-arm -machine lm3s6965evb` 不对读取存储的操作进行检查,且读取无效存储时将会开心地返回 `0`。 ```rust,ignore #![no_main] @@ -199,7 +199,7 @@ ExceptionFrame { } ``` -`pc`值是异常时,程序计数器(Program Counter)的值,它指向触发了异常的指令。 +`pc`值是异常时程序计数器(Program Counter)的值,它指向触发了异常的指令。 如果你看向程序的反汇编: diff --git a/src/start/interrupts.md b/src/start/interrupts.md index b3154137..5f220001 100644 --- a/src/start/interrupts.md +++ b/src/start/interrupts.md @@ -1,14 +1,14 @@ # 中断 -虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎一样,且它们也能被同一个中断控制器处理。然而异常是由Cortex-M微架构定义的,中断在命名和功能上总是供应商(经常甚至是芯片)特定的实现。 +虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎一样,且它们也能被同一个中断控制器处理。然而异常是由Cortex-M微架构定义的,中断在命名和功能上总是由特定厂商(经常甚至是芯片)实现的。 -中断提供了很多灵活性,当尝试用一种高级的方法使用它们时这种灵活性需要被解释。我们将不会在这本书里涵盖那些使用,然而把下面的东西记在心里是个不错的想法: +中断提供了更多的灵活性,当尝试用一种高级的方法使用它们时,我们需要对这种灵活性进行解释。但我们将不会在这本书里涵盖那些内容,把下面的东西记在心里是个不错的想法: * 中断有可以编程的优先级,其决定了它们的处理函数的执行顺序。 -* 中断能嵌套且抢占,i.e. 一个中断处理函数的执行可以被其它更高优先级的中断中断。 -* 通常导致中断被触发的原因需要被清除,避免无限地再次进入中断处理函数。 +* 中断能嵌套且抢占,i.e. 一个中断处理函数的执行可以被其它更高优先级的中断打断。 +* 通常导致中断被触发的因素需要被清除,避免无限地再次进入中断处理函数。 -在运行时的常规初始化步骤始终相同: -* 设置外设在遇到想要的时候产生中断请求 +运行时的常规初始化步骤始终相同: +* 设置外设在遇到想要的事故发生的时候产生中断请求 * 在中断控制器中设置需要的中断处理函数的优先级 * 在中断控制器中使能中断处理函数 @@ -25,7 +25,7 @@ fn TIM2() { } ``` -中断处理函数和异常处理函数一样看起来像是普通的函数(除了没有入参)。然而由于特殊的调用约定,它不能被固件的其它部分直接调用。但是,可以在软件中生成中断请求去触发一个转移,进入中断处理函数。 +中断处理函数和异常处理函数一样看起来像是普通的函数(除了没有入参)。然而由于特殊的调用规定,它不能被固件的其它部分直接调用。但是,可以在软件中生成中断请求,触发一个控制权的转移。 与异常处理函数一样,它也能在中断处理函数中声明`static mut`变量且保持 *safe* 状态。 diff --git a/src/start/panicking.md b/src/start/panicking.md index baf799cb..2c118d2d 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -6,7 +6,7 @@ 然而在没有标准库的程序中,运行时恐慌的行为是未被定义了的。通过声明一个 `#[painc_handler]` 函数可以选择一个运行时恐慌的行为。 -这个函数必须在一个程序的依赖图中只出现一次,且必须有这样的签名: `fn(&PanicInfo) -> !`,`PanicInfo`是一个包含关于运行时恐慌位置的信息的结构体。 +这个函数必须在一个程序的依赖图中只出现一次,且必须有这样的签名: `fn(&PanicInfo) -> !`,`PanicInfo`是一个包含关于运行时恐慌发生位置的信息的结构体。 [`PanicInfo`]: https://doc.rust-lang.org/core/panic/struct.PanicInfo.html @@ -45,7 +45,7 @@ use panic_abort as _; 在这个例子里,当使用dev配置编译的时候(`cargo build`),crate链接到 `panic-halt` crate上,但是当使用release配置编译时(`cargo build --release`),crate链接到`panic-abort` crate上。 -> `use panic_abort as _` 形式的 `use` 语句被用来确保 `panic_abort` 运行时恐慌函数被包含进我们最终的可执行程序里,同时让编译器清除地知道我们不会从这个crate显式地使用任何东西。没有 `_` 重命名,编译器将会警告我们有一个未使用的导入。反而,有时候你可能看到的是 `extern crate panic_abort`,这是Rust 2018之前的版本使用的更旧的写法,现在应该只被用于 "sysroot" crates (与Rust一起发布的crates),比如 `proc_macro`,`alloc`,`std` 和 `test` 。 +> `use panic_abort as _` 形式的 `use` 语句被用来确保 `panic_abort` 运行时恐慌函数被包含进我们最终的可执行程序里,同时让编译器清楚地知道我们不会从这个crate显式地使用任何东西。没有 `_` 重命名,编译器将会警告我们有一个未使用的导入。有时候你可能会看到 `extern crate panic_abort`,这是Rust 2018之前的版本使用的更旧的写法,现在应该只被用于 "sysroot" crates (与Rust一起发布的crates),比如 `proc_macro`,`alloc`,`std` 和 `test` 。 ## 一个例子 @@ -77,4 +77,4 @@ $ cargo run panicked at 'index out of bounds: the len is 3 but the index is 4', src/main.rs:12:13 ``` -你可以尝试将行为改成`panic-halt`,确定在这个案例里没有信息被打印。 +你可以尝试将行为改成`panic-halt`,确保在这个案例里没有信息被打印。 diff --git a/src/start/semihosting.md b/src/start/semihosting.md index 44920fdd..e9d86deb 100644 --- a/src/start/semihosting.md +++ b/src/start/semihosting.md @@ -1,8 +1,8 @@ # 半主机模式 -半主机模式是一种让嵌入式设备在主机上进行I/O操作的的机制,主要被用来记录信息到主机控制台上。半主机模式需要一个debug会话,除此之外几乎没有其它要求了,因此它非常易于使用。缺点是它非常慢:取决于你的硬件调试器(e.g. ST-LINK),每个写操作需要几毫秒的时间。 +半主机模式是一种让嵌入式设备在主机上进行I/O操作的的机制,主要被用来记录信息到主机控制台上。半主机模式需要一个debug会话,除此之外几乎没有其它要求了,因此它非常易于使用。缺点是它非常慢:每个写操作需要几毫秒的时间,其取决于你的硬件调试器(e.g. ST-LINK)。 -[`cortex-m-semihosting`] crate 提供一个API去在Cortex-M设备上执行半主机操作。下面的程序是"Hello, world!"的半主机版本。 +[`cortex-m-semihosting`] crate 提供了一个API去在Cortex-M设备上执行半主机操作。下面的程序是"Hello, world!"的半主机版本。 [`cortex-m-semihosting`]: https://crates.io/crates/cortex-m-semihosting @@ -38,7 +38,7 @@ Hello, world! semihosting is enabled ``` -QEMU理解半主机操作,因此上面的程序不需要启动一个debug会话,也能在`qemu-system-arm`中工作。注意你需要传递`-semihosting-config`标志给QEMU去使能半主机支持;这些标识已经被包括在模板的`.cargo/config.toml`文件中了。 +QEMU理解半主机操作,因此上面的程序不需要启动一个debug会话,也能在`qemu-system-arm`中工作。注意你需要传递`-semihosting-config`标志给QEMU去使能支持半主机模式;这些标识已经被包括在模板的`.cargo/config.toml`文件中了。 ``` text $ # this program will block the terminal @@ -47,7 +47,7 @@ $ cargo run Hello, world! ``` -`exit`半主机操作也能被用于终止QEMU进程。重要:**不要**在硬件上使用`debug::exit`;这个函数会关闭你的OpenOCD对话,除了重启它,你将不能执行更多的程序调试操作。 +`exit`半主机操作也能被用于终止QEMU进程。重要:**不要**在硬件上使用`debug::exit`;这个函数会关闭你的OpenOCD对话,这样你将不能执行其它程序调试操作,除了重启它。 ```rust,ignore #![no_main] @@ -82,7 +82,7 @@ $ echo $? 最后一个提示:你可以将运行时恐慌(panicking)的行为设置成 `exit(EXIT_FAILURE)`。这将允许你编写可以在QEMU上运行通过的 `no_std` 测试。 -为了方便,`panic-semihosting`crate有一个 "exit" 特性。当它使能的时候,在主机stderr上打印恐慌(painc)信息后会调用 `exit(EXIT_FAILURE)` 。 +为了方便,`panic-semihosting` crate有一个 "exit" 特性。当它使能的时候,在主机stderr上打印恐慌(painc)信息后会调用 `exit(EXIT_FAILURE)` 。 ```rust,ignore #![no_main] @@ -120,7 +120,7 @@ $ echo $? panic-semihosting = { version = "VERSION", features = ["exit"] } ``` -`VERSION`是想要的版本。关于依赖特性的更多信息查看Cargo book的[`specifying dependencies`]部分。 +`VERSION`是想要的版本。关于依赖features的更多信息查看Cargo book的[`specifying dependencies`]部分。 [`specifying dependencies`]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html From 05eb63c70a212e353a0791c8677c0f0620b00268 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 19:13:51 +0800 Subject: [PATCH 099/137] minor --- src/peripherals/a-first-attempt.md | 16 ++++++++-------- src/peripherals/borrowck.md | 6 +++--- src/peripherals/index.md | 8 ++++---- src/peripherals/singletons.md | 14 +++++++------- src/static-guarantees/design-contracts.md | 6 +++--- src/static-guarantees/index.md | 5 ++--- src/static-guarantees/state-machines.md | 8 ++++---- src/static-guarantees/typestate-programming.md | 12 ++++++------ src/static-guarantees/zero-cost-abstractions.md | 6 +++--- 9 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 6a3c3295..a835b8cb 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -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 @@ -15,7 +15,7 @@ ## C语言风格的方法(The C Approach) -在Rust中,我们可以像我们在C语言中做的那样,用一个 `struct` 表征一组寄存器。 +在Rust中,我们可以像我们在C语言中做的那样,用一个 `struct` 表示一组寄存器。 ```rust,ignore #[repr(C)] @@ -26,7 +26,7 @@ struct SysTick { pub calib: u32, } ``` -限定符 `#[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; @@ -42,7 +42,7 @@ let time = unsafe { (*systick).cvr }; 3. 你程序中任何地方的任何一段代码都可以通过这个结构体访问硬件。 4. 最重要的是,实际上它并不能工作。 -现在,问题是编译器很聪明。如果你往RAM同个地方写两次,一个接着一个,编译器会注意到这个且完全跳过第一个写入操作。在C语言中,我们能标记变量为`volatile`去确保每个读或写操作按预期发生。在Rust中,我们将*访问* 标记为易变的(volatile),而不是变量。 +现在的问题是编译器很聪明。如果你往RAM同个地方写两次,一个接着一个,编译器会注意到这个行为,且完全跳过第一个写入操作。在C语言中,我们能标记变量为`volatile`去确保每个读或写操作按预期发生。在Rust中,我们将*访问* 标记为易变的(volatile),而不是将变量标记为volatile。 ```rust,ignore let systick = unsafe { &mut *(0xE000_E010 as *mut SysTick) }; @@ -77,9 +77,9 @@ fn get_time() -> u32 { ## Rusty封装 -我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于我们用户来说,可以安全地被调用。作为驱动的作者,我们手动地验证不安全的代码是否正确,然后为我们的用户提供一个安全的API,因此他们不必担心它(让他们相信我们做对了!)。 +我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于我们用户来说,可以安全地被调用。作为驱动的作者,我们手动地验证不安全的代码是否正确,然后为我们的用户提供一个safe的API,因此他们不必担心它(让他们相信我们做对了!)。 -一个可能的例子是: +有可能有这样的例子: ```rust,ignore use volatile_register::{RW, RO}; @@ -119,7 +119,7 @@ pub fn example_usage() -> String { } ``` -现在,这种方法带来的问题是下列的代码完全可以被编译器接受: +现在,这种方法带来的问题是,下列的代码完全可以被编译器接受: ```rust,ignore fn thread1() { @@ -133,4 +133,4 @@ fn thread2() { } ``` -虽然 `set_reload` 函数的 `&mut self` 参数保证了对某个`SystemTimer`结构体的引用只有一个,但是他们不能阻止用户去创造第二个`SystemTimer`,其指向同个外设!如果作者足够尽力,能发现所有这些'重复的'驱动实例,按这种方式写的代码将可以工作,但是一旦代码被散播几天,散播到多个模块,驱动,开发者,它会越来越容易犯此类错误。 +虽然 `set_reload` 函数的 `&mut self` 参数保证了对某个`SystemTimer`结构体的引用只有一个,但是他们不能阻止用户去创造第二个`SystemTimer`,其指向同个外设!如果作者足够尽力,能发现所有这些'重复的'驱动实例,那么按这种方式写的代码将可以工作,但是一旦代码被散播几天,散播到多个模块,驱动,开发者,它会越来越容易犯此类错误。 diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index 7e4303f7..6eb49179 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -7,8 +7,8 @@ 我们如何才能做到可靠地与这些外设交互? 1. 总是使用 `volatile` 方法去读或者写外设存储器。因为它可以随时改变。 -2. 在软件中,我们应该能共享任何数量的 对这些外设的只读访问 -3. 如果一些软件对一个外设应该可以读写访问,它应该保有对那个外设的唯一引用。 +2. 在软件中,我们应该能共享任何数量的关于这些外设的只读访问 +3. 如果某些软件对一个外设应该可以读写访问,它应该保有对那个外设的唯一引用。 ## 借用检查器 @@ -16,4 +16,4 @@ 思考一下,我们是否可以分发这些外设的所有权,或者引用它们? -我们当然可以,但是对于借用检查器来说,每个外设我们都只需要一个实例,这样Rust才可以正确地处理这个实例。在硬件中幸运的是,任何给定的外设,只有一个实例,但是我们该如何暴露它在我们代码的结构体中? +我们当然可以,但是对于借用检查器来说,每个外设我们都只需要一个实例的话,Rust才可以正确地处理这个实例。在硬件中幸运的是,任何给定的外设,只有一个实例,但是我们该如何将它暴露在我们代码的结构中呢? diff --git a/src/peripherals/index.md b/src/peripherals/index.md index 3bd71a12..179d6d25 100644 --- a/src/peripherals/index.md +++ b/src/peripherals/index.md @@ -2,9 +2,9 @@ ## 什么是外设? -大多数微处理器不仅有一个CPU,RAM,或者Flash存储器 - 它们包含硅片被用来与微处理器的外部系统交互的部分,通过传感器,电机控制器,或者人机接口比如一个显示器或者键盘直接和间接地与它们周围的世界交互。这些组件统称为外设。 +大多数微处理器不仅有一个CPU,RAM,或者Flash存储器 - 它们包含部分被用来与微处理器的外部系统交互的硅片,通过传感器,电机控制器,或者人机接口比如一个显示器或者键盘直接和间接地与它们周围的世界交互。这些组件统称为外设。 -这些外设很有用因为它们允许一个开发者将处理工作给它们来做,避免必须在软件中处理每件事。就像一个桌面开发者如何将图形处理工作让给一个显卡那样,嵌入式开发者能将一些任务让给外设去做,让CPU可以把时间放在做其它更重要的事上,或者为了省电啥事也不做。 +这些外设很有用,因为它们允许一个开发者将处理工作交给它们来做,避免了必须在软件中处理每件事。就像一个桌面开发者如何将图形处理工作让给一个显卡那样,嵌入式开发者能将一些任务让给外设去做,让CPU可以把时间放在做其它更重要的事上,或者为了省电啥事也不做。 如果你看向从1970s或者1980s的旧型号的家庭电脑的主板(其实,昨日的桌面PCs与今日的嵌入式系统没太大区别),你将看到: @@ -13,13 +13,13 @@ * 一个ROM芯片 * 一个I/O控制器 -RAM芯片,ROM芯片和I/O控制器(这个系统中的外设)将会通过一系列并行的迹(traces)又被称为一个"总线"被加进处理器中。这个总线搬运地址信息,其用来选择处理器希望跟总线上哪个设备通信,还有一个用来搬运实际数据的数据总线。在我们的嵌入式微控制器中,应用了相同的概念 - 只是所有的东西被打包到一片硅片上。 +RAM芯片,处理器将会通过一系列并行的迹(traces)又被称为一个"总线"将ROM芯片和I/O控制器(这个系统中的外设)加进来。这个总线搬运地址信息,其用来选择处理器希望跟总线上哪个设备通信,还有一个用来搬运实际数据的数据总线。在我们的嵌入式微控制器中,应用了相同的概念 - 只是所有的东西被打包到一片硅片上。 然而,不像显卡,显卡通常有像是Vulkan,Metal,或者OpenGL这样的一个软件API。外设暴露给微控制器的是一个硬件接口,其被映射到一块存储区域。 ## 线性的真实存储空间 -在一个微控制器上,往一些任意别的地址写一些数据,比如 `0x4000_0000` 或者 `0x0000_0000`,可能也是一个完全有效的动作。 +在一个微控制器上,往一些别的任意地址写一些数据,比如 `0x4000_0000` 或者 `0x0000_0000`,可能也是一个完全有效的动作。 在一个桌面系统上,访问内存被MMU,或者内存管理单元紧紧地控制着。这个组件有两个主要责任: 对部分内存加入访问权限(防止一个进程读取或者修改另一个进程的内存);重映射物理内存的段到软件中使用的虚拟内存范围上。微控制器通常没有一个MMU,反而在软件中只使用真实的物理地址。 diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index f1f28057..1a7a87f9 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -21,7 +21,7 @@ fn main() { } ``` -但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是不安全的。这些变量在你的整个程序间也是可见的,意味着借用检查器不能帮你跟踪引用和这些变量的所有者。 +但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你的整个程序间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有者。 ## 我们在Rust中要怎么做? @@ -42,7 +42,7 @@ static mut PERIPHERALS: Peripherals = Peripherals { }; ``` -这个结构体允许我们获得我们外设的一个实例。如果我们尝试调用`take_serial()`获得多个实例,我们的代码将会抛出运行时恐慌(panic)! +这个结构体允许我们获得外设的一个实例。如果我们尝试调用`take_serial()`获得多个实例,我们的代码将会抛出运行时恐慌(panic)! ```rust,ignore fn main() { @@ -56,7 +56,7 @@ fn main() { 这个带来了少量的运行时消耗,因为我们必须打包 `SerialPort` 结构体进一个option中,且我们将需要调用一次 `take_serial()`,但是这种少量的前期成本,能使我们在接下来的程序中使用借用检查器(borrow checker) 。 -## 已存的库支持 +## 已存在的库支持 虽然我们在上面创造了我们自己的 `Peripherals` 结构体,但这并不是必须的。`cortex_m` crate 包含一个被叫做 `singleton!()` 的宏,其将为你执行这个任务。 @@ -73,7 +73,7 @@ fn main() { [cortex_m docs](https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html) -另外,如果你使用 [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic),获取和定义这些外设的整个过程为你做了抽象,你获得了一个`Peripherals`结构体,其包含一个所有你定义了的项的非 `Option` 的版本。 +另外,如果你使用 [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic),其为你抽象了获取和定义这些外设的整个过程,你获得了一个`Peripherals`结构体,其包含了一个所有你定义了的项的非 `Option` 的版本。 ```rust,ignore // cortex-m-rtic v0.5.x @@ -83,10 +83,10 @@ const APP: () = { fn init(cx: init::Context) { static mut X: u32 = 0; - // Cortex-M peripherals + // Cortex-M外设 let core: cortex_m::Peripherals = cx.core; - // Device specific peripherals + // 设备特定的外设 let device: lm3s6965::Peripherals = cx.device; } } @@ -153,5 +153,5 @@ fn read_button(gpio: &GpioPin) -> bool { } ``` -这允许我们强制代码是否应该或者不应该在**编译时**而不是运行时对硬件进行改变,这通常在只有一个应用的情况下起作用,但是对于裸机系统来说,我们的软件将被编译进一个单一应用中,因此它通常是不受限的。 +这允许我们在**编译时**而不是运行时强制代码是否应该或者不应该对硬件进行改变,这通常在只有一个应用的情况下起作用,但是对于裸机系统来说,我们的软件将被编译进一个单一应用中,因此它通常是不受限的。 diff --git a/src/static-guarantees/design-contracts.md b/src/static-guarantees/design-contracts.md index 3524c480..c7a68ad0 100644 --- a/src/static-guarantees/design-contracts.md +++ b/src/static-guarantees/design-contracts.md @@ -247,10 +247,10 @@ output_pin.set_bit(true); // output_pin.into_input_pull_down(); ``` -这绝对是存储管脚状态的便捷方法,但是为什么这么做?为什么这比把状态当成一个`enum`存在我们的`CpioConfig`结构体中更好? +这绝对是存储管脚状态的便捷方法,但是为什么这么做?为什么这比把状态当成一个`enum`存在我们的`GpioConfig`结构体中更好? ## 编译时功能安全(Functional Safety) -因为我们在编译时完全强制执行我们的设计约定,这造成了没有运行时消耗。当你有一个在输入模式的管脚时,是不可能去设置一个输出模式的。你必须先把它设置成一个输出管脚,然后在设置输出模式。因为在执行一个函数前会检查现在的状态,因此没有运行时消耗。 +因为我们在编译时完全强制我们遵守我们的设计约定,这造成了没有运行时开销。当你有一个在输入模式的管脚时,是不可能去设置一个输出模式的。你必须先把它设置成一个输出管脚,然后再设置输出模式。因为在执行一个函数前会检查现在的状态,因此没有运行时消耗。 -也因为这些状态被类型系统强制执行,因此没有为这个接口的使用者留太多的犯错空间。如果它们尝试执行一个非法的状态转换,代码将不会编译! +也因为这些状态被类型系统强制遵守,因此没有为这个接口的使用者留太多的犯错空间。如果它们尝试执行一个非法的状态转换,代码将不会编译! diff --git a/src/static-guarantees/index.md b/src/static-guarantees/index.md index fc44d30f..cc12d035 100644 --- a/src/static-guarantees/index.md +++ b/src/static-guarantees/index.md @@ -1,12 +1,11 @@ -# Static Guarantees # 静态保障 -Rust的类型系统可防止编译时的数据竞争(看[`Send`]和[`Sync`]特性(traits))。类型系统也能被用来在编译时检查其它属性;减少某些案例中运行时检查的需要。 +Rust的类型系统可防止编译时的数据竞争(看[`Send`]和[`Sync`]特性(traits))。类型系统也能被用来在编译时检查其它属性;减少某些样例中运行时检查的需要。 [`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来初始化一个串行接口,这个API只能通过先配置接口需要的管脚才能被正确地使用。 +当应用到嵌入式程序时,这些*静态检查*能被用来,比如,强制I/O接口被恰当地配置。例如,可以设计一个API来初始化一个串行接口,这个API只能通过先配置接口需要的管脚才能被正确地使用。 也可以静态检查操作是否被执行在正确配置了的外设上,像是拉低一个管脚。例如尝试修改一个被配置成浮空输入模式的管脚的输出状态时,将会出发一个编译时错误。 diff --git a/src/static-guarantees/state-machines.md b/src/static-guarantees/state-machines.md index 63a3d250..4ddad371 100644 --- a/src/static-guarantees/state-machines.md +++ b/src/static-guarantees/state-machines.md @@ -34,7 +34,7 @@ ## 硬件表征(Hardware Representation) -通常,上面列的状态通过向指定的映射到一个GPIO外设的寄存器写入值来配置。让我们定义一个假想的GPIO配置寄存器来解释它: +通常,上面列的状态通过向指定的映射到一个GPIO外设的寄存器中写入值来配置。让我们定义一个假想的GPIO配置寄存器来解释下它: | 名字 | 位数(s) | 值 | 含义 | 注释 | | ---: | ------------: | ----: | ------: | ----: | @@ -90,8 +90,8 @@ impl GpioConfig { } ``` -然而,这将会允许我们修改某些没有意义的寄存器。比如,如果当我们的GPIO被配置为GPIO时我们设置`output_mode`字段,将会发生什么? +然而,这将会允许我们修改某些没有意义的寄存器。比如,如果当我们的GPIO被配置为输入时我们设置`output_mode`字段,将会发生什么? -通常,使用这个结构体将会允许我们访问我们上面的状态机没有定义的状态: e.g. 一个被上拉的输出,或者一个被拉高的输入。对于一些硬件,这并没有关系。在另外一些硬件,这将会导致不可预期或者没有定义的行为! +正常使用这个结构体将会允许我们访问我们上面的状态机没有定义的状态: e.g. 一个被上拉的输出,或者一个被拉高的输入。对于一些硬件,这并没有关系。对另外一些硬件来说,这将会导致不可预期或者没有定义的行为! -虽然这个接口很方便写入,但是它没有强制执行我们的硬件实现所规定的设计协约。 +虽然这个接口很方便写入,但是它没有强制我们遵守硬件的实现所规定的设计协约。 diff --git a/src/static-guarantees/typestate-programming.md b/src/static-guarantees/typestate-programming.md index 51748f86..161f7de3 100644 --- a/src/static-guarantees/typestate-programming.md +++ b/src/static-guarantees/typestate-programming.md @@ -1,6 +1,6 @@ # 类型状态编程(Typestate Programming) -[typestates]的概念描述了将有关对象当前状态的信息编码为该对象的类型中。虽然这听起来有点神秘,如果你在Rust中使用了[建造者模式],你就已经开始使用类型状态编程了! +[typestates]的概念描述了将有关对象当前状态的信息编码进该对象的类型中。虽然这听起来有点神秘,如果你在Rust中使用了[建造者模式],你就已经开始使用类型状态编程了! [typestates]: https://en.wikipedia.org/wiki/Typestate_analysis [建造者模式]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html @@ -49,18 +49,18 @@ fn main() { } ``` -在这个例子里,不能直接生成一个`Foo`对象。我们必须创造一个`FooBuilder`,在我们获取我们需要的`Foo`对象之前恰当地初始化它。 +在这个例子里,不能直接生成一个`Foo`对象。我们必须创造一个`FooBuilder`,在我们获取我们需要的`Foo`对象之前恰当地初始化`FooBuilder`。 这个最小的例子编码了两个状态: -* `FooBuilder`,其表征了一个"没有被配置",或者"正在配置"状态 -* `Foo`,其表征了一个"被配置",或者"可以使用"状态。 +* `FooBuilder`,其表示了一个"没有被配置",或者"正在配置"状态 +* `Foo`,其表示了一个"被配置",或者"可以使用"状态。 ## 强类型 -因为Rust有一个[强类型系统],没有一个简单的,魔法般地创造一个`Foo`实例或者不用调用`into_foo()`方法把一个`FooBuilder`变成一个`Foo`的方法。另外,调用`into_foo()`方法消费了最初的`FooBuilder`结构体,意味着不创造一个新的实例它就不能被再次使用。 +因为Rust有一个[强类型系统],没有方法简单的,魔法般地创造一个`Foo`实例或者不用调用`into_foo()`方法把一个`FooBuilder`变成一个`Foo`。另外,调用`into_foo()`方法消费了最初的`FooBuilder`结构体,意味着不创造一个新的实例它就不能被再次使用。 [强类型系统]: https://en.wikipedia.org/wiki/Strong_and_weak_typing -这允许我们去将我们系统的状态表示成类型,把状态转换必须的动作包括进交换两个类型的方法中。通过创造一个 `FooBuilder`,与一个 `Foo` 对象交换,我们已经使用了一个基本状态机。 +这允许我们去将我们系统的状态表示成类型,把状态转换必须的动作包括进交换两个类型的方法中。通过创造一个 `FooBuilder`,与一个 `Foo` 对象交换,我们已经使用了一个基本的状态机。 diff --git a/src/static-guarantees/zero-cost-abstractions.md b/src/static-guarantees/zero-cost-abstractions.md index 7be659c9..44252bd1 100644 --- a/src/static-guarantees/zero-cost-abstractions.md +++ b/src/static-guarantees/zero-cost-abstractions.md @@ -1,6 +1,6 @@ # 零成本抽象 -类型状态是一个零成本抽象的杰出案例 - 把某些行为移到编译时执行或者分析的能力。这些类型状态不包含真实的数据,只用来作为标记。因为它们不包含数据,在运行时它们在内存中不存在实际的表征。 +类型状态是一个零成本抽象的杰出案例 - 把某些行为移到编译时执行或者分析的能力。这些类型状态不包含真实的数据,只用来作为标记。因为它们不包含数据,在运行时它们在内存中不存在实际的表示。 ```rust,ignore use core::mem::size_of; @@ -33,10 +33,10 @@ pub fn into_input_high_z(self) -> GpioConfig { } ``` -我们返回的CpioConfig在运行时并不存在。调用这个函数通常将会总结为一个单一的汇编指令 - 保存一个常量寄存器值进一个寄存器里。这意味着我们开发的类型状态接口是一个零成本抽象 - 它不会用更多的CPU,RAM,或者代码空间去跟踪`GpioConfig`的状态,会渲染成和直接访问寄存器一样的机器码。 +我们返回的GpioConfig在运行时并不存在。调用这个函数通常将会被总结为一个单一的汇编指令 - 保存一个常量寄存器值进一个寄存器里。这意味着我们开发的类型状态接口是一个零成本抽象 - 它不会用更多的CPU,RAM,或者代码空间去跟踪`GpioConfig`的状态,会渲染成和直接访问寄存器一样的机器码。 ## 嵌套 通常,你可能会把这些抽象深深地嵌套起来。一旦所有的被使用的组件是零大小类型,整个结构体将不会在运行时存在。 -对于复杂或者深度嵌套的结构体,定义所有可能的状态组合可能很乏味。在这些案例中,宏大概能被用来产生所有的实现。 +对于复杂或者深度嵌套的结构体,定义所有可能的状态组合可能很乏味。在这些案例中,宏可能能被用来产生所有的实现。 From 4868bfea49cccd7ddc669641f0b95810d0c2983c Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 20:56:24 +0800 Subject: [PATCH 100/137] minor --- src/portability/index.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/portability/index.md b/src/portability/index.md index 56704589..7df5462f 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,11 +1,11 @@ # 可移植性 -在嵌入式环境中,可移植性是一个非常重要的主题: 来自单个厂家的每个供应链,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式将会不一样。 +在嵌入式环境中,可移植性是一个非常重要的主题: 来自单个厂家的每个供应链,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 通过一个被叫做硬件抽象层或者**HAL**的层去均等化这种差异是一种常见的方法。 -> 在软件中硬件抽象是一组函数,模仿了一些平台特定的细节,让程序可以直接访问硬件资源。 -> 它们经常通过提供标准的对硬件的操作系统(OS)调用来让程序员可以编写独立于设备的,高性能应用。 +> 在软件中硬件抽象是一组函数,其模仿了一些平台特定的细节,让程序可以直接访问硬件资源。 +> 它们经常通过提供对硬件的操作系统(OS)的标准调用来让程序员可以编写独立于设备的高性能应用。 > > *Wikipedia: [Hardware Abstraction Layer]* @@ -13,11 +13,11 @@ 在这方面嵌入式系统有点特别,因为我们通常没有操作系统和用户可安装的软件,而是只有固件镜像,其作为一个整体被编译且伴着许多约束。因此虽然维基百科定义的传统方法可能有效,但是它不是确保可移植性最有效的方法。 -在Rust中我们要怎么实现这个目标?让我们进入**embedded-hal**... +在Rust中我们要怎么实现这个目标呢?让我们进入**embedded-hal**... ## 什么是embedded-hal? -简而言之,它是一组特性(traits),其定义了**HAL实现**,**驱动**,**应用(或者固件)** 之间的实现约定。那些约定包括功能(即约定,如果某个类型实现了某个特性,其**HAL实现**就提供了某个功能)和方法(即,如果你构建了一个实现了某个trait的类型,约定保障你肯定有在trait中指定的方法)。 +简而言之,它是一组traits,其定义了**HAL实现**,**驱动**,**应用(或者固件)** 之间的实现约定。那些约定包括功能(即约定,如果某个类型实现了某个特性,其**HAL实现**就提供了某个功能)和方法(即,如果你构建了一个实现了某个trait的类型,约定保障你肯定有在trait中指定的方法)。 典型的分层可能如下所示: @@ -32,17 +32,16 @@ * Timers/Countdowns * Analog Digital Conversion -使用**embedded-hal**traits和实现且使用了它们的crates的主要理由是为了控制复杂性。如果你发现一个应用可能必须要去实现硬件中的外设的接口和潜在的要被其他硬件组件使用的驱动,那么其应该很容易被看作是可复用性有限。用数学语言来说就是,如果**M**是外设HAL实现的数量,**N**是驱动的数量,那么如果我们要为每个应用重新发明轮子我们最终会有**M*N**个实现,然而通过使用**embedded-hal**的traits提供的 *API* 将会使实现复杂性变成**M+N** 。当然还有其它好处,比如由于API定义良好,开箱即用,导致的试错减少。 +使用**embedded-hal** traits和使用那些实现且使用了它们的crates的主要理由是为了控制复杂性。如果你发现一个应用可能必须要去实现硬件中的外设的接口和潜在的要被其他硬件组件使用的驱动,那么其应该很容易被看作是可复用性有限的。用数学语言来说就是,如果**M**是外设HAL实现的数量,**N**是驱动的数量,那么如果我们要为每个应用重新发明轮子我们最终会有**M*N**个实现,然而通过使用**embedded-hal**的traits提供的 *API* 将会使实现复杂性变成**M+N** 。当然还有其它好处,比如由于API定义良好,开箱即用,导致试错减少。 ## embedded-hal的用户 像上面说的,HAL有三个主要用户: -As said above there are three main users of the HAL: ### HAL实现 -一个HAL实现提供了硬件和HAL traits的用户之间的接口。典型的实现由三部分组成: +一个HAL实现为硬件和HAL traits的用户之间提供了接口。典型的实现由三部分组成: * 一个或者多个特定于硬件的类型 * 生成和初始化这个类型的函数,经常伴随着不同的配置选项(速度,操作模式,使用的管脚,etc 。) @@ -51,7 +50,7 @@ As said above there are three main users of the HAL: 这样的一个 **HAL实现** 可以有各种风格: * 通过低级硬件访问,e.g. 通过寄存器。 * 通过操作系统,e.g. 通过使用Linux下的 `sysfs` -* 通过适配器,e.g. 单元测试相关的类型的一个模仿 +* 通过适配器,e.g. 单元测试有关的类型的一个模仿 * 通过硬件适配器的驱动,e.g. I2C多路复用器或者GPIO扩展器(I2C multiplexer or GPIO expander) ### 驱动 @@ -62,4 +61,4 @@ As said above there are three main users of the HAL: ### 应用 -应用把多个部分结合在一起并确保需要的功能被实现。当在不同的系统间移植时,这部分的适配是花费最多精力的地方,因为应用需要通过HAL实现正确地初始化真实的硬件且不同硬件的初始化也不相同,甚至有时候差别非常大。用户的选择也在其中扮演了非常重大的角色,因为组件能被物理连接到不同的端口,硬件总线有时候需要外部硬件去配合,或者在内部外设的使用上有不同的考量(e.g. 多个计时器被赋予了不同的功能或者外设与其它外设冲突) +应用把多个部分结合在一起并确保需要的功能被实现。当在不同的系统间移植时,这部分的适配是花费最多精力的地方,因为应用需要通过HAL实现正确地初始化真实的硬件,而且不同硬件的初始化也不相同,甚至有时候差别非常大。用户的选择也在其中扮演了非常重大的角色,因为组件能被物理连接到不同的端口,硬件总线有时候需要外部硬件去配合,或者在内部外设的使用上有不同的考量(e.g. 可使用多个被赋予了不同的功能的计时器或者某个外设与其它外设冲突) From 84799ad1db5739050600f5795a4bca559762201f Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 22 Jun 2022 22:21:13 +0800 Subject: [PATCH 101/137] minor --- src/c-tips/index.md | 10 ++-- src/collections/index.md | 6 +-- src/concurrency/index.md | 52 ++++++++++----------- src/design-patterns/hal/checklist.md | 4 +- src/design-patterns/hal/gpio.md | 2 +- src/design-patterns/hal/interoperability.md | 6 +-- src/design-patterns/hal/naming.md | 2 +- src/interoperability/c-with-rust.md | 8 ++-- src/interoperability/index.md | 4 +- src/interoperability/rust-with-c.md | 2 +- 10 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index 9c50681e..e209a7bc 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -8,7 +8,7 @@ * 使用`#ifdef`编译时选择代码块 * 编译时的数组大小和计算 -* 用来简化常见模式的宏(避免函数调用的开销) +* 用来简化常见的模式的宏(避免函数调用的开销) 在Rust中没有预处理器,所以许多用例有不同的处理方法。本章节剩下的部分,我们将介绍使用预处理器的各种替代方法。 @@ -18,7 +18,7 @@ Rust中最接近`#ifdef ... #endif`的是[Cargo features]。这些比C预处理 [Cargo features]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section -比如,你可能有一个crate,其提供一个信号处理的原语库(library of signal processing primitives)。每个原语可能带来一些额外的时间去编译大量的常量,你想要躲开这些常量。你可以为你的`Cargo.toml`中每个组件声明一个Cargo feature。 +比如,你可能有一个crate,其提供一个信号处理的基本类型库(library of signal processing primitives)。每个基本类型可能带来一些额外的时间去编译大量的常量,你想要躲开这些常量。你可以为你的`Cargo.toml`中每个组件声明一个Cargo feature。 ```toml [features] @@ -43,7 +43,7 @@ pub mod iir; [conditional compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html -条件编译将只应用于下一条语句或者块。如果一个块不能在现在的作用域中被使用,那么`cfg`属性将需要被多次使用。值得注意的是大多数时间,仅是包含所有的代码,让编译器在优化时去删除死代码(dead code)更好,通常,在移除不使用的代码方面的工作,编译器做得很好。 +条件编译将只应用于下一条语句或者块。如果一个块不能在现在的作用域中被使用,那么`cfg`属性将需要被多次使用。值得注意的是大多数时间,仅是包含所有的代码而让编译器在优化时去删除死代码(dead code)更好,通常,在移除不使用的代码方面的工作,编译器做得很好。 ### 编译时大小和计算 @@ -143,7 +143,7 @@ for element in arr.iter() { ## Volatile访问 -在C中,某个变量可能被标记成`volatile`,向编译器指出,变量中的值在访问间可能改变。Volatile变量通常用于一个与存储映射寄存器有关的嵌入式上下文中。 +在C中,某个变量可能被标记成`volatile`,向编译器指出,变量中的值在访问间可能改变。Volatile变量通常用于一个与存储映射的寄存器有关的嵌入式上下文中。 在Rsut中,并不使用`volatile`标记变量,我们使用特定的方法去执行volatile访问: [`core::ptr::read_volatile`] 和 [`core::ptr::write_volatile`]。这些方法使用一个 `*const T` 或者一个 `*mut T` (上面说的 _裸指针_ ),执行一个volatile读取或者写入。 @@ -301,7 +301,7 @@ fn main() { ## 其它资源 -* 在这本书中: +* 这本书中: * [给Rust配点C](../interoperability/c-with-rust.md) * [给C配点Rust](../interoperability/rust-with-c.md) * [The Rust Embedded FAQs](https://docs.rust-embedded.org/faq.html) diff --git a/src/collections/index.md b/src/collections/index.md index 5516b092..032e75c6 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -1,6 +1,6 @@ # 集合 -最终,你将希望在你的程序里使用动态数据结构(也称为集合)。`std` 提供了一组常见的集合: [`Vec`],[`String`],[`HashMap`],等等。所有这些在`std`中被实现的集合都使用一个全局动态分配器(也称为堆)。 +最后,你还希望在你的程序里使用动态数据结构(也称为集合)。`std` 提供了一组常见的集合: [`Vec`],[`String`],[`HashMap`],等等。所有这些在`std`中被实现的集合都使用一个全局动态分配器(也称为堆)。 [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html [`String`]: https://doc.rust-lang.org/std/string/struct.String.html @@ -145,7 +145,7 @@ fn main() -> ! { [`typenum`]: https://crates.io/crates/typenum -第二,`push`方法和许多其它方法返回的是一个`Result`。因为`heapless`集合有一个固定的容量,所以所有插入的操作可能会失败。通过返回一个`Result`,API反应了这个问题,指出操作是否成功还是失败。相反,`alloc`集合自己将会在堆上重新分配去增加它的容量。 +第二,`push`方法和许多其它方法返回的是一个`Result`。因为`heapless`集合有一个固定的容量,所以所有插入的操作都可能会失败。通过返回一个`Result`,API反应了这个问题,指出操作是否成功还是失败。相反,`alloc`集合自己将会在堆上重新分配去增加它的容量。 自v0.4.x版本起,所有的`heapless`集合将它们所有的元素内联地存储起来了。这意味着像是`let x = heapless::Vec::new()`这样的一个操作将会在栈上分配集合,但是它也能够在一个`static`变量上分配集合,或者甚至在堆上(`Box>`)。 @@ -165,7 +165,7 @@ fn main() -> ! { 推理堆分配集合的内存使用是很难的因为长期使用的集合的大小会在运行时改变。一些操作可能隐式地重分配集合,增加了它的内存使用,一些集合暴露的方法,像是`shrink_to_fit`,会潜在地减少集合使用的内存 -- 最终,它由分配器去决定是否确定减小内存的分配或者不。另外,分配器可能不得不处理内存碎片,它会*明显*增加内存的使用。 -另一方面,如果你只使用固定容量的集合,请把大多数的数据保存在`static`变量中,并为调用栈设置一个最大尺寸,随后如果你尝试使用大于可用的物理内存的内存大小连接器会发现它。 +另一方面,如果你只使用固定容量的集合,请把大多数的数据保存在`static`变量中,并为调用栈设置一个最大尺寸,随后如果你尝试使用大于可用的物理内存的内存大小时,链接器会发现它。 另外,在栈上分配的固定容量的集合可以通过[`-Z emit-stack-sizes`]标识来报告,其意味着用来分析栈使用的工具(像是[`stack-sizes`])将会把在栈上分配的集合包含进它们的分析中。 diff --git a/src/concurrency/index.md b/src/concurrency/index.md index d7a884f2..30348c5b 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -1,8 +1,8 @@ # 并发 -无论何时你程序的不同部分可能会在不同时刻执行或者不按顺序执行,那并发就发生了。在一个嵌入式环境中,这包括: +无论何时你程序有可能会在不同时刻执行或者不按顺序执行不同的部分,那并发就出现了。在一个嵌入式环境中,这包括: -* 中断处理函数,无论何时相关的中断发生时,其会运行, +* 无论何时相关的中断发生时,中断处理函数会运行, * 不同的多线程形式,在这块,你的微处理器通常会在你的程序的不同部分间进行切换, * 在一些多核微处理器系统中,每个核可以同时独立地运行你的程序的不同部分。 @@ -28,9 +28,9 @@ fn main() { ## 全局可变数据 -不像非嵌入式Rust,我们通常不能分配堆和将对那个数据的引用传递进一个新创造的线程。反而,我们的中断处理函数可能在任何时间被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _static allocated_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 +不像非嵌入式Rust,我们通常不能分配堆和将对那个数据的引用传递进一个新创造的线程。反而,我们的中断处理函数可能在任何时间被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _静态分配的_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 -在Rust中,[`static mut`]这样的变量读取或者写入总是不安全的,因为不特别关注它们的话,你可能会触发一个竞态条件,你对变量的访问在中途就被一个也访问那个变量的中断打断。 +在Rust中,[`static mut`]这样的变量读取或者写入总是unsafe的,因为不特别关注它们的话,你可能会触发一个竞态条件,你对变量的访问在中途就被一个也访问那个变量的中断打断了。 [`static mut`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable @@ -59,7 +59,7 @@ fn timer() { } ``` -每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当它看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在读取之后但是写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那段时间,一些变化我们会计算两次。 +每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当它看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在读取之后但是在写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那段时间,某些变化我们会进行两次计算。 ## 临界区(Critical Sections) @@ -99,13 +99,13 @@ fn timer() { 如果`COUNTER`被多个可能相互 _抢占_ 的中断处理函数共享,那么每一个也需要一个临界区。 -这解决了我们的眼前问题,但是我们仍然要编写许多不安全的代码,我们需要仔细推敲这些代码,有些我们可能不需要使用临界区。因为每个临界区暂时地暂停了中断处理,就会出现一些消耗,其与一些额外的代码大小和更高的中断延迟和抖动有关(中断可能花费很长时间去处理,等待被处理的时间变化非常大)。这是否是个问题取决于你的系统,但是通常,我们想要避免它。 +这解决了我们的眼前问题,但是我们仍然要编写许多unsafe的代码,我们需要仔细推敲这些代码,有些我们可能不需要使用临界区。因为每个临界区暂时地暂停了中断处理,就会出现一些消耗,其与某些代码尺寸,更高的中断延迟和抖动有关(中断可能花费很长时间去处理,等待被处理的时间变化非常大)。这是否是个问题取决于你的系统,但是通常,我们想要避免它。 -值得注意的是,虽然一个临界区保障了没有中断将会发生,但是它在多核系统上不提供一个排他性保证(exclusivity guarantee)!其它核可能很开心访问与你的核一样的内存区域,设置不用中断。如果你正在使用多核,你将需要更强的同步原语(synchronisation primitives)。 +值得注意的是,虽然一个临界区保障了没有中断将会发生,但是它在多核系统上不提供一个排他性保证(exclusivity guarantee)!其它核可能很开心访问与你的核一样的内存区域,即使没有中断。如果你正在使用多核,你将需要更强的同步原语(synchronisation primitives)。 ## 原子访问 -在一些平台上,可以使用特定的原子指令,它保障了读取-修改-写回操作。针对Cortex-M: `thumbv6`(Cortex-M0,Cortex-M0+)只提供原子读取和存取指令,而`thumv7`(Cortex-M3和其上)提供完全的比较和交换(CAS)指令。这些CAS指令提供了一种对所有中断禁用的替代方法: 我们可以尝试执行增加操作,它在大多数情况下都会成功,但是如果它被中断了它将会自动重试完整的增加操作。这些原子操作甚至在多核间也是安全的。 +在一些平台上,可以使用特定的原子指令,它保障了读取-修改-写回操作。针对Cortex-M: `thumbv6`(Cortex-M0,Cortex-M0+)只提供原子读取和存取指令,而`thumv7`(Cortex-M3和其上)提供完全的比较和交换(CAS)指令。这些CAS指令提供了一种替代方法,替代禁用所有中断: 我们可以尝试执行增加操作,它在大多数情况下都会成功,但是如果它被中断了它将会自动重试完整的增加操作。这些原子操作甚至在多核间也是安全的。 ```rust,ignore use core::sync::atomic::{AtomicUsize, Ordering}; @@ -133,9 +133,9 @@ fn timer() { } ``` -这时,`COUNTER`是一个安全的`static`变量。多亏了`AtomicUsize`类型,不需要禁用中断,`COUNTER`能从中断处理函数和main线程被安全地修改。当可以这么做时,这是一个更好的解决方案 - 然而你的平台上可能不支持。 +这时,`COUNTER`是一个safe的`static`变量。多亏了`AtomicUsize`类型,不需要禁用中断,`COUNTER`能从中断处理函数和main线程被安全地修改。当可以这么做时,这是一个更好的解决方案 - 然而你的平台上可能不支持。 -一个关于[`Ordering`]的提示: 这影响编译器和硬件可能如何重新排序指令,也会影响缓存可见性。假设目标是个单核平台,在这个案例里`Relaxed`是充足和最有效的选择。更严格的排序将导致编译器在原子操作周围产生内存屏障(Memory Barriers);取决于你做什么原子操作,你可能需要或者不需要这个!原子模型的精确细节是复杂的,最好写在其它地方。 +一个关于[`Ordering`]的提醒: 它可能影响编译器和硬件如何重新排序指令,也会影响缓存可见性。假设目标是个单核平台,在这个案例里`Relaxed`是充足和最有效的选择。更严格的排序将导致编译器在原子操作周围产生内存屏障(Memory Barriers);取决于你做什么原子操作,你可能需要或者不需要这个排序!原子模型的精确细节是复杂的,细节最好写在其它地方。 关于原子操作和排序的更多细节,可以看这里[nomicon]。 @@ -145,9 +145,9 @@ fn timer() { ## 抽象,Send和Sync -上面的解决方案都不是特别令人满意。它们需要`unsafe`块,`unsafe`块必须要被十分小心地检查且符合人体工程学。确实,我们在Rust中可以做得更好! +上面的解决方案都不是特别令人满意。它们需要`unsafe`块,`unsafe`块必须要被十分小心地检查且要符合人体工程学。确实,我们在Rust中可以做得更好! -我们可以把我们的计数器抽象进一个安全的接口,其可以在我们代码的其它地方被安全地使用。对于这个例子,我们将使用临界区的(cirtical-section)计数器,但是你可以用原子操作做一些非常类似的事情。 +我们可以把我们的计数器抽象进一个安全的接口,其可以在我们代码的其它地方被安全地使用。在这个例子里,我们将使用临界区的(cirtical-section)计数器,但是你可以用原子操作做一些非常类似的事情。 ```rust,ignore use core::cell::UnsafeCell; @@ -200,16 +200,16 @@ fn timer() { interrupt::free(|cs| COUNTER.reset(cs)); // 如果我们真的需要,我们可以使用unsafe代码去生成一个假CriticalSection, - // 避免消耗: + // 避免开销: // let cs = unsafe { interrupt::CriticalSection::new() }; } ``` 我们已经把我们的`unsafe`代码移进了精心安排的抽象中,现在我们的应用代码不包含任何`unsafe`块。 -这个设计要求应用传递一个`CriticalSection` token进来: 这些tokens仅由`interrupt::free`安全地产生,因此通过要求传递进一个`CriticalSection` token,我们确保我们正在一个临界区中操作,不用自己动手锁起来。这个保障由编译器静态地提供: 这将不会有任何与`cs`有关的运行时消耗。如果我们由多个计数器,它们都可以被指定同一个`cs`,而不用要求多个嵌套的临界区。 +这个设计要求应用传递一个`CriticalSection` token进来: 这些tokens仅由`interrupt::free`安全地产生,因此通过要求传递进一个`CriticalSection` token,我们确保我们正在一个临界区中操作,不用自己动手锁起来。这个保障由编译器静态地提供: 这将不会带来任何与`cs`有关的运行时消耗。如果我们有多个计数器,它们都可以被指定同一个`cs`,而不用要求多个嵌套的临界区。 -这也带来了Rust中关于并发的一个重要主题: [`Send` and `Sync`] traits。总结一下Rust book,当一个类型能够安全地被移动到另一个线程,它是Send,当一个类型能被安全地在多个线程间共享的时候,它是Sync。在一个嵌入式上下文中,我们认为中断是在应用代码的一个独立线程中执行的,因此在一个中断和main代码中都被访问的变量必须是Sync。 +这也带来了Rust中关于并发的一个重要主题: [`Send` and `Sync`] traits。总结一下Rust book,当一个类型能够安全地被移动到另一个线程,它是Send,当一个类型能被安全地在多个线程间共享的时候,它是Sync。在一个嵌入式上下文中,我们认为中断是在应用代码的一个独立线程中执行的,因此在一个中断和main代码中都能被访问的变量必须是Sync。 [`Send` and `Sync`]: https://doc.rust-lang.org/nomicon/send-and-sync.html @@ -221,9 +221,9 @@ fn timer() { ## 互斥量(Mutexs) -我们已经为我们的计数器问题创造了一个有用的抽象,但是关于并发这里存在许多通用的抽象。 +我们已经为我们的计数器问题创造了一个有用的抽象,但是关于并发这里还存在许多通用的抽象。 -一个这样的 _同步原语_ 是一个互斥量(mutex),互斥(mutual exclusion)的缩写。这些构造确保了对一个变量的排他访问,比如我们的计数器。一个线程尝试 _lock_ (或者 _acquire_) 互斥量,或者当互斥量不能被锁住时返回一个错误。当线程持有锁时,它有权访问被保护的数据,当线程工作完成了,它 _unlocks_ (或者 _releases_) 互斥量,允许其它线程锁住它。在Rust中,我们通常使用[`Drop`] trait实现unlock去确保当互斥量超出作用域时它总是被释放。 +一个互斥量(mutex),互斥(mutual exclusion)的缩写,就是这样的一个 _同步原语_ 。这些构造确保了对一个变量的排他访问,比如我们的计数器。一个线程会尝试 _lock_ (或者 _acquire_) 互斥量,或者当互斥量不能被锁住时返回一个错误。当线程持有锁时,它有权访问被保护的数据,当线程工作完成了,它 _unlocks_ (或者 _releases_) 互斥量,允许其它线程锁住它。在Rust中,我们通常使用[`Drop`] trait实现unlock去确保当互斥量超出作用域时它总是被释放。 [`Drop`]: https://doc.rust-lang.org/core/ops/trait.Drop.html @@ -260,19 +260,19 @@ fn timer() { } ``` -我们现在使用了[`Cell`],它与它的兄弟`RefCell`一起被用于提供安全的内部可变性。我们已经见过`UnsafeCell`了,在Rust中它是内部可变性的底层: 它允许你去获得多个对值的可变引用,但是只能与不安全的代码一起工作。一个`Cell`像一个`UnsafeCell`一样但是它提供了一个安全的接口: 它只允许拷贝现在的值或者替换它,不允许获取一个引用,因此它不是Sync,它不能被在线程间共享。这些限制意味着它用起来是安全的,但是我们不能直接将它用于`static`变量因为一个`static`必须是Sync。 +我们现在使用了[`Cell`],它与它的兄弟`RefCell`一起被用于提供safe的内部可变性。我们已经见过`UnsafeCell`了,在Rust中它是内部可变性的底层: 它允许你去获得对某个值的多个可变引用,但是只能与不安全的代码一起工作。一个`Cell`像一个`UnsafeCell`一样但是它提供了一个安全的接口: 它只允许拷贝现在的值或者替换它,不允许获取一个引用,因此它不是Sync,它不能被在线程间共享。这些限制意味着它用起来是safe的,但是我们不能直接将它用于`static`变量因为一个`static`必须是Sync。 [`Cell`]: https://doc.rust-lang.org/core/cell/struct.Cell.html -因此为什么上面的例子可以工作?`Mutex`对于任何是Send的`T`实现了Sync - 比如一个`Cell`。因为它只能在临界区对它的内容进行访问,所以它这么做是安全的。因此我们可以即使没有一点unsafe代码我们也能获取一个safe的计数器! +因此为什么上面的例子可以工作?`Mutex`对于任何是Send的`T`实现了Sync - 比如一个`Cell`。因为它只能在临界区对它的内容进行访问,所以它这么做是safe的。因此我们可以即使没有一点unsafe的代码我们也能获取一个safe的计数器! -对于我们的简单类型,像是我们的计数器的`u32`,来说是很棒的,但是对于更复杂的不能拷贝的类型呢?在一个嵌入式上下文中一个极度常见的例子是一个外设结构体,通常它们不是Copy。针对那种情况,我们可以使用`RefCell`。 +对于我们的简单类型,像是我们的计数器的`u32`来说是很棒的,但是对于更复杂的不能拷贝的类型呢?在一个嵌入式上下文中一个极度常见的例子是一个外设结构体,通常它们不是Copy。针对那种情况,我们可以使用`RefCell`。 ## 共享外设 使用`svd2rust`生成的设备crates和相似的抽象,通过强制要求同时只能存在一个外设结构体的实例,提供了对外设的安全的访问。这个确保了安全性,但是使得它很难从main线程和一个中断处理函数一起访问一个外设。 -为了安全地共享对外设的访问,我们能使用我们之前看到的`Mutex`。我们也将需要使用[`RefCell`],它使用一个运行时检查去确保对一个外设m每次只有一个引用被给出。这个比纯`Cell`消耗更多,但是因为我们正给出引用而不是拷贝,我们必须确保每次只有一个引用存在。 +为了安全地共享对外设的访问,我们能使用我们之前看到的`Mutex`。我们也将需要使用[`RefCell`],它使用一个运行时检查去确保对一个外设每次只有一个引用被给出。这个比纯`Cell`消耗更多,但是因为我们正给出引用而不是拷贝,我们必须确保每次只有一个引用存在。 [`RefCell`]: https://doc.rust-lang.org/core/cell/struct.RefCell.html @@ -305,7 +305,7 @@ fn main() -> ! { set_timer_1hz(); let mut last_state = false; loop { - // 我们现在将通过互斥量,读取作为数字输入时的状态。 + // 我们现在将通过互斥量,读取其作为数字输入时的状态。 let state = interrupt::free(|cs| { let gpioa = MY_GPIO.borrow(cs).borrow(); gpioa.as_ref().unwrap().idr.read().idr0().bit_is_set() @@ -341,7 +341,7 @@ static MY_GPIO: Mutex>> = Mutex::new(RefCell::new(None)); ``` -我们的共享变量现在是一个包围了一个`RefCell`的`Mutex`,`RefCell`包含一个`Option`。`Mutex`确保只在一个临界区的时候可以访问,因此使变量变成Sync,设置即使一个纯`RefCell`不是Sync。`RefCell`赋予了我们引用的内部可变性,我们将需要使用我们的`GPIOA`。`Option`让我们初始化这个变量成空的东西,只在随后实际移动变量进来。我们不能静态地访问外设单例,只有在运行时,因此这是需要的。 +我们的共享变量现在是一个包围了一个`RefCell`的`Mutex`,`RefCell`包含一个`Option`。`Mutex`确保只在一个临界区中的时候可以访问,因此使变量变成了Sync,甚至即使一个纯`RefCell`不是Sync。`RefCell`赋予了我们引用的内部可变性,我们将需要使用我们的`GPIOA`。`Option`让我们可以初始化这个变量成空的东西,只在随后实际移动变量进来。只有在运行时,我们才能静态地访问外设单例,因此这是必须的。 ```rust,ignore interrupt::free(|cs| MY_GPIO.borrow(cs).replace(Some(dp.GPIOA))); @@ -356,7 +356,7 @@ interrupt::free(|cs| { }); ``` -最终,我们用一种安全和并发的方式使用`MY_GPIO`。临界区禁止了中断像往常一样发生,让我们借用互斥量。`RefCell`然后给了我们一个`&Option`,追踪它还要借用多久 - 一旦引用超出作用域,`RefCell`将会被更新去指出引用不再被借用。 +最终,我们用一种安全和并发的方式使用`MY_GPIO`。临界区禁止了中断像往常一样发生,让我们借用互斥量。`RefCell`然后给了我们一个`&Option`并追踪它还要借用多久 - 一旦引用超出作用域,`RefCell`将会被更新去指出引用不再被借用。 因为我不能把`GPIOA`移出`&Option`,我们需要用`as_ref()`将它转换成一个`&Option<&GPIOA>`,最终我们能使用`unwrap()`获得`&GPIOA`,其让我们可以修改外设。 @@ -405,7 +405,7 @@ fn timer() { ## RTIC -另一个方法是使用[RTIC框架],Real Time Interrupt-driven Concurrency的缩写。它强制执行静态优先级并追踪对`static mut`变量("资源")的访问去确保共享资源总是能被安全地访问,而不需要总是进入临界区且使用引用计数带来的消耗(如`RefCell`中所示)。这有许多好处,比如保证没有死锁且时间和内存的消耗极度低。 +另一个方法是使用[RTIC框架],Real Time Interrupt-driven Concurrency的缩写。它强制执行静态优先级并追踪对`static mut`变量("资源")的访问去确保共享资源总是能被安全地访问,而不需要总是进入临界区和使用引用计数带来的消耗(如`RefCell`中所示)。这有许多好处,比如保证没有死锁且时间和内存的消耗极度低。 [RTIC框架]: /~https://github.com/rtic-rs/cortex-m-rtic @@ -415,7 +415,7 @@ fn timer() { ## 实时操作系统 -与嵌入式并发有关的另一个模型是实时操作系统(RTOS)。虽然现在在Rust中的研究较少,但是它们被广泛用于传统的嵌入式开发。开源的例子包括[FreeRTOS]和[ChibiOS]。这些RTOSs提供对运行多个应用线程的支持,CPU在这些线程间进行切换,切换要么发生在当线程让出控制权的时候(被称为非抢占式多任务),要么是基于一个常规计时器或者中断(抢占式多任务)。RTOS通常提供互斥量或者其它的同步原语,经常与硬件特性互相操作,比如DMA引擎。 +与嵌入式并发有关的另一个模型是实时操作系统(RTOS)。虽然现在在Rust中的研究较少,但是它们被广泛用于传统的嵌入式开发。开源的例子包括[FreeRTOS]和[ChibiOS](译者注: 目前有个纯Rust实现的[Tock](https://www.tockos.org/))。这些RTOSs提供对运行多个应用线程的支持,CPU在这些线程间进行切换,切换要么发生在当线程让出控制权的时候(被称为非抢占式多任务),要么是基于一个常规计时器或者中断(抢占式多任务)。RTOS通常提供互斥量或者其它的同步原语,经常与硬件功能相互使用,比如DMA引擎。 [FreeRTOS]: https://freertos.org/ [ChibiOS]: http://chibios.org/ diff --git a/src/design-patterns/hal/checklist.md b/src/design-patterns/hal/checklist.md index 966dd99e..316d9ac9 100644 --- a/src/design-patterns/hal/checklist.md +++ b/src/design-patterns/hal/checklist.md @@ -3,11 +3,11 @@ - **命名** *(crate符合Rust命名规则)* - [ ] crate被恰当地命名 ([C-CRATE-NAME]) - **互用性** *(crate很好地与其它的库功能交互)* - - [ ] 封装类型提供一种解构方法 ([C-FREE]) + - [ ] 封装类型提供一种析构方法 ([C-FREE]) - [ ] HALs重新导出了它们的寄存器访问crate ([C-REEXPORT-PAC]) - [ ] 类型实现了 `embedded-hal` traits ([C-HAL-TRAITS]) - **可预见性** *(crate使清晰的代码工作起来像它们看起来一样)* - - [ ] 使用构造函数而不是扩展traies ([C-CTOR]) + - [ ] 使用构造函数而不是扩展traits ([C-CTOR]) - **GPIO接口** *(GPIO接口遵循一个常见的模式)* - [ ] Pin类型默认是零大小类型 ([C-ZST-PIN]) - [ ] Pin类型提供擦除管脚和端口的方法 ([C-ERASED-PIN]) diff --git a/src/design-patterns/hal/gpio.md b/src/design-patterns/hal/gpio.md index c4897b2f..83c3fb50 100644 --- a/src/design-patterns/hal/gpio.md +++ b/src/design-patterns/hal/gpio.md @@ -83,7 +83,7 @@ enum Port { ## 管脚状态应该被编码成类型参数 (C-PIN-STATE) -取决于芯片或者芯片家族,管家可能被配置为具有不同特性的输出或者输入。这个状态应该在类型系统中被编码去避免在错误的状态中使用管脚。 +取决于芯片或者芯片系列,管脚可能被配置为具有不同特性的输出或者输入。这个状态应该在类型系统中被编码去避免在错误的状态中使用管脚。 另外,芯片特定的状态(eg. 驱动强度)可能也用这个办法被编码,使用额外的类型参数。 diff --git a/src/design-patterns/hal/interoperability.md b/src/design-patterns/hal/interoperability.md index 87cad5e9..320b6f83 100644 --- a/src/design-patterns/hal/interoperability.md +++ b/src/design-patterns/hal/interoperability.md @@ -8,7 +8,7 @@ 如果有必要方法应该关闭和重置外设。使用由`free`返回的原始外设调用`new`不应该由于设备的意外状态而失败, -如果HAL类型要求构造其它的非`Copy`对象(比如 I/O 管脚),任何这样的对象应该也由`free`返回和释放。`free`应该返回一个元组。 +如果HAL类型要求构造其它的非`Copy`对象(比如 I/O 管脚),任何这样的对象应该也由`free`返回和释放。在那个案例中`free`应该返回一个元组。 比如: @@ -28,9 +28,9 @@ impl Timer { ``` -## HALs重新导出它们的寄存器访问cra(C-REEXPORT-PAC) +## HALs重新导出它们的寄存器访问crate(C-REEXPORT-PAC) -HALs能被编写在[svd2rust]生成的PACs之上,或在其它提供纯寄存器访问的crates之上。HALs应该总是能在它们的crate root中重新导出它们所基于的寄存器访问crate +HALs能被编写在[svd2rust]生成的PACs之上,或在其它纯寄存器访问的crates之上。HALs应该总是能在它们的crate root中重新导出它们所基于的寄存器访问crate 一个PAC应该被重新导出在名字`pac`下,无论这个crate实际的名字是什么,因为HAL的名字应该已经明确了正被访问的是什么PAC 。 diff --git a/src/design-patterns/hal/naming.md b/src/design-patterns/hal/naming.md index 6057cbef..28b20aa7 100644 --- a/src/design-patterns/hal/naming.md +++ b/src/design-patterns/hal/naming.md @@ -4,4 +4,4 @@ ## crate被恰当地命名(C-CRATE-NAME) -HAL crates应该在它目标支持的芯片或者芯片家族之后被命名。它们的名字应该以`-hal`结尾,为了将它们与寄存器访问crates区分开来。名字不应该包含下划线(请改用破折号)。 +HAL crates应该被命名在它目标支持的芯片或者芯片系列之后。它们的名字应该以`-hal`结尾,为了将它们与寄存器访问crates区分开来。名字不应该包含下划线(请改用破折号)。 diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 3c230b80..eda05930 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -44,14 +44,14 @@ pub extern "C" fn cool_function( ); ``` -让我们一次看一个定义,来解释每个部分。 +让我们一次看一个语句,来解释每个部分。 ```rust,ignore #[repr(C)] pub struct CoolStruct { ... } ``` -默认,Rust不会保证包含在一个`struct`中的数据的大小,padding,或者顺序。为了保证与C代码兼容,我们使用`#[repr(C)]`属性,它指示Rust编译器总是使用和C一样的规则去组织一个结构体中的数据。 +默认,Rust不会保证包含在一个`struct`中的数据的大小,填充,或者顺序。为了保证与C代码兼容,我们使用`#[repr(C)]`属性,它指示Rust编译器总是使用和C一样的规则去组织一个结构体中的数据。 ```rust,ignore pub x: cty::c_int, @@ -96,13 +96,13 @@ pub extern "C" fn cool_function( ... ); 如果你要使用的库已经作为一个静态库文档被发布,那就没必要重新编译你的代码。只需按照上面所述转换提供的接口头文件,且在编译/链接时包含静态库文档。 -如果你的代码作为一个源项目存在,将你的C/C++代码编译成一个静态库将是必须的,要么通过使用你现存的编译系统(比如 `make`,`CMake`,等等),要么通过使用一个被叫做`cc` crate的工具移植必要的编译步骤。关于这两个,都必须使用一个`build.rs`脚本。 +如果你的代码作为一个源项目(source project)存在,将你的C/C++代码编译成一个静态库将是必须的,要么通过使用你现存的编译系统(比如 `make`,`CMake`,等等),要么通过使用一个被叫做`cc` crate的工具移植必要的编译步骤。关于这两个,都必须使用一个`build.rs`脚本。 ### Rust的 `build.rs` 编译脚本 一个 `build.rs` 脚本是一个用Rust语法编写的文件,它被运行在你的编译机器上,发生在你项目的依赖项被编译**之后**,但是在你的项目被编译**之前** 。 -可能能在[这里](https://doc.rust-lang.org/cargo/reference/build-scripts.html)发现完整的参考。`build.rs` 脚本能用来生成代码(比如通过[bindgen]),调用外部编译系统,比如`Make`,或者直接通过使用`cc` crate直接编译C/C++ 。 +可能能在[这里](https://doc.rust-lang.org/cargo/reference/build-scripts.html)发现完整的参考。`build.rs` 脚本能用来生成代码(比如通过[bindgen]),调用外部编译系统,比如`Make`,或者直接通过使用`cc` crate来直接编译C/C++ 。 ### 使用外部编译系统 diff --git a/src/interoperability/index.md b/src/interoperability/index.md index 1bda977c..a002b60b 100644 --- a/src/interoperability/index.md +++ b/src/interoperability/index.md @@ -6,7 +6,7 @@ Rust和C代码间的互用性始终取决于两种语言间的数据转换。为 `std::ffi`提供了一些工具去转换更复杂的类型,比如Strings,将`&str`和`String`映射成更容易和安全处理的C类型。 -这两个模块在`core`中都没有,但是你可以在[`cstr_core`] crate中,找到一个`std::ffi::{CStr,CString}` 的 `#![no_std]`兼容版本,大多数的`std::os::raw`类型在[`cty`] crate中。 +这两个模块在`core`中都没有,但是你可以在[`cstr_core`] crate中找到一个`std::ffi::{CStr,CString}` 的 `#![no_std]`兼容版本,大多数的`std::os::raw`类型在[`cty`] crate中。 [`cstr_core`]: https://crates.io/crates/cstr_core [`cty`]: https://crates.io/crates/cty @@ -19,7 +19,7 @@ Rust和C代码间的互用性始终取决于两种语言间的数据转换。为 | u32 or u64 | c_uint | unsigned int | | etc | ... | ... | -像上面提到的,基本类型能被编译器隐式地转换。 +像上面提到的基本类型能被编译器隐式地转换。 ```rust,ignore unsafe fn foo(num: u32) { diff --git a/src/interoperability/rust-with-c.md b/src/interoperability/rust-with-c.md index 163dbc71..39375d88 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -28,7 +28,7 @@ Rust对符号名的修饰与本机的代码链接器所期望的不同。因此 ### `extern "C"` -默认,任何用Rust写的函数将使用Rust ABI(这也不稳定)。相反,当编译面向外部的FFI APIs,我们需要告诉编译器去使用系统ABI 。 +默认,任何用Rust写的函数将使用Rust ABI(这也不稳定)。相反,当编译面向外部的FFI APIs时,我们需要告诉编译器去使用系统ABI 。 取决于你的平台,你可能需要针对一个特定的ABI版本,其记录在[这里](https://doc.rust-lang.org/reference/items/external-blocks.html)。 From b99a8f339f98e99b8032ddb79318203fb948bbae Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 9 Aug 2022 08:32:06 +0800 Subject: [PATCH 102/137] minor --- src/portability/index.md | 2 +- src/start/qemu.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/portability/index.md b/src/portability/index.md index 7df5462f..bef44e0d 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,6 +1,6 @@ # 可移植性 -在嵌入式环境中,可移植性是一个非常重要的主题: 来自单个厂家的每个供应链,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 +在嵌入式环境中,可移植性是一个非常重要的主题: 来自同个厂家的每个商品,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 通过一个被叫做硬件抽象层或者**HAL**的层去均等化这种差异是一种常见的方法。 diff --git a/src/start/qemu.md b/src/start/qemu.md index a0083ccf..9b2148a5 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -96,7 +96,7 @@ fn main() -> ! { `#![no_std]`指出这个程序将 *不会* 链接标准crate`std`。反而它将会链接到它的子集: `core` crate。 -`#![no_main]`指出这个程序将不会使用标准的且被大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是,因为在`no_std`上下文中使用`main`接口要求nightly rust(译者注:`main`接口对程序的运行环境有要求,比如,它假设命令行参数存在,这不适合`no_std`环境)。 +`#![no_main]`指出这个程序将不会使用标准的且被大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是,因为在`no_std`上下文中使用`main`接口要求rust nightly(译者注:`main`接口对程序的运行环境有要求,比如,它假设命令行参数存在,这不适合`no_std`环境)。 `use panic_halt as _;`。这个crate提供了一个`panic_handler`,它定义了程序陷入`panic`时的行为。我们将会在这本书的[运行时恐慌(Panicking)](panicking.md)章节中覆盖更多的细节。 From 109013991f28aec383734138c415a261ec2d5365 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 16 Aug 2022 09:10:32 +0800 Subject: [PATCH 103/137] minor --- src/intro/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/intro/index.md b/src/intro/index.md index 24e7a6d7..5b6934ed 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -2,7 +2,7 @@ 欢迎阅读The Embedded Rust Book:一本介绍如何在裸机(比如,微处理器)上使用Rust编程语言的书籍。 ## Embedded Rust 是为谁准备的 -Embedded Rust是为了那些即想要进行嵌入式编程,又想使用Rust语言提供的高级语言概念和安全保障的人们准备的(也可以看 [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) +Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust语言所提供的高级语言概念和安全保障的人们而准备的(也可以看 [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) ## 本书范围 这本书的目的是: @@ -21,7 +21,7 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想使用Rust语 + 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你也应该要熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 [2018 edition]: https://doc.rust-lang.org/edition-guide/ -+ 你可以轻松地使用其它语言,比如C,C++或者Ada开发和调试嵌入式系统,且熟悉如下的概念: ++ 你可以轻松地使用其它语言,比如C,C++或者Ada,开发和调试嵌入式系统,且熟悉如下的概念: + 交叉编译 + 存储映射的外设(Memory Mapped Peripherals) + 中断 @@ -29,7 +29,7 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想使用Rust语 ### 其它资源 -如果你不熟悉上面提到的东西或者你对这本书中提到的某个特定主题感兴趣,你可能可以从这些资源中找到有用的信息。 +如果你不熟悉上面提到的东西或者你对这本书中提到的某个特定主题感兴趣,你也许能从这些资源中找到有用的信息。 | Topic | Resource | Description | |--------------|----------|-------------| From 4145316cd608da73848b1e3a0552da6bbb2c7ffe Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 26 Sep 2022 11:37:06 +0800 Subject: [PATCH 104/137] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/start/hardware.md | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/start/hardware.md b/src/start/hardware.md index 2466e4eb..addd5ebf 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -199,7 +199,7 @@ hello::__cortex_m_rt_main () at examples/hello.rs:13 13 hprintln!("Hello, world!").unwrap(); ``` -这时,你应该看到 "Hello, world!" 被打印到了OpenOCD控制台上。 +在使用了`next`让函数继续执行之后,你应该看到 "Hello, world!" 被打印到了OpenOCD控制台上。 ``` text $ openocd @@ -214,32 +214,7 @@ Info : halted: PC: 0x08000a0c Info : halted: PC: 0x08000d70 Info : halted: PC: 0x08000d72 ``` - -使用另一个 `next` 将会让处理器执行 `debug::exit`。这个函数和断点的作用一样,会悬挂其进程。 - -``` console -(gdb) next - -Program received signal SIGTRAP, Trace/breakpoint trap. -0x0800141a in __syscall () -``` - -它也会导致这个东西被打印到OpenOCD控制台: - -``` text -$ openocd -(..) -Info : halted: PC: 0x08000502 -Hello, world! -Info : halted: PC: 0x080004ac -Info : halted: PC: 0x080004ae -Info : halted: PC: 0x080004b0 -Info : halted: PC: 0x080004b4 -Info : halted: PC: 0x080004b8 -Info : halted: PC: 0x080004bc -``` - -然而,运行在微控制器上的进程还没有被终止,使用 `continue` 或者一个相同的命令,你能重新启动它。 +消息只打印一次,然后进入定义在19行的无限循环中: `loop {}` 使用 `quit` 命令,你现在可以退出 GDB 了。 From 51f22f8105d6c53c5676d05c5882657f8dfc9c7c Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 28 Sep 2022 09:40:34 +0800 Subject: [PATCH 105/137] minor --- src/portability/index.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/portability/index.md b/src/portability/index.md index bef44e0d..f148fd26 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,11 +1,11 @@ # 可移植性 -在嵌入式环境中,可移植性是一个非常重要的主题: 来自同个厂家的每个商品,甚至每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 +在嵌入式环境中,可移植性是一个非常重要的主题: 每个供应商甚至同个制造商的每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 通过一个被叫做硬件抽象层或者**HAL**的层去均等化这种差异是一种常见的方法。 > 在软件中硬件抽象是一组函数,其模仿了一些平台特定的细节,让程序可以直接访问硬件资源。 -> 它们经常通过提供对硬件的操作系统(OS)的标准调用来让程序员可以编写独立于设备的高性能应用。 +> 通过向硬件提供标准的操作系统(OS)调用,它可以让程序员编写独立于设备的高性能应用。 > > *Wikipedia: [Hardware Abstraction Layer]* @@ -17,14 +17,14 @@ ## 什么是embedded-hal? -简而言之,它是一组traits,其定义了**HAL实现**,**驱动**,**应用(或者固件)** 之间的实现约定。那些约定包括功能(即约定,如果某个类型实现了某个特性,其**HAL实现**就提供了某个功能)和方法(即,如果你构建了一个实现了某个trait的类型,约定保障你肯定有在trait中指定的方法)。 +简而言之,它是一组traits,其定义了**HAL implementations**,**驱动**,**应用(或者固件)** 之间的实现约定(implementation contracts)。这些约定包括功能(即约定,如果为某个类型实现了某个trait,**HAL implementation**就提供了某个功能)和方法(即,如果你要构造一个实现了某个trait的类型,约定保障你肯定有在trait中指定的方法)。 典型的分层可能如下所示: ![](../assets/rust_layers.svg) -一些在**embedded-hal**中被定义的traits是: +一些在**embedded-hal**中被定义的traits: * GPIO (input and output pins) * Serial communication * I2C @@ -32,33 +32,33 @@ * Timers/Countdowns * Analog Digital Conversion -使用**embedded-hal** traits和使用那些实现且使用了它们的crates的主要理由是为了控制复杂性。如果你发现一个应用可能必须要去实现硬件中的外设的接口和潜在的要被其他硬件组件使用的驱动,那么其应该很容易被看作是可复用性有限的。用数学语言来说就是,如果**M**是外设HAL实现的数量,**N**是驱动的数量,那么如果我们要为每个应用重新发明轮子我们最终会有**M*N**个实现,然而通过使用**embedded-hal**的traits提供的 *API* 将会使实现复杂性变成**M+N** 。当然还有其它好处,比如由于API定义良好,开箱即用,导致试错减少。 +出现 **embedded-hal** traits和依赖它们的crates的主要原因是为了控制复杂性。如果你认为一个应用程序可能必须要在硬件中实现外设的使用,以及应用程序和其它硬件组件潜在的驱动,那么其应该很容易被看作是可复用性有限的。用数学语言来说就是,如果**M**是外设HAL implementations的数量,**N**是驱动的数量,那么如果我们要为每个应用重新发明轮子我们最终会有**M*N**个实现,然而通过使用**embedded-hal**的traits提供的 *API* 将会使实现复杂性变成**M+N** 。当然还有其它好处,比如由于API定义良好,开箱即用,导致试错减少。 ## embedded-hal的用户 像上面说的,HAL有三个主要用户: -### HAL实现 +### HAL implementation -一个HAL实现为硬件和HAL traits的用户之间提供了接口。典型的实现由三部分组成: +一个HAL implentation提供硬件和HAL traits的用户之间的接口。典型的实现由三部分组成: * 一个或者多个特定于硬件的类型 -* 生成和初始化这个类型的函数,经常伴随着不同的配置选项(速度,操作模式,使用的管脚,etc 。) -* 与那个类型有关的一个或者更多**embedded-hal** traits 的 `trait` `impl` +* 生成和初始化这个类型的函数,其经常提供不同的配置选项(速度,操作模式,使用的管脚,etc 。) +* 与那个类型有关的一个或者多个 **embedded-hal** traits 的 `trait` `impl` -这样的一个 **HAL实现** 可以有各种风格: +这样的一个 **HAL implementation** 可以有各种类型: * 通过低级硬件访问,e.g. 通过寄存器。 * 通过操作系统,e.g. 通过使用Linux下的 `sysfs` -* 通过适配器,e.g. 单元测试有关的类型的一个模仿 -* 通过硬件适配器的驱动,e.g. I2C多路复用器或者GPIO扩展器(I2C multiplexer or GPIO expander) +* 通过适配器,e.g. 一个与单元测试有关的类型的模仿 +* 通过相关硬件适配器的驱动,e.g. I2C多路复用器或者GPIO扩展器(I2C multiplexer or GPIO expander) ### 驱动 一个驱动为一个外部或者内部组件实现了一组自定义的功能,被连接到一个实现了embedded-hal traits的外设上。这种驱动的典型的例子包括多个传感器(温度计,磁力计,加速度计,光照计),显示设备(LED阵列,LCD显示屏)和执行器(电机,发送器)。 -必须使用一个实现了embedded-hal的某个`trait`的类型的实例来初始化一个驱动,这是通过trait bound来确保的,驱动也提供了它自己的类型实例,这个实例具有一组自定义的方法,这些方法允许与被驱动的设备交互。 +必须使用实现了embedded-hal的某个`trait`的类型的实例来初始化一个驱动,这是通过trait bound来确保的,驱动也提供了它自己的类型实例,这个实例具有一组自定义的方法,这些方法允许与被驱动的设备交互。 ### 应用 -应用把多个部分结合在一起并确保需要的功能被实现。当在不同的系统间移植时,这部分的适配是花费最多精力的地方,因为应用需要通过HAL实现正确地初始化真实的硬件,而且不同硬件的初始化也不相同,甚至有时候差别非常大。用户的选择也在其中扮演了非常重大的角色,因为组件能被物理连接到不同的端口,硬件总线有时候需要外部硬件去配合,或者在内部外设的使用上有不同的考量(e.g. 可使用多个被赋予了不同的功能的计时器或者某个外设与其它外设冲突) +应用把多个部分结合在一起并确保需要的功能被实现。当在不同的系统间移植时,这部分的适配是花费最多精力的地方,因为应用需要通过HAL implementation正确地初始化真实的硬件,而且不同硬件的初始化也不相同,甚至有时候差别非常大。用户的选择也在其中扮演了非常重大的角色,因为组件能被物理连接到不同的端口,硬件总线有时候需要外部硬件去匹配配置,或者用户在内部外设的使用上有不同的考量。 From f03d0458c5a81d9bf121b6c6aa650831b0f7b7d2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Wed, 28 Sep 2022 09:52:16 +0800 Subject: [PATCH 106/137] minor --- src/concurrency/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index 30348c5b..5337f388 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -2,7 +2,7 @@ 无论何时你程序有可能会在不同时刻执行或者不按顺序执行不同的部分,那并发就出现了。在一个嵌入式环境中,这包括: -* 无论何时相关的中断发生时,中断处理函数会运行, +* 中断处理函数,一旦相关的中断发生时,中断处理函数就会运行, * 不同的多线程形式,在这块,你的微处理器通常会在你的程序的不同部分间进行切换, * 在一些多核微处理器系统中,每个核可以同时独立地运行你的程序的不同部分。 @@ -28,13 +28,13 @@ fn main() { ## 全局可变数据 -不像非嵌入式Rust,我们通常不能分配堆和将对那个数据的引用传递进一个新创造的线程。反而,我们的中断处理函数可能在任何时间被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _静态分配的_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 +不像非嵌入式Rust,我们通常不能分配堆和将对那个数据的引用传递进一个新创造的线程中。反而,我们的中断处理函数可能在任何时间被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _静态分配的_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 在Rust中,[`static mut`]这样的变量读取或者写入总是unsafe的,因为不特别关注它们的话,你可能会触发一个竞态条件,你对变量的访问在中途就被一个也访问那个变量的中断打断了。 [`static mut`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable -为了举例这种行为如何在你的代码中导致了微妙的错误,思考一个嵌入式程序,这个程序在每个一秒的周期内计数一些输入信号的上升沿(一个频率计数器): +为了举例这种行为如何在你的代码中导致了微妙的错误,思考一个嵌入式程序,这个程序在每一秒的周期内计数一些输入信号的上升沿(一个频率计数器): ```rust,ignore static mut COUNTER: u32 = 0; @@ -59,7 +59,7 @@ fn timer() { } ``` -每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当它看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在读取之后但是在写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那段时间,某些变化我们会进行两次计算。 +每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当它看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在读取之后但是在写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那期间某些变化我们会计算两次。 ## 临界区(Critical Sections) From 4af032d6fe2007112bf0d577522c6b3a8bcf24e4 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 13 Oct 2022 23:00:31 +0800 Subject: [PATCH 107/137] minor --- src/intro/hardware.md | 2 +- src/intro/index.md | 10 +++++----- src/intro/install.md | 2 +- src/intro/install/linux.md | 8 ++++---- src/intro/install/verify.md | 10 +++++----- src/intro/tooling.md | 8 ++++---- src/start/hardware.md | 22 +++++++++++----------- src/start/index.md | 2 +- src/start/qemu.md | 30 +++++++++++++++--------------- 9 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/intro/hardware.md b/src/intro/hardware.md index b0d2d59a..5e022b25 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -8,7 +8,7 @@

-这个板子包含什么? +这个板子有什么? + 一个[STM32F303VCT6](https://www.st.com/en/microcontrollers/stm32f303vc.html)微控制器。这个微控制器包含 + 一个单核的ARM Cortex-M4F 处理器,支持单精度浮点运算,72MHz的最大时钟频率。 diff --git a/src/intro/index.md b/src/intro/index.md index 5b6934ed..676dfb73 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -4,19 +4,19 @@ ## Embedded Rust 是为谁准备的 Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust语言所提供的高级语言概念和安全保障的人们而准备的(也可以看 [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) -## 本书范围 +## 范围 这本书的目的是: + 让开发者快速上手Rust嵌入式开发,比如,如何设置一个开发环境。 + 分享那些关于使用Rust进行嵌入式开发的,现存的,最好的实践经验,比如,如何最大程度上地利用好Rust语言的特性去写更正确的嵌入式软件 + 某种程度下作为工具书,比如,如何在一个项目里将C和Rust混合在一起使用 -这本书尽可能地尝试变得适用于所有场景,但是为了使读者和作者更容易理解,在所有的案例中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个架构,书中会在需要时解释这个架构的特定细节。 +虽然这本书尝试尽可能地可以适用于大多数场景,但是为了使读者和作者更容易理解,在所有的案例中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个架构,书中会在需要时对这个架构的特定细节进行解释。 ## 这本书是为谁准备的 -这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对Rust嵌入式编程好奇的人都能从这本书中获得某些东西。对于那些先前没有任何经验的人,我们建议你读一下“要求和前提”部分。从其它资料中获取、补充缺失的知识,这样能提高你的阅读体验。你可以看看“其它资源”部分,以找到你感兴趣的主题的资源。 +这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对Rust嵌入式编程好奇的人都能从这本书中得到某些收获。对于那些先前没有任何经验的人,我们建议你读一下“要求和预备知识”部分。从其它资料中获取、补充缺失的知识,这样能提高你的阅读体验。你可以看看“其它资源”部分,以找到你感兴趣的那些主题的资源。 -### 要求和前提 +### 要求和预备知识 + 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你也应该要熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 @@ -55,7 +55,7 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust ## 如何使用这本书 这本书通常假设你是前后阅读的。之后章节是建立在先前章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,因为在随后的章节将会再次重温这个主题。 在大多数案例中这本书将使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的厂家之间是不同的,甚至来自同一个厂家,不同处理器系列之间也是不同的。 -因为这个理由,我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。(译者注:我使用[renode](https://renode.io/about/)来测试大多数例子) +因此我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。(译者注:我使用[renode](https://renode.io/about/)来测试大多数例子) [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html diff --git a/src/intro/install.md b/src/intro/install.md index 800dd817..2e004a83 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -10,7 +10,7 @@ $ rustc -V rustc 1.31.1 (b6c32da9b 2018-12-18) ``` -考虑到带宽和磁盘的使用量,默认安装只支持本地环境的编译。为了添加对ARM Cortex-M架构交叉编译的支持,从下列编译目标选择一个。对于这本书里使用的STM32F3DISCOVERY板子,使用`thumbv7em-none-eabihf`目标。 +考虑到带宽和磁盘的使用量,默认的安装只支持主机环境的编译。为了添加对ARM Cortex-M架构交叉编译的支持,从下列编译目标中选择一个。对于这本书里使用的STM32F3DISCOVERY板子,使用`thumbv7em-none-eabihf`作为目标。 Cortex-M0, M0+, and M1 (ARMv6-M 架构): ``` console diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index 77a43648..52ab8f0a 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -2,7 +2,7 @@ 这部分是在某些Linux发行版下的安装指令。 -## Packages +## 依赖包 - Ubuntu 18.04 或者更新的版本 / Debian stretch 或者更新的版本 @@ -59,7 +59,7 @@ sudo pacman -S arm-none-eabi-gdb qemu-arch-extra openocd ## udev 规则 -这个规则可以让你在不需要超级用户权限的情况下,使用OpenOCD和Discovery开发板。 +这个规则可以让你在不使用超级用户权限的情况下,使用OpenOCD和Discovery开发板。 生成包含下列内容的 `/etc/udev/rules.d/70-st-link.rules` 文件 @@ -77,7 +77,7 @@ ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", TAG+="uaccess" sudo udevadm control --reload-rules ``` -如果开发板已经被插入了你的笔记本,拔下它然后再插上它。 +如果开发板已经被插入了你的笔记本中,拔下它然后再插上它。 你可以通过运行这个命令检查权限: @@ -93,7 +93,7 @@ Bus 001 Device 018: ID 0483:374b STMicroelectronics ST-LINK/V2.1 (..) ``` -记住bus和device号,使用这些数字创造一个像是 `/dev/bus/usb//` 这样的路径。然后像这样使用这个路径: +记住bus和device号,使用这些数字组合成一个像是 `/dev/bus/usb//` 这样的路径。然后像这样使用这个路径: ``` console ls -l /dev/bus/usb/001/018 diff --git a/src/intro/install/verify.md b/src/intro/install/verify.md index 037e4df3..ef812823 100644 --- a/src/intro/install/verify.md +++ b/src/intro/install/verify.md @@ -4,7 +4,7 @@ 使用一个micro USB线缆将你的笔记本/个人电脑连接到discovery开发板上。discovery开发板有两个USB连接器;使用标记着"USB ST-LINK"的那个,它位于开发板边缘的中间位置。 -也要检查下ST-LINK的短路帽被安装了。看下面的图;ST-LINK短路帽用红色圈起来了。 +也要检查下ST-LINK的短路帽是否被安装了。看下面的图;ST-LINK短路帽用红色圈起来了。

@@ -19,7 +19,7 @@ openocd -f interface/stlink.cfg -f target/stm32f3x.cfg > **注意**: 旧版的openocd, 包括从2017发布的0.10.0, 不包含新的(且更适合的)`interface/stlink.cfg`文件; 你需要使用`interface/stlink-v2.cfg` 或者 `interface/stlink-v2-1.cfg`。 -你应该看到下列的输出,且程序应该阻塞住了控制台: +你应该看到了下面的输出,且程序应该阻塞住了控制台: ``` text Open On-Chip Debugger 0.10.0 @@ -44,7 +44,7 @@ Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints [下个章节]: ../../start/index.md -如果你没看到"breakpoints"这行,尝试下下列命令中的某一个。 +如果你没看到"breakpoints"这行,尝试下下列命令中的某一个命令。 ``` console openocd -f interface/stlink-v2.cfg -f target/stm32f3x.cfg @@ -54,9 +54,9 @@ openocd -f interface/stlink-v2.cfg -f target/stm32f3x.cfg openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg ``` -如果这些命令的某条起作用了,那意味着你使用的discovery开发板是一个旧的版本。那也不成问题,但是你要记住这件事,因为随后你可能需要点不同的配置。你可以移到[下个章节]了。 +如果这些命令的某条起作用了,那意味着你使用的discovery开发板是一个旧的版本。那也不成问题,但是你要记住这件事,因为随后你的配置可能有点不同。你可以移到[下个章节]了。 -如果这些命令在normal user模式下都没用,尝试下使用root模式运行它们(e.g. `sudo openocd ..`)。如果命令在root模式下起作用,要检查下[udev rules]是否被正确地设置了。 +如果这些命令在normal user模式下都没用,尝试下使用root模式运行它们(e.g. `sudo openocd ..`)。如果命令在root模式下起作用,需要检查下[udev rules]是否被正确地设置了。 [udev rules]: linux.md#udev-rules diff --git a/src/intro/tooling.md b/src/intro/tooling.md index 1a5b2f98..3e819275 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -7,7 +7,7 @@ - 有ARM支持的GDB。强烈建议7.12或者更新的版本。测试版本: 7.10, 7.11 和 8.1 - [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) 或者 `git` -这些工具都是可选的,但是跟着书来使用它们,会更容易。下面的文档将解释我们为什么使用这些工具。安装指令可以在下一页找到。 +这些工具都是可选的,但是跟着书一起来使用它们,会更容易。下面的文档将解释我们为什么使用这些工具。安装指令可以在下一页找到。 ## `cargo-generate` 或者 `git` 裸机编程是非标准 Rust 编程,为了得到正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进了一个模板里,你只需要补充缺失的信息(比如项目名和你的目标硬件的特性)。
@@ -15,14 +15,14 @@ ## `cargo-binutils` `cargo-binutils`是一个Cargo命令的子集,它让我们能轻松使用Rust工具链带来的LLVM工具。这些工具包括LLVM版本的`objdump`,`nm`和`size`,用来查看二进制文件。
-在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工具是相同的一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 +在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工具是相同的一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具,支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 ## `qemu-system-arm` -QEMU是一个仿真器。在这个例子里,我们使用能完全仿真ARM系统的改良版。我们使用QEMU在主机上运行嵌入式程序。多亏了它,你可以在没有任何硬件的情况下,尝试这本书的部分示例。 +QEMU是一个仿真器。在这个例子里,我们使用能完全仿真ARM系统的改良版QEMU。我们使用QEMU在主机上运行嵌入式程序。多亏了它,你可以在没有任何硬件的情况下,尝试这本书的部分示例。 ## GDB -调试器是嵌入式开发的一个非常重要的组件,你不可能总是有机会将内容记录到主机控制台上。在某些情况里,你可能甚至没有LEDs去点亮你的硬件!
+调试器是嵌入式开发的一个非常重要的组件,你不可能总是可以将内容记录到主机控制台上。在某些情况里,你甚至可能都没法用LEDs去点亮你的硬件!
通常,提到调试,LLDB和GDB的功能一样,但是我们还没找到与GDB的`load`命令对应的LLDB命令,这条命令负责将程序上传到目标硬件,因此我们现在推荐你使用GDB。 ## OpenOCD diff --git a/src/start/hardware.md b/src/start/hardware.md index addd5ebf..4751d9f9 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -1,14 +1,14 @@ # 硬件 -现在你应该有点熟悉工具和开发过程了。这部分我们将转换到真正的硬件上;步骤非常相似。让我们深入进去。 +现在你应该有点熟悉工具和开发过程了。在这部分我们将切换到真正的硬件上;步骤非常相似。让我们深入进去。 ## 认识你的硬件 -在我们开始之前,你需要了解下你的目标设备的一些特性,因为它们将被用于配置项目: -- ARM 内核。e.g. Cortex-M3 。 +在我们开始之前,你需要了解下你的目标设备的一些特性,因为你将用它们来配置项目: +- ARM 内核。比如 Cortex-M3 。 - ARM 内核包括一个FPU吗?Cortex-M4**F**和Cortex-M7**F**有。 -- 目标设备有多少Flash和RAM?e.g. 256KiB的Flash和32KiB的RAM。 -- Flash和RAM映射在地址空间什么位置?e.g. RAM通常位于 `0x2000_0000` 地址处。 +- 目标设备有多少Flash和RAM?比如 256KiB的Flash和32KiB的RAM。 +- Flash和RAM映射在地址空间的什么位置?比如 RAM通常位于 `0x2000_0000` 地址处。 你可以在你的设备的数据手册和参考手册上找到这些信息。 @@ -46,7 +46,7 @@ tail -n5 .cargo/config.toml target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) ``` -我们将使用`thumbv7em-none-eabihf`,因为它覆盖Cortex-M4F核。 +我们将使用`thumbv7em-none-eabihf`,因为它涉及到Cortex-M4F核。 第二步是将存储区域信息(memory region information)输入`memory.x`。 @@ -60,7 +60,7 @@ MEMORY RAM : ORIGIN = 0x20000000, LENGTH = 40K } ``` -> **注意**:如果你因为一些理由,在对某个编译目标第一次编译后,改变了`memory.x`文件,需要在`cargo build`之前执行`cargo clean`。因为`cargo build`可能不会追踪`memory.x`的更新。 +> **注意**:如果你因为一些理由,在对某个编译目标首次编译后,改变了`memory.x`文件,需要在`cargo build`之前执行`cargo clean`。因为`cargo build`可能不会追踪`memory.x`的更新。 我们将使用hello示例再次开始,但是首先我们必须做一个小改变。 @@ -71,15 +71,15 @@ MEMORY fn main() -> ! { hprintln!("Hello, world!").unwrap(); - // exit QEMU - // NOTE do not run this on hardware; it can corrupt OpenOCD state + // 退出 QEMU + // 注意 不要在硬件上运行这个;它会打破OpenOCD的状态 // debug::exit(debug::EXIT_SUCCESS); loop {} } ``` -你能像你之前做的一样,使用`cargo build`检查编译程序,使用`cargo-binutils`观察二进制文件。`cortex-m-rt`库可以处理所有运行芯片所需的魔法,几乎所有的Cortex-M CPUs都按同样的方式启动,这同样有用。 +你可以像你之前做的一样,使用`cargo build`检查编译程序,使用`cargo-binutils`观察二进制项。`cortex-m-rt`库可以处理所有运行芯片所需的魔法,几乎所有的Cortex-M CPUs都按同样的方式启动,这同样有用。 ``` console cargo build --example hello @@ -87,7 +87,7 @@ cargo build --example hello ## 调试 -调试将看起来有点不同。事实上,取决于不同的目标设备第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试的特定的信息,可以看[the Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 +调试将看起来有点不同。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试有关的信息,可以看[the Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 像之前一样,我们将进行远程调试,客户端将是一个GDB进程。不同的是,OpenOCD将是服务器。 diff --git a/src/start/index.md b/src/start/index.md index 18de458d..70ab15d8 100644 --- a/src/start/index.md +++ b/src/start/index.md @@ -1,4 +1,4 @@ # 开始 -在这部分里,你将会经历编写,编译,烧录和调试嵌入式程序的过程。大多数的例子都不需要特定的硬件,你就可以试试,因为我们将要向你展示一个开源硬件仿真器,QEMU的基本使用。唯一需要硬件的部分,自然是,[硬件](./hardware.md)章节,我们会使用OpenOCD去编程一个[STM32F3DISCOVERY]。 +在这部分里,你将会经历编写,编译,烧录和调试嵌入式程序。大多数的例子都不需要特定的硬件就可以试试,因为我们将要向你展示一个开源硬件仿真器,QEMU的基本使用。唯一需要硬件的部分,那就是,[硬件](./hardware.md)那一章,我们会使用OpenOCD去编程一个[STM32F3DISCOVERY]。 [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html diff --git a/src/start/qemu.md b/src/start/qemu.md index 9b2148a5..b2da8c65 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -7,7 +7,7 @@ 在这个引导里,我们将使用"app"这个名字来代指项目名。无论何时你看到单词"app",你应该用你选择的项目名来替代"app"。或者你也可以选择把你的项目命名为"app",避免替代。 ## 生成一个非标准的 Rust program -我们将使用[`cortex-m-quickstart`]项目模板来生成一个新项目。生成的项目将包含一个最基本的应用:对于一个新的嵌入式rust应用来说,是一个很好的起点。另外,项目将包含一个`example`文件夹,文件夹中有许多独立的应用,突出了一些关键的嵌入式rust的功能。 +我们将使用[`cortex-m-quickstart`]项目模板来生成一个新项目。生成的项目将包含一个最基本的应用:对于一个新的嵌入式rust应用来说,是一个很好的开始。另外,项目将包含一个`example`文件夹,文件夹中有许多独立的应用,突出了一些关键的嵌入式rust的功能。 [`cortex-m-quickstart`]: /~https://github.com/rust-embedded/cortex-m-quickstart @@ -96,7 +96,7 @@ fn main() -> ! { `#![no_std]`指出这个程序将 *不会* 链接标准crate`std`。反而它将会链接到它的子集: `core` crate。 -`#![no_main]`指出这个程序将不会使用标准的且被大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是,因为在`no_std`上下文中使用`main`接口要求rust nightly(译者注:`main`接口对程序的运行环境有要求,比如,它假设命令行参数存在,这不适合`no_std`环境)。 +`#![no_main]`指出这个程序将不会使用标准的且被大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是,因为在`no_std`上下文中使用`main`接口要求开发版的rust 。 `use panic_halt as _;`。这个crate提供了一个`panic_handler`,它定义了程序陷入`panic`时的行为。我们将会在这本书的[运行时恐慌(Panicking)](panicking.md)章节中覆盖更多的细节。 @@ -121,7 +121,7 @@ target = "thumbv7m-none-eabi" # Cortex-M3 # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) # target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) ``` -为了交叉编译Cortex-M3架构我们不得不使用`thumbv7m-none-eabi`。当安装Rust工具时,target不会自动被安装,如果你还没有做,现在是个好时机,去添加那个target到工具链上。 +为了交叉编译Cortex-M3架构我们不得不使用`thumbv7m-none-eabi`。当安装Rust工具时,target不会自动被安装,如果你还没有做,现在可以去添加那个target到工具链上。 ``` console rustup target add thumbv7m-none-eabi ``` @@ -143,7 +143,7 @@ cargo readobj --bin app -- --file-headers 注意: * `--bin app` 是一个用来检查`target/$TRIPLE/debug/app`的语法糖 -* `--bin app` 如果需要,将也会重新编译二进制。 +* `--bin app` 如果有需要,也会重新编译二进制项。 ``` text ELF Header: @@ -168,7 +168,7 @@ ELF Header: Section header string table index: 18 ``` -`cargo-size` 能打印二进制文件的linker部分的大小。 +`cargo-size` 能打印二进制项的linker部分的大小。 ```console cargo size --bin app --release -- -A @@ -207,9 +207,9 @@ Total 14570 > - `.vector_table` 是一个我们用来存储向量(中断)表的*非*标准的section > - `.ARM.attributes` 和 `.debug_*` sections包含元数据,当烧录二进制文件时,它们不会被加载到目标上的。 -**重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*没有正确地反应了程序被烧录到设备上时将占据的空间的大小。*一直*使用`cargo-size`检查一个二进制文件有多大。 +**重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*没有正确地反应了程序被烧录到设备上时将占据的空间的大小。要*一直*使用`cargo-size`检查一个二进制项的大小。 -`cargo-objdump` 能用来反编译二进制文件。 +`cargo-objdump` 能用来反编译二进制项。 ```console cargo objdump --bin app --release -- --disassemble --no-show-raw-insn --print-imm-hex @@ -280,14 +280,14 @@ fn main() -> ! { hprintln!("Hello, world!").unwrap(); // 退出 QEMU - // NOTE 不要在硬件上运行这个;它会打破OpenOCD状态 + // NOTE 不要在硬件上运行这个;它会打破OpenOCD的状态 debug::exit(debug::EXIT_SUCCESS); loop {} } ``` -这个程序使用一些被叫做semihosting的东西去打印文本到主机调试台上。当使用真实的硬件时,这要求一个调试对话,但是当使用QEMU时这样就可以工作了。 +这个程序使用一些被叫做semihosting的东西去打印文本到主机调试台上。当使用的是真实的硬件时,这需要一个调试对话,但是当使用的是QEMU时这就可以工作了。 通过编译示例,让我们开始 @@ -295,9 +295,9 @@ fn main() -> ! { cargo build --example hello ``` -输出的二进制文件将位于`target/thumbv7m-none-eabi/debug/examples/hello`。 +输出的二进制项将位于`target/thumbv7m-none-eabi/debug/examples/hello`。 -为了在QEMU上运行这个二进制文件,执行下列的命令: +为了在QEMU上运行这个二进制项,执行下列的命令: ```console qemu-system-arm \ @@ -312,7 +312,7 @@ qemu-system-arm \ Hello, world! ``` -这个命令应该打印文本之后成功地退出 (exit code = 0)。你能使用下列的指令检查下: +这个命令应该在打印文本之后成功地退出 (exit code = 0)。你可以使用下列的指令检查下: ```console echo $? @@ -324,13 +324,13 @@ echo $? 让我们看看QEMU命令: -+ `qemu-system-arm`。这是QEMU仿真器。这些QEMU有一些改良版的二进制文件;这个仿真器能做ARM机器的全系统仿真。 ++ `qemu-system-arm`。这是QEMU仿真器。这些QEMU有一些改良版的二进制项;这个仿真器能做ARM机器的全系统仿真。 + `-cpu cortex-m3`。这告诉QEMU去仿真一个Cortex-M3 CPU。指定CPU模型会让我们捕捉到一些误编译错误:比如,运行一个为Cortex-M4F编译的程序,它具有一个硬件FPU,在执行时将会使QEMU报错。 + `-machine lm3s6965evb`。这告诉QEMU去仿真 LM3S6965EVB,一个包含LM3S6965微控制器的评估板。 + `-nographic`。这告诉QEMU不要启动它的GUI。 + `-semihosting-config (..)`。这告诉QEMU使能半主机模式。半主机模式允许被仿真的设备,使用主机的stdout,stderr,和stdin,并在主机上创建文件。 -+ `-kernel $file`。这告诉QEMU在仿真机器上加载和运行哪个二进制文件。 ++ `-kernel $file`。这告诉QEMU在仿真机器上加载和运行哪个二进制项。 输入这么长的QEMU命令太费功夫了!我们可以设置一个自定义运行器(runner)简化步骤。`.cargo/config.toml` 有一个被注释掉的,可以调用QEMU的运行器。让我们去掉注释。 ```console @@ -376,7 +376,7 @@ qemu-system-arm \ 这个命令将不打印任何东西到调试台上,且将会阻塞住终端。此刻我们还传递了两个额外的标志。 + `-gdb tcp::3333`。这告诉QEMU在3333的TCP端口上等待一个GDB连接。 -+ `-S`。这告诉QEMU在启动时,冻结机器。没有这个,在我们有机会启动调试器之前,程序可能已经到达了主程序的底部了! ++ `-S`。这告诉QEMU在启动时,冻结机器。没有这个,在我们有机会启动调试器之前,程序有可能已经到达了主程序的底部了! 接下来我们在另一个终端启动GDB,且告诉它去加载示例的调试符号。 ```console From e08766e36dbece6788d5313d43e4237a1cd59a5a Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 13 Oct 2022 23:45:43 +0800 Subject: [PATCH 108/137] minor --- src/start/registers.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 2a48d1c4..08a31e83 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,17 +1,17 @@ # 存储映射的寄存器 -嵌入式系统只能通过执行普通的Rust代码和在RAM间移动数据来运行下去。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 +嵌入式系统运行下去只能通过执行常规的Rust代码并在RAM间移动数据来进行。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 -你可能发现,访问你的微控制器外设所需要的代码,已经被写进了下面的某个抽象层中。 +你可能发现,访问你的微控制器外设所需要的代码,已经被写进了下面的某个抽象层中了。

-* Micro-architecture Crate(微架构Crate) - 这个Crate提供那些对你正在使用的微控制器的处理器内核和任何使用这个处理器内核的微控制器的那些常见外设,有用的程序。比如 [cortex-m] crate提供给你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个能力。 +* Micro-architecture Crate(微架构Crate) - 这个库拥有任何对于你正用着的微控制器的处理器内核来说经常会用到的程序,也包括对于这些微控制器中通用的外设有用的程序。比如 [cortex-m] crate提供给你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个能力。 * Peripheral Access Crate(PAC)(外设访问Crate) - 这种crate是在被不同存储器封装的寄存器上再进行的一次封装,其由你正在使用的微控制器的产品号定义的。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,你将根据你的微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 * HAL Crate - 这些crates为你的处理器提供了一个更友好的API,通常是通过实现在[embedded-hal]中定义的一些常用的traits来实现的。比如,这个crate可能提供一个`Serial`结构体,它的构造函数需要一组合适的GPIO端口和一个波特率,它为了发送数据提供一些 `write_byte` 函数。查看 [可移植性] 可以看到更多关于 [embedded-hal] 的信息。 -* Board Crate(开发板crate) - 这个通过预先配置的不同的外设和GPIO管脚再进行了一层抽象去适配你正在使用的特定的开发者工具或者开发板,比如对于STM32F3DISCOVERY开发板来说,是[stm32f3-discovery] +* Board Crate(开发板crate) - 这些Crate通过预配置不同的外设和GPIO管脚再进行了一层抽象以适配你正在使用的特定的开发者工具或者开发板,比如对于STM32F3DISCOVERY开发板来说,是[stm32f3-discovery] [cortex-m]: https://crates.io/crates/cortex-m [tm4c123x]: https://crates.io/crates/tm4c123x @@ -23,7 +23,7 @@ ## 开发板Crate (Board Crate) -如果你是嵌入式Rust新手,board crate是一个完美的起点。它们很好地抽象出了,在开始学习这个项目时,需要耗费心力了解的硬件细节,使得标准工作变得简单,像是打开或者关闭LED。不同的板子间,它们提供的功能变化很大。因为这本书是不假设我们使用的是何种板子,所以board crate不会被这本书涉及。 +如果你是嵌入式Rust新手,board crate是一个完美的开始。它们很好地抽象出了,在开始学习这个项目时,需要耗费心力了解的硬件细节,使得标准工作变得简单,像是打开或者关闭LED。不同的板子间,它们提供的功能变化很大。因为这本书是不假设我们使用的是何种板子,所以这本书不会提到board crate。 如果你想要用STM32F3DISCOVERY开发板做实验,强烈建议看一下[stm32f3-discovery]开发板crate,它提供了点亮LEDs,访问它的指南针,蓝牙和其它的功能。[Discovery]书对于一个board crate的用法提供一个很好的介绍。 @@ -55,7 +55,7 @@ fn main() -> ! { loop {} } ``` -`SYST`结构体上的功能,相当接近ARM技术手册为这个外设定义的功能。在这个API中没有关于 '延迟X毫秒' 的功能 - 我们不得不使用一个 `while` 循环自己粗略地实现它。注意,我们调用了`Peripherals::take()`才能访问我们的`SYST`结构体 - 这是一个特别的程序,保障了在我们的整个程序中只存在一个`SYST`结构体实例,更多的信息可以看[外设]部分。 +`SYST`结构体上的功能,相当接近ARM技术手册为这个外设定义的功能。在这个API中没有关于 '延迟X毫秒' 的功能 - 我们不得不通过使用一个 `while` 循环来粗略地实现它。注意,我们调用了`Peripherals::take()`才能访问我们的`SYST`结构体 - 这是一个特别的程序,保障了在我们的整个程序中只存在一个`SYST`结构体实例,更多的信息可以看[外设]部分。 [外设]: ../peripherals/index.md @@ -90,7 +90,7 @@ pub fn init() -> (Delay, Leds) { ``` -我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器位域的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是更多代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数,其所有可能的参数都有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 +我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器字段的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是有了更多的代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数,其所有可能的参数都有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 ### 读取 From a3722357040e577f117105aa0908ad542c60c55b Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 13 Oct 2022 23:50:38 +0800 Subject: [PATCH 109/137] minor --- src/peripherals/a-first-attempt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index a835b8cb..dc574efb 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -75,7 +75,7 @@ fn get_time() -> u32 { 现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件有一堆可变的状态,对于编译器来说没有方法去知道是否这些写操作是真正安全的,因此默认就这样是个不错的选择。 -## Rusty封装 +## Rust风格的封装 我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于我们用户来说,可以安全地被调用。作为驱动的作者,我们手动地验证不安全的代码是否正确,然后为我们的用户提供一个safe的API,因此他们不必担心它(让他们相信我们做对了!)。 From f27f2c0cabffe6592fd42c65d2164ecf620733b3 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 14 Oct 2022 11:14:05 +0800 Subject: [PATCH 110/137] minor --- src/peripherals/borrowck.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index 6eb49179..f8436bf7 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -1,6 +1,6 @@ ## 可变的全局状态 -不幸地是,硬件本质上是个可变的全局状态,对于Rust开发者来说可能感到很害怕。因为硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 +不幸地是,硬件本质上是个可变的全局状态,Rust开发者可能对此感到很害怕。因为硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 ## 我们应该遵循什么规则? From 27d370571181e78b7cd07acd364a46621f5f3381 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 14 Oct 2022 11:22:02 +0800 Subject: [PATCH 111/137] minor --- src/intro/no-std.md | 4 ++-- src/intro/tooling.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/intro/no-std.md b/src/intro/no-std.md index 040d6ad0..a28ef35e 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -30,9 +30,9 @@ | libcore available | ✓ | ✓ | | writing firmware, kernel, or bootloader code | ✓ | ✘ | -\* 只有在你使用了 `alloc` crate 和一个适合的分配器,比如[alloc-cortex-m]时有效。 +\* 只有在你使用了 `alloc` crate 和设置了一个适合的分配器,比如[alloc-cortex-m]后有效。 -\** 只有在你使用了 `collections` crate 和配置了一个全局默认的分配器时有效。 +\** 只有在你使用了 `collections` crate 并配置了一个全局默认的分配器后有效。 [alloc-cortex-m]: /~https://github.com/rust-embedded/alloc-cortex-m diff --git a/src/intro/tooling.md b/src/intro/tooling.md index 1a5b2f98..54ab00fb 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -1,5 +1,5 @@ # 工具 -与微控制器打交道需要使用几种不同的工具,因为我们要处理的架构与笔记本电脑不同,我们必须在 *远程* 设备上运行和调试程序。我们将使用下面列举出来的工具。当没有指定一个最小版本时,最新的版本应该可以工作,但是我们已经列出了我们已经测过的那些版本。 +与微控制器打交道需要使用几种不同的工具,因为我们要处理的架构与笔记本电脑不同,我们必须在 *远程* 设备上运行和调试程序。我们将使用下面列举出来的工具。当没有指定一个最小版本时,最新的版本应该也可以用,但是我们还是列出了我们已经测过的那些版本。 - Rust 1.31, 1.31-beta, 或者一个更新的,支持ARM Cortex-M编译的工具链。 - [`cargo-binutils`](/~https://github.com/rust-embedded/cargo-binutils) ~0.1.4 - [`qemu-system-arm`](https://www.qemu.org/). 测试的版本: 3.0.0 @@ -27,7 +27,7 @@ QEMU是一个仿真器。在这个例子里,我们使用能完全仿真ARM系 ## OpenOCD GDB不能直接和你的STM32F3DISCOVERY开发板上的ST-Link调试硬件通信。它需要一个转译者,the Open On-Chip Debugger,OpenOCD就是这样的转译者。OpenOCD是一个运行在你的笔记本/个人电脑上的程序,它在基于远程调试的TCP/IP协议和ST-Link的USB协议间进行转译。 -OpenOCD也执行其它一些转译中的重要的工作,用于调试你的STM32FDISCOVERY开发板上的ARM Cortex-M微控制器: +OpenOCD也执行其它一些转译中的重要的工作,这些工作用于调试你的STM32FDISCOVERY开发板上的ARM Cortex-M微控制器: * 它知道如何与ARM CoreSight调试外设使用的内存映射的寄存器进行交互。这些CoreSight寄存器的功能有: * 断点操作 * 读取和写入CPU寄存器 From 0bf60da40c2df673076a354381fd0ec3c7d6b970 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 14 Oct 2022 15:04:54 +0800 Subject: [PATCH 112/137] minor --- src/start/panicking.md | 8 ++++---- src/start/registers.md | 14 +++++++------- src/start/semihosting.md | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/start/panicking.md b/src/start/panicking.md index 2c118d2d..e6b60e50 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -2,15 +2,15 @@ 运行时恐慌是Rust语言的一个核心部分。像是索引这样的內建的操作为了存储安全性是运行时检查的。当尝试越界索引时,这会导致运行时恐慌(panic)。 -在标准库中,运行时恐慌有一个被定义了的行为:它展开(unwinds)恐慌的线程的栈,除非用户选择在恐慌时终止程序。 +在标准库中,运行时恐慌的行为被定义了:它展开(unwinds)恐慌的线程的栈,除非用户选择在恐慌时终止程序。 然而在没有标准库的程序中,运行时恐慌的行为是未被定义了的。通过声明一个 `#[painc_handler]` 函数可以选择一个运行时恐慌的行为。 -这个函数必须在一个程序的依赖图中只出现一次,且必须有这样的签名: `fn(&PanicInfo) -> !`,`PanicInfo`是一个包含关于运行时恐慌发生位置的信息的结构体。 +这个函数在一个程序的依赖图中必须只出现一次,且必须有这样的签名: `fn(&PanicInfo) -> !`,`PanicInfo`是一个包含关于发生运行时恐慌的位置信息的结构体。 [`PanicInfo`]: https://doc.rust-lang.org/core/panic/struct.PanicInfo.html -鉴于嵌入式系统的范围从面向用户的系统到安全关键系统,没有一个运行时恐慌行为能满足所有场景,但是有许多常用的行为。这些常用的行为已经被打包进了一些crates中,这些crates定义了 `#[panic_handler]`函数。比如: +鉴于嵌入式系统的范围从面向用户的系统到安全关键系统,没有一个运行时恐慌行为能满足所有场景,但是有许多常用的行为。这些常用的行为已经被打包进了一些crates中,这些crates中定义了 `#[panic_handler]`函数。比如: - [`panic-abort`]. 这个运行时恐慌会导致终止指令被执行。 - [`panic-halt`]. 这个运行时恐慌会导致程序,或者现在的线程,通过进入一个无限循环中而挂起。 @@ -26,7 +26,7 @@ [`panic-handler`]: https://crates.io/keywords/panic-handler -仅仅通过连接到相关的crate,一个程序就可以从这些行为中选择一个行为。将运行时恐慌的行为作为一行代码放进一个应用的源码中,不仅仅是可以作为文档使用,而且能根据编译配置改变运行时恐慌的行为。比如: +仅仅通过链接到相关的crate中,一个程序就可以从这些行为中选择一个运行时恐慌行为。将运行时恐慌的行为作为一行代码放进一个应用的源码中,不仅仅是可以作为文档使用,而且能根据编译配置改变运行时恐慌的行为。比如: ``` rust,ignore #![no_main] diff --git a/src/start/registers.md b/src/start/registers.md index 08a31e83..0535afcd 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,15 +1,15 @@ # 存储映射的寄存器 -嵌入式系统运行下去只能通过执行常规的Rust代码并在RAM间移动数据来进行。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 +只有通过执行常规的Rust代码并在RAM间移动数据,嵌入式系统才能继续运行下去。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 -你可能发现,访问你的微控制器外设所需要的代码,已经被写进了下面的某个抽象层中了。 +你可能会发现,访问你的微控制器外设所需要的代码,已经被写进了下面的某个抽象层中了。

-* Micro-architecture Crate(微架构Crate) - 这个库拥有任何对于你正用着的微控制器的处理器内核来说经常会用到的程序,也包括对于这些微控制器中通用的外设有用的程序。比如 [cortex-m] crate提供给你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个能力。 -* Peripheral Access Crate(PAC)(外设访问Crate) - 这种crate是在被不同存储器封装的寄存器上再进行的一次封装,其由你正在使用的微控制器的产品号定义的。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,你将根据你的微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 +* Micro-architecture Crate(微架构Crate) - 这个库拥有任何对于微控制器的处理器内核来说经常会用到的程序,也包括对于在这些微控制器中的通用外设来说有用的程序。比如 [cortex-m] crate提供给你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个外设功能。 +* Peripheral Access Crate(PAC)(外设访问Crate) - 这种crate是在被不同存储器封装的寄存器上再进行的一次封装,寄存器由你正在使用的微控制器的产品号定义的。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,你将根据你的微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 * HAL Crate - 这些crates为你的处理器提供了一个更友好的API,通常是通过实现在[embedded-hal]中定义的一些常用的traits来实现的。比如,这个crate可能提供一个`Serial`结构体,它的构造函数需要一组合适的GPIO端口和一个波特率,它为了发送数据提供一些 `write_byte` 函数。查看 [可移植性] 可以看到更多关于 [embedded-hal] 的信息。 * Board Crate(开发板crate) - 这些Crate通过预配置不同的外设和GPIO管脚再进行了一层抽象以适配你正在使用的特定的开发者工具或者开发板,比如对于STM32F3DISCOVERY开发板来说,是[stm32f3-discovery] @@ -31,7 +31,7 @@ ## Micro-architecture crate -让我们看一下对于所有的Cortex-M微控制器来说很常见的SysTick外设。我们能在[cortex-m] crate中找到一个相当底层的API,我们能像这样使用它: +让我们看一下SysTick外设,SysTick外设存在于所有的Cortex-M微控制器中。我们能在[cortex-m] crate中找到一个相当底层的API,我们能像这样使用它: ```rust,ignore #![no_std] @@ -90,7 +90,7 @@ pub fn init() -> (Delay, Leds) { ``` -我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器字段的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是有了更多的代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数,其所有可能的参数都有效(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 +我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器字段的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是有了更多的代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数其所有可能的参数都能发挥作用(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 ### 读取 @@ -136,7 +136,7 @@ pwm0.enable.write(temp); // 哦 不! 错误的变量! ## 使用一个HAL crate -一个芯片的HAL crate通常通过为PAC暴露的基础结构体们实现一个自定义Trait来发挥作用。经常这个trait将会为某个外设定义一个被称作 `constrain()` 的函数或者为像是有多个管脚的GPIO端口定义一个`split()`函数。这个函数将会使用基础外设结构体,然后返回一个具有更高抽象的API的新对象。这个API还可以做一些事,像是让Serial port的 `new` 函数变成需要某些`Clock`结构体的函数,这个结构体只能通过调用配置PLLs和设置所有的时钟频率的函数来生成。在这时,生成一个Serial port对象而不先配置时钟速率是不可能的,对于Serial port对象来说错误地将波特率转换为时钟滴答数也是不可能发生的。一些crates甚至为每个GPIO管脚的状态定义了特定的 traits,在把管脚传递进外设前,要求用户去把一个管脚设置成正确的状态(通过选择Alternate Function模式) 。所有这些都没有运行时开销的! +一个芯片的HAL crate通常通过为PAC暴露的基础结构体们实现一个自定义Trait来发挥作用。经常这个trait将会为某个外设定义一个被称作 `constrain()` 的函数或者为像是有多个管脚的GPIO端口这类东西定义一个`split()`函数。这个函数将会使用基础外设结构体,然后返回一个具有更高抽象的API的新对象。这个API还可以做一些事,像是让Serial port的 `new` 函数变成需要某些`Clock`结构体的函数,这个结构体只能通过调用配置PLLs并设置所有的时钟频率的函数来生成。在这时,生成一个Serial port对象而不先配置时钟速率是不可能的,对于Serial port对象来说错误地将波特率转换为时钟滴答数也是不可能发生的。一些crates甚至为每个GPIO管脚的状态定义了特定的 traits,在把管脚传递进外设前,要求用户去把一个管脚设置成正确的状态(通过选择Alternate Function模式) 。所有这些都没有运行时开销的! 让我们看一个例子: diff --git a/src/start/semihosting.md b/src/start/semihosting.md index e9d86deb..2de73235 100644 --- a/src/start/semihosting.md +++ b/src/start/semihosting.md @@ -1,6 +1,6 @@ # 半主机模式 -半主机模式是一种让嵌入式设备在主机上进行I/O操作的的机制,主要被用来记录信息到主机控制台上。半主机模式需要一个debug会话,除此之外几乎没有其它要求了,因此它非常易于使用。缺点是它非常慢:每个写操作需要几毫秒的时间,其取决于你的硬件调试器(e.g. ST-LINK)。 +半主机模式是一种可以让嵌入式设备在主机上进行I/O操作的的机制,主要被用来记录信息到主机控制台上。半主机模式需要一个debug会话,除此之外几乎没有其它要求了,因此它非常易于使用。缺点是它非常慢:每个写操作需要几毫秒的时间,其取决于你的硬件调试器(e.g. ST-LINK)。 [`cortex-m-semihosting`] crate 提供了一个API去在Cortex-M设备上执行半主机操作。下面的程序是"Hello, world!"的半主机版本。 From 2e99bc815edd7073e287dc287feb4c86f047ac7c Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 14 Oct 2022 15:56:53 +0800 Subject: [PATCH 113/137] minor --- src/SUMMARY.md | 2 +- src/peripherals/a-first-attempt.md | 20 ++++++++++---------- src/start/exceptions.md | 8 ++++---- src/start/interrupts.md | 6 +++--- src/start/registers.md | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c5a302fa..f7499fe7 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -28,7 +28,7 @@ more information and coordination - [中断](./start/interrupts.md) - [IO](./start/io.md) - [外设](./peripherals/index.md) - - [首次尝试](./peripherals/a-first-attempt.md) + - [Rust的首次尝试](./peripherals/a-first-attempt.md) - [借用检查器](./peripherals/borrowck.md) - [单例](./peripherals/singletons.md) - [静态保障(static guarantees)](./static-guarantees/index.md) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index dc574efb..36007d86 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -1,4 +1,4 @@ -# 首次尝试 +# Rust的首次尝试 ## 寄存器 @@ -8,10 +8,10 @@ | Offset | Name | Description | Width | |--------|-------------|-----------------------------|--------| -| 0x00 | SYST_CSR | Control and Status Register | 32 bits| -| 0x04 | SYST_RVR | Reload Value Register | 32 bits| -| 0x08 | SYST_CVR | Current Value Register | 32 bits| -| 0x0C | SYST_CALIB | Calibration Value Register | 32 bits| +| 0x00 | SYST_CSR | 控制和状态寄存器 | 32 bits| +| 0x04 | SYST_RVR | 重装载值寄存器 | 32 bits| +| 0x08 | SYST_CVR | 当前值寄存器 | 32 bits| +| 0x0C | SYST_CALIB | 校准值寄存器 | 32 bits| ## C语言风格的方法(The C Approach) @@ -26,14 +26,14 @@ struct SysTick { pub calib: u32, } ``` -限定符 `#[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 Accesses) +## volatile访问(Volatile Accesses) 现在,上面的方法有一堆问题。 @@ -42,7 +42,7 @@ let time = unsafe { (*systick).cvr }; 3. 你程序中任何地方的任何一段代码都可以通过这个结构体访问硬件。 4. 最重要的是,实际上它并不能工作。 -现在的问题是编译器很聪明。如果你往RAM同个地方写两次,一个接着一个,编译器会注意到这个行为,且完全跳过第一个写入操作。在C语言中,我们能标记变量为`volatile`去确保每个读或写操作按预期发生。在Rust中,我们将*访问* 标记为易变的(volatile),而不是将变量标记为volatile。 +现在的问题是编译器很聪明。如果你往RAM同个地方写两次,一个接着一个,编译器会注意到这个行为,且完全跳过第一个写入操作。在C语言中,我们能标记变量为`volatile`去确保每个读或写操作按预期发生。在Rust中,我们将*访问*操作标记为易变的(volatile),而不是将变量标记为volatile。 ```rust,ignore let systick = unsafe { &mut *(0xE000_E010 as *mut SysTick) }; @@ -77,7 +77,7 @@ fn get_time() -> u32 { ## Rust风格的封装 -我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于我们用户来说,可以安全地被调用。作为驱动的作者,我们手动地验证不安全的代码是否正确,然后为我们的用户提供一个safe的API,因此他们不必担心它(让他们相信我们做对了!)。 +我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于我们用户来说,可以安全地被调用。作为驱动的作者,我们亲手验证不安全的代码是否正确,然后为我们的用户提供一个safe的API,因此用户们不必担心它(让他们相信我们不会出错!)。 有可能有这样的例子: @@ -133,4 +133,4 @@ fn thread2() { } ``` -虽然 `set_reload` 函数的 `&mut self` 参数保证了对某个`SystemTimer`结构体的引用只有一个,但是他们不能阻止用户去创造第二个`SystemTimer`,其指向同个外设!如果作者足够尽力,能发现所有这些'重复的'驱动实例,那么按这种方式写的代码将可以工作,但是一旦代码被散播几天,散播到多个模块,驱动,开发者,它会越来越容易犯此类错误。 +虽然 `set_reload` 函数的 `&mut self` 参数保证了对某个`SystemTimer`结构体的引用只有一个,但是他们不能阻止用户去创造第二个`SystemTimer`,其指向同个外设!如果作者足够尽力,他能发现所有这些'重复的'驱动实例,那么按这种方式写的代码将可以工作,但是一旦代码被散播几天,散播到多个模块,驱动,开发者,它会越来越容易犯此类错误。 diff --git a/src/start/exceptions.md b/src/start/exceptions.md index 0c11c86b..29751b2d 100644 --- a/src/start/exceptions.md +++ b/src/start/exceptions.md @@ -1,6 +1,6 @@ # 异常 -异常和中断,是处理器用来处理异步事件和致命错误(e.g. 执行一个无效的指令)的一种硬件机制。异常意味着抢占且涉及到异常处理程序,即响应触发事件的信号的子例程。 +异常和中断,是处理器用来处理异步事件和致命错误(e.g. 执行一个无效的指令)的一种硬件机制。异常意味着抢占并涉及到异常处理程序,即响应触发事件的信号的子程序。 `cortex-m-rt` crate提供了一个 [`exception`] 属性去声明异常处理程序。 @@ -16,7 +16,7 @@ fn SysTick() { 除了 `exception` 属性,异常处理函数看起来和普通函数一样,但是有一个很大的不同: `exception` 处理函数 *不能* 被软件调用。在先前的例子中,语句 `SysTick();` 将会导致一个编译错误。 -这么做是有目的的,因为异常处理函数必须具有一个特性: 在异常处理函数中被声明`static mut`的变量能被安全(safe)地使用。 +这么做是故意的,因为异常处理函数必须具有一个特性: 在异常处理函数中被声明为`static mut`的变量能被安全(safe)地使用。 ``` rust,ignore #[exception] @@ -28,7 +28,7 @@ fn SysTick() { } ``` -就像你可能已经知道的那样,在一个函数里使用`static mut`变量,会让函数变成[*非可重入函数(non-reentrancy)*](https://en.wikipedia.org/wiki/Reentrancy_(computing))。从多个异常/中断处理函数,或者从`main`函数同多个异常/中断处理函数中,直接或者间接地调用一个非可重入(non-reentrancy)函数是未定义的行为。 +就像你可能已经知道的那样,在一个函数里使用`static mut`变量,会让函数变成[*非可重入函数(non-reentrancy)*](https://en.wikipedia.org/wiki/Reentrancy_(computing))。从多个异常/中断处理函数,或者从`main`函数和多个异常/中断处理函数中,直接或者间接地调用一个非可重入(non-reentrancy)函数是未定义的行为。 安全的Rust不能导致未定义的行为出现,所以非可重入函数必须被标记为 `unsafe`。然而,我刚说了`exception`处理函数能安全地使用`static mut`变量。这怎么可能?因为`exception`处理函数 *不* 能被软件调用因此重入(reentrancy)不会发生,所以这才变得可能。 @@ -127,7 +127,7 @@ fn DefaultHandler() { } ``` -这个函数是 `cortex-m-rt` crate提供的,且被标记为 `#[no_mangle]` 因此你能在 "DefaultHandler" 上放置一个断点和捕获 *unhandled* 异常。 +这个函数是 `cortex-m-rt` crate提供的,且被标记为 `#[no_mangle]` 因此你能在 "DefaultHandler" 上放置一个断点并捕获 *unhandled* 异常。 可以使用 `exception` 属性覆盖这个 `DefaultHandler`: diff --git a/src/start/interrupts.md b/src/start/interrupts.md index 5f220001..dc22b8b7 100644 --- a/src/start/interrupts.md +++ b/src/start/interrupts.md @@ -2,10 +2,10 @@ 虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎一样,且它们也能被同一个中断控制器处理。然而异常是由Cortex-M微架构定义的,中断在命名和功能上总是由特定厂商(经常甚至是芯片)实现的。 -中断提供了更多的灵活性,当尝试用一种高级的方法使用它们时,我们需要对这种灵活性进行解释。但我们将不会在这本书里涵盖那些内容,把下面的东西记在心里是个不错的想法: +中断提供了更多的灵活性,当尝试用一种高级的方法使用它们时,我们需要对这种灵活性进行解释。但我们将不会在这本书里涵盖这些内容,最好把下面的东西记在心里: * 中断有可以编程的优先级,其决定了它们的处理函数的执行顺序。 * 中断能嵌套且抢占,i.e. 一个中断处理函数的执行可以被其它更高优先级的中断打断。 -* 通常导致中断被触发的因素需要被清除,避免无限地再次进入中断处理函数。 +* 通常需要清除掉导致中断被触发的原因,避免无限地再次进入中断处理函数。 运行时的常规初始化步骤始终相同: * 设置外设在遇到想要的事故发生的时候产生中断请求 @@ -39,6 +39,6 @@ fn TIM2() { } ``` -关于这里说的机制的更多细节描述,请参考[异常章节]。 +关于这里所说的机制的更多细节描述,请参考[异常章节]。 [异常章节]: ./exceptions.md diff --git a/src/start/registers.md b/src/start/registers.md index 0535afcd..0ec67807 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -90,7 +90,7 @@ pub fn init() -> (Delay, Leds) { ``` -我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器字段的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是有了更多的代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数其所有可能的参数都能发挥作用(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 +我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器位段的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是有了更多的代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数其所有可能的参数都能发挥作用(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 ### 读取 From e44135e202fb0b6d349a1c2870ee5a8362f0d37d Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 14 Oct 2022 16:09:24 +0800 Subject: [PATCH 114/137] minor --- src/peripherals/borrowck.md | 2 +- src/peripherals/singletons.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index f8436bf7..2f3a21c7 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -16,4 +16,4 @@ 思考一下,我们是否可以分发这些外设的所有权,或者引用它们? -我们当然可以,但是对于借用检查器来说,每个外设我们都只需要一个实例的话,Rust才可以正确地处理这个实例。在硬件中幸运的是,任何给定的外设,只有一个实例,但是我们该如何将它暴露在我们代码的结构中呢? +我们当然可以,但是对于借用检查器来说,每个外设我们都只需要一个实例,因此Rust可以正确地处理这个实例。幸运的是,在硬件中,任何给定的外设,只有一个实例,但是我们该如何将它暴露在我们代码的结构中呢? diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 1a7a87f9..906f9c78 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -21,7 +21,7 @@ fn main() { } ``` -但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你的整个程序间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有者。 +但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你的整个程序之间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有者。 ## 我们在Rust中要怎么做? From a9c13cfd47c5a0758b0b7d08acfd93b03b058f64 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 15 Oct 2022 21:57:53 +0800 Subject: [PATCH 115/137] minor --- src/peripherals/singletons.md | 6 +++--- src/static-guarantees/index.md | 4 ++-- src/static-guarantees/typestate-programming.md | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 906f9c78..bd0e48f4 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -21,11 +21,11 @@ fn main() { } ``` -但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你的整个程序之间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有者。 +但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你所有的程序之间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有权。 ## 我们在Rust中要怎么做? -与其只是让我们的外设变成一个全局变量,我们不如决定使用一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,这个全局变量对于我们的每个外设,它都有一个与之对应的 `Option` 。 +与其只是让我们的外设变成一个全局变量,我们不如决定创造一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,这个全局变量对于我们的每个外设,它都有一个与之对应的 `Option` 。 ```rust,ignore struct Peripherals { @@ -54,7 +54,7 @@ fn main() { 虽然与这个结构体交互是`unsafe`,然而一旦我们获得了它包含的 `SerialPort`,我们将不再需要使用`unsafe`,或者`PERIPHERALS`结构体。 -这个带来了少量的运行时消耗,因为我们必须打包 `SerialPort` 结构体进一个option中,且我们将需要调用一次 `take_serial()`,但是这种少量的前期成本,能使我们在接下来的程序中使用借用检查器(borrow checker) 。 +这个带来了少量的运行时开销,因为我们必须打包 `SerialPort` 结构体进一个option中,且我们将需要调用一次 `take_serial()`,但是这种少量的前期成本,能使我们在接下来的程序中使用借用检查器(borrow checker) 。 ## 已存在的库支持 diff --git a/src/static-guarantees/index.md b/src/static-guarantees/index.md index cc12d035..ee10a4f0 100644 --- a/src/static-guarantees/index.md +++ b/src/static-guarantees/index.md @@ -1,11 +1,11 @@ # 静态保障 -Rust的类型系统可防止编译时的数据竞争(看[`Send`]和[`Sync`]特性(traits))。类型系统也能被用来在编译时检查其它属性;减少某些样例中运行时检查的需要。 +Rust的类型系统可防止编译时的数据竞争(看[`Send`]和[`Sync`]特性(traits))。类型系统也能被用来在编译时检查其它属性;减少某些案例中运行时检查的需要。 [`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来初始化一个串行接口,这个API只能通过先配置接口需要的管脚才能被正确地使用。 +当应用到嵌入式程序时,这些*静态检查*能被用来,比如,强制I/O接口的配置被恰当地执行了。例如,可以设计一个API来初始化一个串行接口,这个API只能通过先配置接口需要的管脚后才能被正确地使用。 也可以静态检查操作是否被执行在正确配置了的外设上,像是拉低一个管脚。例如尝试修改一个被配置成浮空输入模式的管脚的输出状态时,将会出发一个编译时错误。 diff --git a/src/static-guarantees/typestate-programming.md b/src/static-guarantees/typestate-programming.md index 161f7de3..1469dee3 100644 --- a/src/static-guarantees/typestate-programming.md +++ b/src/static-guarantees/typestate-programming.md @@ -1,6 +1,6 @@ # 类型状态编程(Typestate Programming) -[typestates]的概念描述了将有关对象当前状态的信息编码进该对象的类型中。虽然这听起来有点神秘,如果你在Rust中使用了[建造者模式],你就已经开始使用类型状态编程了! +[typestates]的概念是指将有关对象当前状态的信息编码进该对象的类型中。虽然这听起来有点神秘,如果你在Rust中使用了[建造者模式],你就已经开始使用类型状态编程了! [typestates]: https://en.wikipedia.org/wiki/Typestate_analysis [建造者模式]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html @@ -49,7 +49,7 @@ fn main() { } ``` -在这个例子里,不能直接生成一个`Foo`对象。我们必须创造一个`FooBuilder`,在我们获取我们需要的`Foo`对象之前恰当地初始化`FooBuilder`。 +在这个例子里,不能直接生成一个`Foo`对象。我们必须创造一个`FooBuilder`,且我们恰当地初始化`FooBuilder`后才能获取到我们需要的`Foo`对象。 这个最小的例子编码了两个状态: @@ -58,7 +58,7 @@ fn main() { ## 强类型 -因为Rust有一个[强类型系统],没有方法简单的,魔法般地创造一个`Foo`实例或者不用调用`into_foo()`方法把一个`FooBuilder`变成一个`Foo`。另外,调用`into_foo()`方法消费了最初的`FooBuilder`结构体,意味着不创造一个新的实例它就不能被再次使用。 +因为Rust有一个[强类型系统],没有简单的方法可以奇迹般地创造一个`Foo`实例或者不用调用`into_foo()`方法把一个`FooBuilder`变成一个`Foo`。另外,调用`into_foo()`方法消费了最初的`FooBuilder`结构体,意味着不创造一个新的实例它就不能被再次使用。 [强类型系统]: https://en.wikipedia.org/wiki/Strong_and_weak_typing From af9b018e3f64128f3e5bd19b4f39f9213e0f6b9f Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 27 Oct 2022 16:45:56 +0800 Subject: [PATCH 116/137] minor --- src/SUMMARY.md | 2 +- src/intro/hardware.md | 4 +-- src/intro/index.md | 12 ++++---- src/intro/no-std.md | 4 +-- src/peripherals/singletons.md | 4 +-- src/static-guarantees/state-machines.md | 2 +- .../zero-cost-abstractions.md | 6 ++-- src/unsorted/math.md | 6 ++-- src/unsorted/speed-vs-size.md | 30 +++++++++---------- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f7499fe7..f42f0f78 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -52,7 +52,7 @@ more information and coordination - [给Rust配上一点C](./interoperability/c-with-rust.md) - [给C配上一点Rust](./interoperability/rust-with-c.md) - [没有排序的主题](./unsorted/index.md) - - [优化: 速度与大小间的均衡](./unsorted/speed-vs-size.md) + - [优化: 速度与大小间的博弈](./unsorted/speed-vs-size.md) - [执行数学运算](./unsorted/math.md) --- diff --git a/src/intro/hardware.md b/src/intro/hardware.md index 5e022b25..8035ffb6 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -15,7 +15,7 @@ + 256 KiB的"Flash"存储。 + 48 KiB的RAM + 多种多样的外设,比如计时器,I2C,SPI和USART - + 通用GPIO和板子两侧的其它类型引脚 + + 通用GPIO和在板子两侧的其它类型的引脚 + 一个写着“USB USER”的USB接口 + 一个位于[LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html)芯片上的[加速度计](https://en.wikipedia.org/wiki/Accelerometer)。 @@ -23,7 +23,7 @@ + 一个位于[L3GD20](https://www.pololu.com/file/0J563/L3GD20.pdf)芯片上的[陀螺仪](https://en.wikipedia.org/wiki/Gyroscope). -+ 8个摆得像一个指南针形状的用户LEDs。 ++ 8个摆得像一个指南针形状的user LEDs。 + 一个二级微控制器: [STM32F103](https://www.st.com/en/microcontrollers/stm32f103cb.html)。这个微控制器实际上是一个板载编程器/调试器的一部分,与名为“USB ST-LINK”的USB端口相连。 diff --git a/src/intro/index.md b/src/intro/index.md index 676dfb73..052a1338 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -10,7 +10,7 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust + 分享那些关于使用Rust进行嵌入式开发的,现存的,最好的实践经验,比如,如何最大程度上地利用好Rust语言的特性去写更正确的嵌入式软件 + 某种程度下作为工具书,比如,如何在一个项目里将C和Rust混合在一起使用 -虽然这本书尝试尽可能地可以适用于大多数场景,但是为了使读者和作者更容易理解,在所有的案例中它都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个架构,书中会在需要时对这个架构的特定细节进行解释。 +虽然尽可能地尝试让这本书可以用于大多数场景,但是为了使读者和作者更容易理解,在所有的示例中这本书都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个架构,书中会在需要时对这个架构的特定细节进行解释。 ## 这本书是为谁准备的 @@ -44,7 +44,7 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust ### 翻译 -这本书是已经被一些慷慨的志愿者翻译了。如果你想要将你的翻译列在这里,请打开一个PR去添加它。 +这本书是已经被一些慷慨的志愿者们翻译了。如果你想要将你的翻译列在这里,请打开一个PR去添加它。 * [Japanese](https://tomoyuki-nakabayashi.github.io/book/) ([repository](/~https://github.com/tomoyuki-nakabayashi/book)) @@ -53,9 +53,9 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust ([repository](/~https://github.com/xxchang/book)) ## 如何使用这本书 -这本书通常假设你是前后阅读的。之后章节是建立在先前章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,因为在随后的章节将会再次重温这个主题。 -在大多数案例中这本书将使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的厂家之间是不同的,甚至来自同一个厂家,不同处理器系列之间也是不同的。 -因此我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。(译者注:我使用[renode](https://renode.io/about/)来测试大多数例子) +这本书通常假设你是前后阅读的。之后的章节是建立在先前的章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,因为在随后的章节将会再次重温这个主题。 +在大多数示例中这本书将使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的厂家之间是不同的,甚至来自同一个厂家,不同处理器系列之间也是不同的。 +因此我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。 [STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html @@ -66,7 +66,7 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust [这个仓库]: /~https://github.com/rust-embedded/book [resouces team]: /~https://github.com/rust-embedded/wg#the-resources-team -如果你按着这本书的操作遇到了什么麻烦,或者这本书的一些部分不够清楚,或者很难进行下去,那这本书就是有个bug,它应该被报道给这本书的[the issue tracker] 。 +如果你按着这本书的操作遇到了什么麻烦,或者这本书的一些部分不够清楚,或者很难进行下去,那这本书就是有个bug,这个bug应该被报道给这本书的[the issue tracker] 。 [the issue tracker]: /~https://github.com/rust-embedded/book/issues/ diff --git a/src/intro/no-std.md b/src/intro/no-std.md index a28ef35e..f612131a 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -1,11 +1,11 @@ # 一个 `no_std` Rust环境 -嵌入式编程这个词被广泛用于许多不同的编程场景中。小到RAM和ROM只有KB的8位机(像是[ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)),大到一个具有32/64位4核Cortex-A53和1GB的RAM的系统,比如树莓派([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications))。当编写代码时,取决于你的目标环境和用例,将会有不同的限制和局限。
+嵌入式编程这个词被广泛用于许多不同的编程场景中。小到RAM和ROM只有KB的8位机(像是[ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)),大到一个具有32/64位4核Cortex-A53和1GB RAM的系统,比如树莓派([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications))。当编写代码时,取决于你的目标环境和用例,将会有不同的限制和局限。
通常嵌入式编程有两类: ## 主机环境 -这类环境类似一个常见的PC环境。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。sysroot可能限制你使用RAM/ROM,可能还有一些特别的硬件或者I/O。总之感觉像是在专用的PC环境上编程一样。 +这类环境与一个常见的PC环境类似。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。sysroot可能限制你使用RAM/ROM,可能还有一些特别的硬件或者I/O。总之感觉像是在专用的PC环境上编程一样。 ## 裸机环境 diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index bd0e48f4..38eedcb3 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -58,7 +58,7 @@ fn main() { ## 已存在的库支持 -虽然我们在上面创造了我们自己的 `Peripherals` 结构体,但这并不是必须的。`cortex_m` crate 包含一个被叫做 `singleton!()` 的宏,其将为你执行这个任务。 +虽然我们在上面创造了我们自己的 `Peripherals` 结构体,但这并不是必须的。`cortex_m` crate 包含一个被叫做 `singleton!()` 的宏,其将为你完成这个任务。 ```rust,ignore #[macro_use(singleton)] @@ -73,7 +73,7 @@ fn main() { [cortex_m docs](https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html) -另外,如果你使用 [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic),其为你抽象了获取和定义这些外设的整个过程,你获得了一个`Peripherals`结构体,其包含了一个所有你定义了的项的非 `Option` 的版本。 +另外,如果你使用 [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic),它将获取和定义这些外设的整个过程抽象了出来,你将获得一个`Peripherals`结构体,其包含了一个所有你定义了的项的非 `Option` 的版本。 ```rust,ignore // cortex-m-rtic v0.5.x diff --git a/src/static-guarantees/state-machines.md b/src/static-guarantees/state-machines.md index 4ddad371..24933c1a 100644 --- a/src/static-guarantees/state-machines.md +++ b/src/static-guarantees/state-machines.md @@ -94,4 +94,4 @@ impl GpioConfig { 正常使用这个结构体将会允许我们访问我们上面的状态机没有定义的状态: e.g. 一个被上拉的输出,或者一个被拉高的输入。对于一些硬件,这并没有关系。对另外一些硬件来说,这将会导致不可预期或者没有定义的行为! -虽然这个接口很方便写入,但是它没有强制我们遵守硬件的实现所规定的设计协约。 +虽然这个接口很方便写入,但是它没有强制我们遵守为硬件的实现所规定的设计协约。 diff --git a/src/static-guarantees/zero-cost-abstractions.md b/src/static-guarantees/zero-cost-abstractions.md index 44252bd1..aaf4126c 100644 --- a/src/static-guarantees/zero-cost-abstractions.md +++ b/src/static-guarantees/zero-cost-abstractions.md @@ -33,10 +33,10 @@ pub fn into_input_high_z(self) -> GpioConfig { } ``` -我们返回的GpioConfig在运行时并不存在。调用这个函数通常将会被总结为一个单一的汇编指令 - 保存一个常量寄存器值进一个寄存器里。这意味着我们开发的类型状态接口是一个零成本抽象 - 它不会用更多的CPU,RAM,或者代码空间去跟踪`GpioConfig`的状态,会渲染成和直接访问寄存器一样的机器码。 +我们返回的GpioConfig在运行时并不存在。对这个函数的调用通常会被归纳为一条汇编指令 - 保存一个常量寄存器值进一个寄存器里。这意味着我们开发的类型状态接口是一个零成本抽象 - 它不会用更多的CPU,RAM,或者代码空间去跟踪`GpioConfig`的状态,会渲染成和直接访问寄存器一样的机器码。 ## 嵌套 -通常,你可能会把这些抽象深深地嵌套起来。一旦所有的被使用的组件是零大小类型,整个结构体将不会在运行时存在。 +通常,你可能会把这些抽象深深地嵌套起来。一旦结构体使用的所有的组件是零大小类型的,整个结构体将不会在运行时存在。 -对于复杂或者深度嵌套的结构体,定义所有可能的状态组合可能很乏味。在这些案例中,宏可能能被用来产生所有的实现。 +对于复杂或者深度嵌套的结构体,定义所有可能的状态组合可能很乏味。在这些例子中,宏能被用来生成所有的实现。 diff --git a/src/unsorted/math.md b/src/unsorted/math.md index 60101c78..63dccb75 100644 --- a/src/unsorted/math.md +++ b/src/unsorted/math.md @@ -1,6 +1,6 @@ # 在`#[no_std]`下执行数学运算 -如果你想要执行数学相关的功能,像是计算平方根或者一个数的指数且你有完整的标准库支持,你的代码可能看起来像这个: +如果你想要执行数学相关的函数,像是计算平方根或者一个数的指数且你有完整的标准库支持,你的代码可能看起来像这个: ```rs //! 可用一些标准支持的数学函数 @@ -24,7 +24,7 @@ fn main() { } ``` -没有标准库支持的时候,这些函数不可用。反而像是[`libm`](https://crates.io/crates/libm)这样一个外部库可以被使用。示例的代码将会看起来像这个: +没有标准库支持的时候,这些函数不可用。反而可以使用像是[`libm`](https://crates.io/crates/libm)这样一个外部库。示例的代码将会看起来像这样: ```rs #![no_main] @@ -62,7 +62,7 @@ fn main() -> ! { } ``` -如果你需要在你的MCU上执行更复杂的操作,像是DSP信号处理或者更高级的线性代数,下列的crates可能可以帮助你 +如果你需要在你的MCU上执行更复杂的操作,像是DSP信号处理或者更高级的线性代数,下列的crates可能可以帮到你 - [CMSIS DSP library binding](/~https://github.com/jacobrosenthal/cmsis-dsp-sys) - [`micromath`](/~https://github.com/tarcieri/micromath) diff --git a/src/unsorted/speed-vs-size.md b/src/unsorted/speed-vs-size.md index f06bcfdf..c56c4640 100644 --- a/src/unsorted/speed-vs-size.md +++ b/src/unsorted/speed-vs-size.md @@ -1,8 +1,8 @@ -# 优化: 速度与大小之间的均衡 +# 优化: 速度与大小之间的博弈 -每个人都想要它们的程序变得超级快且超级小,但是同时满足这两个条件是不可能的。这部分讨论`rustc`提供的不同的优化等级,和它们是如何影响执行时间和一个程序的二进制文件大小。 +每个人都想要它们的程序变得超级快且超级小,但是同时满足这两个条件是不可能的。这部分讨论`rustc`提供的不同的优化等级,和它们是如何影响执行时间和一个程序的二进制项的大小。 -## 不优化 +## 无优化 这是默认的。当你调用`cargo build`时,你使用的是development(又叫`dev`)配置。这个配置优化的目的是为了调试,因此它使能了调试信息且*关闭*了所有优化,i.e. 它使用 `-C opt-level = 0` 。 @@ -10,15 +10,15 @@ ``` toml [profile.release] -# 符号很好且它们不会增加Flash上的大小 +# 调试符号很好且它们不会增加Flash上的大小 debug = true ``` -不优化对于调试来说是最好的,因为单步调试代码感觉像是你正在逐条语句地执行程序,且你能在GDB中`print`栈变量和函数参数。当代码被优化了,尝试打印变量会导致`$0 = `被打印出来。 +无优化对于调试来说是最好的选择,因为单步调试代码感觉像是你正在逐条语句地执行程序,且你能在GDB中`print`栈变量和函数参数。当代码被优化了,尝试打印变量会导致`$0 = `被打印出来。 -`dev`配置最大的缺点就是最终的二进制文件将会变得巨大且缓慢。大小通常是一个更大的问题,因为未优化的二进制文件会占据大量KiB的Flash,你的目标设备可能没这么多Flash -- 结果: 你未优化的二进制文件无法烧录进你的设备中! +`dev`配置最大的缺点就是最终的二进制项将会变得巨大且缓慢。大小通常是一个更大的问题,因为未优化的二进制项会占据大量KiB的Flash,你的目标设备可能没这么多Flash -- 结果: 你未优化的二进制项无法烧录进你的设备中! -我们可以有更小的,调试友好的二进制文件吗?是的,这里有一个技巧。 +我们可以有更小的,调试友好的二进制项吗?是的,这里有一个技巧。 ### 优化依赖 @@ -85,15 +85,15 @@ opt-level = "z" 自2018-09-18开始 `rustc` 支持三个 "优化速度" 的等级: `opt-level = 1`, `2` 和 `3` 。当你运行 `cargo build --release` 时,你正在使用的是release配置,其默认是 `opt-level = 3` 。 -`opt-level = 2` 和 `3` 都以二进制文件大小为代价优化速度,但是等级`3`比等级`2`做了更多的向量化和内联。特别是,你将看到在`opt-level`等于或者大于`2`时LLVM将展开循环。循环展开在 Flash / ROM 方面的成本相当高(e.g. from 26 bytes to 194 for a zero this array loop)但是如果条件合适(迭代次数足够大),也可以将执行时间减半。 +`opt-level = 2` 和 `3` 都以二进制项大小为代价优化速度,但是等级`3`比等级`2`做了更多的向量化和内联。特别是,你将看到在`opt-level`等于或者大于`2`时LLVM将展开循环。循环展开在 Flash / ROM 方面的成本相当高(e.g. from 26 bytes to 194 for a zero this array loop)但是如果条件合适(迭代次数足够大),也可以将执行时间减半。 -现在还没有办法在`opt-level = 2`和`3`的情况下关闭循环展开,因此如果你不能接受它的开销,你应该优化你程序的尺寸。 +现在还没有办法在`opt-level = 2`和`3`的情况下关闭循环展开,因此如果你不能接受它的开销,你应该选择优化你的程序的大小。 -## 优化尺寸 +## 优化大小 -自2018-09-18开始`rustc`支持两个"优化尺寸"的等级: `opt-level = "s"` 和 `"z"` 。这些名字传承自 clang / LLVM 且不具有描述性,但是`"z"`意味着它产生的二进制文件比`"s"`更小。 +自2018-09-18开始`rustc`支持两个"优化大小"的等级: `opt-level = "s"` 和 `"z"` 。这些名字传承自 clang / LLVM 且不具有描述性,但是`"z"`意味着它产生的二进制文件比`"s"`更小。 -如果你想要发布一个优化了尺寸的二进制文件,那么改变下面展示的`Cargo.toml`中的`profile.release.opt-level`配置。 +如果你想要发布一个优化了大小的二进制项,那么改变下面展示的`Cargo.toml`中的`profile.release.opt-level`配置。 ``` toml [profile.release] @@ -101,9 +101,9 @@ opt-level = "z" opt-level = "s" ``` -这两个优化等级极大地减少了LLVM的内联阈值,一个用来决定是否内联或者不内联一个函数的度量。Rust其中一个概念是零成本抽象;这些抽象趋向于去使用许多新类型和小函数去保持不变量(e.g. 像是`deref`,`as_ref`这样借用内部值的函数)因此一个低内联阈值会使LLVM失去优化的机会(e.g. 去掉死分支(dead branches),内联对闭包的调用)。 +这两个优化等级极大地减小了LLVM的内联阈值,一个用来决定是否内联或者不内联一个函数的度量。Rust其中一个概念是零成本抽象;这些抽象趋向于去使用许多新类型和小函数去保持不变量(e.g. 像是`deref`,`as_ref`这样借用内部值的函数)因此一个低内联阈值会使LLVM失去优化的机会(e.g. 去掉死分支(dead branches),内联对闭包的调用)。 -当优化尺寸时,你可能想要尝试增加增加内联阈值去观察是否会对你的二进制文件的大小有影响。推荐的改变内联阈值的方法是在`.cargo/config.toml`中往其它rustflags后插入`-C inline-threshold` 。 +当优化大小时,你可能想要尝试增加内联阈值去观察是否会对你的二进制项的大小有影响。推荐的改变内联阈值的方法是在`.cargo/config.toml`中往其它rustflags后插入`-C inline-threshold` 。 ``` toml # .cargo/config.toml @@ -124,4 +124,4 @@ rustflags = [ - `opt-level = "s"` 使用 75 - `opt-level = "z"` 使用 25 -当优化尺寸时,你应该尝试`225`和`275` 。 +当优化大小时,你应该尝试`225`和`275` 。 From 44643af0325773706361ef44d31d3d03bbdb6e36 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 19 Nov 2022 08:47:02 +0800 Subject: [PATCH 117/137] minor --- src/intro/hardware.md | 6 +++--- src/intro/index.md | 20 ++++++++++---------- src/intro/install/linux.md | 2 +- src/intro/no-std.md | 12 ++++++------ src/intro/tooling.md | 18 +++++++++--------- src/start/hardware.md | 18 +++++++++--------- src/start/qemu.md | 32 ++++++++++++++++---------------- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/intro/hardware.md b/src/intro/hardware.md index 8035ffb6..47ad66a0 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -1,6 +1,6 @@ # 熟悉你的硬件 -让我们来熟悉下我们将使用的硬件。 +先来熟悉下我们要用的硬件。 ## STM32F3DISCOVERY (the "F3") @@ -27,6 +27,6 @@ + 一个二级微控制器: [STM32F103](https://www.st.com/en/microcontrollers/stm32f103cb.html)。这个微控制器实际上是一个板载编程器/调试器的一部分,与名为“USB ST-LINK”的USB端口相连。 -关于所列举的功能的更多细节和开发板的更多规格请看向[STMicroelectronics](https://www.st.com/en/evaluation-tools/stm32f3discovery.html)网站。 +关于所列举的功能的更多细节和开发板的更多规格请查阅[STMicroelectronics](https://www.st.com/en/evaluation-tools/stm32f3discovery.html)网站。 -提醒一句: 如果你想要为板子提供外部信号,请小心。微控制器STM32F303VCT6管脚的标称电压是3.3伏。更多信息请查看[6.2 Absolute maximum ratings section in the manual](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf)。 +提醒一句: 如果想要为板子提供外部信号,请小心。微控制器STM32F303VCT6管脚的标称电压是3.3伏。更多信息请查看[6.2 Absolute maximum ratings section in the manual](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf)。 diff --git a/src/intro/index.md b/src/intro/index.md index 052a1338..f9513634 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -1,31 +1,31 @@ # 引言 -欢迎阅读The Embedded Rust Book:一本介绍如何在裸机(比如,微处理器)上使用Rust编程语言的书籍。 +欢迎阅读嵌入式Rust:一本关于如何在裸机(比如,微处理器)上使用Rust编程语言的入门书籍。 -## Embedded Rust 是为谁准备的 -Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust语言所提供的高级语言概念和安全保障的人们而准备的(也可以看 [Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) +## 嵌入式Rust是为谁准备的 +嵌入式Rust是为了那些即想要进行嵌入式编程,又想要使用Rust语言所提供的高级概念和安全保障的人们而准备的(参见[Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) -## 范围 +## 本书范围 这本书的目的是: + 让开发者快速上手Rust嵌入式开发,比如,如何设置一个开发环境。 + 分享那些关于使用Rust进行嵌入式开发的,现存的,最好的实践经验,比如,如何最大程度上地利用好Rust语言的特性去写更正确的嵌入式软件 + 某种程度下作为工具书,比如,如何在一个项目里将C和Rust混合在一起使用 -虽然尽可能地尝试让这本书可以用于大多数场景,但是为了使读者和作者更容易理解,在所有的示例中这本书都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个架构,书中会在需要时对这个架构的特定细节进行解释。 +虽然尽可能地尝试让这本书可以用于大多数场景,但是为了使读者和作者更容易理解,在所有的示例中,这本书都使用了ARM Cortex-M架构。然而,这本书并不需要读者熟悉这个架构,书中会在需要时对这个架构的特定细节进行解释。 ## 这本书是为谁准备的 -这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对Rust嵌入式编程好奇的人都能从这本书中得到某些收获。对于那些先前没有任何经验的人,我们建议你读一下“要求和预备知识”部分。从其它资料中获取、补充缺失的知识,这样能提高你的阅读体验。你可以看看“其它资源”部分,以找到你感兴趣的那些主题的资源。 +这本书适合那些有一些嵌入式背景或者有Rust背景的人,然而我相信每一个对Rust嵌入式编程好奇的人都能从这本书中获得某些收获。对于那些先前没有任何经验的人,我们建议你读一下“要求和预备知识”部分。从其它资料中获取、补充缺失的知识,这样能提高你的阅读体验。你可以看看“其它资源”部分,以找到你感兴趣的那些主题的资源。 ### 要求和预备知识 -+ 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你也应该要熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 ++ 你可以轻松地使用Rust编程语言,且在一个桌面环境上写过,运行过,调试过Rust应用。你应该也要熟悉[2018 edition]的术语,因为这本书是面向Rust 2018的。 [2018 edition]: https://doc.rust-lang.org/edition-guide/ + 你可以轻松地使用其它语言,比如C,C++或者Ada,开发和调试嵌入式系统,且熟悉如下的概念: + 交叉编译 + 存储映射的外设(Memory Mapped Peripherals) + 中断 - + I2C,SPI,串口等等常见接口 + + I2C,SPI,串口等等常见的接口 ### 其它资源 @@ -46,10 +46,10 @@ Embedded Rust是为了那些即想要进行嵌入式编程,又想要使用Rust 这本书是已经被一些慷慨的志愿者们翻译了。如果你想要将你的翻译列在这里,请打开一个PR去添加它。 -* [Japanese](https://tomoyuki-nakabayashi.github.io/book/) +* [日文](https://tomoyuki-nakabayashi.github.io/book/) ([repository](/~https://github.com/tomoyuki-nakabayashi/book)) -* [Chinese](https://xxchang.github.io/book/) +* [中文](https://xxchang.github.io/book/) ([repository](/~https://github.com/xxchang/book)) ## 如何使用这本书 diff --git a/src/intro/install/linux.md b/src/intro/install/linux.md index 52ab8f0a..abf61319 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -112,7 +112,7 @@ user::rw- user:you:rw- ``` -权限后的 `+` 指出存在一个扩展权限。`getfacl` 命令显示,`user`也就是`你`能使用这个设备。 +权限后的 `+` 指出存在一个扩展权限。`getfacl` 命令显示,`user`也就是`你`,可以使用这个设备。 现在,去往[下个章节]. diff --git a/src/intro/no-std.md b/src/intro/no-std.md index f612131a..298c32b2 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -3,13 +3,13 @@ 嵌入式编程这个词被广泛用于许多不同的编程场景中。小到RAM和ROM只有KB的8位机(像是[ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)),大到一个具有32/64位4核Cortex-A53和1GB RAM的系统,比如树莓派([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications))。当编写代码时,取决于你的目标环境和用例,将会有不同的限制和局限。
通常嵌入式编程有两类: -## 主机环境 +## 主机环境下 -这类环境与一个常见的PC环境类似。意味着向你提供了一个系统接口[E.G. POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现它们的功能。sysroot可能限制你使用RAM/ROM,可能还有一些特别的硬件或者I/O。总之感觉像是在专用的PC环境上编程一样。 +这类环境与一个常见的PC环境类似。意味着向你提供了一个系统接口[比如 POSIX](https://en.wikipedia.org/wiki/POSIX),使你能和不同的系统进行交互,比如文件系统,网络,内存管理,进程,等等。标准库相应地依赖这些接口去实现了它们的功能。可能有某种sysroot并限制了对RAM/ROM的使用,可能还有一些特别的硬件或者I/O。总之感觉像是在专用的PC环境上编程一样。 -## 裸机环境 +## 裸机环境下 -在一个裸机环境中,你的程序被加载前,不存在代码。没有系统提供的软件,我们不能加载标准库。相反地,该程序,以及它使用的crates只能使用硬件(裸机)去运行。使用`no-std`可以防止rust读取标准库。标准库中与平台无关的部分在[libcore](https://doc.rust-lang.org/core/)中。libcore剔除了那些在一个嵌入式环境中非必要的东西。比如用于动态分配的内存分配器。如果你需要这些或者其它的某些功能,通常会有提供这些功能的crates。 +在一个裸机环境中,程序被加载前,环境中不存在代码。没有系统提供的软件,我们不能加载标准库。相反地,程序和它使用的crates只能使用硬件(裸机)去运行。使用`no-std`可以防止rust读取标准库。标准库中与平台无关的部分在[libcore](https://doc.rust-lang.org/core/)中。libcore剔除了那些在一个嵌入式环境中非必要的东西。比如用于动态分配的内存分配器。如果你需要这些或者其它的某些功能,通常会有提供这些功能的crates。 ### libstd运行时 @@ -30,11 +30,11 @@ | libcore available | ✓ | ✓ | | writing firmware, kernel, or bootloader code | ✓ | ✘ | -\* 只有在你使用了 `alloc` crate 和设置了一个适合的分配器,比如[alloc-cortex-m]后有效。 +\* 只有在你使用了 `alloc` crate 并设置了一个适合的分配器后,比如[alloc-cortex-m]后有效。 \** 只有在你使用了 `collections` crate 并配置了一个全局默认的分配器后有效。 [alloc-cortex-m]: /~https://github.com/rust-embedded/alloc-cortex-m -## See Also +## 参见 * [RFC-1184](/~https://github.com/rust-lang/rfcs/blob/master/text/1184-stabilize-no_std.md) diff --git a/src/intro/tooling.md b/src/intro/tooling.md index f1e346af..a402e2f3 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -5,29 +5,29 @@ - [`qemu-system-arm`](https://www.qemu.org/). 测试的版本: 3.0.0 - OpenOCD >=0.8. 测试的版本: v0.9.0 and v0.10.0 - 有ARM支持的GDB。强烈建议7.12或者更新的版本。测试版本: 7.10, 7.11 和 8.1 -- [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) 或者 `git` - -这些工具都是可选的,但是跟着书一起来使用它们,会更容易。下面的文档将解释我们为什么使用这些工具。安装指令可以在下一页找到。 +- [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) 或者 `git`。这些工具都是可选的,但是跟着这本书来使用它们,会更容易。 +- +- 下面的文档将解释我们为什么使用这些工具。安装指令可以在下一页找到。 ## `cargo-generate` 或者 `git` -裸机编程是非标准 Rust 编程,为了得到正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进了一个模板里,你只需要补充缺失的信息(比如项目名和你的目标硬件的特性)。
+裸机编程是非标准Rust编程,为了得到正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进了一个模板里了,你只需要补充缺失的信息(比如项目名和目标硬件的特性)。
我们的模板兼容`cargo-generate`:一个用来从模板生成新的Cargo项目的Cargo子命令。你也能使用`git`,`curl`,`wget`,或者你的网页浏览器下载模板。 ## `cargo-binutils` `cargo-binutils`是一个Cargo命令的子集,它让我们能轻松使用Rust工具链带来的LLVM工具。这些工具包括LLVM版本的`objdump`,`nm`和`size`,用来查看二进制文件。
-在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工具是相同的一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具,支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 +在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工都可以用同一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具,支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 ## `qemu-system-arm` QEMU是一个仿真器。在这个例子里,我们使用能完全仿真ARM系统的改良版QEMU。我们使用QEMU在主机上运行嵌入式程序。多亏了它,你可以在没有任何硬件的情况下,尝试这本书的部分示例。 ## GDB -调试器是嵌入式开发的一个非常重要的组件,你不可能总是可以将内容记录到主机控制台上。在某些情况里,你甚至可能都没法用LEDs去点亮你的硬件!
-通常,提到调试,LLDB和GDB的功能一样,但是我们还没找到与GDB的`load`命令对应的LLDB命令,这条命令负责将程序上传到目标硬件,因此我们现在推荐你使用GDB。 +调试器是嵌入式开发的一个非常重要的组件,你不可能总是可以将内容打印到主机控制台上。在某些情况里,你甚至可能都没法用LEDs去点亮你的硬件!
+通常,提到调试,LLDB和GDB的功能一样,但是我们还没找到与GDB的`load`命令对应的LLDB命令,这条命令负责将程序上传到目标硬件,因此目前我们推荐使用GDB。 ## OpenOCD -GDB不能直接和你的STM32F3DISCOVERY开发板上的ST-Link调试硬件通信。它需要一个转译者,the Open On-Chip Debugger,OpenOCD就是这样的转译者。OpenOCD是一个运行在你的笔记本/个人电脑上的程序,它在基于远程调试的TCP/IP协议和ST-Link的USB协议间进行转译。 -OpenOCD也执行其它一些转译中的重要的工作,这些工作用于调试你的STM32FDISCOVERY开发板上的ARM Cortex-M微控制器: +GDB不能直接和你的STM32F3DISCOVERY开发板上的ST-Link调试硬件通信。它需要一个转译者,the Open On-Chip Debugger,OpenOCD就是这样的转译者。OpenOCD是一个运行在笔记本/个人电脑上的程序,它在基于远程调试的TCP/IP协议和ST-Link的USB协议间进行转译。 +OpenOCD也负责转译中一些其它的重要的工作,这些工作用于调试你的STM32FDISCOVERY开发板上的ARM Cortex-M微控制器: * 它知道如何与ARM CoreSight调试外设使用的内存映射的寄存器进行交互。这些CoreSight寄存器的功能有: * 断点操作 * 读取和写入CPU寄存器 diff --git a/src/start/hardware.md b/src/start/hardware.md index 4751d9f9..5894f4a3 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -1,6 +1,6 @@ # 硬件 -现在你应该有点熟悉工具和开发过程了。在这部分我们将切换到真正的硬件上;步骤非常相似。让我们深入进去。 +现在你应该有点熟悉工具和开发过程了。在这部分我们将切换到真正的硬件上;步骤非常相似。让我们深入下去。 ## 认识你的硬件 @@ -12,14 +12,14 @@ 你可以在你的设备的数据手册和参考手册上找到这些信息。 -这部分,我们会用我们的参考硬件,STM32F3DISCOVERY。这个板子包含一个STM32F303VCT6微控制器。这个微控制器拥有: +这部分,我们将使用我们的参考硬件,STM32F3DISCOVERY。这个板子包含一个STM32F303VCT6微控制器。这个微控制器拥有: - 一个Cortex-M4F核心,它包含一个单精度FPU。 - 位于 0x0800_0000 地址的256KiB的Flash。 - 位于 0x2000_0000 地址的40KiB的RAM。(这里还有其它的RAM区域,但是为了方便起见,我们将忽略它)。 ## 配置 -我们将从一个新的模板实例开始。参考[先前的QEMU]章节,了解如何在没有`cargo-generate`的情况下完成它。 +我们将使用一个新的模板实例从零开始。对于新手参考[先前的QEMU]章节,了解如何在没有`cargo-generate`的情况下完成它。 [先前的QEMU]: qemu.md @@ -60,11 +60,11 @@ MEMORY RAM : ORIGIN = 0x20000000, LENGTH = 40K } ``` -> **注意**:如果你因为一些理由,在对某个编译目标首次编译后,改变了`memory.x`文件,需要在`cargo build`之前执行`cargo clean`。因为`cargo build`可能不会追踪`memory.x`的更新。 +> **注意**:如果你因为某些理由,在对某个编译目标首次编译后,改变了`memory.x`文件,需要在`cargo build`之前执行`cargo clean`。因为`cargo build`可能不会跟踪`memory.x`的更新。 -我们将使用hello示例再次开始,但是首先我们必须做一个小改变。 +我们将再次使用hello示例作为开始,但是首先我们必须做一个小改变。 -在`examples/hello.rs`中,确保`debug::exit()`调用被注释掉了或者移除。它只能用于在QEMU中运行的情况。 +在`examples/hello.rs`中,确保`debug::exit()`调用被注释掉了或者移除掉了。它只能用于在QEMU中运行的情况。 ```rust,ignore #[entry] @@ -79,7 +79,7 @@ fn main() -> ! { } ``` -你可以像你之前做的一样,使用`cargo build`检查编译程序,使用`cargo-binutils`观察二进制项。`cortex-m-rt`库可以处理所有运行芯片所需的魔法,几乎所有的Cortex-M CPUs都按同样的方式启动,这同样有用。 +你可以像你之前做的一样,使用`cargo build`检查编译程序,使用`cargo-binutils`观察二进制项。`cortex-m-rt`库可以处理所有让芯片运行起来所需的魔法,几乎所有的Cortex-M CPUs都按同样的方式启动。 ``` console cargo build --example hello @@ -87,7 +87,7 @@ cargo build --example hello ## 调试 -调试将看起来有点不同。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。对于设备,关于调试有关的信息,可以看[the Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 +调试会看起来有点不一样。事实上,取决于不同的目标设备,第一步可能看起来不一样。在这个章节里,我们将展示,调试一个在STM32F3DISCOVERY上运行的程序,所需要的步骤。这作为一个参考。关于调试有关的设备特定的信息,可以看[the Debugonomicon](/~https://github.com/rust-embedded/debugonomicon)。 像之前一样,我们将进行远程调试,客户端将是一个GDB进程。不同的是,OpenOCD将是服务器。 @@ -116,7 +116,7 @@ source [find interface/stlink.cfg] source [find target/stm32f3x.cfg] ``` -> **注意** 如果你在[安装验证]章节中,发现你的discovery开发板是一个更旧的版本,那么你应该修改你的 `openocd.cfg` 文件,让它去使用 `interface/stlink-v2.cfg` 。注释掉 `interface/stlink.cfg` +> **注意** 如果你在[安装验证]章节中,发现你的discovery开发板是一个更旧的版本,那么你应该修改你的 `openocd.cfg` 文件,注释掉 `interface/stlink.cfg`,让它去使用 `interface/stlink-v2.cfg` 。 ``` text $ openocd diff --git a/src/start/qemu.md b/src/start/qemu.md index b2da8c65..1c3b08c5 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -1,10 +1,10 @@ # QEMU -我们将开始为[LM3S6965]编写程序,一个Cortex-M3微控制器。我们选择这个作为我们的第一个目标,因为它能使用[QEMU仿真](https://wiki.qemu.org/Documentation/Platforms/ARM#Supported_in_qemu-system-arm),因此本节中,你不需要摆弄硬件,我们注意力可以集中在工具和开发过程上。 +我们将开始为[LM3S6965]编写程序,一个Cortex-M3微控制器。因为它能使用[QEMU仿真](https://wiki.qemu.org/Documentation/Platforms/ARM#Supported_in_qemu-system-arm),所以我们选择它作为我们的第一个目标,本节中,不需要使用硬件,我们注意力可以集中在工具和开发过程上。 [LM3S6965]: http://www.ti.com/product/LM3S6965 **重要** -在这个引导里,我们将使用"app"这个名字来代指项目名。无论何时你看到单词"app",你应该用你选择的项目名来替代"app"。或者你也可以选择把你的项目命名为"app",避免替代。 +在这个引导里,我们将使用"app"这个名字来代指项目名。无论何时你看到单词"app",你应该用你选择的项目名来替代"app"。或者你也可以选择把你的项目命名为"app",避免要替换掉。 ## 生成一个非标准的 Rust program 我们将使用[`cortex-m-quickstart`]项目模板来生成一个新项目。生成的项目将包含一个最基本的应用:对于一个新的嵌入式rust应用来说,是一个很好的开始。另外,项目将包含一个`example`文件夹,文件夹中有许多独立的应用,突出了一些关键的嵌入式rust的功能。 @@ -74,7 +74,7 @@ cd app ## 项目概览 -为了便利,这是`src/main.rs`中源码最重要的部分。 +这是`src/main.rs`中源码最重要的部分。 ```rust,ignore #![no_std] @@ -92,7 +92,7 @@ fn main() -> ! { } ``` -这个程序与标准Rust程序有一点不同,因此让我们走近点看看。 +这个程序与标准Rust程序有一点不同,让我们走近点看看。 `#![no_std]`指出这个程序将 *不会* 链接标准crate`std`。反而它将会链接到它的子集: `core` crate。 @@ -109,7 +109,7 @@ fn main() -> ! { ## 交叉编译 -下一步是为Cortex-M3架构*交叉*编译程序。如果你知道编译目标(`$TRIPLE`)应该是什么,那就和运行`cargo build --target $TRIPLE`一样简单。幸运地,模板中的`.cargo/config.toml`有这个答案: +下一步是为Cortex-M3架构*交叉*编译程序。如果你知道编译目标(`$TRIPLE`)应该是什么,运行`cargo build --target $TRIPLE`就可以了。幸运地,模板中的`.cargo/config.toml`有这个答案: ```console tail -n6 .cargo/config.toml ``` @@ -121,7 +121,7 @@ target = "thumbv7m-none-eabi" # Cortex-M3 # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) # target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) ``` -为了交叉编译Cortex-M3架构我们不得不使用`thumbv7m-none-eabi`。当安装Rust工具时,target不会自动被安装,如果你还没有做,现在可以去添加那个target到工具链上。 +为了交叉编译Cortex-M3架构我们不得不使用`thumbv7m-none-eabi`。当安装Rust工具时,target不会自动被安装,如果还没有添加,现在可以去添加那个target到工具链上。 ``` console rustup target add thumbv7m-none-eabi ``` @@ -133,7 +133,7 @@ cargo build ## 检查 -现在我们在`target/thumbv7m-none-eabi/debug/app`中有一个非主机环境的ELF二进制文件。我们能使用`cargo-binutils`检查它。 +现在在`target/thumbv7m-none-eabi/debug/app`中有一个非主机环境的ELF二进制文件。我们能使用`cargo-binutils`检查它。 使用`cargo-readobj`我们能打印ELF头,确认这是一个ARM二进制。 @@ -142,8 +142,8 @@ cargo readobj --bin app -- --file-headers ``` 注意: -* `--bin app` 是一个用来检查`target/$TRIPLE/debug/app`的语法糖 -* `--bin app` 如果有需要,也会重新编译二进制项。 +* `--bin app` 是一个用来查看二进制项`target/$TRIPLE/debug/app`的语法糖 +* `--bin app` 需要时也会重新编译二进制项。 ``` text ELF Header: @@ -174,7 +174,7 @@ ELF Header: cargo size --bin app --release -- -A ``` -我们使用`--release`检查优化的版本 +我们使用`--release`查看优化后的版本 ``` text app : @@ -207,7 +207,7 @@ Total 14570 > - `.vector_table` 是一个我们用来存储向量(中断)表的*非*标准的section > - `.ARM.attributes` 和 `.debug_*` sections包含元数据,当烧录二进制文件时,它们不会被加载到目标上的。 -**重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*没有正确地反应了程序被烧录到设备上时将占据的空间的大小。要*一直*使用`cargo-size`检查一个二进制项的大小。 +**重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*没有正确地反应处程序被烧录到设备上时将占据的空间的大小。要*一直*使用`cargo-size`检查一个二进制项的大小。 `cargo-objdump` 能用来反编译二进制项。 @@ -287,9 +287,9 @@ fn main() -> ! { } ``` -这个程序使用一些被叫做semihosting的东西去打印文本到主机调试台上。当使用的是真实的硬件时,这需要一个调试对话,但是当使用的是QEMU时这就可以工作了。 +这个程序使用被叫做semihosting的东西去打印文本到主机调试台上。当使用的是真实的硬件时,需要一个调试对话这个程序才能工作,但是当使用的是QEMU时这就可以工作了。 -通过编译示例,让我们开始 +让我们开始编译示例 ```console cargo build --example hello @@ -353,13 +353,13 @@ Hello, world! ``` ## 调试 -对于嵌入式开发来说,调试非常重要。让我们来看下如何完成它。 +对于嵌入式开发来说,调试非常重要。让我们来看下如何调试它。 因为我们想要调试的程序所运行的机器上并没有运行一个调试器程序(GDB或者LLDB),所以调试一个嵌入式设备就涉及到了 *远程* 调试 远程调试涉及一个客户端和一个服务器。在QEMU的情况中,客户端将是一个GDB(或者LLDM)进程且服务器将会是运行着嵌入式程序的QEMU进程。 -在这部分,我们将使用我们已经编译的 `hello` 示例。 +在这部分,我们要使用我们已经编译的 `hello` 示例。 调试的第一步是在调试模式中启动QEMU: @@ -403,7 +403,7 @@ Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473 >`core::num::bignum::Big32x40::mul_small () at src/libcore/num/bignum.rs:254` > ` src/libcore/num/bignum.rs: No such file or directory.` > -> 那是一个已知的小bug,你可以安全地忽略这些警告,你非常大可能已经Reset()了。 +> 那是一个已知的小bug,你可以安全地忽略这些警告,你非常大可能已经进入Reset()了。 这个reset句柄最终将调用我们的主函数,让我们使用一个断点和`continue`命令跳过所有的步骤。为了设置断点,让我们首先看下我们想要在我们代码哪里打断点,使用`list`指令 From 1ea1d3ac8c976b838764dbdbbdb31efc6afa48de Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 19 Nov 2022 10:53:35 +0800 Subject: [PATCH 118/137] minor --- src/start/exceptions.md | 14 +++++++------- src/start/hardware.md | 2 +- src/start/interrupts.md | 8 ++++---- src/start/panicking.md | 8 ++++---- src/start/registers.md | 28 ++++++++++++++-------------- src/start/semihosting.md | 4 ++-- 6 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/start/exceptions.md b/src/start/exceptions.md index 29751b2d..653640f9 100644 --- a/src/start/exceptions.md +++ b/src/start/exceptions.md @@ -16,7 +16,7 @@ fn SysTick() { 除了 `exception` 属性,异常处理函数看起来和普通函数一样,但是有一个很大的不同: `exception` 处理函数 *不能* 被软件调用。在先前的例子中,语句 `SysTick();` 将会导致一个编译错误。 -这么做是故意的,因为异常处理函数必须具有一个特性: 在异常处理函数中被声明为`static mut`的变量能被安全(safe)地使用。 +这么做是有目的的,因为异常处理函数必须具有一个特性: 在异常处理函数中被声明为`static mut`的变量能被安全(safe)地使用。 ``` rust,ignore #[exception] @@ -32,11 +32,11 @@ fn SysTick() { 安全的Rust不能导致未定义的行为出现,所以非可重入函数必须被标记为 `unsafe`。然而,我刚说了`exception`处理函数能安全地使用`static mut`变量。这怎么可能?因为`exception`处理函数 *不* 能被软件调用因此重入(reentrancy)不会发生,所以这才变得可能。 -> 注意,`exception`属性,通过将静态变量封装进`unsafe`块中且为我们提供了名字相同的,类型为 `&mut` 的,新的合适的变量,转换了函数中静态变量的定义。因此我们可以通过 `*` 解引用访问变量的值而不需要将它们打包进一个 `unsafe` 块中。 +> 注意,`exception`属性,通过将静态变量封装进`unsafe`块中并为我们提供了名字相同的,类型为 `&mut` 的,新的合适的变量,转换了函数中静态变量的定义。因此我们可以通过 `*` 解引用访问变量的值而不需要将它们打包进一个 `unsafe` 块中。 -## 一个复杂的例子 +## 一个完整的例子 -这里有个例子,使用系统计时器大概每秒会抛出一个 `SysTick` 异常。异常处理函数使用 `COUNT` 变量追踪它自己被调用了多少次,然后使用半主机模式(semihosting)打印 `COUNT` 的值到主机控制台上。 +这里有个例子,使用系统计时器大概每秒抛出一个 `SysTick` 异常。异常处理函数使用 `COUNT` 变量追踪它自己被调用了多少次,然后使用半主机模式(semihosting)打印 `COUNT` 的值到主机控制台上。 > **注意**: 你能在任何Cortex-M设备上运行这个例子;你也能在QEMU运行它。 @@ -89,7 +89,7 @@ fn SysTick() { } // 重要信息 如果运行在真正的硬件上,去掉这个 `if` 块, - // 否则你的调试器将会以一种不一样的状态结束 + // 否则你的调试器将会以一种不一致的状态结束 if *COUNT == 9 { // 这将终结QEMU进程 debug::exit(debug::EXIT_SUCCESS); @@ -142,13 +142,13 @@ fn DefaultHandler(irqn: i16) { ## 硬错误(Hard Fault)处理函数 -`HardFault`异常有点特别。当程序进入一个无法工作的状态时,这个异常被触发,因此它的处理函数 *不能* 返回,因为这么做可能导致一个未定义的行为。在用户定义的 `HardFault` 处理函数被调用之前,运行时crate还做了一些工作去提供可调试性。 +`HardFault`异常有点特别。当程序进入一个无法工作的状态时,这个异常被触发,因此它的处理函数 *不能* 返回,因为这么做可能导致一个未定义的行为。在用户定义的 `HardFault` 处理函数被调用之前,运行时crate还做了一些工作以改进调试功能。 结果是,`HardFault`处理函数必须有下列的签名: `fn(&ExceptionFrame) -> !` 。处理函数的参数是一个指针,它指向被异常推入栈中的寄存器。这些寄存器是异常被触发那刻,处理器状态的一个记录,能被用来分析一个硬错误。 这里有个执行不合法操作的案例: 读取一个不存在的存储位置。 -> **注意**: 这个程序在QEMU上将不会工作,i.e. 它将不会崩溃,因为 `qemu-system-arm -machine lm3s6965evb` 不对读取存储的操作进行检查,且读取无效存储时将会开心地返回 `0`。 +> **注意**: 这个程序在QEMU上不能起作用,i.e. 它不会崩溃,因为 `qemu-system-arm -machine lm3s6965evb` 不对读取存储的操作进行检查,且读取无效存储时将会开心地返回 `0`。 ```rust,ignore #![no_main] diff --git a/src/start/hardware.md b/src/start/hardware.md index 5894f4a3..3e978040 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -12,7 +12,7 @@ 你可以在你的设备的数据手册和参考手册上找到这些信息。 -这部分,我们将使用我们的参考硬件,STM32F3DISCOVERY。这个板子包含一个STM32F303VCT6微控制器。这个微控制器拥有: +这部分,要使用我们的参考硬件,STM32F3DISCOVERY。这个板子包含一个STM32F303VCT6微控制器。这个微控制器拥有: - 一个Cortex-M4F核心,它包含一个单精度FPU。 - 位于 0x0800_0000 地址的256KiB的Flash。 - 位于 0x2000_0000 地址的40KiB的RAM。(这里还有其它的RAM区域,但是为了方便起见,我们将忽略它)。 diff --git a/src/start/interrupts.md b/src/start/interrupts.md index dc22b8b7..b10b66e8 100644 --- a/src/start/interrupts.md +++ b/src/start/interrupts.md @@ -1,13 +1,13 @@ # 中断 -虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎一样,且它们也能被同一个中断控制器处理。然而异常是由Cortex-M微架构定义的,中断在命名和功能上总是由特定厂商(经常甚至是芯片)实现的。 +虽然中断和异常在很多方面都不一样,但是它们的操作和使用几乎是一样的,且它们也能被同一个中断控制器处理。然而异常是由Cortex-M微架构定义的,中断在命名和功能上总是由特定厂商(经常甚至是芯片)实现的。 中断提供了更多的灵活性,当尝试用一种高级的方法使用它们时,我们需要对这种灵活性进行解释。但我们将不会在这本书里涵盖这些内容,最好把下面的东西记在心里: * 中断有可以编程的优先级,其决定了它们的处理函数的执行顺序。 * 中断能嵌套且抢占,i.e. 一个中断处理函数的执行可以被其它更高优先级的中断打断。 * 通常需要清除掉导致中断被触发的原因,避免无限地再次进入中断处理函数。 -运行时的常规初始化步骤始终相同: +运行时的初始化步骤总是相同的: * 设置外设在遇到想要的事故发生的时候产生中断请求 * 在中断控制器中设置需要的中断处理函数的优先级 * 在中断控制器中使能中断处理函数 @@ -25,9 +25,9 @@ fn TIM2() { } ``` -中断处理函数和异常处理函数一样看起来像是普通的函数(除了没有入参)。然而由于特殊的调用规定,它不能被固件的其它部分直接调用。但是,可以在软件中生成中断请求,触发一个控制权的转移。 +中断处理函数和异常处理函数一样看起来像是普通的函数(除了没有入参)。然而由于特殊的调用规定,它不能被固件的其它部分直接调用。然而,可以在软件中生成中断请求,转移到中断处理函数中。 -与异常处理函数一样,它也能在中断处理函数中声明`static mut`变量且保持 *safe* 状态。 +与异常处理函数一样,也能在中断处理函数中声明`static mut`变量且保持 *safe* 状态。 ``` rust,ignore #[interrupt] diff --git a/src/start/panicking.md b/src/start/panicking.md index e6b60e50..d2decf60 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -2,7 +2,7 @@ 运行时恐慌是Rust语言的一个核心部分。像是索引这样的內建的操作为了存储安全性是运行时检查的。当尝试越界索引时,这会导致运行时恐慌(panic)。 -在标准库中,运行时恐慌的行为被定义了:它展开(unwinds)恐慌的线程的栈,除非用户选择在恐慌时终止程序。 +在标准库中,运行时恐慌的行为被定义成:展开(unwinds)恐慌的线程的栈,除非用户选择在恐慌时终止程序。 然而在没有标准库的程序中,运行时恐慌的行为是未被定义了的。通过声明一个 `#[painc_handler]` 函数可以选择一个运行时恐慌的行为。 @@ -22,11 +22,11 @@ [`panic-itm`]: https://crates.io/crates/panic-itm [`panic-semihosting`]: https://crates.io/crates/panic-semihosting -在 crates.io 上搜索 [`panic-handler`],你甚至能找到更多的crates。 +在crates.io上搜索 [`panic-handler`],你甚至可以找到更多的crates。 [`panic-handler`]: https://crates.io/keywords/panic-handler -仅仅通过链接到相关的crate中,一个程序就可以从这些行为中选择一个运行时恐慌行为。将运行时恐慌的行为作为一行代码放进一个应用的源码中,不仅仅是可以作为文档使用,而且能根据编译配置改变运行时恐慌的行为。比如: +仅仅通过链接到相关的crate中,一个程序就可以简单地从这些行为中选择一个运行时恐慌行为。将运行时恐慌的行为作为一行代码放进一个应用的源码中,不仅仅是因为可以作为文档使用,而且能根据编译配置改变运行时恐慌的行为。比如: ``` rust,ignore #![no_main] @@ -45,7 +45,7 @@ use panic_abort as _; 在这个例子里,当使用dev配置编译的时候(`cargo build`),crate链接到 `panic-halt` crate上,但是当使用release配置编译时(`cargo build --release`),crate链接到`panic-abort` crate上。 -> `use panic_abort as _` 形式的 `use` 语句被用来确保 `panic_abort` 运行时恐慌函数被包含进我们最终的可执行程序里,同时让编译器清楚地知道我们不会从这个crate显式地使用任何东西。没有 `_` 重命名,编译器将会警告我们有一个未使用的导入。有时候你可能会看到 `extern crate panic_abort`,这是Rust 2018之前的版本使用的更旧的写法,现在应该只被用于 "sysroot" crates (与Rust一起发布的crates),比如 `proc_macro`,`alloc`,`std` 和 `test` 。 +> `use panic_abort as _` 形式的 `use` 语句,被用来确保 `panic_abort` 运行时恐慌函数被包含进我们最终的可执行程序里,同时让编译器清楚地知道我们不会从这个crate显式地使用任何东西。没有 `_` 重命名,编译器将会警告我们有一个未使用的导入。有时候你可能会看到 `extern crate panic_abort`,这是Rust 2018之前的版本使用的更旧的写法,现在应该只被用于 "sysroot" crates (与Rust一起发布的crates),比如 `proc_macro`,`alloc`,`std` 和 `test` 。 ## 一个例子 diff --git a/src/start/registers.md b/src/start/registers.md index 0ec67807..e46f5f8d 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,17 +1,17 @@ # 存储映射的寄存器 -只有通过执行常规的Rust代码并在RAM间移动数据,嵌入式系统才能继续运行下去。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 +嵌入式系统想要继续执行下去,只有通过执行常规的Rust代码并在RAM间移动数据才行。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 -你可能会发现,访问你的微控制器外设所需要的代码,已经被写进了下面的某个抽象层中了。 +你可能会发现,访问你的微控制器外设所需要的代码,已经存在于下面的某个抽象层中了。

-* Micro-architecture Crate(微架构Crate) - 这个库拥有任何对于微控制器的处理器内核来说经常会用到的程序,也包括对于在这些微控制器中的通用外设来说有用的程序。比如 [cortex-m] crate提供给你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个外设功能。 -* Peripheral Access Crate(PAC)(外设访问Crate) - 这种crate是在被不同存储器封装的寄存器上再进行的一次封装,寄存器由你正在使用的微控制器的产品号定义的。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,你将根据你的微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 -* HAL Crate - 这些crates为你的处理器提供了一个更友好的API,通常是通过实现在[embedded-hal]中定义的一些常用的traits来实现的。比如,这个crate可能提供一个`Serial`结构体,它的构造函数需要一组合适的GPIO端口和一个波特率,它为了发送数据提供一些 `write_byte` 函数。查看 [可移植性] 可以看到更多关于 [embedded-hal] 的信息。 -* Board Crate(开发板crate) - 这些Crate通过预配置不同的外设和GPIO管脚再进行了一层抽象以适配你正在使用的特定的开发者工具或者开发板,比如对于STM32F3DISCOVERY开发板来说,是[stm32f3-discovery] +* Micro-architecture Crate(微架构库) - 这个库拥有任何对于微控制器的处理器内核来说经常会用到的程序,也包括在这些微控制器中的通用外设。比如 [cortex-m] crate提供给你可以使能和关闭中断的函数,其对于所有的Cortex-M微控制器都是一样的。它也提供你访问'SysTick'外设的能力,在所有的Cortex-M微控制器中都包括了这个外设功能。 +* Peripheral Access Crate(PAC)(外设访问库) - 这个库是对各种存储器封装的寄存器再进行的一次浅陋封装,特定于所使用的微控制器的产品号。比如,[tm4c123x]针对TI的Tiva-C TM4C123系列,[stm32f30x]针对ST的STM32F30x系列。这块,根据微控制器的技术手册写的每个外设操作指令,直接和寄存器交互。 +* HAL Crate - 这些crates为你的处理器提供了一个更友好的API,通常是通过实现在[embedded-hal]中定义的一些常用的traits来实现的。比如,这个crate可能提供一个`Serial`结构体,它的构造函数需要一组合适的GPIO端口和一个波特率,它为发送数据提供了 `write_byte` 函数。查看 [可移植性] 可以看到更多关于 [embedded-hal] 的信息。 +* Board Crate(开发板库) - 这些Crate通过预配置不同的外设和GPIO管脚再进行了一层抽象以适配你正在使用的特定的开发者工具或者开发板,比如对于STM32F3DISCOVERY开发板来说,是[stm32f3-discovery] [cortex-m]: https://crates.io/crates/cortex-m [tm4c123x]: https://crates.io/crates/tm4c123x @@ -23,7 +23,7 @@ ## 开发板Crate (Board Crate) -如果你是嵌入式Rust新手,board crate是一个完美的开始。它们很好地抽象出了,在开始学习这个项目时,需要耗费心力了解的硬件细节,使得标准工作变得简单,像是打开或者关闭LED。不同的板子间,它们提供的功能变化很大。因为这本书是不假设我们使用的是何种板子,所以这本书不会提到board crate。 +如果你是嵌入式Rust新手,board crate是一个完美的开始。它们很好地抽象出了,在开始学习这个项目时,需要耗费心力了解的硬件细节,使得标准工作,像是打开或者关闭LED,变得简单。不同的板子间,它们提供的功能变化很大。因为这本书是不假设我们使用的是何种板子,所以这本书不会提到board crate。 如果你想要用STM32F3DISCOVERY开发板做实验,强烈建议看一下[stm32f3-discovery]开发板crate,它提供了点亮LEDs,访问它的指南针,蓝牙和其它的功能。[Discovery]书对于一个board crate的用法提供一个很好的介绍。 @@ -90,9 +90,9 @@ pub fn init() -> (Delay, Leds) { ``` -我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器位段的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是有了更多的代码了,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数其所有可能的参数都能发挥作用(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊含义),则该函数被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 +我们访问 `PWM0` 外设的方法和我们之前访问 `SYST` 的方法一样,除了我们调用的是 `tm4c123x::Peripherals::take()` 之外。因为这个crate是使用[svd2rust]自动生成的,访问我们寄存器位段的函数的参数是一个闭包,而不是一个数值参数。虽然这看起来像是有了更多的代码,但是Rust编译器能使用这个闭包为我们执行一系列检查,且产生的机器码十分接近手写的汇编码!如果自动生成的代码不能确保某个访问函数其所有可能的参数都能发挥作用(比如,如果寄存器被SVD定义为32位,但是没有说明某些32位值是否有特殊作用),那么该函数需要被标记为 `unsafe` 。我们能在上面看到这样的例子,我们使用 `bits()` 函数设置 `load` 和 `compa` 子域。 -### 读取 +### Reading `read()` 函数返回一个对象,这个对象提供了对这个寄存器中不同子域的只读访问,由厂商提供的这个芯片的SVD文件定义。在 [tm4c123x documentation][tm4c123x documentation R] 中你能找到在这个特别的返回类型 `R` 上所有可用的函数,其与特定芯片中的特定外设的特定寄存器有关。 @@ -102,16 +102,16 @@ if pwm.ctl.read().globalsync0().is_set() { } ``` -### 写入 +### Writing -`write()`函数使用一个只有一个参数的闭包。通常我们把这个参数叫做 `w`。然后这个参数提供对这个寄存器中不同的子域的读写访问,由厂商关于这个芯片的SVD文件提供。再一次,在 [tm4c123x documentation][tm4c123x documentation W] 中你能找到 `W` 所有可用的函数,其与特定芯片中的特定外设的特定寄存器有关。注意所有我们没有设置的子域将会被设置一个默认值 - 任何在这个寄存器中的现存的内容将会丢失。 +`write()`函数使用一个只有一个参数的闭包。通常我们把这个参数叫做 `w`。然后这个参数提供对这个寄存器中不同的子域的读写访问,由厂商关于这个芯片的SVD文件提供。再一次,在 [tm4c123x documentation][tm4c123x documentation W] 中你能找到 `W` 所有可用的函数,其与特定芯片中的特定外设的特定寄存器有关。注意所有我们没有设置的子域将会被设置成一个默认值 - 任何在这个寄存器中的现存的内容将会丢失。 ```rust,ignore pwm.ctl.write(|w| w.globalsync0().clear_bit()); ``` -### 修改 +### Modifying 如果我们希望只改变这个寄存器中某个特定的子域而让其它子域不变,我们能使用`modify`函数。这个函数使用一个具有两个参数的闭包 - 一个用来读取,一个用来写入。通常我们分别称它们为 `r` 和 `w` 。 `r` 参数能被用来查看这个寄存器现在的内容,`w` 参数能被用来修改寄存器的内容。 @@ -119,7 +119,7 @@ pwm.ctl.write(|w| w.globalsync0().clear_bit()); pwm.ctl.modify(|r, w| w.globalsync0().clear_bit()); ``` -`modify` 函数在这里真正展示了闭包的能量。在C中,我们不得不读取一些临时值,修改成正确的比特,然后再把值写回。这意味着出现错误的范围非常大。 +`modify` 函数在这里真正展示了闭包的能量。在C中,我们经常需要读取一些临时值,修改成正确的比特,然后再把值写回。这意味着出现错误的范围非常大。 ```C uint32_t temp = pwm0.ctl.read(); @@ -136,7 +136,7 @@ pwm0.enable.write(temp); // 哦 不! 错误的变量! ## 使用一个HAL crate -一个芯片的HAL crate通常通过为PAC暴露的基础结构体们实现一个自定义Trait来发挥作用。经常这个trait将会为某个外设定义一个被称作 `constrain()` 的函数或者为像是有多个管脚的GPIO端口这类东西定义一个`split()`函数。这个函数将会使用基础外设结构体,然后返回一个具有更高抽象的API的新对象。这个API还可以做一些事,像是让Serial port的 `new` 函数变成需要某些`Clock`结构体的函数,这个结构体只能通过调用配置PLLs并设置所有的时钟频率的函数来生成。在这时,生成一个Serial port对象而不先配置时钟速率是不可能的,对于Serial port对象来说错误地将波特率转换为时钟滴答数也是不可能发生的。一些crates甚至为每个GPIO管脚的状态定义了特定的 traits,在把管脚传递进外设前,要求用户去把一个管脚设置成正确的状态(通过选择Alternate Function模式) 。所有这些都没有运行时开销的! +一个芯片的HAL crate是通过为PAC暴露的基础结构体们实现一个自定义Trait来发挥作用的。经常这个trait将会为某个外设定义一个被称作 `constrain()` 的函数,或者为像是有多个管脚的GPIO端口这类东西定义一个`split()`函数。这个函数将会使用基础的外设结构体,然后返回一个具有更高抽象的API的新对象。这个API还可以做一些事,比如让Serial port的 `new` 函数变成需要某个`Clock`结构体的函数,这个结构体只能通过调用配置PLLs并设置所有的时钟频率的函数来生成。在这时,生成一个Serial port对象而不先配置时钟速率是不可能的,对于Serial port对象来说错误地将波特率转换为时钟滴答数也是不会发生的。一些crates甚至为每个GPIO管脚的状态定义了特定的 traits,在把管脚传递进外设前,要求用户去把一个管脚设置成正确的状态(通过选择Alternate Function模式) 。所有这些都没有运行时开销的! 让我们看一个例子: diff --git a/src/start/semihosting.md b/src/start/semihosting.md index 2de73235..57ea2d24 100644 --- a/src/start/semihosting.md +++ b/src/start/semihosting.md @@ -47,7 +47,7 @@ $ cargo run Hello, world! ``` -`exit`半主机操作也能被用于终止QEMU进程。重要:**不要**在硬件上使用`debug::exit`;这个函数会关闭你的OpenOCD对话,这样你将不能执行其它程序调试操作,除了重启它。 +`exit`半主机操作也能被用于终止QEMU进程。重要:**不要**在硬件上使用`debug::exit`;这个函数会关闭你的OpenOCD对话,这样你就不能执行其它的程序调试操作了,除了重启它。 ```rust,ignore #![no_main] @@ -80,7 +80,7 @@ $ echo $? 1 ``` -最后一个提示:你可以将运行时恐慌(panicking)的行为设置成 `exit(EXIT_FAILURE)`。这将允许你编写可以在QEMU上运行通过的 `no_std` 测试。 +最后一个提示:你可以将运行时恐慌(panicking)的行为设置成 `exit(EXIT_FAILURE)`。这会允许你编写可以在QEMU上运行通过的 `no_std` 测试。 为了方便,`panic-semihosting` crate有一个 "exit" 特性。当它使能的时候,在主机stderr上打印恐慌(painc)信息后会调用 `exit(EXIT_FAILURE)` 。 From 99eecc2174d7e3a9fdcd0c560e793ac373398dad Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 19 Nov 2022 21:23:03 +0800 Subject: [PATCH 119/137] minor --- src/SUMMARY.md | 2 +- src/peripherals/a-first-attempt.md | 26 +++++++++++++------------- src/peripherals/index.md | 18 +++++++++--------- src/start/registers.md | 2 +- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f42f0f78..fd5ccef9 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -28,7 +28,7 @@ more information and coordination - [中断](./start/interrupts.md) - [IO](./start/io.md) - [外设](./peripherals/index.md) - - [Rust的首次尝试](./peripherals/a-first-attempt.md) + - [Rust尝鲜](./peripherals/a-first-attempt.md) - [借用检查器](./peripherals/borrowck.md) - [单例](./peripherals/singletons.md) - [静态保障(static guarantees)](./static-guarantees/index.md) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 36007d86..fc8daf8c 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -1,8 +1,8 @@ -# Rust的首次尝试 +# Rust尝鲜 ## 寄存器 -让我们看向 '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 @@ -15,7 +15,7 @@ ## C语言风格的方法(The C Approach) -在Rust中,我们可以像我们在C语言中做的那样,用一个 `struct` 表示一组寄存器。 +在Rust中,我们可以像C语言一样,用一个 `struct` 表示一组寄存器。 ```rust,ignore #[repr(C)] @@ -26,7 +26,7 @@ struct SysTick { pub calib: u32, } ``` -限定符 `#[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; @@ -37,18 +37,18 @@ let time = unsafe { (*systick).cvr }; 现在,上面的方法有一堆问题。 -1. 每次我们想要访问我们的外设,我们不得不使用unsafe 。 -2. 我们无法指定哪个寄存器是只读的或者读写的。 -3. 你程序中任何地方的任何一段代码都可以通过这个结构体访问硬件。 +1. 每次想要访问外设,不得不使用unsafe 。 +2. 无法指定哪个寄存器是只读的或者读写的。 +3. 程序中任何地方的任何一段代码都可以通过这个结构体访问硬件。 4. 最重要的是,实际上它并不能工作。 -现在的问题是编译器很聪明。如果你往RAM同个地方写两次,一个接着一个,编译器会注意到这个行为,且完全跳过第一个写入操作。在C语言中,我们能标记变量为`volatile`去确保每个读或写操作按预期发生。在Rust中,我们将*访问*操作标记为易变的(volatile),而不是将变量标记为volatile。 +现在的问题是编译器很聪明。如果你往RAM同个地方写两次,一个接着一个,编译器会注意到这个行为,且完全跳过第一个写入操作。在C语言中,我们能标记变量为`volatile`去确保每个读或写操作按所想的那样发生。在Rust中,我们将*访问*操作标记为易变的(volatile),而不是将变量标记为volatile。 ```rust,ignore let systick = unsafe { &mut *(0xE000_E010 as *mut SysTick) }; let time = unsafe { core::ptr::read_volatile(&mut systick.cvr) }; ``` -因此,我们已经修复了我们四个问题中的一个,但是现在我们有了更多的 `unsafe` 代码!幸运的是,有个第三方的crate可以帮助到我们 - [`volatile_register`] +这样,我们已经修复了一个问题,但是现在我们有了更多的 `unsafe` 代码!幸运的是,有个第三方的crate可以帮助到我们 - [`volatile_register`] [`volatile_register`]: https://crates.io/crates/volatile_register @@ -73,13 +73,13 @@ fn get_time() -> u32 { } ``` -现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件有一堆可变的状态,对于编译器来说没有方法去知道是否这些写操作是真正安全的,因此默认就这样是个不错的选择。 +现在通过`read`和`write`方法,volatile accesses可以被自动执行。执行写操作仍然是 `unsafe` 的,但是公平地讲,硬件有一堆可变的状态,对于编译器来说没有办法知道是否这些写操作是真正安全的,因此默认就这样是个不错的选择。 ## Rust风格的封装 -我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于我们用户来说,可以安全地被调用。作为驱动的作者,我们亲手验证不安全的代码是否正确,然后为我们的用户提供一个safe的API,因此用户们不必担心它(让他们相信我们不会出错!)。 +我们需要把这个`struct`封装进一个更高抽象的API中,这个API对于用户来说,可以安全地调用。作为驱动的作者,我们亲手验证不安全的代码是否正确,然后为我们的用户提供一个safe的API,因此用户们不必担心它(让他们相信我们不会出错!)。 -有可能有这样的例子: +有可能可以这样写: ```rust,ignore use volatile_register::{RW, RO}; @@ -133,4 +133,4 @@ fn thread2() { } ``` -虽然 `set_reload` 函数的 `&mut self` 参数保证了对某个`SystemTimer`结构体的引用只有一个,但是他们不能阻止用户去创造第二个`SystemTimer`,其指向同个外设!如果作者足够尽力,他能发现所有这些'重复的'驱动实例,那么按这种方式写的代码将可以工作,但是一旦代码被散播几天,散播到多个模块,驱动,开发者,它会越来越容易犯此类错误。 +虽然 `set_reload` 函数的 `&mut self` 参数保证了没有引用到其它的`SystemTimer`结构体,但是不能阻止用户去创造第二个`SystemTimer`,其指向同个外设!如果作者足够努力的话,他能发现所有这些'重复的'驱动实例,那么按这种方式写的代码就可以工作,但是一旦代码被散播一段时间,散播给多个模块,驱动,开发者,它会越来越容易触发此类错误。 diff --git a/src/peripherals/index.md b/src/peripherals/index.md index 179d6d25..2a4530d8 100644 --- a/src/peripherals/index.md +++ b/src/peripherals/index.md @@ -2,28 +2,28 @@ ## 什么是外设? -大多数微处理器不仅有一个CPU,RAM,或者Flash存储器 - 它们包含部分被用来与微处理器的外部系统交互的硅片,通过传感器,电机控制器,或者人机接口比如一个显示器或者键盘直接和间接地与它们周围的世界交互。这些组件统称为外设。 +大多数微处理器不仅仅有一个CPU,RAM,或者Flash存储器 - 它们还包含被用来与微处理器的外部系统进行交互的硅片部分,通过传感器,电机控制器,或者人机接口比如一个显示器或者键盘直接和间接地与周遭世界交互。这些组件统称为外设。 这些外设很有用,因为它们允许一个开发者将处理工作交给它们来做,避免了必须在软件中处理每件事。就像一个桌面开发者如何将图形处理工作让给一个显卡那样,嵌入式开发者能将一些任务让给外设去做,让CPU可以把时间放在做其它更重要的事上,或者为了省电啥事也不做。 -如果你看向从1970s或者1980s的旧型号的家庭电脑的主板(其实,昨日的桌面PCs与今日的嵌入式系统没太大区别),你将看到: +如果你看向来自1970s或者1980s的旧型号的家庭电脑的主板(其实,昨日的桌面PCs与今日的嵌入式系统没太大区别),你将看到: * 一个处理器 * 一个RAM芯片 * 一个ROM芯片 * 一个I/O控制器 -RAM芯片,处理器将会通过一系列并行的迹(traces)又被称为一个"总线"将ROM芯片和I/O控制器(这个系统中的外设)加进来。这个总线搬运地址信息,其用来选择处理器希望跟总线上哪个设备通信,还有一个用来搬运实际数据的数据总线。在我们的嵌入式微控制器中,应用了相同的概念 - 只是所有的东西被打包到一片硅片上。 +RAM芯片,ROM芯片和I/O控制器(这个系统中的外设)会通过一系列并行的迹(traces)又被称为一个"总线"被加进处理器中。地址总线搬运地址信息,其用来选择处理器希望跟总线上哪个设备通信,还有一个用来搬运实际数据的数据总线。在我们的嵌入式微控制器中,应用了相同的概念 - 只是所有的东西被打包到一片硅片上。 然而,不像显卡,显卡通常有像是Vulkan,Metal,或者OpenGL这样的一个软件API。外设暴露给微控制器的是一个硬件接口,其被映射到一块存储区域。 -## 线性的真实存储空间 +## 线性的物理存储空间 -在一个微控制器上,往一些别的任意地址写一些数据,比如 `0x4000_0000` 或者 `0x0000_0000`,可能也是一个完全有效的动作。 +在一个微控制器上,随便往一些地址写一些数据,比如 `0x4000_0000` 或者 `0x0000_0000`,可能也是一个完全有效的动作。 -在一个桌面系统上,访问内存被MMU,或者内存管理单元紧紧地控制着。这个组件有两个主要责任: 对部分内存加入访问权限(防止一个进程读取或者修改另一个进程的内存);重映射物理内存的段到软件中使用的虚拟内存范围上。微控制器通常没有一个MMU,反而在软件中只使用真实的物理地址。 +在一个桌面系统上,访问内存被MMU,或者内存管理单元紧紧地控制着。这个组件有两个主要责任: 对部分内存加入访问权限(防止一个进程读取或者修改另一个进程的内存);重映射物理内存的段到软件中使用的虚拟内存范围上。微控制器通常没有一个MMU,反而在软件中只使用真实的物理地址。 -虽然32位微控制器有一个从`0x0000_0000`到`0xFFFF_FFFF`的真实的线性的地址空间,但是它们通常只使用几百KiB的实际内存。有相当大部分的地址空间遗留。在早期的章节中,我们说到RAM被放置在地址`0x2000_0000`处。如果我们的RAM是64KiB大小(i.e. 最大地址为0xFFFF),那么地址 `0x2000_0000`到`0x2000_FFFF`与我们的RAM有关。当我们写入一个位于地址`0x2000_1234`的变量时,内部发生的是,一些逻辑发现了地址的上部(这个例子里是0x2000),然后激活RAM,因此它能对地址的下部(这个例子里是0x1234)起作用。在一个Cortex-M上,我们也把我们的Flash ROM映射进地址 `0x000_0000` 到地址 `0x0007_FFFF` 上 (如果我们有一个512KiB Flash ROM)。微控制器设计者没有忽略这两个区域间的所有剩余空间,反而将外设的接口映射到某些地址上。最后看起来像这样: +虽然32位微控制器有一个从`0x0000_0000`到`0xFFFF_FFFF`的线性的物理地址空间,但是它们通常只使用几百KiB的实际内存。有相当大部分的地址空间保留着。在早期的章节中,我们说到RAM被放置在地址`0x2000_0000`处。如果我们的RAM是64KiB大小(i.e. 最大地址为0xFFFF),那么地址 `0x2000_0000`到`0x2000_FFFF`与我们的RAM有关。当我们写入一个位于地址`0x2000_1234`的变量时,内部发生的是,一些逻辑发现了地址的上部(这个例子里是0x2000),然后激活RAM,以便能操作地址的下部(这个例子里是0x1234)。在一个Cortex-M上,我们也也会把Flash ROM映射进地址 `0x000_0000` 到地址 `0x0007_FFFF` 上 (如果我们有一个512KiB Flash ROM)。微控制器设计者没有忽略这两个区域间的所有剩余空间,反而将外设的接口映射到这些地址上。最后看起来像这样: ![](../assets/nrf52-memory-map.png) @@ -31,9 +31,9 @@ RAM芯片,处理器将会通过一系列并行的迹(traces)又被称为一个 ## 存储映射的外设 -乍一看,与这些外设交互很简单 - 将正确的数据写入正确的地址。比如,在一个串行端口上发送一个32位字(32 bit word),可以像把那个32位字写入某些存储地址一样直接。串行端口外设然后能自动获取和发出数据。 +乍一看,与这些外设交互很简单 - 将正确的数据写入正确的地址。比如,在一个串行端口上发送一个32位字,可以直接把那个32位字写入某个存储地址。串行端口外设然后能自动获取和发出数据。 -这些外设的配置工作相似。不是调用一个函数去配置一个外设,而是暴露一块地址空间作为硬件API。向一个SPI频率控制寄存器写入 `0x8000_0000`,SPI端口将会按照每秒8MB的速度发送数据。像同个地址写入 `0x0200_0000`,SPI端口将会按照每秒125KiB的速度发送数据。这些配置寄存器看起来有点像这个: +这些外设的配置工作相似。不是调用一个函数去配置一个外设,而是暴露一块地址空间作为硬件API。向一个SPI频率控制寄存器写入 `0x8000_0000`,SPI端口将会按照每秒8MB的速度发送数据。向同个地址写入 `0x0200_0000`,SPI端口将会按照每秒125KiB的速度发送数据。这些配置寄存器看起来有点像这个: ![](../assets/nrf52-spi-frequency-register.png) diff --git a/src/start/registers.md b/src/start/registers.md index e46f5f8d..a5584ffb 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,4 +1,4 @@ -# 存储映射的寄存器 +# 存储映射的寄存器(Memory-Mapped Registers) 嵌入式系统想要继续执行下去,只有通过执行常规的Rust代码并在RAM间移动数据才行。如果我们想要获取或者发出信息(点亮一个LED,发现一个按钮按下或者在总线上与芯片外设通信),我们不得不深入了解外设和它们的"存储映射的寄存器"。 From a7801330deb0ffda1c114cbf23e109f8c8b32e14 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sat, 19 Nov 2022 21:54:17 +0800 Subject: [PATCH 120/137] minor --- src/peripherals/borrowck.md | 12 ++++++------ src/peripherals/singletons.md | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index 2f3a21c7..027faa76 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -1,19 +1,19 @@ ## 可变的全局状态 -不幸地是,硬件本质上是个可变的全局状态,Rust开发者可能对此感到很害怕。因为硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 +不幸的是,硬件本质上是个可变的全局状态,Rust开发者可能会对此感到很害怕。因为硬件独立于我们所写的代码的结构,能被真实世界在任何时候改变。 ## 我们应该遵循什么规则? 我们如何才能做到可靠地与这些外设交互? -1. 总是使用 `volatile` 方法去读或者写外设存储器。因为它可以随时改变。 +1. 总是使用 `volatile` 方法去读或者写外设存储器。因为它随时会改变。 2. 在软件中,我们应该能共享任何数量的关于这些外设的只读访问 -3. 如果某些软件对一个外设应该可以读写访问,它应该保有对那个外设的唯一引用。 +3. 如果某个软件可以读写一个外设,它应该保有对那个外设的唯一引用。 ## 借用检查器 -这些规则最后两个听起来与借用检查器已经做的事相似。 +这些规则最后两个听起来与借用检查器在做的事情很像! -思考一下,我们是否可以分发这些外设的所有权,或者引用它们? +思考一下,我们是否可以传递这些外设的所有权,或者提供对它们的可变或者不可变的引用? -我们当然可以,但是对于借用检查器来说,每个外设我们都只需要一个实例,因此Rust可以正确地处理这个实例。幸运的是,在硬件中,任何给定的外设,只有一个实例,但是我们该如何将它暴露在我们代码的结构中呢? +我们当然可以,但是对于借用检查器来说,每个外设只有一个实例的话,Rust才可以正确地处理这件事。幸运的是,在硬件中,任何给定的外设,只有一个实例,但是我们该如何将它暴露在代码的结构中呢? diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 38eedcb3..0782c25e 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -21,11 +21,11 @@ fn main() { } ``` -但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你所有的程序之间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有权。 +但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你所有的程序间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有权。 ## 我们在Rust中要怎么做? -与其只是让我们的外设变成一个全局变量,我们不如决定创造一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,这个全局变量对于我们的每个外设,它都有一个与之对应的 `Option` 。 +与其只是让我们的外设变成一个全局变量,我们不如生成一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,这个全局变量对于我们的每个外设,它都有一个与之对应的 `Option` 。 ```rust,ignore struct Peripherals { @@ -42,7 +42,7 @@ static mut PERIPHERALS: Peripherals = Peripherals { }; ``` -这个结构体允许我们获得外设的一个实例。如果我们尝试调用`take_serial()`获得多个实例,我们的代码将会抛出运行时恐慌(panic)! +这个结构体允许我们获得一个外设的实例。如果我们尝试调用`take_serial()`获得多个实例,我们的代码将会抛出运行时恐慌(panic)! ```rust,ignore fn main() { @@ -58,7 +58,7 @@ fn main() { ## 已存在的库支持 -虽然我们在上面创造了我们自己的 `Peripherals` 结构体,但这并不是必须的。`cortex_m` crate 包含一个被叫做 `singleton!()` 的宏,其将为你完成这个任务。 +虽然我们在上面生成了我们自己的 `Peripherals` 结构体,但这并不是必须的。`cortex_m` crate 包含一个被叫做 `singleton!()` 的宏,它可以为你完成这个任务。 ```rust,ignore #[macro_use(singleton)] @@ -73,7 +73,7 @@ fn main() { [cortex_m docs](https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html) -另外,如果你使用 [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic),它将获取和定义这些外设的整个过程抽象了出来,你将获得一个`Peripherals`结构体,其包含了一个所有你定义了的项的非 `Option` 的版本。 +另外,如果你使用 [`cortex-m-rtic`](/~https://github.com/rtic-rs/cortex-m-rtic),它将获取和定义这些外设的整个过程抽象了出来,你将获得一个`Peripherals`结构体,其包含了所有你定义了的项的一个非 `Option` 的版本。 ```rust,ignore // cortex-m-rtic v0.5.x @@ -116,7 +116,7 @@ impl SerialPort { * 因为我们正在使用一个单例模式,所以我们只有一种方法或者地方去获得一个 `SerialPort` 结构体。 * 为了调用 `read_speed()` 方法,我们必须拥有一个 `SerialPort` 结构体的所有权或者一个引用。 -这两个因素放在一起意味着,只有当我们满足了借用检查器的条件时,我们才有可能访问硬件,也意味着我们在任何时候不可能有多个对同一个硬件的可变引用(&mut)! +这两个因素放在一起意味着,只有当我们满足了借用检查器的条件时,我们才有可能访问硬件,也意味着在任何时候不可能存在多个对同一个硬件的可变引用(&mut)! ```rust,ignore fn main() { @@ -132,9 +132,9 @@ fn main() { ## 像数据一样对待你的硬件 -另外,因为一些引用是可变的,一些是不可变的,观察一个函数或者方法是否会潜在修改硬件的状态变得可能了。比如, +另外,因为一些引用是可变的,一些是不可变的,就可以知道一个函数或者方法是否有能力修改硬件的状态。比如, -这个函数允许改变硬件的配置: +这个函数可以改变硬件的配置: ```rust,ignore fn setup_spi_port( @@ -153,5 +153,5 @@ fn read_button(gpio: &GpioPin) -> bool { } ``` -这允许我们在**编译时**而不是运行时强制代码是否应该或者不应该对硬件进行改变,这通常在只有一个应用的情况下起作用,但是对于裸机系统来说,我们的软件将被编译进一个单一应用中,因此它通常是不受限的。 +这允许我们在**编译时**而不是运行时强制代码是否应该或者不应该对硬件进行修改。要注意,这通常在只有一个应用的情况下起作用,但是对于裸机系统来说,我们的软件将被编译进一个单一应用中,因此这通常不是一个限制。 From 96072231537eee7dded14b6e89ce026323f5d7d2 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 21 Nov 2022 08:37:47 +0800 Subject: [PATCH 121/137] minor --- src/peripherals/singletons.md | 8 ++++---- src/static-guarantees/index.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 0782c25e..e820dabc 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -7,7 +7,7 @@ [Singleton Pattern]: https://en.wikipedia.org/wiki/Singleton_pattern -## 但是为什么我们不可以使用全局变量呢? +## 为什么不可以使用全局变量? 可以像这样,我们可以使每个东西都变成公共静态的(public static): @@ -23,7 +23,7 @@ fn main() { 但是这个带来了一些问题。它是一个可变的全局变量,在Rust,与这些变量交互总是unsafe的。这些变量在你所有的程序间也是可见的,意味着借用检查器不能帮你跟踪这些变量的引用和所有权。 -## 我们在Rust中要怎么做? +## 在Rust中要怎么做? 与其只是让我们的外设变成一个全局变量,我们不如生成一个全局变量,在这个例子里其被叫做 `PERIPHERALS`,这个全局变量对于我们的每个外设,它都有一个与之对应的 `Option` 。 @@ -92,7 +92,7 @@ const APP: () = { } ``` -## 但是为什么? +## 为什么? 但是这些单例模式是如何使我们的Rust代码在工作方式上产生很大不同的? @@ -130,7 +130,7 @@ fn main() { } ``` -## 像数据一样对待你的硬件 +## 像对待数据一样对待硬件 另外,因为一些引用是可变的,一些是不可变的,就可以知道一个函数或者方法是否有能力修改硬件的状态。比如, diff --git a/src/static-guarantees/index.md b/src/static-guarantees/index.md index ee10a4f0..15ff4dd8 100644 --- a/src/static-guarantees/index.md +++ b/src/static-guarantees/index.md @@ -1,13 +1,13 @@ # 静态保障 -Rust的类型系统可防止编译时的数据竞争(看[`Send`]和[`Sync`]特性(traits))。类型系统也能被用来在编译时检查其它属性;减少某些案例中运行时检查的需要。 +Rust的类型系统可以在编译时防止数据竞争(看[`Send`]和[`Sync`]特性(traits))。也可以在编译时使用类型系统来完成一些检查工作;减少某些案例中运行时检查的需要。 [`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来初始化一个串行接口,这个API只能通过先配置接口需要的管脚后才能被正确地使用。 +当应用到嵌入式程序时,这些*静态检查*能被用来,比如,强制I/O接口的配置被恰当地执行了。例如,可以设计一个API来初始化一个串行接口,这个API只能通过先配置接口需要的管脚后才可以正确地使用。 -也可以静态检查操作是否被执行在正确配置了的外设上,像是拉低一个管脚。例如尝试修改一个被配置成浮空输入模式的管脚的输出状态时,将会出发一个编译时错误。 +也可以静态检查操作是否被执行在正确配置了的外设上,像是拉低一个管脚。例如尝试修改一个被配置成浮空输入模式的管脚的输出状态时,会触发一个编译时错误。 -且,像是在前面章节看到的,所有权的概念能被应用到外设上确保只有一个程序的某些部分可以修改一个外设。与将这个外设当做全局可变的状态相比,*访问控制*(assess control)使得软件更容易推理。 +并且,像是在前面章节看到的,所有权的概念能被应用到外设上确保只有一个程序的某些部分可以修改一个外设。与将这个外设当做全局可变的状态相比,*访问控制*(assess control)使得软件更容易推理。 From c8a3b2692c949ff524e5bf58f90938cd42c4d417 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 25 Nov 2022 14:04:24 +0800 Subject: [PATCH 122/137] minor --- src/SUMMARY.md | 2 +- src/concurrency/index.md | 28 ++++++++--------- src/portability/index.md | 30 +++++++++---------- src/start/registers.md | 2 +- src/static-guarantees/design-contracts.md | 22 +++++++------- src/static-guarantees/index.md | 8 ++--- src/static-guarantees/state-machines.md | 28 ++++++++--------- .../typestate-programming.md | 10 +++---- .../zero-cost-abstractions.md | 8 ++--- 9 files changed, 69 insertions(+), 69 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index fd5ccef9..1c372239 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -34,7 +34,7 @@ more information and coordination - [静态保障(static guarantees)](./static-guarantees/index.md) - [类型状态编程](./static-guarantees/typestate-programming.md) - [作为状态机的外设](./static-guarantees/state-machines.md) - - [设计协约](./static-guarantees/design-contracts.md) + - [设计约定](./static-guarantees/design-contracts.md) - [零成本抽象](./static-guarantees/zero-cost-abstractions.md) - [可移植性](./portability/index.md) - [并发](./concurrency/index.md) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index 5337f388..b986b46f 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -1,16 +1,16 @@ # 并发 -无论何时你程序有可能会在不同时刻执行或者不按顺序执行不同的部分,那并发就出现了。在一个嵌入式环境中,这包括: +无论何时程序有可能会在不同的时刻执行或者不按顺序执行不同的部分,那并发就出现了。在一个嵌入式环境中,这包括: * 中断处理函数,一旦相关的中断发生时,中断处理函数就会运行, -* 不同的多线程形式,在这块,你的微处理器通常会在你的程序的不同部分间进行切换, -* 在一些多核微处理器系统中,每个核可以同时独立地运行你的程序的不同部分。 +* 不同的多线程形式,在这块,微处理器通常会在程序的不同部分间进行切换, +* 在一些多核微处理器系统中,每个核可以同时独立地运行程序的不同部分。 因为许多嵌入式程序需要处理中断,因此并发迟早会出现,这也是许多微妙和困难的bugs会出现的地方。幸运地是,Rust提供了许多抽象和安全保障去帮助我们写正确的代码。 ## 没有并发 -对于一个嵌入式程序来说最简单的并发是没有并发: 你的软件由单个保持运行的main循环组成,一点中断也没有。有时候这非常适合手边的问题! 通常你的循环将会读取一些输入,执行一些处理,且写入一些输出。 +对于一个嵌入式程序来说最简单的并发是没有并发: 你的软件由一个保持运行的main循环组成,一点中断也没有。有时候这非常适合手边的问题! 通常你的循环将会读取一些输入,执行一些处理,且写入一些输出。 ```rust,ignore #[entry] @@ -34,7 +34,7 @@ fn main() { [`static mut`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable -为了举例这种行为如何在你的代码中导致了微妙的错误,思考一个嵌入式程序,这个程序在每一秒的周期内计数一些输入信号的上升沿(一个频率计数器): +为了举例这种行为如何在代码中导致了微妙的错误,思考一个嵌入式程序,这个程序在每一秒的周期内计数一些输入信号的上升沿(一个频率计数器): ```rust,ignore static mut COUNTER: u32 = 0; @@ -59,7 +59,7 @@ fn timer() { } ``` -每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当它看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在读取之后但是在写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那期间某些变化我们会计算两次。 +每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`的,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在计数器被读取之后但是在被写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那期间,我们会算出两倍的转换次数。 ## 临界区(Critical Sections) @@ -92,20 +92,20 @@ fn timer() { 在这个例子里,我们使用 `cortex_m::interrupt::free`,但是其它平台将会有更简单的机制在一个临界区中执行代码。它们都有一样的逻辑,关闭中断,运行一些代码,然后重新使能中断。 -注意,有两个理由,我们不需要把一个临界区放进计时器中断中: +注意,有两个理由,不需要把一个临界区放进计时器中断中: * 向`COUNTER`写入0不会被一个竞争影响,因为我们不需要读取它 * 无论如何,它永远不会被`main`线程中断 如果`COUNTER`被多个可能相互 _抢占_ 的中断处理函数共享,那么每一个也需要一个临界区。 -这解决了我们的眼前问题,但是我们仍然要编写许多unsafe的代码,我们需要仔细推敲这些代码,有些我们可能不需要使用临界区。因为每个临界区暂时地暂停了中断处理,就会出现一些消耗,其与某些代码尺寸,更高的中断延迟和抖动有关(中断可能花费很长时间去处理,等待被处理的时间变化非常大)。这是否是个问题取决于你的系统,但是通常,我们想要避免它。 +这解决了我们眼前的问题,但是我们仍然要编写许多unsafe的代码,我们需要仔细推敲这些代码,有些我们可能不需要使用临界区。因为每个临界区暂时暂停了中断处理,就会带来一些相关的成本,一些额外的代码大小,更高的中断延迟和抖动(中断可能花费很长时间去处理,等待被处理的时间变化非常大)。这是否是个问题取决于你的系统,但是通常,我们想要避免它。 -值得注意的是,虽然一个临界区保障了没有中断将会发生,但是它在多核系统上不提供一个排他性保证(exclusivity guarantee)!其它核可能很开心访问与你的核一样的内存区域,即使没有中断。如果你正在使用多核,你将需要更强的同步原语(synchronisation primitives)。 +值得注意的是,虽然一个临界区保障了不会发生中断,但是它在多核系统上不提供一个排他性保证(exclusivity guarantee)!其它核可能很开心访问与你的核一样的内存区域,即使没有中断。如果你正在使用多核,你将需要更强的同步原语(synchronisation primitives)。 ## 原子访问 -在一些平台上,可以使用特定的原子指令,它保障了读取-修改-写回操作。针对Cortex-M: `thumbv6`(Cortex-M0,Cortex-M0+)只提供原子读取和存取指令,而`thumv7`(Cortex-M3和其上)提供完全的比较和交换(CAS)指令。这些CAS指令提供了一种替代方法,替代禁用所有中断: 我们可以尝试执行增加操作,它在大多数情况下都会成功,但是如果它被中断了它将会自动重试完整的增加操作。这些原子操作甚至在多核间也是安全的。 +在一些平台上,可以使用特定的原子指令,它保障了读取-修改-写回操作。针对Cortex-M: `thumbv6`(Cortex-M0,Cortex-M0+)只提供原子读取和存取指令,而`thumv7`(Cortex-M3和其上)提供完整的比较和交换(CAS)指令。这些CAS指令可以替代过重的禁用所有中断的方法: 我们可以尝试执行加法操作,它在大多数情况下都会成功,但是如果它被中断了它将会自动重试完整的加法操作。这些原子操作甚至在多核间也是安全的。 ```rust,ignore use core::sync::atomic::{AtomicUsize, Ordering}; @@ -133,9 +133,9 @@ fn timer() { } ``` -这时,`COUNTER`是一个safe的`static`变量。多亏了`AtomicUsize`类型,不需要禁用中断,`COUNTER`能从中断处理函数和main线程被安全地修改。当可以这么做时,这是一个更好的解决方案 - 然而你的平台上可能不支持。 +这时,`COUNTER`是一个safe的`static`变量。多亏了`AtomicUsize`类型,不需要禁用中断,`COUNTER`能从中断处理函数和main线程被安全地修改。当可以这么做时,这是一个更好的解决方案 - 然而平台上可能不支持这么做。 -一个关于[`Ordering`]的提醒: 它可能影响编译器和硬件如何重新排序指令,也会影响缓存可见性。假设目标是个单核平台,在这个案例里`Relaxed`是充足和最有效的选择。更严格的排序将导致编译器在原子操作周围产生内存屏障(Memory Barriers);取决于你做什么原子操作,你可能需要或者不需要这个排序!原子模型的精确细节是复杂的,细节最好写在其它地方。 +关于[`Ordering`]的提醒: 它可能影响编译器和硬件如何重新排序指令,也会影响缓存可见性。假设目标是个单核平台,在这个案例里`Relaxed`是充足的和最有效的选择。更严格的排序将导致编译器在原子操作周围产生内存屏障(Memory Barriers);取决于你做什么原子操作,你可能需要或者不需要这个排序!原子模型的精确细节是复杂的,最好写在其它地方。 关于原子操作和排序的更多细节,可以看这里[nomicon]。 @@ -145,9 +145,9 @@ fn timer() { ## 抽象,Send和Sync -上面的解决方案都不是特别令人满意。它们需要`unsafe`块,`unsafe`块必须要被十分小心地检查且要符合人体工程学。确实,我们在Rust中可以做得更好! +上面的解决方案都不是特别令人满意。它们需要`unsafe`块,`unsafe`块必须要被十分小心地检查且不符合人体工程学。确实,我们在Rust中可以做得更好! -我们可以把我们的计数器抽象进一个安全的接口,其可以在我们代码的其它地方被安全地使用。在这个例子里,我们将使用临界区的(cirtical-section)计数器,但是你可以用原子操作做一些非常类似的事情。 +我们可以把我们的计数器抽象进一个安全的接口中,它可以在代码的其它地方被安全地使用。在这个例子里,我们将使用临界区的(cirtical-section)计数器,但是你可以用原子操作做一些非常类似的事情。 ```rust,ignore use core::cell::UnsafeCell; diff --git a/src/portability/index.md b/src/portability/index.md index f148fd26..6f527465 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,6 +1,6 @@ # 可移植性 -在嵌入式环境中,可移植性是一个非常重要的主题: 每个供应商甚至同个制造商的每个系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 +在嵌入式环境中,可移植性是一个非常重要的主题: 每个供应商甚至同个制造商的不同系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 通过一个被叫做硬件抽象层或者**HAL**的层去均等化这种差异是一种常见的方法。 @@ -11,13 +11,13 @@ [Hardware Abstraction Layer]: https://en.wikipedia.org/wiki/Hardware_abstraction -在这方面嵌入式系统有点特别,因为我们通常没有操作系统和用户可安装的软件,而是只有固件镜像,其作为一个整体被编译且伴着许多约束。因此虽然维基百科定义的传统方法可能有效,但是它不是确保可移植性最有效的方法。 +在这方面嵌入式系统有点特别,因为我们通常没有操作系统和用户可安装的软件,而是只有固件镜像,其作为一个整体被编译且伴着许多约束。因此虽然维基百科定义的传统方法可能有用,但是它不是确保可移植性最有效的方法。 在Rust中我们要怎么实现这个目标呢?让我们进入**embedded-hal**... ## 什么是embedded-hal? -简而言之,它是一组traits,其定义了**HAL implementations**,**驱动**,**应用(或者固件)** 之间的实现约定(implementation contracts)。这些约定包括功能(即约定,如果为某个类型实现了某个trait,**HAL implementation**就提供了某个功能)和方法(即,如果你要构造一个实现了某个trait的类型,约定保障你肯定有在trait中指定的方法)。 +简而言之,它是一组traits,其定义了**HAL implementations**,**驱动**,**应用(或者固件)** 之间的实现约定(implementation contracts)。这些约定包括功能(即约定,如果为某个类型实现了某个trait,**HAL implementation**就提供了某个功能)和方法(即,如果构造一个实现了某个trait的类型,约定保障类型肯定有在trait中指定的方法)。 典型的分层可能如下所示: @@ -32,32 +32,32 @@ * Timers/Countdowns * Analog Digital Conversion -出现 **embedded-hal** traits和依赖它们的crates的主要原因是为了控制复杂性。如果你认为一个应用程序可能必须要在硬件中实现外设的使用,以及应用程序和其它硬件组件潜在的驱动,那么其应该很容易被看作是可复用性有限的。用数学语言来说就是,如果**M**是外设HAL implementations的数量,**N**是驱动的数量,那么如果我们要为每个应用重新发明轮子我们最终会有**M*N**个实现,然而通过使用**embedded-hal**的traits提供的 *API* 将会使实现复杂性变成**M+N** 。当然还有其它好处,比如由于API定义良好,开箱即用,导致试错减少。 +使用**embedded-hal** traits和依赖**embedded-hal**的crates的主要原因是为了控制复杂性。如果发现一个应用可能必须要实现对硬件外设的使用,以及需要实现应用程序和其它硬件组件间潜在的驱动,那么其应该很容易被看作是可复用性有限的。用数学语言来说就是,如果**M**是外设HAL implementations的数量,**N**是驱动的数量,那么如果我们要为每个应用重新发明轮子我们最终会有**M*N**个实现,然而通过使用**embedded-hal**的traits提供的 *API* 将会使实现复杂性变成**M+N** 。当然还有其它好处,比如由于API定义良好,开箱即用,导致试错减少。 ## embedded-hal的用户 -像上面说的,HAL有三个主要用户: +像上面所说的,HAL有三个主要用户: ### HAL implementation -一个HAL implentation提供硬件和HAL traits的用户之间的接口。典型的实现由三部分组成: +HAL implentation提供硬件和HAL traits的用户之间的接口。典型的实现由三部分组成: -* 一个或者多个特定于硬件的类型 -* 生成和初始化这个类型的函数,其经常提供不同的配置选项(速度,操作模式,使用的管脚,etc 。) -* 与那个类型有关的一个或者多个 **embedded-hal** traits 的 `trait` `impl` +* 一个或者多个硬件特定的类型 +* 生成和初始化这个类型的函数,函数经常提供不同的配置选项(速度,操作模式,使用的管脚,etc 。) +* 与这个类型有关的一个或者多个 **embedded-hal** traits 的 `trait` `impl` -这样的一个 **HAL implementation** 可以有各种类型: -* 通过低级硬件访问,e.g. 通过寄存器。 -* 通过操作系统,e.g. 通过使用Linux下的 `sysfs` -* 通过适配器,e.g. 一个与单元测试有关的类型的模仿 +这样的一个 **HAL implementation** 可以有多个方法来实现: +* 通过低级硬件访问,比如通过寄存器。 +* 通过操作系统,比如通过使用Linux下的 `sysfs` +* 通过适配器,比如一个与单元测试有关的类型的仿真 * 通过相关硬件适配器的驱动,e.g. I2C多路复用器或者GPIO扩展器(I2C multiplexer or GPIO expander) ### 驱动 -一个驱动为一个外部或者内部组件实现了一组自定义的功能,被连接到一个实现了embedded-hal traits的外设上。这种驱动的典型的例子包括多个传感器(温度计,磁力计,加速度计,光照计),显示设备(LED阵列,LCD显示屏)和执行器(电机,发送器)。 +驱动为一个外部或者内部组件实现了一组自定义的功能,被连接到一个实现了embedded-hal traits的外设上。这种驱动的典型的例子包括多个传感器(温度计,磁力计,加速度计,光照计),显示设备(LED阵列,LCD显示屏)和执行器(电机,发送器)。 -必须使用实现了embedded-hal的某个`trait`的类型的实例来初始化一个驱动,这是通过trait bound来确保的,驱动也提供了它自己的类型实例,这个实例具有一组自定义的方法,这些方法允许与被驱动的设备交互。 +必须使用实现了embedded-hal的某个`trait`的类型的实例来初始化驱动,这是通过trait bound来确保的,驱动也提供了它自己的类型实例,这个实例具有一组自定义的方法,这些方法允许与被驱动的设备交互。 ### 应用 diff --git a/src/start/registers.md b/src/start/registers.md index a5584ffb..21876810 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -104,7 +104,7 @@ if pwm.ctl.read().globalsync0().is_set() { ### Writing -`write()`函数使用一个只有一个参数的闭包。通常我们把这个参数叫做 `w`。然后这个参数提供对这个寄存器中不同的子域的读写访问,由厂商关于这个芯片的SVD文件提供。再一次,在 [tm4c123x documentation][tm4c123x documentation W] 中你能找到 `W` 所有可用的函数,其与特定芯片中的特定外设的特定寄存器有关。注意所有我们没有设置的子域将会被设置成一个默认值 - 任何在这个寄存器中的现存的内容将会丢失。 +`write()`函数使用一个只有一个参数的闭包。通常我们把这个参数叫做 `w`。然后这个参数提供对这个寄存器中不同的子域的读写访问,由厂商关于这个芯片的SVD文件提供。再一次,在 [tm4c123x documentation][tm4c123x documentation W] 中你能找到 `W` 所有可用的函数,其与特定芯片中的特定外设的特定寄存器有关。注意,所有我们没有设置的子域将会被设置成一个默认值 - 将会丢失任何在这个寄存器中的现存的内容。 ```rust,ignore diff --git a/src/static-guarantees/design-contracts.md b/src/static-guarantees/design-contracts.md index c7a68ad0..1438e52e 100644 --- a/src/static-guarantees/design-contracts.md +++ b/src/static-guarantees/design-contracts.md @@ -1,6 +1,6 @@ -# 设计协约 +# 设计约定(design contracts) -在我们的上个章节中,我们写了一个接口,其没有强制执行设计协约。让我们再看下我们假想的GPIO配置寄存器: +在我们的上个章节中,我们写了一个接口,但没有强制遵守设计约定。让我们再看下我们假想的GPIO配置寄存器: | 名字 | 位数(s) | 值 | 含义 | 注释 | @@ -9,16 +9,16 @@ | | | 1 | 使能 | 使能GPIO | | 方向 | 1 | 0 | 输入 | 方向设置成输入 | | | | 1 | 输出 | 方向设置成输出 | -| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻抗 | +| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻态 | | | | 01 | 下拉 | 下拉输入管脚 | | | | 10 | 上拉 | 上拉输入管脚 | -| | | 11 | n/a | 无效模式。不要设置 | -| 输出模式 | 4 | 0 | 拉低 | 拉低输出管脚 | -| | | 1 | 拉高 | 拉高输出管脚 | +| | | 11 | n/a | 无效状态。不要设置 | +| 输出模式 | 4 | 0 | 拉低 | 把管脚设置成低电平 | +| | | 1 | 拉高 | 把管脚设置成高电平 | | 输入状态 | 5 | x | in-val | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 | -如果我们在使用底层硬件之前检查状态,在运行时强制遵守我们的设计协约,我们写的代码可能像这一样: +如果在使用底层硬件之前检查状态,在运行时强制遵守设计约定,代码可能像这一样: ```rust,ignore /// GPIO接口 @@ -99,11 +99,11 @@ impl GpioConfig { } ``` -因为我们不需要强制遵守硬件上的限制,所以我们最后做了很多运行时检查,它浪费了我们很多时间和资源,对于开发者来说,这个代码用起来就没那么愉快了。 +因为需要强制遵守硬件上的限制,所以最后做了很多运行时检查,它浪费了我们很多时间和资源,对于开发者来说,这个代码用起来就没那么愉快了。 ## 类型状态(Type states) -但是,如果我们使用Rust的类型系统去强制状态转换的规则会怎样?看下这个例子: +但是,如果我们使用Rust的类型系统去强制遵守状态转换的规则会怎样?看下这个例子: ```rust,ignore /// GPIO接口 @@ -251,6 +251,6 @@ output_pin.set_bit(true); ## 编译时功能安全(Functional Safety) -因为我们在编译时完全强制我们遵守我们的设计约定,这造成了没有运行时开销。当你有一个在输入模式的管脚时,是不可能去设置一个输出模式的。你必须先把它设置成一个输出管脚,然后再设置输出模式。因为在执行一个函数前会检查现在的状态,因此没有运行时消耗。 +因为我们在编译时完全强制遵守设计约定,这造成了没有运行时开销。当管脚处于输入模式时时,是不可能设置输出模式的。必须先把它设置成一个输出管脚,然后再设置输出模式。因为在执行一个函数前会检查现在的状态,因此没有运行时消耗。 -也因为这些状态被类型系统强制遵守,因此没有为这个接口的使用者留太多的犯错空间。如果它们尝试执行一个非法的状态转换,代码将不会编译! +也因为这些状态被类型系统强制遵守,因此没有为这个接口的使用者留太多的犯错余地。如果它们尝试执行一个非法的状态转换,代码将不会编译! diff --git a/src/static-guarantees/index.md b/src/static-guarantees/index.md index 15ff4dd8..5dcfe15b 100644 --- a/src/static-guarantees/index.md +++ b/src/static-guarantees/index.md @@ -1,13 +1,13 @@ # 静态保障 -Rust的类型系统可以在编译时防止数据竞争(看[`Send`]和[`Sync`]特性(traits))。也可以在编译时使用类型系统来完成一些检查工作;减少某些案例中运行时检查的需要。 +Rust的类型系统可以在编译时防止数据竞争(看[`Send`]和[`Sync`]特性(traits))。也可以在编译时使用类型系统来完成一些检查工作;减少某些例子中对运行时检查的需要。 [`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来初始化一个串行接口,这个API只能通过先配置接口需要的管脚后才可以正确地使用。 +当应用到嵌入式程序时,这些*静态检查*能被用来,比如,强制按需配置I/O接口。例如,可以设计一个初始化串行接口的API,这个API只有在配置好接口需要的管脚后才可以被正确地使用。 -也可以静态检查操作是否被执行在正确配置了的外设上,像是拉低一个管脚。例如尝试修改一个被配置成浮空输入模式的管脚的输出状态时,会触发一个编译时错误。 +也可以静态检查,是否是在正确配置了的外设上执行的操作,像是拉低一个管脚这种操作。比如尝试修改一个被配置成浮空输入模式的管脚的输出状态时,会触发一个编译时错误。 -并且,像是在前面章节看到的,所有权的概念能被应用到外设上确保只有一个程序的某些部分可以修改一个外设。与将这个外设当做全局可变的状态相比,*访问控制*(assess control)使得软件更容易推理。 +并且,像是在前面章节看到的,所有权的概念能被应用到外设上确保一个程序只有某些部分可以修改一个外设。与将这个外设当做全局可变的状态相比,*访问控制*(assess control)使得软件更容易推理。 diff --git a/src/static-guarantees/state-machines.md b/src/static-guarantees/state-machines.md index 24933c1a..44f3d7bd 100644 --- a/src/static-guarantees/state-machines.md +++ b/src/static-guarantees/state-machines.md @@ -10,18 +10,18 @@ * 输出: 高 * 输出: 低 * 配置成输入 - * 输入: 高阻抗 + * 输入: 高阻态 * 输入: 下拉 * 输入: 上拉 -如果外设开始于`关闭`模式,切换到`输入: 高阻抗`模式,我们必须执行下面的步骤: +如果外设开始于`关闭`模式,切换到`输入: 高阻态`模式,我们必须执行下面的步骤: 1. 关闭 2. 使能 3. 配置成输入 -4. 输入: 高阻抗 +4. 输入: 高阻态 -如果我们想要从`输入: 高阻抗`切换到`输入: 下拉`,我们必须执行下列的步骤: +如果我们想要从`输入: 高阻态`切换到`输入: 下拉`,我们必须执行下列的步骤: 1. 输入: 高阻抗 2. 输入: 下拉 @@ -34,7 +34,7 @@ ## 硬件表征(Hardware Representation) -通常,上面列的状态通过向指定的映射到一个GPIO外设的寄存器中写入值来配置。让我们定义一个假想的GPIO配置寄存器来解释下它: +通常,通过向映射到GPIO外设上的指定的寄存器中写入值可以配置上面列出的状态。让我们定义一个假想的GPIO配置寄存器来解释下它: | 名字 | 位数(s) | 值 | 含义 | 注释 | | ---: | ------------: | ----: | ------: | ----: | @@ -42,15 +42,15 @@ | | | 1 | 使能 | 使能GPIO | | 方向 | 1 | 0 | 输入 | 方向设置成输入 | | | | 1 | 输出 | 方向设置成输出 | -| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻抗 | +| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻态 | | | | 01 | 下拉 | 下拉输入管脚 | | | | 10 | 上拉 | 上拉输入管脚 | -| | | 11 | n/a | 无效模式。不要设置 | -| 输出模式 | 4 | 0 | 拉低 | 拉低输出管脚 | -| | | 1 | 拉高 | 拉高输出管脚 | -| 输入状态 | 5 | x | in-val | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 | +| | | 11 | n/a | 无效状态。不要设置 | +| 输出模式 | 4 | 0 | 拉低 | 输出管脚变成地电平 | +| | | 1 | 拉高 | 输出管脚变成高电平 | +| 输入状态 | 5 | x | in-val | 如果输入 < 1.5v为0,如果输入 >= 1.5v为1 | -我们 _能_ 在Rust中暴露下列的结构体来控制这个GPIO: +_可以_ 在Rust中暴露下列的结构体来控制这个GPIO: ```rust,ignore /// GPIO接口 @@ -90,8 +90,8 @@ impl GpioConfig { } ``` -然而,这将会允许我们修改某些没有意义的寄存器。比如,如果当我们的GPIO被配置为输入时我们设置`output_mode`字段,将会发生什么? +然而,这会允许我们修改某些没有意义的寄存器。比如,如果当我们的GPIO被配置为输入时我们设置`output_mode`字段,将会发生什么? -正常使用这个结构体将会允许我们访问我们上面的状态机没有定义的状态: e.g. 一个被上拉的输出,或者一个被拉高的输入。对于一些硬件,这并没有关系。对另外一些硬件来说,这将会导致不可预期或者没有定义的行为! +通常使用这个结构体会允许我们访问到上面的状态机没有定义的状态:比如,一个被上拉的输出,或者一个被拉高的输入。对于一些硬件,这并没有关系。对另外一些硬件来说,这将会导致不可预期或者没有定义的行为! -虽然这个接口很方便写入,但是它没有强制我们遵守为硬件的实现所规定的设计协约。 +虽然这个接口很方便写入,但是它没有强制我们遵守硬件实现所设的设计约定。 diff --git a/src/static-guarantees/typestate-programming.md b/src/static-guarantees/typestate-programming.md index 1469dee3..10f5af6d 100644 --- a/src/static-guarantees/typestate-programming.md +++ b/src/static-guarantees/typestate-programming.md @@ -1,6 +1,6 @@ # 类型状态编程(Typestate Programming) -[typestates]的概念是指将有关对象当前状态的信息编码进该对象的类型中。虽然这听起来有点神秘,如果你在Rust中使用了[建造者模式],你就已经开始使用类型状态编程了! +[typestates]的概念是指将有关对象当前状态的信息编码进该对象的类型中。虽然这听起来有点神秘,如果你在Rust中用过[建造者模式],你就已经开始使用类型状态编程了! [typestates]: https://en.wikipedia.org/wiki/Typestate_analysis [建造者模式]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html @@ -49,18 +49,18 @@ fn main() { } ``` -在这个例子里,不能直接生成一个`Foo`对象。我们必须创造一个`FooBuilder`,且我们恰当地初始化`FooBuilder`后才能获取到我们需要的`Foo`对象。 +在这个例子里,不能直接生成一个`Foo`对象。必须先生成一个`FooBuilder`,并且恰当地初始化`FooBuilder`后,才能获取到需要的`Foo`对象。 这个最小的例子编码了两个状态: -* `FooBuilder`,其表示了一个"没有被配置",或者"正在配置"状态 +* `FooBuilder`,其表示一个"没有被配置",或者"正在配置"状态 * `Foo`,其表示了一个"被配置",或者"可以使用"状态。 ## 强类型 -因为Rust有一个[强类型系统],没有简单的方法可以奇迹般地创造一个`Foo`实例或者不用调用`into_foo()`方法把一个`FooBuilder`变成一个`Foo`。另外,调用`into_foo()`方法消费了最初的`FooBuilder`结构体,意味着不创造一个新的实例它就不能被再次使用。 +因为Rust有一个[强类型系统],没有什么简单的方法可以奇迹般地生成一个`Foo`实例,也没有简单的方法可以不用调用`into_foo()`方法而把一个`FooBuilder`变成一个`Foo`。另外,调用`into_foo()`方法消费了最初的`FooBuilder`结构体,意味着不生成一个新的实例就不能被再次使用它。 [强类型系统]: https://en.wikipedia.org/wiki/Strong_and_weak_typing -这允许我们去将我们系统的状态表示成类型,把状态转换必须的动作包括进交换两个类型的方法中。通过创造一个 `FooBuilder`,与一个 `Foo` 对象交换,我们已经使用了一个基本的状态机。 +这允许我们可以将系统的状态表示成类型,把状态转换必须的动作包括进转换两个类型的方法中。通过生成一个 `FooBuilder`,转换成一个 `Foo` 对象,我们已经使用了一个基本的状态机。 diff --git a/src/static-guarantees/zero-cost-abstractions.md b/src/static-guarantees/zero-cost-abstractions.md index aaf4126c..ef88f021 100644 --- a/src/static-guarantees/zero-cost-abstractions.md +++ b/src/static-guarantees/zero-cost-abstractions.md @@ -17,7 +17,7 @@ let _ = size_of::>(); // == 0 struct Enabled; ``` -像这样定义的结构体被称为零大小的类型,因为它们不包含实际数据。虽然这些类型在编译时像是"真的"(real) - 你可以拷贝它们,移动它们,引用它们,etc.,然而优化器将会完全跳过它们。 +像这样定义的结构体被称为零大小的类型,因为它们不包含实际数据。虽然这些类型在编译时像是"真实的"(real) - 你可以拷贝它们,移动它们,引用它们,等等,然而优化器将会完全跳过它们。 在这个代码片段里: @@ -33,10 +33,10 @@ pub fn into_input_high_z(self) -> GpioConfig { } ``` -我们返回的GpioConfig在运行时并不存在。对这个函数的调用通常会被归纳为一条汇编指令 - 保存一个常量寄存器值进一个寄存器里。这意味着我们开发的类型状态接口是一个零成本抽象 - 它不会用更多的CPU,RAM,或者代码空间去跟踪`GpioConfig`的状态,会渲染成和直接访问寄存器一样的机器码。 +我们返回的GpioConfig在运行时并不存在。对这个函数的调用通常会被归纳为一条汇编指令 - 把一个常量寄存器值存进一个寄存器里。这意味着我们开发的类型状态接口是一个零成本抽象 - 它不会用更多的CPU,RAM,或者代码空间去跟踪`GpioConfig`的状态,会被渲染成和直接访问寄存器一样的机器码。 ## 嵌套 -通常,你可能会把这些抽象深深地嵌套起来。一旦结构体使用的所有的组件是零大小类型的,整个结构体将不会在运行时存在。 +通常,这些抽象可能会被深深地嵌套起来。一旦结构体使用的所有的组件是零大小类型的,整个结构体将不会在运行时存在。 -对于复杂或者深度嵌套的结构体,定义所有可能的状态组合可能很乏味。在这些例子中,宏能被用来生成所有的实现。 +对于复杂或者深度嵌套的结构体,定义所有可能的状态组合可能很乏味。在这些例子中,宏可能可以被用来生成所有的实现。 From 67a90023e4771631a410b588ecbb88bdc58bf88d Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 25 Nov 2022 14:56:37 +0800 Subject: [PATCH 123/137] minor --- src/SUMMARY.md | 4 ++-- src/appendix/glossary.md | 8 ++++---- src/interoperability/c-with-rust.md | 2 +- src/interoperability/index.md | 12 ++++++------ src/interoperability/rust-with-c.md | 20 ++++++++++---------- src/unsorted/math.md | 6 +++--- src/unsorted/speed-vs-size.md | 2 +- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 1c372239..c3251e12 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -49,8 +49,8 @@ more information and coordination - [给嵌入式C开发者的贴士](./c-tips/index.md) - [互用性](./interoperability/index.md) - - [给Rust配上一点C](./interoperability/c-with-rust.md) - - [给C配上一点Rust](./interoperability/rust-with-c.md) + - [使用C的Rust](./interoperability/c-with-rust.md) + - [使用Rust的C](./interoperability/rust-with-c.md) - [没有排序的主题](./unsorted/index.md) - [优化: 速度与大小间的博弈](./unsorted/speed-vs-size.md) - [执行数学运算](./unsorted/math.md) diff --git a/src/appendix/glossary.md b/src/appendix/glossary.md index a18d9f3b..fdbb26a1 100644 --- a/src/appendix/glossary.md +++ b/src/appendix/glossary.md @@ -1,10 +1,10 @@ # 附录A: 词汇表 -嵌入式生态系统充满了不同的协议,硬件组件,还有许多与生产商相关的东西,它们都使用自己的缩写和项目名。这个词汇表尝试列出它们以便更高理解它们。 +嵌入式生态系统中充满了不同的协议,硬件组件,还有许多与生产商相关的东西,它们都使用自己的缩写和项目名。这个词汇表尝试列出它们以便更好理解它们。 ### BSP -一个板级支持Crate(Board Support Crate)提供一个为某个特定板子配置的高级接口。它通常依赖一个[HAL](#hal) crate 。在[存储映射的寄存器那页](../start/registers.md)有更多细节的描述或者看[这个视频](https://youtu.be/vLYit_HHPaY)来获取一个更广泛的概述。 +板级支持的Crate(Board Support Crate)提供为某个特定板子配置的高级接口。它通常依赖一个[HAL](#hal) crate 。在[存储映射的寄存器那页](../start/registers.md)有更多细节的描述或者看[这个视频](https://youtu.be/vLYit_HHPaY)来获取一个更广泛的概述。 ### FPU @@ -12,7 +12,7 @@ ### HAL -一个硬件抽象层(Hardware Abstraction Layer) crate为一个微控制器的功能和外设提供一个开发者友好的接口。它通常在[Peripheral Access Crate (PAC)](#pac)之上被实现。它可能也会实现来自[`embedded-hal`](https://crates.io/crates/embedded-hal) crate的traits 。在[存储映射的寄存器那页](../start/registers.md)上有更多的细节或者看[这个视频](https://youtu.be/vLYit_HHPaY)获取一个更广泛的概述。 +硬件抽象层(Hardware Abstraction Layer) crate为微控制器的功能和外设提供一个开发者友好的接口。它通常在[Peripheral Access Crate (PAC)](#pac)之上被实现。它可能也会实现来自[`embedded-hal`](https://crates.io/crates/embedded-hal) crate的traits 。在[存储映射的寄存器那页](../start/registers.md)上有更多的细节或者看[这个视频](https://youtu.be/vLYit_HHPaY)获取一个更广泛的概述。 ### I2C @@ -32,7 +32,7 @@ ### SVD -系统视图描述文件(System View Description)是一个XML文件格式,被用来描述一个微控制器设备的程序员视角。你能在[the ARM CMSIS documentation site](https://www.keil.com/pack/doc/CMSIS/SVD/html/index.html)上获取更多信息。 +系统视图描述文件(System View Description)是一个XML文件格式,以程序员视角来描述一个微控制器设备。你能在[the ARM CMSIS documentation site](https://www.keil.com/pack/doc/CMSIS/SVD/html/index.html)上获取更多信息。 ### UART diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 51dc695a..ec204419 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -1,4 +1,4 @@ -# Rust配点C +# 使用C的Rust 在一个Rust项目中使用C或者C++,由两个主要部分组成: diff --git a/src/interoperability/index.md b/src/interoperability/index.md index a002b60b..6431a155 100644 --- a/src/interoperability/index.md +++ b/src/interoperability/index.md @@ -1,8 +1,8 @@ # 互用性 -Rust和C代码间的互用性始终取决于两种语言间的数据转换。为了实现它,在`stdlib`中,有两个专用模块,叫做[`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html)和[`std::os::raw`](https://doc.rust-lang.org/std/os/raw/index.html) 。 +Rust和C代码间的互用性始终取决于两种语言间的数据转换。为了实现互操性,在`stdlib`中,有两个专用模块,叫做[`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html)和[`std::os::raw`](https://doc.rust-lang.org/std/os/raw/index.html) 。 -`std::os::raw`处理底层基本类型,这些类型可以被编译器隐式地转换,因为Rust和C之间的内存布局足够相似或相同。 +`std::os::raw`处理底层的基本类型,这些类型可以被编译器隐式地转换,因为Rust和C之间的内存布局足够相似或相同。 `std::ffi`提供了一些工具去转换更复杂的类型,比如Strings,将`&str`和`String`映射成更容易和安全处理的C类型。 @@ -19,7 +19,7 @@ Rust和C代码间的互用性始终取决于两种语言间的数据转换。为 | u32 or u64 | c_uint | unsigned int | | etc | ... | ... | -像上面提到的基本类型能被编译器隐式地转换。 +像上面提到的基本类型都能被编译器隐式地转换。 ```rust,ignore unsafe fn foo(num: u32) { @@ -30,9 +30,9 @@ unsafe fn foo(num: u32) { ## 与其它编译系统的互用性 -把Rust包含进你的嵌入式项目的一个常见需求是,把Cargo结合进你现存的编译系统中,比如make或者cmake。 +在嵌入式项目中引入Rust的一个常见需求是,把Cargo结合进你现存的编译系统中,比如make或者cmake。 -在[issue #61]中我们的issue tracker上,我们正在为这个需求收集例子和用例。 +在[issue #61]的issue tracker上,我们正在为这个需求收集例子和用例。 [issue #61]: /~https://github.com/rust-embedded/book/issues/61 @@ -41,6 +41,6 @@ unsafe fn foo(num: u32) { 将Rust和一个RTOS集成在一起,比如FreeRTOS或者ChibiOS仍然在进行中; 尤其是从Rust调用RTOS函数可能很棘手。 -在[issue #62]中我们的issue tracker上,我们正为这件事收集例子和用例。 +在[issue #62]的issue tracker上,我们正为这件事收集例子和用例。 [issue #62]: /~https://github.com/rust-embedded/book/issues/62 diff --git a/src/interoperability/rust-with-c.md b/src/interoperability/rust-with-c.md index 39375d88..c9a49c1f 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -1,15 +1,15 @@ -# 你的C配点Rust +# 使用Rust的C 在C或者C++中使用Rust代码通常由两部分组成。 -- 用Rust创造一个C友好的API +- 用Rust生成一个C友好的API - 将你的Rust项目嵌入一个外部的编译系统 除了`cargo`和`meson`,大多数编译系统没有原生Rust支持。因此你最好只用`cargo`编译你的crate和依赖。 ## 设置一个项目 -像往常一样创建一个新的`cargo`项目。有一些标志可以告诉`cargo`去生成一个系统库,而不是常规的rust目标文件。如果你想要它与你的crate的其它部分不一样,这也允许你为你的库设置一个不同的输出名。 +像往常一样创建一个新的`cargo`项目。有一些标志可以告诉`cargo`去生成一个系统库,而不是常规的rust目标文件。如果你想要它与crate的其它部分不一样,你也可以为你的库设置一个不同的输出名。 ```toml [lib] @@ -20,17 +20,17 @@ crate-type = ["cdylib"] # 生成动态链接库 ## 构建一个`C` API -因为对于Rust编译器来说,C++没有稳定的ABI,因此我们使用`C`表示不同语言间的互用性。在C和C++代码的内部使用Rust时也不例外。 +因为对于Rust编译器来说,C++没有稳定的ABI,因此对于不同语言间的互操性我们使用`C`。在C和C++代码的内部使用Rust时也不例外。 ### `#[no_mangle]` -Rust对符号名的修饰与本机的代码链接器所期望的不同。因此,需要告知任何被Rust导出到Rust外部去使用的函数不要被编译器修饰。 +Rust对符号名的修饰与主机的代码链接器所期望的不同。因此,需要告知任何被Rust导出到Rust外部去使用的函数不要被编译器修饰。 ### `extern "C"` -默认,任何用Rust写的函数将使用Rust ABI(这也不稳定)。相反,当编译面向外部的FFI APIs时,我们需要告诉编译器去使用系统ABI 。 +默认,任何用Rust写的函数将使用Rust ABI(这也不稳定)。当编译面向外部的FFI APIs时,我们需要告诉编译器去使用系统ABI 。 -取决于你的平台,你可能需要针对一个特定的ABI版本,其记录在[这里](https://doc.rust-lang.org/reference/items/external-blocks.html)。 +取决于你的平台,你可能想要针对一个特定的ABI版本,其记录在[这里](https://doc.rust-lang.org/reference/items/external-blocks.html)。 --- @@ -43,7 +43,7 @@ pub extern "C" fn rust_function() { } ``` -就像在你的Rust项目中使用`C`代码时那样,你现在将需要把数据转换为应用其它部分可以理解的形式。 +就像在Rust项目中使用`C`代码时那样,现在需要把数据转换为应用中其它部分可以理解的形式。 ## 链接和更大的项目上下文 @@ -57,7 +57,7 @@ pub extern "C" fn rust_function() { 然而,从C调用一个Rust函数要求一个头文件去声明函数的签名。 -在你的Rust-ffi API中的每个函数需要有一个相关的头文件函数。 +在Rust-ffi API中的每个函数需要有一个相关的头文件函数。 ```rust,ignore #[no_mangle] @@ -72,7 +72,7 @@ void rust_function(); 等等。 -这里有个工具可以自动化这个过程,被叫做[cbindgen],其会分析你的Rust代码然后从它为你的C和C++项目生成头文件。 +这里有个工具可以自动化这个过程,被叫做[cbindgen],其会分析你的Rust代码然后从它为C和C++项目生成头文件。 [cbindgen]: /~https://github.com/eqrion/cbindgen diff --git a/src/unsorted/math.md b/src/unsorted/math.md index 63dccb75..0b9eec8d 100644 --- a/src/unsorted/math.md +++ b/src/unsorted/math.md @@ -1,6 +1,6 @@ # 在`#[no_std]`下执行数学运算 -如果你想要执行数学相关的函数,像是计算平方根或者一个数的指数且你有完整的标准库支持,你的代码可能看起来像这个: +如果你想要执行数学相关的函数,像是计算平方根或者一个数的指数并有完整的标准库支持,代码可能看起来像这样: ```rs //! 可用一些标准支持的数学函数 @@ -55,14 +55,14 @@ fn main() -> ! { ) .unwrap(); // 退出QEMU - // 注意不要在硬件上允许这个; 它能破坏OpenOCD的状态 + // 注意不要在硬件上使用这个; 它能破坏OpenOCD的状态 // debug::exit(debug::EXIT_SUCCESS); loop {} } ``` -如果你需要在你的MCU上执行更复杂的操作,像是DSP信号处理或者更高级的线性代数,下列的crates可能可以帮到你 +如果需要在MCU上执行更复杂的操作,像是DSP信号处理或者更高级的线性代数,下列的crates可能可以帮到你 - [CMSIS DSP library binding](/~https://github.com/jacobrosenthal/cmsis-dsp-sys) - [`micromath`](/~https://github.com/tarcieri/micromath) diff --git a/src/unsorted/speed-vs-size.md b/src/unsorted/speed-vs-size.md index c56c4640..899f79c2 100644 --- a/src/unsorted/speed-vs-size.md +++ b/src/unsorted/speed-vs-size.md @@ -1,6 +1,6 @@ # 优化: 速度与大小之间的博弈 -每个人都想要它们的程序变得超级快且超级小,但是同时满足这两个条件是不可能的。这部分讨论`rustc`提供的不同的优化等级,和它们是如何影响执行时间和一个程序的二进制项的大小。 +每个人都想要程序变得即快又小,但是同时满足这两个条件是不可能的。这部分讨论`rustc`提供的不同的优化等级,和它们是如何影响执行时间和一个程序的二进制项的大小。 ## 无优化 From bf8d03fbcf01fc02d5aa55bf63a0708eaf19d9b1 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 29 Nov 2022 15:55:53 +0800 Subject: [PATCH 124/137] minor --- src/interoperability/rust-with-c.md | 2 +- src/start/hardware.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interoperability/rust-with-c.md b/src/interoperability/rust-with-c.md index c9a49c1f..a6f4b804 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -72,7 +72,7 @@ void rust_function(); 等等。 -这里有个工具可以自动化这个过程,被叫做[cbindgen],其会分析你的Rust代码然后从它为C和C++项目生成头文件。 +这里有个工具可以自动化这个过程,叫做[cbindgen],其会分析你的Rust代码然后为C和C++项目生成头文件。 [cbindgen]: /~https://github.com/eqrion/cbindgen diff --git a/src/start/hardware.md b/src/start/hardware.md index 3e978040..f427818d 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -19,7 +19,7 @@ ## 配置 -我们将使用一个新的模板实例从零开始。对于新手参考[先前的QEMU]章节,了解如何在没有`cargo-generate`的情况下完成它。 +我们将使用一个新的模板实例从零开始。对于新手,请参考[先前的QEMU]章节,了解如何在没有`cargo-generate`的情况下完成配置。 [先前的QEMU]: qemu.md From cb1a02d7af9400182f89703d74c5fe574ef0c498 Mon Sep 17 00:00:00 2001 From: XxChang Date: Mon, 12 Dec 2022 15:26:42 +0800 Subject: [PATCH 125/137] minor --- src/collections/index.md | 22 ++++++++++----------- src/concurrency/index.md | 12 +++++------ src/design-patterns/hal/checklist.md | 10 +++++----- src/design-patterns/hal/gpio.md | 2 +- src/design-patterns/hal/index.md | 4 ++-- src/design-patterns/hal/interoperability.md | 8 ++++---- src/design-patterns/hal/naming.md | 4 ++-- src/design-patterns/hal/predictability.md | 6 +++--- src/portability/index.md | 6 +++--- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/collections/index.md b/src/collections/index.md index 032e75c6..b2902e2b 100644 --- a/src/collections/index.md +++ b/src/collections/index.md @@ -1,14 +1,14 @@ # 集合 -最后,你还希望在你的程序里使用动态数据结构(也称为集合)。`std` 提供了一组常见的集合: [`Vec`],[`String`],[`HashMap`],等等。所有这些在`std`中被实现的集合都使用一个全局动态分配器(也称为堆)。 +最后,还希望在程序里使用动态数据结构(也称为集合)。`std` 提供了一组常见的集合: [`Vec`],[`String`],[`HashMap`],等等。所有这些在`std`中被实现的集合都使用一个全局动态分配器(也称为堆)。 [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html [`String`]: https://doc.rust-lang.org/std/string/struct.String.html [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html -因为`core`的定义是没有内存分配的,所以这些实现在`core`中是没有的,但是我们可以在编译器附带的`alloc` crate中找到。 +因为`core`的定义中是没有内存分配的,所以这些实现在`core`中是没有的,但是我们可以在编译器附带的`alloc` crate中找到。 -如果你需要集合,一个堆分配的实现不是你唯一的选择。你也可以使用 *fixed capacity* 集合; 一个这样的实现可以在 [`heapless`] crate中被找到。 +如果需要集合,一个基于堆分配的实现不是唯一的选择。也可以使用 *fixed capacity* 集合; 其实现可以在 [`heapless`] crate中被找到。 [`heapless`]: https://crates.io/crates/heapless @@ -16,7 +16,7 @@ ## 使用 `alloc` -`alloc` crate与标准的Rust发行版在一起。为了导入这个crate,你可以直接 `use` 它而不需要在你的`Cargo.toml`文件中把它声明为一个依赖。 +`alloc` crate与标准的Rust发行版在一起。你可以直接 `use` 导入这个crate,而不需要在`Cargo.toml`文件中把它声明为一个依赖。 ``` rust,ignore #![feature(alloc)] @@ -26,7 +26,7 @@ extern crate alloc; use alloc::vec::Vec; ``` -为了能使用集合,你首先需要使用`global_allocator`属性去声明你程序将使用的全局的分配器。它要求你选择的分配器实现了[`GlobalAlloc`] trait 。 +为了能使用集合,首先需要使用`global_allocator`属性去声明程序将使用的全局分配器。它要求选择的分配器实现了[`GlobalAlloc`] trait 。 [`GlobalAlloc`]: https://doc.rust-lang.org/core/alloc/trait.GlobalAlloc.html @@ -87,7 +87,7 @@ static HEAP: BumpPointerAlloc = BumpPointerAlloc { }; ``` -除了选择一个全局分配器,用户也将必须定义如何使用*不稳定的*`alloc_error_handler`属性来处理内存溢出错误。 +除了选择一个全局分配器,用户也必须要定义如何使用*不稳定的*`alloc_error_handler`属性来处理内存溢出错误。 ``` rust,ignore #![feature(alloc_error_handler)] @@ -102,7 +102,7 @@ fn on_oom(_layout: Layout) -> ! { } ``` -一旦一切都满足了,用户最终可以在`alloc`中使用集合。 +一旦一切都完成了,用户最后就可以在`alloc`中使用集合。 ```rust,ignore #[entry] @@ -122,7 +122,7 @@ fn main() -> ! { ## 使用 `heapless` -`heapless`无需设置因为它的集合不依赖一个全局内存分配器。只是`use`它的集合然后实例化它们: +`heapless`无需设置,因为它的集合不依赖一个全局内存分配器。只是`use`它的集合然后实例化它们: ```rust,ignore extern crate heapless; // v0.4.x @@ -145,9 +145,9 @@ fn main() -> ! { [`typenum`]: https://crates.io/crates/typenum -第二,`push`方法和许多其它方法返回的是一个`Result`。因为`heapless`集合有一个固定的容量,所以所有插入的操作都可能会失败。通过返回一个`Result`,API反应了这个问题,指出操作是否成功还是失败。相反,`alloc`集合自己将会在堆上重新分配去增加它的容量。 +第二,`push`方法和另外一些方法返回的是一个`Result`。因为`heapless`集合有一个固定的容量,所以所有插入的操作都可能会失败。通过返回一个`Result`,API反应了这个问题,指出操作是否成功还是失败。相反,`alloc`集合自己将会在堆上重新分配去增加它的容量。 -自v0.4.x版本起,所有的`heapless`集合将它们所有的元素内联地存储起来了。这意味着像是`let x = heapless::Vec::new()`这样的一个操作将会在栈上分配集合,但是它也能够在一个`static`变量上分配集合,或者甚至在堆上(`Box>`)。 +自v0.4.x版本起,所有的`heapless`集合将所有的元素内联地存储起来了。这意味着像是`let x = heapless::Vec::new()`这样的一个操作将会在栈上分配集合,但是它也能够在一个`static`变量上分配集合,或者甚至在堆上(`Box>`)。 ## 取舍 @@ -157,7 +157,7 @@ fn main() -> ! { 使用堆分配,内存溢出总是有可能出现的且会发生在任何一个集合需要增长的地方: 比如,所有的 `alloc::Vec.push` 调用会潜在地产生一个OOM(Out of Memory)条件。因此一些操作可能会*隐式地*失败。一些`alloc`集合暴露了`try_reserve`方法,可以当增加集合时让你检查潜在的OOM条件,但是你需要主动地使用它们。 -如果你只使用`heapless`集合,而不使用内存分配器,那么一个OOM条件不可能出现。反而,你必须逐个处理容量不足的集合。也就是你必须处理*所有*的`Result`,其由像是`Vec.push`这样的方法返回的。 +如果你只使用`heapless`集合,而不使用内存分配器,那么一个OOM条件不可能出现。反而,你必须逐个处理容量不足的集合。也就是必须处理*所有*的`Result`,`Result`由像是`Vec.push`这样的方法返回的。 与在所有由`heapless::Vec.push`返回的`Result`上调用`unwrap`相比,OOM错误更难调试,因为错误被发现的位置可能与导致问题的位置*不*一致。比如,甚至如果分配器接近消耗完`vec.reserve(1)`都能触发一个OOM,因为一些其它的集合正在泄露内存(内存泄露在安全的Rust是会发生的)。 diff --git a/src/concurrency/index.md b/src/concurrency/index.md index b986b46f..099f865b 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -1,6 +1,6 @@ # 并发 -无论何时程序有可能会在不同的时刻执行或者不按顺序执行不同的部分,那并发就出现了。在一个嵌入式环境中,这包括: +当程序的不同部分有可能会在不同的时刻被执行或者不按顺序地被执行时,那并发就出现了。在一个嵌入式环境中,这包括: * 中断处理函数,一旦相关的中断发生时,中断处理函数就会运行, * 不同的多线程形式,在这块,微处理器通常会在程序的不同部分间进行切换, @@ -10,7 +10,7 @@ ## 没有并发 -对于一个嵌入式程序来说最简单的并发是没有并发: 你的软件由一个保持运行的main循环组成,一点中断也没有。有时候这非常适合手边的问题! 通常你的循环将会读取一些输入,执行一些处理,且写入一些输出。 +对于一个嵌入式程序来说最简单的并发是没有并发: 软件由一个保持运行的main循环组成,一点中断也没有。有时候这非常适合手边的问题! 通常你的循环将会读取一些输入,执行一些处理,且写入一些输出。 ```rust,ignore #[entry] @@ -24,13 +24,13 @@ fn main() { } ``` -因为这里没有并发,因此不需要担心程序不同部分间的共享数据或者同步对外设的访问。如果你可以使用一个简单的方法来解决问题,这种方法是个不错的选择。 +因为这里没有并发,因此不需要担心程序不同部分间的共享数据或者同步对外设的访问。如果可以使用一个简单的方法来解决问题,这种方法是个不错的选择。 ## 全局可变数据 不像非嵌入式Rust,我们通常不能分配堆和将对那个数据的引用传递进一个新创造的线程中。反而,我们的中断处理函数可能在任何时间被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _静态分配的_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 -在Rust中,[`static mut`]这样的变量读取或者写入总是unsafe的,因为不特别关注它们的话,你可能会触发一个竞态条件,你对变量的访问在中途就被一个也访问那个变量的中断打断了。 +在Rust中,[`static mut`]这样的变量读取或者写入总是unsafe的,因为不特别关注它们的话,可能会触发一个竞态条件,对变量的访问在中途就被一个也访问那个变量的中断打断了。 [`static mut`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable @@ -59,11 +59,11 @@ fn timer() { } ``` -每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`的,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在计数器被读取之后但是在被写回之前被激活,在中断返回后,重置回0的操作会被忽略 - 那期间,我们会算出两倍的转换次数。 +每秒计时器中断会把计数器设置回0。这期间,main循环连续地测量信号,且当看到从低电平到高电平的变化时,增加计数器的值。因为它是`static mut`的,我们不得不使用`unsafe`去访问`COUNTER`,意思是我们向编译器保证我们的操作不会导致任何未定义的行为。你能发现竞态条件吗?`COUNTER`上的增加并不一定是原子的 - 事实上,在大多数嵌入式平台上,它将被分开成一个读取操作,然后是增加,然后是写回。如果中断在计数器被读取之后但是在被写回之前被激活,在中断返回后,重置回0的操作会被忽略掉 - 那期间,我们会算出两倍的转换次数。 ## 临界区(Critical Sections) -因此,关于数据竞争我们能做些什么?一个简单的方法是使用 _临界区(critical sections)_ ,在临界区的上下文中中断被关闭了。通过把对`main`中的`COUNTER`访问封装进一个临界区,我们能确保计时器中断将不会激活,直到我们完成了增加`COUNTER`的操作: +因此,关于数据竞争可以做些什么?一个简单的方法是使用 _临界区(critical sections)_ ,在临界区的上下文中中断被关闭了。通过把对`main`中的`COUNTER`访问封装进一个临界区,我们能确保计时器中断将不会激活,直到我们完成了增加`COUNTER`的操作: ```rust,ignore static mut COUNTER: u32 = 0; diff --git a/src/design-patterns/hal/checklist.md b/src/design-patterns/hal/checklist.md index 316d9ac9..10b842de 100644 --- a/src/design-patterns/hal/checklist.md +++ b/src/design-patterns/hal/checklist.md @@ -1,14 +1,14 @@ -# HAL设计模式清单 +# HAL设计检查清单 -- **命名** *(crate符合Rust命名规则)* +- **命名** *(crate要符合Rust命名规则)* - [ ] crate被恰当地命名 ([C-CRATE-NAME]) -- **互用性** *(crate很好地与其它的库功能交互)* +- **互用性** *(crate要很好地与其它的库功能交互)* - [ ] 封装类型提供一种析构方法 ([C-FREE]) - [ ] HALs重新导出了它们的寄存器访问crate ([C-REEXPORT-PAC]) - [ ] 类型实现了 `embedded-hal` traits ([C-HAL-TRAITS]) -- **可预见性** *(crate使清晰的代码工作起来像它们看起来一样)* +- **可预见性** *(crate的代码清晰可读,行为和看起来的一样)* - [ ] 使用构造函数而不是扩展traits ([C-CTOR]) -- **GPIO接口** *(GPIO接口遵循一个常见的模式)* +- **GPIO接口** *(GPIO接口要遵循一个公共的模式)* - [ ] Pin类型默认是零大小类型 ([C-ZST-PIN]) - [ ] Pin类型提供擦除管脚和端口的方法 ([C-ERASED-PIN]) - [ ] Pin状态应该被编码为类型参数 ([C-PIN-STATE]) diff --git a/src/design-patterns/hal/gpio.md b/src/design-patterns/hal/gpio.md index 83c3fb50..38c96c22 100644 --- a/src/design-patterns/hal/gpio.md +++ b/src/design-patterns/hal/gpio.md @@ -36,7 +36,7 @@ pub struct PortAPins { ## 管脚类型提供方法去擦除管脚和端口(C-ERASED-PIN) -管脚应该提供类型擦除方法去将它们的属性从编译时移到运行时,允许在应用中有更多的灵活性。 +管脚应该提供类型擦除方法,将其属性从编译时移到运行时,允许在应用中有更多的灵活性。 案例: diff --git a/src/design-patterns/hal/index.md b/src/design-patterns/hal/index.md index d15d1fe0..af0a1917 100644 --- a/src/design-patterns/hal/index.md +++ b/src/design-patterns/hal/index.md @@ -1,10 +1,10 @@ # HAL设计模式 -这是一组关于使用Rust为微控制器写硬件抽象层的常见的和推荐的模式。当为微控制器编写HALs时,除了现有的 [Rust API 指南] 外,还打算使用这些模式。 +这是一组关于使用Rust为微控制器写硬件抽象层的常见的和推荐的模式。当为微控制器编写HALs时,除了现有的 [Rust API 指南] 外,这些模式也会被使用。 [Rust API 指南]: https://rust-lang.github.io/api-guidelines/ -[清单](checklist.md) +[检查清单](checklist.md) - [命名](naming.md) - [互用性](interoperability.md) diff --git a/src/design-patterns/hal/interoperability.md b/src/design-patterns/hal/interoperability.md index 320b6f83..9263a278 100644 --- a/src/design-patterns/hal/interoperability.md +++ b/src/design-patterns/hal/interoperability.md @@ -6,9 +6,9 @@ 任何由HAL提供的非`Copy`封装类型应该提供一个`free`方法,这个方法消费封装类且返回最初生成它的外设(可能是其它对象)。 -如果有必要方法应该关闭和重置外设。使用由`free`返回的原始外设调用`new`不应该由于设备的意外状态而失败, +如果有必要,方法应该关闭和重置外设。使用由`free`返回的原始外设去调用`new`不应该由于设备的意外状态而失败, -如果HAL类型要求构造其它的非`Copy`对象(比如 I/O 管脚),任何这样的对象应该也由`free`返回和释放。在那个案例中`free`应该返回一个元组。 +如果HAL类型要求构造其它的非`Copy`对象(比如 I/O 管脚),任何这样的对象应该也由`free`返回和释放。在这种情况下`free`应该返回一个元组。 比如: @@ -30,9 +30,9 @@ impl Timer { ## HALs重新导出它们的寄存器访问crate(C-REEXPORT-PAC) -HALs能被编写在[svd2rust]生成的PACs之上,或在其它纯寄存器访问的crates之上。HALs应该总是能在它们的crate root中重新导出它们所基于的寄存器访问crate +可以在[svd2rust]生成的PACs之上,或在其它纯寄存器访问的crates之上编写HALs。HALs需要在crate root中重新导出它们所基于的寄存器访问crate -一个PAC应该被重新导出在名字`pac`下,无论这个crate实际的名字是什么,因为HAL的名字应该已经明确了正被访问的是什么PAC 。 +一个PAC应该被重新导出在`pac`名下,无论这个crate实际的名字是什么,因为HAL的名字应该已经明确了正被访问的是什么PAC 。 [svd2rust]: /~https://github.com/rust-embedded/svd2rust diff --git a/src/design-patterns/hal/naming.md b/src/design-patterns/hal/naming.md index 28b20aa7..5b77b488 100644 --- a/src/design-patterns/hal/naming.md +++ b/src/design-patterns/hal/naming.md @@ -2,6 +2,6 @@ -## crate被恰当地命名(C-CRATE-NAME) +## crate要被恰当地命名(C-CRATE-NAME) -HAL crates应该被命名在它目标支持的芯片或者芯片系列之后。它们的名字应该以`-hal`结尾,为了将它们与寄存器访问crates区分开来。名字不应该包含下划线(请改用破折号)。 +HAL crates应该在目标支持的芯片或者芯片系列之后被命名。它们的名字应该以`-hal`结尾,为了将它们与PAC区分开来。名字不应该包含下划线(请改用破折号)。 diff --git a/src/design-patterns/hal/predictability.md b/src/design-patterns/hal/predictability.md index 1b57f59a..5cfddb7e 100644 --- a/src/design-patterns/hal/predictability.md +++ b/src/design-patterns/hal/predictability.md @@ -6,11 +6,11 @@ 所有由HAL添加功能的外设应该被封装进一个新类型,即使该功能不需要额外的字段。 -应该避免为原始外设扩展traits。 +应该避免为基本外设扩展traits。 ## 方法在适当的地方用`#[inline]`修饰 Rust编译器默认不会越过crate边界执行完全内联。因为嵌入式应用对于不可预期的代码大小的增加很敏感,`#[inline]`应该如下所示用来指导编译器: -* 所有的"小"函数应该被标记`#[inline]`。什么是"小"是主观的,但是通常所有有希望被编译成一位数的指令序列(single-digit instruction sequences)都可以被视为"小"。 -* 非常有可能把一个常量数值作为你参数的函数应该被标记为`#[inline]`。这让编译器在编译时就可以进行计算甚至是复杂的初始化逻辑,前提是函数输入是已知的。 +* 所有的"小"函数应该被标记`#[inline]`。什么是"小"是主观的,但是通常所有有可能被编译成一位数的指令序列(single-digit instruction sequences)都可以被视为"小"。 +* 非常有可能把一个常量数值作为参数的函数应该被标记为`#[inline]`。这让编译器在编译时就可以进行计算甚至是复杂的初始化逻辑,前提是函数输入是已知的。 diff --git a/src/portability/index.md b/src/portability/index.md index 6f527465..1a928e3b 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,6 +1,6 @@ # 可移植性 -在嵌入式环境中,可移植性是一个非常重要的主题: 每个供应商甚至同个制造商的不同系列,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 +在嵌入式环境中,可移植性是一个非常重要的主题: 每个供应商甚至同个制造商的不同系列间,都提供了不同的外设和功能。同样地,与外设交互的方式也将会不一样。 通过一个被叫做硬件抽象层或者**HAL**的层去均等化这种差异是一种常见的方法。 @@ -11,7 +11,7 @@ [Hardware Abstraction Layer]: https://en.wikipedia.org/wiki/Hardware_abstraction -在这方面嵌入式系统有点特别,因为我们通常没有操作系统和用户可安装的软件,而是只有固件镜像,其作为一个整体被编译且伴着许多约束。因此虽然维基百科定义的传统方法可能有用,但是它不是确保可移植性最有效的方法。 +在这方面,嵌入式系统有点特别,因为通常没有操作系统和用户可安装的软件,而只有固件镜像,其作为一个整体被编译且伴着许多约束。因此虽然维基百科定义的传统方法可能有用,但是它不是确保可移植性最有效的方法。 在Rust中我们要怎么实现这个目标呢?让我们进入**embedded-hal**... @@ -55,7 +55,7 @@ HAL implentation提供硬件和HAL traits的用户之间的接口。典型的实 ### 驱动 -驱动为一个外部或者内部组件实现了一组自定义的功能,被连接到一个实现了embedded-hal traits的外设上。这种驱动的典型的例子包括多个传感器(温度计,磁力计,加速度计,光照计),显示设备(LED阵列,LCD显示屏)和执行器(电机,发送器)。 +驱动为一个外部或者内部组件实现了一组自定义的功能,被连接到一个实现了embedded-hal traits的外设上。这种驱动的典型的例子包括多种传感器(温度计,磁力计,加速度计,光照计),显示设备(LED阵列,LCD显示屏)和执行器(电机,发送器)。 必须使用实现了embedded-hal的某个`trait`的类型的实例来初始化驱动,这是通过trait bound来确保的,驱动也提供了它自己的类型实例,这个实例具有一组自定义的方法,这些方法允许与被驱动的设备交互。 From 3e171244cb6dd9aa40b8c65b54cce12e146f2a18 Mon Sep 17 00:00:00 2001 From: XxChang Date: Tue, 13 Dec 2022 10:18:08 +0800 Subject: [PATCH 126/137] minor --- src/c-tips/index.md | 42 ++++++++++++++++----------------- src/design-patterns/hal/gpio.md | 10 ++++---- src/interoperability/index.md | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/c-tips/index.md b/src/c-tips/index.md index d215dbc4..cb718800 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -1,6 +1,6 @@ # 给嵌入式C开发者的贴士 -这个章节收集了可能对于正在寻求开始编写Rust有经验的嵌入式C开发者有用的各种各样的贴士。它将解释你在C中可能已经用到的那些东西与Rust中有多不同。 +这个章节收集了可能对于刚开始编写Rust的,有经验的嵌入式C开发者来说,有用的各种各样的贴士。它将解释你在C中可能已经用到的那些东西与Rust中的有何不同。 ## 预处理器 @@ -8,17 +8,17 @@ * 使用`#ifdef`编译时选择代码块 * 编译时的数组大小和计算 -* 用来简化常见的模式的宏(避免函数调用的开销) +* 用来简化常见的模式的宏(避免调用函数的开销) -在Rust中没有预处理器,所以许多用例有不同的处理方法。本章节剩下的部分,我们将介绍使用预处理器的各种替代方法。 +在Rust中没有预处理器,所以许多案例有不同的处理方法。本章节剩下的部分,我们将介绍各种替代预处理器的方法。 ### 编译时的代码选择 -Rust中最接近`#ifdef ... #endif`的是[Cargo features]。这些比C预处理器更正式一点: 每个crate显式列举的所有可能的features只能是关了的或者打开了的。当你把一个crate列为依赖项时,Features被打开,且是可添加的: 如果你依赖树中的任何crate为另一个crate打开了一个feature,那么这个feature将为所有那个crate的用户而打开。 +Rust中最接近`#ifdef ... #endif`的是[Cargo features]。这些比C预处理器更正式一点: 每个crate显式列举的,所有可能的features只能是关了的或者打开了的。当你把一个crate列为依赖项时,Features被打开,且是可添加的:如果你依赖树中的任何crate为另一个crate打开了一个feature,那么这个feature将为所有使用那个crate的用户而打开。 [Cargo features]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section -比如,你可能有一个crate,其提供一个信号处理的基本类型库(library of signal processing primitives)。每个基本类型可能带来一些额外的时间去编译大量的常量,你想要躲开这些常量。你可以为你的`Cargo.toml`中每个组件声明一个Cargo feature。 +比如,你可能有一个crate,其提供一个信号处理的基本类型库(library of signal processing primitives)。每个基本类型可能带来一些额外的时间去编译大量的常量,你想要避开这些常量。你可以为你的`Cargo.toml`中每个组件声明一个Cargo feature。 ```toml [features] @@ -26,7 +26,7 @@ FIR = [] IIR = [] ``` -然后,在你的代码中,使用`#[cfg(feature="FIR")]`去控制什么东西应该被包含。 +然后,在你的代码中,使用`#[cfg(feature="FIR")]`去控制要包含什么东西。 ```rust /// 在你的顶层的lib.rs中 @@ -39,7 +39,7 @@ pub mod iir; 同样地,你可以控制,只有当某个feature _没有_ 被打开时,包含代码块,或者某些features的组合被打开或者被关闭时。 -另外,Rust提供许多你可以使用的自动配置了的条件,比如`target_arch`用来选择不同的代码所基于的架构。对于条件编译的全部细节,可以参看the Rust reference的[conditional compilation]章节。 +另外,Rust提供了许多可以使用的自动配置了的条件,比如`target_arch`用来选择不同的代码所基于的架构。对于条件编译的全部细节,可以参看the Rust reference的[conditional compilation]章节。 [conditional compilation]: https://doc.rust-lang.org/reference/conditional-compilation.html @@ -64,13 +64,13 @@ static BUF: [u32; array_size()] = [0u32; array_size()]; ### 宏 -Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在你的源代码之上进行操作,但是Rust宏系统可以在一个更高的级别上操作。存在两种Rust宏: _声明宏_ 和 _过程宏_ 。前者更简单也最常见; 它们看起来像是函数调用,且能扩展成一个完整的表达式,语句,项目,或者模式。过程宏更复杂但是却能让Rust更强大: 它们可以把任一条Rust语法变成一个新的Rust语法。 +Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在你的源代码之上进行操作,但是Rust宏系统可以在一个更高的级别上操作。存在两种Rust宏: _声明宏_ 和 _过程宏_ 。前者更简单也最常见; 它们看起来像是函数调用,且能扩展成一个完整的表达式,语句,项,或者模式。过程宏更复杂但是却能让Rust更强大: 它们可以把任一条Rust语法变成一个新的Rust语法。 [宏系统]: https://doc.rust-lang.org/book/ch19-06-macros.html -通常,你可能想知道在那些你可能使用一个C预处理器宏的地方,能否使用一个声明宏做同样的工作。你能在你的crate中定义它们,且在你的crate中轻松使用它们或者导出给其他人用。但是请注意,因为它们必须扩展成完整的表达式,语句,项或者模式,因此C预处理器的某些用例将无法工作,比如扩展成一个变量名的一部分或者一个列表中不完整的项目集。 +通常,你可能想知道在那些使用一个C预处理器宏的地方,能否使用一个声明宏做同样的工作。你可以在crate中定义它们,且在你的crate中轻松使用它们或者导出给其他人用。但是请注意,因为它们必须扩展成完整的表达式,语句,项或者模式,因此C预处理器宏的某些用例没法用,比如可以扩展成一个变量名的一部分的宏或者可以把列表中的项扩展成不完整的集合的宏。 -和Cargo features一样,值得考虑下你是否真的需要宏。在一些例子中一个常规的函数更容易被理解,它也能被内联成和一个和宏一样的代码。`#[inline]`和`#[inline(always)]` [attributes] 能让你更深入控制这个过程,这里也要小心 - 编译器将自动地从同一个crate的合适的的地方内联函数,因此不恰当地强迫它内联函数实际可能会导致性能下降。 +和Cargo features一样,值得考虑下你是否真的需要宏。在一些例子中一个常规的函数更容易被理解,它也能被内联成和一个和宏一样的代码。`#[inline]`和`#[inline(always)]` [attributes] 能让你更深入控制这个过程,这里也要小心 - 编译器会从同一个crate的恰当的地方自动地内联函数,因此不恰当地强迫它内联函数实际可能会导致性能下降。 [attributes]: https://doc.rust-lang.org/reference/attributes.html#inline-attribute @@ -89,13 +89,13 @@ Rust提供一个极度强大的[宏系统]。虽然C预处理器几乎直接在 * 改变Cargo的编译配置 * 添加额外的静态链接库以进行链接 -现在还不支持post-build脚本,你通常将它用于像是从编译的对象自动生生成二进制文件或者打印编译信息这类任务。 +现在还不支持post-build脚本,通常将它用于像是从编译的对象自动生生成二进制文件或者打印编译信息这类任务中。 ### 交叉编译 -为你的编译系统使用Cargo也能简化交叉编译。在大多数例子里,告诉Cargo `--target thumbv6m-none-eabi`就行了,它会在`target/thumbv6m-none-eabi/debug/myapp`找到一个合适的可执行文件。 +为你的编译系统使用Cargo也能简化交叉编译。在大多数例子里,告诉Cargo `--target thumbv6m-none-eabi`就行了,可以在`target/thumbv6m-none-eabi/debug/myapp`中找到一个合适的可执行文件。 -对于那些并不是Rust原生支持的平台,你将需要自己为那个目标平台编译`libcore`。遇到这样的平台,[Xargo]可以作为Cargo的替代来使用,它可以自动地为你编译`libcore`。 +对于那些并不是Rust原生支持的平台,将需要自己为那个目标平台编译`libcore`。遇到这样的平台,[Xargo]可以作为Cargo的替代来使用,它可以自动地为你编译`libcore`。 [Xargo]: /~https://github.com/japaric/xargo @@ -111,7 +111,7 @@ for(i=0; i ## 管脚类型提供方法去擦除管脚和端口(C-ERASED-PIN) -管脚应该提供类型擦除方法,将其属性从编译时移到运行时,允许在应用中有更多的灵活性。 +从编译时到运行时,管脚都应该提供可以改变属性的类型擦出方法,允许在应用中有更多的灵活性。 案例: @@ -83,9 +83,9 @@ enum Port { ## 管脚状态应该被编码成类型参数 (C-PIN-STATE) -取决于芯片或者芯片系列,管脚可能被配置为具有不同特性的输出或者输入。这个状态应该在类型系统中被编码去避免在错误的状态中使用管脚。 +取决于芯片或者芯片系列,管脚可能被配置为具有不同特性的输出或者输入。这个状态应该编码进类型系统中以避免在错误的状态中使用管脚。 -另外,芯片特定的状态(eg. 驱动强度)可能也用这个办法被编码,使用额外的类型参数。 +另外,也可以用这个方法使用额外的类型参数编码芯片特定的状态(eg. 驱动强度)。 用来改变管脚状态的方法应该被实现成`into_input`和`into_output`方法。 @@ -110,7 +110,7 @@ enum Port { ) -> R ``` -管脚状态应该用sealed traits来绑定。HAL的用户应该不需要添加它们自己的状态。这个traits能提供HAL特定的方法,实现管脚状态API需要这些方法。 +管脚状态应该用sealed traits来绑定。HAL的用户不必添加他们自己的状态。这个traits能提供HAL特定的方法,实现管脚状态API需要这些方法。 案例: diff --git a/src/interoperability/index.md b/src/interoperability/index.md index 6431a155..8de43fad 100644 --- a/src/interoperability/index.md +++ b/src/interoperability/index.md @@ -1,6 +1,6 @@ # 互用性 -Rust和C代码间的互用性始终取决于两种语言间的数据转换。为了实现互操性,在`stdlib`中,有两个专用模块,叫做[`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html)和[`std::os::raw`](https://doc.rust-lang.org/std/os/raw/index.html) 。 +Rust和C代码间的互用性始终取决于两种语言间的数据转换。为了实现互用性,在`stdlib`中,有两个专用模块,叫做[`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html)和[`std::os::raw`](https://doc.rust-lang.org/std/os/raw/index.html) 。 `std::os::raw`处理底层的基本类型,这些类型可以被编译器隐式地转换,因为Rust和C之间的内存布局足够相似或相同。 From 8eb9ff4090d25edce0929c88832baf3dda90ba51 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 6 Jan 2023 10:43:24 +0800 Subject: [PATCH 127/137] minor --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a25146c6..b2da75c0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # The Embedded Rust Book -> 如何使用Rust编程语言在裸板上开发固件的中文文档 +> 一本关于如何使用Rust编程语言在裸板上开发固件的中文文档 -This project is developed and maintained by the [Resources team][team]. +这个项目由 [Resources team][team] 开发和维护。 See [the issue tracker] for more details. This book is a living document, and is updated continuously. @@ -9,7 +9,7 @@ See [the issue tracker] for more details. This book is a living document, and is ## Online Copies of this Book -This book is located at https://docs.rust-embedded.org/book/ +This book is located at https://xxchang.github.io/book/ ## License From 7cdec7401f05d14f45d09d72a9f79b72526e7521 Mon Sep 17 00:00:00 2001 From: Narukara Date: Fri, 13 Jan 2023 23:50:09 +0800 Subject: [PATCH 128/137] update intro --- src/intro/index.md | 18 +++++++++--------- src/intro/install.md | 5 +++-- src/intro/install/macos.md | 8 ++++---- src/intro/install/verify.md | 2 +- src/intro/no-std.md | 16 ++++++++-------- src/intro/tooling.md | 6 +++--- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/intro/index.md b/src/intro/index.md index f9513634..f223f6e9 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -2,7 +2,7 @@ 欢迎阅读嵌入式Rust:一本关于如何在裸机(比如,微处理器)上使用Rust编程语言的入门书籍。 ## 嵌入式Rust是为谁准备的 -嵌入式Rust是为了那些即想要进行嵌入式编程,又想要使用Rust语言所提供的高级概念和安全保障的人们而准备的(参见[Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) +嵌入式Rust是为了那些既想要进行嵌入式编程,又想要使用Rust语言所提供的高级概念和安全保障的人们而准备的(参见[Who Rust Is For](https://doc.rust-lang.org/book/ch00-00-introduction.html)) ## 本书范围 这本书的目的是: @@ -31,15 +31,15 @@ 如果你不熟悉上面提到的东西或者你对这本书中提到的某个特定主题感兴趣,你也许能从这些资源中找到有用的信息。 -| Topic | Resource | Description | +| 主题 | 资源 | 描述 | |--------------|----------|-------------| | Rust | [Rust Book](https://doc.rust-lang.org/book/) | 如果你还不能轻松地使用Rust,我们强烈地建议读这本书。| -| Rust, Embedded | [Discovery Book](https://docs.rust-embedded.org/discovery/) | 如果你从来没有做过嵌入式编程,这本书可能是个更好的开始。 | -| Rust, Embedded | [Embedded Rust Bookshelf](https://docs.rust-embedded.org) | 这里你能找到许多Rust's Embedded Working Group提供的额外资源。| -| Rust, Embedded | [Embedonomicon](https://docs.rust-embedded.org/embedonomicon/) | 在Rust中进行嵌入式编程的细节。 | -| Rust, Embedded | [embedded FAQ](https://docs.rust-embedded.org/faq.html) | 关于嵌入式环境下的Rust会遇到的常见问题。| -| Interrupts | [Interrupt](https://en.wikipedia.org/wiki/Interrupt) | - | -| Memory-mapped IO/Peripherals | [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O) | - | +| Rust, 嵌入式 | [Discovery Book](https://docs.rust-embedded.org/discovery/) | 如果你从来没有做过嵌入式编程,这本书可能是个更好的开始。 | +| Rust, 嵌入式 | [Embedded Rust Bookshelf](https://docs.rust-embedded.org) | 这里你能找到许多Rust's Embedded Working Group提供的额外资源。| +| Rust, 嵌入式 | [Embedonomicon](https://docs.rust-embedded.org/embedonomicon/) | 在Rust中进行嵌入式编程的细节。 | +| Rust, 嵌入式 | [embedded FAQ](https://docs.rust-embedded.org/faq.html) | 关于嵌入式环境下的Rust会遇到的常见问题。| +| 中断 | [Interrupt](https://en.wikipedia.org/wiki/Interrupt) | - | +| 存储映射的IO/外设 | [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O) | - | | SPI, UART, RS232, USB, I2C, TTL | [Stack Exchange about SPI, UART, and other interfaces](https://electronics.stackexchange.com/questions/37814/usart-uart-rs232-usb-spi-i2c-ttl-etc-what-are-all-of-these-and-how-do-th) | - | ### 翻译 @@ -53,7 +53,7 @@ ([repository](/~https://github.com/xxchang/book)) ## 如何使用这本书 -这本书通常假设你是前后阅读的。之后的章节是建立在先前的章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,因为在随后的章节将会再次重温这个主题。 +这本书通常假设你是按顺序阅读的。之后的章节是建立在先前的章节中提到的概念之上的,先前章节可能不会深入一个主题的细节,因为在随后的章节将会再次重温这个主题。 在大多数示例中这本书将使用[STM32F3DISCOVERY]开发板。这个板子是基于ARM Cortex-M架构的,且基本功能与大多数基于这个架构的CPUs功能相似。微处理器的外设和其它实现细节在不同的厂家之间是不同的,甚至来自同一个厂家,不同处理器系列之间也是不同的。 因此我们建议购买[STM32F3DISCOVERY]开发板来尝试这本书中的例子。 diff --git a/src/intro/install.md b/src/intro/install.md index 2e004a83..4ba6ce6c 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -12,7 +12,7 @@ rustc 1.31.1 (b6c32da9b 2018-12-18) ``` 考虑到带宽和磁盘的使用量,默认的安装只支持主机环境的编译。为了添加对ARM Cortex-M架构交叉编译的支持,从下列编译目标中选择一个。对于这本书里使用的STM32F3DISCOVERY板子,使用`thumbv7em-none-eabihf`作为目标。 -Cortex-M0, M0+, and M1 (ARMv6-M 架构): +Cortex-M0, M0+, 和 M1 (ARMv6-M 架构): ``` console rustup target add thumbv6m-none-eabi ``` @@ -55,7 +55,8 @@ cargo install cargo-binutils rustup component add llvm-tools-preview ``` -WINDOWS: prerequisite C++ Build Tools for Visual Studio 2019 is installed. https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16 +WINDOWS: 需要预先安装 C++ Build Tools for Visual Studio 2019。https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16 + ### `cargo-generate` 我们随后将使用这个来从模板生成一个项目。 diff --git a/src/intro/install/macos.md b/src/intro/install/macos.md index b3e51bcf..c5ee5dc6 100644 --- a/src/intro/install/macos.md +++ b/src/intro/install/macos.md @@ -1,6 +1,6 @@ # macOS -All the tools can be install using [Homebrew]: +所有工具都可以通过 [Homebrew] 安装: [Homebrew]: http://brew.sh/ @@ -15,11 +15,11 @@ $ # QEMU $ brew install qemu ``` -> **NOTE** If OpenOCD crashes you may need to install the latest version using: +> **注意** 如果 OpenOCD 崩溃,你可能需要使用如下命令安装最新版本: ```text $ brew install --HEAD openocd ``` -That's all! Go to the [next section]. +就这些! 去往 [下个章节]. -[next section]: verify.md +[下个章节]: verify.md diff --git a/src/intro/install/verify.md b/src/intro/install/verify.md index ef812823..26b2262a 100644 --- a/src/intro/install/verify.md +++ b/src/intro/install/verify.md @@ -56,7 +56,7 @@ openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg 如果这些命令的某条起作用了,那意味着你使用的discovery开发板是一个旧的版本。那也不成问题,但是你要记住这件事,因为随后你的配置可能有点不同。你可以移到[下个章节]了。 -如果这些命令在normal user模式下都没用,尝试下使用root模式运行它们(e.g. `sudo openocd ..`)。如果命令在root模式下起作用,需要检查下[udev rules]是否被正确地设置了。 +如果这些命令在普通用户模式下都没用,尝试下使用root模式运行它们(e.g. `sudo openocd ..`)。如果命令在root模式下起作用,需要检查下[udev rules]是否被正确地设置了。 [udev rules]: linux.md#udev-rules diff --git a/src/intro/no-std.md b/src/intro/no-std.md index 298c32b2..5f27c33c 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -20,15 +20,15 @@ ### 概述 -| feature | no\_std | std | +| 特性 | no\_std | std | |-----------------------------------------------------------|--------|-----| -| heap (dynamic memory) | * | ✓ | -| collections (Vec, HashMap, etc) | ** | ✓ | -| stack overflow protection | ✘ | ✓ | -| runs init code before main | ✘ | ✓ | -| libstd available | ✘ | ✓ | -| libcore available | ✓ | ✓ | -| writing firmware, kernel, or bootloader code | ✓ | ✘ | +| 堆 (动态内存) | * | ✓ | +| collections (Vec, HashMap, 等) | ** | ✓ | +| 堆栈溢出保护 | ✘ | ✓ | +| 在 main 之前运行初始化代码 | ✘ | ✓ | +| libstd 可用 | ✘ | ✓ | +| libcore 可用 | ✓ | ✓ | +| 编写固件、内核或 bootloader 代码 | ✓ | ✘ | \* 只有在你使用了 `alloc` crate 并设置了一个适合的分配器后,比如[alloc-cortex-m]后有效。 diff --git a/src/intro/tooling.md b/src/intro/tooling.md index a402e2f3..85786641 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -6,8 +6,8 @@ - OpenOCD >=0.8. 测试的版本: v0.9.0 and v0.10.0 - 有ARM支持的GDB。强烈建议7.12或者更新的版本。测试版本: 7.10, 7.11 和 8.1 - [`cargo-generate`](/~https://github.com/ashleygwilliams/cargo-generate) 或者 `git`。这些工具都是可选的,但是跟着这本书来使用它们,会更容易。 -- -- 下面的文档将解释我们为什么使用这些工具。安装指令可以在下一页找到。 + +下面的文档将解释我们为什么使用这些工具。安装指令可以在下一页找到。 ## `cargo-generate` 或者 `git` 裸机编程是非标准Rust编程,为了得到正确的程序的内存布局,需要对链接过程进行一些调整,这要求添加一些额外的文件(比如linker scripts)和配置(比如linker flags)。我们已经为你把这些打包进了一个模板里了,你只需要补充缺失的信息(比如项目名和目标硬件的特性)。
@@ -15,7 +15,7 @@ ## `cargo-binutils` `cargo-binutils`是一个Cargo命令的子集,它让我们能轻松使用Rust工具链带来的LLVM工具。这些工具包括LLVM版本的`objdump`,`nm`和`size`,用来查看二进制文件。
-在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工都可以用同一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具,支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 +在GNU binutils之上使用这些工具的好处是,(a)无论你的操作系统是什么,安装这些LLVM工具都可以用同一条命令(`rustup component add llvm-tools-preview`)。(b)像是`objdump`这样的工具,支持所有`rustc`支持的架构--从ARM到x86_64--因为它们都有一样的LLVM后端。 ## `qemu-system-arm` From 0c2d76bc7e60d384c97d17913040a944ee813eae Mon Sep 17 00:00:00 2001 From: Narukara Date: Sat, 14 Jan 2023 22:47:47 +0800 Subject: [PATCH 129/137] modified start --- src/start/interrupts.md | 2 +- src/start/panicking.md | 2 +- src/start/qemu.md | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/start/interrupts.md b/src/start/interrupts.md index b10b66e8..2cd047d7 100644 --- a/src/start/interrupts.md +++ b/src/start/interrupts.md @@ -8,7 +8,7 @@ * 通常需要清除掉导致中断被触发的原因,避免无限地再次进入中断处理函数。 运行时的初始化步骤总是相同的: -* 设置外设在遇到想要的事故发生的时候产生中断请求 +* 设置外设在想要的事件发生时产生中断请求 * 在中断控制器中设置需要的中断处理函数的优先级 * 在中断控制器中使能中断处理函数 diff --git a/src/start/panicking.md b/src/start/panicking.md index d2decf60..2f11f74c 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -1,6 +1,6 @@ # 运行时恐慌(Panicking) -运行时恐慌是Rust语言的一个核心部分。像是索引这样的內建的操作为了存储安全性是运行时检查的。当尝试越界索引时,这会导致运行时恐慌(panic)。 +运行时恐慌是Rust语言的一个核心部分。像是索引这样的内建的操作为了存储安全性是运行时检查的。当尝试越界索引时,这会导致运行时恐慌(panic)。 在标准库中,运行时恐慌的行为被定义成:展开(unwinds)恐慌的线程的栈,除非用户选择在恐慌时终止程序。 diff --git a/src/start/qemu.md b/src/start/qemu.md index 1c3b08c5..209c853a 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -96,7 +96,7 @@ fn main() -> ! { `#![no_std]`指出这个程序将 *不会* 链接标准crate`std`。反而它将会链接到它的子集: `core` crate。 -`#![no_main]`指出这个程序将不会使用标准的且被大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是,因为在`no_std`上下文中使用`main`接口要求开发版的rust 。 +`#![no_main]`指出这个程序将不会使用标准的且被大多数Rust程序使用的`main`接口。使用`no_main`的主要理由是,在`no_std`上下文中使用`main`接口需要 nightly 版的 Rust。 `use panic_halt as _;`。这个crate提供了一个`panic_handler`,它定义了程序陷入`panic`时的行为。我们将会在这本书的[运行时恐慌(Panicking)](panicking.md)章节中覆盖更多的细节。 @@ -105,7 +105,7 @@ fn main() -> ! { [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 -`fn main() -> !`。我们的程序将会是运行在目标板子上的 *唯一* 的进程,因此我们不想要它结束!我们使用一个[divergent function](https://doc.rust-lang.org/rust-by-example/fn/diverging.html) (函数签名中的 `-> !` )来确保在编译时就是这么回事儿。 +`fn main() -> !`。我们的程序将会是运行在目标板子上的 *唯一* 的进程,因此我们不想要它结束!我们使用一个[发散函数](https://doc.rust-lang.org/rust-by-example/fn/diverging.html) (函数签名中的 `-> !` )来确保在编译时就是这么回事儿。 ## 交叉编译 @@ -168,7 +168,7 @@ ELF Header: Section header string table index: 18 ``` -`cargo-size` 能打印二进制项的linker部分的大小。 +`cargo-size` 能打印二进制项的linker section的大小。 ```console cargo size --bin app --release -- -A @@ -198,14 +198,14 @@ section size addr Total 14570 ``` -> ELF linker sections的新手 +> ELF linker sections的复习 > > - `.text` 包含程序指令 > - `.rodata` 包含像是字符串这样的常量 > - `.data` 包含静态分配的初始值*非*零的变量 > - `.bss` 也包含静态分配的初始值*是*零的变量 > - `.vector_table` 是一个我们用来存储向量(中断)表的*非*标准的section -> - `.ARM.attributes` 和 `.debug_*` sections包含元数据,当烧录二进制文件时,它们不会被加载到目标上的。 +> - `.ARM.attributes` 和 `.debug_*` sections包含元数据,当烧录二进制文件时,它们不会被加载到目标上。 **重要**: ELF文件包含像是调试信息这样的元数据,因此它们在*硬盘上的尺寸*没有正确地反应处程序被烧录到设备上时将占据的空间的大小。要*一直*使用`cargo-size`检查一个二进制项的大小。 @@ -230,7 +230,7 @@ main: Reset: 406: bl #0x24e 40a: movw r0, #0x0 - < .. truncated any more instructions .. > + < .. 截断了更多的指令 .. > DefaultHandler_: 656: b #-0x4 @@ -324,7 +324,7 @@ echo $? 让我们看看QEMU命令: -+ `qemu-system-arm`。这是QEMU仿真器。这些QEMU有一些改良版的二进制项;这个仿真器能做ARM机器的全系统仿真。 ++ `qemu-system-arm`。这是QEMU仿真器。这些QEMU二进制项有一些变体,这个仿真器能做ARM机器的全系统仿真。 + `-cpu cortex-m3`。这告诉QEMU去仿真一个Cortex-M3 CPU。指定CPU模型会让我们捕捉到一些误编译错误:比如,运行一个为Cortex-M4F编译的程序,它具有一个硬件FPU,在执行时将会使QEMU报错。 + `-machine lm3s6965evb`。这告诉QEMU去仿真 LM3S6965EVB,一个包含LM3S6965微控制器的评估板。 From 58d1d03828d892c353a2d0901efa73de2139625f Mon Sep 17 00:00:00 2001 From: Narukara Date: Tue, 17 Jan 2023 20:08:44 +0800 Subject: [PATCH 130/137] modified concurrency & design-contracts --- src/concurrency/index.md | 10 +++++----- src/static-guarantees/design-contracts.md | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/concurrency/index.md b/src/concurrency/index.md index 099f865b..f450a0cd 100644 --- a/src/concurrency/index.md +++ b/src/concurrency/index.md @@ -28,7 +28,7 @@ fn main() { ## 全局可变数据 -不像非嵌入式Rust,我们通常不能分配堆和将对那个数据的引用传递进一个新创造的线程中。反而,我们的中断处理函数可能在任何时间被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _静态分配的_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 +不像非嵌入式Rust,我们通常不会奢侈地在堆上分配数据,并将对该数据的引用传递到新创建的线程中。相反,我们的中断处理函数随时可能被调用,且必须知道如何访问我们正在使用的共享内存。从最底层看来,这意味着我们必须有 _静态分配的_ 可变的内存,中断处理函数和main代码都可以引用这块内存。 在Rust中,[`static mut`]这样的变量读取或者写入总是unsafe的,因为不特别关注它们的话,可能会触发一个竞态条件,对变量的访问在中途就被一个也访问那个变量的中断打断了。 @@ -101,11 +101,11 @@ fn timer() { 这解决了我们眼前的问题,但是我们仍然要编写许多unsafe的代码,我们需要仔细推敲这些代码,有些我们可能不需要使用临界区。因为每个临界区暂时暂停了中断处理,就会带来一些相关的成本,一些额外的代码大小,更高的中断延迟和抖动(中断可能花费很长时间去处理,等待被处理的时间变化非常大)。这是否是个问题取决于你的系统,但是通常,我们想要避免它。 -值得注意的是,虽然一个临界区保障了不会发生中断,但是它在多核系统上不提供一个排他性保证(exclusivity guarantee)!其它核可能很开心访问与你的核一样的内存区域,即使没有中断。如果你正在使用多核,你将需要更强的同步原语(synchronisation primitives)。 +值得注意的是,虽然一个临界区保障了不会发生中断,但是它在多核系统上不提供一个排他性保证(exclusivity guarantee)!其它核可能很开心地访问与你的核一样的内存区域,即使没有中断。如果你正在使用多核,你将需要更强的同步原语(synchronisation primitives)。 ## 原子访问 -在一些平台上,可以使用特定的原子指令,它保障了读取-修改-写回操作。针对Cortex-M: `thumbv6`(Cortex-M0,Cortex-M0+)只提供原子读取和存取指令,而`thumv7`(Cortex-M3和其上)提供完整的比较和交换(CAS)指令。这些CAS指令可以替代过重的禁用所有中断的方法: 我们可以尝试执行加法操作,它在大多数情况下都会成功,但是如果它被中断了它将会自动重试完整的加法操作。这些原子操作甚至在多核间也是安全的。 +在一些平台上,可以使用特定的原子指令,它保障了读取-修改-写回操作。针对Cortex-M: `thumbv6`(Cortex-M0,Cortex-M0+)只提供原子读取和存取指令,而`thumv7`(Cortex-M3及以上)提供完整的比较和交换(CAS)指令。这些CAS指令可以替代过重的禁用所有中断的方法: 我们可以尝试执行加法操作,它在大多数情况下都会成功,但是如果它被中断了它将会自动重试完整的加法操作。这些原子操作甚至在多核间也是安全的。 ```rust,ignore use core::sync::atomic::{AtomicUsize, Ordering}; @@ -119,7 +119,7 @@ fn main() -> ! { loop { let state = read_signal_level(); if state && !last_state { - // Use `fetch_add` to atomically add 1 to COUNTER + // 使用 `fetch_add` 原子性地给 COUNTER 加一 COUNTER.fetch_add(1, Ordering::Relaxed); } last_state = state; @@ -128,7 +128,7 @@ fn main() -> ! { #[interrupt] fn timer() { - // Use `store` to write 0 directly to COUNTER + // 使用 `store` 将 0 直接写入 COUNTER COUNTER.store(0, Ordering::Relaxed) } ``` diff --git a/src/static-guarantees/design-contracts.md b/src/static-guarantees/design-contracts.md index 1438e52e..61a87697 100644 --- a/src/static-guarantees/design-contracts.md +++ b/src/static-guarantees/design-contracts.md @@ -9,13 +9,13 @@ | | | 1 | 使能 | 使能GPIO | | 方向 | 1 | 0 | 输入 | 方向设置成输入 | | | | 1 | 输出 | 方向设置成输出 | -| 输入模式 | 2..3 | 00 | hi-z | 输入设置为高阻态 | +| 输入模式 | 2..3 | 00 | 高阻态 | 输入设置为高阻态 | | | | 01 | 下拉 | 下拉输入管脚 | | | | 10 | 上拉 | 上拉输入管脚 | | | | 11 | n/a | 无效状态。不要设置 | | 输出模式 | 4 | 0 | 拉低 | 把管脚设置成低电平 | | | | 1 | 拉高 | 把管脚设置成高电平 | -| 输入状态 | 5 | x | in-val | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 | +| 输入状态 | 5 | x | 输入电平 | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 | 如果在使用底层硬件之前检查状态,在运行时强制遵守设计约定,代码可能像这一样: @@ -173,7 +173,7 @@ impl GpioConfig { } } -/// 这些方法可能被用于使能一个输入GPIO +/// 这些方法可能被用于任意一个使能的输入GPIO impl GpioConfig { pub fn bit_is_set(&self) -> bool { self.periph.read().input_status.bit_is_set() @@ -215,14 +215,14 @@ impl GpioConfig { ```rust,ignore /* - * 案例 1: Unconfigured to High-Z input + * 案例 1: 从未配置到高阻输入 */ let pin: GpioConfig = get_gpio(); // 不能这么做,pin没有被使能 // pin.into_input_pull_down(); -// 现在从unconfigured to a high-z input打开管脚 +// 现在把管脚从未配置变为高阻输入 let input_pin = pin.into_enabled_input(); // 从管脚读取 @@ -232,7 +232,7 @@ let pin_state = input_pin.bit_is_set(); // input_pin.set_bit(true); /* - * 案例 2: High-Z 输入到下拉输入 + * 案例 2: 高阻输入到下拉输入 */ let pulled_low = input_pin.into_input_pull_down(); let pin_state = pulled_low.bit_is_set(); From 1cd5be83ce5de3dae473679ce1de47f61df2d450 Mon Sep 17 00:00:00 2001 From: AgainstWar Date: Wed, 18 Jan 2023 23:48:34 +0800 Subject: [PATCH 131/137] =?UTF-8?q?Windows=20=E5=AE=89=E8=A3=85=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=B1=89=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/intro/install/windows.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/intro/install/windows.md b/src/intro/install/windows.md index e0074035..86d60474 100644 --- a/src/intro/install/windows.md +++ b/src/intro/install/windows.md @@ -2,9 +2,9 @@ ## `arm-none-eabi-gdb` -ARM provides `.exe` installers for Windows. Grab one from [here][gcc], and follow the instructions. -Just before the installation process finishes tick/select the "Add path to environment variable" -option. Then verify that the tools are in your `%PATH%`: +ARM提供了用于Windows的`.exe`安装程序。从[这里][gcc]获取, 然后按照说明操作。 +在完成安装之前,勾选/选择"Add path to environment variable"选项。 +然后验证环境变量是否添加到 `%PATH%`中: ``` text $ arm-none-eabi-gdb -v @@ -16,15 +16,12 @@ GNU gdb (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 8.1.0.20180315- ## OpenOCD -There's no official binary release of OpenOCD for Windows but if you're not in the mood to compile -it yourself, the xPack project provides a binary distribution, [here][openocd]. Follow the -provided installation instructions. Then update your `%PATH%` environment variable to -include the path where the binaries were installed. (`C:\Users\USERNAME\AppData\Roaming\xPacks\@xpack-dev-tools\openocd\0.10.0-13.1\.content\bin\`, -if you've been using the easy install) +OpenOCD 官方没有提供Windows的二进制版本, 若你没有心情去折腾编译,[这里][openocd]有xPack提供的一个二进制发布.。按照说明进行安装。然后更新你的`%PATH%` 环境变量,将安装目录包括进去。 (`C:\Users\USERNAME\AppData\Roaming\xPacks\@xpack-dev-tools\openocd\0.10.0-13.1\.content\bin\`, +如果使用简易安装) [openocd]: https://xpack.github.io/openocd/ -Verify that OpenOCD is in your `%PATH%` with: +使用以下命令验证OpenOCD是否在你的`%PATH%`环境变量中 : ``` text $ openocd -v @@ -34,17 +31,16 @@ Open On-Chip Debugger 0.10.0 ## QEMU -Grab QEMU from [the official website][qemu]. +从[官网][qemu]获取QEMU。 [qemu]: https://www.qemu.org/download/#windows ## ST-LINK USB driver -You'll also need to install [this USB driver] or OpenOCD won't work. Follow the installer -instructions and make sure you install the right version (32-bit or 64-bit) of the driver. +你还需要安装这个 [USB驱动] 否则OpenOCD将无法工作。按照安装程序的说明,确保你安装了正确版本(32位或64位)的驱动程序。 -[this USB driver]: http://www.st.com/en/embedded-software/stsw-link009.html +[USB驱动]: http://www.st.com/en/embedded-software/stsw-link009.html -That's all! Go to the [next section]. +以上是全部内容!转到 [下一节]。 -[next section]: verify.md +[下一节]: verify.md From 16792ae1cd937c1e6879149b0674387ef4fdd70f Mon Sep 17 00:00:00 2001 From: AgainstWar Date: Thu, 19 Jan 2023 10:56:11 +0800 Subject: [PATCH 132/137] =?UTF-8?q?MacOS=20=E5=AE=89=E8=A3=85=E9=83=A8?= =?UTF-8?q?=E5=88=86=E6=B1=89=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/intro/install/macos.md | 8 ++++---- src/intro/install/windows.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/intro/install/macos.md b/src/intro/install/macos.md index b3e51bcf..d3bf20a3 100644 --- a/src/intro/install/macos.md +++ b/src/intro/install/macos.md @@ -1,6 +1,6 @@ # macOS -All the tools can be install using [Homebrew]: +所有的工具都可以用[Homebrew]来安装: [Homebrew]: http://brew.sh/ @@ -15,11 +15,11 @@ $ # QEMU $ brew install qemu ``` -> **NOTE** If OpenOCD crashes you may need to install the latest version using: +> **注意** 如果OpenOCD崩溃了,你可能需要用以下方法安装最新版本: ```text $ brew install --HEAD openocd ``` -That's all! Go to the [next section]. +以上是全部内容!转到 [下个章节]。 -[next section]: verify.md +[下个章节]: verify.md diff --git a/src/intro/install/windows.md b/src/intro/install/windows.md index 86d60474..c1264395 100644 --- a/src/intro/install/windows.md +++ b/src/intro/install/windows.md @@ -41,6 +41,6 @@ Open On-Chip Debugger 0.10.0 [USB驱动]: http://www.st.com/en/embedded-software/stsw-link009.html -以上是全部内容!转到 [下一节]。 +以上是全部内容!转到 [下个章节]。 -[下一节]: verify.md +[下个章节]: verify.md From 6e7b145c67056da1111ab919fb4d5cc19d97c4c3 Mon Sep 17 00:00:00 2001 From: XxChang Date: Fri, 27 Jan 2023 01:16:08 +0800 Subject: [PATCH 133/137] fix typo --- src/start/panicking.md | 2 +- src/static-guarantees/design-contracts.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/start/panicking.md b/src/start/panicking.md index d2decf60..05dcd09e 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -2,7 +2,7 @@ 运行时恐慌是Rust语言的一个核心部分。像是索引这样的內建的操作为了存储安全性是运行时检查的。当尝试越界索引时,这会导致运行时恐慌(panic)。 -在标准库中,运行时恐慌的行为被定义成:展开(unwinds)恐慌的线程的栈,除非用户选择在恐慌时终止程序。 +在标准库中,运行时恐慌的行为被定义成:展开(unwinds)恐慌的线程的栈,除非用户自己选择在恐慌时终止程序。 然而在没有标准库的程序中,运行时恐慌的行为是未被定义了的。通过声明一个 `#[painc_handler]` 函数可以选择一个运行时恐慌的行为。 diff --git a/src/static-guarantees/design-contracts.md b/src/static-guarantees/design-contracts.md index 1438e52e..58c84e7c 100644 --- a/src/static-guarantees/design-contracts.md +++ b/src/static-guarantees/design-contracts.md @@ -18,7 +18,7 @@ | 输入状态 | 5 | x | in-val | 如果输入 < 1.5v 为0,如果输入 >= 1.5v 为1 | -如果在使用底层硬件之前检查状态,在运行时强制遵守设计约定,代码可能像这一样: +如果在使用底层硬件之前检查硬件的状态,在运行时强制用户遵守设计约定,代码可能像这一样: ```rust,ignore /// GPIO接口 @@ -103,7 +103,7 @@ impl GpioConfig { ## 类型状态(Type states) -但是,如果我们使用Rust的类型系统去强制遵守状态转换的规则会怎样?看下这个例子: +但是,如果我们让Rust的类型系统去强制遵守状态转换的规则会怎样?看下这个例子: ```rust,ignore /// GPIO接口 @@ -253,4 +253,4 @@ output_pin.set_bit(true); 因为我们在编译时完全强制遵守设计约定,这造成了没有运行时开销。当管脚处于输入模式时时,是不可能设置输出模式的。必须先把它设置成一个输出管脚,然后再设置输出模式。因为在执行一个函数前会检查现在的状态,因此没有运行时消耗。 -也因为这些状态被类型系统强制遵守,因此没有为这个接口的使用者留太多的犯错余地。如果它们尝试执行一个非法的状态转换,代码将不会编译! +也因为这些状态被类型系统强制遵守,因此没有为这个接口的使用者留太多的犯错余地。如果它们尝试执行一个非法的状态转换,代码将不会编译成功! From 0d8c55c49403d5eb75376398e5f813b0e0dbaf49 Mon Sep 17 00:00:00 2001 From: Narukara Date: Fri, 10 Feb 2023 15:26:29 +0800 Subject: [PATCH 134/137] add language = "zh-CN" --- book.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/book.toml b/book.toml index 4ed695de..37e67e54 100644 --- a/book.toml +++ b/book.toml @@ -1,5 +1,6 @@ [book] authors = ["James Munns"] +language = "zh-CN" multilingual = false src = "src" title = "The Embedded Rust Book" From 28b868d27226f28814792826c083017b7a3bb5f3 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 31 Aug 2023 21:59:45 +0800 Subject: [PATCH 135/137] minor --- src/design-patterns/hal/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/design-patterns/hal/index.md b/src/design-patterns/hal/index.md index af0a1917..eded1f29 100644 --- a/src/design-patterns/hal/index.md +++ b/src/design-patterns/hal/index.md @@ -1,6 +1,6 @@ # HAL设计模式 -这是一组关于使用Rust为微控制器写硬件抽象层的常见的和推荐的模式。当为微控制器编写HALs时,除了现有的 [Rust API 指南] 外,这些模式也会被使用。 +这是一组关于使用Rust为微控制器写硬件抽象层的常见的和推荐的模式。当为微控制器编写HALs时,除了现有的 [Rust API 指南] 外,也可以使用这些模式。 [Rust API 指南]: https://rust-lang.github.io/api-guidelines/ From 8dc5568ff48bed35c90175bda1ad4294c2dbb4d6 Mon Sep 17 00:00:00 2001 From: XxChang Date: Thu, 31 Aug 2023 22:10:04 +0800 Subject: [PATCH 136/137] minor --- src/intro/no-std.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/intro/no-std.md b/src/intro/no-std.md index 5f27c33c..dd403fb2 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -26,13 +26,13 @@ | collections (Vec, HashMap, 等) | ** | ✓ | | 堆栈溢出保护 | ✘ | ✓ | | 在 main 之前运行初始化代码 | ✘ | ✓ | -| libstd 可用 | ✘ | ✓ | -| libcore 可用 | ✓ | ✓ | +| 能用 libstd | ✘ | ✓ | +| 能用 libcore | ✓ | ✓ | | 编写固件、内核或 bootloader 代码 | ✓ | ✘ | -\* 只有在你使用了 `alloc` crate 并设置了一个适合的分配器后,比如[alloc-cortex-m]后有效。 +\* 只有在你使用了 `alloc` crate 并设置了一个适合的分配器后,比如[alloc-cortex-m]后可用。 -\** 只有在你使用了 `collections` crate 并配置了一个全局默认的分配器后有效。 +\** 只有在你使用了 `collections` crate 并配置了一个全局默认的分配器后可用。 [alloc-cortex-m]: /~https://github.com/rust-embedded/alloc-cortex-m From d7ec79385c7bb84e1095030d045d19af6bfc9b57 Mon Sep 17 00:00:00 2001 From: XxChang Date: Sun, 10 Sep 2023 00:11:59 +0800 Subject: [PATCH 137/137] minor --- src/interoperability/c-with-rust.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index ec204419..ae965edf 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -1,21 +1,21 @@ # 使用C的Rust -在一个Rust项目中使用C或者C++,由两个主要部分组成: +要在一个Rust项目中使用C或者C++,主要有两个步骤: -+ 使用Rust封装要暴露的C API来用 -+ 编译要和Rust代码集成的C或者C++代码 ++ 用Rust封装要暴露出来使用的C API ++ 编译要和Rust代码集成在一起的C或者C++代码 -因为对于目标的Rust编译器,C++没有一个稳定的ABI,当将Rust和C或者C++结合时,建议使用`C`。 +因为对于Rust编译器来说,C++没有一个稳定的ABI,当要将Rust和C或者C++结合时,建议优先选择`C`。 ## 定义接口 -从Rust消费C或者C++代码之前,必须定义(在Rust中)在被链接的代码中存在什么数据类型和函数签名。在C或者C++中,你要包括一个头文件(`.h`或者`.hpp`),其定义了这个数据。在Rsut中,必须手动地将这些定义翻译成Rust,或者使用一个工具去生成这些定义。 +在Rust消费C或者C++代码之前,必须定义(在Rust中定义),在要被链接的代码中存在什么数据类型和函数签名。在C或者C++中,你要包含一个头文件(`.h`或者`.hpp`),其定义了这个数据。而在Rust中,必须手动地将这些定义翻译成Rust,或者使用一个工具去生成这些定义。 -首先,我们将介绍如何将这些定义从C/C++手动转换为Rust。 +首先,我们将介绍如何将这些定义从C/C++手动地转换为Rust。 ### 封装C函数和数据类型 -通常,用C或者C++写的库会提供一个头文件,头文件定义了所有的类型和用于公共接口的函数。一个示例文件可能如下所示: +通常,用C或者C++写的库会提供一个头文件,头文件定义了所有的类型和用于公共接口的函数。如下是一个示例文件: ```C /* 文件: cool.h */ @@ -53,7 +53,7 @@ extern "C" { pub struct CoolStruct { ... } ``` -默认,Rust不会保证包含在一个`struct`中的数据的大小,填充,或者顺序。为了保证与C代码兼容,我们使用`#[repr(C)]`属性,它指示Rust编译器总是使用和C一样的规则去组织一个结构体中的数据。 +默认,Rust不会保证包含在`struct`中的数据的大小,填充,或者顺序。为了保证与C代码兼容,我们使用`#[repr(C)]`属性,它指示Rust编译器总是使用和C一样的规则去组织一个结构体中的数据。 ```rust,ignore pub x: cty::c_int, @@ -66,7 +66,7 @@ pub y: cty::c_int, extern "C" { pub fn cool_function( ... ); } ``` -这个语句定义了一个使用C ABI的函数的签名,被叫做`cool_function`。通过定义签名而不定义函数的主体,这个函数的定义将需要在其它地方定义,或者从一个静态库链接进最终的库或者一个二进制文件中。 +这个语句定义了一个使用C ABI的函数的签名,叫做`cool_function`。因为只定义了签名而没有定义函数的主体,所以这个函数的定义将需要在其它地方定义,或者从一个静态库链接进最终的库或者一个二进制文件中。 ```rust,ignore i: cty::c_int, @@ -76,13 +76,13 @@ extern "C" { pub fn cool_function( ... ); } 与我们上面的数据类型一样,我们使用C兼容的定义去定义函数参数的数据类型。为了清晰可见,我们还保留了相同的参数名。 -这里我们有个新类型,`*mut CoolStruct` 。因为C没有Rust中的引用的概念,其看起来像是这个: `&mut CoolStruct`,所以我们使用一个裸指针。因为解引用这个指针是`unsafe`的,且实际上指针可能是一个`null`指针,因此当与C或者C++代码交互时必须要小心对待那些Rust做出的安全保证。 +这里我们有了一个新类型,`*mut CoolStruct` 。因为C没有Rust中像 `&mut CoolStruct` 这样的引用,替代的是一个裸指针。所以解引用这个指针是`unsafe`的,因为这个指针实际上可能是一个`null`指针,因此当与C或者C++代码交互时必须要小心对待那些Rust做出的安全保证。 ### 自动产生接口 -有一个叫做[bindgen]的工具,它可以自动执行这些转换,而不用手动生成这些接口那么繁琐且容易出错。关于[bindgen]使用的指令,请参考[bindgen user's manual],然而经典的过程有下面几步: +有一个叫做[bindgen]的工具,它可以自动执行这些转换,而不用手动生成这些接口,手动进行这样的操作非常繁琐且容易出错。关于[bindgen]的使用指令,可以参考[bindgen user's manual],常用的步骤大致如下: -1. 收集所有定义了你可能在Rust中用到的数据类型或者接口的C或者C++头文件。 +1. 收集所有定义了你可能在Rust中会用到的数据类型或者接口的C或者C++头文件。 2. 写一个`bindings.h`文件,其`#include "..."`每一个你在步骤一中收集的文件。 3. 将这个`bindings.h`文件和任何用来编译你代码的编译标识发给`bindgen`。贴士: 使用`Builder.ctypes_prefix("cty")` / `--ctypes-prefix=cty` 和 `Builder.use_core()` / `--use-core` 去使生成的代码兼容`#![no_std]` 4. `bindgen`将会在终端窗口输出生成的Rust代码。这个文件可能会被通过管道发送给你项目中的一个文件,比如`bindings.rs` 。你可能要在你的Rust项目中使用这个文件来与被编译和链接成一个外部库的C/C++代码交互。贴士: 如果你的类型在生成的绑定中被前缀了`cty`,不要忘记使用[`cty`](https://crates.io/crates/cty) crate 。