mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-25 03:19:34 +01:00
Book: Remove RTIC v1 Russian translation
This commit is contained in:
parent
032316855d
commit
0fc86d972c
26 changed files with 1 additions and 3500 deletions
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
|
@ -380,21 +380,14 @@ jobs:
|
|||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
run: cd book/en && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi
|
||||
|
||||
- name: Build book in Russian
|
||||
shell: 'script --return --quiet --command "bash {0}"'
|
||||
run: cd book/ru && if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then echo "Russian book needs updating!"; else exit 0; fi
|
||||
|
||||
- name: Check links
|
||||
run: |
|
||||
td=$(mktemp -d)
|
||||
mkdir $td/book
|
||||
cp -r book/en/book $td/book/en
|
||||
cp -r book/ru/book $td/book/ru
|
||||
cp LICENSE-* $td/book/en
|
||||
cp LICENSE-* $td/book/ru
|
||||
|
||||
lychee --offline $td/book/en/
|
||||
lychee --offline $td/book/ru/
|
||||
|
||||
# # Update stable branch
|
||||
# #
|
||||
|
@ -485,7 +478,7 @@ jobs:
|
|||
# - name: Build books
|
||||
# shell: 'script --return --quiet --command "bash {0}"'
|
||||
# run: |
|
||||
# langs=( en ru )
|
||||
# langs=( en )
|
||||
# devver=( dev )
|
||||
# # The latest stable must be the first element in the array
|
||||
# vers=( "1" "0.5" "0.4" )
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
[book]
|
||||
authors = ["Jorge Aparicio, Per Lindgren and The Real-Time Interrupt-driven Concurrency developers"]
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Real-Time Interrupt-driven Concurrency"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/rtic-rs/cortex-m-rtic"
|
||||
git-repository-icon = "fa-github"
|
|
@ -1,109 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
|
||||
inkscape:export-ydpi="145.74001"
|
||||
inkscape:export-xdpi="145.74001"
|
||||
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248_2.png"
|
||||
sodipodi:docname="RTIC.svg"
|
||||
viewBox="0 0 375.55994 408.84339"
|
||||
height="408.84338"
|
||||
width="375.55994"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
version="1.1"><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6"><clipPath
|
||||
id="clipPath18"
|
||||
clipPathUnits="userSpaceOnUse"><path
|
||||
id="path16"
|
||||
d="M 0,500 H 500 V 0 H 0 Z" /></clipPath></defs><sodipodi:namedview
|
||||
inkscape:current-layer="g10"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-x="0"
|
||||
inkscape:cy="229.27385"
|
||||
inkscape:cx="150.39187"
|
||||
inkscape:zoom="1.5119999"
|
||||
fit-margin-bottom="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-top="0"
|
||||
inkscape:pagecheckerboard="false"
|
||||
showgrid="false"
|
||||
id="namedview4"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:document-rotation="0" /><g
|
||||
transform="matrix(1.3333333,0,0,-1.3333333,-148.85309,622.34951)"
|
||||
inkscape:label="45453_RTIC logo_JK"
|
||||
inkscape:groupmode="layer"
|
||||
id="g10"><g
|
||||
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
|
||||
inkscape:export-ydpi="153.37898"
|
||||
inkscape:export-xdpi="153.37898"
|
||||
style="opacity:1;fill:#4d4d4d;fill-opacity:1"
|
||||
transform="matrix(7.464224,0,0,7.464224,393.30978,300.96457)"
|
||||
id="g248"><path
|
||||
id="path250"
|
||||
style="fill:#4d4d4d;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c 0,-10.421 -8.448,-18.868 -18.868,-18.868 -10.421,0 -18.868,8.447 -18.868,18.868 0,10.421 8.447,18.868 18.868,18.868 C -8.448,18.868 0,10.421 0,0" /></g><g
|
||||
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
|
||||
inkscape:export-ydpi="153.37898"
|
||||
inkscape:export-xdpi="153.37898"
|
||||
transform="matrix(7.464224,0,0,7.464224,292.89574,388.12804)"
|
||||
id="g252"><path
|
||||
sodipodi:nodetypes="cccccccccc"
|
||||
id="path254"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="M 0,0 C -0.604,5.477 -5.967,9.765 -6.856,10.442 -6.487,9.748 -5.71,8.123 -5.267,6.023 -4.92,4.374 -4.845,2.758 -5.043,1.221 -5.291,-0.701 -5.97,-2.505 -7.062,-4.14 c -0.294,-0.441 -0.601,-0.894 -0.926,-1.374 -3.428,-5.065 -8.25205,-11.907209 -7.04305,-17.843209 0.528,-2.592 2.166,-4.805 4.866,-6.583 -7.606,6.593 -2.20795,13.944209 1.62005,17.105209 C -5.253,-10.117 0.659,-5.974 0,0" /></g><g
|
||||
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
|
||||
inkscape:export-ydpi="153.37898"
|
||||
inkscape:export-xdpi="153.37898"
|
||||
transform="matrix(7.464224,0,0,7.464224,193.42458,186.62982)"
|
||||
id="g256"><path
|
||||
sodipodi:nodetypes="ccccccccccc"
|
||||
id="path258"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c -0.777,1.074 -1.303,2.263 -1.562,3.535 -1.212,5.951 3.488,12.895 6.92,17.966 0.325,0.48 0.632,0.933 0.926,1.374 2.464,3.693 2.333,7.549 1.789,10.135 -0.456,2.168 -1.27,3.828 -1.621,4.477 -0.038,0.028 -0.058,0.043 -0.058,0.043 0,0 -6.038,-7.951 -8.738,-12.258 C -5.045,20.964 -8.509,12.81 -5.274,5.863 -2.263,-0.605 2.4913395,-2.6700085 3.1613395,-2.9450085 1.7523395,-2.0240085 0.824,-1.138 0,0" /></g><g
|
||||
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
|
||||
inkscape:export-ydpi="153.37898"
|
||||
inkscape:export-xdpi="153.37898"
|
||||
transform="matrix(7.464224,0,0,7.464224,286.22601,210.85049)"
|
||||
id="g260"><path
|
||||
sodipodi:nodetypes="cccccccssc"
|
||||
id="path262"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c -0.199,-4.847 -3.7433301,-6.7788234 -3.7433301,-6.7788234 0,0 0.2005158,0.00584 0.4557728,0.023109 C -0.01255733,-5.7517164 4.496,-3.342 4.518,2.624 4.53,5.687 2.682,7.663 1.13,8.781 c -1.149,0.828 -2.309,1.321 -2.935,1.551 -0.396,-0.067 -2.392,-0.519 -2.51,-2.836 0,0 -3.5425677,-1.2008654 -3.56,-1.632 C -7.9046856,5.1298176 -6.9355723,4.1874599 -6.187,3.63 -5.1908601,2.8881772 0.199,4.847 0,0" /></g><g
|
||||
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
|
||||
inkscape:export-ydpi="153.37898"
|
||||
inkscape:export-xdpi="153.37898"
|
||||
transform="matrix(7.464224,0,0,7.464224,360.6426,228.88853)"
|
||||
id="g264"><path
|
||||
sodipodi:nodetypes="zcccccccccccczzz"
|
||||
id="path266"
|
||||
style="fill:#cccccc;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="M -0.34917151,1.6816738 C -0.7974951,5.9368052 -3.1264734,7.1611735 -5.072,8.56 c 0,0 -0.8516082,3.022335 -1.7015402,3.1237 0,0 0.3570815,0.04169 0,0 -0.6687287,0.05444 -1.1522423,-0.270149 -1.9532423,-1.364149 0,0 -1.1502065,1.167917 -2.4848885,1.093235 C -12.505303,11.107968 -11.817,7.957 -11.818,7.928 c 0.64,-0.24 1.768,-0.729 2.886,-1.535 0.992,-0.715 1.781,-1.534 2.346,-2.437 0.707,-1.128 1.062,-2.389 1.057,-3.748 -0.006,-1.773 -0.433,-3.369 -1.267,-4.743 -0.712,-1.172 -1.724,-2.193 -3.01,-3.036 -1.181,-0.774 -2.329326,-1.2453139 -3.451326,-1.6013139 1.173268,0.050293 3.778241,0.431572 5.8646425,1.3359556 2.0864016,0.9043837 3.5682459,1.7417342 4.4081274,2.592566 0.8398814,0.8508318 3.08370818,2.6703347 2.63538459,6.9254661 z" /></g><path
|
||||
inkscape:export-filename="/home/emifre/Documents/logo/v2_seller1/vctr/g248.png"
|
||||
sodipodi:nodetypes="ssss"
|
||||
inkscape:export-ydpi="153.37898"
|
||||
inkscape:export-xdpi="153.37898"
|
||||
id="path1340"
|
||||
d="m 227.38125,254.73726 c -0.52355,-1.50734 0.39304,-4.38366 2.33326,-6.47436 2.23581,-2.40923 7.33976,11.89073 4.18714,10.96111 -2.21547,-0.65328 -6.03712,-3.09534 -6.5204,-4.48675 z"
|
||||
style="fill:#808080;fill-opacity:1;stroke:#cccccc;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></svg>
|
Before Width: | Height: | Size: 6.8 KiB |
|
@ -1,25 +0,0 @@
|
|||
# Summary
|
||||
|
||||
[Введение](./preface.md)
|
||||
|
||||
- [RTIC в примерах](./by-example.md)
|
||||
- [Атрибут `app`](./by-example/app.md)
|
||||
- [Ресурсы](./by-example/resources.md)
|
||||
- [Программные задачи](./by-example/tasks.md)
|
||||
- [Очередь таймера](./by-example/timer-queue.md)
|
||||
- [Типы, Send и Sync](./by-example/types-send-sync.md)
|
||||
- [Создание нового проекта](./by-example/new.md)
|
||||
- [Советы и хитрости](./by-example/tips.md)
|
||||
- [Инструкции по миграции](./migration.md)
|
||||
- [v0.5.x на v1.0.x](./migration/migration_v5.md)
|
||||
- [v0.4.x на v0.5.x](./migration/migration_v4.md)
|
||||
- [RTFM на RTIC](./migration/migration_rtic.md)
|
||||
- [Под капотом](./internals.md)
|
||||
- [Настройка прерываний](./internals/interrupt-configuration.md)
|
||||
- [Нереентерабельнось](./internals/non-reentrancy.md)
|
||||
- [Контроль доступа](./internals/access.md)
|
||||
- [Поздние ресурсы](./internals/late-resources.md)
|
||||
- [Критические секции](./internals/critical-sections.md)
|
||||
- [Анализ приоритетов](./internals/ceilings.md)
|
||||
- [Программные задачи](./internals/tasks.md)
|
||||
- [Очередь таймера](./internals/timer-queue.md)
|
|
@ -1,23 +0,0 @@
|
|||
# RTIC в примерах
|
||||
|
||||
В этой части книги фреймворк Real-Time Interrupt-driven Concurrency (RTIC) представляется
|
||||
новым пользователям путем прохода по примерам от простых к более сложным.
|
||||
|
||||
Все примеры в этой части книги можно найти в [репозитарии] проекта.
|
||||
Большинство из них можно пройти, запустив их на эмуляторе QEMU без специального оборудования.
|
||||
|
||||
[репозитарии]: https://github.com/rtic-rs/cortex-m-rtic
|
||||
|
||||
Для запуска примеров на вашем ПК, вам понадобится программа `qemu-system-arm`.
|
||||
В [the embedded Rust book] есть инструкции по настройке среды для эмбеддед разработке,
|
||||
в том числе QEMU.
|
||||
|
||||
[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html
|
||||
|
||||
## Примеры из реальной жизни
|
||||
|
||||
Ниже представлены примеры использования RTIC (RTFM) в реальных проектах.
|
||||
|
||||
### RTFM V0.4.2
|
||||
|
||||
- [etrombly/sandbox](https://github.com/etrombly/sandbox/tree/41d423bcdd0d8e42fd46b79771400a8ca349af55). Аппаратный дзэн-сад, рисующий картинки на песке. Картинки передаются по последовательному порту с помощью G-кода.
|
|
@ -1,161 +0,0 @@
|
|||
# Атрибут `app`
|
||||
|
||||
Это простейшая из возможных программ на RTIC:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/smallest.rs}}
|
||||
```
|
||||
|
||||
Все программы на RTIC используют атрибут [`app`] (`#[app(..)]`). Этот атрибут
|
||||
должен применяться к элементу `mod`. Атрибут `app` имеет обязательный аргумент `device`,
|
||||
который принимает *путь* как значение. Это должен быть полный путь, указывающий на
|
||||
*крейт доступа к периферии* (PAC), сгенерированный с помощью [`svd2rust`] версии **v0.14.x**
|
||||
или новее. Более подробно в разделе [Создание нового проекта](./new.md).
|
||||
|
||||
Атрибут `app` будет раскрыт в подходящую точку входа программы, поэтому
|
||||
атрибут [`cortex_m_rt::entry`] не нужен.
|
||||
|
||||
[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
|
||||
[`svd2rust`]: https://crates.io/crates/svd2rust
|
||||
[`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html
|
||||
|
||||
## `init`
|
||||
|
||||
Внутри модуля `app` атрибут ожидает найти функцию инициализации, помеченную
|
||||
атрибутом `init`. Эта функция должна иметь сигнатуру
|
||||
`fn(init::Context) (-> init::LateResources, init::Monotonics)`.
|
||||
|
||||
Эта функция инициализации будет первой частью программы, выполняемой при запуске.
|
||||
Функция `init` будет запущена *с отключенными прерываниями* и будет иметь эксклюзивный доступ
|
||||
к Cortex-M, в котором токен `bare_metal::CriticalSection` доступен как `cs`.
|
||||
Опционально, устройство-специфичные периферия доступна через поля `core` и `device` структуры
|
||||
`init::Context`.
|
||||
|
||||
`static mut` переменные, определенные в начале `init` будут преобразованы в
|
||||
`&'static mut` ссылки, безопасные для доступа. Обратите внимание, данная возможность может
|
||||
быть удалена в следующем релизе, см. `task_local` ресурсы.
|
||||
|
||||
[`rtic::Peripherals`]: ../../api/rtic/struct.Peripherals.html
|
||||
|
||||
Пример ниже показывает типы полей `core`, `device` и `cs`, и демонстрирует
|
||||
безопасный доступ к `static mut` переменной. Поле `device` доступно только
|
||||
когда аргумент `peripherals` установлен в `true` (по умолчанию).
|
||||
В редких случаях, когда вы захотите создать приложение с минимальным потреблением ресурсов,
|
||||
можно явно установить `peripherals` в `false`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/init.rs}}
|
||||
```
|
||||
|
||||
Запуск примера напечатате `init` в консоли, а затем завершит процесс QEMU.
|
||||
|
||||
``` console
|
||||
$ cargo run --example init
|
||||
{{#include ../../../../ci/expected/init.run}}
|
||||
```
|
||||
|
||||
> **ПРИМЕЧАНИЕ**: Не забывайте указывать выбранное вами целевое устройство, передавая параметр target
|
||||
> в cargo (например `cargo run --example init --target thumbv7m-none-eabi`) или
|
||||
> настроив устройство, используемое по умолчанию для сборки примеров в `.cargo/config.toml`.
|
||||
> В нашем случае используется Cortex M3, эмулируемый с помощью QEMU, поэтому пишем `thumbv7m-none-eabi`.
|
||||
> Смотрите [`Создание нового проекта`](./new.md) для большей информации.
|
||||
|
||||
## `idle`
|
||||
|
||||
Функцию, помеченную атрибутом `idle` может опционально добавить в модуль.
|
||||
Эта функция используется как специальная *задача ожидания* и должна иметь сигнатуру
|
||||
`fn(idle::Context) - > !`.
|
||||
|
||||
Если она присутствует, задача `idle` будет запущена после `init`. В отличие от
|
||||
`init`, `idle` будет запущена *с включенными прерываниями* и она не может вернуть результат,
|
||||
а значит должна работать вечно.
|
||||
|
||||
Как и в `init`, `static mut` переменные будут трансформированы в `&'static mut` ссылки,
|
||||
безопасные для доступа. Обратите внимание, данная возможность может
|
||||
быть удалена в следующем релизе, см. `task_local` ресурсы.
|
||||
|
||||
Пример ниже показывает, что `idle` запускается после `init`.
|
||||
|
||||
**Примечание:** Цикл `loop {}` в функци ожидания не может быть пустым, так как это сломает
|
||||
микроконтроллер, из-за того, что LLVM компилирует пустые циклы в инструкцию `UDF` в release mode.
|
||||
Чтобы избежать неопределенного поведения, цикл должен включать "side-effect"
|
||||
путем вставки ассемблерной инструкции (например, `WFI`) или ключевого слова `continue`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/idle.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example idle
|
||||
{{#include ../../../../ci/expected/idle.run}}
|
||||
```
|
||||
|
||||
## Аппаратные задачи
|
||||
|
||||
Чтобы объявить обработчик прерывания, фреймворк предоставляет атрибут `#[task]`,
|
||||
который можно применять к функциям. Этот атрибут берет аргумент `binds`, чье значение -
|
||||
это имя прерывания, которому будет назначен обработчик;
|
||||
функция, декорированная этим атрибутом становится обработчиком прерывания.
|
||||
В фреймворке такие типы задач именуются *аппаратными*, потому что они начинают
|
||||
выполняться в ответ на аппаратное событие.
|
||||
|
||||
Пример ниже демонстрирует использование атрибута `#[task]`, чтобы объявить
|
||||
обработчик прерывания. Как и в случае с `#[init]` и `#[idle]` локальные `static
|
||||
mut` переменные безопасны для использования с аппаратной задачей.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/hardware.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example hardware
|
||||
{{#include ../../../../ci/expected/hardware.run}}
|
||||
```
|
||||
|
||||
До сих пор все программы на RTIC, которые мы видели, не отличались от программ,
|
||||
которые можно написать, используя лишь крейт `cortex-m-rt`. С этого момента мы
|
||||
начинаем представлять возможности, уникальные для RTIC.
|
||||
|
||||
## Приоритеты
|
||||
|
||||
Статический приоритет каждого обработчика можно оределить в атрибуте `task`, используя
|
||||
аргумент `priority`. Задачи могут иметь приоритет в диапазоне `1..=(1 << NVIC_PRIO_BITS)`,
|
||||
где `NVIC_PRIO_BITS` - это константа, определенная в крейте `устройства`.
|
||||
Когда аргумент `priority` не указан, предполагается, что приоритет равен `1`.
|
||||
Задача `idle` имеет ненастраиваемый приоритет `0`, наименьший из возможных.
|
||||
|
||||
> Более высокое значение означает более высокий приоритет в RTIC, что противоположно тому,
|
||||
> что указано в периферии NVIC Cortex-M.
|
||||
> Точнее, это значит, что число `10` обозначает приоритет **выше**, чем число `9`.
|
||||
|
||||
Когда несколько задач готовы к запуску, задача с самым большим статическим
|
||||
приоритетом будет запущена первой. Приоритезацию задач можно рассматривать по
|
||||
такому сценарию: сигнал прерывания приходит во время выполнения задачи с низким приоритетом;
|
||||
сигнал переключает задачу с высоким приоритетом в режим ожидания.
|
||||
Разница в приоритетах приводи к тому, что задача с высоким приоритетом вытесняет задачу с низким:
|
||||
выполнение задачи с низким приоритетом замораживается и задача с высоким приоритетом выполняется,
|
||||
пока не будет завершена. Как только задача с высоким приоритетом будет остановлена,
|
||||
продолжится выполнение задачи с низким приоритетом.
|
||||
|
||||
Следующий пример демонстрирует диспетчеризацию на основе приоритетов задач.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/preempt.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example preempt
|
||||
{{#include ../../../../ci/expected/preempt.run}}
|
||||
```
|
||||
|
||||
Заметьте, что задача `gpiob` *не* вытесняет задачу `gpioc`, потому что ее приоритет
|
||||
*такой же*, как и у `gpioc`. Однако, как только `gpioc` возвращает результат,
|
||||
выполненяется задача `gpiob`, как более приоритетная по сравнению с `gpioa`.
|
||||
Выполнение `gpioa` возобновляется только после выхода из `gpiob`.
|
||||
|
||||
Еще одно замечание по поводу приоритетов: выбор приоритета большего, чем поддерживает устройство
|
||||
(а именно `1 << NVIC_PRIO_BITS`) приведет к ошибке компиляции.
|
||||
Из-за ограничений языка, сообщение об ошибке далеко от понимания:
|
||||
вам скажут что-то похожее на "evaluation of constant value failed", а указатель на ошибку
|
||||
*не* покажет на проблемное значение прерывания --
|
||||
мы извиняемся за это!
|
|
@ -1,84 +0,0 @@
|
|||
# Создание нового проекта
|
||||
|
||||
Теперь, когда Вы изучили основные возможности фреймворка RTIC, Вы можете
|
||||
попробовать его использовать на Вашем оборудовании следуя этим инструкциям.
|
||||
|
||||
1. Создайте экземпляр из шаблона [`cortex-m-quickstart`].
|
||||
|
||||
[`cortex-m-quickstart`]: https://github.com/rust-embedded/cortex-m-quickstart#cortex-m-quickstart
|
||||
|
||||
``` console
|
||||
$ # например используя `cargo-generate`
|
||||
$ cargo generate \
|
||||
--git https://github.com/rust-embedded/cortex-m-quickstart \
|
||||
--name app
|
||||
|
||||
$ # следуйте остальным инструкциям
|
||||
```
|
||||
|
||||
2. Добавьте крейт доступа к периферии (PAC), сгенерированный с помощью[`svd2rust`]
|
||||
**v0.14.x**, или крейт отладочной платы, у которой в зависимостях один из таких PAC'ов.
|
||||
Убедитесь, что опция `rt` крейта включена.
|
||||
|
||||
[`svd2rust`]: https://crates.io/crates/svd2rust
|
||||
|
||||
В этом примере я буду использовать крейт устройства [`lm3s6965`].
|
||||
Эта библиотека не имеет Cargo-опции `rt`; эта опция всегда включена.
|
||||
|
||||
[`lm3s6965`]: https://crates.io/crates/lm3s6965
|
||||
|
||||
Этот крейт устройства предоставляет линковочный скрипт с макетом памяти
|
||||
целевого устройства, поэтому `memory.x` и `build.rs` нужно удалить.
|
||||
|
||||
``` console
|
||||
$ cargo add lm3s6965 --vers 0.1.3
|
||||
|
||||
$ rm memory.x build.rs
|
||||
```
|
||||
|
||||
3. Добавьте крейт `cortex-m-rtic` как зависимость.
|
||||
|
||||
``` console
|
||||
$ cargo add cortex-m-rtic --allow-prerelease
|
||||
```
|
||||
|
||||
4. Напишите свою RTIC программу.
|
||||
|
||||
Здесь я буду использовать пример `init` из крейта `cortex-m-rtic`.
|
||||
|
||||
Примеры находтся в папке `examples`, а содержание `init.rs` показано здесь:
|
||||
|
||||
``` console
|
||||
{{#include ../../../../examples/init.rs}}
|
||||
```
|
||||
|
||||
Пример `init` использует устройство `lm3s6965`. Не забудьте настроить аргумент `device`
|
||||
в атрибуте макроса app так, чтобы он соответствовал пути к PAC-крейту, если он отличается,
|
||||
а также добавить перифериб и другие аргументы если необходимо.
|
||||
Несмотря на то, что в программе могут использоваться псевдонимы типов,
|
||||
здесь необходимо указать полный путь (из корня крейта). Для многих устройств,
|
||||
есть общий подход в крейтах реализации HAL (с псевдонимом `hal`) и крейтах поддержки
|
||||
отладочных плат реекспортиорвать PAC как `pac`, что приводит нас к образцу, аналогичному
|
||||
приведенному ниже:
|
||||
|
||||
```rust
|
||||
use abcd123_hal as hal;
|
||||
//...
|
||||
|
||||
#[rtic::app(device = crate::hal::pac, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
|
||||
mod app { /*...*/ }
|
||||
```
|
||||
|
||||
Пример `init` также зависит от крейта `panic-semihosting`:
|
||||
|
||||
``` console
|
||||
$ cargo add panic-semihosting
|
||||
```
|
||||
|
||||
5. Соберите его, загрузите в микроконтроллер и запустите.
|
||||
|
||||
``` console
|
||||
$ # ПРИМЕЧАНИЕ: Я раскомментировал опцию `runner` в `.cargo/config.toml`
|
||||
$ cargo run
|
||||
{{#include ../../../../ci/expected/init.run}}
|
||||
```
|
|
@ -1,140 +0,0 @@
|
|||
# Ресурсы
|
||||
|
||||
Фреймворк предоставляет абстракцию для разделения данных между любыми контекстами,
|
||||
с которыми мы встречались в предыдущей главе (задачами-обработчиками, `init` и `idle`): ресурсы.
|
||||
|
||||
Ресурсы - это данные, видимые только функциями, определенными внутри модуля `#[app]`.
|
||||
Фреймворк дает пользователю полный контроль за тем, какой контекст может
|
||||
получить доступ к какому ресурсу.
|
||||
|
||||
Все ресурсы определены в *двух* структурах внутри модуля `#[app]`.
|
||||
Каждое поле этих структур соответствует отдельному ресурсу.
|
||||
Одна `struct`-ура должна быть аннотирована атрибутом `#[local]`.
|
||||
Другая `struct`-ура должна быть аннотирована атрибутом `#[shared]`.
|
||||
Разница между этими двумя множествами ресурсов будет описана познее.
|
||||
|
||||
Каждый контекс (задача-обработчик, `init` или `idle`) должен указать ресурсы, к которым
|
||||
он намерен обращаться, в соответсятвующем ему атрибуте с метаданными, используя
|
||||
либо аргумент `local`, либо `shared`. Этот аргумент принимает список имен ресурсов в качестве значения.
|
||||
Перечисленные ресурсы становятся доступны в контексте через поля `local` и `shared` структуры `Context`.
|
||||
|
||||
Во время выполнения при выходе из функции `#[init]` все ресурсы инициализированы.
|
||||
Функция `#[init]` должна возвращать начальные значения для всех ресурсов;
|
||||
отсюда следует, что тип возвращаемого ею значения включает типы
|
||||
структур `#[shared]` и `#[local]`.
|
||||
Поскольку ресурсы инициализированы в ходе функции `#[init]`, к ним нельзя
|
||||
получить доступ внетри функции `#[init]`.
|
||||
|
||||
Пример программы, показанной ниже содержит два обработчика прерывания.
|
||||
Каждый обработчик имеет доступ к его собственному `#[local]` ресурсу.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/resource.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example resource
|
||||
{{#include ../../../../ci/expected/resource.run}}
|
||||
```
|
||||
|
||||
К ресурсу `#[local]` нельзя получить доступ извне задачи к которой он
|
||||
привязан атрибутом `#[task]`.
|
||||
Попытка обращения к одному и тому же ресурсу `#[local]` из более чем одной
|
||||
задачи - ошибка компиляции.
|
||||
|
||||
## `lock`
|
||||
|
||||
Критические секции необходимы для доступа к ресурсам `#[shared]` таким образом,
|
||||
чтобы избежать гонок данных.
|
||||
|
||||
Поле `shared`, передаваемого `Context` реализует трейт [`Mutex`] для каждого разделяемого
|
||||
ресурса, доступного задаче.
|
||||
|
||||
Единственный метод этого трейта, [`lock`], запускает свой аргумент-замыкание в критической секции.
|
||||
|
||||
[`Mutex`]: ../../../api/rtic/trait.Mutex.html
|
||||
[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock
|
||||
|
||||
Критическая секция, создаваемая интерфейсом `lock` основана на динамических приоритетах:
|
||||
она временно повышает динамический приоритет контекста до *максимального* приоритета,
|
||||
что не дает другим задачам возможности вытеснить критическую секцию.
|
||||
Этот протокол синхронизации известен как [Протокол немедленного максимального приоритета
|
||||
(ICPP)][icpp], и компилируется диспетчером RTIC с [Политикой ресурсов стека(SRP)][srp].
|
||||
|
||||
[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol
|
||||
[srp]: https://en.wikipedia.org/wiki/Stack_Resource_Policy
|
||||
|
||||
В примере ниже у нас есть три обработчика прерываний с приоритетами от одного до трех.
|
||||
Два из обработчиков с более низким приоритетом соревнуются за ресурс `shared`,
|
||||
поэтому должны блокировать доступа к данным ресурса.
|
||||
Обработчик с наивысшим приоритетом, который не имеет доступа к ресурсу `shared`,
|
||||
может свободно вытеснять критическую секцию, созданную обработчиком с низким приоритетом.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/lock.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example lock
|
||||
{{#include ../../../../ci/expected/lock.run}}
|
||||
```
|
||||
|
||||
## Множественное блокировка
|
||||
|
||||
Это расширение к `lock`, чтобы уменьшить количесво отступов, блокируемые ресурсы можно объединять в кортежи.
|
||||
Следующий пример это демонстрирует:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/multilock.rs}}
|
||||
```
|
||||
|
||||
## Только разделяемый (`&-`) доступ
|
||||
|
||||
По-умолчанию фреймворк предполагает, что все задачи требуют эксклюзивный доступ (`&mut-`) к ресурсам,
|
||||
но возможно указать, что задаче достаточен разделяемый доступ (`&-`) к ресурсы с помощью синтакисиса
|
||||
`&resource_name` в списке `resources`.
|
||||
|
||||
Преимущество указания разделяемого досупа (`&-`) к ресурсу в том, что для доступа к ресурсу
|
||||
не нужна блокировка, даже если за ресурс соревнуются несколько задач, запускаемые с
|
||||
разными приоритетами. Недостаток в том, что задача получает только разделяемую ссылку (`&-`)
|
||||
на ресурс, и ограничена операциями, возможными с ней, но там, где разделяемой ссылки достаточно,
|
||||
такой подход уменьшает количесво требуемых блокировок.
|
||||
В дополнение к простым неизменяемым данным, такой разделяемый доступ может быть полезен для
|
||||
ресурсов, безопасно реализующих внутреннюю мутабельность с самоблокировкой или атомарными операциями.
|
||||
|
||||
Заметьте, что в этом релизе RTIC невозможно запросить и эксклюзивный доступ (`&mut-`)
|
||||
и разделяемый (`&-`) для *одного и того же* ресурса из различных задач.
|
||||
Попытка это сделать приведет к ошибке компиляции.
|
||||
|
||||
В примере ниже ключ (например криптографический ключ) загружается (или создается) во время выполнения,
|
||||
а затем используется двумя задачами, запускаемымы с различным приоритетом без каких-либо блокировок.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/only-shared-access.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example only-shared-access
|
||||
{{#include ../../../../ci/expected/only-shared-access.run}}
|
||||
```
|
||||
|
||||
## Неблокируемый доступ к изменяемым ресурсам
|
||||
|
||||
Критическая секция *не* требуется для доступа к ресурсу `#[shared]`,
|
||||
к которому обращаются только из задач с *одинаковым* приоритетом.
|
||||
В этом случае вы можете избежать `lock` API, добавив атрибут поля `#[lock_free]` при объявдении ресурса (смотреть пример ниже).
|
||||
Заметьте, что это лишь для удобства: даже если вы используете `lock` API,
|
||||
во время выполнения фреймворк *не* создаст критическую секцию.
|
||||
Еще одно ценное замечание: использование `#[lock_free]` на ресурсах,
|
||||
разделяемых задачами, запускаемыми с разными приоритетами
|
||||
приведет к ошибке *компиляции* -- не импользование `lock` API может
|
||||
привести к гонке данных в этом случае.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/lock-free.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example lock-free
|
||||
{{#include ../../../../ci/expected/lock-free.run}}
|
||||
```
|
|
@ -1,116 +0,0 @@
|
|||
# Программные задачи
|
||||
|
||||
В дополнение к аппаратным задачам, вызываемым в ответ на аппаратные события,
|
||||
RTIC также поддерживает *программные* задачи, которые могут порождаться
|
||||
приложением из любого контекста выполнения.
|
||||
|
||||
Программным задачам можно также назначать приоритет и, под капотом, они
|
||||
диспетчеризуются обработчиками прерываний. RTIC требует, чтобы свободные
|
||||
прерывания, были указаны в аргументе `dispatchers` модуля `app`, если используются
|
||||
программные задачи; часть из этих свободных прерываний будут использованы для
|
||||
управления программными задачами. Преимущество программных задач над аппаратными
|
||||
в том, что множество задач можно назначить на один обработчик прерывания.
|
||||
|
||||
Программные задачи также определяются атрибутом `task`, но аргумент `binds` опускается.
|
||||
|
||||
Пример ниже демонстрирует три программные задачи, запускаемых 2-х разных приоритетах.
|
||||
Три программные задачи привязаны к 2-м обработчикам прерываний.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/task.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example task
|
||||
{{#include ../../../../ci/expected/task.run}}
|
||||
```
|
||||
|
||||
## Передача сообщений
|
||||
|
||||
Другое преимущество программной задачи в том, что задачам можно передать сообщения
|
||||
в момент их запуска. Тип передаваемого сообщения должен быть определен в сигнатуре
|
||||
задачи-обработчика.
|
||||
|
||||
Пример ниже демонстрирует три задачи, две из которых ожидают сообщение.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/message.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example message
|
||||
{{#include ../../../../ci/expected/message.run}}
|
||||
```
|
||||
|
||||
## Вместимость
|
||||
|
||||
RTIC *не* производит никакого рода аллокаций памяти в куче.
|
||||
Память, необходимая для размещения сообщения резервируется статически.
|
||||
По-умолчанию фреймворк минимизирует выделение памяти программой таким образом,
|
||||
что каждая задача имеет "вместимость" для сообщения равную 1:
|
||||
это значит, что не более одного сообщения можно передать задаче перед тем, как
|
||||
у нее появится возможность к запуску. Это значение по-умолчанию можно
|
||||
изменить для каждой задачи, используя аргумент `capacity`.
|
||||
Этот аргумент принимает положительное целое, которое определяет как много
|
||||
сообщений буфер сообщений задачи может хранить.
|
||||
|
||||
Пример ниже устанавливает вместимость программной задачи `foo` равной 4.
|
||||
Если вместимость не установить, второй вызов `spawn.foo` в `UART0` приведет к ошибке (панике).
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/capacity.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example capacity
|
||||
{{#include ../../../../ci/expected/capacity.run}}
|
||||
```
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
Интерфейс `spawn` возвращает вариант `Err`, если для размещения сообщения нет места.
|
||||
В большинстве сценариев возникающие ошибки обрабатываются одним из двух способов:
|
||||
|
||||
- Паника, с помощью `unwrap`, `expect`, и т.п. Этот метод используется, чтобы обнаружить
|
||||
ошибку программиста (например bug) выбора вместительности, которая оказалась недостаточна.
|
||||
Когда эта паника встречается во время тестирования, выбирается большая вместительность,
|
||||
и перекомпиляция программы может решить проблему, но иногда достаточно окунуться глубже
|
||||
и провести анализ времени выполнения программы, чтобы выяснить, может ли платформа
|
||||
обрабатывать пиковые нагрузки, или процессор необходимо заменить на более быстрый.
|
||||
|
||||
- Игнорирование результата. В программах реального времени, как и в обычных, может быть
|
||||
нормальным иногда терять данные, или не получать ответ на некоторые события в пиковых ситуациях.
|
||||
В таких сценариях может быть допустимо игнорирование ошибки вызова `spawn`.
|
||||
|
||||
Следует отметить, что повторная попытка вызова `spawn` обычно неверный подход, поскольку
|
||||
такая операция на практике вероятно никогда не завершится успешно.
|
||||
Так как у нас есть только переключения контекста на задачи с *более высоким* приоритетом,
|
||||
повторение вызова `spawn` на задаче с низким приоритом никогда не позволит планировщику
|
||||
вызвать задачу, что значит, что буфер никогда не будет очищен. Такая ситуация отражена в
|
||||
следующем наброске:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(..)]
|
||||
mod app {
|
||||
#[init(spawn = [foo, bar])]
|
||||
fn init(cx: init::Context) {
|
||||
cx.spawn.foo().unwrap();
|
||||
cx.spawn.bar().unwrap();
|
||||
}
|
||||
|
||||
#[task(priority = 2, spawn = [bar])]
|
||||
fn foo(cx: foo::Context) {
|
||||
// ..
|
||||
|
||||
// программа зависнет здесь
|
||||
while cx.spawn.bar(payload).is_err() {
|
||||
// повтор попытки вызова spawn, если произошла ошибка
|
||||
}
|
||||
}
|
||||
|
||||
#[task(priority = 1)]
|
||||
fn bar(cx: bar::Context, payload: i32) {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,108 +0,0 @@
|
|||
# Очередь таймера
|
||||
|
||||
В отличие от интерфейса `spawn`, который немедленно передает программную задачу
|
||||
планировщику для немедленного запуска, интерфейс `schedule` можно использовать
|
||||
для планирования задачи к запуске через какое-то время в будущем.
|
||||
|
||||
Чтобы использовать интерфейс `schedule`, предварительно должен быть определен
|
||||
монотонный таймер с помощью аргумента `monotonic` атрибута `#[app]`.
|
||||
Этот аргумент принимает путь к типу, реализующему трейт [`Monotonic`].
|
||||
Ассоциированный тип, `Instant`, этого трейта представляет метку времени в соответствущих
|
||||
единицах измерения и широко используется в интерфейсе `schedule` -- предлагается смоделировать
|
||||
этот тип позднее [один из таких есть в стандартной библиотеке][std-instant].
|
||||
|
||||
Хотя это не отражено в определении трейта (из-за ограничений системы типов / трейтов),
|
||||
разница двух `Instant`ов должна возвращать какой-то тип `Duration` (см. [`core::time::Duration`])
|
||||
и этот `Duration` должен реализовывать трейт `TryInto<u32>`.
|
||||
Реализация этого трейта должна конвертировать значение `Duration`, которое
|
||||
использует какую-то определенную единицу измерения времени, в единицы измерения "тактов системного таймера
|
||||
(SYST)". Результат преобразований должен быть 32-битным целым.
|
||||
Если результат не соответствует 32-битному целому, тогда операция должна возвращать ошибку любого типа.
|
||||
|
||||
[`Monotonic`]: ../../../api/rtic/trait.Monotonic.html
|
||||
[std-instant]: https://doc.rust-lang.org/std/time/struct.Instant.html
|
||||
[`core::time::Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html
|
||||
|
||||
Для целевых платформ ARMv7+ крейт `rtic` предоставляет реализацию `Monotonic`, основанную на
|
||||
встроенном CYCle CouNTer (CYCCNT). Заметьте, что это 32-битный таймер, работающий на
|
||||
частоте центрального процессора, и поэтому не подходит для отслеживания интервалов времени в секундах.
|
||||
|
||||
Когда планируется задача, (определенный пользователем) `Instant`, в который задача должна быть
|
||||
выполнена, должен передаваться в качестве первого аргумента вызова `schedule`.
|
||||
|
||||
К тому же, выбранный `monotonic` таймер, необходимо сконфигурировать и инициализировать в
|
||||
фазе работы `#[init]`. Заметьте, что *также* касается случая использования `CYCCNT`,
|
||||
предоставляемого крейтом `cortex-m-rtic`.
|
||||
|
||||
Пример ниже планирует к выполнению две задачи из `init`: `foo` и `bar`. `foo` запланирована
|
||||
к запуску через 8 миллионов циклов в будущем. Далее, `bar` запланировано запустить через
|
||||
4 миллиона циклов в будущем. Таким образом, `bar` запустится до `foo`, так как и запланировано.
|
||||
|
||||
> **DF:YJ**: Примеры, использующие интерфейс `schedule` или абстракцию `Instant`
|
||||
> **не будут** правильно работать на эмуляторе QEMU, поскольку счетчик циклов Cortex-M
|
||||
> функционально не был реализован в `qemu-system-arm`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/schedule.rs}}
|
||||
```
|
||||
|
||||
Запусе программы на реальном оборудовании создает следующий вывод в консоли:
|
||||
|
||||
``` text
|
||||
{{#include ../../../../ci/expected/schedule.run}}
|
||||
```
|
||||
|
||||
Когда интерфейс `schedule` используется, среда исполнения использует внутри
|
||||
обработчик прерываний `SysTick` и периферию системного таймера (`SYST`), поэтому ни
|
||||
тот ни другой нельзя использовать в программе. Это гарантируется изменением типа
|
||||
`init::Context.core` с `cortex_m::Peripherals` на `rtic::Peripherals`.
|
||||
Последняя структура содержит все поля из предыдущей кроме `SYST`.
|
||||
|
||||
## Периодические задачи
|
||||
|
||||
Программные задачи имеют доступ к моменту времени `Instant`, в который они были запланированы
|
||||
на выполнение переменной `scheduled`. Эта информация и интерфейс `schedule` можно использовать,
|
||||
чтобы реализовать периодические задачи, как показано ниже.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/periodic.rs}}
|
||||
```
|
||||
|
||||
Это вывод, создаваемый примером. Заметьте, что здесь пристствует небольшой дрейф / колебания
|
||||
даже несмотря на то, что `schedule.foo` была вызвана в *конце* `foo`. Использование
|
||||
`Instant::now` вместо `scheduled` вызвало бы дрейф / колебания.
|
||||
|
||||
``` text
|
||||
{{#include ../../../../ci/expected/periodic.run}}
|
||||
```
|
||||
|
||||
## Базовое время
|
||||
|
||||
Для задач, вызываемых из `init` мы имеем точную информацию о их `scheduled` времени.
|
||||
Для аппаратных задач такого времени нет, поскольку они асинхронны по природе.
|
||||
Для аппаратных задач среда исполнения предоставляет время запуска (`start`), которое отражает
|
||||
время, в которое обработчик прерывания будет запущен.
|
||||
|
||||
Заметьте, что `start` **не** равно времени прихода события, которое вызывает задачу.
|
||||
В зависимости от приоритета задачи и загрузки системы, время `start` может сильно отдалиться от
|
||||
времени прихода события.
|
||||
|
||||
Какое по вашему мнению будет значение `scheduled` для программных задач, которые вызываются через
|
||||
`spawn` вместо планирования? Ответ в том, что вызываемые задачи наследуют
|
||||
*базовое* время того контекста, который их вызывает. Базовое время аппаратных задач -
|
||||
это их время `start`, базовое время программных задач - их время `scheduled`, а
|
||||
базовое время `init` - время старта системы, или нулевое
|
||||
(`Instant::zero()`). `idle` на самом деле не имеет базового времени, но задачи вызываемые из нее,
|
||||
используют `Instant::now()` в качестве базового.
|
||||
|
||||
Пример ниже демонстрирует разные смыслы *базового времени*.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/baseline.rs}}
|
||||
```
|
||||
|
||||
Запуск программы на реальном оборудовании приведет к следующему выводу в консоли:
|
||||
|
||||
``` text
|
||||
{{#include ../../../../ci/expected/baseline.run}}
|
||||
```
|
|
@ -1,175 +0,0 @@
|
|||
# Советы и хитрости
|
||||
|
||||
Полные примеры для RTIC смотрите в репозитарии [rtic-examples][rtic-examples].
|
||||
|
||||
[rtic-examples]: https://github.com/rtic-rs/rtic-examples
|
||||
|
||||
## Обобщенное программирование (Generics)
|
||||
|
||||
Все объекты, предоставляющие ресурысы реализуют трейт `rtic::Mutex`.
|
||||
Если ресурс не реализует его, можно обернуть его в новый тип [`rtic::Exclusive`],
|
||||
который реализует трейт `Mutex`. С помощью этого нового типа
|
||||
можно написать обобщенную функцию, которая работает с обобщенным ресурсом и
|
||||
вызывать его из различных задач, чтобы производить однотипные операции над
|
||||
похожим множеством ресурсов.
|
||||
Вот один такой пример:
|
||||
|
||||
[`rtic::Exclusive`]: ../../../api/rtic/struct.Exclusive.html
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/generics.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example generics
|
||||
{{#include ../../../../ci/expected/generics.run}}
|
||||
```
|
||||
|
||||
## Условная компиляция
|
||||
|
||||
Вы можете использовать условную компиляцию (`#[cfg]`) на ресурсах (полях структуры
|
||||
`#[resources] struct Resources`) и задачах (элементах `fn`).
|
||||
Эффект использования атрибутов `#[cfg]` в том, что ресурс/ задача
|
||||
будут *не* доступны в соответствующих структурах `Context` если условие не выполняется.
|
||||
|
||||
В примере ниже выводится сообщение каждый раз, когда вызывается задача `foo`, но только
|
||||
если программы скомпилирова с профилем `dev`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/cfg.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example cfg --release
|
||||
|
||||
$ cargo run --example cfg
|
||||
{{#include ../../../../ci/expected/cfg.run}}
|
||||
```
|
||||
|
||||
## Запуск задач из ОЗУ
|
||||
|
||||
Главной целью переноса описания программы на RTIC в атрибуты в
|
||||
RTIC v0.4.x была возможность взаимодействия с другими атрибутами.
|
||||
Напримерe, атрибут `link_section` можно применять к задачам, чтобы разместить
|
||||
их в ОЗУ; это может улучшить производительность в некоторых случаях.
|
||||
|
||||
> **ВАЖНО**: Обычно атрибуты `link_section`, `export_name` и `no_mangle`
|
||||
> очень мощные, но их легко использовать неправильно. Неверное использование
|
||||
> любого из этих атрибутов может вызвать неопределенное поведение;
|
||||
> Вам следует всегда предпочитать использование безопасных, высокоуровневых
|
||||
> атрибутов вместо них, таких как атрибуты `interrupt` и `exception`
|
||||
> из `cortex-m-rt`.
|
||||
>
|
||||
> В особых функций, размещаемых в ОЗУ нет безопасной абстракции в `cortex-m-rt`
|
||||
> v0.6.5 но создано [RFC] для добавления атрибута `ramfunc` в будущем релизе.
|
||||
|
||||
[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100
|
||||
|
||||
В примере ниже показано как разместить высокоприоритетную задачу `bar` в ОЗУ.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/ramfunc.rs}}
|
||||
```
|
||||
|
||||
Запуск этой программы создаст ожидаемый вывод.
|
||||
|
||||
``` console
|
||||
$ cargo run --example ramfunc
|
||||
{{#include ../../../../ci/expected/ramfunc.run}}
|
||||
```
|
||||
|
||||
Можно посмотреть на вывод `cargo-nm`, чтобы убедиться, что `bar` расположен в ОЗУ
|
||||
(`0x2000_0000`), тогда как `foo` расположен во Flash (`0x0000_0000`).
|
||||
|
||||
``` console
|
||||
$ cargo nm --example ramfunc --release | grep ' foo::'
|
||||
{{#include ../../../../ci/expected/ramfunc.run.grep.foo}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo nm --example ramfunc --release | grep ' bar::'
|
||||
{{#include ../../../../ci/expected/ramfunc.run.grep.bar}}
|
||||
```
|
||||
|
||||
## Обходной путь для быстрой передачи сообщений
|
||||
|
||||
Передача сообщений всегда вызывает копирование от отправителя в
|
||||
статическую переменную, а затем из статической переменной получателю.
|
||||
Таким образом, при передаче большого буфера, например `[u8; 128]`, передача сообщения
|
||||
вызывает два дорогих вызова `memcpy`. Чтобы минимизировать накладные расходы на передачу
|
||||
сообщения, можно использовать обходной путь: вместо передачи буфера по значению,
|
||||
можно передавать владеющий указатель на буфер.
|
||||
|
||||
Можно использовать глобальный аллокатор, чтобы реализовать данный трюк (`alloc::Box`,
|
||||
`alloc::Rc`, и т.п.), либо использовать статически аллоцируемый пул памяти, например [`heapless::Pool`].
|
||||
|
||||
[`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html
|
||||
|
||||
Здесь приведен пример использования `heapless::Pool` для "упаковки" буфера из 128 байт.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/pool.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --example pool
|
||||
{{#include ../../../../ci/expected/pool.run}}
|
||||
```
|
||||
|
||||
## Инспектирование раскрываемого кода
|
||||
|
||||
`#[rtic::app]` - это процедурный макрос, который создает код.
|
||||
Если по какой-то причине вам нужно увидеть код, сгенерированный этим макросом,
|
||||
у вас есть два пути:
|
||||
|
||||
Вы можете изучить файл `rtic-expansion.rs` внутри папки `target`. Этот файл
|
||||
содержит элемент `#[rtic::app]` в раскрытом виде (не всю вашу программу!)
|
||||
из *последней сборки* (с помощью `cargo build` или `cargo check`) RTIC программы.
|
||||
Раскрытый код не отформатирован по-умолчанию, но вы можете запустить `rustfmt`
|
||||
на нем перед тем, как читать.
|
||||
|
||||
``` console
|
||||
$ cargo build --example foo
|
||||
|
||||
$ rustfmt target/rtic-expansion.rs
|
||||
|
||||
$ tail target/rtic-expansion.rs
|
||||
```
|
||||
|
||||
``` rust
|
||||
#[doc = r" Implementation details"]
|
||||
mod app {
|
||||
#[doc = r" Always include the device crate which contains the vector table"]
|
||||
use lm3s6965 as _;
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn main() -> ! {
|
||||
rtic::export::interrupt::disable();
|
||||
let mut core: rtic::export::Peripherals = core::mem::transmute(());
|
||||
core.SCB.scr.modify(|r| r | 1 << 1);
|
||||
rtic::export::interrupt::enable();
|
||||
loop {
|
||||
rtic::export::wfi()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Или, вы можете использовать подкоманду [`cargo-expand`]. Она раскроет
|
||||
*все* макросы, включая атрибут `#[rtic::app]`, и модули в вашем крейте и
|
||||
напечатает вывод в консоль.
|
||||
|
||||
[`cargo-expand`]: https://crates.io/crates/cargo-expand
|
||||
|
||||
``` console
|
||||
$ # создаст такой же вывод, как выше
|
||||
$ cargo expand --example smallest | tail
|
||||
```
|
||||
|
||||
## Деструктуризация ресурса
|
||||
|
||||
Если задача требует нескольких ресурсов, разбиение структуры ресурсов
|
||||
может улучшить читабельность. Вот два примера того, как это можно сделать:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/destructure.rs}}
|
||||
```
|
|
@ -1,49 +0,0 @@
|
|||
# Типы, Send и Sync
|
||||
|
||||
Каждая функция в модуле `app` принимает структуру `Context` в качесте первого параметра.
|
||||
Все поля этих структур имеют предсказуемые, неанонимные типы,
|
||||
поэтому вы можете написать обычные функции, принимающие их как аргументы.
|
||||
|
||||
Справочник по API определяет как эти типы генерируются на основе входных данных.
|
||||
Вы можете также сгенерировать документацию к вашему крейту программы (`cargo doc --bin <name>`);
|
||||
в документации вы найдете структуры `Context` (например `init::Context` и
|
||||
`idle::Context`).
|
||||
|
||||
Пример ниже показывает различные типы, сгенерированные атрибутом `app`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/types.rs}}
|
||||
```
|
||||
|
||||
## `Send`
|
||||
|
||||
[`Send`] - это маркерный трейт для "типов, которые можно передавать через границы
|
||||
потоков", как это определено в `core`. В контексте RTIC трейт `Send` необходим
|
||||
только там, где возможна передача значения между задачами, запускаемыми на
|
||||
*разных* приоритетах. Это возникает в нескольких случаях: при передаче сообщений,
|
||||
в разделяемых `static mut` ресурсах и при инициализации поздних ресурсов.
|
||||
|
||||
[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
|
||||
|
||||
Атрибут `app` проверит, что `Send` реализован, где необходимо, поэтому вам не
|
||||
стоит волноваться об этом. В настоящий момент все передаваемые типы в RTIC должны быть `Send`, но
|
||||
это ограничение возможно будет ослаблено в будущем.
|
||||
|
||||
## `Sync`
|
||||
|
||||
Аналогично, [`Sync`] - маркерный трейт для "типов, на которые можно безопасно разделять между потоками",
|
||||
как это определено в `core`. В контексте RTIC типаж `Sync` необходим только там,
|
||||
где возможно для двух или более задач, запускаемых на разных приоритетах получить разделяемую ссылку (`&-`) на
|
||||
ресурс. Это возникает только (`&-`) ресурсах с разделяемым доступом.
|
||||
|
||||
[`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
|
||||
|
||||
Атрибут `app` проверит, что `Sync` реализован, где необходимо, но важно знать,
|
||||
где ограничение `Sync` не требуется: в (`&-`) ресурсах с разделяемым доступом, за которые
|
||||
соперничают задачи с *одинаковым* приоритетом.
|
||||
|
||||
В примере ниже показано, где можно использовать типы, не реализующие `Sync`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/not-sync.rs}}
|
||||
```
|
|
@ -1,14 +0,0 @@
|
|||
# Под капотом
|
||||
|
||||
**Этот раздел в настоящий момент находится в разработке,
|
||||
он появится снова, когда будет завершен**
|
||||
|
||||
Этот раздел описывает внутренности фреймворка RTIC на *высоком уровне*.
|
||||
Низкоуровневые детали, такие как парсинг и генерация кода, выполняемые процедурным макросом
|
||||
(`#[app]`) объясняться не будут. Внимание будет сосредоточено на анализе
|
||||
спецификации пользователя и структурах данных, используемых на этапе выполнения.
|
||||
|
||||
Мы настоятельно рекомендуем вам прочитать раздел о [конкуренции] в embedonomicon
|
||||
перед тем, как погружаться в материал.
|
||||
|
||||
[конкуренции]: https://github.com/rust-embedded/embedonomicon/pull/48
|
|
@ -1,158 +0,0 @@
|
|||
# Контроль доступа
|
||||
|
||||
Одна из основ RTIC - контроль доступа. Контроль того, какая часть программы
|
||||
может получить доступ к какой статической переменной - инструмент обеспечения
|
||||
безопасности памяти.
|
||||
|
||||
Статические переменные используются для разделения состояний между обработчиками
|
||||
прерываний, или между обработчиком прерывания и нижним контекстом выполнения, `main`.
|
||||
В обычном Rust коде трудно обеспечить гранулированный контроль за тем, какие функции
|
||||
могут получать доступ к статическим переменным, поскольку к статическим переменным
|
||||
можно получить доступ из любой функции, находящейся в той же области видимости,
|
||||
в которой они определены. Модули дают частичный контроль над доступом
|
||||
к статическим переменным, но они недостаточно гибкие.
|
||||
|
||||
Чтобы добиться полного контроля за тем, что задачи могут получить доступ
|
||||
только к статическим переменным (ресурсам), которые им были указаны в RTIC атрибуте,
|
||||
фреймворк RTIC производит трансформацию структуры кода.
|
||||
Эта трансформация состоит из размещения ресурсов (статических переменных), определенных
|
||||
пользователем *внутри* модуля, а пользовательского кода *вне* модуля.
|
||||
Это делает невозможным обращение пользовательского кода к статическим переменным.
|
||||
|
||||
Затем доступ к ресурсам предоставляется каждой задаче с помощью структуры `Resources`,
|
||||
чьи поля соответствуют ресурсам, к которым получает доступ задача.
|
||||
Есть лишь одна такая структура на задачу и структура `Resources` инициализируется
|
||||
либо уникальной ссылкой (`&mut-`) на статическую переменную, либо с помощью прокси-ресурса (см.
|
||||
раздел [критические секции](critical-sections.html)).
|
||||
|
||||
Код ниже - пример разных трансформаций структуры кода, происходящих за сценой:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
static mut X: u64: 0;
|
||||
static mut Y: bool: 0;
|
||||
|
||||
#[init(resources = [Y])]
|
||||
fn init(c: init::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART0, resources = [X])]
|
||||
fn foo(c: foo::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART1, resources = [X, Y])]
|
||||
fn bar(c: bar::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
Фреймворк создает код, подобный этому:
|
||||
|
||||
``` rust
|
||||
fn init(c: init::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
fn foo(c: foo::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
fn bar(c: bar::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
// Публичное API
|
||||
pub mod init {
|
||||
pub struct Context<'a> {
|
||||
pub resources: Resources<'a>,
|
||||
// ..
|
||||
}
|
||||
|
||||
pub struct Resources<'a> {
|
||||
pub Y: &'a mut bool,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod foo {
|
||||
pub struct Context<'a> {
|
||||
pub resources: Resources<'a>,
|
||||
// ..
|
||||
}
|
||||
|
||||
pub struct Resources<'a> {
|
||||
pub X: &'a mut u64,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bar {
|
||||
pub struct Context<'a> {
|
||||
pub resources: Resources<'a>,
|
||||
// ..
|
||||
}
|
||||
|
||||
pub struct Resources<'a> {
|
||||
pub X: &'a mut u64,
|
||||
pub Y: &'a mut bool,
|
||||
}
|
||||
}
|
||||
|
||||
/// Детали реализации
|
||||
mod app {
|
||||
// все, что внутри этого модуля спрятано от пользовательского кода
|
||||
|
||||
static mut X: u64 = 0;
|
||||
static mut Y: bool = 0;
|
||||
|
||||
// настоящая точка входа в программу
|
||||
unsafe fn main() -> ! {
|
||||
interrupt::disable();
|
||||
|
||||
// ..
|
||||
|
||||
// вызов пользовательского кода; передача ссылок на статические переменные
|
||||
init(init::Context {
|
||||
resources: init::Resources {
|
||||
X: &mut X,
|
||||
},
|
||||
// ..
|
||||
});
|
||||
|
||||
// ..
|
||||
|
||||
interrupt::enable();
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
// обработчик прерывания,с которым связан `foo`
|
||||
#[no_mangle]
|
||||
unsafe fn UART0() {
|
||||
// вызов пользовательского кода; передача ссылок на статические переменные
|
||||
foo(foo::Context {
|
||||
resources: foo::Resources {
|
||||
X: &mut X,
|
||||
},
|
||||
// ..
|
||||
});
|
||||
}
|
||||
|
||||
// обработчик прерывания,с которым связан `bar`
|
||||
#[no_mangle]
|
||||
unsafe fn UART1() {
|
||||
// вызов пользовательского кода; передача ссылок на статические переменные
|
||||
bar(bar::Context {
|
||||
resources: bar::Resources {
|
||||
X: &mut X,
|
||||
Y: &mut Y,
|
||||
},
|
||||
// ..
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,92 +0,0 @@
|
|||
# Анализ приоритетов
|
||||
|
||||
*Поиск максимального приоритета* ресурса (*ceiling*) - поиск динамического
|
||||
приоритета, который любая задача должна иметь, чтобы безопасно работать с
|
||||
памятью ресурсов. Анализ приоритетов - относительно прост,
|
||||
но критичен для безопасности памяти RTIC программ.
|
||||
|
||||
Для расчета максимального приоритета ресурса мы должны сначала составить
|
||||
список задач, имеющих доступ к ресурсу -- так как фреймворк RTIC
|
||||
форсирует контроль доступа к ресурсам на этапе компиляции, он
|
||||
также имеет доступ к этой информации на этапе компиляции.
|
||||
Максимальный приоритет ресурса - просто наивысший логический приоритет
|
||||
среди этих задач.
|
||||
|
||||
`init` и `idle` не настоящие задачи, но у них есть доступ к ресурсам,
|
||||
поэтому они должны учитываться при анализе приоритетов.
|
||||
`idle` учитывается как задача, имеющая логический приоритет `0`,
|
||||
в то время как `init` полностью исключается из анализа --
|
||||
причина этому в том, что `init` никогда не использует (не нуждается) критические
|
||||
секции для доступа к статическим переменным.
|
||||
|
||||
В предыдущем разделе мы показывали, что разделяемые ресусы
|
||||
могут быть представлены уникальными ссылками (`&mut-`) или скрываться за
|
||||
прокси в зависимости от того, имеет ли задача к ним доступ.
|
||||
Какой из вариантов представляется задаче зависит от приоритета задачи и
|
||||
максимального приоритета ресурса.
|
||||
Если приоритет задачи такой же, как максимальный приоритет ресурса, тогда
|
||||
задача получает уникальную ссылку (`&mut-`) на память ресурса,
|
||||
в противном случае задача получает прокси -- это также касается `idle`.
|
||||
`init` особеннвй: он всегда получает уникальные ссылки (`&mut-`) на ресурсы.
|
||||
|
||||
Пример для иллюстрации анализа приоритетов:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
struct Resources {
|
||||
// доступен из `foo` (prio = 1) и `bar` (prio = 2)
|
||||
// -> CEILING = 2
|
||||
#[init(0)]
|
||||
x: u64,
|
||||
|
||||
// доступен из `idle` (prio = 0)
|
||||
// -> CEILING = 0
|
||||
#[init(0)]
|
||||
y: u64,
|
||||
}
|
||||
|
||||
#[init(resources = [x])]
|
||||
fn init(c: init::Context) {
|
||||
// уникальная ссылка, потому что это `init`
|
||||
let x: &mut u64 = c.resources.x;
|
||||
|
||||
// уникальная ссылка, потому что это `init`
|
||||
let y: &mut u64 = c.resources.y;
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
// PRIORITY = 0
|
||||
#[idle(resources = [y])]
|
||||
fn idle(c: idle::Context) -> ! {
|
||||
// уникальная ссылка, потому что
|
||||
// приоритет (0) == максимальному приоритету ресурса (0)
|
||||
let y: &'static mut u64 = c.resources.y;
|
||||
|
||||
loop {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART0, priority = 1, resources = [x])]
|
||||
fn foo(c: foo::Context) {
|
||||
// прокси-ресурс, потому что
|
||||
// приоритет задач (1) < максимальному приоритету ресурса (2)
|
||||
let x: resources::x = c.resources.x;
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART1, priority = 2, resources = [x])]
|
||||
fn bar(c: foo::Context) {
|
||||
// уникальная ссылка, потому что
|
||||
// приоритет задачи (2) == максимальному приоритету ресурса (2)
|
||||
let x: &mut u64 = c.resources.x;
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
|
@ -1,521 +0,0 @@
|
|||
# Критические секции
|
||||
|
||||
Когда ресурсы (статические переменные) разделяются между двумя или более задачами,
|
||||
которые выполняются с разными приоритетами, некая форма запрета изменений
|
||||
необходима, чтобы изменять память без гонки данных. В RTIC мы используем
|
||||
основанные на приоритетах критические секции, чтобы гарантировать запрет изменений
|
||||
(см. [Протокол немедленного максимального приоритета][icpp]).
|
||||
|
||||
[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol
|
||||
|
||||
Критическия секция состоит во временном увеличении *динамического* приоритета задачи.
|
||||
Пока задача находится в критической секции, все другие задачи, которые могут
|
||||
послать запрос переменной *не могут запуститься*.
|
||||
|
||||
Насколько большим должен быть динамический приориткт, чтобы гарантировать запрет изменений
|
||||
определенного ресурса? [Анализ приоритетов](ceilings.html) отвечает на этот вопрос
|
||||
и будет обсужден в следующем разделе. В этом разделе мы сфокусируемся
|
||||
на реализации критической секции.
|
||||
|
||||
## Прокси-ресурсы
|
||||
|
||||
Для упрощения, давайте взглянем на ресурс, разделяемый двумя задачами,
|
||||
запускаемыми с разными приоритетами. Очевидно, что одна задача может вытеснить
|
||||
другую; чтобы предотвратить гонку данных задача с *низким приоритетом* должна
|
||||
использовать критическую секцию, когда необходимо изменять разделяемую память.
|
||||
С другой стороны, высокоприоритетная задача может напрямую изменять
|
||||
разделяемую память, поскольку не может быть вытеснена низкоприоритетной задачей.
|
||||
Чтобы заставить использовать критическую секцию на задаче с низким приоритетом,
|
||||
мы предоставляем *прокси-ресурсы*, в которых мы отдаем уникальную ссылку
|
||||
(`&mut-`) высокоприоритетной задаче.
|
||||
|
||||
Пример ниже показывает разные типы, передаваемые каждой задаче:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mut app {
|
||||
struct Resources {
|
||||
#[init(0)]
|
||||
x: u64,
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART0, priority = 1, resources = [x])]
|
||||
fn foo(c: foo::Context) {
|
||||
// прокси-ресурс
|
||||
let mut x: resources::x = c.resources.x;
|
||||
|
||||
x.lock(|x: &mut u64| {
|
||||
// критическая секция
|
||||
*x += 1
|
||||
});
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART1, priority = 2, resources = [x])]
|
||||
fn bar(c: bar::Context) {
|
||||
let mut x: &mut u64 = c.resources.x;
|
||||
|
||||
*x += 1;
|
||||
}
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
Теперь давайте посмотрим. как эти типы создаются фреймворком.
|
||||
|
||||
``` rust
|
||||
fn foo(c: foo::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
fn bar(c: bar::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
pub mod resources {
|
||||
pub struct x {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
pub mod foo {
|
||||
pub struct Resources {
|
||||
pub x: resources::x,
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
pub resources: Resources,
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bar {
|
||||
pub struct Resources<'a> {
|
||||
pub x: &'a mut u64,
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
pub resources: Resources,
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
mod app {
|
||||
static mut x: u64 = 0;
|
||||
|
||||
impl rtic::Mutex for resources::x {
|
||||
type T = u64;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut u64) -> R) -> R {
|
||||
// мы рассмотрим это детально позднее
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn UART0() {
|
||||
foo(foo::Context {
|
||||
resources: foo::Resources {
|
||||
x: resources::x::new(/* .. */),
|
||||
},
|
||||
// ..
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn UART1() {
|
||||
bar(bar::Context {
|
||||
resources: bar::Resources {
|
||||
x: &mut x,
|
||||
},
|
||||
// ..
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `lock`
|
||||
|
||||
Теперь давайте рассмотрим непосредственно критическую секцию. В этом примере мы должны
|
||||
увеличить динамический приоритет минимум до `2`, чтобы избежать гонки данных.
|
||||
В архитектуре Cortex-M динамический приоритет можно изменить записью в регистр `BASEPRI`.
|
||||
|
||||
Семантика регистра `BASEPRI` такова:
|
||||
|
||||
- Запись `0` в `BASEPRI` отключает его функциональность.
|
||||
- Запись ненулевого значения в `BASEPRI` изменяет уровень приоритета, требуемого для
|
||||
вытеснения прерывания. Однако, это имеет эффект, только когда записываемое значение
|
||||
*меньше*, чем уровень приоритета текущего контекста выполнения, но обращаем внимание, что
|
||||
более низкий уровень аппаратного приоритета означает более высокий логический приоритет
|
||||
|
||||
Таким образом, динамический приоритет в любой момент времени может быть рассчитан как
|
||||
|
||||
``` rust
|
||||
dynamic_priority = max(hw2logical(BASEPRI), hw2logical(static_priority))
|
||||
```
|
||||
|
||||
Где `static_priority` - приоритет, запрограммированный в NVIC для текущего прерывания,
|
||||
или логический `0`, когда текущий контекств - это `idle`.
|
||||
|
||||
В этом конкретном примере мы можем реализовать критическую секцию так:
|
||||
|
||||
> **ПРИМЕЧАНИЕ:** это упрощенная реализация
|
||||
|
||||
``` rust
|
||||
impl rtic::Mutex for resources::x {
|
||||
type T = u64;
|
||||
|
||||
fn lock<R, F>(&mut self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut u64) -> R,
|
||||
{
|
||||
unsafe {
|
||||
// начать критическую секцию: увеличить динамический приоритет до `2`
|
||||
asm!("msr BASEPRI, 192" : : : "memory" : "volatile");
|
||||
|
||||
// запустить пользовательский код в критической секции
|
||||
let r = f(&mut x);
|
||||
|
||||
// окончить критическую секцию: восстановить динамический приоритет до статического значения (`1`)
|
||||
asm!("msr BASEPRI, 0" : : : "memory" : "volatile");
|
||||
|
||||
r
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
В данном случае важно указать `"memory"` в блоке `asm!`.
|
||||
Это не даст компилятору менять местами операции вокруг него.
|
||||
Это важно, поскольку доступ к переменной `x` вне критической секции привело бы
|
||||
к гонке данных.
|
||||
|
||||
Важно отметить, что сигнатура метода `lock` препятствет его вложенным вызовам.
|
||||
Это необходимо для безопасности памяти, так как вложенные вызовы привели бы
|
||||
к созданию множественных уникальных ссылок (`&mut-`) на `x`, ломая правила заимствования Rust.
|
||||
Смотреть ниже:
|
||||
|
||||
``` rust
|
||||
#[interrupt(binds = UART0, priority = 1, resources = [x])]
|
||||
fn foo(c: foo::Context) {
|
||||
// resource proxy
|
||||
let mut res: resources::x = c.resources.x;
|
||||
|
||||
res.lock(|x: &mut u64| {
|
||||
res.lock(|alias: &mut u64| {
|
||||
//~^ ошибка: `res` уже был заимствован уникально (`&mut-`)
|
||||
// ..
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Вложенность
|
||||
|
||||
Вложенные вызовы `lock` на *том же* ресурсе должны отклоняться компилятором
|
||||
для безопасности памяти, однако вложенные вызовы `lock` на *разных* ресурсах -
|
||||
нормальная операция. В этом случае мы хотим убедиться, что вложенные критические секции
|
||||
никогда не приведут к понижению динамического приоритета, так как это плохо,
|
||||
и мы хотим оптимизировать несколько записей в регистр `BASEPRI` и compiler fences.
|
||||
Чтобы справиться с этим, мы проследим динамический приоритет задачи, с помощью стековой
|
||||
переменной и используем ее, чтобы решить, записывать `BASEPRI` или нет.
|
||||
На практике, стековая переменная будет соптимизирована компилятором, но все еще
|
||||
будет предоставлять информацию компилятору.
|
||||
|
||||
Рассмотрим такую программу:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
struct Resources {
|
||||
#[init(0)]
|
||||
x: u64,
|
||||
#[init(0)]
|
||||
y: u64,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init() {
|
||||
rtic::pend(Interrupt::UART0);
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART0, priority = 1, resources = [x, y])]
|
||||
fn foo(c: foo::Context) {
|
||||
let mut x = c.resources.x;
|
||||
let mut y = c.resources.y;
|
||||
|
||||
y.lock(|y| {
|
||||
*y += 1;
|
||||
|
||||
*x.lock(|x| {
|
||||
x += 1;
|
||||
});
|
||||
|
||||
*y += 1;
|
||||
});
|
||||
|
||||
// середина
|
||||
|
||||
x.lock(|x| {
|
||||
*x += 1;
|
||||
|
||||
y.lock(|y| {
|
||||
*y += 1;
|
||||
});
|
||||
|
||||
*x += 1;
|
||||
})
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART1, priority = 2, resources = [x])]
|
||||
fn bar(c: foo::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART2, priority = 3, resources = [y])]
|
||||
fn baz(c: foo::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
Код, сгенерированный фреймворком, выглядит так:
|
||||
|
||||
``` rust
|
||||
// опущено: пользовательский код
|
||||
|
||||
pub mod resources {
|
||||
pub struct x<'a> {
|
||||
priority: &'a Cell<u8>,
|
||||
}
|
||||
|
||||
impl<'a> x<'a> {
|
||||
pub unsafe fn new(priority: &'a Cell<u8>) -> Self {
|
||||
x { priority }
|
||||
}
|
||||
|
||||
pub unsafe fn priority(&self) -> &Cell<u8> {
|
||||
self.priority
|
||||
}
|
||||
}
|
||||
|
||||
// repeat for `y`
|
||||
}
|
||||
|
||||
pub mod foo {
|
||||
pub struct Context {
|
||||
pub resources: Resources,
|
||||
// ..
|
||||
}
|
||||
|
||||
pub struct Resources<'a> {
|
||||
pub x: resources::x<'a>,
|
||||
pub y: resources::y<'a>,
|
||||
}
|
||||
}
|
||||
|
||||
mod app {
|
||||
use cortex_m::register::basepri;
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn UART1() {
|
||||
// статический приоритет прерывания (определено пользователем)
|
||||
const PRIORITY: u8 = 2;
|
||||
|
||||
// сделать снимок BASEPRI
|
||||
let initial = basepri::read();
|
||||
|
||||
let priority = Cell::new(PRIORITY);
|
||||
bar(bar::Context {
|
||||
resources: bar::Resources::new(&priority),
|
||||
// ..
|
||||
});
|
||||
|
||||
// вернуть BASEPRI значение из снимка, сделанного ранее
|
||||
basepri::write(initial); // то же, что и `asm!` блок, виденный ранее
|
||||
}
|
||||
|
||||
// так же для `UART0` / `foo` и `UART2` / `baz`
|
||||
|
||||
impl<'a> rtic::Mutex for resources::x<'a> {
|
||||
type T = u64;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut u64) -> R) -> R {
|
||||
unsafe {
|
||||
// определение максимального приоритет ресурса
|
||||
const CEILING: u8 = 2;
|
||||
|
||||
let current = self.priority().get();
|
||||
if current < CEILING {
|
||||
// увеличить динамический приоритет
|
||||
self.priority().set(CEILING);
|
||||
basepri::write(logical2hw(CEILING));
|
||||
|
||||
let r = f(&mut y);
|
||||
|
||||
// восстановить динамический приоритет
|
||||
basepri::write(logical2hw(current));
|
||||
self.priority().set(current);
|
||||
|
||||
r
|
||||
} else {
|
||||
// динамический приоритет достаточно высок
|
||||
f(&mut y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// повторить для ресурса `y`
|
||||
}
|
||||
```
|
||||
|
||||
Наконец, компилятор оптимизирует функцию `foo` во что-то наподобие такого:
|
||||
|
||||
``` rust
|
||||
fn foo(c: foo::Context) {
|
||||
// ПРИМЕЧАНИЕ: BASEPRI содержит значение `0` (значение сброса) в этот момент
|
||||
|
||||
// увеличить динамический приоритет до `3`
|
||||
unsafe { basepri::write(160) }
|
||||
|
||||
// две операции над `y` объединены в одну
|
||||
y += 2;
|
||||
|
||||
// BASEPRI не изменяется для доступа к `x`, потому что динамический приоритет достаточно высок
|
||||
x += 1;
|
||||
|
||||
// уменьшить (восстановить) динамический приоритет до `1`
|
||||
unsafe { basepri::write(224) }
|
||||
|
||||
// средина
|
||||
|
||||
// увеличить динамический приоритет до `2`
|
||||
unsafe { basepri::write(192) }
|
||||
|
||||
x += 1;
|
||||
|
||||
// увеличить динамический приоритет до `3`
|
||||
unsafe { basepri::write(160) }
|
||||
|
||||
y += 1;
|
||||
|
||||
// уменьшить (восстановить) динамический приоритет до `2`
|
||||
unsafe { basepri::write(192) }
|
||||
|
||||
// ПРИМЕЧАНИЕ: было вы правильно объединить эту операцию над `x` с предыдущей, но
|
||||
// compiler fences грубые и предотвращают оптимизацию
|
||||
x += 1;
|
||||
|
||||
// уменьшить (восстановить) динамический приоритет до `1`
|
||||
unsafe { basepri::write(224) }
|
||||
|
||||
// ПРИМЕЧАНИЕ: BASEPRI содержит значение `224` в этот момент
|
||||
// обработчик UART0 восстановит значение `0` перед завершением
|
||||
}
|
||||
```
|
||||
|
||||
## Инвариант BASEPRI
|
||||
|
||||
Инвариант, который фреймворк RTIC должен сохранять в том, что значение
|
||||
BASEPRI в начале обработчика *прерывания* должно быть таким же, как и при выходе
|
||||
из него. BASEPRI может изменяться в процессе выполнения обработчика прерывания,
|
||||
но но выполнения обработчика прерывания в начале и конце не должно вызвать
|
||||
наблюдаемого изменения BASEPRI.
|
||||
|
||||
Этот инвариант нужен, чтобы избежать уеличения динамического приоритета до значений,
|
||||
при которых обработчик не сможет быть вытеснен. Лучше всего это видно на следующем примере:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
struct Resources {
|
||||
#[init(0)]
|
||||
x: u64,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init() {
|
||||
// `foo` запустится сразу после завершения `init`
|
||||
rtic::pend(Interrupt::UART0);
|
||||
}
|
||||
|
||||
#[task(binds = UART0, priority = 1)]
|
||||
fn foo() {
|
||||
// BASEPRI равен `0` в этот момент; динамический приоритет равен `1`
|
||||
|
||||
// `bar` вытеснит `foo` в этот момент
|
||||
rtic::pend(Interrupt::UART1);
|
||||
|
||||
// BASEPRI равен `192` в этот момент (из-за бага); динамический приоритет равен `2`
|
||||
// эта функция возвращается в `idle`
|
||||
}
|
||||
|
||||
#[task(binds = UART1, priority = 2, resources = [x])]
|
||||
fn bar() {
|
||||
// BASEPRI равен `0` (динамический приоритет = 2)
|
||||
|
||||
x.lock(|x| {
|
||||
// BASEPRI увеличен до `160` (динамический приоритет = 3)
|
||||
|
||||
// ..
|
||||
});
|
||||
|
||||
// BASEPRI восстановлен до `192` (динамический приоритет = 2)
|
||||
}
|
||||
|
||||
#[idle]
|
||||
fn idle() -> ! {
|
||||
// BASEPRI равен `192` (из-за бага); динамический приоритет = 2
|
||||
|
||||
// это не оказывает эффекта, из-за значени BASEPRI
|
||||
// задача `foo` не будет выполнена снова никогда
|
||||
rtic::pend(Interrupt::UART0);
|
||||
|
||||
loop {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = UART2, priority = 3, resources = [x])]
|
||||
fn baz() {
|
||||
// ..
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
ВАЖНО: давайте например мы *забудем* восстановить `BASEPRI` в `UART1` -- из-за
|
||||
какого нибудь бага в генераторе кода RTIC.
|
||||
|
||||
``` rust
|
||||
// код, сгенерированный RTIC
|
||||
|
||||
mod app {
|
||||
// ..
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn UART1() {
|
||||
// статический приоритет этого прерывания (определен пользователем)
|
||||
const PRIORITY: u8 = 2;
|
||||
|
||||
// сделать снимок BASEPRI
|
||||
let initial = basepri::read();
|
||||
|
||||
let priority = Cell::new(PRIORITY);
|
||||
bar(bar::Context {
|
||||
resources: bar::Resources::new(&priority),
|
||||
// ..
|
||||
});
|
||||
|
||||
// БАГ: ЗАБЫЛИ восстановить BASEPRI на значение из снимка
|
||||
basepri::write(initial);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
В результате, `idle` запустится на динамическом приоритете `2` и на самом деле
|
||||
система больше никогда не перейдет на динамический приоритет ниже `2`.
|
||||
Это не компромис для безопасности памяти программы, а влияет на диспетчеризацию задач:
|
||||
в этом конкретном случае задачи с приоритетом `1` никогда не получат шанс на запуск.
|
|
@ -1,72 +0,0 @@
|
|||
# Настройка прерываний
|
||||
|
||||
Прерывания - это основа работы программ на RTIC. Правильно настроить приоритеты
|
||||
прерываний и убедиться, что они не изменяются во время выполнения обязательно
|
||||
для безопасной работы программы.
|
||||
|
||||
Фреймворк RTIC представляет приоритеты прерываний, как нечто, что должно быть определено
|
||||
на этапе компиляции. Однако, статическая настройка должна быть зашита в соответствующие регистры
|
||||
в процессе инициализации программы. Настройка прерываний происходит до запуска функции `init`.
|
||||
|
||||
Этот пример дает представление о коде, запускаемом фреймворком RTIC:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = lm3s6965)]
|
||||
mod app {
|
||||
#[init]
|
||||
fn init(c: init::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
#[idle]
|
||||
fn idle(c: idle::Context) -> ! {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART0, priority = 2)]
|
||||
fn foo(c: foo::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Фреймворк генерирует точку входа в программу, которая выглядит примерно так:
|
||||
|
||||
``` rust
|
||||
// настоящая точку входа в программу
|
||||
#[no_mangle]
|
||||
unsafe fn main() -> ! {
|
||||
// преобразует логические приоритеты в аппаратные / NVIC приоритеты
|
||||
fn logical2hw(priority: u8) -> u8 {
|
||||
use lm3s6965::NVIC_PRIO_BITS;
|
||||
|
||||
// NVIC кодирует приоритеты верхними битами
|
||||
// большие значения обозначают меньший приоритет
|
||||
((1 << NVIC_PRIORITY_BITS) - priority) << (8 - NVIC_PRIO_BITS)
|
||||
}
|
||||
|
||||
cortex_m::interrupt::disable();
|
||||
|
||||
let mut core = cortex_m::Peripheral::steal();
|
||||
|
||||
core.NVIC.enable(Interrupt::UART0);
|
||||
|
||||
// значение, определенное пользователем
|
||||
let uart0_prio = 2;
|
||||
|
||||
// проверка на этапе компиляции, что определенный приоритет входит в поддерживаемый диапазон
|
||||
let _ = [(); (1 << NVIC_PRIORITY_BITS) - (uart0_prio as usize)];
|
||||
|
||||
core.NVIC.set_priority(Interrupt::UART0, logical2hw(uart0_prio));
|
||||
|
||||
// вызов пользовательского кода
|
||||
init(/* .. */);
|
||||
|
||||
// ..
|
||||
|
||||
cortex_m::interrupt::enable();
|
||||
|
||||
// вызов пользовательского кода
|
||||
idle(/* .. */)
|
||||
}
|
||||
```
|
|
@ -1,113 +0,0 @@
|
|||
# Поздние ресурсы
|
||||
|
||||
Некоторые ресурсы инициализируются во время выполнения после завершения функции `init`.
|
||||
Важно то, что ресурсы (статические переменные) полностью инициализируются
|
||||
до того, как задачи смогут запуститься, вот почему они должны быть инициализированы
|
||||
пока прерывания отключены.
|
||||
|
||||
Ниже показан пример кода, генерируемого фреймворком для инициализации позних ресурсов.
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
struct Resources {
|
||||
x: Thing,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init() -> init::LateResources {
|
||||
// ..
|
||||
|
||||
init::LateResources {
|
||||
x: Thing::new(..),
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = UART0, resources = [x])]
|
||||
fn foo(c: foo::Context) {
|
||||
let x: &mut Thing = c.resources.x;
|
||||
|
||||
x.frob();
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
Код, генерируемы фреймворком выглядит примерно так:
|
||||
|
||||
``` rust
|
||||
fn init(c: init::Context) -> init::LateResources {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
fn foo(c: foo::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
// Public API
|
||||
pub mod init {
|
||||
pub struct LateResources {
|
||||
pub x: Thing,
|
||||
}
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
pub mod foo {
|
||||
pub struct Resources<'a> {
|
||||
pub x: &'a mut Thing,
|
||||
}
|
||||
|
||||
pub struct Context<'a> {
|
||||
pub resources: Resources<'a>,
|
||||
// ..
|
||||
}
|
||||
}
|
||||
|
||||
/// Детали реализации
|
||||
mod app {
|
||||
// неинициализированная статическая переменная
|
||||
static mut x: MaybeUninit<Thing> = MaybeUninit::uninit();
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn main() -> ! {
|
||||
cortex_m::interrupt::disable();
|
||||
|
||||
// ..
|
||||
|
||||
let late = init(..);
|
||||
|
||||
// инициализация поздних ресурсов
|
||||
x.as_mut_ptr().write(late.x);
|
||||
|
||||
cortex_m::interrupt::enable(); //~ compiler fence
|
||||
|
||||
// исключения, прерывания и задачи могут вытеснить `main` в этой точке
|
||||
|
||||
idle(..)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn UART0() {
|
||||
foo(foo::Context {
|
||||
resources: foo::Resources {
|
||||
// `x` уже инициализирована к этому моменту
|
||||
x: &mut *x.as_mut_ptr(),
|
||||
},
|
||||
// ..
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Важная деталь здесь то, что `interrupt::enable` ведет себя как *барьер компиляции*, который не дает компилятору переставить запись в `X` *после*
|
||||
`interrupt::enable`. Если бы компилятор мог делать такие перестановки появились
|
||||
бы гонки данных между этой записью и любой операцией `foo`, взаимодействующей с `X`.
|
||||
|
||||
Архитектурам с более сложным конвейером инструкций нужен барьер памяти
|
||||
(`atomic::fence`) вместо compiler fence для полной очистки операции записи
|
||||
перед включением прерываний. Архитектура ARM Cortex-M не нуждается в барьере памяти
|
||||
в одноядерном контексте.
|
|
@ -1,79 +0,0 @@
|
|||
# Нереентерабельность
|
||||
|
||||
В RTIC задачи-обработчики *не* могут использоваться повторно. Переиспользование задачи-обработчика
|
||||
может сломать правила заимствования Rust и привести к *неопределенному поведению*.
|
||||
Задача-обработчик теоретически может быть переиспользована одним из двух способов: программно или аппаратно.
|
||||
|
||||
## Программно
|
||||
|
||||
Чтобы переиспользовать задачу-обработчик программно, назначенный ей обработчик прерывания
|
||||
должен быть вызван с помощью FFI (смотрите пример ниже). FFI требует `unsafe` код,
|
||||
что уменьшает желание конечных пользователей вызывать обработчик прерывания.
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
#[init]
|
||||
fn init(c: init::Context) { .. }
|
||||
|
||||
#[interrupt(binds = UART0)]
|
||||
fn foo(c: foo::Context) {
|
||||
static mut X: u64 = 0;
|
||||
|
||||
let x: &mut u64 = X;
|
||||
|
||||
// ..
|
||||
|
||||
//~ `bar` может вытеснить `foo` в этом месте
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
#[interrupt(binds = UART1, priority = 2)]
|
||||
fn bar(c: foo::Context) {
|
||||
extern "C" {
|
||||
fn UART0();
|
||||
}
|
||||
|
||||
// этот обработчик прерывания вызовет задачу-обработчик `foo`, что сломает
|
||||
// ссылку на статическую переменную `X`
|
||||
unsafe { UART0() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Фреймворк RTIC должен сгенерировать код обработчика прерывания, который вызывает
|
||||
определенные пользователем задачи-обработчики. Мы аккуратны в том, чтобы обеспечить
|
||||
невозможность вызова этих обработчиков из пользовательского кода.
|
||||
|
||||
Пример выше раскрывается в:
|
||||
|
||||
``` rust
|
||||
fn foo(c: foo::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
fn bar(c: bar::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
mod app {
|
||||
// все в этом блоке невидимо для пользовательского кода
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn USART0() {
|
||||
foo(..);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn USART1() {
|
||||
bar(..);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Аппаратно
|
||||
|
||||
Обработчик прерывания также может быть вызван без программного вмешательства.
|
||||
Это может произойти, если один обработчик будет назначен двум или более прерываниям
|
||||
в векторе прерываний, но синтаксиса для такого рода функциональности в RTIC нет.
|
|
@ -1,399 +0,0 @@
|
|||
# Программные задачи
|
||||
|
||||
RTIC поддерживает программные и аппаратные задачи. Каждая аппаратная задача
|
||||
назначается на отдельный обработчик прерывания. С другой стороны, несколько
|
||||
программных задач могут управляться одним обработчиком прерывания --
|
||||
это сделано, чтобы минимизировать количество обработчиков прерывания,
|
||||
используемых фреймворком.
|
||||
|
||||
Фреймворк группирует задачи, для которых вызывается `spawn` по уровню приоритета,
|
||||
и генерирует один *диспетчер задачи* для каждого уровня приоритета.
|
||||
Каждый диспетчер запускается на отдельном обработчике прерывания,
|
||||
а приоритет этого обработчика прерывания устанавливается так, чтобы соответствовать
|
||||
уровню приоритета задач, управляемых диспетчером.
|
||||
|
||||
Каждый диспетчер задач хранит *очередь* задач, *готовых* к выполнению;
|
||||
эта очередь называется *очередью готовности*. Вызов программной задачи состоит
|
||||
из добавления записи в очередь и вызова прерывания, который запускает соответствующий
|
||||
диспетчер задач. Каждая запись в эту очередь содержит метку (`enum`),
|
||||
которая идентифицирует задачу, которую необходимо выполнить и *указатель*
|
||||
на сообщение, передаваемое задаче.
|
||||
|
||||
Очередь готовности - неблокируемая очередь типа SPSC (один производитель - один потребитель).
|
||||
Диспетчер задач владеет конечным потребителем в очереди; конечным производителем
|
||||
считается ресурс, за который соперничают задачи, которые могут вызывать (`spawn`) другие задачи.
|
||||
|
||||
## Дисметчер задач
|
||||
|
||||
Давайте сначала глянем на код, генерируемый фреймворком для диспетчеризации задач.
|
||||
Рассмотрим пример:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
// ..
|
||||
|
||||
#[interrupt(binds = UART0, priority = 2, spawn = [bar, baz])]
|
||||
fn foo(c: foo::Context) {
|
||||
foo.spawn.bar().ok();
|
||||
|
||||
foo.spawn.baz(42).ok();
|
||||
}
|
||||
|
||||
#[task(capacity = 2, priority = 1)]
|
||||
fn bar(c: bar::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[task(capacity = 2, priority = 1, resources = [X])]
|
||||
fn baz(c: baz::Context, input: i32) {
|
||||
// ..
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn UART1();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Фреймворк создает следующий диспетчер задач, состоящий из обработчика прерывания и очереди готовности:
|
||||
|
||||
``` rust
|
||||
fn bar(c: bar::Context) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
mod app {
|
||||
use heapless::spsc::Queue;
|
||||
use cortex_m::register::basepri;
|
||||
|
||||
struct Ready<T> {
|
||||
task: T,
|
||||
// ..
|
||||
}
|
||||
|
||||
/// вызываемые (`spawn`) задачи, выполняющиеся с уровнем приоритета `1`
|
||||
enum T1 {
|
||||
bar,
|
||||
baz,
|
||||
}
|
||||
|
||||
// очередь готовности диспетчера задач
|
||||
// `5-1=4` - представляет собой емкость этой очереди
|
||||
static mut RQ1: Queue<Ready<T1>, 5> = Queue::new();
|
||||
|
||||
// обработчик прерывания, выбранный для диспетчеризации задач с приоритетом `1`
|
||||
#[no_mangle]
|
||||
unsafe UART1() {
|
||||
// приоритет данного обработчика прерывания
|
||||
const PRIORITY: u8 = 1;
|
||||
|
||||
let snapshot = basepri::read();
|
||||
|
||||
while let Some(ready) = RQ1.split().1.dequeue() {
|
||||
match ready.task {
|
||||
T1::bar => {
|
||||
// **ПРИМЕЧАНИЕ** упрощенная реализация
|
||||
|
||||
// используется для отслеживания динамического приоритета
|
||||
let priority = Cell::new(PRIORITY);
|
||||
|
||||
// вызов пользовательского кода
|
||||
bar(bar::Context::new(&priority));
|
||||
}
|
||||
|
||||
T1::baz => {
|
||||
// рассмотрим `baz` позднее
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// инвариант BASEPRI
|
||||
basepri::write(snapshot);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Вызов задачи
|
||||
|
||||
Интерфейс `spawn` предоставлен пользователю как методы структурв `Spawn`.
|
||||
Для каждой задачи существует своя структура `Spawn`.
|
||||
|
||||
Код `Spawn`, генерируемый фреймворком для предыдущего примера выглядит так:
|
||||
|
||||
``` rust
|
||||
mod foo {
|
||||
// ..
|
||||
|
||||
pub struct Context<'a> {
|
||||
pub spawn: Spawn<'a>,
|
||||
// ..
|
||||
}
|
||||
|
||||
pub struct Spawn<'a> {
|
||||
// отслеживает динамический приоритет задачи
|
||||
priority: &'a Cell<u8>,
|
||||
}
|
||||
|
||||
impl<'a> Spawn<'a> {
|
||||
// `unsafe` и спрятано, поскольку сы не хотит, чтобы пользователь вмешивался сюда
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn priority(&self) -> &Cell<u8> {
|
||||
self.priority
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod app {
|
||||
// ..
|
||||
|
||||
// Поиск максимального приоритета для конечного производителя `RQ1`
|
||||
const RQ1_CEILING: u8 = 2;
|
||||
|
||||
// используется, чтобы отследить сколько еще сообщений для `bar` можно поставить в очередь
|
||||
// `3-1=2` - емкость задачи `bar`; максимум 2 экземпляра можно добавить в очередь
|
||||
// эта очередь заполняется фреймворком до того, как запустится `init`
|
||||
static mut bar_FQ: Queue<(), 3> = Queue::new();
|
||||
|
||||
// Поиск максимального приоритета для конечного потребителя `bar_FQ`
|
||||
const bar_FQ_CEILING: u8 = 2;
|
||||
|
||||
// приоритет-ориентированная критическая секция
|
||||
//
|
||||
// это запускае переданное замыкание `f` с динамическим приоритетом не ниже
|
||||
// `ceiling`
|
||||
fn lock(priority: &Cell<u8>, ceiling: u8, f: impl FnOnce()) {
|
||||
// ..
|
||||
}
|
||||
|
||||
impl<'a> foo::Spawn<'a> {
|
||||
/// Вызывает задачу `bar`
|
||||
pub fn bar(&self) -> Result<(), ()> {
|
||||
unsafe {
|
||||
match lock(self.priority(), bar_FQ_CEILING, || {
|
||||
bar_FQ.split().1.dequeue()
|
||||
}) {
|
||||
Some(()) => {
|
||||
lock(self.priority(), RQ1_CEILING, || {
|
||||
// помещаем задачу в очередь готовности
|
||||
RQ1.split().1.enqueue_unchecked(Ready {
|
||||
task: T1::bar,
|
||||
// ..
|
||||
})
|
||||
});
|
||||
|
||||
// вызываем прерывание, которое запускает диспетчер задач
|
||||
rtic::pend(Interrupt::UART0);
|
||||
}
|
||||
|
||||
None => {
|
||||
// достигнута максимальная вместительность; неудачный вызов
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Использование `bar_FQ` для ограничения числа задач `bar`, которые могут бы вызваны,
|
||||
может показаться искусственным, но это будет иметь больше смысла, когда мы поговорим
|
||||
о вместительности задач.
|
||||
|
||||
## Сообщения
|
||||
|
||||
Мы пропустили, как на самом деле работает передача сообщений, поэтому давайте вернемся
|
||||
к реализации `spawn`, но в этот раз для задачи `baz`, которая принимает сообщение типа `u64`.
|
||||
|
||||
``` rust
|
||||
fn baz(c: baz::Context, input: u64) {
|
||||
// .. пользовательский код ..
|
||||
}
|
||||
|
||||
mod app {
|
||||
// ..
|
||||
|
||||
// Теперь мы покажем все содержимое структуры `Ready`
|
||||
struct Ready {
|
||||
task: Task,
|
||||
// индекс сообщения; используется с буфером `INPUTS`
|
||||
index: u8,
|
||||
}
|
||||
|
||||
// память, зарезервированная для хранения сообщений, переданных `baz`
|
||||
static mut baz_INPUTS: [MaybeUninit<u64>; 2] =
|
||||
[MaybeUninit::uninit(), MaybeUninit::uninit()];
|
||||
|
||||
// список свободной памяти: используется для отслеживания свободных ячеек в массиве `baz_INPUTS`
|
||||
// эта очередь инициализируется значениями `0` и `1` перед запуском `init`
|
||||
static mut baz_FQ: Queue<u8, 3> = Queue::new();
|
||||
|
||||
// Поиск максимального приоритета для конечного потребителя `baz_FQ`
|
||||
const baz_FQ_CEILING: u8 = 2;
|
||||
|
||||
impl<'a> foo::Spawn<'a> {
|
||||
/// Spawns the `baz` task
|
||||
pub fn baz(&self, message: u64) -> Result<(), u64> {
|
||||
unsafe {
|
||||
match lock(self.priority(), baz_FQ_CEILING, || {
|
||||
baz_FQ.split().1.dequeue()
|
||||
}) {
|
||||
Some(index) => {
|
||||
// ПРИМЕЧАНИЕ: `index` - владеющий указатель на ячейку буфера
|
||||
baz_INPUTS[index as usize].write(message);
|
||||
|
||||
lock(self.priority(), RQ1_CEILING, || {
|
||||
// помещаем задачу в очередь готовности
|
||||
RQ1.split().1.enqueue_unchecked(Ready {
|
||||
task: T1::baz,
|
||||
index,
|
||||
});
|
||||
});
|
||||
|
||||
// вызываем прерывание, которое запускает диспетчер задач
|
||||
rtic::pend(Interrupt::UART0);
|
||||
}
|
||||
|
||||
None => {
|
||||
// достигнута максимальная вместительность; неудачный вызов
|
||||
Err(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
А теперь давайте взглянем на настоящую реализацию диспетчера задач:
|
||||
|
||||
``` rust
|
||||
mod app {
|
||||
// ..
|
||||
|
||||
#[no_mangle]
|
||||
unsafe UART1() {
|
||||
const PRIORITY: u8 = 1;
|
||||
|
||||
let snapshot = basepri::read();
|
||||
|
||||
while let Some(ready) = RQ1.split().1.dequeue() {
|
||||
match ready.task {
|
||||
Task::baz => {
|
||||
// ПРИМЕЧАНИЕ: `index` - владеющий указатель на ячейку буфера
|
||||
let input = baz_INPUTS[ready.index as usize].read();
|
||||
|
||||
// сообщение было прочитано, поэтому можно вернуть ячейку обратно
|
||||
// чтобы освободить очередь
|
||||
// (диспетчер задач имеет эксклюзивный доступ к
|
||||
// последнему элементу очереди)
|
||||
baz_FQ.split().0.enqueue_unchecked(ready.index);
|
||||
|
||||
let priority = Cell::new(PRIORITY);
|
||||
baz(baz::Context::new(&priority), input)
|
||||
}
|
||||
|
||||
Task::bar => {
|
||||
// выглядит также как ветка для `baz`
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// инвариант BASEPRI
|
||||
basepri::write(snapshot);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`INPUTS` плюс `FQ`, список свободной памяти равняется эффективному пулу памяти.
|
||||
Однако, вместо того *список свободной памяти* (связный список), чтобы отслеживать
|
||||
пустые ячейки в буфере `INPUTS`, мы используем SPSC очередь; это позволяет нам
|
||||
уменьшить количество критических секций.
|
||||
На самом деле благодаря этому выбору код диспетчера задач неблокируемый.
|
||||
|
||||
## Вместительность очереди
|
||||
|
||||
Фреймворк RTIC использует несколько очередей, такие как очереди готовности и
|
||||
списки свободной памяти. Когда список свободной памяти пуст, попытка выызова
|
||||
(`spawn`) задачи приводит к ошибке; это условие проверяется во время выполнения.
|
||||
Не все операции, произвожимые фреймворком с этими очередями проверяют их
|
||||
пустоту / наличие места. Например, возвращение ячейки списка свободной памяти
|
||||
(см. диспетчер задач) не проверяется, поскольку есть фиксированное количество
|
||||
таких ячеек циркулирующих в системе, равное вместительности списка свободной памяти.
|
||||
Аналогично, добавление записи в очередь готовности (см. `Spawn`) не проверяется,
|
||||
потому что вместительность очереди выбрана фреймворком.
|
||||
|
||||
Пользователи могут задавать вместительность программных задач;
|
||||
эта вместительность - максимальное количество сообщений, которые можно
|
||||
послать указанной задаче от задачи более высоким приоритетом до того,
|
||||
как `spawn` вернет ошибку. Эта определяемая пользователем иместительность -
|
||||
размер списка свободной памяти задачи (например `foo_FQ`), а также размер массива,
|
||||
содержащего входные данные для задачи (например `foo_INPUTS`).
|
||||
|
||||
Вместительность очереди готовности (например `RQ1`) вычисляется как *сумма*
|
||||
вместительностей всех задач, управляемх диспетчером; эта сумма является также
|
||||
количеством сообщений, которые очередь может хранить в худшем сценарии, когда
|
||||
все возможные сообщения были посланы до того, как диспетчер задач получает шанс
|
||||
на запуск. По этой причине получение ячейки списка свободной памяти при любой
|
||||
операции `spawn` приводит к тому, что очередь готовности еще не заполнена,
|
||||
поэтому вставка записи в список готовности может пропустить проверку "полна ли очередь?".
|
||||
|
||||
В нашем запущенном примере задача `bar` не принимает входных данных, поэтому
|
||||
мы можем пропустить проверку как `bar_INPUTS`, так и `bar_FQ` и позволить
|
||||
пользователю посылать неограниченное число сообщений задаче, но если бы мы сделали это,
|
||||
было бы невозможно превысить вместительность для `RQ1`, что позволяет нам
|
||||
пропустить проверку "полна ли очередь?" при вызове задачи `baz`.
|
||||
В разделе о [очереди таймера](timer-queue.html) мы увидим как
|
||||
список свободной памяти используется для задач без входных данных.
|
||||
|
||||
## Анализ приоритетов
|
||||
|
||||
Очереди, использемые внутри интерфейса `spawn`, рассматриваются как обычные ресурсы
|
||||
и для них тоже работает анализ приоритетов. Важно заметить, что это SPSC очереди,
|
||||
и только один из конечных элементов становится ресурсом; другим конечным элементом
|
||||
владеет диспетчер задач.
|
||||
|
||||
Рассмотрим следующий пример:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
#[idle(spawn = [foo, bar])]
|
||||
fn idle(c: idle::Context) -> ! {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn foo(c: foo::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn bar(c: bar::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[task(priority = 2, spawn = [foo])]
|
||||
fn baz(c: baz::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[task(priority = 3, spawn = [bar])]
|
||||
fn quux(c: quux::Context) {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Вот как будет проходить анализ приоритетов:
|
||||
|
||||
- `idle` (prio = 0) и `baz` (prio = 2) соревнуются за конечный потребитель
|
||||
`foo_FQ`; это приводит к максимальному приоритету `2`.
|
||||
|
||||
- `idle` (prio = 0) и `quux` (prio = 3) соревнуются за конечный потребитель
|
||||
`bar_FQ`; это приводит к максимальному приоритету `3`.
|
||||
|
||||
- `idle` (prio = 0), `baz` (prio = 2) и `quux` (prio = 3) соревнуются за
|
||||
конечный производитель `RQ1`; это приводит к максимальному приоритету `3`
|
|
@ -1,372 +0,0 @@
|
|||
# Очередь таймера
|
||||
|
||||
Функциональность очередь таймера позволяет пользователю планировать задачи на запуск
|
||||
в опреленное время в будущем. Неудивительно, что эта функция также реализуется с помощью очереди:
|
||||
очередь приоритетов, где запланированные задачи сортируются в порядке аозрастания времени.
|
||||
Эта функция требует таймер, способный устанавливать прерывания истечения времени.
|
||||
Таймер используется для пуска прерывания, когда настает запланированное время задачи;
|
||||
в этот момент задача удаляется из очереди таймера и помещается в очередь готовности.
|
||||
|
||||
Давайте посмотрим, как это реализовано в коде. Рассмотрим следующую программу:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
// ..
|
||||
|
||||
#[task(capacity = 2, schedule = [foo])]
|
||||
fn foo(c: foo::Context, x: u32) {
|
||||
// запланировать задачу на повторный запуск через 1 млн. тактов
|
||||
c.schedule.foo(c.scheduled + Duration::cycles(1_000_000), x + 1).ok();
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn UART0();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `schedule`
|
||||
|
||||
Давайте сначала взглянем на интерфейс `schedule`.
|
||||
|
||||
``` rust
|
||||
mod foo {
|
||||
pub struct Schedule<'a> {
|
||||
priority: &'a Cell<u8>,
|
||||
}
|
||||
|
||||
impl<'a> Schedule<'a> {
|
||||
// `unsafe` и спрятано, потому что мы не хотим, чтобы пользовать сюда вмешивался
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn priority(&self) -> &Cell<u8> {
|
||||
self.priority
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod app {
|
||||
type Instant = <path::to::user::monotonic::timer as rtic::Monotonic>::Instant;
|
||||
|
||||
// все задачи, которые могут быть запланированы (`schedule`)
|
||||
enum T {
|
||||
foo,
|
||||
}
|
||||
|
||||
struct NotReady {
|
||||
index: u8,
|
||||
instant: Instant,
|
||||
task: T,
|
||||
}
|
||||
|
||||
// Очередь таймера - двоичная куча (min-heap) задач `NotReady`
|
||||
static mut TQ: TimerQueue<U2> = ..;
|
||||
const TQ_CEILING: u8 = 1;
|
||||
|
||||
static mut foo_FQ: Queue<u8, U2> = Queue::new();
|
||||
const foo_FQ_CEILING: u8 = 1;
|
||||
|
||||
static mut foo_INPUTS: [MaybeUninit<u32>; 2] =
|
||||
[MaybeUninit::uninit(), MaybeUninit::uninit()];
|
||||
|
||||
static mut foo_INSTANTS: [MaybeUninit<Instant>; 2] =
|
||||
[MaybeUninit::uninit(), MaybeUninit::uninit()];
|
||||
|
||||
impl<'a> foo::Schedule<'a> {
|
||||
fn foo(&self, instant: Instant, input: u32) -> Result<(), u32> {
|
||||
unsafe {
|
||||
let priority = self.priority();
|
||||
if let Some(index) = lock(priority, foo_FQ_CEILING, || {
|
||||
foo_FQ.split().1.dequeue()
|
||||
}) {
|
||||
// `index` - владеющий укачатель на ячейки в этих буферах
|
||||
foo_INSTANTS[index as usize].write(instant);
|
||||
foo_INPUTS[index as usize].write(input);
|
||||
|
||||
let nr = NotReady {
|
||||
index,
|
||||
instant,
|
||||
task: T::foo,
|
||||
};
|
||||
|
||||
lock(priority, TQ_CEILING, || {
|
||||
TQ.enqueue_unchecked(nr);
|
||||
});
|
||||
} else {
|
||||
// Не осталось места, чтобы разместить входные данные / instant
|
||||
Err(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Это очень похоже на реализацию `Spawn`. На самом деле одни и те же буфер
|
||||
`INPUTS` и список сободной памяти (`FQ`) используются совместно интерфейсами
|
||||
`spawn` и `schedule`. Главное отличие между ними в том, что `schedule` также
|
||||
размещает `Instant`, момент на который задача запланирована на запуск,
|
||||
в отдельном буфере (`foo_INSTANTS` в нашем случае).
|
||||
|
||||
`TimerQueue::enqueue_unchecked` делает немного больше работы, чем
|
||||
просто добавление записи в min-heap: он также вызывает прерывание
|
||||
системного таймера (`SysTick`), если новая запись оказывается первой в очереди.
|
||||
|
||||
## Системный таймер
|
||||
|
||||
Прерывание системного таймера (`SysTick`) заботится о двух вещах:
|
||||
передаче задач, которых становятся готовыми из очереди таймера в очередь готовности
|
||||
и установке прерывания истечения времени, когда наступит запланированное
|
||||
время следующей задачи.
|
||||
|
||||
Давайте посмотрим на соответствующий код.
|
||||
|
||||
``` rust
|
||||
mod app {
|
||||
#[no_mangle]
|
||||
fn SysTick() {
|
||||
const PRIORITY: u8 = 1;
|
||||
|
||||
let priority = &Cell::new(PRIORITY);
|
||||
while let Some(ready) = lock(priority, TQ_CEILING, || TQ.dequeue()) {
|
||||
match ready.task {
|
||||
T::foo => {
|
||||
// переместить эту задачу в очередь готовности `RQ1`
|
||||
lock(priority, RQ1_CEILING, || {
|
||||
RQ1.split().0.enqueue_unchecked(Ready {
|
||||
task: T1::foo,
|
||||
index: ready.index,
|
||||
})
|
||||
});
|
||||
|
||||
// вызвать диспетчер задач
|
||||
rtic::pend(Interrupt::UART0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Выглядит похоже на диспетчер задач, за исключением того, что
|
||||
вместо запуска готовой задачи, она лишь переносится в очередь готовности,
|
||||
что ведет к ее запуску с нужным приоритетом.
|
||||
|
||||
`TimerQueue::dequeue` установит новое прерывание истечения времени, если вернет
|
||||
`None`. Он сязан с `TimerQueue::enqueue_unchecked`, который вызывает это
|
||||
прерывание; на самом деле, `enqueue_unchecked` передает задачу установки
|
||||
нового прерывание истечения времени обработчику `SysTick`.
|
||||
|
||||
## Точность и диапазон `cyccnt::Instant` и `cyccnt::Duration`
|
||||
|
||||
RTIC предоставляет реализацию `Monotonic`, основанную на счетчике тактов `DWT` (Data Watchpoint and Trace). `Instant::now` возвращает снимок таймера; эти снимки
|
||||
DWT (`Instant`ы) используются для сортировки записей в очереди таймера.
|
||||
Счетчик тактов - 32-битный счетчик, работающий на частоте ядра.
|
||||
Этот счетчик обнуляется каждые `(1 << 32)` тактов; у нас нет прерывания,
|
||||
ассоциированног с этим счетчиком, поэтому ничего ужасного не случится,
|
||||
когда он пройдет оборот.
|
||||
|
||||
Чтобы упорядочить `Instant`ы в очереди, нам нужно сравнить 32-битные целые.
|
||||
Чтобы учесть обороты, мы используем разницу между двумя `Instant`ами, `a - b`,
|
||||
и рассматриваем результат как 32-битное знаковое целое.
|
||||
Если результат меньше нуля, значит `b` более поздний `Instant`;
|
||||
если результат больше нуля, значит `b` более ранний `Instant`.
|
||||
Это значит, что планирование задачи на `Instant`, который на `(1 << 31) - 1` тактов
|
||||
больше, чем запланированное время (`Instant`) первой (самой ранней) записи
|
||||
в очереди приведет к тому, что задача будет помещена в неправильное
|
||||
место в очереди. У нас есть несколько debug assertions в коде, чтобы
|
||||
предотвратить эту пользовательскую ошибку, но этого нельзя избежать,
|
||||
поскольку пользователь может написать
|
||||
`(instant + duration_a) + duration_b` и переполнить `Instant`.
|
||||
|
||||
Системный таймер, `SysTick` - 24-битный счетчик также работающий
|
||||
на частоте процессора. Когда следующая планируемая задача более, чем в
|
||||
`1 << 24` тактов в будущем, прерывание устанавливается на время в пределах
|
||||
`1 << 24` тактов. Этот процесс может происходить несколько раз, пока
|
||||
следующая запланированная задача не будет в диапазоне счетчика `SysTick`.
|
||||
|
||||
Подведем итог, оба `Instant` и `Duration` имеют разрешение 1 такт ядра, и `Duration` эффективно имеет (полуоткрытый) диапазон `0..(1 << 31)` (не включая максимум) тактов ядра.
|
||||
|
||||
## Вместительность очереди
|
||||
|
||||
Вместительность очереди таймера рассчитывается как сумма вместительностей
|
||||
всех планируемых (`schedule`) задач. Как и в случае очередей готовности,
|
||||
это значит, что как только мы затребовали пустую ячейку в буфере `INPUTS`,
|
||||
мы гарантируем, что способны передать задачу в очередь таймера;
|
||||
это позволяет нам опустить проверки времени выполнения.
|
||||
|
||||
## Приоритет системного таймера
|
||||
|
||||
Приориет системного таймера не может быть установлен пользователем;
|
||||
он выбирается фреймворком.
|
||||
Чтобы убедиться, что низкоприоритетные задачи не препятствуют
|
||||
запуску высокоприоритетных, мы выбираем приоритет системного таймера
|
||||
максимальным из всех планируемых задач.
|
||||
|
||||
Чтобы понять, почему это нужно, рассмотрим вариант, когда две ранее
|
||||
запланированные задачи с приоритетами `2` и `3` становятся готовыми в
|
||||
примерно одинаковое время, но низкоприоритетная задача перемещается
|
||||
в очередь готовности первой.
|
||||
Если бы приоритет системного таймера был, например, равен `1`,
|
||||
тогда после перемещения низкоприоритетной (`2`) задачи, это бы привело
|
||||
к завершению (из-за того, что приоритет выше приоритета системного таймера)
|
||||
ожидания выполнения высокоприоритетной задачи (`3`).
|
||||
Чтобы избежать такого сценария, системный таймер должен работать на
|
||||
приоритете, равном наивысшему из приоритетов планируемых задач;
|
||||
в этом примере это `3`.
|
||||
|
||||
## Анализ приоритетов
|
||||
|
||||
Очередь таймера - это ресурс, разделяемый всеми задачами, которые могут
|
||||
планировать (`schedule`) задачи и обработчиком `SysTick`.
|
||||
Также интерфейс `schedule` соперничает с интерфейсом `spawn`
|
||||
за списки свободной памяти. Все это должно уситываться в анализе приоритетов.
|
||||
|
||||
Чтобы проиллюстрировать, рассмотрим следующий пример:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = ..)]
|
||||
mod app {
|
||||
#[task(priority = 3, spawn = [baz])]
|
||||
fn foo(c: foo::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[task(priority = 2, schedule = [foo, baz])]
|
||||
fn bar(c: bar::Context) {
|
||||
// ..
|
||||
}
|
||||
|
||||
#[task(priority = 1)]
|
||||
fn baz(c: baz::Context) {
|
||||
// ..
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Анализ приоритетов происходил бы вот так:
|
||||
|
||||
- `foo` (prio = 3) и `baz` (prio = 1) планируемые задачи, поэтому
|
||||
`SysTick` должен работать на максимальном из этих двух приоритетов, т.е. `3`.
|
||||
|
||||
- `foo::Spawn` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за
|
||||
конечный потребитель `baz_FQ`; это приводит к максимальному приоритету `3`.
|
||||
|
||||
- `bar::Schedule` (prio = 2) имеет экслюзивный доступ к
|
||||
конечному потребителю `foo_FQ`; поэтому максимальный приоритет `foo_FQ` фактически `2`.
|
||||
|
||||
- `SysTick` (prio = 3) и `bar::Schedule` (prio = 2) соперничают за
|
||||
очередь таймера `TQ`; это приводит к максимальному приоритету `3`.
|
||||
|
||||
- `SysTick` (prio = 3) и `foo::Spawn` (prio = 3) оба имеют неблокируемый
|
||||
доступ к очереди готовности `RQ3`, что хранит записи `foo`;
|
||||
поэтому максимальный приоритет `RQ3` фактически `3`.
|
||||
|
||||
- `SysTick` имеет эксклюзивный доступ к очереди готовности `RQ1`,
|
||||
которая хранит записи `baz`; поэтому максимальный приоритет `RQ1` фактически `3`.
|
||||
|
||||
## Изменения в реализации `spawn`
|
||||
|
||||
Когда интерфейс `schedule` используется, реализация `spawn` немного
|
||||
изменяется, чтобы отслеживать baseline задач. Как можете видеть в
|
||||
реализации `schedule` есть буферы `INSTANTS`, используемые, чтобы
|
||||
хранить время, в которое задача была запланирована навыполнение;
|
||||
этот `Instant` читается диспетчером задач и передается в пользовательский
|
||||
код, как часть контекста задачи.
|
||||
|
||||
``` rust
|
||||
mod app {
|
||||
// ..
|
||||
|
||||
#[no_mangle]
|
||||
unsafe UART1() {
|
||||
const PRIORITY: u8 = 1;
|
||||
|
||||
let snapshot = basepri::read();
|
||||
|
||||
while let Some(ready) = RQ1.split().1.dequeue() {
|
||||
match ready.task {
|
||||
Task::baz => {
|
||||
let input = baz_INPUTS[ready.index as usize].read();
|
||||
// ADDED
|
||||
let instant = baz_INSTANTS[ready.index as usize].read();
|
||||
|
||||
baz_FQ.split().0.enqueue_unchecked(ready.index);
|
||||
|
||||
let priority = Cell::new(PRIORITY);
|
||||
// ИЗМЕНЕНО instant передан как часть контекста задачи
|
||||
baz(baz::Context::new(&priority, instant), input)
|
||||
}
|
||||
|
||||
Task::bar => {
|
||||
// выглядит также как ветка для `baz`
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// инвариант BASEPRI
|
||||
basepri::write(snapshot);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
И наоборот, реализации `spawn` нужно писать значение в буфер `INSTANTS`.
|
||||
Записанное значение располагается в структуре `Spawn` и это либо
|
||||
время `start` аппаратной задачи, либо время `scheduled` программной задачи.
|
||||
|
||||
``` rust
|
||||
mod foo {
|
||||
// ..
|
||||
|
||||
pub struct Spawn<'a> {
|
||||
priority: &'a Cell<u8>,
|
||||
// ADDED
|
||||
instant: Instant,
|
||||
}
|
||||
|
||||
impl<'a> Spawn<'a> {
|
||||
pub unsafe fn priority(&self) -> &Cell<u8> {
|
||||
&self.priority
|
||||
}
|
||||
|
||||
// ADDED
|
||||
pub unsafe fn instant(&self) -> Instant {
|
||||
self.instant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod app {
|
||||
impl<'a> foo::Spawn<'a> {
|
||||
/// Spawns the `baz` task
|
||||
pub fn baz(&self, message: u64) -> Result<(), u64> {
|
||||
unsafe {
|
||||
match lock(self.priority(), baz_FQ_CEILING, || {
|
||||
baz_FQ.split().1.dequeue()
|
||||
}) {
|
||||
Some(index) => {
|
||||
baz_INPUTS[index as usize].write(message);
|
||||
// ADDED
|
||||
baz_INSTANTS[index as usize].write(self.instant());
|
||||
|
||||
lock(self.priority(), RQ1_CEILING, || {
|
||||
RQ1.split().1.enqueue_unchecked(Ready {
|
||||
task: Task::foo,
|
||||
index,
|
||||
});
|
||||
});
|
||||
|
||||
rtic::pend(Interrupt::UART0);
|
||||
}
|
||||
|
||||
None => {
|
||||
// достигнута максимальная вместительность; неудачный вызов
|
||||
Err(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,4 +0,0 @@
|
|||
# Инструкции по миграции
|
||||
|
||||
В этом разделе описывается как мигрировать между различными версиями RTIC.
|
||||
Можно также использовать для сравнения версий.
|
|
@ -1,48 +0,0 @@
|
|||
# Миграция с RTFM на RTIC
|
||||
|
||||
В этом разделе описано, как обновить приложение, написанное на RTFM v0.5.x на RTIC той же версии.
|
||||
Это необходимо из-за переименования фреймворка в соответствии с [RFC #33].
|
||||
|
||||
**Примечание:** Между RTFM v0.5.3 и RTIC v0.5.3 нет разниц в коде, это исключительно изменение имен.
|
||||
|
||||
[RFC #33]: https://github.com/rtic-rs/rfcs/pull/33
|
||||
|
||||
## `Cargo.toml`
|
||||
|
||||
Во-первых, зависимость `cortex-m-rtfm` должна быть изменена на `cortex-m-rtic`.
|
||||
|
||||
``` toml
|
||||
[dependencies]
|
||||
# измените это
|
||||
cortex-m-rtfm = "0.5.3"
|
||||
|
||||
# на это
|
||||
cortex-m-rtic = "0.5.3"
|
||||
```
|
||||
|
||||
## Изменения в коде
|
||||
|
||||
Единственное изменение в коде, которое нужно сделать - поменять все ссылки на `rtfm`,
|
||||
чтобы они указывали на `rtic`:
|
||||
|
||||
``` rust
|
||||
//
|
||||
// Измените это
|
||||
//
|
||||
|
||||
#[rtfm::app(/* .. */, monotonic = rtfm::cyccnt::CYCCNT)]
|
||||
const APP: () = {
|
||||
// ...
|
||||
|
||||
};
|
||||
|
||||
//
|
||||
// На это
|
||||
//
|
||||
|
||||
#[rtic::app(/* .. */, monotonic = rtic::cyccnt::CYCCNT)]
|
||||
const APP: () = {
|
||||
// ...
|
||||
|
||||
};
|
||||
```
|
|
@ -1,230 +0,0 @@
|
|||
# Миграция с v0.4.x на v0.5.0
|
||||
|
||||
Этот раздел описывает как обновить программы, написанные на RTIC v0.4.x
|
||||
на версию v0.5.0 фреймворка.
|
||||
|
||||
## `Cargo.toml`
|
||||
|
||||
Во-первых, нужно обновить версию зависимости `cortex-m-rtic` до
|
||||
`"0.5.0"`. Опцию `timer-queue` нужно удалить.
|
||||
|
||||
``` toml
|
||||
[dependencies.cortex-m-rtic]
|
||||
# изменить это
|
||||
version = "0.4.3"
|
||||
|
||||
# на это
|
||||
version = "0.5.0"
|
||||
|
||||
# и удалить Cargo feature
|
||||
features = ["timer-queue"]
|
||||
# ^^^^^^^^^^^^^
|
||||
```
|
||||
|
||||
## Аргумент `Context`
|
||||
|
||||
Все функции внутри элемента `#[rtic::app]` должны принимать первым аргументом
|
||||
структуру `Context`. Этот тип `Context` будет содержать переменные, которые были магически
|
||||
инъецированы в область видимости функции версией v0.4.x фреймворка:
|
||||
`resources`, `spawn`, `schedule` -- эти переменные станут полями структуры `Context`.
|
||||
Каждая функция элемента `#[rtic::app]` получит отдельный тип `Context`.
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
// change this
|
||||
#[task(resources = [x], spawn = [a], schedule = [b])]
|
||||
fn foo() {
|
||||
resources.x.lock(|x| /* .. */);
|
||||
spawn.a(message);
|
||||
schedule.b(baseline);
|
||||
}
|
||||
|
||||
// into this
|
||||
#[task(resources = [x], spawn = [a], schedule = [b])]
|
||||
fn foo(mut cx: foo::Context) {
|
||||
// ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
cx.resources.x.lock(|x| /* .. */);
|
||||
// ^^^
|
||||
|
||||
cx.spawn.a(message);
|
||||
// ^^^
|
||||
|
||||
cx.schedule.b(message, baseline);
|
||||
// ^^^
|
||||
}
|
||||
|
||||
// change this
|
||||
#[init]
|
||||
fn init() {
|
||||
// ..
|
||||
}
|
||||
|
||||
// into this
|
||||
#[init]
|
||||
fn init(cx: init::Context) {
|
||||
// ^^^^^^^^^^^^^^^^^
|
||||
// ..
|
||||
}
|
||||
|
||||
// ..
|
||||
};
|
||||
```
|
||||
|
||||
## Ресурсы
|
||||
|
||||
Синтаксис, используемый, для определения ресурсов был изменен с переменных `static mut`
|
||||
на структуру `Resources`.
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
// измените это
|
||||
static mut X: u32 = 0;
|
||||
static mut Y: u32 = (); // поздний ресурс
|
||||
|
||||
// на это
|
||||
struct Resources {
|
||||
#[init(0)] // <- начальное значение
|
||||
X: u32, // ПРИМЕЧАНИЕ: мы предлагаем изменить стиль именования на `snake_case`
|
||||
|
||||
Y: u32, // поздний ресурс
|
||||
}
|
||||
|
||||
// ..
|
||||
};
|
||||
```
|
||||
|
||||
## Периферия устройства
|
||||
|
||||
Если ваша программа получала доступ к периферии в `#[init]` через
|
||||
переменну `device`, вам нужно будет добавить `peripherals = true` в атрибут
|
||||
`#[rtic::app]`, чтобы и дальше получать доступ к периферии через поле `device` структуры `init::Context`.
|
||||
|
||||
Измените это:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
#[init]
|
||||
fn init() {
|
||||
device.SOME_PERIPHERAL.write(something);
|
||||
}
|
||||
|
||||
// ..
|
||||
};
|
||||
```
|
||||
|
||||
На это:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */, peripherals = true)]
|
||||
// ^^^^^^^^^^^^^^^^^^
|
||||
const APP: () = {
|
||||
#[init]
|
||||
fn init(cx: init::Context) {
|
||||
// ^^^^^^^^^^^^^^^^^
|
||||
cx.device.SOME_PERIPHERAL.write(something);
|
||||
// ^^^
|
||||
}
|
||||
|
||||
// ..
|
||||
};
|
||||
```
|
||||
|
||||
## `#[interrupt]` и `#[exception]`
|
||||
|
||||
Атрибуты `#[interrupt]` и `#[exception]` были удалены. Чтобы определять аппаратные задачи в v0.5.x
|
||||
используте атрибут `#[task]` с аргументом `binds`.
|
||||
|
||||
Измените это:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
// аппаратные задачи
|
||||
#[exception]
|
||||
fn SVCall() { /* .. */ }
|
||||
|
||||
#[interrupt]
|
||||
fn UART0() { /* .. */ }
|
||||
|
||||
// программные задачи
|
||||
#[task]
|
||||
fn foo() { /* .. */ }
|
||||
|
||||
// ..
|
||||
};
|
||||
```
|
||||
|
||||
На это:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
#[task(binds = SVCall)]
|
||||
// ^^^^^^^^^^^^^^
|
||||
fn svcall(cx: svcall::Context) { /* .. */ }
|
||||
// ^^^^^^ мы предлагаем использовать `snake_case` имя здесь
|
||||
|
||||
#[task(binds = UART0)]
|
||||
// ^^^^^^^^^^^^^
|
||||
fn uart0(cx: uart0::Context) { /* .. */ }
|
||||
|
||||
#[task]
|
||||
fn foo(cx: foo::Context) { /* .. */ }
|
||||
|
||||
// ..
|
||||
};
|
||||
```
|
||||
|
||||
## `schedule`
|
||||
|
||||
Интерфейс `schedule` больше не требует cargo опции `timer-queue`, которая была удалена.
|
||||
Чтобы использовать интерфес `schedule`, нужно сначала определить
|
||||
монотонный тамер, который будет использоваьт среды выполнения, с помощью аргумента `monotonic`
|
||||
атрибута `#[rtic::app]`. Чтобы продолжить использовать счетчик циклов
|
||||
(CYCCNT) в качестве монотонного таймера, как было в версии v0.4.x, добавьте
|
||||
аргумент `monotonic = rtic::cyccnt::CYCCNT` в атрибут `#[rtic::app]`.
|
||||
|
||||
Также были добавлены типы `Duration` и `Instant`, а трейт `U32Ext` был перемещен в модуль `rtic::cyccnt`.
|
||||
Этот модуль доступен только на устройствах ARMv7-M+.
|
||||
Удаление `timer-queue` также возвращает периферию `DWT` в структуру периферии ядра,
|
||||
включить ее в работу можно внутри `init`.
|
||||
|
||||
Измените это:
|
||||
|
||||
``` rust
|
||||
use rtic::{Duration, Instant, U32Ext};
|
||||
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
#[task(schedule = [b])]
|
||||
fn a() {
|
||||
// ..
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
На это:
|
||||
|
||||
``` rust
|
||||
use rtic::cyccnt::{Duration, Instant, U32Ext};
|
||||
// ^^^^^^^^
|
||||
|
||||
#[rtic::app(/* .. */, monotonic = rtic::cyccnt::CYCCNT)]
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
const APP: () = {
|
||||
#[init]
|
||||
fn init(cx: init::Context) {
|
||||
cx.core.DWT.enable_cycle_counter();
|
||||
// опционально, настройка запуска DWT без подключенного отладчика
|
||||
cx.core.DCB.enable_trace();
|
||||
}
|
||||
#[task(schedule = [b])]
|
||||
fn a(cx: a::Context) {
|
||||
// ..
|
||||
}
|
||||
};
|
||||
```
|
|
@ -1,365 +0,0 @@
|
|||
# Миграция с v0.5.x на v1.0.0
|
||||
|
||||
Этот раздел описывает как обновиться с версии v0.5.x на v1.0.0 фреймворка RTIC.
|
||||
|
||||
## `Cargo.toml` - увеличьте версию
|
||||
|
||||
Измените версию `cortex-m-rtic` на `"0.6.0"`.
|
||||
|
||||
## `mod` вместо `const`
|
||||
|
||||
С поддержкой атрибутов над модулями трюк с `const APP` теперь не нужен.
|
||||
|
||||
Измените
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
[код здесь]
|
||||
};
|
||||
```
|
||||
|
||||
на
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
mod app {
|
||||
[код здесь]
|
||||
}
|
||||
```
|
||||
|
||||
Так как теперь используется обычный модуль Rust, это значит, что можно использовать
|
||||
обычный пользовательский код в этом модуле.
|
||||
Также это значит, что `use`-выражения для ресурсов, используемые
|
||||
в пользовательском коде должны быть перемещены внутрь `mod app`,
|
||||
либо на них можно сослаться с помощью `super`. Например, измените:
|
||||
|
||||
```rust
|
||||
use some_crate::some_func;
|
||||
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
fn func() {
|
||||
some_crate::some_func();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
на
|
||||
|
||||
```rust
|
||||
#[rtic::app(/* .. */)]
|
||||
mod app {
|
||||
use some_crate::some_func;
|
||||
|
||||
fn func() {
|
||||
some_crate::some_func();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
или
|
||||
|
||||
```rust
|
||||
use some_crate::some_func;
|
||||
|
||||
#[rtic::app(/* .. */)]
|
||||
mod app {
|
||||
fn func() {
|
||||
super::some_crate::some_func();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Перенос диспетчеров из `extern "C"` в аргументы app.
|
||||
|
||||
Измените
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */)]
|
||||
const APP: () = {
|
||||
[код здесь]
|
||||
|
||||
// RTIC требует, чтобы неиспользуемые прерывания были задекларированы в блоке extern, когда
|
||||
// используются программные задачи; эти свободные прерывания будут использованы для управления
|
||||
// программными задачами.
|
||||
extern "C" {
|
||||
fn SSI0();
|
||||
fn QEI0();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
на
|
||||
|
||||
``` rust
|
||||
#[rtic::app(/* .. */, dispatchers = [SSI0, QEI0])]
|
||||
mod app {
|
||||
[код здесь]
|
||||
}
|
||||
```
|
||||
|
||||
Это работает и для ОЗУ-функций, см. examples/ramfunc.rs
|
||||
|
||||
|
||||
## Структуры ресурсов - `#[shared]`, `#[local]`
|
||||
|
||||
Ранее ресурсы RTIC должны были размещаться в структуре с именем "Resources":
|
||||
|
||||
``` rust
|
||||
struct Resources {
|
||||
// Ресурсы определяются здесь
|
||||
}
|
||||
```
|
||||
|
||||
Начиная с RTIC v1.0.0 структуры ресурсов аннотируются подобно
|
||||
`#[task]`, `#[init]`, `#[idle]`: аттрибутами `#[shared]` и `#[local]`
|
||||
|
||||
``` rust
|
||||
#[shared]
|
||||
struct MySharedResources {
|
||||
// Разделяемые задачами ресурсы определены здесь
|
||||
}
|
||||
|
||||
#[local]
|
||||
struct MyLocalResources {
|
||||
// Ресурсы, определенные здесь нельзя передавать между задачами; каждый из них локальный для единственной задачи
|
||||
}
|
||||
```
|
||||
|
||||
Эти структуры разработчик может называть по своему желанию.
|
||||
|
||||
## `shared` и `local` аргументы в `#[task]`'ах
|
||||
|
||||
В v1.0.0 ресурсы разделены на `shared` ресурсы и `local` ресурсы.
|
||||
`#[task]`, `#[init]` и `#[idle]` больше не имеют аргумента `resources`;
|
||||
они должны использовать аргументы `shared` и `local`.
|
||||
|
||||
В v0.5.x:
|
||||
|
||||
``` rust
|
||||
struct Resources {
|
||||
local_to_b: i64,
|
||||
shared_by_a_and_b: i64,
|
||||
}
|
||||
|
||||
#[task(resources = [shared_by_a_and_b])]
|
||||
fn a(_: a::Context) {}
|
||||
|
||||
#[task(resources = [shared_by_a_and_b, local_to_b])]
|
||||
fn b(_: b::Context) {}
|
||||
```
|
||||
|
||||
В v1.0.0:
|
||||
|
||||
``` rust
|
||||
#[shared]
|
||||
struct Shared {
|
||||
shared_by_a_and_b: i64,
|
||||
}
|
||||
|
||||
#[local]
|
||||
struct Local {
|
||||
local_to_b: i64,
|
||||
}
|
||||
|
||||
#[task(shared = [shared_by_a_and_b])]
|
||||
fn a(_: a::Context) {}
|
||||
|
||||
#[task(shared = [shared_by_a_and_b], local = [local_to_b])]
|
||||
fn b(_: b::Context) {}
|
||||
```
|
||||
|
||||
## Симметричные блокировки
|
||||
|
||||
Теперь RTIC использует симметричные блокировки, это значит, что метод `lock` нужно использовать для
|
||||
всех доступов к `shared` ресурсам. Поскольку высокоприоритетные задачи имеют эксклюзивный доступ к ресурсу,
|
||||
в старом коде можно было следующее:
|
||||
|
||||
``` rust
|
||||
#[task(priority = 2, resources = [r])]
|
||||
fn foo(cx: foo::Context) {
|
||||
cx.resources.r = /* ... */;
|
||||
}
|
||||
|
||||
#[task(resources = [r])]
|
||||
fn bar(cx: bar::Context) {
|
||||
cx.resources.r.lock(|r| r = /* ... */);
|
||||
}
|
||||
```
|
||||
|
||||
С симметричными блокировками нужно вызывать `lock` для обоих задач:
|
||||
|
||||
``` rust
|
||||
#[task(priority = 2, shared = [r])]
|
||||
fn foo(cx: foo::Context) {
|
||||
cx.shared.r.lock(|r| r = /* ... */);
|
||||
}
|
||||
|
||||
#[task(shared = [r])]
|
||||
fn bar(cx: bar::Context) {
|
||||
cx.shared.r.lock(|r| r = /* ... */);
|
||||
}
|
||||
```
|
||||
|
||||
Заметьте, что скорость работы не изменяется благодаря оптимизациям LLVM, которые убирают ненужные блокировки.
|
||||
|
||||
## Неблокирующий доступ к ресурсам
|
||||
|
||||
В RTIC 0.5 к ресурсам разделяемым задачами, запускаемыми с одинаковым
|
||||
приоритетом, можно получить доступ *без* `lock` API.
|
||||
Это все еще возможно в 0.6: ресурс `#[shared]` должен быть аннотирован
|
||||
аттрибутом поля `#[lock_free]`.
|
||||
|
||||
v0.5 код:
|
||||
|
||||
``` rust
|
||||
struct Resources {
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
#[task(resources = [counter])]
|
||||
fn a(cx: a::Context) {
|
||||
*cx.resources.counter += 1;
|
||||
}
|
||||
|
||||
#[task(resources = [counter])]
|
||||
fn b(cx: b::Context) {
|
||||
*cx.resources.counter += 1;
|
||||
}
|
||||
```
|
||||
|
||||
v1.0 код:
|
||||
|
||||
``` rust
|
||||
#[shared]
|
||||
struct Shared {
|
||||
#[lock_free]
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
#[task(shared = [counter])]
|
||||
fn a(cx: a::Context) {
|
||||
*cx.shared.counter += 1;
|
||||
}
|
||||
|
||||
#[task(shared = [counter])]
|
||||
fn b(cx: b::Context) {
|
||||
*cx.shared.counter += 1;
|
||||
}
|
||||
```
|
||||
|
||||
## нет преобразования `static mut`
|
||||
|
||||
`static mut` переменные больше не преобразуются в безопасные `&'static mut` ссылки.
|
||||
Вместо этого синтаксиса используйте аргумент `local` в `#[init]`.
|
||||
|
||||
v0.5.x code:
|
||||
|
||||
``` rust
|
||||
#[init]
|
||||
fn init(_: init::Context) {
|
||||
static mut BUFFER: [u8; 1024] = [0; 1024];
|
||||
let buffer: &'static mut [u8; 1024] = BUFFER;
|
||||
}
|
||||
```
|
||||
|
||||
v1.0.0 code:
|
||||
|
||||
``` rust
|
||||
#[init(local = [
|
||||
buffer: [u8; 1024] = [0; 1024]
|
||||
// type ^^^^^^^^^^^^ ^^^^^^^^^ initial value
|
||||
])]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let buffer: &'static mut [u8; 1024] = cx.local.buffer;
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics())
|
||||
}
|
||||
```
|
||||
|
||||
## Init всегда возвращает поздние ресурсы
|
||||
|
||||
С целью сделать API более симметричным задача #[init] всегда возвращает поздние ресурсы.
|
||||
|
||||
С этого:
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = lm3s6965)]
|
||||
mod app {
|
||||
#[init]
|
||||
fn init(_: init::Context) {
|
||||
rtic::pend(Interrupt::UART0);
|
||||
}
|
||||
|
||||
// [еще код]
|
||||
}
|
||||
```
|
||||
|
||||
на это:
|
||||
|
||||
|
||||
``` rust
|
||||
#[rtic::app(device = lm3s6965)]
|
||||
mod app {
|
||||
#[shared]
|
||||
struct MySharedResources {}
|
||||
|
||||
#[local]
|
||||
struct MyLocalResources {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (MySharedResources, MyLocalResources, init::Monotonics) {
|
||||
rtic::pend(Interrupt::UART0);
|
||||
|
||||
(MySharedResources, MyLocalResources, init::Monotonics())
|
||||
}
|
||||
|
||||
// [more code]
|
||||
}
|
||||
```
|
||||
|
||||
## Вызов/планирование откуда угодно
|
||||
|
||||
С этой новой возвожностью, старый код, такой как:
|
||||
|
||||
|
||||
``` rust
|
||||
#[task(spawn = [bar])]
|
||||
fn foo(cx: foo::Context) {
|
||||
cx.spawn.bar().unwrap();
|
||||
}
|
||||
|
||||
#[task(schedule = [bar])]
|
||||
fn bar(cx: bar::Context) {
|
||||
cx.schedule.foo(/* ... */).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
Теперь будет выглядеть так:
|
||||
|
||||
``` rust
|
||||
#[task]
|
||||
fn foo(_c: foo::Context) {
|
||||
bar::spawn().unwrap();
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn bar(_c: bar::Context) {
|
||||
foo::schedule(/* ... */).unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
Заметьте, что атрибуты `spawn` и `schedule` больше не нужны.
|
||||
|
||||
---
|
||||
|
||||
## Дополнительно
|
||||
|
||||
### Внешние задачи
|
||||
|
||||
Как программные, так и аппаратные задачи теперь можно определять вне модуля `mod app`.
|
||||
Ранее это было возможно только путем реализации обертки, вызывающей реализацию задачи.
|
||||
|
||||
Смотреть примеры `examples/extern_binds.rs` и `examples/extern_spawn.rs`.
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<div align="center"><img width="300" height="300" src="RTIC.svg"></div>
|
||||
<div style="font-size: 6em; font-weight: bolder;" align="center">RTIC</div>
|
||||
|
||||
<h1 align="center">Real-Time Interrupt-driven Concurrency</h1>
|
||||
|
||||
<p align="center">Конкурентный фреймворк для создания систем реального времени</p>
|
||||
|
||||
# Введение
|
||||
|
||||
Эта книга содержит документацию пользовательского уровня о фреймворке Real-Time Interrupt-driven Concurrency
|
||||
(RTIC). Справочник по API можно найти [здесь](../../api/).
|
||||
|
||||
Также известен как Real-Time For the Masses.
|
||||
|
||||
<!--Оригинал данного руководства на [английском].-->
|
||||
|
||||
<!--[английском]: ../en/index.html-->
|
||||
|
||||
Это документация по RTIC версии v1.0.x; за документацией по другим версиям:
|
||||
|
||||
* v0.5.x [сюда](/0.5).
|
||||
* v0.4.x [сюда](/0.4).
|
||||
|
||||
{{#include ../../../README_ru.md:7:45}}
|
||||
|
||||
{{#include ../../../README_ru.md:51:}}
|
Loading…
Reference in a new issue