diff --git a/README.md b/README.md index 2ed54e3b..4185f931 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,31 @@ -# The Embedded Rust Book +# Книга по Embedded Rust -> Documentation on how to use the Rust Programming Language to develop firmware for bare metal (microcontroller) devices +> Документация по использованию языка программирования Rust для разработки прошивок для устройств без операционной системы (микроконтроллеры) -This project is developed and maintained by the [Resources team][team]. +Данный перевод был сделан с помощью нейросети, поэтому могут быть ошибки, которые при нахождении будут исправлены -See [the issue tracker] for more details. This book is a living document, and is updated continuously. +Этот проект разрабатывается и поддерживается [командой ресурсов][team]. -[the issue tracker]: https://github.com/rust-embedded/book/issues +Подробности можно найти в [отслеживателе задач]. Эта книга является живым документом и постоянно обновляется. -## Online Copies of this Book +[отслеживатель задач]: https://github.com/rust-embedded/book/issues -This book is located at https://docs.rust-embedded.org/book/ +## Онлайн-копии этой книги -## License +Эта книга доступна по адресу https://docs.rust-embedded.org/book/ -The Embedded Rust Book (this project) is distributed under the following licenses: +## Лицензия -* The code samples and free-standing Cargo projects contained within this book are licensed under the terms of both the [MIT License] and the [Apache License v2.0]. -* The written prose contained within this book is licensed under the terms of the Creative Commons [CC-BY-SA v4.0] license. +Книга по Embedded Rust (этот проект) распространяется под следующими лицензиями: -Copies of the licenses used by this project may also be found here: +* Примеры кода и отдельные проекты Cargo, содержащиеся в этой книге, лицензируются на условиях [MIT License] и [Apache License v2.0]. +* Книга лицензируется на условиях лицензии Creative Commons [CC-BY-SA v4.0]. -* [MIT License Hosted] -* [Apache License v2.0 Hosted] -* [CC-BY-SA v4.0 Hosted] +Копии лицензий, используемых этим проектом, также можно найти здесь: + +* [MIT License] +* [Apache License v2.0] +* [CC-BY-SA v4.0] [MIT License]: ./LICENSE-MIT [Apache License v2.0]: ./LICENSE-APACHE @@ -32,15 +34,13 @@ Copies of the licenses used by this project may also be found here: [Apache License v2.0 Hosted]: http://www.apache.org/licenses/LICENSE-2.0 [CC-BY-SA v4.0 Hosted]: https://creativecommons.org/licenses/by-sa/4.0/legalcode -### Contribution +### Вклад -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions. +Если не указано иное, любой вклад, намеренно представленный для включения в этот проект, как определено в лицензии Apache-2.0, лицензируется, как указано выше, без дополнительных условий. -## Code of Conduct +## Кодекс поведения -Contribution to this crate is organized under the terms of the [Rust Code of -Conduct][CoC], the maintainer of this crate, the [Resources team][team], promises -to intervene to uphold that code of conduct. +Вклад в этот проект организуется в соответствии с [Кодексом поведения Rust][CoC], и сопровождающий этого проекта, [команда ресурсов][team], обязуется вмешиваться для соблюдения этого кодекса. [CoC]: CODE_OF_CONDUCT.md [team]: https://github.com/rust-embedded/wg#the-resources-team diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f62fa633..dfcf9f4f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,60 +1,59 @@ -# Summary +# Содержание -- [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) - - [Memory-mapped Registers](./start/registers.md) - - [Semihosting](./start/semihosting.md) - - [Panicking](./start/panicking.md) - - [Exceptions](./start/exceptions.md) - - [Interrupts](./start/interrupts.md) - - [IO](./start/io.md) -- [Peripherals](./peripherals/index.md) - - [A first attempt in Rust](./peripherals/a-first-attempt.md) - - [The Borrow Checker](./peripherals/borrowck.md) - - [Singletons](./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) -- [Portability](./portability/index.md) -- [Concurrency](./concurrency/index.md) -- [Collections](./collections/index.md) -- [Design Patterns](./design-patterns/index.md) - - [HALs](./design-patterns/hal/index.md) - - [Checklist](./design-patterns/hal/checklist.md) - - [Naming](./design-patterns/hal/naming.md) - - [Interoperability](./design-patterns/hal/interoperability.md) - - [Predictability](./design-patterns/hal/predictability.md) + - [Аппаратное обеспечение](./start/hardware.md) + - [Регистры с отображением в память](./start/registers.md) + - [Полухостинг](./start/semihosting.md) + - [Паника](./start/panicking.md) + - [Исключения](./start/exceptions.md) + - [Прерывания](./start/interrupts.md) + - [Ввод/вывод](./start/io.md) +- [Периферийные устройства](./peripherals/index.md) + - [Первая попытка на Rust](./peripherals/a-first-attempt.md) + - [Проверка заимствования](./peripherals/borrowck.md) + - [Синглтоны](./peripherals/singletons.md) +- [Статические гарантии](./static-guarantees/index.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/index.md) +- [Шаблоны проектирования](./design-patterns/index.md) + - [HAL](./design-patterns/hal/index.md) + - [Контрольный список](./design-patterns/hal/checklist.md) + - [Именование](./design-patterns/hal/naming.md) + - [Взаимодействие](./design-patterns/hal/interoperability.md) + - [Предсказуемость](./design-patterns/hal/predictability.md) - [GPIO](./design-patterns/hal/gpio.md) -- [Tips for embedded C developers](./c-tips/index.md) - -- [Interoperability](./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) - - [Optimizations: The speed size tradeoff](./unsorted/speed-vs-size.md) - - [Performing Math Functionality](./unsorted/math.md) +- [Советы для разработчиков на C для встраиваемых систем](./c-tips/index.md) + +- [Взаимодействие](./interoperability/index.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) --- -[Appendix A: Glossary](./appendix/glossary.md) +[Приложение A: Глоссарий](./appendix/glossary.md) diff --git a/src/appendix/glossary.md b/src/appendix/glossary.md index 6a898b49..5bc95cd5 100644 --- a/src/appendix/glossary.md +++ b/src/appendix/glossary.md @@ -1,64 +1,55 @@ -# 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 -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). +Крейт поддержки платы (Board Support Crate) предоставляет высокоуровневый интерфейс, настроенный для конкретной платы. Обычно он зависит от крейта [HAL](#hal). +Более подробное описание можно найти на [странице о регистрах с отображением в память](../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 -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) предоставляет удобный для разработчика интерфейс к функциям и периферийным устройствам микроконтроллера. Обычно он реализуется поверх крейта [Peripheral Access Crate (PAC)](#pac). +Также он может реализовывать трейты из крейта [`embedded-hal`](https://crates.io/crates/embedded-hal). +Более подробное описание можно найти на [странице о регистрах с отображением в память](../start/registers.md) +или для более общего обзора смотрите [это видео](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` или Inter-IC. Это протокол, предназначенный для коммуникации между аппаратными компонентами внутри одной интегральной схемы. Подробности смотрите [здесь][i2c]. [i2c]: https://en.wikipedia.org/wiki/I2c ### 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) -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). +Крейт доступа к периферийным устройствам (Peripheral Access Crate) предоставляет доступ к периферийным устройствам микроконтроллера. Это один из низкоуровневых крейтов, который обычно генерируется непосредственно из предоставленного [SVD](#svd), часто с использованием [svd2rust](https://github.com/rust-embedded/svd2rust/). Крейт [уровня абстракции аппаратного обеспечения](#hal) обычно зависит от этого крейта. +Более подробное описание можно найти на [странице о регистрах с отображением в память](../start/registers.md) +или для более общего обзора смотрите [это видео](https://youtu.be/vLYit_HHPaY). ### SPI -Serial Peripheral Interface. See [here][spi] for more information. +Интерфейс периферийных устройств (Serial Peripheral Interface). Подробности смотрите [здесь][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-файла, используемый для описания представления микроконтроллера с точки зрения программиста. Подробности можно прочитать на +[сайте документации ARM CMSIS](https://www.keil.com/pack/doc/CMSIS/SVD/html/index.html). ### UART -Universal asynchronous receiver-transmitter. See [here][uart] for more information. +Универсальный асинхронный приёмопередатчик (Universal Asynchronous Receiver-Transmitter). Подробности смотрите [здесь][uart]. [uart]: https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter ### USART -Universal synchronous and asynchronous receiver-transmitter. See [here][usart] for more information. +Универсальный синхронный и асинхронный приёмопередатчик (Universal Synchronous and Asynchronous Receiver-Transmitter). Подробности смотрите [здесь][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 0c67108e..2f736e23 100644 --- a/src/c-tips/index.md +++ b/src/c-tips/index.md @@ -1,37 +1,24 @@ -# Tips for embedded C developers +# Советы для разработчиков на embedded 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. +Эта глава собирает различные советы, которые могут быть полезны опытным разработчикам на embedded C, желающим начать писать на Rust. Особое внимание будет уделено тому, как вещи, к которым вы уже привыкли в C, отличаются в Rust. -## Preprocessor +## Препроцессор -In embedded C it is very common to use the preprocessor for a variety of -purposes, such as: +В embedded 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. +Наиболее близким аналогом `#ifdef ... #endif` в Rust являются [функции Cargo][Cargo features]. Они немного более формальны, чем препроцессор C: все возможные функции явно перечислены для каждого крейта и могут быть либо включены, либо выключены. Функции включаются при перечислении крейта как зависимости и являются аддитивными: если любой крейт в вашем дереве зависимостей включает функцию для другого крейта, эта функция будет включена для всех пользователей этого крейта. [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`: +Например, у вас может быть крейт, предоставляющий библиотеку примитивов обработки сигналов. Каждый из них может требовать дополнительного времени на компиляцию или объявлять большую таблицу констант, которую вы хотите избежать. Вы можете объявить функцию Cargo для каждого компонента в вашем `Cargo.toml`: ```toml [features] @@ -39,10 +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,29 +38,17 @@ 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. +Аналогично вы можете включать блоки кода только если функция _не_ включена или если любая комбинация функций включена или не включена. -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` для выбора разного кода в зависимости от архитектуры. Для полного описания поддержки условной компиляции обратитесь к главе [условная компиляция][conditional compilation] справочника Rust. [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 -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` нужно использовать несколько раз. Стоит отметить, что в большинстве случаев лучше просто включить весь код и позволить компилятору удалить неиспользуемый код при оптимизации: это проще для вас и ваших пользователей, и в общем случае компилятор хорошо справляется с удалением неиспользуемого кода. -### 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`, функции, которые гарантированно вычисляемы во время компиляции и поэтому могут использоваться там, где требуются константы, например, в размере массивов. Это можно использовать вместе с функциями, упомянутыми выше, например: ```rust const fn array_size() -> usize { @@ -86,228 +61,152 @@ 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`. +Это новинка в стабильном Rust с версии 1.31, поэтому документация все еще скудна. Функциональность, доступная для `const fn`, также очень ограничена на момент написания; в будущих релизах Rust ожидается расширение того, что разрешено в `const fn`. -### 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 -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 предоставляет чрезвычайно мощную [систему макросов][macro system]. В то время как препроцессор C работает почти напрямую с текстом вашего исходного кода, система макросов Rust работает на более высоком уровне. Существуют два вида макросов Rust: _макросы по примеру_ и _процедурные макросы_. Первые проще и наиболее распространены; они выглядят как вызовы функций и могут расширяться в полное выражение, утверждение, элемент или шаблон. Процедурные макросы более сложны, но позволяют чрезвычайно мощные дополнения к языку Rust: они могут преобразовывать произвольный синтаксис Rust в новый синтаксис Rust. [macro system]: 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. - -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. +В общем, там, где вы могли бы использовать макрос препроцессора C, вы, вероятно, хотите посмотреть, может ли макрос по примеру справиться с задачей. Они могут быть определены в вашем крейте и легко использоваться вашим крейтом или экспортироваться для других пользователей. Имейте в виду, что поскольку они должны расширяться в полные выражения, утверждения, элементы или шаблоны, некоторые случаи использования макросов препроцессора C не будут работать, например, макрос, который расширяется в часть имени переменной или неполный набор элементов в списке. + +Как и с функциями Cargo, стоит подумать, нужен ли вам макрос вообще. Во многих случаях обычная функция проще для понимания и будет встроена в тот же код, что и макрос. Атрибуты `#[inline]` и `#[inline(always)]` [attributes] дают вам дополнительный контроль над этим процессом, хотя здесь тоже следует проявлять осторожность — компилятор автоматически встраивает функции из того же крейта, где это уместно, так что принуждение к этому неуместно может привести к снижению производительности. [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, -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 собираются с использованием Cargo (хотя это не обязательно). Это решает многие сложные проблемы традиционных систем сборки. Однако вы можете захотеть настроить процесс сборки. Cargo предоставляет [скрипты `build.rs`][`build.rs` scripts] для этой цели. Это скрипты на Rust, которые могут взаимодействовать с системой сборки Cargo по мере необходимости. [`build.rs` scripts]: 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 в исполняемый файл +* генерация скриптов линковки во время сборки в зависимости от выбранных функций или другой логики +* изменение конфигурации сборки 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 -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]; int i; -for(i=0; i, end: usize, @@ -65,19 +51,19 @@ 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(); let align = layout.align(); let align_mask = !(align - 1); - // move start up to the next alignment boundary + // перемещаем начало к следующей границе выравнивания let start = (*head + align - 1) & align_mask; if start + size > self.end { - // a null pointer signal an Out Of Memory condition + // нулевой указатель сигнализирует об условии Out Of Memory ptr::null_mut() } else { *head = start + size; @@ -87,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), @@ -101,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. +Помимо выбора глобального распределителя, пользователь также должен определить, как обрабатываются ошибки Out Of Memory (OOM), используя *нестабильный* атрибут `alloc_error_handler`. ``` rust,ignore #![feature(alloc_error_handler)] @@ -118,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] @@ -134,16 +118,14 @@ fn main() -> ! { } ``` -If you have used the collections in the `std` crate then these will be familiar -as they are exact same implementation. +Если вы использовали коллекции в крейте `std`, то эти будут знакомы, поскольку это точно такая же реализация. -## 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 -// heapless version: v0.4.x +// версия heapless: v0.4.x use heapless::Vec; use heapless::consts::*; @@ -157,105 +139,51 @@ 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 элементов, то есть вектор может содержать максимум 8 элементов. Это указано `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. - -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>`). - -## Trade-offs - -Keep these in mind when choosing between heap allocated, relocatable collections -and fixed capacity collections. - -### Out Of Memory and error handling - -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 -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 -*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). - -### 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 -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. - -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. - -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. +Во-вторых, метод `push` и многие другие методы возвращают `Result`. Поскольку коллекции `heapless` имеют фиксированную емкость, все операции, вставляющие элементы в коллекцию, потенциально могут завершиться неудачей. API отражает эту проблему, возвращая `Result`, указывающий, удалась ли операция. В отличие от этого, коллекции `alloc` перераспределят себя на куче, чтобы увеличить емкость. + +Начиная с версии v0.4.x, все коллекции `heapless` хранят все свои элементы inline. Это означает, что операция вроде `let x = heapless::Vec::new();` выделит коллекцию на стеке, но также возможно выделить коллекцию в `static` переменной или даже на куче (`Box>`). + +## Компромиссы + +Учитывайте эти аспекты при выборе между коллекциями с выделением на куче, перемещаемыми, и коллекциями с фиксированной емкостью. + +### Out Of Memory и обработка ошибок + +С выделением на куче Out Of Memory всегда возможен и может возникнуть в любом месте, где коллекция может нуждаться в росте: например, все вызовы `alloc::Vec.push` потенциально могут генерировать условие OOM. Таким образом, некоторые операции могут *неявно* завершаться неудачей. Некоторые коллекции `alloc` предоставляют методы `try_reserve`, которые позволяют проверить потенциальные условия OOM при росте коллекции, но вы должны быть proactive в их использовании. + +Если вы исключительно используете коллекции `heapless` и не используете распределитель памяти ни для чего другого, то условие OOM невозможно. Вместо этого вам придется справляться с исчерпанием емкости коллекций на основе случая за случаем. То есть вам придется справляться со *всеми* `Result`, возвращаемыми методами вроде `Vec.push`. + +Сбои OOM могут быть сложнее отлаживать, чем, скажем, `unwrap` на всех `Result`, возвращаемых `heapless::Vec.push`, потому что наблюдаемое место сбоя может *не* совпадать с местом причины проблемы. Например, даже `vec.reserve(1)` может вызвать OOM, если распределитель почти исчерпан, потому что какая-то другая коллекция протекала память (утечки памяти возможны в безопасном Rust). + +### Использование памяти + +Рассуждения об использовании памяти коллекций с выделением на куче сложны, потому что емкость долгоживущих коллекций может изменяться во время выполнения. Некоторые операции могут неявно перераспределять коллекцию, увеличивая использование памяти, и некоторые коллекции предоставляют методы вроде `shrink_to_fit`, которые потенциально могут уменьшить память, используемую коллекцией — в конечном итоге, распределитель решает, действительно ли сжимать выделение памяти или нет. Кроме того, распределитель может сталкиваться с фрагментацией памяти, что может увеличивать *видимое* использование памяти. + +С другой стороны, если вы исключительно используете коллекции с фиксированной емкостью, храните большинство из них в `static` переменных и устанавливаете максимальный размер стека вызовов, то линкер обнаружит, если вы пытаетесь использовать больше памяти, чем физически доступно. + +Кроме того, коллекции с фиксированной емкостью, выделенные на стеке, будут сообщены флагом [`-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 операций, которые могут расти коллекцию, также будет включать время, затрачиваемое на перераспределение коллекции, которое само зависит от *времени выполнения* емкости коллекции. Это делает сложным определение WCET, например, операции `alloc::Vec.push`, поскольку оно зависит как от используемого распределителя, так и от его емкости во время выполнения. -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` выполняется за постоянное время. -### Ease of use +### Простота использования -`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. +`alloc` требует настройки глобального распределителя, в то время как `heapless` нет. Однако `heapless` требует, чтобы вы выбирали емкость каждойインスタциируемой коллекции. -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. +API `alloc` будет знаком практически каждому разработчику на Rust. API `heapless` пытается тесно имитировать API `alloc`, но никогда не будет точно таким же из-за явной обработки ошибок — некоторые разработчики могут считать явную обработку ошибок чрезмерной или слишком громоздкой. \ No newline at end of file diff --git a/src/concurrency/index.md b/src/concurrency/index.md index fe30e235..359dbd55 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. +Поскольку многие программы для встраиваемых систем должны работать с прерываниями, вопросы параллелизма возникают рано или поздно, и именно здесь могут появляться тонкие и сложные ошибки. К счастью, 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. +Самый простой подход к параллелизму в программе для встраиваемых систем — это его полное отсутствие: программа состоит из одного основного цикла, который непрерывно выполняется, и прерывания полностью отсутствуют. Иногда это идеально подходит для решаемой задачи! Обычно такой цикл считывает входные данные, выполняет их обработку и записывает выходные данные. ```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 для не-встраиваемых систем, мы обычно не можем позволить себе создавать выделения памяти на куче и передавать ссылки на эти данные в новые потоки. Вместо этого обработчики прерываний могут быть вызваны в любой момент и должны знать, как получить доступ к общей памяти. На самом низком уровне это означает, что у нас должна быть _статически выделенная_ изменяемая память, на которую могут ссылаться как обработчик прерываний, так и основной код. -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`] всегда небезопасны для чтения или записи, так как без особых мер предосторожности можно вызвать состояние гонки, когда доступ к переменной прерывается на полпути прерыванием, которое также обращается к этой переменной. [`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,23 +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. Тем временем основной цикл непрерывно измеряет сигнал и увеличивает счетчик при обнаружении перехода с низкого уровня на высокий. Нам пришлось использовать `unsafe` для доступа к `COUNTER`, так как это `static mut`, что означает, что мы обещаем компилятору не вызывать неопределенное поведение. Можете ли вы заметить состояние гонки? Операция увеличения `COUNTER` _не_ гарантированно атомарна — на большинстве платформ для встраиваемых систем она будет разделена на загрузку, увеличение и сохранение. Если прерывание сработает после загрузки, но до сохранения, сброс в 0 будет проигнорирован после возврата из прерывания — и мы подсчитаем в два раза больше переходов за этот период. -## 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`: +Как справиться с гонками данных? Простой подход — использовать _критические секции_, контекст, в котором прерывания отключены. Обернув доступ к `COUNTER` в `main` в критическую секцию, мы можем быть уверены, что прерывание таймера не сработает, пока мы не закончим увеличение `COUNTER`: ```rust,ignore static mut COUNTER: u32 = 0; @@ -109,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 }; }); @@ -124,39 +90,22 @@ 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 + * Запись 0 в `COUNTER` не может быть затронута гонкой, так как мы не читаем его. + * Прерывание в любом случае не будет прервано основным потоком. -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. +Важно отметить, что критическая секция гарантирует отсутствие срабатывания прерываний, но не обеспечивает гарантии эксклюзивности в многоядерных системах! Другое ядро может одновременно обращаться к той же памяти, даже без прерываний. Для многоядерных систем потребуются более мощные примитивы синхронизации. -## Atomic Access +## Атомарный доступ -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+) поддерживает только атомарные инструкции загрузки и сохранения, тогда как `thumbv7` (Cortex-M3 и выше) предоставляет полные инструкции Compare and Swap (CAS). Эти инструкции CAS являются альтернативой полному отключению прерываний: мы можем попытаться выполнить увеличение, которое в большинстве случаев успешно, но если оно прерывается, операция увеличения автоматически повторяется. Эти атомарные операции безопасны даже в многоядерных системах. ```rust,ignore use core::sync::atomic::{AtomicUsize, Ordering}; @@ -170,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` для атомарного добавления 1 к COUNTER COUNTER.fetch_add(1, Ordering::Relaxed); } last_state = state; @@ -179,234 +128,18 @@ fn main() -> ! { #[interrupt] fn timer() { - // Use `store` to write 0 directly to COUNTER - COUNTER.store(0, Ordering::Relaxed) + // Используем `store` для прямой записи 0 в COUNTER + COUNTER.store(0, Ordering::Relaxed); } ``` -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` может быть безопасно модифицирована как из обработчика прерываний, так и из основного потока без отключения прерываний. Использование `Ordering::Relaxed` минимизирует накладные расходы на синхронизацию, так как в данном случае не требуется строгая упорядоченность операций между потоками. -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. +## Доступ к периферийным устройствам -For more details on atomics and ordering, see the [nomicon]. +В дополнение к общим данным программы часто требуется разделять доступ к периферийным устройствам между основным потоком и обработчиками прерываний. Например, мы можем захотеть считывать входной сигнал с пина GPIO в основном цикле и управлять выходным сигналом с того же порта GPIO в обработчике прерываний. -[`Ordering`]: https://doc.rust-lang.org/core/sync/atomic/enum.Ordering.html -[nomicon]: https://doc.rust-lang.org/nomicon/atomics.html - - -## Abstractions, Send, and 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! - -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. - -```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. -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). - unsafe { *self.0.get() = 0 }; - } - - pub fn increment(&self, _cs: &interrupt::CriticalSection) { - unsafe { *self.0.get() += 1 }; - } -} - -// Required to allow static CSCounter. See explanation below. -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. -static COUNTER: CSCounter = CS_COUNTER_INIT; - -#[entry] -fn main() -> ! { - set_timer_1hz(); - let mut last_state = false; - loop { - let state = read_signal_level(); - if state && !last_state { - // No unsafe here! - interrupt::free(|cs| COUNTER.increment(cs)); - } - last_state = state; - } -} - -#[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. - interrupt::free(|cs| COUNTER.reset(cs)); - - // We could use unsafe code to generate a fake CriticalSection if we - // really wanted to, avoiding the overhead: - // 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. - -[`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. - -[`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. - -## Mutexes - -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. - -[`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: - -```rust,ignore -use core::cell::Cell; -use cortex_m::interrupt::Mutex; - -static COUNTER: Mutex> = Mutex::new(Cell::new(0)); - -#[entry] -fn main() -> ! { - set_timer_1hz(); - let mut last_state = false; - loop { - let state = read_signal_level(); - if state && !last_state { - interrupt::free(|cs| - COUNTER.borrow(cs).set(COUNTER.borrow(cs).get() + 1)); - } - last_state = state; - } -} - -#[interrupt] -fn timer() { - // We still need to enter a critical section here to satisfy the Mutex. - interrupt::free(|cs| COUNTER.borrow(cs).set(0)); -} -``` - -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`]: 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! - -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`. - -## Sharing Peripherals - -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. - -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. - -[`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. +Типичная проблема в embedded-программах заключается в том, что периферийные устройства являются синглтонами: существует только один экземпляр каждого периферийного устройства. В Rust это часто моделируется с помощью метода `take()`, который возвращает `Option` и позволяет захватить периферийное устройство в момент выполнения программы. Чтобы безопасно разделять доступ к такому синглтону, мы можем использовать `Mutex` и `RefCell` из библиотеки `cortex_m` для обеспечения синхронизированного доступа в критических секциях. ```rust,ignore use core::cell::RefCell; @@ -418,35 +151,35 @@ 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, но + // большинство крейтов для встраиваемых устройств будут похожими. 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. + // Устанавливаем PA1 в высокий уровень, если обнаружен восходящий фронт на PA0. interrupt::free(|cs| { let gpioa = MY_GPIO.borrow(cs).borrow(); gpioa.as_ref().unwrap().odr.modify(|_, w| w.odr1().set_bit()); @@ -458,40 +191,31 @@ 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. +Наша общая переменная — это `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| { @@ -500,18 +224,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`, нам нужно преобразовать его в `&Option<&GPIOA>` с помощью `as_ref()`, который мы затем можем `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; @@ -520,17 +237,17 @@ use cortex_m::interrupt::{self, Mutex}; use cortex_m::asm::wfi; use stm32f4::stm32f405; -static G_TIM: Mutex>>> = - Mutex::new(RefCell::new(None)); +static G_TIM: Mutex>>> = + Mutex::new(RefCell::new(None)); #[entry] fn main() -> ! { - let mut cp = cm::Peripherals::take().unwrap(); - let dp = stm32f405::Peripherals::take().unwrap(); + let mut cp = cortex_m::Peripherals::take().unwrap(); + let dp = stm32f4::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| { @@ -545,63 +262,38 @@ 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()); } }); } - ``` -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][RTIC framework], сокращение от Real Time Interrupt-driven Concurrency (Параллелизм, управляемый прерываниями реального времени). Он обеспечивает статические приоритеты и отслеживает доступ к переменным `static mut` (называемым "ресурсами"), чтобы статически гарантировать безопасный доступ к общим ресурсам без необходимости постоянного входа в критические секции и использования подсчета ссылок (как в `RefCell`). Это дает ряд преимуществ, таких как отсутствие тупиков и чрезвычайно низкие затраты по времени и памяти. [RTIC framework]: 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! +Фреймворк также включает другие функции, такие как передача сообщений, что уменьшает необходимость в явном общем состоянии, и возможность планировать задачи для выполнения в определенное время, что можно использовать для реализации периодических задач. Подробности см. в [документации][the documentation]. [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 -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]. Эти RTOS предоставляют поддержку выполнения нескольких потоков приложения, между которыми процессор переключается, либо когда потоки уступают управление (кооперативная многозадачность), либо на основе регулярного таймера или прерываний (вытесняющая многозадачность). 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! +На момент написания мало примеров RTOS на Rust, но это интересная область, так что следите за обновлениями! -## 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 -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, симметричная многопроцессорность). -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. +Подробное рассмотрение этих тем пока выходит за рамки этой книги, но общие шаблоны аналогичны случаю с одним ядром. diff --git a/src/design-patterns/hal/checklist.md b/src/design-patterns/hal/checklist.md index 6fdf98b7..fa6437e4 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]) +- **Именование** *(крейт соответствует соглашениям об именовании в Rust)* + - [ ] Крейт назван правильно ([C-CRATE-NAME]) +- **Взаимодействие** *(крейт хорошо взаимодействует с функциональностью других библиотек)* + - [ ] Типы-обертки предоставляют метод деструктора ([C-FREE]) + - [ ] HAL переэкспортируют свой крейт доступа к регистрам ([C-REEXPORT-PAC]) + - [ ] Типы реализуют трейты `embedded-hal` ([C-HAL-TRAITS]) +- **Предсказуемость** *(крейт позволяет писать читаемый код, который работает так, как выглядит)* + - [ ] Используются конструкторы вместо трейтов расширения ([C-CTOR]) +- **Интерфейсы GPIO** *(Интерфейсы GPIO следуют общему шаблону)* + - [ ] Типы пинов по умолчанию нулевого размера ([C-ZST-PIN]) + - [ ] Типы пинов предоставляют методы для стирания пина и порта ([C-ERASED-PIN]) + - [ ] Состояние пина должно быть закодировано как параметры типа ([C-PIN-STATE]) [C-CRATE-NAME]: naming.html#c-crate-name diff --git a/src/design-patterns/hal/gpio.md b/src/design-patterns/hal/gpio.md index 6582070c..5ba4dd63 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) +## Типы пинов по умолчанию имеют нулевой размер (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. +Интерфейсы GPIO, предоставляемые 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. pub struct PA { - /// The pin number. + /// Номер пина. pin: u8, } @@ -72,7 +68,7 @@ impl PA { pub struct Pin { port: Port, pin: u8, - // (these fields can be packed to reduce the memory footprint) + // (эти поля могут быть упакованы для уменьшения занимаемой памяти) } enum Port { @@ -84,47 +80,17 @@ 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. +Дополнительное, специфичное для микросхемы состояние (например, сила тока) также может быть закодировано таким образом с использованием дополнительных параметров типа. -Methods for changing the pin state should be provided as `into_input` and -`into_output` methods. +Методы для изменения состояния пина должны предоставляться как `into_input` и `into_output`. -Additionally, `with_{input,output}_state` methods should be provided that -temporarily reconfigure a pin in a different state without moving it. +Кроме того, должны быть предоставлены методы `with_input_state` и `with_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): - -* `pub fn into_input(self, input: N) -> Pin` -* `pub fn into_output(self, output: N) -> Pin` -* ```ignore - pub fn with_input_state( - &mut self, - input: N, - f: impl FnOnce(&mut PA1) -> R, - ) -> R - ``` -* ```ignore - pub fn with_output_state( - &mut self, - output: N, - f: impl FnOnce(&mut PA1) -> R, - ) -> 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. - -Example: +Пример: ```rust # use std::marker::PhantomData; @@ -201,5 +167,5 @@ impl PA1 { } } -// Same for `PA` and `Pin`, and other pin types. +// То же самое для `PA` и `Pin`, и других типов пинов. ``` diff --git a/src/design-patterns/hal/index.md b/src/design-patterns/hal/index.md index c493a873..df736329 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. +Это набор общих и рекомендуемых шаблонов для написания уровней аппаратной абстракции (HAL) для микроконтроллеров на Rust. Эти шаблоны предназначены для использования в дополнение к существующим [Рекомендациям по API Rust] при написании HAL для микроконтроллеров. -[Rust API Guidelines]: https://rust-lang.github.io/api-guidelines/ +[Рекомендациям по API Rust]: 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/hal/interoperability.md b/src/design-patterns/hal/interoperability.md index 12049020..dc630b37 100644 --- a/src/design-patterns/hal/interoperability.md +++ b/src/design-patterns/hal/interoperability.md @@ -1,22 +1,15 @@ -# 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. +Любой тип-обертка, не являющийся `Copy`, предоставляемый HAL, должен иметь метод `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. +Метод должен при необходимости выключать и сбрасывать периферийное устройство. Вызов `new` с исходным периферийным устройством, возвращенным из `free`, не должен завершаться с ошибкой из-за неожиданного состояния периферийного устройства. -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` (например, пинов ввода-вывода), такие объекты также должны быть освобождены и возвращены методом `free`. В этом случае `free` должен возвращать кортеж. -For example: +Пример: ```rust # pub struct TIMER0; @@ -34,24 +27,19 @@ impl Timer { ``` -## HALs reexport their register access crate (C-REEXPORT-PAC) +## HAL переэкспортирует свой крейт доступа к регистрам (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. +HAL могут быть написаны на основе PAC, сгенерированных [svd2rust], или на основе других крейтов, предоставляющих прямой доступ к регистрам. HAL всегда должны переэкспортировать крейт доступа к регистрам, на котором они основаны, в корне своего крейта. -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`, независимо от фактического имени крейта, поскольку имя HAL уже должно ясно указывать, какой PAC используется. [svd2rust]: https://github.com/rust-embedded/svd2rust -## Types implement the `embedded-hal` traits (C-HAL-TRAITS) +## Типы реализуют трейты `embedded-hal` (C-HAL-TRAITS) -Types provided by the HAL should implement all applicable traits provided by the -[`embedded-hal`] crate. +Типы, предоставляемые HAL, должны реализовывать все применимые трейты, предоставляемые крейтом [`embedded-hal`]. -Multiple traits may be implemented for the same type. +Один и тот же тип может реализовывать несколько трейтов. [`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 65877d3e..44e05965 100644 --- a/src/design-patterns/hal/naming.md +++ b/src/design-patterns/hal/naming.md @@ -1,9 +1,6 @@ -# Naming - +# Именование -## The crate is named appropriately (C-CRATE-NAME) +## Крейт назван корректно (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 должны быть названы по имени микросхемы или семейства микросхем, которые они поддерживают. Их имя должно заканчиваться на `-hal`, чтобы отличать их от крейтов доступа к регистрам. В имени не должны использоваться подчеркивания (вместо них используйте дефисы). diff --git a/src/design-patterns/hal/predictability.md b/src/design-patterns/hal/predictability.md index e3181856..f94c0410 100644 --- a/src/design-patterns/hal/predictability.md +++ b/src/design-patterns/hal/predictability.md @@ -1,24 +1,16 @@ -# Predictability - +# Предсказуемость -## Constructors are used instead of extension traits (C-CTOR) +## Используются конструкторы вместо трейтов-расширений (C-CTOR) -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. +Следует избегать реализации трейтов-расширений для исходного периферийного устройства. -## Methods are decorated with `#[inline]` where appropriate (C-INLINE) +## Методы помечены `#[inline]` там, где это уместно (C-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: +Компилятор Rust по умолчанию не выполняет полное встраивание через границы крейтов. Поскольку приложения для встраиваемых систем чувствительны к неожиданным увеличениям размера кода, `#[inline]` следует использовать для направления компилятора следующим образом: -* 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. +* Все "маленькие" функции должны быть помечены `#[inline]`. Что считать "маленьким" — субъективно, но, как правило, все функции, которые компилируются в последовательности инструкций с однозначным количеством, считаются маленькими. +* Функции, которые с высокой вероятностью принимают константные значения в качестве параметров, должны быть помечены `#[inline]`. Это позволяет компилятору вычислять даже сложную логику инициализации во время компиляции, если входные данные функции известны. diff --git a/src/design-patterns/index.md b/src/design-patterns/index.md index 32153faf..780b056f 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. +Эта глава стремится собрать различные полезные шаблоны проектирования для embedded Rust. diff --git a/src/interoperability/c-with-rust.md b/src/interoperability/c-with-rust.md index 74ec868b..6c20dc74 100644 --- a/src/interoperability/c-with-rust.md +++ b/src/interoperability/c-with-rust.md @@ -1,21 +1,21 @@ -# A little C with your Rust +# Немного C в вашем Rust -Using C or C++ inside of a Rust project consists of two major parts: +Использование кода на C или C++ внутри проекта на Rust состоит из двух основных частей: -- Wrapping the exposed C API for use with Rust -- Building your C or C++ code to be integrated with the Rust code +- Обертывание открытого API на C для использования в Rust +- Сборка кода на C или C++ для интеграции с кодом на Rust -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++. +Поскольку C++ не имеет стабильного ABI для компилятора Rust, рекомендуется использовать ABI `C` при комбинировании Rust с C или 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. +Перед использованием кода на C или C++ из Rust необходимо определить (на Rust) типы данных и сигнатуры функций, существующие в связанном коде. В C или C++ вы бы подключили заголовочный файл (`.h` или `.hpp`), который определяет эти данные. В Rust необходимо либо вручную перевести эти определения в 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 */ @@ -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 */ @@ -46,83 +46,102 @@ extern "C" { } ``` -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`. Чтобы обеспечить совместимость с кодом на 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. +Из-за гибкости определения `int` или `char` в C или C++ рекомендуется использовать типы из модуля `cty`, такие как `c_int` и `c_char`, чтобы обеспечить совместимость с платформой. ```rust,ignore -extern "C" { pub fn cool_function( ... ); } +extern "C" { + pub fn cool_function( + i: cty::c_int, + c: cty::c_char, + cs: *mut CoolStruct + ); +} ``` -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. +Блок `extern "C"` сообщает компилятору Rust, что указанные функции используют ABI C, обеспечивая их совместимость с функциями, определенными в коде на C. Указатель `*mut CoolStruct` соответствует указателю `CoolStruct*` в C, позволяя передавать изменяемые структуры между языками. -```rust,ignore - i: cty::c_int, - c: cty::c_char, - cs: *mut CoolStruct -``` +### Автоматизация с помощью `bindgen` + +Ручной перевод заголовочных файлов может быть трудоемким и подверженным ошибкам, особенно для больших библиотек. Инструмент [`bindgen`] автоматизирует этот процесс, генерируя определения Rust из заголовочных файлов C или C++. -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. +Чтобы использовать `bindgen`, добавьте его в зависимости вашего проекта в `Cargo.toml`: -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. +```toml +[build-dependencies] +bindgen = "0.59" +``` + +Затем создайте скрипт `build.rs` для генерации привязок: -### Automatically generating the interface +```rust,ignore +use bindgen; -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: +fn main() { + println!("cargo:rerun-if-changed=wrapper.h"); + + bindgen::Builder::default() + .header("wrapper.h") + .generate() + .expect("Unable to generate bindings") + .write_to_file("src/bindings.rs") + .expect("Couldn't write bindings!"); +} +``` -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 - 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 output 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`. +Файл `wrapper.h` должен включать заголовочные файлы C, которые вы хотите преобразовать: -[bindgen]: https://github.com/rust-lang/rust-bindgen -[bindgen user's manual]: https://rust-lang.github.io/rust-bindgen/ +```C +/* wrapper.h */ +#include "cool.h" +``` -## Building your C/C++ code +Запуск `cargo build` сгенерирует файл `src/bindings.rs`, содержащий определения Rust для всех типов и функций из `cool.h`. Используйте их в вашем коде на Rust: -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,ignore +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +``` -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. +[`bindgen`]: https://github.com/rust-lang/bindgen -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. +## Сборка кода на C/C++ -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++ и связать его с вашим проектом на Rust. Это обычно делается с помощью скрипта `build.rs`. -### Rust `build.rs` build scripts +### Скрипты сборки -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. +Скрипт `build.rs` — это файл, написанный на синтаксисе Rust, который выполняется на вашей машине компиляции ПОСЛЕ сборки зависимостей вашего проекта, но ДО сборки самого проекта. -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`, или прямой компиляции C/C++ с использованием крейта `cc`. -### 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. +Для проектов с сложными внешними проектами или системами сборки проще всего использовать [`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. +Хотя ваш крейт может быть нацелен на платформу `no_std`, ваш `build.rs` выполняется только на машинах, компилирующих ваш крейт. Это означает, что вы можете использовать любые крейты Rust, которые работают на вашем хосте компиляции. [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html -### Building C/C++ code with the `cc` crate +### Сборка кода на C/C++ с помощью крейта `cc` -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`], который предоставляет идиоматичный интерфейс 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 в качестве зависимости для статической библиотеки пример скрипта `build.rs`, использующего крейт [`cc`], будет выглядеть так: ```rust,ignore fn main() { @@ -132,4 +151,4 @@ fn main() { } ``` -The `build.rs` is placed at the root of the package. Then `cargo build` will compile and execute it before the build of the package. A static archive named `libfoo.a` is generated and placed in the `target` directory. +Файл `build.rs` размещается в корне пакета. Затем `cargo build` скомпилирует и выполнит его перед сборкой пакета. Генерируется статический архив с именем `libfoo.a`, который помещается в директорию `target`. diff --git a/src/interoperability/index.md b/src/interoperability/index.md index 267bd678..42c275ef 100644 --- a/src/interoperability/index.md +++ b/src/interoperability/index.md @@ -1,40 +1,23 @@ -# Interoperability +# Интероперабельность -Interoperability between Rust and C code is always dependent -on transforming data between the two languages. -For this purpose, there is a dedicated module -in the `stdlib` called -[`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html). +Интероперабельность между кодом на Rust и C всегда зависит от преобразования данных между двумя языками. Для этой цели в `stdlib` есть специальный модуль, называемый [`std::ffi`](https://doc.rust-lang.org/std/ffi/index.html). -`std::ffi` provides type definitions for C primitive types, -such as `char`, `int`, and `long`. -It also 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` предоставляет определения типов для примитивов C, таких как `char`, `int` и `long`. Также он предоставляет утилиты для преобразования более сложных типов, таких как строки, отображая как `&str`, так и `String` на типы C, которые легче и безопаснее обрабатывать. -As of Rust 1.30, -functionalities of `std::ffi` are available -in either `core::ffi` or `alloc::ffi` -depending on whether or not memory allocation is involved. -The [`cty`] crate and the [`cstr_core`] crate -also offer similar functionalities. +Начиная с Rust 1.30, функциональность `std::ffi` доступна либо в `core::ffi`, либо в `alloc::ffi`, в зависимости от того, связано ли это с выделением памяти. Крейты [`cty`] и [`cstr_core`] также предлагают аналогичные функциональности. [`cstr_core`]: https://crates.io/crates/cstr_core [`cty`]: https://crates.io/crates/cty -| Rust type | Intermediate | C type | -|----------------|--------------|----------------| -| `String` | `CString` | `char *` | -| `&str` | `CStr` | `const char *` | -| `()` | `c_void` | `void` | -| `u32` or `u64` | `c_uint` | `unsigned int` | -| etc | ... | ... | +| Тип Rust | Промежуточный | Тип C | +|----------------|---------------|----------------| +| `String` | `CString` | `char *` | +| `&str` | `CStr` | `const char *` | +| `()` | `c_void` | `void` | +| `u32` или `u64`| `c_uint` | `unsigned int` | +| и т.д. | ... | ... | -A value of a C primitive type can be used -as one of the corresponding Rust type and vice versa, -since the former is simply a type alias of the latter. -For example, the following code compiles on platforms -where `unsigned int` is 32-bit long. +Значение типа-примитива C можно использовать как соответствующий тип Rust и наоборот, поскольку первый является просто псевдонимом второго. Например, следующий код компилируется на платформах, где `unsigned int` имеет длину 32 бита: ```rust,ignore fn foo(num: u32) { @@ -43,22 +26,18 @@ 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 #61]: https://github.com/rust-embedded/book/issues/61 -## Interoperability with RTOSs +## Интероперабельность с RTOS -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, все еще находится в стадии разработки; особенно вызов функций RTOS из Rust может быть сложным. -We are collecting examples and use cases for this on our issue tracker in -[issue #62]. +Мы собираем примеры и случаи использования для этого в нашем трекере задач в [issue #62]. [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 5e596f02..4e15a615 100644 --- a/src/interoperability/rust-with-c.md +++ b/src/interoperability/rust-with-c.md @@ -1,55 +1,42 @@ -# A little Rust with your C +# Немного Rust в вашем C -Using Rust code inside a C or C++ project mostly consists of two parts. +Использование кода на Rust внутри проекта на C или C++ в основном состоит из двух частей: -- Creating a C-friendly API in Rust -- Embedding your Rust project into an external build system +- Создание API, совместимого с C, на Rust +- Встраивание вашего проекта на 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` для компиляции вашего крейта и любых его зависимостей. -## Setting up a project +## Настройка проекта -Create a new `cargo` project as usual. +Создайте новый проект `cargo` как обычно. -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` генерировать системную библиотеку вместо обычной цели Rust. Это также позволяет задать другое имя выходной библиотеки, если вы хотите, чтобы оно отличалось от остальной части вашего крейта. ```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 +## Создание API для C -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. +Поскольку C++ не имеет стабильного ABI для компилятора Rust, мы используем `C` для любой интероперабельности между разными языками. Это не исключение при использовании Rust внутри кода на C и C++. ### `#[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, использует ABI Rust (который также не стабилизирован). Вместо этого, при создании внешних API FFI, нам нужно указать компилятору использовать системный 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] @@ -58,46 +45,38 @@ 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. +Как и при использовании кода на `C` в вашем проекте на Rust, вам теперь нужно преобразовывать данные в форму, понятную остальной части приложения. -## 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. +`cargo` создаст файл `my_lib.so`/`my_lib.dll` или `my_lib.a` в зависимости от вашей платформы и настроек. Эту библиотеку можно просто слинковать вашей системой сборки. -However, calling a Rust function from C requires a header file to declare -the function signatures. +Однако вызов функции Rust из C требует заголовочного файла для объявления сигнатур функций. -Every function in your Rust-ffi API needs to have a corresponding header function. +Каждая функция в вашем 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! +На этом этапе использование функций Rust из C так же просто, как включение заголовочного файла и их вызов! ```C #include "my-rust-project.h" diff --git a/src/intro/hardware.md b/src/intro/hardware.md index b458aa19..37b07ba5 100644 --- a/src/intro/hardware.md +++ b/src/intro/hardware.md @@ -1,39 +1,38 @@ -# Meet Your Hardware +# Знакомство с аппаратным обеспечением -Let's get familiar with the hardware we'll be working with. +Давайте познакомимся с аппаратным обеспечением, с которым мы будем работать. -## STM32F3DISCOVERY (the "F3") +## STM32F3DISCOVERY ( "F3")

-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. +- Микроконтроллер [STM32F303VCT6](https://www.st.com/en/microcontrollers/stm32f303vc.html). Этот микроконтроллер имеет + - Одноядерный процессор ARM Cortex-M4F с аппаратной поддержкой операций с плавающей запятой одинарной точности и максимальной тактовой частотой 72 МГц. - - 256 KiB of "Flash" memory. (1 KiB = 10**24** bytes) + - 256 КБ "Flash" памяти. (1 КБ = 1024 байта) - - 48 KiB of RAM. + - 48 КБ ОЗУ. - - A variety of integrated peripherals such as timers, I2C, SPI and USART. + - Разнообразные интегрированные периферийные устройства, такие как таймеры, I2C, SPI и USART. - - General purpose Input Output (GPIO) and other types of pins accessible through the two rows of headers along side the board. + - Ввод/вывод общего назначения (GPIO) и другие типы пинов, доступные через две ряда заголовков вдоль края платы. - - A USB interface accessible through the USB port labeled "USB USER". + - Интерфейс USB, доступный через порт USB с меткой "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. +- Акцелерометр как часть чипа [LSM303DLHC](https://www.st.com/en/mems-and-sensors/lsm303dlhc.html). -- 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). -- 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). -- 8 user LEDs arranged in the shape of a compass. +- 8 пользовательских светодиодов, расположенных в форме компаса. -- 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". +- Второй микроконтроллер: [STM32F103](https://www.st.com/en/microcontrollers/stm32f103cb.html). Этот микроконтроллер на самом деле является частью встроенного программатора/отладчика и подключен к порту USB с меткой "USB ST-LINK". -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. +Для более подробного списка функций и дальнейших спецификаций платы посмотрите на сайте [STMicroelectronics](https://www.st.com/en/evaluation-tools/stm32f3discovery.html). -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 в руководстве](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf) diff --git a/src/intro/index.md b/src/intro/index.md index 272c2428..0dc2b660 100644 --- a/src/intro/index.md +++ b/src/intro/index.md @@ -1,131 +1,105 @@ -# Introduction +# Введение -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: вводную книгу об использовании языка программирования Rust на "Bare Metal" встраиваемых системах, таких как микроконтроллеры. -## 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)) +## Для кого предназначен Embedded Rust -## Scope +Embedded Rust предназначен для всех, кто хочет заниматься встраиваемым программированием, используя преимущества концепций более высокого уровня и гарантий безопасности, предоставляемых языком Rust. +(См. также [Для кого предназначен Rust](https://doc.rust-lang.org/book/ch00-00-introduction.html)) -The goals of this book are: +## Область применения -* 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. +* Помочь разработчикам быстро освоить разработку на embedded Rust. Т.е. как настроить среду разработки. -* Serve as a cookbook in some cases. e.g. How do I mix C and Rust in a single - project? +* Поделиться *текущими* лучшими практиками использования Rust для разработки встраиваемых систем. Т.е. как лучше использовать функции языка Rust для написания более правильного ПО для встраиваемых систем. -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. +* Служить кулинарной книгой в некоторых случаях. Например, как смешать C и Rust в одном проекте? -## 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. +Эта книга старается быть как можно более общей, но для облегчения как для читателей, так и для авторов она использует архитектуру ARM Cortex-M во всех примерах. Однако книга не предполагает, что читатель знаком с этой конкретной архитектурой, и объясняет детали, специфичные для этой архитектуры, где это необходимо. -### 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. +Эта книга ориентирована на людей с опытом либо в embedded-разработке, либо в Rust, однако мы считаем, что каждый, интересующийся embedded-программированием на Rust, может извлечь из этой книги пользу. Для тех, у кого нет предварительных знаний, мы предлагаем прочитать раздел "Предположения и предпосылки" и наверстать упущенные знания, чтобы получить больше от книги и улучшить опыт чтения. Вы можете посмотреть раздел "Другие ресурсы", чтобы найти материалы по темам, которые вы хотите наверстать. + +### Предположения и предпосылки + +* Вы комфортно используете язык программирования Rust и написали, запустили и отлаживали приложения на Rust в десктопной среде. Вы также должны быть знакомы с идиомами [издания 2018 года], поскольку эта книга ориентирована на Rust 2018. [2018 edition]: https://doc.rust-lang.org/edition-guide/ -* 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. +* Вы комфортно разрабатываете и отлаживаете встраиваемые системы на другом языке, таком как C, C++ или Ada, и знакомы с концепциями, такими как: + * Кросс-компиляция + * Периферийные устройства, отображенные в память + * Прерывания + * Общие интерфейсы, такие как I2C, SPI, Serial и т.д. + +### Другие ресурсы -### 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, Embedded | [Comprehensive Rust 🦀: Bare Metal](https://google.github.io/comprehensive-rust/bare-metal.html) | Teaching material for a 1-day class on bare-metal Rust development | -| 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) | - | +| Тема | Ресурс | Описание | +|---------------|--------|-----------| +| Rust | [Книга по Rust](https://doc.rust-lang.org/book/) | Если вы еще не комфортно владеете Rust, мы настоятельно рекомендуем прочитать эту книгу. | +| Rust, Embedded | [Книга Discovery](https://docs.rust-embedded.org/discovery/) | Если вы никогда не занимались embedded-программированием, эта книга может быть лучшим стартом | +| Rust, Embedded | [Полка книг по Embedded Rust](https://docs.rust-embedded.org) | Здесь вы можете найти несколько других ресурсов, предоставленных рабочей группой Embedded Rust. | +| Rust, Embedded | [Embedonomicon](https://docs.rust-embedded.org/embedonomicon/) | Детали embedded-программирования на Rust. | +| Rust, Embedded | [FAQ по embedded](https://docs.rust-embedded.org/faq.html) | Часто задаваемые вопросы по Rust в embedded. | +| Встраиваемое программирование | [Курс на Coursera](https://www.coursera.org/learn/introduction-embedded-systems) | Бесплатный курс на Coursera по встраиваемым системам. | +| Встраиваемое программирование | [Курс на edX](https://www.edx.org/course/embedded-systems-shape-world-utaustinx-ut-6-02x) | Бесплатный курс на edX по встраиваемым системам. | +| Прерывания | [Прерывание](https://en.wikipedia.org/wiki/Interrupt) | - | +| Отображение ввода/вывода в память/Периферийные устройства | [Отображение ввода/вывода в память](https://en.wikipedia.org/wiki/Memory-mapped_I/O) | - | +| SPI, UART, RS232, USB, I2C, TTL | [Stack Exchange о SPI, UART и других интерфейсах](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)) +* [Японский](https://tomoyuki-nakabayashi.github.io/book/) + ([репозиторий](https://github.com/tomoyuki-nakabayashi/book)) -* [Chinese](https://xxchang.github.io/book/) - ([repository](https://github.com/XxChang/book)) +* [Китайский](https://xxchang.github.io/book/) + ([репозиторий](https://github.com/XxChang/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. +Эта книга будет использовать плату разработки [STM32F3DISCOVERY] от STMicroelectronics для большинства примеров. Эта плата основана на архитектуре ARM Cortex-M, и хотя базовая функциональность одинакова для большинства CPU на этой архитектуре, периферийные устройства и другие детали реализации микроконтроллеров отличаются между разными производителями и даже между семьями микроконтроллеров от одного производителя. -For this reason, we suggest purchasing the [STM32F3DISCOVERY] development board -for the purpose of following the examples in this book. +По этой причине мы рекомендуем приобрести плату разработки [STM32F3DISCOVERY] для следования примерам в этой книге. [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]. +Работа над этой книгой координируется в [этом репозитории] и в основном разрабатывается [командой ресурсов]. -[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 +[командой ресурсов]: 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. +Если у вас проблемы со следующими инструкциями в этой книге или вы находите, что какой-то раздел книги недостаточно ясен или трудно следовать, то это ошибка, и ее следует сообщить в [отслеживателе задач] этой книги. -[the issue tracker]: https://github.com/rust-embedded/book/issues/ +[отслеживателе задач]: https://github.com/rust-embedded/book/issues/ -Pull requests fixing typos and adding new content are very welcome! +Пулл-реквесты, исправляющие опечатки и добавляющие новый контент, очень приветствуются! -## 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. +* Примеры кода и отдельные проекты Cargo, содержащиеся в этой книге, лицензированы на условиях как [лицензии MIT], так и [лицензии Apache v2.0]. +* Проза, изображения и диаграммы, содержащиеся в этой книге, лицензированы на условиях лицензии Creative Commons [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 +* Дать соответствующую атрибуцию (т.е. упомянуть эту книгу на вашем слайде и предоставить ссылку на соответствующую страницу) +* Предоставить ссылку на лицензию [CC-BY-SA v4.0] +* Указать, если вы изменили материал каким-либо образом, и сделать любые изменения в нашем материале доступными под той же лицензией -Also, please do let us know if you find this book useful! +Также, пожалуйста, дайте нам знать, если вы находите эту книгу полезной! diff --git a/src/intro/install.md b/src/intro/install.md index dc785af0..89082abf 100644 --- a/src/intro/install.md +++ b/src/intro/install.md @@ -1,61 +1,56 @@ -# Installing the tools +# Установка инструментов -This page contains OS-agnostic installation instructions for a few of the tools: +Эта страница содержит инструкции по установке некоторых инструментов, не зависящие от ОС: -### Rust Toolchain +### Цепочка инструментов Rust -Install rustup by following the instructions at [https://rustup.rs](https://rustup.rs). +Установите rustup, следуя инструкциям на [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. +**ПРИМЕЧАНИЕ** Убедитесь, что у вас версия компилятора не ниже 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. -[Find the best Cortex-M for you.](https://developer.arm.com/ip-products/processors/cortex-m#c-7d3b69ce-5b17-4c9e-8f06-59b605713133) +Для экономии трафика и места на диске установка по умолчанию поддерживает только нативную компиляцию. Чтобы добавить поддержку кросс-компиляции для архитектур ARM Cortex-M, выберите один из следующих целевых объектов компиляции. Для платы STM32F3DISCOVERY, используемой в примерах этой книги, используйте цель `thumbv7em-none-eabihf`. +[Найдите подходящий Cortex-M для вас.](https://developer.arm.com/ip-products/processors/cortex-m#c-7d3b69ce-5b17-4c9e-8f06-59b605713133) -Cortex-M0, M0+, and M1 (ARMv6-M architecture): +Cortex-M0, M0+ и 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 ``` - ### `cargo-binutils` ``` text @@ -63,20 +58,20 @@ cargo install cargo-binutils rustup component add llvm-tools ``` -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 для Visual Studio 2019. https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16 ### `cargo-generate` -We'll use this later to generate a project from a template. +Мы используем это позже для генерации проекта из шаблона. ``` console cargo install cargo-generate ``` -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. +Примечание: в некоторых дистрибутивах Linux (например, Ubuntu) может потребоваться установка пакетов `libssl-dev` и `pkg-config` перед установкой 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 41027b8d..de9f058b 100644 --- a/src/intro/install/linux.md +++ b/src/intro/install/linux.md @@ -1,13 +1,12 @@ # Linux -Here are the installation commands for a few Linux distributions. +Вот команды установки для нескольких дистрибутивов 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` — это команда GDB, которую вы будете использовать для отладки программ для ARM Cortex-M @@ -23,10 +22,9 @@ Here are the installation commands for a few Linux distributions. sudo apt install gdb-multiarch openocd qemu-system-arm ``` -- Ubuntu 14.04 and 16.04 +- Ubuntu 14.04 и 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` — это команда GDB, которую вы будете использовать для отладки программ для ARM Cortex-M @@ -37,7 +35,7 @@ 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 или новее @@ -50,18 +48,17 @@ sudo dnf install 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` — это команда GDB, которую вы будете использовать для отладки программ для ARM Cortex-M ``` console sudo pacman -S arm-none-eabi-gdb qemu-system-arm openocd ``` -## udev rules +## Правила udev -This rule lets you use OpenOCD with the Discovery board without root privilege. +Это правило позволяет использовать OpenOCD с платой Discovery без привилегий root. -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 @@ -71,21 +68,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 (..) @@ -93,8 +90,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: +Запишите номера шины и устройства. Используйте эти номера для создания пути вроде `/dev/bus/usb//`. Затем используйте этот путь так: ``` console ls -l /dev/bus/usb/001/018 @@ -113,10 +109,8 @@ 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` показывает, что пользователь `you` может использовать это устройство. -Now, go to the [next section]. +Теперь перейдите к [следующему разделу]. -[next section]: verify.md +[следующему разделу]: verify.md diff --git a/src/intro/install/macos.md b/src/intro/install/macos.md index 95ecaf64..c7a3b73f 100644 --- a/src/intro/install/macos.md +++ b/src/intro/install/macos.md @@ -1,11 +1,11 @@ # macOS -All the tools can be installed using [Homebrew] or [MacPorts]: +Все инструменты можно установить с помощью [Homebrew] или [MacPorts]: [Homebrew]: http://brew.sh/ [MacPorts]: https://www.macports.org/ -## Install tools with [Homebrew] +## Установка инструментов с [Homebrew] ``` text $ # GDB @@ -18,12 +18,12 @@ $ # QEMU $ brew install qemu ``` -> **NOTE** If OpenOCD crashes you may need to install the latest version using: +> **ПРИМЕЧАНИЕ** Если OpenOCD падает, может потребоваться установка последней версии с помощью: ```text $ brew install --HEAD openocd ``` -## Install tools with [MacPorts] +## Установка инструментов с [MacPorts] ``` text $ # GDB @@ -36,8 +36,6 @@ $ # QEMU $ sudo port install qemu ``` +Это все! Перейдите к [следующему разделу]. - -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 45caec90..8bc86645 100644 --- a/src/intro/install/verify.md +++ b/src/intro/install/verify.md @@ -1,30 +1,24 @@ -# 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 Mini-USB 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. +Подключите ваш ноутбук / ПК к плате discovery с помощью кабеля Mini-USB. Плата discovery имеет два разъема USB; используйте тот, с меткой "USB ST-LINK", который находится в центре края платы. -Also check that the ST-LINK header is populated. See the picture below; the -ST-LINK header is highlighted. +Также проверьте, что заголовок 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, включая релиз 0.10.0 от 2017 года, не содержат новый (и предпочтительный) файл `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 +39,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]. +Содержимое может не совпадать точно, но вы должны получить последнюю строку о точках останова и наблюдения. Если вы получили ее, завершите процесс 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 @@ -61,18 +53,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. +Если ни одна из команд не работает от обычного пользователя, попробуйте запустить их с правами root (например, `sudo openocd ..`). Если команды работают с правами root, проверьте, что [правила udev] установлены правильно. -[udev rules]: linux.md#udev-rules +[правила udev]: 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 diff --git a/src/intro/install/windows.md b/src/intro/install/windows.md index fe2e9ec4..93bd97dc 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 предоставляет установщики `.exe` для Windows. Возьмите один отсюда [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 нет, но если вы не в настроении компилировать его самостоятельно, проект xPack предоставляет бинарную дистрибуцию [здесь][openocd]. Следуйте предоставленным инструкциям по установке. Затем обновите переменную окружения `%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 +## Драйвер USB ST-LINK -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 diff --git a/src/intro/no-std.md b/src/intro/no-std.md index 93aadf65..126fca04 100644 --- a/src/intro/no-std.md +++ b/src/intro/no-std.md @@ -1,66 +1,67 @@ -# A `no_std` Rust Environment - -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. - -There are two general Embedded Programming classifications: - -## 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. - -## 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. - -### 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 | +# Окружение Rust с `no_std` + +Термин "встраиваемое программирование" используется для широкого спектра классов программирования. +От программирования 8-битных микроконтроллеров (например, [ST72325xx](https://www.st.com/resource/en/datasheet/st72325j6.pdf)) +с всего несколькими КБ ОЗУ и ПЗУ до систем вроде Raspberry Pi +([Model B 3+](https://en.wikipedia.org/wiki/Raspberry_Pi#Specifications)), которая имеет 32/64-битный 4-ядерный процессор Cortex-A53 с частотой 1.4 ГГц и 1 ГБ ОЗУ. Разные ограничения применяются при написании кода в зависимости от цели и случая использования. + +Существуют два общих класса встраиваемого программирования: + +## Хостинговые окружения + +Такие окружения близки к обычному окружению ПК. +Это означает, что предоставляется системный интерфейс [например, POSIX](https://en.wikipedia.org/wiki/POSIX), +который дает примитивы для взаимодействия с различными системами, такими как файловые системы, сеть, управление памятью, потоки и т.д. +Стандартные библиотеки, в свою очередь, обычно зависят от этих примитивов для реализации своей функциональности. +Также может быть sysroot и ограничения на использование ОЗУ/ПЗУ, а также специальное оборудование или ввод/вывод. В целом это похоже на программирование в специальной среде ПК. + +## Окружения без ОС (Bare Metal) + +В окружении без ОС (bare metal) перед вашей программой не загружено никакого кода. +Без ПО, предоставляемого ОС, мы не можем загрузить стандартную библиотеку. +Вместо этого программа вместе с используемыми крейтами может использовать только аппаратное обеспечение (bare metal) для выполнения. +Чтобы предотвратить загрузку стандартной библиотеки Rust, используйте `no_std`. +Части стандартной библиотеки, не зависящие от платформы, доступны через [libcore](https://doc.rust-lang.org/core/). +libcore также исключает вещи, которые не всегда желательны в окружении встраиваемых систем. +Одна из них — распределитель памяти для динамического выделения памяти. +Если требуется это или другие функциональности, часто есть крейты, которые их предоставляют. + +### Runtime libstd + +Как упоминалось ранее, использование [libstd](https://doc.rust-lang.org/std/) требует некоторой системной интеграции, но не только потому, +что [libstd](https://doc.rust-lang.org/std/) просто предоставляет общий способ доступа к абстракциям ОС, она также предоставляет runtime. +Эта runtime, среди прочего, настраивает защиту от переполнения стека, обрабатывает аргументы командной строки +и порождает основной поток перед вызовом главной функции программы. Эта runtime также недоступна в окружении `no_std`. + +## Итог + +`#![no_std]` — это атрибут на уровне крейта, указывающий, что крейт будет ссылаться на крейт core вместо std. +Крейт [libcore](https://doc.rust-lang.org/core/), в свою очередь, — это подмножество std, не зависящее от платформы, +которое не делает предположений о системе, на которой будет работать программа. +Таким образом, он предоставляет API для языковых примитивов, таких как числа с плавающей запятой, строки и слайсы, а также API, раскрывающие функции процессора, +такие как атомарные операции и инструкции SIMD. Однако он не предоставляет API для чего-либо, что включает интеграцию с платформой. +Благодаря этим свойствам код с no_std и [libcore](https://doc.rust-lang.org/core/) может использоваться для любого вида +загрузочного (stage 0) кода, такого как загрузчики, прошивки или ядра. + +### Обзор + +| Функция | no_std | std | |-----------------------------------------------------------|--------|-----| -| heap (dynamic memory) | * | ✓ | -| collections (Vec, BTreeMap, etc) | ** | ✓ | -| stack overflow protection | ✘ | ✓ | -| runs init code before main | ✘ | ✓ | -| libstd available | ✘ | ✓ | -| libcore available | ✓ | ✓ | -| writing firmware, kernel, or bootloader code | ✓ | ✘ | +| куча (динамическая память) | * | ✓ | +| коллекции (Vec, BTreeMap и т.д.) | ** | ✓ | +| защита от переполнения стека | ✘ | ✓ | +| выполнение кода инициализации перед main | ✘ | ✓ | +| доступна libstd | ✘ | ✓ | +| доступна libcore | ✓ | ✓ | +| написание прошивки, ядра или кода загрузчика | ✓ | ✘ | -\* Only if you use the `alloc` crate and use a suitable allocator like [alloc-cortex-m]. +\* Только если вы используете крейт `alloc` и подходящий распределитель, такой как [alloc-cortex-m]. -\** Only if you use the `collections` crate and configure a global default allocator. +\** Только если вы используете крейт `collections` и настраиваете глобальный распределитель по умолчанию. -\** HashMap and HashSet are not available due to a lack of a secure random number generator. +\** HashMap и HashSet недоступны из-за отсутствия безопасного генератора случайных чисел. [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 359c4e58..b04be3f2 100644 --- a/src/intro/tooling.md +++ b/src/intro/tooling.md @@ -1,112 +1,86 @@ -# 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. +- 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`. - These tools are optional but will make it easier to follow along with the book. +- [`qemu-system-arm`](https://www.qemu.org/). Протестированные версии: 3.0.0 +- OpenOCD >=0.8. Протестированные версии: v0.9.0 и v0.10.0 +- GDB с поддержкой ARM. Рекомендуется версия 7.12 или новее. Протестированные версии: 7.10, 7.11, 7.12 и 8.1 +- [`cargo-generate`](https://github.com/ashleygwilliams/cargo-generate) или `git`. Эти инструменты опциональны, но облегчат следование книге. -The text below explains why we are using these tools. Installation instructions -can be found on the next page. +Текст ниже объясняет, почему мы используем эти инструменты. Инструкции по установке можно найти на следующей странице. -## `cargo-generate` OR `git` +## `cargo-generate` ИЛИ `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). +Программы без ОС (bare metal) — это нестандартные (`no_std`) программы на Rust, требующие некоторых корректировок процесса линковки для правильной компоновки памяти. Это требует дополнительных файлов (таких как скрипты линковки) и настроек (таких как флаги линковки). Мы упаковали их для вас в шаблон, так что вам нужно только заполнить недостающую информацию (например, имя проекта и характеристики целевого оборудования). -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. +Наш шаблон совместим с `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. +`cargo-binutils` — это коллекция подкоманд Cargo, облегчающих использование инструментов LLVM, поставляемых с цепочкой инструментов Rust. Эти инструменты включают версии LLVM `objdump`, `nm` и `size` и используются для инспекции бинарных файлов. -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`) 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. +Преимущество использования этих инструментов перед GNU binutils заключается в том, что (a) установка инструментов LLVM — это одна команда (`rustup component add llvm-tools`) независимо от вашей ОС и (b) инструменты вроде `objdump` поддерживают все архитектуры, поддерживаемые `rustc` — от ARM до x86_64 — поскольку они оба используют один и тот же бэкенд LLVM. ## `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 для запуска программ для встраиваемых систем на хосте. Благодаря этому вы можете следовать некоторым частям этой книги, даже если у вас нет оборудования! -# Tooling for Embedded Rust Debugging +# Инструменты для отладки Embedded Rust -## Overview +## Обзор -Debugging embedded systems in Rust requires specialized tools including software to manage the debugging process, debuggers to inspect and control program execution, and hardware probes to facilitate interaction between the host and the embedded device. This document outlines essential software tools like Probe-rs and OpenOCD, which simplify and support the debugging process, alongside prominent debuggers such as GDB and the Probe-rs Visual Studio Code extension. Additionally, it covers key hardware probes such as Rusty-probe, ST-Link, J-Link, and MCU-Link, which are integral for effective debugging and programming of embedded devices. +Отладка встраиваемых систем в Rust требует специализированных инструментов, включая ПО для управления процессом отладки, отладчики для инспекции и управления выполнением программы, а также аппаратные пробники для взаимодействия между хостом и встраиваемым устройством. Этот документ описывает основные программные инструменты, такие как Probe-rs и OpenOCD, которые упрощают и поддерживают процесс отладки, а также известные отладчики, такие как GDB и расширение Probe-rs для Visual Studio Code. Кроме того, он охватывает ключевые аппаратные пробники, такие как Rusty-probe, ST-Link, J-Link и MCU-Link, которые необходимы для эффективной отладки и программирования встраиваемых устройств. -## Software that drives debugging tools +## ПО, управляющее инструментами отладки ### Probe-rs -Probe-rs is a modern, Rust-focused software designed to work with debuggers in embedded systems. Unlike OpenOCD, Probe-rs is built with simplicity in mind and aims to reduce the configuration burden often found in other debugging solutions. It supports various probes and targets, providing a high-level interface for interacting with embedded hardware. Probe-rs integrates directly with Rust tooling, and integrates with Visual Studio Code through its extension, allowing developers to streamline their debugging workflow. +Probe-rs — это современное ПО, ориентированное на Rust, предназначенное для работы с отладчиками во встраиваемых системах. В отличие от OpenOCD, Probe-rs разработан с учетом простоты и стремится уменьшить нагрузку на конфигурацию, часто встречающуюся в других решениях отладки. Он поддерживает различные пробники и цели, предоставляя высокоуровневый интерфейс для взаимодействия со встраиваемыми системами. Probe-rs позволяет разработчикам устанавливать точки останова, шагать по коду и исследовать состояние памяти и регистров процессора. Он интегрируется с популярными IDE, такими как Visual Studio Code, и поддерживает функции, специфичные для Rust, такие как красивая печать и детализированные сообщения об ошибках. +### OpenOCD -### OpenOCD (Open On-Chip Debugger) +OpenOCD (Open On-Chip Debugger) — это открытое ПО для отладки и программирования встраиваемых систем. Оно поддерживает широкий спектр аппаратных пробников и микроконтроллеров, позволяя разработчикам взаимодействовать с целевыми устройствами через интерфейсы вроде JTAG или SWD. OpenOCD служит сервером отладки, который может подключаться к отладчикам вроде GDB, предоставляя низкоуровневый доступ к регистрам, памяти и периферийным устройствам микроконтроллера. Он высоко конфигурируем и используется в различных окружениях разработки для встраиваемых систем. -OpenOCD is an open-source software tool used for debugging, testing, and programming embedded systems. It provides an interface between the host system and embedded hardware, supporting various transport layers like JTAG and SWD (Serial Wire Debug). OpenOCD integrates with GDB, which is a debugger. OpenOCD is widely supported, with extensive documentation and a large community, but may require complex configuration, especially for custom embedded setups. +## Отладчики -## Debuggers +Отладчики — это инструменты, позволяющие разработчикам проверять состояние программ во время выполнения или после сбоя. Они предоставляют функциональности, такие как установка точек останова, шагание по коду строка за строкой и исследование значений переменных и состояний памяти. Отладчики необходимы для тщательной разработки и обслуживания ПО, позволяя разработчикам убедиться, что их код ведет себя как ожидается в различных условиях. -A debugger allows developers to inspect and control the execution of a program in order to identify and correct errors or bugs. It provides functionalities such as setting breakpoints, stepping through code line by line, and examining the values of variables and memory states. Debuggers are essential for thorough software development and maintenance, enabling developers to ensure that their code behaves as intended under various conditions. +Отладчики знают, как: + * Взаимодействовать с регистрами, отображенными в память. + * Устанавливать точки останова/наблюдения. + * Читать и писать в регистры, отображенные в память. + * Обнаруживать, когда микроконтроллер остановлен для события отладки. + * Продолжать выполнение микроконтроллера после события отладки. + * Стирать и записывать в FLASH микроконтроллера. -Debuggers know how to: - * Interact with the memory mapped registers. - * Set Breakpoints/Watchpoints. - * Read and write to the memory mapped registers. - * Detect when the MCU has been halted for a debug event. - * Continue MCU execution after a debug event has been encountered. - * Erase and write to the microcontroller's FLASH. +### Расширение Probe-rs для Visual Studio Code -### Probe-rs Visual Studio Code Extension +Probe-rs имеет расширение для Visual Studio Code, предоставляющее seamless опыт отладки без обширной настройки. Через это соединение разработчики могут использовать функции, специфичные для Rust, такие как красивая печать и детализированные сообщения об ошибках, обеспечивая, что процесс отладки соответствует экосистеме Rust. -Probe-rs has a Visual Studio Code extension, providing a seamless debugging experience without extensive setup. Through this connection, developers can use Rust-specific features like pretty printing and detailed error messages, ensuring that their debugging process aligns with the Rust ecosystem. +### GDB (GNU Debugger) -### GDB (GNU Debugger) +GDB — это универсальный инструмент отладки, позволяющий разработчикам проверять состояние программ во время выполнения или после сбоя. Для embedded Rust GDB подключается к целевой системе через OpenOCD или другие серверы отладки для взаимодействия с кодом встраиваемой системы. GDB высоко конфигурируем и поддерживает функции вроде удаленной отладки, инспекции переменных и условных точек останова. Он может использоваться на различных платформах и имеет обширную поддержку нужд отладки, специфичных для Rust, таких как красивая печать и интеграция с IDE. -GDB is a versatile debugging tool that allows developers to examine the state of programs while they run or after they crash. For embedded Rust, GDB connects to the target system via OpenOCD or other debugging servers to interact with the embedded code. GDB is highly configurable and supports features like remote debugging, variable inspection, and conditional breakpoints. It can be used on a variety of platforms, and has extensive support for Rust-specific debugging needs, such as pretty printing and integration with IDEs. +## Пробники - -## Probes - -A hardware probe is a device used in the development and debugging of embedded systems to facilitate communication between a host computer and the target embedded device. It typically supports protocols like JTAG or SWD, enabling it to program, debug, and analyze the microcontroller or microprocessor on the embedded system. Hardware probes are crucial for developers to set breakpoints, step through code, and inspect memory and processor registers, effectively allowing them to diagnose and fix issues in real-time. +Аппаратный пробник — это устройство, используемое в разработке и отладке встраиваемых систем для облегчения коммуникации между хост-компьютером и целевым встраиваемым устройством. Он обычно поддерживает протоколы вроде JTAG или SWD, позволяя программировать, отлаживать и анализировать микроконтроллер или микропроцессор на встраиваемой системе. Аппаратные пробники критичны для разработчиков, чтобы устанавливать точки останова, шагать по коду и инспектировать память и регистры процессора, эффективно позволяя диагностировать и исправлять проблемы в реальном времени. ### Rusty-probe -Rusty-probe is an open-sourced USB-based hardware debugging probe designed to work with probe-rs. The combination of Rusty-Probe and probe-rs provides an easy-to-use, cost-effective solution for developers working with embedded Rust applications. +Rusty-probe — это открытый USB-основанный аппаратный пробник отладки, предназначенный для работы с probe-rs. Комбинация Rusty-Probe и probe-rs предоставляет простое в использовании, экономичное решение для разработчиков, работающих с приложениями embedded Rust. ### ST-Link -The ST-Link is a popular debugging and programming probe developed by STMicroelectronics primarily for their STM32 and STM8 microcontroller series. It supports both debugging and programming via JTAG or SWD (Serial Wire Debug) interfaces. ST-Link is widely used due to its direct support from STMicroelectronics' extensive range of development boards and its integration into major IDEs, making it a convenient choice for developers working with STM microcontrollers. +ST-Link — это популярный пробник отладки и программирования, разработанный STMicroelectronics в основном для серий микроконтроллеров STM32 и STM8. Он поддерживает отладку и программирование через интерфейсы JTAG или SWD (Serial Wire Debug). ST-Link широко используется благодаря прямой поддержке от STMicroelectronics для широкого спектра плат разработки и интеграции в основные IDE, делая его удобным выбором для разработчиков, работающих с микроконтроллерами STM. ### J-Link -J-Link, developed by SEGGER Microcontroller, is a robust and versatile debugger supporting a wide range of CPU cores and devices beyond just ARM, such as RISC-V. Known for its high performance and reliability, J-Link supports various communication interfaces, including JTAG, SWD, and fine-pitch JTAG interfaces. It is favored for its advanced features like unlimited breakpoints in flash memory and its compatibility with a multitude of development environments. +J-Link, разработанный SEGGER Microcontroller, — это надежный и универсальный отладчик, поддерживающий широкий спектр ядер CPU и устройств за пределами ARM, таких как RISC-V. Известный своей высокой производительностью и надежностью, J-Link поддерживает различные интерфейсы связи, включая JTAG, SWD и fine-pitch JTAG. Он популярен благодаря продвинутым функциям, таким как неограниченные точки останова в flash-памяти и совместимость с множеством сред разработки. ### MCU-Link -MCU-Link is a debugging probe that also functions as a programmer, provided by NXP Semiconductors. It supports a variety of ARM Cortex microcontrollers and interfaces seamlessly with development tools like MCUXpresso IDE. MCU-Link is particularly notable for its versatility and affordability, making it an accessible option for hobbyists, educators, and professional developers alike. \ No newline at end of file +MCU-Link — это пробник отладки, который также функционирует как программатор, предоставляемый NXP Semiconductors. Он поддерживает разнообразные микроконтроллеры ARM Cortex и seamless интегрируется с инструментами разработки вроде MCUXpresso IDE. MCU-Link особенно известен своей универсальностью и доступностью, делая его доступным вариантом для хоббиистов, преподавателей и профессиональных разработчиков. diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 59b65608..cefee183 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -1,21 +1,21 @@ -# 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 | -|--------|-------------|-----------------------------|--------| -| 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 бита | +| 0x04 | SYST_RVR | Регистр значения перезагрузки | 32 бита | +| 0x08 | SYST_CVR | Регистр текущего значения | 32 бита | +| 0x0C | SYST_CALIB | Регистр значения калибровки | 32 бита | -## The C Approach +## Подход на C -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)] @@ -27,62 +27,29 @@ struct SysTick { } ``` -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 +## Волатильные доступы -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. У нас нет способа указать, какие регистры предназначены только для чтения, а какие — для чтения и записи. -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. +Чтобы решить эти проблемы, мы можем использовать крейт `volatile-register`, который предоставляет типы `RO` (только для чтения), `WO` (только для записи) и `RW` (чтение/запись). Это позволяет нам определить, какие операции безопасны, и избежать случайной записи в регистр только для чтения. -```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`]. - -[`volatile_register`]: https://crates.io/crates/volatile_register - -```rust,ignore -use volatile_register::{RW, RO}; - -#[repr(C)] -struct SysTick { - pub csr: RW, - pub rvr: RW, - pub cvr: RW, - pub calib: RO, -} - -fn get_systick() -> &'static mut SysTick { - unsafe { &mut *(0xE000_E010 as *mut SysTick) } -} - -fn get_time() -> u32 { - let systick = get_systick(); - systick.cvr.read() -} -``` - -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-register`. -## The Rusty Wrapper +## Обертка в стиле Rust -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 для пользователей, чтобы они не беспокоились об этом (при условии, что они доверяют нам, что мы сделали это правильно!). -One example might be: +Пример может выглядеть так: ```rust,ignore use volatile_register::{RW, RO}; @@ -122,7 +89,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() { @@ -136,4 +103,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. +Наш аргумент `&mut self` в функции `set_reload` проверяет, что нет других ссылок на *этот* конкретный экземпляр структуры `SystemTimer`, но он не мешает пользователю создать второй экземпляр `SystemTimer`, который указывает на то же самое периферийное устройство! Код, написанный в таком стиле, будет работать, если автор достаточно внимателен, чтобы заметить все эти "дублирующиеся" экземпляры драйвера, но как только код распространяется по нескольким модулям, драйверам, разработчикам и дням, такие ошибки становятся все проще совершать. diff --git a/src/peripherals/borrowck.md b/src/peripherals/borrowck.md index fb817b2d..6c5910a8 100644 --- a/src/peripherals/borrowck.md +++ b/src/peripherals/borrowck.md @@ -1,19 +1,19 @@ -## 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` для чтения или записи в память периферийных устройств, так как она может измениться в любой момент. +2. В программном обеспечении мы должны иметь возможность предоставлять любое количество доступов только для чтения к этим периферийным устройствам. +3. Если программное обеспечение должно иметь доступ на чтение и запись к периферийному устройству, оно должно быть единственным обладателем ссылки на это устройство. -## The Borrow Checker +## Проверяющий заимствования -The last two of these rules sound suspiciously similar to what the Borrow Checker does already! +Последние два правила подозрительно похожи на то, что уже делает проверяющий заимствования (Borrow Checker)! -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? +Мы можем это сделать, но для проверяющего заимствования нам нужно иметь ровно один экземпляр каждого периферийного устройства, чтобы Rust мог корректно это обработать. К счастью, в аппаратном обеспечении есть только один экземпляр любого данного периферийного устройства, но как мы можем отразить это в структуре нашего кода? diff --git a/src/peripherals/index.md b/src/peripherals/index.md index 23731c56..7ee3ae87 100644 --- a/src/peripherals/index.md +++ b/src/peripherals/index.md @@ -1,44 +1,44 @@ -# 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. +Большинство микроконтроллеров имеют не только процессор, оперативную память или флэш-память — они содержат участки кремния, которые используются для взаимодействия с системами вне микроконтроллера, а также для прямого и косвенного взаимодействия с окружающим миром через датчики, контроллеры двигателей или интерфейсы для человека, такие как дисплей или клавиатура. Эти компоненты в совокупности называются периферийными устройствами. -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. +Эти периферийные устройства полезны, потому что позволяют разработчику переложить обработку на них, избегая необходимости обрабатывать все в программном обеспечении. Подобно тому, как разработчик настольных приложений перекладывает обработку графики на видеокарту, разработчики встраиваемых систем могут переложить некоторые задачи на периферийные устройства, позволяя процессору заниматься чем-то другим важным или вообще ничего не делать, чтобы сэкономить энергию. -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: +Если посмотреть на основную печатную плату старомодного домашнего компьютера 1970-х или 1980-х годов (а на самом деле настольные ПК прошлого не так уж далеки от современных встраиваемых систем), вы ожидаете увидеть: -* A processor -* A RAM chip -* A ROM chip -* An I/O controller +* Процессор +* Чип оперативной памяти +* Чип ПЗУ +* Контроллер ввода-вывода -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. +Чип оперативной памяти, чип ПЗУ и контроллер ввода-вывода (периферийное устройство в этой системе) будут соединены с процессором через серию параллельных дорожек, известных как "шина". Эта шина передает адресную информацию, которая выбирает, с каким устройством на шине процессор хочет взаимодействовать, и шину данных, которая передает фактические данные. В наших встраиваемых микроконтроллерах применяются те же принципы — просто все упаковано на одном куске кремния. -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. +Однако, в отличие от видеокарт, которые обычно имеют программный API, такой как Vulkan, Metal или OpenGL, периферийные устройства в микроконтроллерах представлены через аппаратный интерфейс, который отображается на участок памяти. -## Linear and Real Memory Space +## Линейное и реальное адресное пространство -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 (Memory Management Unit, блок управления памятью). Этот компонент выполняет две основные функции: обеспечение доступа... -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: +На микроконтроллере адресное пространство используется иначе. Например, как упоминалось в предыдущих главах, оперативная память может быть расположена по адресу `0x2000_0000`. Если наша оперативная память имеет размер 64 КиБ (т.е. максимальный адрес `0x2000_FFFF`), то адреса от `0x2000_0000` до `0x2000_FFFF` будут соответствовать нашей оперативной памяти. Когда мы записываем в переменную, находящуюся по адресу `0x2000_1234`, внутри происходит логика, которая определяет верхнюю часть адреса (в данном случае `0x2000`) и активирует оперативную память, чтобы она могла работать с нижней частью адреса (`0x1234` в данном случае). На Cortex-M у нас также есть флэш-ПЗУ, отображенное по адресу `0x0000_0000` до, скажем, адреса `0x0007_FFFF` (если у нас флэш-ПЗУ на 512 КиБ). Вместо того чтобы игнорировать все оставшееся пространство между этими двумя областями, разработчики микроконтроллеров отобразили интерфейс для периферийных устройств на определенные адреса памяти. Это выглядит примерно так: ![](../assets/nrf52-memory-map.png) -[Nordic nRF52832 Datasheet (pdf)] +[Техническое описание Nordic nRF52832 (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-битного слова по определенному адресу памяти. Периферийное устройство последовательного порта затем возьмет на себя задачу и автоматически отправит данные. -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. Запишите `0x8000_0000` в регистр конфигурации частоты SPI, и порт SPI будет отправлять данные со скоростью 8 мегабит в секунду. Запишите `0x0200_0000` по тому же адресу, и порт SPI будет отправлять данные со скоростью 125 килобит в секунду. Эти регистры конфигурации выглядят примерно так: ![](../assets/nrf52-spi-frequency-register.png) -[Nordic nRF52832 Datasheet (pdf)] +[Техническое описание Nordic nRF52832 (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 +[Техническое описание Nordic nRF52832 (pdf)]: http://infocenter.nordicsemi.com/pdf/nRF52832_PS_v1.1.pdf diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 1f3a556e..cd062ff4 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -1,15 +1,14 @@ -# 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 +[Шаблон синглтон]: 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 +Мы могли бы сделать все публичными статическими переменными, например так: ```rust,ignore static mut THE_SERIAL_PORT: SerialPort = SerialPort; @@ -21,11 +20,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 structure, in this case called `PERIPHERALS`, which contains an `Option` for each of our peripherals. +Вместо того чтобы делать наше периферийное устройство глобальной переменной, мы можем создать структуру, в данном случае названную `PERIPHERALS`, которая содержит `Option` для каждого из наших периферийных устройств. ```rust,ignore struct Peripherals { @@ -42,65 +41,41 @@ 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()` более одного раза, наш код вызовет панику! ```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. - -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. - -## 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. - -```rust,ignore -use cortex_m::singleton; - -fn main() { - // OK if `main` is executed only once - let x: &'static mut bool = - singleton!(: bool = false).unwrap(); -} -``` - -[cortex_m docs](https://docs.rs/cortex-m/latest/cortex_m/macro.singleton.html) +Хотя взаимодействие с этой структурой является `unsafe`, после того как мы получили содержащийся в ней `SerialPort`, нам больше не нужно использовать `unsafe` или саму структуру `PERIPHERALS`. -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. +Это имеет небольшую накладную стоимость во время выполнения, поскольку нам нужно обернуть структуру `SerialPort` в `Option`, и нам придется один раз вызвать `take_serial()`, однако эта небольшая начальная стоимость позволяет нам использовать проверяющий заимствования... ```rust,ignore -// cortex-m-rtic v0.5.x -#[rtic::app(device = lm3s6965, peripherals = true)] -const APP: () = { - #[init] - fn init(cx: init::Context) { - static mut X: u32 = 0; - - // Cortex-M peripherals - let core: cortex_m::Peripherals = cx.core; +#[entry] +fn main(cx: main::Context) -> ! { + // Получение доступа к периферийным устройствам ядра + let core: CorePeripherals = cx.core; - // Device specific peripherals - let device: lm3s6965::Peripherals = cx.device; - } + // Устройство-специфичные периферийные устройства + let device: lm3s6965::Peripherals = cx.device; } ``` -## 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) @@ -109,30 +84,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! +Эти два фактора вместе означают, что доступ к аппаратному обеспечению возможен только в том случае, если мы соответствующим образом удовлетворили проверяющий заимствования, что означает, что у нас никогда не будет нескольких изменяемых ссылок на одно и то же аппаратное обеспечение! ```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( @@ -143,7 +118,7 @@ fn setup_spi_port( } ``` -This isn't: +А это — нет: ```rust,ignore fn read_button(gpio: &GpioPin) -> bool { @@ -151,4 +126,4 @@ 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/portability/index.md b/src/portability/index.md index 87a6ce96..9b3513e5 100644 --- a/src/portability/index.md +++ b/src/portability/index.md @@ -1,64 +1,64 @@ -# 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. +> Они часто позволяют программистам писать независимые от устройства высокопроизводительные приложения, предоставляя стандартные вызовы операционной системы к оборудованию. > -> *Wikipedia: [Hardware Abstraction Layer]* +> *Википедия: [Уровень абстракции оборудования]* -[Hardware Abstraction Layer]: https://en.wikipedia.org/wiki/Hardware_abstraction +[Уровень абстракции оборудования]: 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. +Встраиваемые системы в этом отношении немного особенные, поскольку обычно у них нет операционных систем и программного обеспечения, устанавливаемого пользователем, а вместо этого используются образы прошивки, которые компилируются целиком, а также существуют другие ограничения. Таким образом, традиционный подход, определенный Википедией, потенциально может работать, но, вероятно, не является наиболее продуктивным для обеспечения портируемости. -How do we do this in Rust? Enter **[embedded-hal]**... +Как это делается в Rust? Встречайте **[embedded-hal]**... -## What is [embedded-hal]? +## Что такое [embedded-hal]? -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). +Вкратце, это набор трейтов, которые определяют контракты реализации между **реализациями HAL**, **драйверами** и **приложениями (или прошивками)**. Эти контракты включают как возможности (т.е. если трейт реализован для определенного типа, **реализация HAL** предоставляет определенную функциональность), так и методы (т.е. если вы можете создать тип, реализующий трейт, гарантируется наличие методов, указанных в этом трейте). -A typical layering might look like this: +Типичная структура уровней может выглядеть следующим образом: ![](../assets/rust_layers.svg) -Some of the defined traits in **[embedded-hal]** are: -* GPIO (input and output pins) -* Serial communication +Некоторые из определенных трейтов в **[embedded-hal]** включают: +* GPIO (пины ввода и вывода) +* Последовательная связь * I2C * SPI -* 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]** и крейтов, их реализующих и использующих, — это контроль сложности. Если учесть, что приложение должно реализовать использование периферийного устройства в оборудовании, а также само приложение и, возможно, драйверы для дополнительных аппаратных компонентов, становится понятно, что возможности повторного использования весьма ограничены. Математически, если **M** — это количество реализаций HAL для периферийных устройств, а **N** — количество драйверов, то без использования **[embedded-hal]** сложность реализации может достигать **M×N**. Использование трейтов **[embedded-hal]** снижает сложность реализации до уровня, близкого к **M+N**. Конечно, есть и дополнительные преимущества, такие как меньшее количество проб и ошибок благодаря хорошо определенным и готовым к использованию API. -## Users of the [embedded-hal] +## Пользователи [embedded-hal] -As said above there are three main users of the HAL: +Как упомянуто выше, есть три основных пользователя 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 -* 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. Типичные реализации состоят из трех частей: +* Один или несколько типов, специфичных для оборудования +* Функции для создания и инициализации таких типов, часто предоставляющие различные параметры конфигурации (скорость, режим работы, используемые пины и т.д.) +* Одна или несколько реализаций (`impl`) трейтов **[embedded-hal]** для этого типа -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** может быть представлена в различных вариантах: +* Через низкоуровневый доступ к оборудованию, например, через регистры +* Через операционную систему, например, с использованием `sysfs` в Linux +* Через адаптер, например, заглушки типов для модульного тестирования +* Через драйвер для аппаратных адаптеров, например, мультиплексор I2C или расширитель GPIO -### 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). +Драйвер реализует набор пользовательских функций для внутреннего или внешнего компонента, подключенного к периферийному устройству, реализующему трейты [embedded-hal]. Типичные примеры таких драйверов включают различные датчики (температуры, магнитометр, акселерометр, освещенности), устройства отображения (светодиодные матрицы, ЖК-дисплеи) и исполнительные механизмы (двигатели, передатчики). -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], что обеспечивается через ограничение трейта, и предоставляет собственный экземпляр типа с пользовательским набором методов, позволяющих взаимодействовать с управляемым устройством. -### 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). +Приложение объединяет различные части и обеспечивает достижение желаемой функциональности. При переносе между различными системами именно эта часть требует наибольших усилий по адаптации, поскольку приложение должно правильно инициализировать реальное оборудование через реализацию HAL, а инициализация различного оборудования может существенно отличаться. Кроме того, выбор пользователя часто играет большую роль, поскольку компоненты могут быть физически подключены к разным терминалам, шины оборудования иногда требуют внешнего оборудования для соответствия конфигурации, или существуют различные компромиссы в использовании внутренних периферийных устройств (например, доступно несколько таймеров с разными возможностями, или периферийные устройства конфликтуют друг с другом). [embedded-hal]: https://crates.io/crates/embedded-hal diff --git a/src/start/exceptions.md b/src/start/exceptions.md index 2634cf58..20c65c24 100644 --- a/src/start/exceptions.md +++ b/src/start/exceptions.md @@ -1,201 +1,38 @@ -# 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. +Исключения и прерывания — это аппаратный механизм, с помощью которого процессор обрабатывает асинхронные события и фатальные ошибки (например, выполнение недопустимой инструкции). Исключения подразумевают вытеснение и включают обработчики исключений — подпрограммы, выполняемые в ответ на сигнал, вызвавший событие. -The `cortex-m-rt` crate provides an [`exception`] attribute to declare exception -handlers. +Крейт `cortex-m-rt` предоставляет атрибут [`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 +```rust,ignore +// Обработчик исключения для исключения SysTick (системный таймер) #[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. - -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. - -``` rust,ignore -#[exception] -fn SysTick() { - static mut COUNT: u32 = 0; - - // `COUNT` has transformed to type `&mut u32` and it's safe to use - *COUNT += 1; -} -``` - -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. - -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. These handlers are called by the hardware itself which is assumed to be physically non-concurrent. - -As a result, in the context of exception handlers in embedded systems, the absence of concurrent invocations of the same handler ensures that there are no reentrancy issues, even if the handler uses static mutable variables. - -In a multicore system, where multiple processor cores are executing code concurrently, the potential for reentrancy issues becomes relevant again, even within exception handlers. While each core may have its own set of exception handlers, there can still be scenarios where multiple cores attempt to execute the same exception handler simultaneously. -To address this concern in a multicore environment, proper synchronization mechanisms need to be employed within the exception handlers to ensure that access to shared resources is properly coordinated among the cores. This typically involves the use of techniques such as locks, semaphores, or atomic operations to prevent data races and maintain data integrity - -> 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 dereference the reference via `*` to access the values of the variables without -> needing to wrap them in an `unsafe` block. - -## A complete example +Помимо атрибута `exception`, обработчики исключений выглядят как обычные функции, но есть одно важное различие: обработчики `exception` *нельзя* вызывать программно. Например, в приведенном выше примере вызов `SysTick();` приведет к ошибке компиляции. -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. - -> **NOTE**: You can run this example on any Cortex-M device; you can also run it -> on QEMU +Такое поведение намеренное и необходимо для обеспечения следующей особенности: переменные `static mut`, объявленные *внутри* обработчиков `exception`, *безопасны* для использования. ```rust,ignore -#![deny(unsafe_code)] -#![no_main] -#![no_std] - -use panic_halt as _; - -use core::fmt::Write; - -use cortex_m::peripheral::syst::SystClkSource; -use cortex_m_rt::{entry, exception}; -use cortex_m_semihosting::{ - debug, - hio::{self, HostStream}, -}; - -#[entry] -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 - syst.set_clock_source(SystClkSource::Core); - // this is configured for the LM3S6965 which has a default CPU clock of 12 MHz - syst.set_reload(12_000_000); - syst.clear_current(); - syst.enable_counter(); - syst.enable_interrupt(); - - loop {} -} - #[exception] fn SysTick() { static mut COUNT: u32 = 0; - static mut STDOUT: Option = None; + // `COUNT` преобразуется в тип `&mut u32` и безопасен для использования *COUNT += 1; - - // Lazy initialization - if STDOUT.is_none() { - *STDOUT = hio::hstdout().ok(); - } - - if let Some(hstdout) = STDOUT.as_mut() { - write!(hstdout, "{}", *COUNT).ok(); - } - - // IMPORTANT omit this `if` block if running on real hardware or your - // debugger will end in an inconsistent state - if *COUNT == 9 { - // This will terminate the QEMU process - debug::exit(debug::EXIT_SUCCESS); - } -} -``` - -``` console -tail -n5 Cargo.toml -``` - -``` toml -[dependencies] -cortex-m = "0.5.7" -cortex-m-rt = "0.6.3" -panic-halt = "0.2.0" -cortex-m-semihosting = "0.3.1" -``` - -``` text -$ cargo run --release - Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..) -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. - -## 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: - -``` rust,ignore -fn DefaultHandler() { - loop {} } ``` -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. +Как известно, использование переменных `static mut` в функции делает ее [*нереентерабельной*](https://en.wikipedia.org/wiki/Reentrancy_(computing)). Вызов нереентерабельной функции, прямо или косвенно, из нескольких обработчиков исключений/прерываний или из `main` и одного или более обработчиков исключений/прерываний приводит к неопределенному поведению. -It's possible to override this `DefaultHandler` using the `exception` attribute: +Безопасный Rust никогда не должен приводить к неопределенному поведению, поэтому нереентерабельные функции должны быть помечены как `unsafe`. Однако, как было сказано, обработчики `exception` могут безопасно использовать переменные `static mut`. Это возможно, потому что обработчики `exception` *не могут* быть вызваны программно, что исключает возможность реентерабельности. Эти обработчики вызываются самим аппаратным обеспечением... -``` 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. - -## The hard fault handler - -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. - -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. - -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, поскольку `qemu-system-arm -machine lm3s6965evb` не проверяет загрузку памяти и с радостью вернет `0` при чтении из недопустимой памяти. ```rust,ignore #![no_main] @@ -211,7 +48,7 @@ use cortex_m_semihosting::hio; #[entry] fn main() -> ! { - // read a nonexistent memory location + // Чтение из несуществующего адреса памяти unsafe { ptr::read_volatile(0x3FFF_0000 as *const u32); } @@ -229,10 +66,9 @@ 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 +```text $ openocd (..) ExceptionFrame { @@ -247,13 +83,11 @@ 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` — это значение программного счетчика на момент исключения, и оно указывает на инструкцию, вызвавшую исключение. +Если посмотреть дизассемблированный код программы: -``` text +```text $ cargo objdump --bin app --release -- -d --no-show-raw-insn --print-imm-hex (..) ResetTrampoline: @@ -263,7 +97,4 @@ ResetTrampoline: 800094c: b #-0x4 ``` -You can lookup the value of the program counter `0x0800094a` in the disassembly. -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]`) вызвала исключение. Поле `r0` в `ExceptionFrame` покажет, что значение регистра `r0` в этот момент было `0x3fff_fffe`. diff --git a/src/start/hardware.md b/src/start/hardware.md index ba9fe6e7..38624f76 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -1,46 +1,31 @@ -# 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: +Перед началом вам нужно определить некоторые характеристики целевого устройства, поскольку они будут использоваться для настройки проекта: -- The ARM core. e.g. Cortex-M3. +- Ядро ARM. Например, Cortex-M3. +- Есть ли у ядра ARM FPU? Ядра Cortex-M4**F** и Cortex-M7**F** имеют FPU. +- Сколько флэш-памяти и оперативной памяти имеет целевое устройство? Например, 256 КиБ флэш-памяти и 32 КиБ оперативной памяти. +- Где отображаются флэш-память и оперативная память в адресном пространстве? Например, оперативная память обычно располагается по адресу `0x2000_0000`. -- Does the ARM core include an FPU? Cortex-M4**F** and Cortex-M7**F** cores do. +Эту информацию можно найти в техническом описании или справочном руководстве вашего устройства. -- How much Flash memory and RAM does the target device have? e.g. 256 KiB of - Flash and 32 KiB of RAM. +В этом разделе мы будем использовать наше эталонное оборудование — плату STM32F3DISCOVERY. Эта плата содержит микроконтроллер STM32F303VCT6. Этот микроконтроллер имеет: -- Where are Flash memory and RAM mapped in the address space? e.g. RAM is - commonly located at address `0x2000_0000`. +- Ядро Cortex-M4F с однопрецизионным FPU. +- 256 КиБ флэш-памяти, расположенной по адресу `0x0800_0000`. +- 40 КиБ оперативной памяти, расположенной по адресу `0x2000_0000`. (Есть еще одна область оперативной памяти, но для простоты мы ее проигнорируем). -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: +Мы начнем с нуля с новым экземпляром шаблона. Обратитесь к [предыдущему разделу о QEMU] для напоминания о том, как это сделать без использования `cargo-generate`. -- A Cortex-M4F core that includes a single precision FPU +[предыдущий раздел о QEMU]: qemu.md -- 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 - -``` text +```text $ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart Project Name: app Creating project called `app`... @@ -49,245 +34,30 @@ $ 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 +```console tail -n5 .cargo/config.toml ``` -``` toml -# Pick ONE of these compilation targets -# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ -# 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) -``` - -We'll use `thumbv7em-none-eabihf` as that covers the Cortex-M4F core. -> **NOTE**: As you may remember from the previous chapter, we have to install -> all targets and this is a new one. So don't forget to run the installation -> process `rustup target add thumbv7em-none-eabihf` for this target. - -The second step is to enter the memory region information into the `memory.x` -file. - -``` text -$ cat memory.x -/* Linker script for the STM32F303VCT6 */ -MEMORY -{ - /* NOTE 1 K = 1 KiBi = 1024 bytes */ - FLASH : ORIGIN = 0x08000000, LENGTH = 256K - 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`. - -We'll start with the hello example again, but first we have to make a small -change. - -In `examples/hello.rs`, make sure the `debug::exit()` call is commented out or -removed. It is used only for running in QEMU. - -```rust,ignore -#[entry] -fn main() -> ! { - hprintln!("Hello, world!").unwrap(); - - // exit QEMU - // NOTE do not run this on hardware; it can corrupt OpenOCD state - // debug::exit(debug::EXIT_SUCCESS); - - loop {} -} -``` - -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. - -``` 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 -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. - -As done during the [verify] section connect the discovery board to your laptop / -PC and check that the ST-LINK header is populated. - -[verify]: ../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. - -``` console -cat openocd.cfg -``` - -``` text -# Sample OpenOCD configuration for the STM32F3DISCOVERY development board - -# Depending on the hardware revision you got you'll have to pick ONE of these -# interfaces. At any time only one interface should be commented out. - -# Revision C (newer revision) -source [find interface/stlink.cfg] - -# Revision A and B (older revisions) -# source [find interface/stlink-v2.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`. - -``` text -$ openocd -Open On-Chip Debugger 0.10.0 -Licensed under GNU GPL v2 -For bug reports, read - http://openocd.org/doc/doxygen/bugs.html -Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '. -adapter speed: 1000 kHz -adapter_nsrst_delay: 100 -Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD -none separate -Info : Unable to match requested speed 1000 kHz, using 950 kHz -Info : Unable to match requested speed 1000 kHz, using 950 kHz -Info : clock speed 950 kHz -Info : STLINK v2 JTAG v27 API v2 SWIM v15 VID 0x0483 PID 0x374B -Info : using stlink api v2 -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. - -``` text -gdb-multiarch -q target/thumbv7em-none-eabihf/debug/examples/hello -``` - -**NOTE**: like before 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`. - -Next connect GDB to OpenOCD, which is waiting for a TCP connection on port 3333. - -``` console -(gdb) target remote :3333 -Remote debugging using :3333 -0x00000000 in ?? () -``` - -Now proceed to *flash* (load) the program onto the microcontroller using the -`load` command. - -``` console -(gdb) load -Loading section .vector_table, size 0x400 lma 0x8000000 -Loading section .text, size 0x1518 lma 0x8000400 -Loading section .rodata, size 0x414 lma 0x8001918 -Start address 0x08000400, load size 7468 -Transfer rate: 13 KB/sec, 2489 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. - -``` console -(gdb) monitor arm semihosting enable -semihosting is enabled -``` - -> You can see all the OpenOCD commands by invoking the `monitor help` command. - -Like before we can skip all the way to `main` using a breakpoint and the -`continue` command. - -``` console -(gdb) break main -Breakpoint 1 at 0x8000490: file examples/hello.rs, line 11. -Note: automatically using hardware breakpoints for read-only addresses. - -(gdb) continue -Continuing. - -Breakpoint 1, hello::__cortex_m_rt_main_trampoline () at examples/hello.rs:11 -11 #[entry] -``` - -> **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). - -Step into the main function with `step`. - -``` console -(gdb) step -halted: PC: 0x08000496 -hello::__cortex_m_rt_main () at examples/hello.rs:13 -13 hprintln!("Hello, world!").unwrap(); -``` - -After advancing the program with `next` you should see "Hello, world!" printed on the OpenOCD console, -among other stuff. - -``` console -$ 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 -``` -The message is only displayed once as the program is about to enter the infinite loop defined in line 19: `loop {}` - -You can now exit GDB using the `quit` command. - -``` console -(gdb) quit -A debugging session is active. - - Inferior 1 [Remote target] will be detached. - -Quit anyway? (y or n) +```toml +# Выберите ОДИН из этих целей компиляции +# target = "thumbv6m-none-eabi... ``` -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 peek: +Теперь нам нужно создать GDB-скрипт для загрузки программы и взаимодействия с платой. В шаблоне уже есть один GDB-скрипт с именем `openocd.gdb`, созданный на этапе `cargo generate`, и он должен работать без изменений. Давайте взглянем на него: -``` console +```console cat openocd.gdb ``` -``` text +```text target extended-remote :3333 -# print demangled symbols +# Печать деманглированных символов set print asm-demangle on -# detect unhandled exceptions, hard faults and panics +# Обнаружение необработанных исключений, жестких сбоев и паник break DefaultHandler break HardFault break rust_begin_unwind @@ -296,35 +66,32 @@ monitor arm semihosting enable load -# start the process but immediately halt the processor +# Запуск процесса с немедленной остановкой процессора 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` в пользовательский запускатель, чтобы `cargo run` одновременно компилировал программу *и* запускал сессию GDB. Этот запускатель включен в `.cargo/config.toml`, но закомментирован. -``` console +```console head -n10 .cargo/config.toml ``` -``` toml +```toml [target.thumbv7m-none-eabi] -# uncomment this to make `cargo run` execute programs on QEMU +# Раскомментируйте это, чтобы `cargo run` запускал программы на QEMU # runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# uncomment ONE of these three option to make `cargo run` start a GDB session -# which option to pick depends on your system +# Раскомментируйте ОДИН из этих трех вариантов, чтобы `cargo run` запускал сессию GDB +# Какой вариант выбрать, зависит от вашей системы runner = "arm-none-eabi-gdb -x openocd.gdb" # runner = "gdb-multiarch -x openocd.gdb" # runner = "gdb -x openocd.gdb" ``` -``` text +```text $ cargo run --example hello (..) Loading section .vector_table, size 0x400 lma 0x8000000 diff --git a/src/start/index.md b/src/start/index.md index 2684955d..7a36b8bb 100644 --- a/src/start/index.md +++ b/src/start/index.md @@ -1,10 +1,5 @@ -# 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/interrupts.md b/src/start/interrupts.md index f43414a7..acb64c3a 100644 --- a/src/start/interrupts.md +++ b/src/start/interrupts.md @@ -1,65 +1,49 @@ -# 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 -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. +Прерывания отличаются от исключений по ряду параметров, но их работа и использование в основном схожи, и они обрабатываются одним и тем же контроллером прерываний. В то время как исключения определяются архитектурой Cortex-M, прерывания всегда являются специфичными для производителя (и часто даже для конкретной микросхемы) как по именованию, так и по функциональности. -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 exposes an [`interrupt`] attribute for declaring interrupt handlers. However, this -attribute is only available when the device feature is enabled. That said, this attribute is not intended to be used directly—doing -so will result in a compilation error. +Аналогично исключениям, крейт `cortex-m-rt` предоставляет атрибут [`interrupt`] для объявления обработчиков прерываний. Однако этот атрибут доступен только при включении функции устройства. При этом данный атрибут не предназначен для прямого использования — это приведет к ошибке компиляции. -Instead, you should use the re-exported version of the interrupt attribute provided by the device crate (usually generated using svd2rust). -This ensures that the compiler can verify that the interrupt actually exists on the target device. The list of available interrupts—and -their position in the interrupt vector table—is typically auto-generated from an SVD file by svd2rust. +Вместо этого вы должны использовать переэкспортированную версию атрибута `interrupt`, предоставляемую крейтом устройства (обычно сгенерированным с помощью `svd2rust`). Это гарантирует, что компилятор может проверить, существует ли прерывание на целевом устройстве. Список доступных прерываний — и их положение в таблице векторов прерываний — обычно автоматически генерируется из файла SVD с помощью `svd2rust`. [`interrupt`]: https://docs.rs/cortex-m-rt-macros/0.1.5/cortex_m_rt_macros/attr.interrupt.html -``` rust,ignore -use lm3s6965::interrupt; // Re-exported attribute from the device crate +```rust,ignore +use lm3s6965::interrupt; // Переэкспортированный атрибут из крейта устройства -// Interrupt handler for the Timer2 interrupt +// Обработчик прерывания для прерывания Timer2 #[interrupt] fn TIMER2A() { // .. - // 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` для хранения состояния. -``` rust,ignore +```rust,ignore #[interrupt] fn TIMER2A() { 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 diff --git a/src/start/io.md b/src/start/io.md index 7febc026..f82501cc 100644 --- a/src/start/io.md +++ b/src/start/io.md @@ -1,3 +1,3 @@ -# IO +# Ввод/вывод -> **TODO** Cover memory mapped I/O using registers. +> **TODO** Рассмотреть отображение ввода/вывода в память с использованием регистров. diff --git a/src/start/panicking.md b/src/start/panicking.md index a1365fa8..2d838ed5 100644 --- a/src/start/panicking.md +++ b/src/start/panicking.md @@ -1,80 +1,53 @@ -# 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. Встроенные операции, такие как индексация, проверяются во время выполнения на предмет безопасности памяти. При попытке индексации за пределами границ массива возникает паника. -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. +В стандартной библиотеке поведение паники определено: оно разворачивает стек вызывающего потока, если пользователь не выбрал завершение программы при панике. -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. +Однако в программах без стандартной библиотеки поведение паники остается неопределенным. Поведение можно выбрать, объявив функцию `#[panic_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: +Учитывая, что встраиваемые системы варьируются от пользовательских до критически важных для безопасности (не могут завершаться сбоем), не существует универсального поведения при панике, но есть множество часто используемых поведений. Эти общие поведения были упакованы в крейты, которые определяют функцию `#[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, периферийного устройства, специфичного для 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. +Вы можете найти еще больше крейтов, выполнив поиск по ключевому слову [`panic-handler`] на crates.io. [`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: +Программа может выбрать одно из этих поведений, просто подключив соответствующий крейт... -``` rust,ignore +```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`). +В этом примере крейт подключается к `panic-halt` при сборке с профилем dev (`cargo build`), но к `panic-abort` при сборке с профилем release (`cargo build --release`). -> 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 _;` используется для обеспечения включения обработчика паники `panic_abort` в итоговый исполняемый файл, при этом ясно указывая компилятору, что мы не будем явно использовать что-либо из крейта. Без переименования `as _` компилятор выдал бы предупреждение о неиспользуемом импорте. Иногда можно встретить `extern crate panic_abort` вместо этого, что является старым стилем, использовавшимся до издания Rust 2018, и теперь должно использоваться только для "sysroot" крейтов (распространяемых вместе с Rust), таких как `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. +Вот пример, который пытается индексировать массив за его пределами. Эта операция приводит к панике. ```rust,ignore #![no_main] @@ -88,20 +61,18 @@ use cortex_m_rt::entry; fn main() -> ! { let xs = [0, 1, 2]; let i = xs.len(); - let _y = xs[i]; // out of bounds access + let _y = xs[i]; // Доступ за пределами массива loop {} } ``` -This example chose the `panic-semihosting` behavior which prints the panic -message to the host console using semihosting. +В этом примере выбрано поведение `panic-semihosting`, которое выводит сообщение о панике на консоль хоста с использованием семихостинга. -``` text +```text $ cargo run Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb (..) 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` и убедиться, что в этом случае сообщение не выводится. diff --git a/src/start/qemu.md b/src/start/qemu.md index f7241b87..1142998d 100644 --- a/src/start/qemu.md +++ b/src/start/qemu.md @@ -1,34 +1,27 @@ # 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. Мы выбрали его в качестве начальной цели, потому что он [может быть эмулирован](https://wiki.qemu.org/Documentation/Platforms/ARM#Supported_in_qemu-system-arm) с использованием QEMU, так что в этом разделе вам не придется возиться с оборудованием, и мы сможем сосредоточиться на инструментах и процессе разработки. [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. +**ВАЖНО** +В этом руководстве мы будем использовать имя "app" для проекта. Везде, где вы видите слово "app", заменяйте его на имя, которое вы выбрали для своего проекта. Или вы можете назвать свой проект "app" и избежать замен. -## Creating a non standard Rust program +## Создание нестандартной программы на Rust -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. +Мы будем использовать шаблон проекта [`cortex-m-quickstart`] для создания нового проекта. Созданный проект будет содержать базовое приложение: хорошую отправную точку для нового встраиваемого приложения на Rust. Кроме того, проект будет содержать директорию `examples` с несколькими отдельными приложениями, демонстрирующими ключевые функции встраиваемого 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 ``` @@ -43,16 +36,16 @@ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart cd app ``` -### Using `git` +### Использование `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] @@ -60,456 +53,65 @@ authors = ["{{authors}}"] # "{{authors}}" -> "John Smith" edition = "2018" name = "{{project-name}}" # "{{project-name}}" -> "app" version = "0.1.0" - -# .. - -[[bin]] -name = "{{project-name}}" # "{{project-name}}" -> "app" -test = false -bench = false -``` - -### Using neither - -Grab the latest snapshot of the `cortex-m-quickstart` template and extract it. - -```console -curl -LO https://github.com/rust-embedded/cortex-m-quickstart/archive/master.zip -unzip master.zip -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". - -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`: - -```rust,ignore -#![no_std] -#![no_main] - -use panic_halt as _; - -use cortex_m_rt::entry; - -#[entry] -fn main() -> ! { - loop { - // your code goes here - } -} -``` - -This program is a bit different from a standard Rust program so let's take a -closer look. - -`#![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_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. - -`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. - -[`#[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]: 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: - -```console -tail -n6 .cargo/config.toml -``` - -```toml -[build] -# Pick ONE of these compilation targets -# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ -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: -``` 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: - -```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`. - -With `cargo-readobj` we can print the ELF headers to confirm that this is an ARM -binary. - -``` 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 - - -``` text -ELF Header: - Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 - Class: ELF32 - Data: 2's complement, little endian - Version: 1 (current) - OS/ABI: UNIX - System V - ABI Version: 0x0 - Type: EXEC (Executable file) - Machine: ARM - Version: 0x1 - Entry point address: 0x405 - Start of program headers: 52 (bytes into file) - Start of section headers: 153204 (bytes into file) - Flags: 0x5000200 - Size of this header: 52 (bytes) - Size of program headers: 32 (bytes) - Number of program headers: 2 - Size of section headers: 40 (bytes) - Number of section headers: 19 - Section header string table index: 18 -``` - -`cargo-size` can print the size of the linker sections of the binary. - - -```console -cargo size --bin app --release -- -A -``` -we use `--release` to inspect the optimized version - -``` text -app : -section size addr -.vector_table 1024 0x0 -.text 92 0x400 -.rodata 0 0x45c -.data 0 0x20000000 -.bss 0 0x20000000 -.debug_str 2958 0x0 -.debug_loc 19 0x0 -.debug_abbrev 567 0x0 -.debug_info 4929 0x0 -.debug_ranges 40 0x0 -.debug_macinfo 1 0x0 -.debug_pubnames 2035 0x0 -.debug_pubtypes 1892 0x0 -.ARM.attributes 46 0x0 -.debug_frame 100 0x0 -.debug_line 867 0x0 -Total 14570 -``` - -> A refresher on 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. - -```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 - -> **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. - -```text -app: file format ELF32-arm-little - -Disassembly of section .text: -main: - 400: bl #0x256 - 404: b #-0x4 - -Reset: - 406: bl #0x24e - 40a: movw r0, #0x0 - < .. truncated any more instructions .. > - -DefaultHandler_: - 656: b #-0x4 - -UsageFault: - 657: strb r7, [r4, #0x3] - -DefaultPreInit: - 658: bx lr - -__pre_init: - 659: strb r7, [r0, #0x1] - -__nop: - 65a: bx lr - -HardFaultTrampoline: - 65c: mrs r0, msp - 660: b #-0x2 - -HardFault_: - 662: b #-0x4 - -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. - -For convenience here's the source code of `examples/hello.rs`: - -```rust,ignore -//! Prints "Hello, world!" on the host console using semihosting - -#![no_main] -#![no_std] - -use panic_halt as _; - -use cortex_m_rt::entry; -use cortex_m_semihosting::{debug, hprintln}; - -#[entry] -fn main() -> ! { - hprintln!("Hello, world!").unwrap(); - - // exit QEMU - // NOTE do not run this on hardware; it can corrupt OpenOCD state - debug::exit(debug::EXIT_SUCCESS); - - loop {} -} ``` -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. +Теперь давайте настроим отладку в GDB, чтобы увидеть, как работает программа. Мы будем использовать пример `hello.rs` из директории `examples`. -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`. - -To run this binary on QEMU run the following command: +Запустите QEMU в одном терминале: ```console -qemu-system-arm \ - -cpu cortex-m3 \ - -machine lm3s6965evb \ - -nographic \ - -semihosting-config enable=on,target=native \ - -kernel target/thumbv7m-none-eabi/debug/examples/hello -``` - -```text -Hello, world! +cargo run --example hello ``` -The command should successfully exit (exit code = 0) after printing the text. On -*nix you can check that with the following command: +В другом терминале запустите GDB: ```console -echo $? +arm-none-eabi-gdb target/thumbv7m-none-eabi/debug/examples/hello ``` -```text -0 -``` - -Let's break down that QEMU command: - -- `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. - -- `-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, an - 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: +В GDB подключитесь к QEMU: ```console -head -n3 .cargo/config.toml +(gdb) target remote :1234 ``` -```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: +Теперь вы можете установить точку останова на функции `Reset`, которая является точкой входа программы: ```console -cargo run --example hello --release -``` - -```text - Compiling app v0.1.0 (file:///tmp/app) - Finished release [optimized + debuginfo] target(s) in 0.26s - Running `qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel target/thumbv7m-none-eabi/release/examples/hello` -Hello, world! +(gdb) break Reset +Breakpoint 1 at 0x8000942: file src/lib.rs, line 473. ``` -## 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). - -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. - -In this section we'll use the `hello` example we already compiled. - -The first debugging step is to launch QEMU in debugging mode: - -```console -qemu-system-arm \ - -cpu cortex-m3 \ - -machine lm3s6965evb \ - -nographic \ - -semihosting-config enable=on,target=native \ - -gdb tcp::3333 \ - -S \ - -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: +Запустите программу до точки останова: ```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. - -```console -target remote :3333 -``` +(gdb) continue +Continuing. -```text -Remote debugging using :3333 -Reset () at $REGISTRY/cortex-m-rt-0.6.1/src/lib.rs:473 -473 pub unsafe extern "C" fn Reset() -> ! { +Breakpoint 1, app::__cortex_m_rt_reset () at src/lib.rs:473 +473 unsafe extern "C" fn Reset() -> ! { ``` +> **ПРИМЕЧАНИЕ**: Если при установке точки останова на `Reset`, как показано выше, GDB выдает предупреждения вроде: +> +> `core::num::bignum::Big32x40::mul_small () at src/libcore/num/bignum.rs:254` +> ` src/libcore/num/bignum.rs: No such file or directory.` +> +> Это известная ошибка. Вы можете спокойно игнорировать эти предупреждения, скорее всего, вы находитесь в `Reset()`. -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 : -> ->`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(). - - -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. +Этот обработчик сброса в конечном итоге вызовет нашу основную функцию. Давайте пропустим все до нее, используя точку останова и команду `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 _; @@ -521,14 +123,16 @@ This will show the source code, from the file examples/hello.rs. 12 fn main() -> ! { 13 hprintln!("Hello, world!").unwrap(); 14 -15 // exit QEMU +15 // Выход из 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: + +Теперь мы можем указать GDB запустить программу до нашей основной функции с помощью команды `continue`: ```console continue @@ -541,10 +145,9 @@ 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 +```console next ``` @@ -552,15 +155,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 @@ -570,8 +172,8 @@ next [Inferior 1 (Remote target) exited normally] ``` -You can now exit the GDB session. +Теперь вы можете выйти из сессии GDB: -``` console +```console quit ``` diff --git a/src/start/registers.md b/src/start/registers.md index 2d4a8e85..27bc8509 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,148 +1,29 @@ -# 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 и перемещая данные в оперативной памяти. Если мы хотим получать информацию в систему или из нее (будь то мигание светодиода, обнаружение нажатия кнопки или взаимодействие с внешним периферийным устройством по какой-либо шине), нам придется погрузиться в мир периферийных устройств и их "отображенных в память регистров". + +Вы можете обнаружить, что код, необходимый для доступа к периферийным устройствам вашего микроконтроллера, уже написан на одном из следующих уровней: -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:

- +

-* 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. +* Крейт микроархитектуры — этот тип крейта предоставляет полезные процедуры, общие для ядра процессора, используемого вашим микроконтроллером, а также любые периферийные устройства, общие для всех микроконтроллеров, использующих этот тип ядра процессора. Например, крейт [cortex-m] предоставляет функции для включения и отключения прерываний, которые одинаковы для всех микроконтроллеров на базе Cortex-M. Он также предоставляет доступ к периферийному устройству 'SysTick', включенному во все микроконтроллеры на базе Cortex-M. +* Крейт доступа к периферийным устройствам (PAC) — этот тип крейта представляет собой тонкую обертку над различными отображенными в память регистрами, определенными для конкретного номера детали вашего микроконтроллера. Например, [tm4c123x] для серии Texas Instruments Tiva-C TM4C123 или [stm32f30x] для серии ST-Micro STM32F30x. Здесь вы будете взаимодействовать с регистрами напрямую, следуя инструкциям по эксплуатации каждого периферийного устройства, приведенным в техническом справочном руководстве вашего микроконтроллера. +* Крейт HAL — эти крейты предлагают более удобный API для вашего конкретного процессора, часто реализуя общие трейты, определенные в [embedded-hal]. Например, этот крейт может предлагать структуру `Serial` с конструктором, который принимает подходящий набор пинов GPIO и скорость передачи данных, и предоставляет функцию `write_byte` для отправки данных. [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 -[stm32f3-discovery]: https://crates.io/crates/stm32f3-discovery -[Discovery]: https://rust-embedded.github.io/discovery/ - -## 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 overwhelming when starting studying this subject, and makes standard tasks easy, like turning a LED on or off. The functionality it exposes varies a lot between boards. Since this book aims at staying hardware agnostic, the board crates won't be covered by this book. - -If you want to experiment with the STM32F3DISCOVERY board, it is highly recommended 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. - -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. - -## 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: - -```rust,ignore -#![no_std] -#![no_main] -use cortex_m::peripheral::{syst, Peripherals}; -use cortex_m_rt::entry; -use panic_halt as _; - -#[entry] -fn main() -> ! { - let peripherals = Peripherals::take().unwrap(); - let mut systick = peripherals.SYST; - systick.set_clock_source(syst::SystClkSource::Core); - systick.set_reload(1_000); - systick.clear_current(); - systick.enable_counter(); - while !systick.has_wrapped() { - // Loop - } - - 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. - -[Peripherals]: ../peripherals/index.md - -## Using a Peripheral Access 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. - -```rust,ignore -#![no_std] -#![no_main] - -use panic_halt as _; // panic handler - -use cortex_m_rt::entry; -use tm4c123x; - -#[entry] -pub fn init() -> (Delay, Leds) { - let cp = cortex_m::Peripherals::take().unwrap(); - let p = tm4c123x::Peripherals::take().unwrap(); - - let pwm = p.PWM0; - pwm.ctl.write(|w| w.globalsync0().clear_bit()); - // Mode = 1 => Count up/down mode - pwm._2_ctl.write(|w| w.enable().set_bit().mode().set_bit()); - pwm._2_gena.write(|w| w.actcmpau().zero().actcmpad().one()); - // 528 cycles (264 up and down) = 4 loops per video line (2112 cycles) - pwm._2_load.write(|w| unsafe { w.load().bits(263) }); - pwm._2_cmpa.write(|w| unsafe { w.compa().bits(64) }); - pwm.enable.write(|w| w.pwm4en().set_bit()); -} - -``` - -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. - -### 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]. - -```rust,ignore -if pwm.ctl.read().globalsync0().is_set() { - // Do a thing -} -``` - -### Writing - -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. - -```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: - -```C -uint32_t temp = pwm0.ctl.read(); -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! -``` - -[svd2rust]: https://crates.io/crates/svd2rust -[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 - -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 misconvert 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] #![no_main] -use panic_halt as _; // panic handler +use panic_halt as _; // Обработчик паники use cortex_m_rt::entry; use tm4c123x_hal as hal; @@ -155,42 +36,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 + // Нам нужны частоты часов для расчета делителей скорости передачи &clocks, - // We need this to power up the UART peripheral + // Это необходимо для включения питания периферийного устройства UART &sc.power_control, ); diff --git a/src/start/semihosting.md b/src/start/semihosting.md index cf4626f4..d6240c81 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. +Семихостинг — это механизм, который позволяет встраиваемым устройствам выполнять ввод/вывод на хосте и в основном используется для записи сообщений в консоль хоста. Семихостинг требует отладочной сессии и почти ничего больше (никаких дополнительных проводов!), поэтому его очень удобно использовать. Недостаток в том, что он очень медленный: каждая операция записи может занимать несколько миллисекунд в зависимости от используемого аппаратного отладчика (например, 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`] предоставляет API для выполнения операций семихостинга на устройствах Cortex-M. Программа ниже — это версия "Hello, world!" с использованием семихостинга: [`cortex-m-semihosting`]: https://crates.io/crates/cortex-m-semihosting @@ -29,39 +23,32 @@ fn main() -> ! { } ``` -If you run this program on hardware you'll see the "Hello, world!" message -within the OpenOCD logs. +Если вы запустите эту программу на оборудовании, вы увидите сообщение "Hello, world!" в логах OpenOCD. -``` text +```text $ openocd (..) Hello, world! (..) ``` -You do need to enable semihosting in OpenOCD from GDB first: -``` console +Вам нужно сначала включить семихостинг в OpenOCD из GDB: + +```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 понимает операции семихостинга, поэтому приведенная выше программа также будет работать с `qemu-system-arm` без необходимости запуска отладочной сессии. Обратите внимание, что вам нужно передать флаг `-semihosting-config` в QEMU для включения поддержки семихостинга; эти флаги уже включены в файл `.cargo/config.toml` шаблона. -``` text -$ # this program will block the terminal +```text +$ # Эта программа заблокирует терминал $ cargo run Running `qemu-system-arm (..) 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] @@ -86,7 +73,7 @@ fn main() -> ! { } ``` -``` text +```text $ cargo run Running `qemu-system-arm (..) @@ -94,12 +81,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. +Один последний совет: вы можете настроить поведение паники на `exit(EXIT_FAILURE)`. Это позволит вам писать тесты `no_std` с проверкой прохождения, которые можно запускать на QEMU. -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` имеет функцию "exit", которая при включении вызывает `exit(EXIT_FAILURE)` после записи сообщения о панике в stderr хоста. ```rust,ignore #![no_main] @@ -120,7 +104,7 @@ fn main() -> ! { } ``` -``` text +```text $ cargo run Running `qemu-system-arm (..) panicked at 'assertion failed: `(left == right)` @@ -131,15 +115,12 @@ $ echo $? 1 ``` -**NOTE**: 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 +```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. -[`specifying dependencies`]: -https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html +[`указание зависимостей`]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html diff --git a/src/static-guarantees/design-contracts.md b/src/static-guarantees/design-contracts.md index 56fec45b..1add7fdf 100644 --- a/src/static-guarantees/design-contracts.md +++ b/src/static-guarantees/design-contracts.md @@ -1,27 +1,27 @@ -# 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: - -| Name | Bit Number(s) | Value | Meaning | Notes | -| ---: | ------------: | ----: | ------: | ----: | -| 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: +# Контракты проектирования + +В предыдущей главе мы создали интерфейс, который *не* обеспечивал соблюдение контрактов проектирования. Давайте еще раз посмотрим на наш воображаемый регистр конфигурации GPIO: + +| Имя | Бит(ы) | Значение | Значение | Примечания | +|--------------|--------------|---------|----------------|-----------| +| enable | 0 | 0 | отключено | Отключает GPIO | +| | | 1 | включено | Включает GPIO | +| direction | 1 | 0 | вход | Устанавливает направление на вход | +| | | 1 | выход | Устанавливает направление на выход | +| input_mode | 2..3 | 00 | высокое сопротивление | Устанавливает вход как высокое сопротивление | +| | | 01 | подтяжка вниз | Входной пин подтянут вниз | +| | | 10 | подтяжка вверх | Входной пин подтянут вверх | +| | | 11 | н/д | Недопустимое состояние. Не устанавливать | +| output_mode | 4 | 0 | установить низкий | Выходной пин притянут к низкому уровню | +| | | 1 | установить высокий | Выходной пин притянут к высокому уровню | +| input_status | 5 | x | входное значение | 0, если вход < 1.5 В, 1, если вход >= 1.5 В | + +Если вместо этого мы проверяли бы состояние перед использованием базового оборудования, обеспечивая соблюдение наших контрактов проектирования во время выполнения, мы могли бы написать код, который выглядит следующим образом: ```rust,ignore -/// GPIO interface +/// Интерфейс GPIO struct GpioConfig { - /// GPIO Configuration structure generated by svd2rust + /// Структура конфигурации GPIO, сгенерированная svd2rust periph: GPIO_CONFIG, } @@ -34,7 +34,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 +47,11 @@ 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(()); } @@ -63,84 +62,59 @@ impl GpioConfig { Ok(()) } - pub fn set_output_status(&mut self, is_high: bool) -> Result<(), ()> { + pub fn set_output_mode(&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(()); } self.periph.modify(|_r, w| { - w.output_mode.set_bit(is_high) + w.output_mode().set_bit(is_high) }); Ok(()) } - 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(()); - } - - Ok(self.periph.read().input_status().bit_is_set()) + pub fn get_input_status(&self) -> bool { + self.periph.read().input_status().bit_is_set() } } ``` -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 - -But what if instead, we used Rust's type system to enforce the state transition rules? Take this example: +Теперь давайте используем типовые состояния для кодирования этих состояний в типах, чтобы обеспечить соблюдение контрактов проектирования на этапе компиляции: ```rust,ignore -/// GPIO interface -struct GpioConfig { - /// GPIO Configuration structure generated by svd2rust - periph: GPIO_CONFIG, - enabled: ENABLED, - direction: DIRECTION, - mode: MODE, -} - -// Type states for MODE in GpioConfig -struct Disabled; struct Enabled; -struct Output; +struct Disabled; struct Input; +struct Output; +struct HighZ; struct PulledLow; struct PulledHigh; -struct HighZ; -struct DontCare; -/// These functions may be used on any GPIO Pin -impl GpioConfig { - pub fn into_disabled(self) -> GpioConfig { - self.periph.modify(|_r, w| w.enable.disabled()); +struct GpioConfig { + periph: GPIO_CONFIG, + enabled: E, + direction: D, + mode: M, +} + +impl GpioConfig { + pub fn new(periph: GPIO_CONFIG) -> Self { GpioConfig { - periph: self.periph, + periph, enabled: Disabled, - direction: DontCare, - mode: DontCare, + direction: Input, + mode: HighZ, } } pub fn into_enabled_input(self) -> GpioConfig { - self.periph.modify(|_r, w| { - w.enable.enabled() - .direction.input() - .input_mode.high_z() - }); + self.periph.modify(|_r, w| w.enable().set_bit(true)); GpioConfig { periph: self.periph, enabled: Enabled, @@ -148,52 +122,56 @@ impl GpioConfig { mode: HighZ, } } +} - pub fn into_enabled_output(self) -> GpioConfig { - self.periph.modify(|_r, w| { - w.enable.enabled() - .direction.output() - .input_mode.set_high() - }); +impl GpioConfig { + pub fn bit_is_set(&self) -> bool { + self.periph.read().input_status().bit_is_set() + } + + pub fn into_enabled_output(self) -> GpioConfig { + self.periph.modify(|_r, w| w.direction().set_bit(true)); GpioConfig { periph: self.periph, enabled: Enabled, direction: Output, - mode: DontCare, + mode: PulledHigh, } } -} - -/// 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 -impl GpioConfig { - pub fn bit_is_set(&self) -> bool { - self.periph.read().input_status.bit_is_set() + pub fn into_input_pull_down(self) -> GpioConfig { + self.periph.modify(|_r, w| w.input_mode().pull_low()); + GpioConfig { + periph: self.periph, + enabled: Enabled, + direction: Input, + mode: PulledLow, + } } - pub fn into_input_high_z(self) -> GpioConfig { - self.periph.modify(|_r, w| w.input_mode().high_z()); + pub fn into_input_pull_up(self) -> GpioConfig { + self.periph.modify(|_r, w| w.input_mode().pull_high()); GpioConfig { periph: self.periph, enabled: Enabled, direction: Input, - mode: HighZ, + mode: PulledHigh, } } +} - pub fn into_input_pull_down(self) -> GpioConfig { - self.periph.modify(|_r, w| w.input_mode().pull_low()); +impl GpioConfig { + pub fn bit_is_set(&self) -> bool { + self.periph.read().input_status().bit_is_set() + } + + pub fn into_enabled_output(self) -> GpioConfig { + self.periph.modify(|_r, w| w.direction().set_bit(true)); GpioConfig { periph: self.periph, enabled: Enabled, - direction: Input, - mode: PulledLow, + direction: Output, + mode: PulledHigh, } } @@ -209,46 +187,46 @@ impl GpioConfig { } ``` -Now let's see what the code using this would look like: +Теперь давайте посмотрим, как будет выглядеть код, использующий это: ```rust,ignore /* - * Example 1: Unconfigured to High-Z input + * Пример 1: Из неконфигурированного в вход с высоким сопротивлением */ let pin: GpioConfig = get_gpio(); -// Can't do this, pin isn't enabled! +// Нельзя сделать это, пин не включен! // pin.into_input_pull_down(); -// Now turn the pin from 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: Из входа с высоким сопротивлением во вход с подтяжкой вниз */ 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` внутри структуры `GpioConfig`? -## Compile Time 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! +Кроме того, поскольку эти состояния обеспечиваются системой типов, для пользователей этого интерфейса больше нет места для ошибок. Если они попытаются выполнить недопустимый переход состояния, код просто не скомпилируется! diff --git a/src/static-guarantees/index.md b/src/static-guarantees/index.md index a5bb8fa3..8483b65a 100644 --- a/src/static-guarantees/index.md +++ b/src/static-guarantees/index.md @@ -1,23 +1,12 @@ -# 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`]). Система типов также может использоваться для проверки других свойств на этапе компиляции, уменьшая необходимость проверок во время выполнения в некоторых случаях. [`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. +При применении к встраиваемым программам эти *статические проверки* могут использоваться, например, для обеспечения правильной конфигурации интерфейсов ввода-вывода. Например, можно разработать 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. +Также можно статически проверять, что такие операции, как установка пина в низкий уровень, могут выполняться только на правильно сконфигурированных периферийных устройствах. Например, попытка изменить состояние выхода пина, настроенного в режиме плавающего входа, вызовет ошибку компиляции. -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..8b35e402 100644 --- a/src/static-guarantees/state-machines.md +++ b/src/static-guarantees/state-machines.md @@ -1,62 +1,62 @@ -# 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: +Периферийные устройства микроконтроллера можно рассматривать как набор конечных автоматов. Например, конфигурация упрощенного [GPIO-пина] может быть представлена следующим деревом состояний: -[GPIO pin]: https://en.wikipedia.org/wiki/General-purpose_input/output +[GPIO-пин]: 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. Disabled -2. Enabled -3. Configured as Input -4. Input: High Resistance +1. Отключен +2. Включен +3. Настроен как вход +4. Вход: Высокое сопротивление -If we wanted to move from `Input: High Resistance` to `Input: Pulled Low`, we must perform the following steps: +Если мы хотим перейти из `Вход: Высокое сопротивление` в `Вход: Подтяжка вниз`, необходимо выполнить следующие шаги: -1. Input: High Resistance -2. Input: Pulled Low +1. Вход: Высокое сопротивление +2. Вход: Подтяжка вниз -Similarly, if we want to move a GPIO pin from configured as `Input: Pulled Low` to `Output: High`, we must perform the following steps: +Аналогично, если мы хотим перевести GPIO-пин из режима `Вход: Подтяжка вниз` в `Выход: Высокий`, необходимо выполнить следующие шаги: -1. Input: Pulled Low -2. Configured as Input -3. Configured as Output -4. Output: High +1. Вход: Подтяжка вниз +2. Настроен как вход +3. Настроен как выход +4. Выход: Высокий -## 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: +Обычно перечисленные выше состояния устанавливаются путем записи значений в заданные регистры, отображенные на периферийное устройство GPIO. Давайте определим воображаемый регистр конфигурации GPIO для иллюстрации: -| Name | Bit Number(s) | Value | Meaning | Notes | -| ---: | ------------: | ----: | ------: | ----: | -| 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 | +| Имя | Бит(ы) | Значение | Значение | Примечания | +|--------------|--------------|---------|----------------|-----------| +| enable | 0 | 0 | отключено | Отключает GPIO | +| | | 1 | включено | Включает GPIO | +| direction | 1 | 0 | вход | Устанавливает направление на вход | +| | | 1 | выход | Устанавливает направление на выход | +| input_mode | 2..3 | 00 | высокое сопротивление | Устанавливает вход как высокое сопротивление | +| | | 01 | подтяжка вниз | Входной пин подтянут вниз | +| | | 10 | подтяжка вверх | Входной пин подтянут вверх | +| | | 11 | н/д | Недопустимое состояние. Не устанавливать | +| output_mode | 4 | 0 | установить низкий | Выходной пин притянут к низкому уровню | +| | | 1 | установить высокий | Выходной пин притянут к высокому уровню | +| input_status | 5 | x | входное значение | 0, если вход < 1.5 В, 1, если вход >= 1.5 В | -We _could_ expose the following structure in Rust to control this GPIO: +Мы *могли бы* предоставить следующую структуру в Rust для управления этим GPIO: ```rust,ignore -/// GPIO interface +/// Интерфейс GPIO struct GpioConfig { - /// GPIO Configuration structure generated by svd2rust + /// Структура конфигурации GPIO, сгенерированная svd2rust periph: GPIO_CONFIG, } @@ -91,8 +91,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? +Однако это позволило бы нам изменять определенные регистры, что не имеет смысла. Например, что произойдет, если мы установим поле `output_mode`, когда наш GPIO настроен как вход? -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! +В общем, использование этой структуры позволило бы нам достичь состояний, не определенных в нашем конечном автомате выше: например, выход, который подтянут вниз, или вход, который установлен на высокий уровень. Для некоторого оборудования это может не иметь значения. На другом оборудовании это может вызвать неожиданное или неопределенное поведение! -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/typestate-programming.md b/src/static-guarantees/typestate-programming.md index 9bd70f40..989d1210 100644 --- a/src/static-guarantees/typestate-programming.md +++ b/src/static-guarantees/typestate-programming.md @@ -1,9 +1,9 @@ -# 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! +Концепция [типовых состояний] описывает кодирование информации о текущем состоянии объекта в тип этого объекта. Хотя это может звучать немного загадочно, если вы использовали [шаблон Builder] в Rust, вы уже начали использовать программирование с типовыми состояниями! -[typestates]: https://en.wikipedia.org/wiki/Typestate_analysis -[Builder Pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html +[типовые состояния]: https://en.wikipedia.org/wiki/Typestate_analysis +[шаблон Builder]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html ```rust pub mod foo_module { @@ -49,17 +49,17 @@ 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` или превратить `FooBuilder` в `Foo` без вызова метода `into_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 -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. +Это позволяет нам представлять состояния нашей системы как типы и включать необходимые действия для переходов между состояниями в методы, которые обменивают один тип на другой. Создавая `FooBuilder` и обменивая его на объект `Foo`, мы проходим через шаги простого конечного автомата. diff --git a/src/static-guarantees/zero-cost-abstractions.md b/src/static-guarantees/zero-cost-abstractions.md index f7defd18..a6d5c956 100644 --- a/src/static-guarantees/zero-cost-abstractions.md +++ b/src/static-guarantees/zero-cost-abstractions.md @@ -1,6 +1,6 @@ -# 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: +Типовые состояния также являются отличным примером абстракций с нулевой стоимостью — способности переносить определенные поведения на этап компиляции или анализа. Эти типовые состояния не содержат фактических данных и вместо этого используются как маркеры. Поскольку они не содержат данных, у них нет фактического представления в памяти во время выполнения: ```rust,ignore use core::mem::size_of; @@ -11,15 +11,15 @@ let _ = size_of::(); // == 0 let _ = size_of::>(); // == 0 ``` -## 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. +Структуры, определенные таким образом, называются типами нулевого размера, поскольку они не содержат фактических данных. Хотя эти типы ведут себя как "настоящие" на этапе компиляции — их можно копировать, перемещать, брать ссылки на них и т.д., оптимизатор полностью удаляет их. -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. +Возвращаемая структура `GpioConfig` никогда не существует во время выполнения. Вызов этой функции обычно сводится к одной инструкции на ассемблере — записи константного значения регистра в адрес регистра. Это означает, что разработанный нами интерфейс типовых состояний является абстракцией с нулевой стоимостью — он не использует больше процессорного времени, оперативной памяти или пространства для кода для отслеживания состояния `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. +Для сложных или глубоко вложенных структур определение всех возможных комбинаций состояний может быть утомительным. В таких случаях можно использовать макросы для генерации всех реализаций. diff --git a/src/unsorted/index.md b/src/unsorted/index.md index 4be66549..571b9c41 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 4367b867..41df5414 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; @@ -16,19 +14,17 @@ fn main() { let sinus_of_four = floored_float.sin(); let exponential_of_four = floored_float.exp(); - println!("Floored test float {} to {}", float, floored_float); - println!("The square root of {} is {}", floored_float, sqrt_of_four); - println!("The sinus of four is {}", sinus_of_four); + println!("Округлено вниз тестовое число {} до {}", float, floored_float); + println!("Квадратный корень из {} равен {}", floored_float, sqrt_of_four); + println!("Синус числа четыре равен {}", sinus_of_four); println!( - "The exponential of four to the base e is {}", + "Экспонента числа четыре с основанием e равна {}", exponential_of_four ) } ``` -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] @@ -50,26 +46,25 @@ fn main() -> ! { let sinus_of_four = sin(floored_float.into()); let exponential_of_four = exp(floored_float.into()); - hprintln!("Floored test float {} to {}", float, floored_float).unwrap(); - hprintln!("The square root of {} is {}", floored_float, sqrt_of_four).unwrap(); - hprintln!("The sinus of four is {}", sinus_of_four).unwrap(); + hprintln!("Округлено вниз тестовое число {} до {}", float, floored_float).unwrap(); + hprintln!("Квадратный корень из {} равен {}", floored_float, sqrt_of_four).unwrap(); + hprintln!("Синус числа четыре равен {}", sinus_of_four).unwrap(); hprintln!( - "The exponential of four to the base e is {}", + "Экспонента числа четыре с основанием e равна {}", 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 +Если вам нужно выполнять более сложные операции, такие как обработка сигналов DSP или продвинутая линейная алгебра на вашем микроконтроллере, следующие крейты могут быть полезны: -- [CMSIS DSP library binding](https://github.com/jacobrosenthal/cmsis-dsp-sys) +- [Привязка к библиотеке CMSIS DSP](https://github.com/jacobrosenthal/cmsis-dsp-sys) - [`constgebra`](https://crates.io/crates/constgebra) - [`micromath`](https://github.com/tarcieri/micromath) - [`microfft`](https://crates.io/crates/microfft) diff --git a/src/unsorted/speed-vs-size.md b/src/unsorted/speed-vs-size.md index 4721c068..8cbb6538 100644 --- a/src/unsorted/speed-vs-size.md +++ b/src/unsorted/speed-vs-size.md @@ -1,164 +1,70 @@ -# 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. +Каждый хочет, чтобы его программа была очень быстрой и очень компактной, но обычно невозможно достичь обеих характеристик одновременно. В этом разделе обсуждаются различные уровни оптимизации, предоставляемые компилятором `rustc`, и их влияние на время выполнения и размер бинарного файла программы. -## No optimizations +## Без оптимизаций -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`. +Это настройка по умолчанию. Когда вы вызываете `cargo build`, используется профиль разработки (также известный как `dev`). Этот профиль оптимизирован для отладки, поэтому он включает отладочную информацию и *не* включает никаких оптимизаций, т.е. используется `-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. +По крайней мере, для разработки без операционной системы отладочная информация не занимает места во флэш-памяти / ПЗУ, поэтому мы рекомендуем включать отладочную информацию в профиле выпуска — по умолчанию она отключена. Это позволит использовать точки останова при отладке сборок выпуска. -``` toml +```toml [profile.release] -# symbols are nice and they don't increase the size on 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 -to print variables results in `$0 = ` being printed. +Отсутствие оптимизаций отлично подходит для отладки, поскольку пошаговое выполнение кода ощущается как выполнение программы оператор за оператором, плюс вы можете выводить значения локальных переменных и аргументов функций в GDB. При оптимизированном коде попытка вывести переменные приводит к сообщению `$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` заключается в том, что получаемый бинарный файл будет огромным и медленным. Размер обычно представляет большую проблему, поскольку неоптимизированные бинарные файлы могут занимать десятки килобайт флэш-памяти, которой может не быть на вашем целевом устройстве — в результате неоптимизированный бинарный файл просто не помещается в ваше устройство! -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. - -Beware that generic code can sometimes be optimized alongside the crate where it -is instantiated, rather than the crate where it is defined. If you create an -instance of a generic struct in your application and find that it pulls in code -with a large footprint, it may be that increasing the optimisation level of the -relevant dependencies has no effect. +Есть функция Cargo под названием [`profile-overrides`], которая позволяет переопределять уровень оптимизации для зависимостей. Вы можете использовать эту функцию, чтобы оптимизировать все зависимости для размера, сохраняя верхний крейт неоптимизированным и удобным для отладки. [`profile-overrides`]: https://doc.rust-lang.org/cargo/reference/profiles.html#overrides -Here's an example: - -``` toml -# Cargo.toml -[package] -name = "app" -# .. +Учтите, что обобщенный код может быть проблематичным при использовании различных уровней оптимизации, поэтому вам может потребоваться экспериментировать с настройками. -[profile.dev.package."*"] # + -opt-level = "z" # + -``` +### Оптимизация для скорости -Without the override: - -``` text -$ cargo size --bin app -- -A -app : -section size addr -.vector_table 1024 0x8000000 -.text 9060 0x8000400 -.rodata 1708 0x8002780 -.data 0 0x20000000 -.bss 4 0x20000000 -``` +Если вы хотите, чтобы ваши бинарные файлы выпуска были оптимизированы для скорости, измените настройку `profile.release.opt-level` в `Cargo.toml`, как показано ниже: -With the override: - -``` text -$ cargo size --bin app -- -A -app : -section size addr -.vector_table 1024 0x8000000 -.text 3490 0x8000400 -.rodata 1100 0x80011c0 -.data 0 0x20000000 -.bss 4 0x20000000 +```toml +[profile.release] +opt-level = 3 ``` -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 -`` 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: - -``` toml -# .. +или -# don't optimize the `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" +```toml +[profile.release] +opt-level = 2 ``` -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 -profile which defaults to `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). - -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`) значительно увеличивают производительность, но также могут увеличивать размер бинарного файла. Если вы не можете позволить себе увеличение размера, вам следует оптимизировать программу для размера. -## 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"`. +По состоянию на 18.09.2018 `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. +Если вы хотите, чтобы ваши бинарные файлы выпуска были оптимизированы для размера, измените настройку `profile.release.opt-level` в `Cargo.toml`, как показано ниже: -``` toml +```toml [profile.release] -# or "z" +# или "z" 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 являются абстракции с нулевой стоимостью; эти абстракции часто используют множество новых типов и небольших функций для сохранения инвариантов (например, функции, которые заимствуют внутреннее значение, такие как `deref`, `as_ref`), поэтому низкий порог встраивания может привести к тому, что LLVM упустит возможности оптимизации (например, устранение мертвых ветвей, встраивание вызовов замыканий). -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`. +При оптимизации для размера вы можете попробовать увеличить порог встраивания, чтобы проверить, влияет ли это на размер бинарного файла. Рекомендуемый способ изменения порога встраивания — добавить флаг `-C inline-threshold` к другим флагам rustflags в `.cargo/config.toml`: -``` 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 = [ # .. @@ -166,14 +72,13 @@ 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 -- `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`.