mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-25 03:19:34 +01:00
Merge #694
694: RTIC 2 r=AfoHT a=korken89 Co-authored-by: Emil Fresk <emil.fresk@gmail.com> Co-authored-by: Per Lindgren <per.lindgren@ltu.se>
This commit is contained in:
commit
7c7d6558f6
387 changed files with 12459 additions and 8517 deletions
579
.github/workflows/build.yml
vendored
579
.github/workflows/build.yml
vendored
|
@ -11,22 +11,28 @@ on:
|
|||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
DEV_VERSION: 2
|
||||
STABLE_VERSION: 1
|
||||
OLDSTABLE_VERSION: 0.5
|
||||
OLDOLDSTABLE_VERSION: 0.4
|
||||
|
||||
jobs:
|
||||
# Run cargo fmt --check, includes macros/
|
||||
style:
|
||||
name: style
|
||||
# Run cargo xtask format-check
|
||||
formatcheck:
|
||||
name: cargo fmt
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} +
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: cargo fmt --check
|
||||
run: cargo fmt --all -- --check
|
||||
- name: cargo xtask format-check
|
||||
run: cargo xtask format-check
|
||||
|
||||
# Compilation check
|
||||
check:
|
||||
|
@ -34,52 +40,74 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- thumbv7m-none-eabi
|
||||
- thumbv6m-none-eabi
|
||||
- x86_64-unknown-linux-gnu
|
||||
backend:
|
||||
- thumbv7
|
||||
- thumbv6
|
||||
- thumbv8-base
|
||||
- thumbv8-main
|
||||
toolchain:
|
||||
- stable
|
||||
- nightly
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust ${{ matrix.toolchain }}
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup override set ${{ matrix.toolchain }}
|
||||
|
||||
- name: Configure Rust target (${{ matrix.target }})
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
- name: Configure Rust target (v6, v7, v8.b v8.m)
|
||||
run: |
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add thumbv6m-none-eabi
|
||||
rustup target add thumbv8m.base-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabi
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} +
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: cargo check
|
||||
run: cargo check --target=${{ matrix.target }}
|
||||
- run: cargo xtask --verbose --backend ${{ matrix.backend }} check
|
||||
|
||||
# Clippy
|
||||
clippy:
|
||||
name: Cargo clippy
|
||||
name: clippy
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
backend:
|
||||
- thumbv7
|
||||
- thumbv6
|
||||
- thumbv8-base
|
||||
- thumbv8-main
|
||||
toolchain:
|
||||
- nightly
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
- name: Install Rust ${{ matrix.toolchain }}
|
||||
run: |
|
||||
rustup override set ${{ matrix.toolchain }}
|
||||
|
||||
- name: Configure Rust target (v6, v7, v8.b v8.m)
|
||||
run: |
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add thumbv6m-none-eabi
|
||||
rustup target add thumbv8m.base-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabi
|
||||
|
||||
- name: Add Rust component clippy
|
||||
run: rustup component add clippy
|
||||
|
||||
- name: Fail on warnings
|
||||
run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} +
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: cargo clippy
|
||||
run: cargo clippy
|
||||
- run: cargo xtask --verbose --backend ${{ matrix.backend }} clippy
|
||||
|
||||
# Verify all examples, checks
|
||||
checkexamples:
|
||||
|
@ -87,45 +115,50 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- thumbv7m-none-eabi
|
||||
- thumbv6m-none-eabi
|
||||
- thumbv8m.base-none-eabi
|
||||
- thumbv8m.main-none-eabi
|
||||
backend:
|
||||
- thumbv7
|
||||
- thumbv6
|
||||
- thumbv8-base
|
||||
- thumbv8-main
|
||||
toolchain:
|
||||
- stable
|
||||
- nightly
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust ${{ matrix.toolchain }}
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup override set ${{ matrix.toolchain }}
|
||||
|
||||
- name: Configure Rust target (${{ matrix.target }})
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Add Rust component llvm-tools-preview
|
||||
run: rustup component add llvm-tools-preview
|
||||
- name: Configure Rust target (v6, v7, v8.b v8.m)
|
||||
run: |
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add thumbv6m-none-eabi
|
||||
rustup target add thumbv8m.base-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabi
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Check the examples
|
||||
run: cargo check --examples --target=${{ matrix.target }}
|
||||
if: ${{ matrix.backend == 'thumbv8-base' }}
|
||||
run: cargo xtask --verbose --backend ${{ matrix.backend }} --exampleexclude pool example-check
|
||||
|
||||
- name: Check the examples
|
||||
if: ${{ matrix.backend != 'thumbv8-base' }}
|
||||
run: cargo xtask --verbose --backend ${{ matrix.backend }} example-check
|
||||
|
||||
# Verify the example output with run-pass tests
|
||||
testexamples:
|
||||
name: testexamples
|
||||
name: QEMU run
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- thumbv7m-none-eabi
|
||||
- thumbv6m-none-eabi
|
||||
backend:
|
||||
- thumbv7
|
||||
- thumbv6
|
||||
toolchain:
|
||||
- stable
|
||||
- nightly
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
@ -135,15 +168,19 @@ jobs:
|
|||
rustup set profile minimal
|
||||
rustup override set ${{ matrix.toolchain }}
|
||||
|
||||
- name: Configure Rust target (${{ matrix.target }})
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
- name: Configure Rust target (v6, v7)
|
||||
run: |
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add thumbv6m-none-eabi
|
||||
|
||||
- name: Add Rust component llvm-tools-preview
|
||||
run: rustup component add llvm-tools-preview
|
||||
|
||||
# Use precompiled binutils
|
||||
- name: cargo install cargo-binutils
|
||||
run: cargo install cargo-binutils
|
||||
- name: Install cargo-binutils
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-binutils
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
@ -154,63 +191,32 @@ jobs:
|
|||
sudo apt install -y qemu-system-arm
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
working-directory: ./rtic
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs
|
||||
|
||||
- name: Run-pass tests
|
||||
run: cargo xtask --target ${{ matrix.target }}
|
||||
|
||||
# Check the correctness of macros/ crate
|
||||
checkmacros:
|
||||
name: checkmacros
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-gnu
|
||||
toolchain:
|
||||
- stable
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust ${{ matrix.toolchain }}
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup override set ${{ matrix.toolchain }}
|
||||
|
||||
- name: Configure Rust target (${{ matrix.target }})
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
|
||||
- name: cargo check
|
||||
run: cargo check --manifest-path macros/Cargo.toml --target=${{ matrix.target }}
|
||||
|
||||
# Run the macros test-suite
|
||||
testmacros:
|
||||
name: testmacros
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
|
||||
- name: cargo check
|
||||
run: cargo test --manifest-path macros/Cargo.toml
|
||||
run: cargo xtask --verbose --backend ${{ matrix.backend }} qemu
|
||||
|
||||
# Run test suite
|
||||
tests:
|
||||
name: tests
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
backend:
|
||||
- thumbv7
|
||||
- thumbv6
|
||||
- thumbv8-base
|
||||
- thumbv8-main
|
||||
package:
|
||||
- rtic
|
||||
- rtic-common
|
||||
- rtic-macros
|
||||
- rtic-monotonics
|
||||
- rtic-sync
|
||||
- rtic-time
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
@ -218,122 +224,201 @@ jobs:
|
|||
- name: Cache Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Configure Rust target (v6, v7, v8.b v8.m)
|
||||
run: |
|
||||
rustup target add thumbv7m-none-eabi
|
||||
rustup target add thumbv6m-none-eabi
|
||||
rustup target add thumbv8m.base-none-eabi
|
||||
rustup target add thumbv8m.main-none-eabi
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} +
|
||||
|
||||
- name: Run cargo test
|
||||
run: cargo test --test tests
|
||||
run: cargo xtask --verbose --backend ${{ matrix.backend }} test ${{ matrix.package }}
|
||||
|
||||
# Build documentation, check links
|
||||
docs:
|
||||
name: docs
|
||||
name: build docs
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Cache pip installed linkchecker
|
||||
uses: actions/cache@v3
|
||||
- name: Install lychee
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
# Semantic version range syntax or exact version of a Python version
|
||||
python-version: '3.x'
|
||||
|
||||
# You can test your matrix by printing the current Python version
|
||||
- name: Display Python version
|
||||
run: python -c "import sys; print(sys.version)"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install git+https://github.com/linkchecker/linkchecker.git
|
||||
tool: lychee
|
||||
|
||||
- name: Remove cargo-config
|
||||
run: rm -f .cargo/config
|
||||
|
||||
- name: Fail on warnings
|
||||
run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs macros/src/lib.rs
|
||||
|
||||
- name: Build docs
|
||||
run: cargo doc
|
||||
# TODO: Any difference between backends?
|
||||
run: cargo doc --features thumbv7-backend
|
||||
|
||||
- name: Check links
|
||||
run: |
|
||||
td=$(mktemp -d)
|
||||
cp -r target/doc $td/api
|
||||
linkchecker $td/api/rtic/
|
||||
linkchecker $td/api/cortex_m_rtic_macros/
|
||||
echo rtic
|
||||
lychee --offline --format detailed $td/api/rtic/
|
||||
|
||||
echo rtic_common
|
||||
lychee --offline --format detailed $td/api/rtic_common/
|
||||
|
||||
echo rtic_macros
|
||||
lychee --offline --format detailed $td/api/rtic_macros/
|
||||
|
||||
echo rtic_monotonics
|
||||
lychee --offline --format detailed $td/api/rtic_monotonics/
|
||||
|
||||
echo rtic_sync
|
||||
lychee --offline --format detailed $td/api/rtic_sync/
|
||||
|
||||
echo rtic_time
|
||||
lychee --offline --format detailed $td/api/rtic_time/
|
||||
|
||||
|
||||
- name: Archive the API docs
|
||||
run: |
|
||||
cp -r target/doc apidocs
|
||||
tar -cf apidocs.tar apidocs
|
||||
|
||||
- name: Store the API docs
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: apidocs
|
||||
path: apidocs.tar
|
||||
|
||||
# Build the books
|
||||
mdbook:
|
||||
name: mdbook
|
||||
name: build mdbook
|
||||
needs: docs
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v4
|
||||
|
||||
- name: Install lychee
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
# Semantic version range syntax or exact version of a Python version
|
||||
python-version: '3.x'
|
||||
tool: lychee
|
||||
|
||||
# You can test your matrix by printing the current Python version
|
||||
- name: Display Python version
|
||||
run: python -c "import sys; print(sys.version)"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install git+https://github.com/linkchecker/linkchecker.git
|
||||
|
||||
- name: mdBook Action
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
- name: Install mdbook
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
mdbook-version: 'latest'
|
||||
tool: mdbook
|
||||
|
||||
- name: Install mdbook-mermaid
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: mdbook-mermaid
|
||||
|
||||
- name: Build book in English
|
||||
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
|
||||
run: cargo xtask book
|
||||
|
||||
- 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: Download built API docs
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: apidocs
|
||||
|
||||
- name: Extract the API docs
|
||||
run: tar -xf apidocs.tar
|
||||
|
||||
- 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
|
||||
cp -r apidocs/ $td/api
|
||||
|
||||
linkchecker $td/book/en/
|
||||
linkchecker $td/book/ru/
|
||||
lychee --offline --format detailed $td/book/en/
|
||||
mv $td bookroot
|
||||
|
||||
# Update stable branch
|
||||
#
|
||||
# This needs to run before book is built
|
||||
mergetostablebranch:
|
||||
name: If CI passes, merge master branch into release/vX
|
||||
- name: Archive the book + API docs
|
||||
run: |
|
||||
tar -cf book.tar bookroot
|
||||
|
||||
- name: Store the Book + API docs
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: book
|
||||
path: book.tar
|
||||
|
||||
mdbookold:
|
||||
name: build docs and mdbook for older releases
|
||||
needs: mergetostablebranch
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- style
|
||||
- check
|
||||
- clippy
|
||||
- checkexamples
|
||||
- testexamples
|
||||
- checkmacros
|
||||
- testmacros
|
||||
- tests
|
||||
- docs
|
||||
- mdbook
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Only run this when pushing to master branch
|
||||
if: github.ref == 'refs/heads/master'
|
||||
- name: Install mdbook
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: mdbook
|
||||
|
||||
- name: Install mdbook-mermaid
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: mdbook-mermaid
|
||||
|
||||
- name: Remove cargo-config
|
||||
run: rm -f .cargo/config
|
||||
|
||||
- name: Prepare output folder
|
||||
run: mkdir -p mdbookold
|
||||
|
||||
- name: Fetch and build books for older versions
|
||||
run: |
|
||||
# The latest stable must be the first element in the array
|
||||
vers=( "${{ env.STABLE_VERSION }}" "${{ env.OLDSTABLE_VERSION }}" )
|
||||
langs=( en )
|
||||
root=$(pwd)
|
||||
webroot=$(pwd)/mdbookold
|
||||
|
||||
for ver in ${vers[@]}; do
|
||||
|
||||
mkdir -p src/$ver
|
||||
src=$root/src/$ver
|
||||
curl -L https://github.com/rtic-rs/rtic/archive/release/v${ver}.tar.gz | tar xz --strip-components 1 -C $src
|
||||
|
||||
pushd $src
|
||||
rm -f .cargo/config
|
||||
cargo doc || cargo doc --features timer-queue
|
||||
mkdir -p $webroot/$ver/book
|
||||
cp -r target/doc $webroot/$ver/api
|
||||
|
||||
sed 's|URL|rtic/index.html|g' $root/redirect.html > $webroot/$ver/api/index.html
|
||||
popd
|
||||
|
||||
for lang in ${langs[@]}; do
|
||||
cargo xtask book build $src/book/$lang
|
||||
|
||||
cp -r $src/book/$lang/book $webroot/$ver/book/$lang
|
||||
cp LICENSE-* $webroot/$ver/book/$lang/
|
||||
done
|
||||
# using master branch redirect file
|
||||
sed 's|URL|book/en|g' $root/redirect.html > $webroot/$ver/index.html
|
||||
|
||||
rm -rf $src
|
||||
done
|
||||
|
||||
- name: Archive the old books
|
||||
run: |
|
||||
tar -cf mdbookold.tar mdbookold
|
||||
|
||||
- name: Store the old API docs
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mdbookold
|
||||
path: mdbookold.tar
|
||||
|
||||
parseversion:
|
||||
name: Parse the master branch RTIC version
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
@ -341,18 +426,37 @@ jobs:
|
|||
id: crateversionbranch
|
||||
# Parse metadata for version number, extract the Semver Major
|
||||
run: |
|
||||
VERSION=$(cargo metadata --format-version 1 --no-deps --offline | jq -r '.packages[] | select(.name =="cortex-m-rtic") | .version')
|
||||
VERSION=$(cargo metadata --format-version 1 --no-deps --offline | jq -r '.packages[] | select(.name =="rtic") | .version')
|
||||
VERSIONMAJOR=${VERSION%.*.*}
|
||||
echo "branch=release/v$VERSIONMAJOR" >> $GITHUB_ENV
|
||||
echo "versionmajor=$VERSIONMAJOR" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- uses: everlytic/branch-merge@1.1.5
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
source_ref: 'master'
|
||||
target_branch: ${{ env.branch }}
|
||||
commit_message_template: '[Bors] Merged {source_ref} into target {target_branch}'
|
||||
|
||||
# Update stable branch
|
||||
#
|
||||
# This is only valid when current stable resides in
|
||||
# master branch.
|
||||
# As master moves on to development, the work on the
|
||||
# stable version will happen in release/v"stable_version".
|
||||
# Thus, no need to push changes
|
||||
#
|
||||
# This needs to run before book is built, as bookbuilding fetches from the branch
|
||||
mergetostablebranch:
|
||||
name: Merge branch into release/vX when pushing to master
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- ci-success
|
||||
- parseversion
|
||||
|
||||
# Only run this when pushing to master branch
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Push to stable release branch if master contains stable version
|
||||
if: ${{ env.versionmajor == env.STABLE_VERSION }}
|
||||
run: git push -u origin ${{ env.branch }}
|
||||
|
||||
# Only runs when pushing to master branch
|
||||
# Bors run CI against staging branch,
|
||||
|
@ -362,65 +466,73 @@ jobs:
|
|||
name: deploy
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
mergetostablebranch
|
||||
- mergetostablebranch
|
||||
- docs
|
||||
- mdbookold
|
||||
- mdbook
|
||||
|
||||
# Only run this when pushing to master branch
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python 3.x
|
||||
uses: actions/setup-python@v4
|
||||
- name: Install lychee
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
# Semantic version range syntax or exact version of a Python version
|
||||
python-version: '3.x'
|
||||
tool: lychee
|
||||
|
||||
# You can test your matrix by printing the current Python version
|
||||
- name: Display Python version
|
||||
run: python -c "import sys; print(sys.version)"
|
||||
- name: Install mdbook-mermaid
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: mdbook-mermaid
|
||||
|
||||
- name: mdBook Action
|
||||
uses: peaceiris/actions-mdbook@v1
|
||||
with:
|
||||
mdbook-version: 'latest'
|
||||
|
||||
- name: Get crate version
|
||||
id: crateversion
|
||||
# Parse metadata for version number, extract the Semver Major
|
||||
run: |
|
||||
VERSION=$(cargo metadata --format-version 1 --no-deps --offline | jq -r '.packages[] | select(.name =="cortex-m-rtic") | .version')
|
||||
VERSIONMAJOR=${VERSION%.*.*}
|
||||
echo "branch=release/v$VERSIONMAJOR" >> $GITHUB_ENV
|
||||
echo "versionmajor=$VERSIONMAJOR" >> $GITHUB_ENV
|
||||
echo "version=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Remove cargo-config
|
||||
run: rm -f .cargo/config
|
||||
|
||||
- name: Build docs
|
||||
run: cargo doc
|
||||
- name: Download built dev-ver book and API docs
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: book
|
||||
|
||||
- name: Build books
|
||||
- name: Extract the dev-version book and API docs
|
||||
run: |
|
||||
tar -xf book.tar
|
||||
|
||||
- name: Download built old versions of books and API docs
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: mdbookold
|
||||
|
||||
- name: Extract the old version books and API docs
|
||||
run: |
|
||||
tar -xf mdbookold.tar
|
||||
|
||||
- name: Prepare 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" )
|
||||
vers=( "${{ env.STABLE_VERSION }}" "${{ env.OLDSTABLE_VERSION }}" )
|
||||
|
||||
# All releases start with "v"
|
||||
# followed by MAJOR.MINOR.PATCH, see semver.org
|
||||
# Store first in array as stable
|
||||
stable=${vers}
|
||||
crateversion={{ env.versionmajor }}
|
||||
crateversion=${{ env.versionmajor }}
|
||||
|
||||
echo "Latest stable version: $stable"
|
||||
echo "Current crate version: $crateversion"
|
||||
|
||||
# Create directories
|
||||
td=$(mktemp -d)
|
||||
mkdir -p $td/$devver/book/
|
||||
cp -r target/doc $td/$devver/api
|
||||
mkdir -p $td/$devver/
|
||||
cp -r bookroot/* $td/$devver/
|
||||
|
||||
# Redirect rtic.rs/meeting/index.html to hackmd
|
||||
mkdir $td/meeting
|
||||
|
@ -441,53 +553,50 @@ jobs:
|
|||
else
|
||||
# If the current stable and the "dev" version in master branch
|
||||
# share the same major version, redirect dev/ to stable book
|
||||
# This makes sense, preferable to have doc/book updates going live directly to rtic.rs
|
||||
sed 's|URL|rtic.rs/$stable/api/rtic|g' redirect.html > $td/$devver/api/index.html
|
||||
sed 's|URL|rtic.rs/$stable|g' redirect.html > $td/$devver/index.html
|
||||
fi
|
||||
|
||||
# Build books
|
||||
for lang in ${langs[@]}; do
|
||||
( cd book/$lang &&
|
||||
if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi
|
||||
)
|
||||
cp -r book/$lang/book $td/$devver/book/$lang
|
||||
cp LICENSE-* $td/$devver/book/$lang/
|
||||
done
|
||||
|
||||
# Build older versions, including stable
|
||||
root=$(pwd)
|
||||
for ver in ${vers[@]}; do
|
||||
prefix=${ver}
|
||||
|
||||
mkdir -p $td/$prefix/book
|
||||
src=$(mktemp -d)
|
||||
curl -L https://github.com/rtic-rs/cortex-m-rtic/archive/release/v${ver}.tar.gz | tar xz --strip-components 1 -C $src
|
||||
|
||||
pushd $src
|
||||
rm -f .cargo/config
|
||||
cargo doc || cargo doc --features timer-queue
|
||||
cp -r target/doc $td/$prefix/api
|
||||
sed 's|URL|rtic/index.html|g' $root/redirect.html > $td/$prefix/api/index.html
|
||||
for lang in ${langs[@]}; do
|
||||
( cd book/$lang &&
|
||||
if mdbook build |& tee /dev/tty | grep "\[ERROR\]"; then exit 1; else exit 0; fi
|
||||
)
|
||||
cp -r book/$lang/book $td/$prefix/book/$lang
|
||||
cp LICENSE-* $td/$prefix/book/$lang/
|
||||
done
|
||||
sed 's|URL|book/en|g' $root/redirect.html > $td/$prefix/index.html
|
||||
popd
|
||||
|
||||
rm -rf $src
|
||||
done
|
||||
# Package older versions, including stable
|
||||
|
||||
# Copy the stable book to the stable alias
|
||||
cp -r $td/$stable $td/stable
|
||||
cp -r mdbookold/${{ env.STABLE_VERSION }} $td/stable
|
||||
|
||||
# Copy the stable book to the webroot
|
||||
cp -r mdbookold/${{ env.STABLE_VERSION }} $td/
|
||||
# Copy the old stable book to the webroot
|
||||
cp -r mdbookold/${{ env.OLDSTABLE_VERSION }} $td/
|
||||
|
||||
# Forward CNAME file
|
||||
cp CNAME $td/
|
||||
mv $td/ bookstodeploy
|
||||
|
||||
- name: Archive the webroot
|
||||
run: |
|
||||
tar -cf bookstodeploy.tar bookstodeploy
|
||||
|
||||
- name: Store the books
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: bookstodeploy
|
||||
path: bookstodeploy.tar
|
||||
|
||||
ghapages:
|
||||
name: Publish rtic.rs
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- deploy
|
||||
steps:
|
||||
- name: Download books
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: bookstodeploy
|
||||
|
||||
- name: Extract the books
|
||||
run: |
|
||||
tar -xf bookstodeploy.tar
|
||||
|
||||
- name: Deploy to GH-pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
|
@ -503,13 +612,11 @@ jobs:
|
|||
name: ci
|
||||
if: github.event_name == 'push' && success()
|
||||
needs:
|
||||
- style
|
||||
- formatcheck
|
||||
- check
|
||||
- clippy
|
||||
- checkexamples
|
||||
- testexamples
|
||||
- checkmacros
|
||||
- testmacros
|
||||
- tests
|
||||
- docs
|
||||
- mdbook
|
||||
|
|
51
.github/workflows/changelog.yml
vendored
51
.github/workflows/changelog.yml
vendored
|
@ -18,11 +18,56 @@ jobs:
|
|||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check that changelog updated
|
||||
- name: Check which component is modified
|
||||
uses: dorny/paths-filter@v2
|
||||
id: changes
|
||||
with:
|
||||
filters: |
|
||||
rtic:
|
||||
- 'rtic/**'
|
||||
rtic-channel:
|
||||
- 'rtic-channel/**'
|
||||
rtic-time:
|
||||
- 'rtic-time/**'
|
||||
rtic-monotonics:
|
||||
- 'rtic-monotonics/**'
|
||||
|
||||
- name: Check that changelog updated (rtic)
|
||||
if: steps.changes.outputs.rtic == 'true'
|
||||
uses: dangoslen/changelog-enforcer@v3
|
||||
with:
|
||||
changeLogPath: CHANGELOG.md
|
||||
changeLogPath: ./rtic/CHANGELOG.md
|
||||
skipLabels: 'needs-changelog, skip-changelog'
|
||||
missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.'
|
||||
missingUpdateErrorMessage: 'Please add a changelog entry in the rtic/CHANGELOG.md file.'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check that changelog updated (rtic-channel)
|
||||
if: steps.changes.outputs.rtic-channel == 'true'
|
||||
uses: dangoslen/changelog-enforcer@v3
|
||||
with:
|
||||
changeLogPath: ./rtic-channel/CHANGELOG.md
|
||||
skipLabels: 'needs-changelog, skip-changelog'
|
||||
missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-channel/CHANGELOG.md file.'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check that changelog updated (rtic-time)
|
||||
if: steps.changes.outputs.rtic-time == 'true'
|
||||
uses: dangoslen/changelog-enforcer@v3
|
||||
with:
|
||||
changeLogPath: ./rtic-time/CHANGELOG.md
|
||||
skipLabels: 'needs-changelog, skip-changelog'
|
||||
missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-time/CHANGELOG.md file.'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check that changelog updated (rtic-monotonics)
|
||||
if: steps.changes.outputs.rtic-monotonics == 'true'
|
||||
uses: dangoslen/changelog-enforcer@v3
|
||||
with:
|
||||
changeLogPath: ./rtic-monotonics/CHANGELOG.md
|
||||
skipLabels: 'needs-changelog, skip-changelog'
|
||||
missingUpdateErrorMessage: 'Please add a changelog entry in the rtic-monotonics/CHANGELOG.md file.'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
6
.github/workflows/matrix-bot.yml
vendored
6
.github/workflows/matrix-bot.yml
vendored
|
@ -5,7 +5,7 @@ on:
|
|||
|
||||
jobs:
|
||||
new-pr:
|
||||
if: github.event.action == 'opened' && github.repository == 'rtic-rs/cortex-m-rtic'
|
||||
if: github.event.action == 'opened' && github.repository == 'rtic-rs/rtic'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
|
@ -18,7 +18,7 @@ jobs:
|
|||
server: "matrix.org"
|
||||
|
||||
merged-pr:
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.repository == 'rtic-rs/cortex-m-rtic'
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.repository == 'rtic-rs/rtic'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
|
@ -31,7 +31,7 @@ jobs:
|
|||
server: "matrix.org"
|
||||
|
||||
abandoned-pr:
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == false && github.repository == 'rtic-rs/cortex-m-rtic'
|
||||
if: github.event.action == 'closed' && github.event.pull_request.merged == false && github.repository == 'rtic-rs/rtic'
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
|
|
64
Cargo.toml
64
Cargo.toml
|
@ -1,57 +1,18 @@
|
|||
[package]
|
||||
authors = [
|
||||
"The Real-Time Interrupt-driven Concurrency developers",
|
||||
"Jorge Aparicio <jorge@japaric.io>",
|
||||
"Per Lindgren <per.lindgren@ltu.se>",
|
||||
[workspace]
|
||||
members = [
|
||||
"rtic",
|
||||
"rtic-sync",
|
||||
"rtic-common",
|
||||
"rtic-macros",
|
||||
"rtic-monotonics",
|
||||
"rtic-time",
|
||||
"xtask",
|
||||
]
|
||||
categories = ["concurrency", "embedded", "no-std"]
|
||||
description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems"
|
||||
documentation = "https://rtic.rs/"
|
||||
edition = "2021"
|
||||
keywords = ["arm", "cortex-m"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "cortex-m-rtic"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/rtic-rs/cortex-m-rtic"
|
||||
|
||||
version = "1.1.4"
|
||||
|
||||
[lib]
|
||||
name = "rtic"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = "0.7.0"
|
||||
cortex-m-rtic-macros = { path = "macros", version = "1.1.6" }
|
||||
rtic-monotonic = "1.0.0"
|
||||
rtic-core = "1.0.0"
|
||||
heapless = "0.7.7"
|
||||
bare-metal = "1.0.0"
|
||||
|
||||
[build-dependencies]
|
||||
version_check = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
lm3s6965 = "0.1.3"
|
||||
cortex-m-semihosting = "0.5.0"
|
||||
systick-monotonic = "1.0.0"
|
||||
|
||||
[dev-dependencies.panic-semihosting]
|
||||
features = ["exit"]
|
||||
version = "0.6.0"
|
||||
|
||||
[target.x86_64-unknown-linux-gnu.dev-dependencies]
|
||||
trybuild = "1"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"macros",
|
||||
"xtask",
|
||||
]
|
||||
|
||||
# do not optimize proc-macro deps or build scripts
|
||||
[profile.dev.build-override]
|
||||
codegen-units = 16
|
||||
|
@ -70,10 +31,3 @@ overflow-checks = false
|
|||
|
||||
[patch.crates-io]
|
||||
lm3s6965 = { git = "https://github.com/japaric/lm3s6965" }
|
||||
|
||||
[features]
|
||||
test-critical-section = ["cortex-m/critical-section-single-core"]
|
||||
|
||||
[[example]]
|
||||
name = "pool"
|
||||
required-features = ["test-critical-section"]
|
||||
|
|
24
README.md
24
README.md
|
@ -1,11 +1,11 @@
|
|||
# Real-Time Interrupt-driven Concurrency
|
||||
|
||||
> The hardware accelerated Rust RTOS
|
||||
|
||||
A concurrency framework for building real-time systems.
|
||||
|
||||
Formerly known as Real-Time For the Masses.
|
||||
|
||||
[![crates.io](https://img.shields.io/crates/v/cortex-m-rtic)](https://crates.io/crates/cortex-m-rtic)
|
||||
[![docs.rs](https://docs.rs/cortex-m-rtic/badge.svg)](https://docs.rs/cortex-m-rtic)
|
||||
[![crates.io](https://img.shields.io/crates/v/rtic)](https://crates.io/crates/rtic)
|
||||
[![docs.rs](https://docs.rs/rtic/badge.svg)](https://docs.rs/rtic)
|
||||
[![book](https://img.shields.io/badge/web-rtic.rs-red.svg?style=flat&label=book&colorB=d33847)](https://rtic.rs/)
|
||||
[![matrix](https://img.shields.io/matrix/rtic:matrix.org)](https://matrix.to/#/#rtic:matrix.org)
|
||||
[![Meeting notes](https://hackmd.io/badge.svg)](https://hackmd.io/@xmis9JvZT8Gvo9lOEKyZ4Q/SkBJKsjuH)
|
||||
|
@ -24,7 +24,7 @@ Formerly known as Real-Time For the Masses.
|
|||
|
||||
- Support for prioritization of tasks and, thus, **preemptive multitasking**.
|
||||
|
||||
- **Efficient and data race free memory sharing** through fine grained *priority
|
||||
- **Efficient and data race free memory sharing** through fine-grained *priority
|
||||
based* critical sections [^1].
|
||||
|
||||
- **Deadlock free execution** guaranteed at compile time. This is a stronger
|
||||
|
@ -44,16 +44,6 @@ Formerly known as Real-Time For the Masses.
|
|||
- This task model is amenable to known WCET (Worst Case Execution Time) analysis
|
||||
and scheduling analysis techniques.
|
||||
|
||||
### Crate `cortex-m` 0.6 vs 0.7 in RTIC 0.5.x
|
||||
|
||||
The crate `cortex-m` 0.7 started using trait `InterruptNumber` for interrupts instead of `Nr` from `bare-metal`. In order to preserve backwards compatibility, RTIC 0.5.x will keep using `cortex-m` 0.6 by default. `cortex-m` 0.7 can be enabled using the feature `cortex-m-7` and disabling default features:
|
||||
|
||||
```
|
||||
cortex-m-rtic = { version = "0.5.8", default-features = false, features = ["cortex-m-7"] }
|
||||
```
|
||||
|
||||
RTIC 1.0.0 already uses `cortex-m` 0.7 by default.
|
||||
|
||||
## [User documentation](https://rtic.rs)
|
||||
|
||||
Documentation for the [development version](https://rtic.rs/dev).
|
||||
|
@ -68,10 +58,10 @@ Documentation for the [development version](https://rtic.rs/dev).
|
|||
|
||||
Join us and talk about RTIC in the [Matrix room][matrix-room].
|
||||
|
||||
Weekly meeting notes can be found over at [HackMD][hackmd]
|
||||
Weekly meeting minutes can be found over at [RTIC HackMD][hackmd]
|
||||
|
||||
[matrix-room]: https://matrix.to/#/#rtic:matrix.org
|
||||
[hackmd]: https://hackmd.io/@xmis9JvZT8Gvo9lOEKyZ4Q/SkBJKsjuH
|
||||
[hackmd]: rtic.rs/meeting
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
2
book/.gitignore
vendored
Normal file
2
book/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Make sure that mdbook output in repo-root/book/<language>/book is ignored
|
||||
*/book
|
|
@ -1,9 +1,22 @@
|
|||
[book]
|
||||
authors = ["Jorge Aparicio, Per Lindgren and The Real-Time Interrupt-driven Concurrency developers"]
|
||||
authors = [
|
||||
"The Real-Time Interrupt-driven Concurrency developers",
|
||||
"Emil Fresk <emil.fresk@gmail.com>",
|
||||
"Henrik Tjäder <henrik@tjaders.com>",
|
||||
"Jorge Aparicio <jorge@japaric.io>",
|
||||
"Per Lindgren <per.lindgren@ltu.se>",
|
||||
]
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Real-Time Interrupt-driven Concurrency"
|
||||
|
||||
[build]
|
||||
create-missing = false
|
||||
|
||||
[preprocessor.mermaid]
|
||||
command = "mdbook-mermaid"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/rtic-rs/cortex-m-rtic"
|
||||
git-repository-url = "https://github.com/rtic-rs/rtic"
|
||||
git-repository-icon = "fa-github"
|
||||
additional-js = ["mermaid.min.js", "mermaid-init.js"]
|
||||
|
|
1
book/en/mermaid-init.js
Normal file
1
book/en/mermaid-init.js
Normal file
|
@ -0,0 +1 @@
|
|||
mermaid.initialize({startOnLoad:true});
|
1282
book/en/mermaid.min.js
vendored
Normal file
1282
book/en/mermaid.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -4,15 +4,13 @@
|
|||
|
||||
- [RTIC by example](./by-example.md)
|
||||
- [The `app`](./by-example/app.md)
|
||||
- [Hardware tasks & `pend`](./by-example/hardware_tasks.md)
|
||||
- [Software tasks & `spawn`](./by-example/software_tasks.md)
|
||||
- [Resources](./by-example/resources.md)
|
||||
- [The init task](./by-example/app_init.md)
|
||||
- [The idle task](./by-example/app_idle.md)
|
||||
- [Defining tasks](./by-example/app_task.md)
|
||||
- [Hardware tasks](./by-example/hardware_tasks.md)
|
||||
- [Software tasks & `spawn`](./by-example/software_tasks.md)
|
||||
- [Message passing & `capacity`](./by-example/message_passing.md)
|
||||
- [Task priorities](./by-example/app_priorities.md)
|
||||
- [Monotonic & `spawn_{at/after}`](./by-example/monotonic.md)
|
||||
- [Channel based communication](./by-example/channel.md)
|
||||
- [Delay and Timeout](./by-example/delay.md)
|
||||
- [Starting a new project](./by-example/starting_a_project.md)
|
||||
- [The minimal app](./by-example/app_minimal.md)
|
||||
- [Tips & Tricks](./by-example/tips.md)
|
||||
|
@ -21,13 +19,14 @@
|
|||
- [Avoid copies when message passing](./by-example/tips_indirection.md)
|
||||
- [`'static` super-powers](./by-example/tips_static_lifetimes.md)
|
||||
- [Inspecting generated code](./by-example/tips_view_code.md)
|
||||
- [Running tasks from RAM](./by-example/tips_from_ram.md)
|
||||
<!-- - [Running tasks from RAM](./by-example/tips_from_ram.md) -->
|
||||
<!-- - [`#[cfg(..)]` support](./by-example/tips.md) -->
|
||||
- [RTIC vs. the world](./rtic_vs.md)
|
||||
- [Awesome RTIC examples](./awesome_rtic.md)
|
||||
- [Migration Guides](./migration.md)
|
||||
<!-- - [Migration Guides](./migration.md)
|
||||
- [v0.5.x to v1.0.x](./migration/migration_v5.md)
|
||||
- [v0.4.x to v0.5.x](./migration/migration_v4.md)
|
||||
- [RTFM to RTIC](./migration/migration_rtic.md)
|
||||
- [RTFM to RTIC](./migration/migration_rtic.md) -->
|
||||
- [Under the hood](./internals.md)
|
||||
- [Cortex-M architectures](./internals/targets.md)
|
||||
<!--- [Interrupt configuration](./internals/interrupt-configuration.md)-->
|
||||
|
@ -38,3 +37,10 @@
|
|||
<!--- [Ceiling analysis](./internals/ceilings.md)-->
|
||||
<!--- [Software tasks](./internals/tasks.md)-->
|
||||
<!--- [Timer queue](./internals/timer-queue.md)-->
|
||||
|
||||
<!-- - [Defining tasks](./by-example/app_task.md) -->
|
||||
<!-- - [Software tasks & `spawn`](./by-example/software_tasks.md)
|
||||
- [Message passing & `capacity`](./by-example/message_passing.md)
|
||||
- [Task priorities](./by-example/app_priorities.md)
|
||||
- [Monotonic & `spawn_{at/after}`](./by-example/monotonic.md)
|
||||
-->
|
|
@ -1,14 +1,15 @@
|
|||
# RTIC by example
|
||||
|
||||
This part of the book introduces the Real-Time Interrupt-driven Concurrency (RTIC) framework
|
||||
to new users by walking them through examples of increasing complexity.
|
||||
This part of the book introduces the RTIC framework to new users by walking them through examples of increasing complexity.
|
||||
|
||||
All examples in this part of the book are accessible at the
|
||||
[GitHub repository][repoexamples].
|
||||
The examples are runnable on QEMU (emulating a Cortex M3 target),
|
||||
thus no special hardware required to follow along.
|
||||
|
||||
[repoexamples]: https://github.com/rtic-rs/cortex-m-rtic/tree/master/examples
|
||||
[repoexamples]: https://github.com/rtic-rs/rtic/tree/master/examples
|
||||
|
||||
## Running an example
|
||||
|
||||
To run the examples with QEMU you will need the `qemu-system-arm` program.
|
||||
Check [the embedded Rust book] for instructions on how to set up an
|
||||
|
@ -28,11 +29,12 @@ $ cargo run --target thumbv7m-none-eabi --example locals
|
|||
Yields this output:
|
||||
|
||||
``` console
|
||||
{{#include ../../../ci/expected/locals.run}}
|
||||
{{#include ../../../rtic/ci/expected/locals.run}}
|
||||
```
|
||||
|
||||
> **NOTE**: You can choose target device by passing a target
|
||||
> triple to cargo (e.g. `cargo run --example init --target thumbv7m-none-eabi`) or
|
||||
> configure a default target in `.cargo/config.toml`.
|
||||
>
|
||||
> For running the examples, we use a Cortex M3 emulated in QEMU, so the target is `thumbv7m-none-eabi`.
|
||||
> For running the examples, we (typically) use a Cortex M3 emulated in QEMU, so the target is `thumbv7m-none-eabi`.
|
||||
> Since the M3 architecture is backwards compatible to the M0/M0+ architecture, you may also use the `thumbv6m-none-eabi`, in case you want to inspect generated assembly code for the M0/M0+ architecture.
|
||||
|
|
|
@ -2,19 +2,25 @@
|
|||
|
||||
## Requirements on the `app` attribute
|
||||
|
||||
All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute
|
||||
only applies to a `mod`-item containing the RTIC application. The `app`
|
||||
attribute has a mandatory `device` argument that takes a *path* as a value.
|
||||
This must be a full path pointing to a
|
||||
*peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or
|
||||
newer.
|
||||
All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute only applies to a `mod`-item containing the RTIC application. The `app` attribute has a mandatory `device` argument that takes a *path* as a value. This must be a full path pointing to a *peripheral access crate* (PAC) generated using [`svd2rust`] **v0.14.x** or newer.
|
||||
|
||||
The `app` attribute will expand into a suitable entry point and thus replaces
|
||||
the use of the [`cortex_m_rt::entry`] attribute.
|
||||
The `app` attribute will expand into a suitable entry point and thus replaces the use of the [`cortex_m_rt::entry`] attribute.
|
||||
|
||||
[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
|
||||
[`app`]: ../../../api/rtic_macros/attr.app.html
|
||||
[`svd2rust`]: https://crates.io/crates/svd2rust
|
||||
[`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html
|
||||
[`cortex_m_rt::entry`]: https://docs.rs/cortex-m-rt-macros/latest/cortex_m_rt_macros/attr.entry.html
|
||||
|
||||
## Structure and zero-cost concurrency
|
||||
|
||||
An RTIC `app` is an executable system model for single-core applications, declaring a set of `local` and `shared` resources operated on by a set of `init`, `idle`, *hardware* and *software* tasks. In short the `init` task runs before any other task returning the set of `local` and `shared` resources. Tasks run preemptively based on their associated static priority, `idle` has the lowest priority (and can be used for background work, and/or to put the system to sleep until woken by some event). Hardware tasks are bound to underlying hardware interrupts, while software tasks are scheduled by asynchronous executors (one for each software task priority).
|
||||
|
||||
At compile time the task/resource model is analyzed under the Stack Resource Policy (SRP) and executable code generated with the following outstanding properties:
|
||||
|
||||
- guaranteed race-free resource access and deadlock-free execution on a single-shared stack
|
||||
- hardware task scheduling is performed directly by the hardware, and
|
||||
- software task scheduling is performed by auto generated async executors tailored to the application.
|
||||
|
||||
Overall, the generated code infers no additional overhead in comparison to a hand-written implementation, thus in Rust terms RTIC offers a zero-cost abstraction to concurrency.
|
||||
|
||||
## An RTIC application example
|
||||
|
||||
|
@ -22,5 +28,5 @@ To give a flavour of RTIC, the following example contains commonly used features
|
|||
In the following sections we will go through each feature in detail.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/common.rs}}
|
||||
{{#include ../../../../rtic/examples/common.rs}}
|
||||
```
|
||||
|
|
|
@ -1,52 +1,53 @@
|
|||
# The background task `#[idle]`
|
||||
|
||||
A function marked with the `idle` attribute can optionally appear in the
|
||||
module. This becomes the special *idle task* and must have signature
|
||||
`fn(idle::Context) -> !`.
|
||||
A function marked with the `idle` attribute can optionally appear in the module. This becomes the special *idle task* and must have signature `fn(idle::Context) -> !`.
|
||||
|
||||
When present, the runtime will execute the `idle` task after `init`. Unlike
|
||||
`init`, `idle` will run *with interrupts enabled* and must never return,
|
||||
as the `-> !` function signature indicates.
|
||||
When present, the runtime will execute the `idle` task after `init`. Unlike `init`, `idle` will run *with interrupts enabled* and must never return, as the `-> !` function signature indicates.
|
||||
[The Rust type `!` means “never”][nevertype].
|
||||
|
||||
[nevertype]: https://doc.rust-lang.org/core/primitive.never.html
|
||||
|
||||
Like in `init`, locally declared resources will have `'static` lifetimes that
|
||||
are safe to access.
|
||||
Like in `init`, locally declared resources will have `'static` lifetimes that are safe to access.
|
||||
|
||||
The example below shows that `idle` runs after `init`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/idle.rs}}
|
||||
{{#include ../../../../rtic/examples/idle.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example idle
|
||||
{{#include ../../../../ci/expected/idle.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/idle.run}}
|
||||
```
|
||||
|
||||
By default, the RTIC `idle` task does not try to optimize for any specific targets.
|
||||
|
||||
A common useful optimization is to enable the [SLEEPONEXIT] and allow the MCU
|
||||
to enter sleep when reaching `idle`.
|
||||
A common useful optimization is to enable the [SLEEPONEXIT] and allow the MCU to enter sleep when reaching `idle`.
|
||||
|
||||
>**Caution** some hardware unless configured disables the debug unit during sleep mode.
|
||||
>**Caution**: some hardware unless configured disables the debug unit during sleep mode.
|
||||
>
|
||||
>Consult your hardware specific documentation as this is outside the scope of RTIC.
|
||||
|
||||
The following example shows how to enable sleep by setting the
|
||||
[`SLEEPONEXIT`][SLEEPONEXIT] and providing a custom `idle` task replacing the
|
||||
default [`nop()`][NOP] with [`wfi()`][WFI].
|
||||
[`SLEEPONEXIT`][SLEEPONEXIT] and providing a custom `idle` task replacing the default [`nop()`][NOP] with [`wfi()`][WFI].
|
||||
|
||||
[SLEEPONEXIT]: https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
|
||||
[WFI]: https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/WFI
|
||||
[NOP]: https://developer.arm.com/documentation/dui0662/b/The-Cortex-M0--Instruction-Set/Miscellaneous-instructions/NOP
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/idle-wfi.rs}}
|
||||
{{#include ../../../../rtic/examples/idle-wfi.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example idle-wfi
|
||||
{{#include ../../../../ci/expected/idle-wfi.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/idle-wfi.run}}
|
||||
```
|
||||
|
||||
> **Notice**: The `idle` task cannot be used together with *software* tasks running at priority zero. The reason is that `idle` is running as a non-returning Rust function at priority zero. Thus there would be no way for an executor at priority zero to give control to *software* tasks at the same priority.
|
||||
|
|
|
@ -1,35 +1,31 @@
|
|||
# App initialization and the `#[init]` task
|
||||
|
||||
An RTIC application requires an `init` task setting up the system. The corresponding `init` function must have the
|
||||
signature `fn(init::Context) -> (Shared, Local, init::Monotonics)`, where `Shared` and `Local` are resource
|
||||
structures defined by the user.
|
||||
signature `fn(init::Context) -> (Shared, Local)`, where `Shared` and `Local` are resource structures defined by the user.
|
||||
|
||||
The `init` task executes after system reset, [after an optionally defined `pre-init` code section][pre-init] and an always occurring internal RTIC
|
||||
initialization.
|
||||
The `init` task executes after system reset, [after an optionally defined `pre-init` code section][pre-init] and an always occurring internal RTIC initialization. [pre-init]: https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.pre_init.html
|
||||
|
||||
[pre-init]: https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.pre_init.html
|
||||
|
||||
The `init` and optional `pre-init` tasks runs *with interrupts disabled* and have exclusive access to Cortex-M (the
|
||||
`bare_metal::CriticalSection` token is available as `cs`).
|
||||
The `init` and optional `pre-init` tasks runs *with interrupts disabled* and have exclusive access to Cortex-M (the `bare_metal::CriticalSection` token is available as `cs`).
|
||||
|
||||
Device specific peripherals are available through the `core` and `device` fields of `init::Context`.
|
||||
|
||||
## Example
|
||||
|
||||
The example below shows the types of the `core`, `device` and `cs` fields, and showcases the use of a `local`
|
||||
variable with `'static` lifetime.
|
||||
Such variables can be delegated from the `init` task to other tasks of the RTIC application.
|
||||
The example below shows the types of the `core`, `device` and `cs` fields, and showcases the use of a `local` variable with `'static` lifetime. Such variables can be delegated from the `init` task to other tasks of the RTIC application.
|
||||
|
||||
The `device` field is only available when the `peripherals` argument is set to the default value `true`.
|
||||
In the rare case you want to implement an ultra-slim application you can explicitly set `peripherals` to `false`.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/init.rs}}
|
||||
{{#include ../../../../rtic/examples/init.rs}}
|
||||
```
|
||||
|
||||
Running the example will print `init` to the console and then exit the QEMU process.
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example init
|
||||
{{#include ../../../../ci/expected/init.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/init.run}}
|
||||
```
|
||||
|
|
|
@ -3,5 +3,22 @@
|
|||
This is the smallest possible RTIC application:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/smallest.rs}}
|
||||
{{#include ../../../../rtic/examples/smallest.rs}}
|
||||
```
|
||||
|
||||
RTIC is designed with resource efficiency in mind. RTIC itself does not rely on any dynamic memory allocation, thus RAM requirement is dependent only on the application. The flash memory footprint is below 1kB including the interrupt vector table.
|
||||
|
||||
For a minimal example you can expect something like:
|
||||
``` console
|
||||
$ cargo size --example smallest --target thumbv7m-none-eabi --release
|
||||
```
|
||||
|
||||
``` console
|
||||
Finished release [optimized] target(s) in 0.07s
|
||||
text data bss dec hex filename
|
||||
924 0 0 924 39c smallest
|
||||
```
|
||||
|
||||
<!-- ---
|
||||
|
||||
Technically, RTIC will generate a statically allocated future for each *software* task (holding the execution context, including the `Context` struct and stack allocated variables). Futures associated to the same static priority will share an asynchronous stack during execution. -->
|
||||
|
|
|
@ -4,23 +4,18 @@
|
|||
|
||||
The `priority` argument declares the static priority of each `task`.
|
||||
|
||||
For Cortex-M, tasks can have priorities in the range `1..=(1 << NVIC_PRIO_BITS)`
|
||||
where `NVIC_PRIO_BITS` is a constant defined in the `device` crate.
|
||||
For Cortex-M, tasks can have priorities in the range `1..=(1 << NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device` crate.
|
||||
|
||||
Omitting the `priority` argument the task priority defaults to `1`.
|
||||
The `idle` task has a non-configurable static priority of `0`, the lowest priority.
|
||||
Omitting the `priority` argument the task priority defaults to `1`. The `idle` task has a non-configurable static priority of `0`, the lowest priority.
|
||||
|
||||
> A higher number means a higher priority in RTIC, which is the opposite from what
|
||||
> Cortex-M does in the NVIC peripheral.
|
||||
> Explicitly, this means that number `10` has a **higher** priority than number `9`.
|
||||
|
||||
The highest static priority task takes precedence when more than one
|
||||
task are ready to execute.
|
||||
The highest static priority task takes precedence when more than one task are ready to execute.
|
||||
|
||||
The following scenario demonstrates task prioritization:
|
||||
Spawning a higher priority task A during execution of a lower priority task B suspends
|
||||
task B. Task A has higher priority thus preempting task B which gets suspended
|
||||
until task A completes execution. Thus, when task A completes task B resumes execution.
|
||||
Spawning a higher priority task A during execution of a lower priority task B suspends task B. Task A has higher priority thus preempting task B which gets suspended until task A completes execution. Thus, when task A completes task B resumes execution.
|
||||
|
||||
```text
|
||||
Task Priority
|
||||
|
@ -39,23 +34,19 @@ Task Priority
|
|||
The following example showcases the priority based scheduling of tasks:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/preempt.rs}}
|
||||
{{#include ../../../../rtic/examples/preempt.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example preempt
|
||||
{{#include ../../../../ci/expected/preempt.run}}
|
||||
{{#include ../../../../rtic/ci/expected/preempt.run}}
|
||||
```
|
||||
|
||||
Note that the task `bar` does *not* preempt task `baz` because its priority
|
||||
is the *same* as `baz`'s. The higher priority task `bar` runs before `foo`
|
||||
when `baz`returns. When `bar` returns `foo` can resume.
|
||||
Note that the task `bar` does *not* preempt task `baz` because its priority is the *same* as `baz`'s. The higher priority task `bar` runs before `foo` when `baz`returns. When `bar` returns `foo` can resume.
|
||||
|
||||
One more note about priorities: choosing a priority higher than what the device
|
||||
supports will result in a compilation error.
|
||||
One more note about priorities: choosing a priority higher than what the device supports will result in a compilation error. The error is cryptic due to limitations in the Rust language, if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like:
|
||||
|
||||
The error is cryptic due to limitations in the Rust language
|
||||
if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like:
|
||||
The error is cryptic due to limitations in the Rust language if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks like:
|
||||
|
||||
```text
|
||||
error[E0080]: evaluation of constant value failed
|
||||
|
@ -68,5 +59,4 @@ if `priority = 9` for task `uart0_interrupt` in `example/common.rs` this looks l
|
|||
|
||||
```
|
||||
|
||||
The error message incorrectly points to the starting point of the macro, but at least the
|
||||
value subtracted (in this case 9) will suggest which task causes the error.
|
||||
The error message incorrectly points to the starting point of the macro, but at least the value subtracted (in this case 9) will suggest which task causes the error.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Should probably be removed -->
|
||||
|
||||
# Defining tasks with `#[task]`
|
||||
|
||||
Tasks, defined with `#[task]`, are the main mechanism of getting work done in RTIC.
|
||||
|
|
126
book/en/src/by-example/channel.md
Normal file
126
book/en/src/by-example/channel.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
# Communication over channels.
|
||||
|
||||
Channels can be used to communicate data between running *software* tasks. The channel is essentially a wait queue, allowing tasks with multiple producers and a single receiver. A channel is constructed in the `init` task and backed by statically allocated memory. Send and receive endpoints are distributed to *software* tasks:
|
||||
|
||||
``` rust
|
||||
...
|
||||
const CAPACITY: usize = 5;
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local) {
|
||||
let (s, r) = make_channel!(u32, CAPACITY);
|
||||
receiver::spawn(r).unwrap();
|
||||
sender1::spawn(s.clone()).unwrap();
|
||||
sender2::spawn(s.clone()).unwrap();
|
||||
...
|
||||
```
|
||||
|
||||
In this case the channel holds data of `u32` type with a capacity of 5 elements.
|
||||
|
||||
## Sending data
|
||||
|
||||
The `send` method post a message on the channel as shown below:
|
||||
|
||||
``` rust
|
||||
#[task]
|
||||
async fn sender1(_c: sender1::Context, mut sender: Sender<'static, u32, CAPACITY>) {
|
||||
hprintln!("Sender 1 sending: 1");
|
||||
sender.send(1).await.unwrap();
|
||||
}
|
||||
```
|
||||
|
||||
## Receiving data
|
||||
|
||||
The receiver can `await` incoming messages:
|
||||
|
||||
``` rust
|
||||
#[task]
|
||||
async fn receiver(_c: receiver::Context, mut receiver: Receiver<'static, u32, CAPACITY>) {
|
||||
while let Ok(val) = receiver.recv().await {
|
||||
hprintln!("Receiver got: {}", val);
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Channels are implemented using a small (global) *Critical Section* (CS) for protection against race-conditions. The user must provide an CS implementation. Compiling the examples given the `--features test-critical-section` gives one possible implementation.
|
||||
|
||||
For a complete example:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/async-channel.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example async-channel --features test-critical-section
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/async-channel.run}}
|
||||
```
|
||||
|
||||
Also sender endpoint can be awaited. In case the channel capacity has not yet been reached, `await`-ing the sender can progress immediately, while in the case the capacity is reached, the sender is blocked until there is free space in the queue. In this way data is never lost.
|
||||
|
||||
In the following example the `CAPACITY` has been reduced to 1, forcing sender tasks to wait until the data in the channel has been received.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/async-channel-done.rs}}
|
||||
```
|
||||
|
||||
Looking at the output, we find that `Sender 2` will wait until the data sent by `Sender 1` as been received.
|
||||
|
||||
> **NOTICE** *Software* tasks at the same priority are executed asynchronously to each other, thus **NO** strict order can be assumed. (The presented order here applies only to the current implementation, and may change between RTIC framework releases.)
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example async-channel-done --features test-critical-section
|
||||
{{#include ../../../../rtic/ci/expected/async-channel-done.run}}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
In case all senders have been dropped `await`-ing on an empty receiver channel results in an error. This allows to gracefully implement different types of shutdown operations.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/async-channel-no-sender.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example async-channel-no-sender --features test-critical-section
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/async-channel-no-sender.run}}
|
||||
```
|
||||
|
||||
Similarly, `await`-ing on a send channel results in an error in case the receiver has been dropped. This allows to gracefully implement application level error handling.
|
||||
|
||||
The resulting error returns the data back to the sender, allowing the sender to take appropriate action (e.g., storing the data to later retry sending it).
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/async-channel-no-receiver.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example async-channel-no-receiver --features test-critical-section
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/async-channel-no-receiver.run}}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Try API
|
||||
|
||||
In cases you wish the sender to proceed even in case the channel is full. To that end, a `try_send` API is provided.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/async-channel-try.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example async-channel-try --features test-critical-section
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/async-channel-try.run}}
|
||||
```
|
127
book/en/src/by-example/delay.md
Normal file
127
book/en/src/by-example/delay.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
# Tasks with delay
|
||||
|
||||
A convenient way to express *miniminal* timing requirements is by means of delaying progression.
|
||||
|
||||
This can be achieved by instantiating a monotonic timer:
|
||||
|
||||
``` rust
|
||||
...
|
||||
rtic_monotonics::make_systick_handler!();
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local) {
|
||||
hprintln!("init");
|
||||
|
||||
Systick::start(cx.core.SYST, 12_000_000);
|
||||
...
|
||||
```
|
||||
|
||||
A *software* task can `await` the delay to expire:
|
||||
|
||||
``` rust
|
||||
#[task]
|
||||
async fn foo(_cx: foo::Context) {
|
||||
...
|
||||
Systick::delay(100.millis()).await;
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Technically, the timer queue is implemented as a list based priority queue, where list-nodes are statically allocated as part of the underlying task `Future`. Thus, the timer queue is infallible at run-time (its size and allocation is determined at compile time).
|
||||
|
||||
Similarly the channels implementation, the timer-queue implementation relies on a global *Critical Section* (CS) for race protection. For the examples a CS implementation is provided by adding `--features test-critical-section` to the build options.
|
||||
|
||||
For a complete example:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/async-delay.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example async-delay --features test-critical-section
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/async-delay.run}}
|
||||
```
|
||||
|
||||
## Timeout
|
||||
|
||||
Rust `Futures` (underlying Rust `async`/`await`) are composable. This makes it possible to `select` in between `Futures` that have completed.
|
||||
|
||||
A common use case is transactions with associated timeout. In the examples shown below, we introduce a fake HAL device which performs some transaction. We have modelled the time it takes based on the input parameter (`n`) as `350ms + n * 100ms)`.
|
||||
|
||||
Using the `select_biased` macro from the `futures` crate it may look like this:
|
||||
|
||||
``` rust
|
||||
// Call hal with short relative timeout using `select_biased`
|
||||
select_biased! {
|
||||
v = hal_get(1).fuse() => hprintln!("hal returned {}", v),
|
||||
_ = Systick::delay(200.millis()).fuse() => hprintln!("timeout", ), // this will finish first
|
||||
}
|
||||
```
|
||||
|
||||
Assuming the `hal_get` will take 450ms to finish, a short timeout of 200ms will expire.
|
||||
|
||||
``` rust
|
||||
// Call hal with long relative timeout using `select_biased`
|
||||
select_biased! {
|
||||
v = hal_get(1).fuse() => hprintln!("hal returned {}", v), // hal finish first
|
||||
_ = Systick::delay(1000.millis()).fuse() => hprintln!("timeout", ),
|
||||
}
|
||||
```
|
||||
|
||||
By extending the timeout to 1000ms, the `hal_get` will finish first.
|
||||
|
||||
Using `select_biased` any number of futures can be combined, so its very powerful. However, as the timeout pattern is frequently used, it is directly supported by the RTIC [rtc-monotonics] and [rtic-time] crates. The second example from above using `timeout_after`:
|
||||
|
||||
``` rust
|
||||
// Call hal with long relative timeout using monotonic `timeout_after`
|
||||
match Systick::timeout_after(1000.millis(), hal_get(1)).await {
|
||||
Ok(v) => hprintln!("hal returned {}", v),
|
||||
_ => hprintln!("timeout"),
|
||||
}
|
||||
```
|
||||
|
||||
In cases you want exact control over time without drift. For this purpose we can use exact points in time using `Instance`, and spans of time using `Duration`. Operations on the `Instance` and `Duration` types are given by the [fugit] crate.
|
||||
|
||||
[fugit]: https://crates.io/crates/fugit
|
||||
|
||||
``` rust
|
||||
// get the current time instance
|
||||
let mut instant = Systick::now();
|
||||
|
||||
// do this 3 times
|
||||
for n in 0..3 {
|
||||
// absolute point in time without drift
|
||||
instant += 1000.millis();
|
||||
Systick::delay_until(instant).await;
|
||||
|
||||
// absolute point it time for timeout
|
||||
let timeout = instant + 500.millis();
|
||||
hprintln!("now is {:?}, timeout at {:?}", Systick::now(), timeout);
|
||||
|
||||
match Systick::timeout_at(timeout, hal_get(n)).await {
|
||||
Ok(v) => hprintln!("hal returned {} at time {:?}", v, Systick::now()),
|
||||
_ => hprintln!("timeout"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`instant = Systick::now()` gives the baseline (i.e., the absolute current point in time). We want to call `hal_get` after 1000ms relative to this absolute point in time. This can be accomplished by `Systick::delay_until(instant).await;`. We define the absolute point in time for the `timeout`, and call `Systick::timeout_at(timeout, hal_get(n)).await`. For the first loop iteration `n == 0`, and the `hal_get` will take 350ms (and finishes before the timeout). For the second iteration `n == 1`, and `hal_get` will take 450ms (and again succeeds to finish before the timeout). For the third iteration `n == 2` (`hal_get` will take 5500ms to finish). In this case we will run into a timeout.
|
||||
|
||||
|
||||
The complete example:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/async-timeout.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example async-timeout --features test-critical-section
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/async-timeout.run}}
|
||||
```
|
|
@ -1,39 +1,32 @@
|
|||
# Hardware tasks
|
||||
|
||||
At its core RTIC is using a hardware interrupt controller ([ARM NVIC on cortex-m][NVIC])
|
||||
to schedule and start execution of tasks. All tasks except `pre-init`, `#[init]` and `#[idle]`
|
||||
run as interrupt handlers.
|
||||
At its core RTIC is using a hardware interrupt controller ([ARM NVIC on cortex-m][NVIC]) to schedule and start execution of tasks. All tasks except `pre-init`, `#[init]` and `#[idle]` run as interrupt handlers.
|
||||
|
||||
Hardware tasks are explicitly bound to interrupt handlers.
|
||||
|
||||
To bind a task to an interrupt, use the `#[task]` attribute argument `binds = InterruptName`.
|
||||
This task then becomes the interrupt handler for this hardware interrupt vector.
|
||||
To bind a task to an interrupt, use the `#[task]` attribute argument `binds = InterruptName`. This task then becomes the interrupt handler for this hardware interrupt vector.
|
||||
|
||||
All tasks bound to an explicit interrupt are called *hardware tasks* since they
|
||||
start execution in reaction to a hardware event.
|
||||
All tasks bound to an explicit interrupt are called *hardware tasks* since they start execution in reaction to a hardware event.
|
||||
|
||||
Specifying a non-existing interrupt name will cause a compilation error. The interrupt names
|
||||
are commonly defined by [PAC or HAL][pacorhal] crates.
|
||||
Specifying a non-existing interrupt name will cause a compilation error. The interrupt names are commonly defined by [PAC or HAL][pacorhal] crates.
|
||||
|
||||
Any available interrupt vector should work. Specific devices may bind
|
||||
specific interrupt priorities to specific interrupt vectors outside
|
||||
user code control. See for example the
|
||||
[nRF “softdevice”](https://github.com/rtic-rs/cortex-m-rtic/issues/434).
|
||||
Any available interrupt vector should work. Specific devices may bind specific interrupt priorities to specific interrupt vectors outside user code control. See for example the [nRF “softdevice”](https://github.com/rtic-rs/rtic/issues/434).
|
||||
|
||||
Beware of using interrupt vectors that are used internally by hardware features;
|
||||
RTIC is unaware of such hardware specific details.
|
||||
Beware of using interrupt vectors that are used internally by hardware features; RTIC is unaware of such hardware specific details.
|
||||
|
||||
[pacorhal]: https://docs.rust-embedded.org/book/start/registers.html
|
||||
[NVIC]: https://developer.arm.com/documentation/100166/0001/Nested-Vectored-Interrupt-Controller/NVIC-functional-description/NVIC-interrupts
|
||||
|
||||
The example below demonstrates the use of the `#[task(binds = InterruptName)]` attribute to declare a
|
||||
hardware task bound to an interrupt handler.
|
||||
The example below demonstrates the use of the `#[task(binds = InterruptName)]` attribute to declare a hardware task bound to an interrupt handler.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/hardware.rs}}
|
||||
{{#include ../../../../rtic/examples/hardware.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example hardware
|
||||
{{#include ../../../../ci/expected/hardware.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/hardware.run}}
|
||||
```
|
||||
|
|
|
@ -1,176 +1,155 @@
|
|||
# Resource usage
|
||||
|
||||
The RTIC framework manages shared and task local resources allowing persistent data
|
||||
storage and safe accesses without the use of `unsafe` code.
|
||||
The RTIC framework manages shared and task local resources allowing persistent data storage and safe accesses without the use of `unsafe` code.
|
||||
|
||||
RTIC resources are visible only to functions declared within the `#[app]` module and the framework
|
||||
gives the user complete control (on a per-task basis) over resource accessibility.
|
||||
RTIC resources are visible only to functions declared within the `#[app]` module and the framework gives the user complete control (on a per-task basis) over resource accessibility.
|
||||
|
||||
Declaration of system-wide resources is done by annotating **two** `struct`s within the `#[app]` module
|
||||
with the attribute `#[local]` and `#[shared]`.
|
||||
Each field in these structures corresponds to a different resource (identified by field name).
|
||||
The difference between these two sets of resources will be covered below.
|
||||
Declaration of system-wide resources is done by annotating **two** `struct`s within the `#[app]` module with the attribute `#[local]` and `#[shared]`. Each field in these structures corresponds to a different resource (identified by field name). The difference between these two sets of resources will be covered below.
|
||||
|
||||
Each task must declare the resources it intends to access in its corresponding metadata attribute
|
||||
using the `local` and `shared` arguments. Each argument takes a list of resource identifiers.
|
||||
The listed resources are made available to the context under the `local` and `shared` fields of the
|
||||
`Context` structure.
|
||||
Each task must declare the resources it intends to access in its corresponding metadata attribute using the `local` and `shared` arguments. Each argument takes a list of resource identifiers. The listed resources are made available to the context under the `local` and `shared` fields of the `Context` structure.
|
||||
|
||||
The `init` task returns the initial values for the system-wide (`#[shared]` and `#[local]`)
|
||||
resources, and the set of initialized timers used by the application. The monotonic timers will be
|
||||
further discussed in [Monotonic & `spawn_{at/after}`](./monotonic.md).
|
||||
The `init` task returns the initial values for the system-wide (`#[shared]` and `#[local]`) resources.
|
||||
|
||||
<!-- and the set of initialized timers used by the application. The monotonic timers will be
|
||||
further discussed in [Monotonic & `spawn_{at/after}`](./monotonic.md). -->
|
||||
|
||||
## `#[local]` resources
|
||||
|
||||
`#[local]` resources are locally accessible to a specific task, meaning that only that task can
|
||||
access the resource and does so without locks or critical sections. This allows for the resources,
|
||||
commonly drivers or large objects, to be initialized in `#[init]` and then be passed to a specific
|
||||
task.
|
||||
`#[local]` resources are locally accessible to a specific task, meaning that only that task can access the resource and does so without locks or critical sections. This allows for the resources, commonly drivers or large objects, to be initialized in `#[init]` and then be passed to a specific task.
|
||||
|
||||
Thus, a task `#[local]` resource can only be accessed by one singular task.
|
||||
Attempting to assign the same `#[local]` resource to more than one task is a compile-time error.
|
||||
Thus, a task `#[local]` resource can only be accessed by one singular task. Attempting to assign the same `#[local]` resource to more than one task is a compile-time error.
|
||||
|
||||
Types of `#[local]` resources must implement a [`Send`] trait as they are being sent from `init`
|
||||
to a target task, crossing a thread boundary.
|
||||
Types of `#[local]` resources must implement a [`Send`] trait as they are being sent from `init` to a target task, crossing a thread boundary.
|
||||
|
||||
[`Send`]: https://doc.rust-lang.org/stable/core/marker/trait.Send.html
|
||||
|
||||
The example application shown below contains two tasks where each task has access to its own
|
||||
`#[local]` resource; the `idle` task has its own `#[local]` as well.
|
||||
The example application shown below contains three tasks `foo`, `bar` and `idle`, each having access to its own `#[local]` resource.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/locals.rs}}
|
||||
{{#include ../../../../rtic/examples/locals.rs}}
|
||||
```
|
||||
|
||||
Running the example:
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example locals
|
||||
{{#include ../../../../ci/expected/locals.run}}
|
||||
```
|
||||
|
||||
Local resources in `#[init]` and `#[idle]` have `'static`
|
||||
lifetimes. This is safe since both tasks are not re-entrant.
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/locals.run}}
|
||||
```
|
||||
|
||||
Local resources in `#[init]` and `#[idle]` have `'static` lifetimes. This is safe since both tasks are not re-entrant.
|
||||
|
||||
### Task local initialized resources
|
||||
|
||||
Local resources can also be specified directly in the resource claim like so:
|
||||
`#[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]`; this allows for creating locals which do no need to be
|
||||
initialized in `#[init]`.
|
||||
Local resources can also be specified directly in the resource claim like so: `#[task(local = [my_var: TYPE = INITIAL_VALUE, ...])]`; this allows for creating locals which do no need to be initialized in `#[init]`.
|
||||
|
||||
Types of `#[task(local = [..])]` resources have to be neither [`Send`] nor [`Sync`] as they
|
||||
are not crossing any thread boundary.
|
||||
Types of `#[task(local = [..])]` resources have to be neither [`Send`] nor [`Sync`] as they are not crossing any thread boundary.
|
||||
|
||||
[`Sync`]: https://doc.rust-lang.org/stable/core/marker/trait.Sync.html
|
||||
|
||||
In the example below the different uses and lifetimes are shown:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/declared_locals.rs}}
|
||||
{{#include ../../../../rtic/examples/declared_locals.rs}}
|
||||
```
|
||||
|
||||
<!-- ``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example declared_locals
|
||||
{{#include ../../../../ci/expected/declared_locals.run}}
|
||||
``` -->
|
||||
You can run the application, but as the example is designed merely to showcase the lifetime properties there is no output (it suffices to build the application).
|
||||
|
||||
``` console
|
||||
$ cargo build --target thumbv7m-none-eabi --example declared_locals
|
||||
```
|
||||
<!-- {{#include ../../../../rtic/ci/expected/declared_locals.run}} -->
|
||||
|
||||
## `#[shared]` resources and `lock`
|
||||
|
||||
Critical sections are required to access `#[shared]` resources in a data race-free manner and to
|
||||
achieve this the `shared` field of the passed `Context` implements the [`Mutex`] trait for each
|
||||
shared resource accessible to the task. This trait has only one method, [`lock`], which runs its
|
||||
closure argument in a critical section.
|
||||
Critical sections are required to access `#[shared]` resources in a data race-free manner and to achieve this the `shared` field of the passed `Context` implements the [`Mutex`] trait for each shared resource accessible to the task. This trait has only one method, [`lock`], which runs its closure argument in a critical section.
|
||||
|
||||
[`Mutex`]: ../../../api/rtic/trait.Mutex.html
|
||||
[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock
|
||||
|
||||
The critical section created by the `lock` API is based on dynamic priorities: it temporarily
|
||||
raises the dynamic priority of the context to a *ceiling* priority that prevents other tasks from
|
||||
preempting the critical section. This synchronization protocol is known as the
|
||||
[Immediate Ceiling Priority Protocol (ICPP)][icpp], and complies with
|
||||
[Stack Resource Policy (SRP)][srp] based scheduling of RTIC.
|
||||
The critical section created by the `lock` API is based on dynamic priorities: it temporarily raises the dynamic priority of the context to a *ceiling* priority that prevents other tasks from preempting the critical section. This synchronization protocol is known as the [Immediate Ceiling Priority Protocol (ICPP)][icpp], and complies with [Stack Resource Policy (SRP)][srp] based scheduling of RTIC.
|
||||
|
||||
[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol
|
||||
[srp]: https://en.wikipedia.org/wiki/Stack_Resource_Policy
|
||||
|
||||
In the example below we have three interrupt handlers with priorities ranging from one to three.
|
||||
The two handlers with the lower priorities contend for a `shared` resource and need to succeed in locking the
|
||||
resource in order to access its data. The highest priority handler, which does not access the `shared`
|
||||
resource, is free to preempt a critical section created by the lowest priority handler.
|
||||
In the example below we have three interrupt handlers with priorities ranging from one to three. The two handlers with the lower priorities contend for a `shared` resource and need to succeed in locking the resource in order to access its data. The highest priority handler, which does not access the `shared` resource, is free to preempt a critical section created by the lowest priority handler.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/lock.rs}}
|
||||
{{#include ../../../../rtic/examples/lock.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example lock
|
||||
{{#include ../../../../ci/expected/lock.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/lock.run}}
|
||||
```
|
||||
|
||||
Types of `#[shared]` resources have to be [`Send`].
|
||||
|
||||
## Multi-lock
|
||||
|
||||
As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The
|
||||
following examples show this in use:
|
||||
As an extension to `lock`, and to reduce rightward drift, locks can be taken as tuples. The following examples show this in use:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/multilock.rs}}
|
||||
{{#include ../../../../rtic/examples/multilock.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example multilock
|
||||
{{#include ../../../../ci/expected/multilock.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/multilock.run}}
|
||||
```
|
||||
|
||||
## Only shared (`&-`) access
|
||||
|
||||
By default, the framework assumes that all tasks require exclusive access (`&mut-`) to resources,
|
||||
but it is possible to specify that a task only requires shared access (`&-`) to a resource using the
|
||||
`&resource_name` syntax in the `shared` list.
|
||||
By default, the framework assumes that all tasks require exclusive mutable access (`&mut-`) to resources, but it is possible to specify that a task only requires shared access (`&-`) to a resource using the `&resource_name` syntax in the `shared` list.
|
||||
|
||||
The advantage of specifying shared access (`&-`) to a resource is that no locks are required to
|
||||
access the resource even if the resource is contended by more than one task running at different
|
||||
priorities. The downside is that the task only gets a shared reference (`&-`) to the resource,
|
||||
limiting the operations it can perform on it, but where a shared reference is enough this approach
|
||||
reduces the number of required locks. In addition to simple immutable data, this shared access can
|
||||
be useful where the resource type safely implements interior mutability, with appropriate locking
|
||||
or atomic operations of its own.
|
||||
The advantage of specifying shared access (`&-`) to a resource is that no locks are required to access the resource even if the resource is contended by more than one task running at different priorities. The downside is that the task only gets a shared reference (`&-`) to the resource, limiting the operations it can perform on it, but where a shared reference is enough this approach reduces the number of required locks. In addition to simple immutable data, this shared access can be useful where the resource type safely implements interior mutability, with appropriate locking or atomic operations of its own.
|
||||
|
||||
Note that in this release of RTIC it is not possible to request both exclusive access (`&mut-`)
|
||||
and shared access (`&-`) to the *same* resource from different tasks. Attempting to do so will
|
||||
result in a compile error.
|
||||
Note that in this release of RTIC it is not possible to request both exclusive access (`&mut-`) and shared access (`&-`) to the *same* resource from different tasks. Attempting to do so will result in a compile error.
|
||||
|
||||
In the example below a key (e.g. a cryptographic key) is loaded (or created) at runtime and then
|
||||
used from two tasks that run at different priorities without any kind of lock.
|
||||
In the example below a key (e.g. a cryptographic key) is loaded (or created) at runtime (returned by `init`) and then used from two tasks that run at different priorities without any kind of lock.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/only-shared-access.rs}}
|
||||
{{#include ../../../../rtic/examples/only-shared-access.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example only-shared-access
|
||||
{{#include ../../../../ci/expected/only-shared-access.run}}
|
||||
```
|
||||
|
||||
## Lock-free resource access of shared resources
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/only-shared-access.run}}
|
||||
```
|
||||
|
||||
A critical section is *not* required to access a `#[shared]` resource that's only accessed by tasks
|
||||
running at the *same* priority. In this case, you can opt out of the `lock` API by adding the
|
||||
`#[lock_free]` field-level attribute to the resource declaration (see example below). Note that
|
||||
this is merely a convenience to reduce needless resource locking code, because even if the
|
||||
## Lock-free access of shared resources
|
||||
|
||||
A critical section is *not* required to access a `#[shared]` resource that's only accessed by tasks running at the *same* priority. In this case, you can opt out of the `lock` API by adding the `#[lock_free]` field-level attribute to the resource declaration (see example below).
|
||||
|
||||
<!-- Note that this is merely a convenience to reduce needless resource locking code, because even if the
|
||||
`lock` API is used, at runtime the framework will **not** produce a critical section due to how
|
||||
the underlying resource-ceiling preemption works.
|
||||
the underlying resource-ceiling preemption works. -->
|
||||
|
||||
Also worth noting: using `#[lock_free]` on resources shared by
|
||||
tasks running at different priorities will result in a *compile-time* error -- not using the `lock`
|
||||
API would be a data race in that case.
|
||||
To adhere to the Rust [aliasing] rule, a resource may be either accessed through multiple immutable references or a singe mutable reference (but not both at the same time).
|
||||
|
||||
[aliasing]: https://doc.rust-lang.org/nomicon/aliasing.html
|
||||
|
||||
Using `#[lock_free]` on resources shared by tasks running at different priorities will result in a *compile-time* error -- not using the `lock` API would violate the aforementioned alias rule. Similarly, for each priority there can be only a single *software* task accessing a shared resource (as an `async` task may yield execution to other *software* or *hardware* tasks running at the same priority). However, under this single-task restriction, we make the observation that the resource is in effect no longer `shared` but rather `local`. Thus, using a `#[lock_free]` shared resource will result in a *compile-time* error -- where applicable, use a `#[local]` resource instead.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/lock-free.rs}}
|
||||
{{#include ../../../../rtic/examples/lock-free.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example lock-free
|
||||
{{#include ../../../../ci/expected/lock-free.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/lock-free.run}}
|
||||
```
|
||||
|
|
|
@ -1,47 +1,111 @@
|
|||
# Software tasks & spawn
|
||||
|
||||
The RTIC concept of a software task shares a lot with that of [hardware tasks](./hardware_tasks.md)
|
||||
with the core difference that a software task is not explicitly bound to a specific
|
||||
interrupt vector, but rather bound to a “dispatcher” interrupt vector running
|
||||
at the intended priority of the software task (see below).
|
||||
The RTIC concept of a software task shares a lot with that of [hardware tasks](./hardware_tasks.md) with the core difference that a software task is not explicitly bound to a specific
|
||||
interrupt vector, but rather bound to a “dispatcher” interrupt vector running at the intended priority of the software task (see below).
|
||||
|
||||
Thus, software tasks are tasks which are not *directly* bound to an interrupt vector.
|
||||
Similarly to *hardware* tasks, the `#[task]` attribute used on a function declare it as a task. The absence of a `binds = InterruptName` argument to the attribute declares the function as a *software task*.
|
||||
|
||||
The `#[task]` attributes used on a function determine if it is
|
||||
software tasks, specifically the absence of a `binds = InterruptName`
|
||||
argument to the attribute definition.
|
||||
The static method `task_name::spawn()` spawns (starts) a software task and given that there are no higher priority tasks running the task will start executing directly.
|
||||
|
||||
The static method `task_name::spawn()` spawns (schedules) a software
|
||||
task by registering it with a specific dispatcher. If there are no
|
||||
higher priority tasks available to the scheduler (which serves a set
|
||||
of dispatchers), the task will start executing directly.
|
||||
The *software* task itself is given as an `async` Rust function, which allows the user to optionally `await` future events. This allows to blend reactive programming (by means of *hardware* tasks) with sequential programming (by means of *software* tasks).
|
||||
|
||||
All software tasks at the same priority level share an interrupt handler bound to their dispatcher.
|
||||
What differentiates software and hardware tasks is the usage of either a dispatcher or a bound interrupt vector.
|
||||
Whereas, *hardware* tasks are assumed to run-to-completion (and return), *software* tasks may be started (`spawned`) once and run forever, with the side condition that any loop (execution path) is broken by at least one `await` (yielding operation).
|
||||
|
||||
The interrupt vectors used as dispatchers cannot be used by hardware tasks.
|
||||
All *software* tasks at the same priority level shares an interrupt handler acting as an async executor dispatching the software tasks.
|
||||
|
||||
Availability of a set of “free” (not in use by hardware tasks) and usable interrupt vectors allows the framework
|
||||
to dispatch software tasks via dedicated interrupt handlers.
|
||||
This list of dispatchers, `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` is an argument to the `#[app]` attribute, where you define the set of free and usable interrupts.
|
||||
|
||||
This set of dispatchers, `dispatchers = [FreeInterrupt1, FreeInterrupt2, ...]` is an
|
||||
argument to the `#[app]` attribute.
|
||||
Each interrupt vector acting as dispatcher gets assigned to one priority level meaning that the list of dispatchers need to cover all priority levels used by software tasks.
|
||||
|
||||
Each interrupt vector acting as dispatcher gets assigned to a unique priority level meaning that
|
||||
the list of dispatchers needs to cover all priority levels used by software tasks.
|
||||
Example: The `dispatchers =` argument needs to have at least 3 entries for an application using three different priorities for software tasks.
|
||||
|
||||
Example: The `dispatchers =` argument needs to have at least 3 entries for an application using
|
||||
three different priorities for software tasks.
|
||||
|
||||
The framework will give a compilation error if there are not enough dispatchers provided.
|
||||
The framework will give a compilation error if there are not enough dispatchers provided, or if a clash occurs between the list of dispatchers and interrupts bound to *hardware* tasks.
|
||||
|
||||
See the following example:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/spawn.rs}}
|
||||
{{#include ../../../../rtic/examples/spawn.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example spawn
|
||||
{{#include ../../../../ci/expected/spawn.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/spawn.run}}
|
||||
```
|
||||
You may `spawn` a *software* task again, given that it has run-to-completion (returned).
|
||||
|
||||
In the below example, we `spawn` the *software* task `foo` from the `idle` task. Since the default priority of the *software* task is 1 (higher than `idle`), the dispatcher will execute `foo` (preempting `idle`). Since `foo` runs-to-completion. It is ok to `spawn` the `foo` task again.
|
||||
|
||||
Technically the async executor will `poll` the `foo` *future* which in this case leaves the *future* in a *completed* state.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/spawn_loop.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example spawn_loop
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/spawn_loop.run}}
|
||||
```
|
||||
|
||||
An attempt to `spawn` an already spawned task (running) task will result in an error. Notice, the that the error is reported before the `foo` task is actually run. This is since, the actual execution of the *software* task is handled by the dispatcher interrupt (`SSIO`), which is not enabled until we exit the `init` task. (Remember, `init` runs in a critical section, i.e. all interrupts being disabled.)
|
||||
|
||||
Technically, a `spawn` to a *future* that is not in *completed* state is considered an error.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/spawn_err.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example spawn_err
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/spawn_err.run}}
|
||||
```
|
||||
|
||||
## Passing arguments
|
||||
You can also pass arguments at spawn as follows.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/spawn_arguments.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example spawn_arguments
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/spawn_arguments.run}}
|
||||
```
|
||||
|
||||
## Priority zero tasks
|
||||
|
||||
In RTIC tasks run preemptively to each other, with priority zero (0) the lowest priority. You can use priority zero tasks for background work, without any strict real-time requirements.
|
||||
|
||||
Conceptually, one can see such tasks as running in the `main` thread of the application, thus the resources associated are not required the [Send] bound.
|
||||
|
||||
[Send]: https://doc.rust-lang.org/nomicon/send-and-sync.html
|
||||
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../rtic/examples/zero-prio-task.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example zero-prio-task
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/zero-prio-task.run}}
|
||||
```
|
||||
|
||||
> **Notice**: *software* task at zero priority cannot co-exist with the [idle] task. The reason is that `idle` is running as a non-returning Rust function at priority zero. Thus there would be no way for an executor at priority zero to give control to *software* tasks at the same priority.
|
||||
|
||||
---
|
||||
|
||||
Application side safety: Technically, the RTIC framework ensures that `poll` is never executed on any *software* task with *completed* future, thus adhering to the soundness rules of async Rust.
|
||||
|
|
|
@ -10,6 +10,8 @@ If you are targeting ARMv6-M or ARMv8-M-base architecture, check out the section
|
|||
This will give you an RTIC application with support for RTT logging with [`defmt`] and stack overflow
|
||||
protection using [`flip-link`]. There is also a multitude of examples provided by the community:
|
||||
|
||||
For inspiration, you may look at the below resources. For now, they cover RTIC v1.x, but will be updated with RTIC v2.x examples over time.
|
||||
|
||||
- [`rtic-examples`] - Multiple projects
|
||||
- [https://github.com/kalkyl/f411-rtic](https://github.com/kalkyl/f411-rtic)
|
||||
- ... More to come
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
# Resource de-structure-ing
|
||||
|
||||
Destructuring task resources might help readability if a task takes multiple
|
||||
resources.
|
||||
Here are two examples on how to split up the resource struct:
|
||||
resources. Here are two examples on how to split up the resource struct:
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/destructure.rs}}
|
||||
{{#include ../../../../rtic/examples/destructure.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example destructure
|
||||
{{#include ../../../../ci/expected/destructure.run}}
|
||||
```
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/destructure.run}}
|
||||
```
|
||||
|
|
|
@ -1,33 +1,28 @@
|
|||
# Running tasks from RAM
|
||||
|
||||
The main goal of moving the specification of RTIC applications to attributes in
|
||||
RTIC v0.4.0 was to allow inter-operation with other attributes. For example, the
|
||||
`link_section` attribute can be applied to tasks to place them in RAM; this can
|
||||
The main goal of moving the specification of RTIC applications to attributes in RTIC v0.4.0 was to allow inter-operation with other attributes. For example, the `link_section` attribute can be applied to tasks to place them in RAM; this can
|
||||
improve performance in some cases.
|
||||
|
||||
> **IMPORTANT**: In general, the `link_section`, `export_name` and `no_mangle`
|
||||
> attributes are powerful but also easy to misuse. Incorrectly using any of
|
||||
> these attributes can cause undefined behavior; you should always prefer to use
|
||||
> safe, higher level attributes around them like `cortex-m-rt`'s `interrupt` and
|
||||
> `exception` attributes.
|
||||
> **IMPORTANT**: In general, the `link_section`, `export_name` and `no_mangle` attributes are powerful but also easy to misuse. Incorrectly using any of these attributes can cause undefined behavior; you should always prefer to use safe, higher level attributes around them like `cortex-m-rt`'s `interrupt` and `exception` attributes.
|
||||
>
|
||||
> In the particular case of RAM functions there's no
|
||||
> safe abstraction for it in `cortex-m-rt` v0.6.5 but there's an [RFC] for
|
||||
> adding a `ramfunc` attribute in a future release.
|
||||
> In the particular case of RAM functions there's no safe abstraction for it in `cortex-m-rt` v0.6.5 but there's an [RFC] for adding a `ramfunc` attribute in a future release.
|
||||
|
||||
[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100
|
||||
|
||||
The example below shows how to place the higher priority task, `bar`, in RAM.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/ramfunc.rs}}
|
||||
{{#include ../../../../rtic/examples/ramfunc.rs}}
|
||||
```
|
||||
|
||||
Running this program produces the expected output.
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example ramfunc
|
||||
{{#include ../../../../ci/expected/ramfunc.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/ramfunc.run}}
|
||||
```
|
||||
|
||||
One can look at the output of `cargo-nm` to confirm that `bar` ended in RAM
|
||||
|
@ -35,10 +30,16 @@ One can look at the output of `cargo-nm` to confirm that `bar` ended in RAM
|
|||
|
||||
``` 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}}
|
||||
{{#include ../../../../rtic/ci/expected/ramfunc.run.grep.foo}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo nm --example ramfunc --target thumbv7m-none-eabi --release | grep '*bar::'
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/ramfunc.run.grep.bar}}
|
||||
```
|
||||
|
|
|
@ -1,31 +1,26 @@
|
|||
# Using indirection for faster message passing
|
||||
|
||||
Message passing always involves copying the payload from the sender into a
|
||||
static variable and then from the static variable into the receiver. Thus
|
||||
sending a large buffer, like a `[u8; 128]`, as a message involves two expensive
|
||||
Message passing always involves copying the payload from the sender into a static variable and then from the static variable into the receiver. Thus sending a large buffer, like a `[u8; 128]`, as a message involves two expensive
|
||||
`memcpy`s.
|
||||
|
||||
Indirection can minimize message passing overhead:
|
||||
instead of sending the buffer by value, one can send an owning pointer into the
|
||||
buffer.
|
||||
Indirection can minimize message passing overhead: instead of sending the buffer by value, one can send an owning pointer into the buffer.
|
||||
|
||||
One can use a global memory allocator to achieve indirection (`alloc::Box`,
|
||||
`alloc::Rc`, etc.), which requires using the nightly channel as of Rust v1.37.0,
|
||||
or one can use a statically allocated memory pool like [`heapless::Pool`].
|
||||
One can use a global memory allocator to achieve indirection (`alloc::Box`, `alloc::Rc`, etc.), which requires using the nightly channel as of Rust v1.37.0, or one can use a statically allocated memory pool like [`heapless::Pool`].
|
||||
|
||||
[`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html
|
||||
|
||||
As this example of approach goes completely outside of RTIC resource
|
||||
model with shared and local the program would rely on the correctness
|
||||
of the memory allocator, in this case `heapless::pool`.
|
||||
As this example of approach goes completely outside of RTIC resource model with shared and local the program would rely on the correctness of the memory allocator, in this case `heapless::pool`.
|
||||
|
||||
Here's an example where `heapless::Pool` is used to "box" buffers of 128 bytes.
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/pool.rs}}
|
||||
{{#include ../../../../rtic/examples/pool.rs}}
|
||||
```
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example pool
|
||||
{{#include ../../../../ci/expected/pool.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/pool.run}}
|
||||
```
|
||||
|
|
|
@ -1,35 +1,29 @@
|
|||
# Implementing a `Monotonic` timer for scheduling
|
||||
|
||||
The framework is flexible because it can use any timer which has compare-match and optionally
|
||||
supporting overflow interrupts for scheduling.
|
||||
The single requirement to make a timer usable with RTIC is implementing the
|
||||
[`rtic_monotonic::Monotonic`] trait.
|
||||
The framework is flexible because it can use any timer which has compare-match and optionally supporting overflow interrupts for scheduling. The single requirement to make a timer usable with RTIC is implementing the [`rtic-time::Monotonic`] trait.
|
||||
|
||||
Implementing time counting that supports large time spans is generally **difficult**, in RTIC 0.5
|
||||
implementing time handling was a common problem.
|
||||
Moreover, the relation between time and timers used for scheduling was difficult to understand.
|
||||
|
||||
For RTIC 1.0 we instead assume the user has a time library, e.g. [`fugit`] or [`embedded_time`],
|
||||
as the basis for all time-based operations when implementing `Monotonic`.
|
||||
These libraries make it much easier to correctly implement the `Monotonic` trait, allowing the use of
|
||||
For RTIC 1.0 and 2.0 we instead assume the user has a time library, e.g. [`fugit`] or [`embedded_time`], as the basis for all time-based operations when implementing `Monotonic`. These libraries make it much easier to correctly implement the `Monotonic` trait, allowing the use of
|
||||
almost any timer in the system for scheduling.
|
||||
|
||||
The trait documents the requirements for each method,
|
||||
and for inspiration here is a list of `Monotonic` implementations:
|
||||
The trait documents the requirements for each method, and for inspiration
|
||||
there is a reference implementation based on the `SysTick` timer available on all ARM Cortex M MCUs.
|
||||
|
||||
- [`Systick based`], runs at a fixed interrupt (tick) rate - with some overhead but simple and provides support for large time spans
|
||||
|
||||
Here is a list of `Monotonic` implementations for RTIC 1.0:
|
||||
|
||||
- [`STM32F411 series`], implemented for the 32-bit timers
|
||||
- [`Nordic nRF52 series Timer`], implemented for the 32-bit timers
|
||||
- [`Nordic nRF52 series RTC`], implemented for the RTCs
|
||||
- [`Systick based`], runs at a fixed interrupt (tick) rate - with some overhead but simple and with support for large time spans
|
||||
- [`DWT and Systick based`], a more efficient (tickless) implementation - requires both `SysTick` and `DWT`, supports both high resolution and large time spans
|
||||
|
||||
If you know of more implementations feel free to add them to this list.
|
||||
|
||||
[`rtic_monotonic::Monotonic`]: https://docs.rs/rtic-monotonic/
|
||||
[`rtic_time::Monotonic`]: https://docs.rs/rtic_time/
|
||||
[`fugit`]: https://docs.rs/fugit/
|
||||
[`embedded_time`]: https://docs.rs/embedded_time/
|
||||
[`STM32F411 series`]: https://github.com/kalkyl/f411-rtic/blob/a696fce7d6d19fda2356c37642c4d53547982cca/src/mono.rs
|
||||
[`Nordic nRF52 series Timer`]: https://github.com/kalkyl/nrf-play/blob/47f4410d4e39374c18ff58dc17c25159085fb526/src/mono.rs
|
||||
[`Nordic nRF52 series RTC`]: https://gist.github.com/korken89/fe94a475726414dd1bce031c76adc3dd
|
||||
[`Systick based`]: https://github.com/rtic-rs/systick-monotonic
|
||||
[`Systick based`]: https://github.com/rtic-monotonics
|
||||
[`DWT and Systick based`]: https://github.com/rtic-rs/dwt-systick-monotonic
|
||||
|
|
|
@ -2,23 +2,22 @@
|
|||
|
||||
In `#[init]` and `#[idle]` `local` resources have `'static` lifetime.
|
||||
|
||||
Useful when pre-allocating and/or splitting resources between tasks, drivers
|
||||
or some other object.
|
||||
This comes in handy when drivers, such as USB drivers, need to allocate memory and
|
||||
when using splittable data structures such as [`heapless::spsc::Queue`].
|
||||
Useful when pre-allocating and/or splitting resources between tasks, drivers or some other object. This comes in handy when drivers, such as USB drivers, need to allocate memory and when using splittable data structures such as [`heapless::spsc::Queue`].
|
||||
|
||||
In the following example two different tasks share a [`heapless::spsc::Queue`]
|
||||
for lock-free access to the shared queue.
|
||||
In the following example two different tasks share a [`heapless::spsc::Queue`] for lock-free access to the shared queue.
|
||||
|
||||
[`heapless::spsc::Queue`]: https://docs.rs/heapless/0.7.5/heapless/spsc/struct.Queue.html
|
||||
|
||||
``` rust
|
||||
{{#include ../../../../examples/static.rs}}
|
||||
{{#include ../../../../rtic/examples/static.rs}}
|
||||
```
|
||||
|
||||
Running this program produces the expected output.
|
||||
|
||||
``` console
|
||||
$ cargo run --target thumbv7m-none-eabi --example static
|
||||
{{#include ../../../../ci/expected/static.run}}
|
||||
```
|
||||
|
||||
``` console
|
||||
{{#include ../../../../rtic/ci/expected/static.run}}
|
||||
```
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
# Inspecting generated code
|
||||
|
||||
`#[rtic::app]` is a procedural macro that produces support code. If for some
|
||||
reason you need to inspect the code generated by this macro you have two
|
||||
options:
|
||||
`#[rtic::app]` is a procedural macro that produces support code. If for some reason you need to inspect the code generated by this macro you have two options:
|
||||
|
||||
You can inspect the file `rtic-expansion.rs` inside the `target` directory. This
|
||||
file contains the expansion of the `#[rtic::app]` item (not your whole program!)
|
||||
of the *last built* (via `cargo build` or `cargo check`) RTIC application. The
|
||||
expanded code is not pretty printed by default, so you'll want to run `rustfmt`
|
||||
on it before you read it.
|
||||
You can inspect the file `rtic-expansion.rs` inside the `target` directory. This file contains the expansion of the `#[rtic::app]` item (not your whole program!) of the *last built* (via `cargo build` or `cargo check`) RTIC application. The expanded code is not pretty printed by default, so you'll want to run `rustfmt` on it before you read it.
|
||||
|
||||
``` console
|
||||
$ cargo build --example foo
|
||||
$ cargo build --example smallest --target thumbv7m-none-eabi
|
||||
```
|
||||
|
||||
``` console
|
||||
$ rustfmt target/rtic-expansion.rs
|
||||
```
|
||||
|
||||
tail target/rtic-expansion.rs
|
||||
``` console
|
||||
$ tail target/rtic-expansion.rs
|
||||
```
|
||||
|
||||
``` rust
|
||||
|
@ -36,13 +34,14 @@ mod app {
|
|||
}
|
||||
```
|
||||
|
||||
Or, you can use the [`cargo-expand`] sub-command. This sub-command will expand
|
||||
*all* the macros, including the `#[rtic::app]` attribute, and modules in your
|
||||
crate and print the output to the console.
|
||||
Or, you can use the [`cargo-expand`] sub-command. This sub-command will expand *all* the macros, including the `#[rtic::app]` attribute, and modules in your crate and print the output to the console.
|
||||
|
||||
[`cargo-expand`]: https://crates.io/crates/cargo-expand
|
||||
|
||||
``` console
|
||||
# produces the same output as before
|
||||
```
|
||||
|
||||
``` console
|
||||
cargo expand --example smallest | tail
|
||||
```
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# Target Architecture
|
||||
|
||||
While RTIC can currently target all Cortex-m devices there are some key architecure differences that
|
||||
users should be aware of. Namely the absence of Base Priority Mask Register (`BASEPRI`) which lends
|
||||
While RTIC can currently target all Cortex-m devices there are some key architecture differences that
|
||||
users should be aware of. Namely, the absence of Base Priority Mask Register (`BASEPRI`) which lends
|
||||
itself exceptionally well to the hardware priority ceiling support used in RTIC, in the ARMv6-M and
|
||||
ARMv8-M-base architectures, which forces RTIC to use source masking instead. For each implementation
|
||||
of lock and a detailed commentary of pros and cons, see the implementation of
|
||||
[lock in src/export.rs][src_export].
|
||||
|
||||
[src_export]: https://github.com/rtic-rs/cortex-m-rtic/blob/master/src/export.rs
|
||||
[src_export]: https://github.com/rtic-rs/rtic/blob/master/src/export.rs
|
||||
|
||||
These differences influence how critical sections are realized, but functionality should be the same
|
||||
except that ARMv6-M/ARMv8-M-base cannot have tasks with shared resources bound to exception
|
||||
|
@ -29,7 +29,7 @@ Table 1 below shows a list of Cortex-m processors and which type of critical sec
|
|||
|
||||
## Priority Ceiling
|
||||
|
||||
This implementation is covered in depth by the [Critical Sections][critical_sections] page of this book.
|
||||
This is covered by the [Resources][resources] page of this book.
|
||||
|
||||
## Source Masking
|
||||
|
||||
|
@ -55,17 +55,14 @@ with B.
|
|||
```
|
||||
|
||||
At time *t1*, task B locks the shared resource by selectively disabling (using the NVIC) all other
|
||||
tasks which have a priority equal to or less than any task which shares resouces with B. In effect
|
||||
this creates a virtual priority ceiling, miroring the `BASEPRI` approach described in the
|
||||
[Critical Sections][critical_Sections] page. Task A is one such task that shares resources with
|
||||
tasks which have a priority equal to or less than any task which shares resources with B. In effect
|
||||
this creates a virtual priority ceiling, mirroring the `BASEPRI` approach. Task A is one such task that shares resources with
|
||||
task B. At time *t2*, task A is either spawned by task B or becomes pending through an interrupt
|
||||
condition, but does not yet preempt task B even though its priority is greater. This is because the
|
||||
NVIC is preventing it from starting due to task A being being disabled. At time *t3*, task B
|
||||
NVIC is preventing it from starting due to task A being disabled. At time *t3*, task B
|
||||
releases the lock by re-enabling the tasks in the NVIC. Because task A was pending and has a higher
|
||||
priority than task B, it immediately preempts task B and is free to use the shared resource without
|
||||
risk of data race conditions. At time *t4*, task A completes and returns the execution context to B.
|
||||
|
||||
Since source masking relies on use of the NVIC, core exception sources such as HardFault, SVCall,
|
||||
PendSV, and SysTick cannot share data with other tasks.
|
||||
|
||||
[critical_sections]: https://github.com/rtic-rs/cortex-m-rtic/blob/master/book/en/src/internals/critical-sections.md
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<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>
|
||||
<h1 align="center">The hardware accelerated Rust RTOS</h1>
|
||||
|
||||
<p align="center">A concurrency framework for building real-time systems</p>
|
||||
|
||||
|
@ -10,29 +10,152 @@
|
|||
This book contains user level documentation for the Real-Time Interrupt-driven Concurrency
|
||||
(RTIC) framework. The API reference is available [here](../../api/).
|
||||
|
||||
Formerly known as Real-Time For the Masses.
|
||||
This is the documentation for RTIC v2.x.
|
||||
|
||||
<!--There is a translation of this book in [Russian].-->
|
||||
Older releases:
|
||||
[RTIC v1.x](/1.0) | [RTIC v0.5.x (unsupported)](/0.5) | [RTFM v0.4.x (unsupported)](/0.4)
|
||||
|
||||
<!--[Russian]: ../ru/index.html-->
|
||||
|
||||
This is the documentation of v1.0.x of RTIC; for the documentation of version
|
||||
|
||||
* v0.5.x go [here](/0.5).
|
||||
* v0.4.x go [here](/0.4).
|
||||
{{#include ../../../README.md:7:12}}
|
||||
|
||||
## Is RTIC an RTOS?
|
||||
|
||||
A common question is whether RTIC is an RTOS or not, and depending on your background the
|
||||
answer may vary. From RTIC's developers point of view; RTIC is a hardware accelerated
|
||||
RTOS that utilizes the NVIC in Cortex-M MCUs to perform scheduling, rather than the more
|
||||
classical software kernel.
|
||||
A common question is whether RTIC is an RTOS or not, and depending on your background the answer may vary. From RTIC's developers point of view; RTIC is a hardware accelerated RTOS that utilizes the hardware such as the NVIC on Cortex-M MCUs, CLIC on RISC-V etc. to perform scheduling, rather than the more classical software kernel.
|
||||
|
||||
Another common view from the community is that RTIC is a concurrency framework as there
|
||||
is no software kernel and that it relies on external HALs.
|
||||
|
||||
---
|
||||
## RTIC - The Past, current and Future
|
||||
|
||||
{{#include ../../../README.md:7:47}}
|
||||
This section gives a background to the RTIC model. Feel free to skip to section [RTIC the model](preface.md#rtic-the-model) for a TL;DR.
|
||||
|
||||
{{#include ../../../README.md:48:}}
|
||||
The RTIC framework takes the outset from real-time systems research at Luleå University of Technology (LTU) Sweden. RTIC is inspired by the concurrency model of the [Timber] language, the [RTFM-SRP] based scheduler, the [RTFM-core] language and [Abstract Timer] implementation. For a full list of related research see [TODO].
|
||||
|
||||
[Timber]: https://timber-lang.org/
|
||||
[RTFM-SRP]: https://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf
|
||||
[RTFM-core]: https://ltu.diva-portal.org/smash/get/diva2:1013248/FULLTEXT01.pdf
|
||||
[Abstract Timer]: https://ltu.diva-portal.org/smash/get/diva2:1013030/FULLTEXT01.pdf
|
||||
|
||||
## Stack Resource Policy based Scheduling
|
||||
|
||||
[Stack Resource Policy (SRP)][SRP] based concurrency and resource management is at heart of the RTIC framework. The SRP model itself extends on [Priority Inheritance Protocols], and provides a set of outstanding properties for single core scheduling. To name a few:
|
||||
|
||||
- preemptive deadlock and race-free scheduling
|
||||
- resource efficiency
|
||||
- tasks execute on a single shared stack
|
||||
- tasks run-to-completion with wait free access to shared resources
|
||||
- predictable scheduling, with bounded priority inversion by a single (named) critical section
|
||||
- theoretical underpinning amenable to static analysis (e.g., for task response times and overall schedulability)
|
||||
|
||||
SRP comes with a set of system-wide requirements:
|
||||
- each task is associated a static priority,
|
||||
- tasks execute on a single-core,
|
||||
- tasks must be run-to-completion, and
|
||||
- resources must be claimed/locked in LIFO order.
|
||||
|
||||
[SRP]: https://link.springer.com/article/10.1007/BF00365393
|
||||
[Priority Inheritance Protocols]: https://ieeexplore.ieee.org/document/57058
|
||||
|
||||
## SRP analysis
|
||||
|
||||
SRP based scheduling requires the set of static priority tasks and their access to shared resources to be known in order to compute a static *ceiling* (𝝅) for each resource. The static resource *ceiling* 𝝅(r) reflects the maximum static priority of any task that accesses the resource `r`.
|
||||
|
||||
### Example
|
||||
|
||||
Assume two tasks `A` (with priority `p(A) = 2`) and `B` (with priority `p(B) = 4`) both accessing the shared resource `R`. The static ceiling of `R` is 4 (computed from `𝝅(R) = max(p(A) = 2, p(B) = 4) = 4`).
|
||||
|
||||
A graph representation of the example:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["p(A) = 2"] --> R
|
||||
B["p(A) = 4"] --> R
|
||||
R["𝝅(R) = 4"]
|
||||
```
|
||||
|
||||
## RTIC the hardware accelerated real-time scheduler
|
||||
|
||||
SRP itself is compatible with both dynamic and static priority scheduling. For the implementation of RTIC we leverage on the underlying hardware for accelerated static priority scheduling.
|
||||
|
||||
In the case of the `ARM Cortex-M` architecture, each interrupt vector entry `v[i]` is associated a function pointer (`v[i].fn`), and a static priority (`v[i].priority`), an enabled- (`v[i].enabled`) and a pending-bit (`v[i].pending`).
|
||||
|
||||
An interrupt `i` is scheduled (run) by the hardware under the conditions:
|
||||
1. is `pended` and `enabled` and has a priority higher than the (optional `BASEPRI`) register, and
|
||||
1. has the highest priority among interrupts meeting 1.
|
||||
|
||||
The first condition (1) can be seen a filter allowing RTIC to take control over which tasks should be allowed to start (and which should be prevented from starting).
|
||||
|
||||
The SPR model for single-core static scheduling on the other hand states that a task should be scheduled (run) under the conditions:
|
||||
1. it is `requested` to run and has a static priority higher than the current system ceiling (𝜫)
|
||||
1. it has the highest static priority among tasks meeting 1.
|
||||
|
||||
The similarities are striking and it is not by chance/luck/coincidence. The hardware was cleverly designed with real-time scheduling in mind.
|
||||
|
||||
In order to map the SRP scheduling onto the hardware we need to take a closer look at the system ceiling (𝜫). Under SRP 𝜫 is computed as the maximum priority ceiling of the currently held resources, and will thus change dynamically during the system operation.
|
||||
|
||||
## Example
|
||||
|
||||
Assume the task model above. Starting from an idle system, 𝜫 is 0, (no task is holding any resource). Assume that `A` is requested for execution, it will immediately be scheduled. Assume that `A` claims (locks) the resource `R`. During the claim (lock of `R`) any request `B` will be blocked from starting (by 𝜫 = `max(𝝅(R) = 4) = 4`, `p(B) = 4`, thus SRP scheduling condition 1 is not met).
|
||||
|
||||
## Mapping
|
||||
|
||||
The mapping of static priority SRP based scheduling to the Cortex M hardware is straightforward:
|
||||
|
||||
- each task `t` are mapped to an interrupt vector index `i` with a corresponding function `v[i].fn = t` and given the static priority `v[i].priority = p(t)`.
|
||||
- the current system ceiling is mapped to the `BASEPRI` register or implemented through masking the interrupt enable bits accordingly.
|
||||
|
||||
## Example
|
||||
|
||||
For the running example, a snapshot of the ARM Cortex M [Nested Vectored Interrupt Controller (NVIC)][NVIC] may have the following configuration (after task `A` has been pended for execution.)
|
||||
|
||||
| Index | Fn | Priority | Enabled | Pended |
|
||||
| ----- | --- | -------- | ------- | ------ |
|
||||
| 0 | A | 2 | true | true |
|
||||
| 1 | B | 4 | true | false |
|
||||
|
||||
[NVIC]: https://developer.arm.com/documentation/ddi0337/h/nested-vectored-interrupt-controller/about-the-nvic
|
||||
|
||||
(As discussed later, the assignment of interrupt and exception vectors is up to the user.)
|
||||
|
||||
|
||||
A claim (lock(r)) will change the current system ceiling (𝜫) and can be implemented as a *named* critical section:
|
||||
- old_ceiling = 𝜫, 𝜫 = 𝝅(r)
|
||||
- execute code within critical section
|
||||
- old_ceiling = 𝜫
|
||||
|
||||
This amounts to a resource protection mechanism requiring only two machine instructions on enter and one on exit the critical section for managing the `BASEPRI` register. For architectures lacking `BASEPRI`, we can implement the system ceiling through a set of machine instructions for disabling/enabling interrupts on entry/exit for the named critical section. The number of machine instructions vary depending on the number of mask registers that needs to be updated (a single machine operation can operate on up to 32 interrupts, so for the M0/M0+ architecture a single instruction suffice). RTIC will determine the ceiling values and masking constants at compile time, thus all operations is in Rust terms zero-cost.
|
||||
|
||||
In this way RTIC fuses SRP based preemptive scheduling with a zero-cost hardware accelerated implementation, resulting in "best in class" guarantees and performance.
|
||||
|
||||
Given that the approach is dead simple, how come SRP and hardware accelerated scheduling is not adopted by any other mainstream RTOS?
|
||||
|
||||
The answer is simple, the commonly adopted threading model does not lend itself well to static analysis - there is no known way to extract the task/resource dependencies from the source code at compile time (thus ceilings cannot be efficiently computed and the LIFO resource locking requirement cannot be ensured). Thus, SRP based scheduling is in the general case out of reach for any thread based RTOS.
|
||||
|
||||
## RTIC into the Future
|
||||
|
||||
Asynchronous programming in various forms are getting increased popularity and language support. Rust natively provides an `async`/`await` API for cooperative multitasking and the compiler generates the necessary boilerplate for storing and retrieving execution contexts (i.e., managing the set of local variables that spans each `await`).
|
||||
|
||||
The Rust standard library provides collections for dynamically allocated data-structures which are useful to manage execution contexts at run-time. However, in the setting of resource constrained real-time systems, dynamic allocations are problematic (both regarding performance and reliability - Rust runs into a *panic* on an out-of-memory condition). Thus, static allocation is the preferable approach!
|
||||
|
||||
RTIC provides a mechanism for `async`/`await` that relies solely on static allocations. However, the implementation relies on the `#![feature(type_alias_impl_trait)]` (TAIT) which is undergoing stabilization (thus RTIC v2.x currently requires a *nightly* toolchain). Technically, using TAIT, the compiler determines the size of each execution context allowing static allocation.
|
||||
|
||||
From a modelling perspective `async/await` lifts the run-to-completion requirement of SRP, and each section of code between two yield points (`await`s) can be seen as an individual task. The compiler will reject any attempt to `await` while holding a resource (not doing so would break the strict LIFO requirement on resource usage under SRP).
|
||||
|
||||
So with the technical stuff out of the way, what does `async/await` bring to the table?
|
||||
|
||||
The answer is - improved ergonomics! A recurring use case is to have task perform a sequence of requests and then await their results in order to progress. Without `async`/`await` the programmer would be forced to split the task into individual sub-tasks and maintain some sort of state encoding (and manually progress by selecting sub-task). Using `async/await` each yield point (`await`) essentially represents a state, and the progression mechanism is built automatically for you at compile time by means of `Futures`.
|
||||
|
||||
Rust `async`/`await` support is still incomplete and/or under development (e.g., there are no stable way to express `async` closures, precluding use in iterator patterns). Nevertheless, Rust `async`/`await` is production ready and covers most common use cases.
|
||||
|
||||
An important property is that futures are composable, thus you can await either, all, or any combination of possible futures (allowing e.g., timeouts and/or asynchronous errors to be promptly handled). For more details and examples see Section [todo].
|
||||
|
||||
## RTIC the model
|
||||
|
||||
An RTIC `app` is a declarative and executable system model for single-core applications, defining a set of (`local` and `shared`) resources operated on by a set of (`init`, `idle`, *hardware* and *software*) tasks. In short the `init` task runs before any other task returning a set of resources (`local` and `shared`). Tasks run preemptively based on their associated static priority, `idle` has the lowest priority (and can be used for background work, and/or to put the system to sleep until woken by some event). Hardware tasks are bound to underlying hardware interrupts, while software tasks are scheduled by asynchronous executors (one for each software task priority).
|
||||
|
||||
At compile time the task/resource model is analyzed under SRP and executable code generated with the following outstanding properties:
|
||||
|
||||
- guaranteed race-free resource access and deadlock-free execution on a single-shared stack (thanks to SRP)
|
||||
- hardware task scheduling is performed directly by the hardware, and
|
||||
- software task scheduling is performed by auto generated async executors tailored to the application.
|
||||
|
||||
The RTIC API design ensures that both SRP requirements and Rust soundness rules are upheld at all times, thus the executable model is correct by construction. Overall, the generated code infers no additional overhead in comparison to a handwritten implementation, thus in Rust terms RTIC offers a zero-cost abstraction to concurrency.
|
||||
|
|
33
book/en/src/rtic_vs.md
Normal file
33
book/en/src/rtic_vs.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# RTIC vs. the world
|
||||
|
||||
RTIC aims to provide the lowest level of abstraction needed for developing robust and reliable embedded software.
|
||||
|
||||
It provides a minimal set of required mechanisms for safe sharing of mutable resources among interrupts and asynchronously executing tasks. The scheduling primitives leverages on the underlying hardware for unparalleled performance and predictability, in effect RTIC provides in Rust terms a zero-cost abstraction to concurrent real-time programming.
|
||||
|
||||
|
||||
|
||||
## Comparison regarding safety and security
|
||||
|
||||
Comparing RTIC to traditional a Real-Time Operating System (RTOS) is hard. Firstly, a traditional RTOS typically comes with no guarantees regarding system safety, even the most hardened kernels like the formally verified [seL4] kernel. Their claims to integrity, confidentiality, and availability regards only the kernel itself (under additional assumptions its configuration and environment). They even state:
|
||||
|
||||
"An OS kernel, verified or not, does not automatically make a system secure. In fact, any system, no matter how secure, can be used in insecure ways." - [seL4 FAQ][sel4faq]
|
||||
|
||||
[sel4faq]: https://docs.sel4.systems/projects/sel4/frequently-asked-questions.html
|
||||
|
||||
[seL4]: https://sel4.systems/
|
||||
|
||||
### Security by design
|
||||
|
||||
In the world of information security we commonly find:
|
||||
|
||||
- confidentiality, protecting the information from being exposed to an unauthorized party,
|
||||
- integrity, referring to accuracy and completeness of data, and
|
||||
- availability, referring to data being accessible to authorized users.
|
||||
|
||||
Obviously, a traditional OS can guarantee neither confidentiality nor integrity, as both requires the security critical code to be trusted. Regarding availability, this typically boils down to the usage of system resources. Any OS that allows for dynamic allocation of resources, relies on that the application correctly handles allocations/de-allocations, and cases of allocation failures.
|
||||
|
||||
Thus their claim is correct, security is completely out of hands for the OS, the best we can hope for is that it does not add further vulnerabilities.
|
||||
|
||||
RTIC on the other hand holds your back. The declarative system wide model gives you a static set of tasks and resources, with precise control over what data is shared and between which parties. Moreover, Rust as a programming language comes with strong properties regarding integrity (compile time aliasing, mutability and lifetime guarantees, together with ensured data validity).
|
||||
|
||||
Using RTIC these properties propagate to the system wide model, without interference of other applications running. The RTIC kernel is internally infallible without any need of dynamically allocated data.
|
|
@ -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:}}
|
28
build.rs
28
build.rs
|
@ -1,28 +0,0 @@
|
|||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
|
||||
if version_check::Channel::read().unwrap().is_nightly() {
|
||||
println!("cargo:rustc-cfg=rustc_is_nightly");
|
||||
}
|
||||
|
||||
// These targets all have know support for the BASEPRI register.
|
||||
if target.starts_with("thumbv7m")
|
||||
| target.starts_with("thumbv7em")
|
||||
| target.starts_with("thumbv8m.main")
|
||||
{
|
||||
println!("cargo:rustc-cfg=have_basepri");
|
||||
|
||||
// These targets are all known to _not_ have the BASEPRI register.
|
||||
} else if target.starts_with("thumb")
|
||||
&& !(target.starts_with("thumbv6m") | target.starts_with("thumbv8m.base"))
|
||||
{
|
||||
panic!(
|
||||
"Unknown target '{}'. Need to update BASEPRI logic in build.rs.",
|
||||
target
|
||||
);
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
foo: a = 0, b = 0, c = 0
|
||||
bar: a = 0, b = 0, c = 0
|
|
@ -1,2 +0,0 @@
|
|||
foo 1, 2
|
||||
foo 2, 3
|
|
@ -1,3 +0,0 @@
|
|||
foo 1, 1
|
||||
foo 1, 2
|
||||
foo 2, 3
|
|
@ -1,4 +0,0 @@
|
|||
foo Instant { ticks: 0 }
|
||||
foo Instant { ticks: 100 }
|
||||
foo Instant { ticks: 200 }
|
||||
foo Instant { ticks: 300 }
|
|
@ -1,7 +0,0 @@
|
|||
foo Instant { ticks: 0 }
|
||||
bar Instant { ticks: 10 }
|
||||
foo Instant { ticks: 110 }
|
||||
bar Instant { ticks: 120 }
|
||||
foo Instant { ticks: 220 }
|
||||
bar Instant { ticks: 230 }
|
||||
foo Instant { ticks: 330 }
|
|
@ -1,74 +0,0 @@
|
|||
//! examples/cancel-reschedule.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use systick_monotonic::*;
|
||||
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
let mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
hprintln!("init");
|
||||
|
||||
// Schedule `foo` to run 1 second in the future
|
||||
foo::spawn_after(1.secs()).unwrap();
|
||||
|
||||
(
|
||||
Shared {},
|
||||
Local {},
|
||||
init::Monotonics(mono), // Give the monotonic to RTIC
|
||||
)
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn foo(_: foo::Context) {
|
||||
hprintln!("foo");
|
||||
|
||||
// Schedule `bar` to run 2 seconds in the future (1 second after foo runs)
|
||||
let spawn_handle = baz::spawn_after(2.secs()).unwrap();
|
||||
bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) {
|
||||
hprintln!("bar");
|
||||
|
||||
if do_reschedule {
|
||||
// Reschedule baz 2 seconds from now, instead of the original 1 second
|
||||
// from now.
|
||||
baz_handle.reschedule_after(2.secs()).unwrap();
|
||||
// Or baz_handle.reschedule_at(/* time */)
|
||||
} else {
|
||||
// Or cancel it
|
||||
baz_handle.cancel().unwrap();
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn baz(_: baz::Context) {
|
||||
hprintln!("baz");
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
//! examples/capacity.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use lm3s6965::Interrupt;
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
rtic::pend(Interrupt::UART0);
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics())
|
||||
}
|
||||
|
||||
#[task(binds = UART0)]
|
||||
fn uart0(_: uart0::Context) {
|
||||
foo::spawn(0).unwrap();
|
||||
foo::spawn(1).unwrap();
|
||||
foo::spawn(2).unwrap();
|
||||
foo::spawn(3).unwrap();
|
||||
|
||||
bar::spawn().unwrap();
|
||||
}
|
||||
|
||||
#[task(capacity = 4)]
|
||||
fn foo(_: foo::Context, x: u32) {
|
||||
hprintln!("foo({})", x);
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn bar(_: bar::Context) {
|
||||
hprintln!("bar");
|
||||
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
//! examples/cfg-monotonic.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use systick_monotonic::*; // Implements the `Monotonic` trait
|
||||
|
||||
// A monotonic timer to enable scheduling in RTIC
|
||||
#[cfg(feature = "killmono")]
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
// Not allowed by current rtic-syntax:
|
||||
// error: `#[monotonic(...)]` on a specific type must appear at most once
|
||||
// --> examples/cfg-monotonic.rs:23:10
|
||||
// |
|
||||
// 23 | type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
// | ^^^^^^
|
||||
// #[monotonic(binds = SysTick, default = true)]
|
||||
// type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
// Not allowed by current rtic-syntax:
|
||||
// error: this interrupt is already bound
|
||||
// --> examples/cfg-monotonic.rs:31:25
|
||||
// |
|
||||
// 31 | #[monotonic(binds = SysTick, default = true)]
|
||||
// | ^^^^^^^
|
||||
// #[monotonic(binds = SysTick, default = true)]
|
||||
// type MyMono2 = DwtSystick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
// Resources shared between tasks
|
||||
#[shared]
|
||||
struct Shared {
|
||||
s1: u32,
|
||||
s2: i32,
|
||||
}
|
||||
|
||||
// Local resources to specific tasks (cannot be shared)
|
||||
#[local]
|
||||
struct Local {
|
||||
l1: u8,
|
||||
l2: i8,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let _systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
#[cfg(feature = "killmono")]
|
||||
let mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
// Spawn the task `foo` directly after `init` finishes
|
||||
foo::spawn().unwrap();
|
||||
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
|
||||
(
|
||||
// Initialization of shared resources
|
||||
Shared { s1: 0, s2: 1 },
|
||||
// Initialization of task local resources
|
||||
Local { l1: 2, l2: 3 },
|
||||
// Move the monotonic timer to the RTIC run-time, this enables
|
||||
// scheduling
|
||||
#[cfg(feature = "killmono")]
|
||||
init::Monotonics(mono),
|
||||
init::Monotonics(),
|
||||
)
|
||||
}
|
||||
|
||||
// Background task, runs whenever no other tasks are running
|
||||
#[idle]
|
||||
fn idle(_: idle::Context) -> ! {
|
||||
loop {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Software task, not bound to a hardware interrupt.
|
||||
// This task takes the task local resource `l1`
|
||||
// The resources `s1` and `s2` are shared between all other tasks.
|
||||
#[task(shared = [s1, s2], local = [l1])]
|
||||
fn foo(_: foo::Context) {
|
||||
// This task is only spawned once in `init`, hence this task will run
|
||||
// only once
|
||||
|
||||
hprintln!("foo");
|
||||
}
|
||||
|
||||
// Software task, also not bound to a hardware interrupt
|
||||
// This task takes the task local resource `l2`
|
||||
// The resources `s1` and `s2` are shared between all other tasks.
|
||||
#[task(shared = [s1, s2], local = [l2])]
|
||||
fn bar(_: bar::Context) {
|
||||
hprintln!("bar");
|
||||
|
||||
// Run `bar` once per second
|
||||
// bar::spawn_after(1.secs()).unwrap();
|
||||
}
|
||||
|
||||
// Hardware task, bound to a hardware interrupt
|
||||
// The resources `s1` and `s2` are shared between all other tasks.
|
||||
#[task(binds = UART0, priority = 3, shared = [s1, s2])]
|
||||
fn uart0_interrupt(_: uart0_interrupt::Context) {
|
||||
// This task is bound to the interrupt `UART0` and will run
|
||||
// whenever the interrupt fires
|
||||
|
||||
// Note that RTIC does NOT clear the interrupt flag, this is up to the
|
||||
// user
|
||||
|
||||
hprintln!("UART0 interrupt!");
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
//! examples/cfg-whole-task.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::debug;
|
||||
#[cfg(debug_assertions)]
|
||||
use cortex_m_semihosting::hprintln;
|
||||
|
||||
#[shared]
|
||||
struct Shared {
|
||||
count: u32,
|
||||
#[cfg(never)]
|
||||
unused: u32,
|
||||
}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
foo::spawn().unwrap();
|
||||
foo::spawn().unwrap();
|
||||
|
||||
(
|
||||
Shared {
|
||||
count: 0,
|
||||
#[cfg(never)]
|
||||
unused: 1,
|
||||
},
|
||||
Local {},
|
||||
init::Monotonics(),
|
||||
)
|
||||
}
|
||||
|
||||
#[idle]
|
||||
fn idle(_: idle::Context) -> ! {
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
|
||||
loop {
|
||||
cortex_m::asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[task(capacity = 2, shared = [count])]
|
||||
fn foo(mut _cx: foo::Context) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
_cx.shared.count.lock(|count| *count += 1);
|
||||
|
||||
log::spawn(_cx.shared.count.lock(|count| *count)).unwrap();
|
||||
}
|
||||
|
||||
// this wouldn't compile in `release` mode
|
||||
// *_cx.shared.count += 1;
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
// The whole task should disappear,
|
||||
// currently still present in the Tasks enum
|
||||
#[cfg(never)]
|
||||
#[task(capacity = 2, shared = [count])]
|
||||
fn foo2(mut _cx: foo2::Context) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
_cx.shared.count.lock(|count| *count += 10);
|
||||
|
||||
log::spawn(_cx.shared.count.lock(|count| *count)).unwrap();
|
||||
}
|
||||
|
||||
// this wouldn't compile in `release` mode
|
||||
// *_cx.shared.count += 1;
|
||||
|
||||
// ..
|
||||
}
|
||||
|
||||
// The whole task should disappear,
|
||||
// currently still present in the Tasks enum
|
||||
#[cfg(never)]
|
||||
#[task(binds = UART1, shared = [count])]
|
||||
fn foo3(mut _cx: foo3::Context) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
_cx.shared.count.lock(|count| *count += 10);
|
||||
|
||||
log::spawn(_cx.shared.count.lock(|count| *count)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[task(capacity = 2)]
|
||||
fn log(_: log::Context, n: u32) {
|
||||
hprintln!(
|
||||
"foo has been called {} time{}",
|
||||
n,
|
||||
if n == 1 { "" } else { "s" }
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
//! examples/common.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use systick_monotonic::*; // Implements the `Monotonic` trait
|
||||
|
||||
// A monotonic timer to enable scheduling in RTIC
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
// Resources shared between tasks
|
||||
#[shared]
|
||||
struct Shared {
|
||||
s1: u32,
|
||||
s2: i32,
|
||||
}
|
||||
|
||||
// Local resources to specific tasks (cannot be shared)
|
||||
#[local]
|
||||
struct Local {
|
||||
l1: u8,
|
||||
l2: i8,
|
||||
}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
let mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
// Spawn the task `foo` directly after `init` finishes
|
||||
foo::spawn().unwrap();
|
||||
|
||||
// Spawn the task `bar` 1 second after `init` finishes, this is enabled
|
||||
// by the `#[monotonic(..)]` above
|
||||
bar::spawn_after(1.secs()).unwrap();
|
||||
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
|
||||
(
|
||||
// Initialization of shared resources
|
||||
Shared { s1: 0, s2: 1 },
|
||||
// Initialization of task local resources
|
||||
Local { l1: 2, l2: 3 },
|
||||
// Move the monotonic timer to the RTIC run-time, this enables
|
||||
// scheduling
|
||||
init::Monotonics(mono),
|
||||
)
|
||||
}
|
||||
|
||||
// Background task, runs whenever no other tasks are running
|
||||
#[idle]
|
||||
fn idle(_: idle::Context) -> ! {
|
||||
loop {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Software task, not bound to a hardware interrupt.
|
||||
// This task takes the task local resource `l1`
|
||||
// The resources `s1` and `s2` are shared between all other tasks.
|
||||
#[task(shared = [s1, s2], local = [l1])]
|
||||
fn foo(_: foo::Context) {
|
||||
// This task is only spawned once in `init`, hence this task will run
|
||||
// only once
|
||||
|
||||
hprintln!("foo");
|
||||
}
|
||||
|
||||
// Software task, also not bound to a hardware interrupt
|
||||
// This task takes the task local resource `l2`
|
||||
// The resources `s1` and `s2` are shared between all other tasks.
|
||||
#[task(shared = [s1, s2], local = [l2])]
|
||||
fn bar(_: bar::Context) {
|
||||
hprintln!("bar");
|
||||
|
||||
// Run `bar` once per second
|
||||
bar::spawn_after(1.secs()).unwrap();
|
||||
}
|
||||
|
||||
// Hardware task, bound to a hardware interrupt
|
||||
// The resources `s1` and `s2` are shared between all other tasks.
|
||||
#[task(binds = UART0, priority = 3, shared = [s1, s2])]
|
||||
fn uart0_interrupt(_: uart0_interrupt::Context) {
|
||||
// This task is bound to the interrupt `UART0` and will run
|
||||
// whenever the interrupt fires
|
||||
|
||||
// Note that RTIC does NOT clear the interrupt flag, this is up to the
|
||||
// user
|
||||
|
||||
hprintln!("UART0 interrupt!");
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
//! examples/message.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
foo::spawn(/* no message */).unwrap();
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics())
|
||||
}
|
||||
|
||||
#[task(local = [count: u32 = 0])]
|
||||
fn foo(cx: foo::Context) {
|
||||
hprintln!("foo");
|
||||
|
||||
bar::spawn(*cx.local.count).unwrap();
|
||||
*cx.local.count += 1;
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn bar(_: bar::Context, x: u32) {
|
||||
hprintln!("bar({})", x);
|
||||
|
||||
baz::spawn(x + 1, x + 2).unwrap();
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn baz(_: baz::Context, x: u32, y: u32) {
|
||||
hprintln!("baz({}, {})", x, y);
|
||||
|
||||
if x + y > 4 {
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
|
||||
foo::spawn().unwrap();
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
//! examples/periodic-at.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use systick_monotonic::*;
|
||||
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
let mut mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
foo::spawn_after(1.secs(), mono.now()).unwrap();
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics(mono))
|
||||
}
|
||||
|
||||
#[task(local = [cnt: u32 = 0])]
|
||||
fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) {
|
||||
hprintln!("foo {:?}", instant);
|
||||
*cx.local.cnt += 1;
|
||||
|
||||
if *cx.local.cnt == 4 {
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
|
||||
// Periodic ever 1 seconds
|
||||
let next_instant = instant + 1.secs();
|
||||
foo::spawn_at(next_instant, next_instant).unwrap();
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
//! examples/periodic-at2.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use systick_monotonic::*;
|
||||
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
let mut mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
foo::spawn_after(1.secs(), mono.now()).unwrap();
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics(mono))
|
||||
}
|
||||
|
||||
// Using the explicit type of the timer implementation
|
||||
#[task(local = [cnt: u32 = 0])]
|
||||
fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) {
|
||||
hprintln!("foo {:?}", instant);
|
||||
*cx.local.cnt += 1;
|
||||
|
||||
if *cx.local.cnt == 4 {
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
|
||||
// Spawn a new message with 100 ms offset to spawned time
|
||||
let next_instant = instant + 100.millis();
|
||||
bar::spawn_at(next_instant, next_instant).unwrap();
|
||||
}
|
||||
|
||||
// Using the Instant from the Monotonic trait
|
||||
// This remains agnostic to the timer implementation
|
||||
#[task(local = [cnt: u32 = 0])]
|
||||
fn bar(_cx: bar::Context, instant: <MyMono as rtic_monotonic::Monotonic>::Instant) {
|
||||
hprintln!("bar {:?}", instant);
|
||||
|
||||
// Spawn a new message with 1s offset to spawned time
|
||||
let next_instant = instant + 1.secs();
|
||||
foo::spawn_at(next_instant, next_instant).unwrap();
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
//! examples/periodic.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use systick_monotonic::*;
|
||||
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
let mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
foo::spawn_after(1.secs()).unwrap();
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics(mono))
|
||||
}
|
||||
|
||||
#[task(local = [cnt: u32 = 0])]
|
||||
fn foo(cx: foo::Context) {
|
||||
hprintln!("foo");
|
||||
*cx.local.cnt += 1;
|
||||
|
||||
if *cx.local.cnt == 4 {
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
|
||||
// Periodic ever 1 seconds
|
||||
foo::spawn_after(1.secs()).unwrap();
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//! examples/schedule.rs
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::{debug, hprintln};
|
||||
use systick_monotonic::*;
|
||||
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
let mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
hprintln!("init");
|
||||
|
||||
// Schedule `foo` to run 1 second in the future
|
||||
foo::spawn_after(1.secs()).unwrap();
|
||||
|
||||
(
|
||||
Shared {},
|
||||
Local {},
|
||||
init::Monotonics(mono), // Give the monotonic to RTIC
|
||||
)
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn foo(_: foo::Context) {
|
||||
hprintln!("foo");
|
||||
|
||||
// Schedule `bar` to run 2 seconds in the future (1 second after foo runs)
|
||||
bar::spawn_after(1.secs()).unwrap();
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn bar(_: bar::Context) {
|
||||
hprintln!("bar");
|
||||
|
||||
// Schedule `baz` to run 1 seconds from now, but with a specific time instant.
|
||||
baz::spawn_at(monotonics::now() + 1.secs()).unwrap();
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn baz(_: baz::Context) {
|
||||
hprintln!("baz");
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
}
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
//! [compile-pass] Check `schedule` code generation
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::debug;
|
||||
use systick_monotonic::*;
|
||||
|
||||
#[monotonic(binds = SysTick, default = true)]
|
||||
type MyMono = Systick<100>; // 100 Hz / 10 ms granularity
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let systick = cx.core.SYST;
|
||||
|
||||
// Initialize the monotonic (SysTick rate in QEMU is 12 MHz)
|
||||
let mono = Systick::new(systick, 12_000_000);
|
||||
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics(mono))
|
||||
}
|
||||
|
||||
#[idle]
|
||||
fn idle(_: idle::Context) -> ! {
|
||||
// Task without message passing
|
||||
|
||||
// Not default
|
||||
let _: Result<foo::MyMono::SpawnHandle, ()> =
|
||||
foo::MyMono::spawn_at(monotonics::MyMono::now());
|
||||
let handle: Result<foo::MyMono::SpawnHandle, ()> = foo::MyMono::spawn_after(1.secs());
|
||||
let _: Result<foo::MyMono::SpawnHandle, ()> = handle.unwrap().reschedule_after(1.secs());
|
||||
|
||||
let handle: Result<foo::MyMono::SpawnHandle, ()> = foo::MyMono::spawn_after(1.secs());
|
||||
let _: Result<foo::MyMono::SpawnHandle, ()> =
|
||||
handle.unwrap().reschedule_at(monotonics::MyMono::now());
|
||||
|
||||
let handle: Result<foo::MyMono::SpawnHandle, ()> = foo::MyMono::spawn_after(1.secs());
|
||||
let _: Result<(), ()> = handle.unwrap().cancel();
|
||||
|
||||
// Using default
|
||||
let _: Result<foo::SpawnHandle, ()> = foo::spawn_at(monotonics::now());
|
||||
let handle: Result<foo::SpawnHandle, ()> = foo::spawn_after(1.secs());
|
||||
let _: Result<foo::SpawnHandle, ()> = handle.unwrap().reschedule_after(1.secs());
|
||||
|
||||
let handle: Result<foo::SpawnHandle, ()> = foo::spawn_after(1.secs());
|
||||
let _: Result<foo::SpawnHandle, ()> =
|
||||
handle.unwrap().reschedule_at(monotonics::MyMono::now());
|
||||
|
||||
let handle: Result<foo::SpawnHandle, ()> = foo::spawn_after(1.secs());
|
||||
let _: Result<(), ()> = handle.unwrap().cancel();
|
||||
|
||||
// Task with single message passing
|
||||
|
||||
// Not default
|
||||
let _: Result<bar::MyMono::SpawnHandle, u32> =
|
||||
bar::MyMono::spawn_at(monotonics::MyMono::now(), 0);
|
||||
let handle: Result<bar::MyMono::SpawnHandle, u32> = bar::MyMono::spawn_after(1.secs(), 1);
|
||||
let _: Result<bar::MyMono::SpawnHandle, ()> = handle.unwrap().reschedule_after(1.secs());
|
||||
|
||||
let handle: Result<bar::MyMono::SpawnHandle, u32> = bar::MyMono::spawn_after(1.secs(), 1);
|
||||
let _: Result<bar::MyMono::SpawnHandle, ()> =
|
||||
handle.unwrap().reschedule_at(monotonics::MyMono::now());
|
||||
|
||||
let handle: Result<bar::MyMono::SpawnHandle, u32> = bar::MyMono::spawn_after(1.secs(), 1);
|
||||
let _: Result<u32, ()> = handle.unwrap().cancel();
|
||||
|
||||
// Using default
|
||||
let _: Result<bar::SpawnHandle, u32> = bar::spawn_at(monotonics::MyMono::now(), 0);
|
||||
let handle: Result<bar::SpawnHandle, u32> = bar::spawn_after(1.secs(), 1);
|
||||
let _: Result<bar::SpawnHandle, ()> = handle.unwrap().reschedule_after(1.secs());
|
||||
|
||||
let handle: Result<bar::SpawnHandle, u32> = bar::spawn_after(1.secs(), 1);
|
||||
let _: Result<bar::SpawnHandle, ()> =
|
||||
handle.unwrap().reschedule_at(monotonics::MyMono::now());
|
||||
|
||||
let handle: Result<bar::SpawnHandle, u32> = bar::spawn_after(1.secs(), 1);
|
||||
let _: Result<u32, ()> = handle.unwrap().cancel();
|
||||
|
||||
// Task with multiple message passing
|
||||
|
||||
// Not default
|
||||
let _: Result<baz::MyMono::SpawnHandle, (u32, u32)> =
|
||||
baz::MyMono::spawn_at(monotonics::MyMono::now(), 0, 1);
|
||||
let handle: Result<baz::MyMono::SpawnHandle, (u32, u32)> =
|
||||
baz::MyMono::spawn_after(1.secs(), 1, 2);
|
||||
let _: Result<baz::MyMono::SpawnHandle, ()> = handle.unwrap().reschedule_after(1.secs());
|
||||
|
||||
let handle: Result<baz::MyMono::SpawnHandle, (u32, u32)> =
|
||||
baz::MyMono::spawn_after(1.secs(), 1, 2);
|
||||
let _: Result<baz::MyMono::SpawnHandle, ()> =
|
||||
handle.unwrap().reschedule_at(monotonics::MyMono::now());
|
||||
|
||||
let handle: Result<baz::MyMono::SpawnHandle, (u32, u32)> =
|
||||
baz::MyMono::spawn_after(1.secs(), 1, 2);
|
||||
let _: Result<(u32, u32), ()> = handle.unwrap().cancel();
|
||||
|
||||
// Using default
|
||||
let _: Result<baz::SpawnHandle, (u32, u32)> =
|
||||
baz::spawn_at(monotonics::MyMono::now(), 0, 1);
|
||||
let handle: Result<baz::SpawnHandle, (u32, u32)> = baz::spawn_after(1.secs(), 1, 2);
|
||||
let _: Result<baz::SpawnHandle, ()> = handle.unwrap().reschedule_after(1.secs());
|
||||
|
||||
let handle: Result<baz::SpawnHandle, (u32, u32)> = baz::spawn_after(1.secs(), 1, 2);
|
||||
let _: Result<baz::SpawnHandle, ()> =
|
||||
handle.unwrap().reschedule_at(monotonics::MyMono::now());
|
||||
|
||||
let handle: Result<baz::SpawnHandle, (u32, u32)> = baz::spawn_after(1.secs(), 1, 2);
|
||||
let _: Result<(u32, u32), ()> = handle.unwrap().cancel();
|
||||
|
||||
loop {
|
||||
cortex_m::asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn foo(_: foo::Context) {}
|
||||
|
||||
#[task]
|
||||
fn bar(_: bar::Context, _x: u32) {}
|
||||
|
||||
#[task]
|
||||
fn baz(_: baz::Context, _x: u32, _y: u32) {}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
//! [compile-pass] Check code generation of `spawn`
|
||||
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![deny(missing_docs)]
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use panic_semihosting as _;
|
||||
|
||||
#[rtic::app(device = lm3s6965, dispatchers = [SSI0])]
|
||||
mod app {
|
||||
use cortex_m_semihosting::debug;
|
||||
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
let _: Result<(), ()> = foo::spawn();
|
||||
let _: Result<(), u32> = bar::spawn(0);
|
||||
let _: Result<(), (u32, u32)> = baz::spawn(0, 1);
|
||||
|
||||
debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator
|
||||
|
||||
(Shared {}, Local {}, init::Monotonics())
|
||||
}
|
||||
|
||||
#[idle]
|
||||
fn idle(_: idle::Context) -> ! {
|
||||
let _: Result<(), ()> = foo::spawn();
|
||||
let _: Result<(), u32> = bar::spawn(0);
|
||||
let _: Result<(), (u32, u32)> = baz::spawn(0, 1);
|
||||
|
||||
loop {
|
||||
cortex_m::asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = SVCall)]
|
||||
fn svcall(_: svcall::Context) {
|
||||
let _: Result<(), ()> = foo::spawn();
|
||||
let _: Result<(), u32> = bar::spawn(0);
|
||||
let _: Result<(), (u32, u32)> = baz::spawn(0, 1);
|
||||
}
|
||||
|
||||
#[task(binds = UART0)]
|
||||
fn uart0(_: uart0::Context) {
|
||||
let _: Result<(), ()> = foo::spawn();
|
||||
let _: Result<(), u32> = bar::spawn(0);
|
||||
let _: Result<(), (u32, u32)> = baz::spawn(0, 1);
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn foo(_: foo::Context) {
|
||||
let _: Result<(), ()> = foo::spawn();
|
||||
let _: Result<(), u32> = bar::spawn(0);
|
||||
let _: Result<(), (u32, u32)> = baz::spawn(0, 1);
|
||||
}
|
||||
|
||||
#[task]
|
||||
fn bar(_: bar::Context, _x: u32) {}
|
||||
|
||||
#[task]
|
||||
fn baz(_: baz::Context, _x: u32, _y: u32) {}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
[package]
|
||||
authors = [
|
||||
"The Real-Time Interrupt-driven Concurrency developers",
|
||||
"Jorge Aparicio <jorge@japaric.io>",
|
||||
]
|
||||
categories = ["concurrency", "embedded", "no-std"]
|
||||
description = "Procedural macros of the cortex-m-rtic crate"
|
||||
documentation = "https://rtic-rs.github.io/cortex-m-rtic/api/cortex_m_rtic"
|
||||
edition = "2021"
|
||||
keywords = ["arm", "cortex-m"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "cortex-m-rtic-macros"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/rtic-rs/cortex-m-rtic"
|
||||
version = "1.1.6"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
proc-macro-error = "1"
|
||||
quote = "1"
|
||||
syn = "1"
|
||||
rtic-syntax = "1.0.3"
|
||||
|
||||
[features]
|
||||
debugprint = []
|
|
@ -1,86 +0,0 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro2::Span;
|
||||
use rtic_syntax::{analyze::Analysis, ast::App};
|
||||
use syn::{parse, Path};
|
||||
|
||||
pub struct Extra {
|
||||
pub device: Path,
|
||||
pub peripherals: bool,
|
||||
}
|
||||
|
||||
pub fn app(app: &App, _analysis: &Analysis) -> parse::Result<Extra> {
|
||||
// Check that external (device-specific) interrupts are not named after known (Cortex-M)
|
||||
// exceptions
|
||||
for name in app.args.extern_interrupts.keys() {
|
||||
let name_s = name.to_string();
|
||||
|
||||
match &*name_s {
|
||||
"NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault"
|
||||
| "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => {
|
||||
return Err(parse::Error::new(
|
||||
name.span(),
|
||||
"Cortex-M exceptions can't be used as `extern` interrupts",
|
||||
));
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that there are enough external interrupts to dispatch the software tasks and the timer
|
||||
// queue handler
|
||||
let mut first = None;
|
||||
let priorities = app
|
||||
.software_tasks
|
||||
.iter()
|
||||
.map(|(name, task)| {
|
||||
first = Some(name);
|
||||
task.args.priority
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let need = priorities.len();
|
||||
let given = app.args.extern_interrupts.len();
|
||||
if need > given {
|
||||
let s = {
|
||||
format!(
|
||||
"not enough interrupts to dispatch \
|
||||
all software tasks (need: {}; given: {})",
|
||||
need, given
|
||||
)
|
||||
};
|
||||
|
||||
// If not enough tasks and first still is None, may cause
|
||||
// "custom attribute panicked" due to unwrap on None
|
||||
return Err(parse::Error::new(first.unwrap().span(), s));
|
||||
}
|
||||
|
||||
// Check that all exceptions are valid; only exceptions with configurable priorities are
|
||||
// accepted
|
||||
for (name, task) in &app.hardware_tasks {
|
||||
let name_s = task.args.binds.to_string();
|
||||
match &*name_s {
|
||||
"NonMaskableInt" | "HardFault" => {
|
||||
return Err(parse::Error::new(
|
||||
name.span(),
|
||||
"only exceptions with configurable priority can be used as hardware tasks",
|
||||
));
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(device) = app.args.device.clone() {
|
||||
Ok(Extra {
|
||||
device,
|
||||
peripherals: app.args.peripherals,
|
||||
})
|
||||
} else {
|
||||
Err(parse::Error::new(
|
||||
Span::call_site(),
|
||||
"a `device` argument must be specified in `#[rtic::app]`",
|
||||
))
|
||||
}
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::ast::App;
|
||||
|
||||
use crate::{analyze::Analysis, check::Extra};
|
||||
|
||||
mod assertions;
|
||||
mod dispatchers;
|
||||
mod hardware_tasks;
|
||||
mod idle;
|
||||
mod init;
|
||||
mod local_resources;
|
||||
mod local_resources_struct;
|
||||
mod module;
|
||||
mod post_init;
|
||||
mod pre_init;
|
||||
mod shared_resources;
|
||||
mod shared_resources_struct;
|
||||
mod software_tasks;
|
||||
mod timer_queue;
|
||||
mod util;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
|
||||
let mut mod_app = vec![];
|
||||
let mut mains = vec![];
|
||||
let mut root = vec![];
|
||||
let mut user = vec![];
|
||||
|
||||
// Generate the `main` function
|
||||
let assertion_stmts = assertions::codegen(app, analysis, extra);
|
||||
|
||||
let pre_init_stmts = pre_init::codegen(app, analysis, extra);
|
||||
|
||||
let (mod_app_init, root_init, user_init, call_init) = init::codegen(app, analysis, extra);
|
||||
|
||||
let post_init_stmts = post_init::codegen(app, analysis);
|
||||
|
||||
let (mod_app_idle, root_idle, user_idle, call_idle) = idle::codegen(app, analysis, extra);
|
||||
|
||||
user.push(quote!(
|
||||
#user_init
|
||||
|
||||
#user_idle
|
||||
));
|
||||
|
||||
root.push(quote!(
|
||||
#(#root_init)*
|
||||
|
||||
#(#root_idle)*
|
||||
));
|
||||
|
||||
mod_app.push(quote!(
|
||||
#mod_app_init
|
||||
|
||||
#(#mod_app_idle)*
|
||||
));
|
||||
|
||||
let main = util::suffixed("main");
|
||||
mains.push(quote!(
|
||||
#[doc(hidden)]
|
||||
mod rtic_ext {
|
||||
use super::*;
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn #main() -> ! {
|
||||
#(#assertion_stmts)*
|
||||
|
||||
#(#pre_init_stmts)*
|
||||
|
||||
#[inline(never)]
|
||||
fn __rtic_init_resources<F>(f: F) where F: FnOnce() {
|
||||
f();
|
||||
}
|
||||
|
||||
// Wrap late_init_stmts in a function to ensure that stack space is reclaimed.
|
||||
__rtic_init_resources(||{
|
||||
#call_init
|
||||
|
||||
#(#post_init_stmts)*
|
||||
});
|
||||
|
||||
#call_idle
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
let (mod_app_shared_resources, mod_shared_resources) =
|
||||
shared_resources::codegen(app, analysis, extra);
|
||||
let (mod_app_local_resources, mod_local_resources) =
|
||||
local_resources::codegen(app, analysis, extra);
|
||||
|
||||
let (mod_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) =
|
||||
hardware_tasks::codegen(app, analysis, extra);
|
||||
|
||||
let (mod_app_software_tasks, root_software_tasks, user_software_tasks) =
|
||||
software_tasks::codegen(app, analysis, extra);
|
||||
|
||||
let mod_app_dispatchers = dispatchers::codegen(app, analysis, extra);
|
||||
let mod_app_timer_queue = timer_queue::codegen(app, analysis, extra);
|
||||
let user_imports = &app.user_imports;
|
||||
let user_code = &app.user_code;
|
||||
let name = &app.name;
|
||||
let device = &extra.device;
|
||||
|
||||
let monotonic_parts: Vec<_> = app
|
||||
.monotonics
|
||||
.iter()
|
||||
.map(|(_, monotonic)| {
|
||||
let name = &monotonic.ident;
|
||||
let name_str = &name.to_string();
|
||||
let cfgs = &monotonic.cfgs;
|
||||
let ident = util::monotonic_ident(name_str);
|
||||
let doc = &format!(
|
||||
"This module holds the static implementation for `{}::now()`",
|
||||
name_str
|
||||
);
|
||||
|
||||
let default_monotonic = if monotonic.args.default {
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
pub use #name::now;
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
#default_monotonic
|
||||
|
||||
#[doc = #doc]
|
||||
#[allow(non_snake_case)]
|
||||
#(#cfgs)*
|
||||
pub mod #name {
|
||||
|
||||
/// Read the current time from this monotonic
|
||||
pub fn now() -> <super::super::#name as rtic::Monotonic>::Instant {
|
||||
rtic::export::interrupt::free(|_| {
|
||||
use rtic::Monotonic as _;
|
||||
if let Some(m) = unsafe{ &mut *super::super::#ident.get_mut() } {
|
||||
m.now()
|
||||
} else {
|
||||
<super::super::#name as rtic::Monotonic>::zero()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let monotonics = if monotonic_parts.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(
|
||||
pub use rtic::Monotonic as _;
|
||||
|
||||
/// Holds static methods for each monotonic.
|
||||
pub mod monotonics {
|
||||
#(#monotonic_parts)*
|
||||
}
|
||||
)
|
||||
};
|
||||
let rt_err = util::rt_err_ident();
|
||||
|
||||
quote!(
|
||||
/// The RTIC application module
|
||||
pub mod #name {
|
||||
/// Always include the device crate which contains the vector table
|
||||
use #device as #rt_err;
|
||||
|
||||
#monotonics
|
||||
|
||||
#(#user_imports)*
|
||||
|
||||
/// User code from within the module
|
||||
#(#user_code)*
|
||||
/// User code end
|
||||
|
||||
#(#user)*
|
||||
|
||||
#(#user_hardware_tasks)*
|
||||
|
||||
#(#user_software_tasks)*
|
||||
|
||||
#(#root)*
|
||||
|
||||
#mod_shared_resources
|
||||
|
||||
#mod_local_resources
|
||||
|
||||
#(#root_hardware_tasks)*
|
||||
|
||||
#(#root_software_tasks)*
|
||||
|
||||
/// App module
|
||||
#(#mod_app)*
|
||||
|
||||
#(#mod_app_shared_resources)*
|
||||
|
||||
#(#mod_app_local_resources)*
|
||||
|
||||
#(#mod_app_hardware_tasks)*
|
||||
|
||||
#(#mod_app_software_tasks)*
|
||||
|
||||
#(#mod_app_dispatchers)*
|
||||
|
||||
#(#mod_app_timer_queue)*
|
||||
|
||||
#(#mains)*
|
||||
}
|
||||
)
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
|
||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
||||
use rtic_syntax::ast::App;
|
||||
|
||||
/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
|
||||
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
|
||||
let mut stmts = vec![];
|
||||
|
||||
for ty in &analysis.send_types {
|
||||
stmts.push(quote!(rtic::export::assert_send::<#ty>();));
|
||||
}
|
||||
|
||||
for ty in &analysis.sync_types {
|
||||
stmts.push(quote!(rtic::export::assert_sync::<#ty>();));
|
||||
}
|
||||
|
||||
for (_, monotonic) in &app.monotonics {
|
||||
let ty = &monotonic.ty;
|
||||
stmts.push(quote!(rtic::export::assert_monotonic::<#ty>();));
|
||||
}
|
||||
|
||||
let device = &extra.device;
|
||||
let chunks_name = util::priority_mask_chunks_ident();
|
||||
let no_basepri_checks: Vec<_> = app
|
||||
.hardware_tasks
|
||||
.iter()
|
||||
.filter_map(|(_, task)| {
|
||||
if !util::is_exception(&task.args.binds) {
|
||||
let interrupt_name = &task.args.binds;
|
||||
let cfgs = &task.cfgs;
|
||||
Some(quote!(
|
||||
#(#cfgs)*
|
||||
if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) {
|
||||
::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base");
|
||||
}
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let const_check = quote! {
|
||||
const _CONST_CHECK: () = {
|
||||
if !rtic::export::have_basepri() {
|
||||
#(#no_basepri_checks)*
|
||||
} else {
|
||||
// TODO: Add armv7 checks here
|
||||
}
|
||||
};
|
||||
|
||||
let _ = _CONST_CHECK;
|
||||
};
|
||||
|
||||
stmts.push(const_check);
|
||||
|
||||
stmts
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::ast::App;
|
||||
|
||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
||||
|
||||
/// Generates task dispatchers
|
||||
pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStream2> {
|
||||
let mut items = vec![];
|
||||
|
||||
let interrupts = &analysis.interrupts;
|
||||
|
||||
for (&level, channel) in &analysis.channels {
|
||||
let mut stmts = vec![];
|
||||
|
||||
let variants = channel
|
||||
.tasks
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let cfgs = &app.software_tasks[name].cfgs;
|
||||
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#name
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// For future use
|
||||
// let doc = format!(
|
||||
// "Software tasks to be dispatched at priority level {}",
|
||||
// level,
|
||||
// );
|
||||
let t = util::spawn_t_ident(level);
|
||||
items.push(quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Copy)]
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
pub enum #t {
|
||||
#(#variants,)*
|
||||
}
|
||||
));
|
||||
|
||||
let n = util::capacity_literal(channel.capacity as usize + 1);
|
||||
let rq = util::rq_ident(level);
|
||||
let (rq_ty, rq_expr) = {
|
||||
(
|
||||
quote!(rtic::export::SCRQ<#t, #n>),
|
||||
quote!(rtic::export::Queue::new()),
|
||||
)
|
||||
};
|
||||
|
||||
// For future use
|
||||
// let doc = format!(
|
||||
// "Queue of tasks ready to be dispatched at priority level {}",
|
||||
// level
|
||||
// );
|
||||
items.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
static #rq: rtic::RacyCell<#rq_ty> = rtic::RacyCell::new(#rq_expr);
|
||||
));
|
||||
|
||||
let arms = channel
|
||||
.tasks
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let task = &app.software_tasks[name];
|
||||
let cfgs = &task.cfgs;
|
||||
let fq = util::fq_ident(name);
|
||||
let inputs = util::inputs_ident(name);
|
||||
let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
|
||||
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#t::#name => {
|
||||
let #tupled =
|
||||
(&*#inputs
|
||||
.get())
|
||||
.get_unchecked(usize::from(index))
|
||||
.as_ptr()
|
||||
.read();
|
||||
(&mut *#fq.get_mut()).split().0.enqueue_unchecked(index);
|
||||
let priority = &rtic::export::Priority::new(PRIORITY);
|
||||
#name(
|
||||
#name::Context::new(priority)
|
||||
#(,#pats)*
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
stmts.push(quote!(
|
||||
while let Some((task, index)) = (&mut *#rq.get_mut()).split().1.dequeue() {
|
||||
match task {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
let doc = format!("Interrupt handler to dispatch tasks at priority {}", level);
|
||||
let interrupt = util::suffixed(&interrupts[&level].0.to_string());
|
||||
let attribute = &interrupts[&level].1.attrs;
|
||||
items.push(quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[doc = #doc]
|
||||
#[no_mangle]
|
||||
#(#attribute)*
|
||||
unsafe fn #interrupt() {
|
||||
/// The priority of this interrupt handler
|
||||
const PRIORITY: u8 = #level;
|
||||
|
||||
rtic::export::run(PRIORITY, || {
|
||||
#(#stmts)*
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
items
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::{ast::App, Context};
|
||||
|
||||
use crate::{
|
||||
analyze::Analysis,
|
||||
check::Extra,
|
||||
codegen::{local_resources_struct, module, shared_resources_struct},
|
||||
};
|
||||
|
||||
/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s)
|
||||
pub fn codegen(
|
||||
app: &App,
|
||||
analysis: &Analysis,
|
||||
extra: &Extra,
|
||||
) -> (
|
||||
// mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors
|
||||
Vec<TokenStream2>,
|
||||
// root_hardware_tasks -- items that must be placed in the root of the crate:
|
||||
// - `${task}Locals` structs
|
||||
// - `${task}Resources` structs
|
||||
// - `${task}` modules
|
||||
Vec<TokenStream2>,
|
||||
// user_hardware_tasks -- the `#[task]` functions written by the user
|
||||
Vec<TokenStream2>,
|
||||
) {
|
||||
let mut mod_app = vec![];
|
||||
let mut root = vec![];
|
||||
let mut user_tasks = vec![];
|
||||
|
||||
for (name, task) in &app.hardware_tasks {
|
||||
let symbol = task.args.binds.clone();
|
||||
let priority = task.args.priority;
|
||||
let cfgs = &task.cfgs;
|
||||
let attrs = &task.attrs;
|
||||
let user_hardware_task_isr_doc = &format!(" User HW task ISR trampoline for {name}");
|
||||
|
||||
mod_app.push(quote!(
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
#[doc = #user_hardware_task_isr_doc]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
unsafe fn #symbol() {
|
||||
const PRIORITY: u8 = #priority;
|
||||
|
||||
rtic::export::run(PRIORITY, || {
|
||||
#name(
|
||||
#name::Context::new(&rtic::export::Priority::new(PRIORITY))
|
||||
)
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
let mut shared_needs_lt = false;
|
||||
let mut local_needs_lt = false;
|
||||
|
||||
// `${task}Locals`
|
||||
if !task.args.local_resources.is_empty() {
|
||||
let (item, constructor) = local_resources_struct::codegen(
|
||||
Context::HardwareTask(name),
|
||||
&mut local_needs_lt,
|
||||
app,
|
||||
);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
// `${task}Resources`
|
||||
if !task.args.shared_resources.is_empty() {
|
||||
let (item, constructor) = shared_resources_struct::codegen(
|
||||
Context::HardwareTask(name),
|
||||
&mut shared_needs_lt,
|
||||
app,
|
||||
);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
root.push(module::codegen(
|
||||
Context::HardwareTask(name),
|
||||
shared_needs_lt,
|
||||
local_needs_lt,
|
||||
app,
|
||||
analysis,
|
||||
extra,
|
||||
));
|
||||
|
||||
let user_hardware_task_doc = &format!(" User HW task: {name}");
|
||||
if !task.is_extern {
|
||||
let attrs = &task.attrs;
|
||||
let cfgs = &task.cfgs;
|
||||
let context = &task.context;
|
||||
let stmts = &task.stmts;
|
||||
user_tasks.push(quote!(
|
||||
#[doc = #user_hardware_task_doc]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
#[allow(non_snake_case)]
|
||||
fn #name(#context: #name::Context) {
|
||||
use rtic::Mutex as _;
|
||||
use rtic::mutex::prelude::*;
|
||||
|
||||
#(#stmts)*
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
(mod_app, root, user_tasks)
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::{ast::App, Context};
|
||||
|
||||
use crate::{
|
||||
analyze::Analysis,
|
||||
check::Extra,
|
||||
codegen::{local_resources_struct, module, shared_resources_struct},
|
||||
};
|
||||
|
||||
/// Generates support code for `#[idle]` functions
|
||||
pub fn codegen(
|
||||
app: &App,
|
||||
analysis: &Analysis,
|
||||
extra: &Extra,
|
||||
) -> (
|
||||
// mod_app_idle -- the `${idle}Resources` constructor
|
||||
Vec<TokenStream2>,
|
||||
// root_idle -- items that must be placed in the root of the crate:
|
||||
// - the `${idle}Locals` struct
|
||||
// - the `${idle}Resources` struct
|
||||
// - the `${idle}` module, which contains types like `${idle}::Context`
|
||||
Vec<TokenStream2>,
|
||||
// user_idle
|
||||
Option<TokenStream2>,
|
||||
// call_idle
|
||||
TokenStream2,
|
||||
) {
|
||||
if let Some(idle) = &app.idle {
|
||||
let mut shared_needs_lt = false;
|
||||
let mut local_needs_lt = false;
|
||||
let mut mod_app = vec![];
|
||||
let mut root_idle = vec![];
|
||||
|
||||
let name = &idle.name;
|
||||
|
||||
if !idle.args.shared_resources.is_empty() {
|
||||
let (item, constructor) =
|
||||
shared_resources_struct::codegen(Context::Idle, &mut shared_needs_lt, app);
|
||||
|
||||
root_idle.push(item);
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
if !idle.args.local_resources.is_empty() {
|
||||
let (item, constructor) =
|
||||
local_resources_struct::codegen(Context::Idle, &mut local_needs_lt, app);
|
||||
|
||||
root_idle.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
root_idle.push(module::codegen(
|
||||
Context::Idle,
|
||||
shared_needs_lt,
|
||||
local_needs_lt,
|
||||
app,
|
||||
analysis,
|
||||
extra,
|
||||
));
|
||||
let idle_doc = " User provided idle function".to_string();
|
||||
|
||||
let attrs = &idle.attrs;
|
||||
let context = &idle.context;
|
||||
let stmts = &idle.stmts;
|
||||
let user_idle = Some(quote!(
|
||||
#(#attrs)*
|
||||
#[doc = #idle_doc]
|
||||
#[allow(non_snake_case)]
|
||||
fn #name(#context: #name::Context) -> ! {
|
||||
use rtic::Mutex as _;
|
||||
use rtic::mutex::prelude::*;
|
||||
|
||||
#(#stmts)*
|
||||
}
|
||||
));
|
||||
|
||||
let call_idle = quote!(#name(
|
||||
#name::Context::new(&rtic::export::Priority::new(0))
|
||||
));
|
||||
|
||||
(mod_app, root_idle, user_idle, call_idle)
|
||||
} else {
|
||||
(
|
||||
vec![],
|
||||
vec![],
|
||||
None,
|
||||
quote!(loop {
|
||||
rtic::export::nop()
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,468 +0,0 @@
|
|||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::{ast::App, Context};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn codegen(
|
||||
ctxt: Context,
|
||||
shared_resources_tick: bool,
|
||||
local_resources_tick: bool,
|
||||
app: &App,
|
||||
analysis: &Analysis,
|
||||
extra: &Extra,
|
||||
) -> TokenStream2 {
|
||||
let mut items = vec![];
|
||||
let mut module_items = vec![];
|
||||
let mut fields = vec![];
|
||||
let mut values = vec![];
|
||||
|
||||
let name = ctxt.ident(app);
|
||||
|
||||
let mut lt = None;
|
||||
match ctxt {
|
||||
Context::Init => {
|
||||
fields.push(quote!(
|
||||
/// Core (Cortex-M) peripherals
|
||||
pub core: rtic::export::Peripherals
|
||||
));
|
||||
|
||||
if extra.peripherals {
|
||||
let device = &extra.device;
|
||||
|
||||
fields.push(quote!(
|
||||
/// Device peripherals
|
||||
pub device: #device::Peripherals
|
||||
));
|
||||
|
||||
values.push(quote!(device: #device::Peripherals::steal()));
|
||||
}
|
||||
|
||||
lt = Some(quote!('a));
|
||||
fields.push(quote!(
|
||||
/// Critical section token for init
|
||||
pub cs: rtic::export::CriticalSection<#lt>
|
||||
));
|
||||
|
||||
values.push(quote!(cs: rtic::export::CriticalSection::new()));
|
||||
|
||||
values.push(quote!(core));
|
||||
}
|
||||
|
||||
Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {}
|
||||
}
|
||||
|
||||
// if ctxt.has_locals(app) {
|
||||
// let ident = util::locals_ident(ctxt, app);
|
||||
// module_items.push(quote!(
|
||||
// #[doc(inline)]
|
||||
// pub use super::#ident as Locals;
|
||||
// ));
|
||||
// }
|
||||
|
||||
if ctxt.has_local_resources(app) {
|
||||
let ident = util::local_resources_ident(ctxt, app);
|
||||
let lt = if local_resources_tick {
|
||||
lt = Some(quote!('a));
|
||||
Some(quote!('a))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
module_items.push(quote!(
|
||||
#[doc(inline)]
|
||||
pub use super::#ident as LocalResources;
|
||||
));
|
||||
|
||||
fields.push(quote!(
|
||||
/// Local Resources this task has access to
|
||||
pub local: #name::LocalResources<#lt>
|
||||
));
|
||||
|
||||
values.push(quote!(local: #name::LocalResources::new()));
|
||||
}
|
||||
|
||||
if ctxt.has_shared_resources(app) {
|
||||
let ident = util::shared_resources_ident(ctxt, app);
|
||||
let lt = if shared_resources_tick {
|
||||
lt = Some(quote!('a));
|
||||
Some(quote!('a))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
module_items.push(quote!(
|
||||
#[doc(inline)]
|
||||
pub use super::#ident as SharedResources;
|
||||
));
|
||||
|
||||
fields.push(quote!(
|
||||
/// Shared Resources this task has access to
|
||||
pub shared: #name::SharedResources<#lt>
|
||||
));
|
||||
|
||||
let priority = if ctxt.is_init() {
|
||||
None
|
||||
} else {
|
||||
Some(quote!(priority))
|
||||
};
|
||||
values.push(quote!(shared: #name::SharedResources::new(#priority)));
|
||||
}
|
||||
|
||||
if let Context::Init = ctxt {
|
||||
let monotonic_types: Vec<_> = app
|
||||
.monotonics
|
||||
.iter()
|
||||
.map(|(_, monotonic)| {
|
||||
let cfgs = &monotonic.cfgs;
|
||||
let mono = &monotonic.ty;
|
||||
quote! {
|
||||
#(#cfgs)*
|
||||
pub #mono
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let internal_monotonics_ident = util::mark_internal_name("Monotonics");
|
||||
|
||||
items.push(quote!(
|
||||
/// Monotonics used by the system
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct #internal_monotonics_ident(
|
||||
#(#monotonic_types),*
|
||||
);
|
||||
));
|
||||
|
||||
module_items.push(quote!(
|
||||
#[doc(inline)]
|
||||
pub use super::#internal_monotonics_ident as Monotonics;
|
||||
));
|
||||
}
|
||||
|
||||
let doc = match ctxt {
|
||||
Context::Idle => " Idle loop",
|
||||
Context::Init => " Initialization function",
|
||||
Context::HardwareTask(_) => " Hardware task",
|
||||
Context::SoftwareTask(_) => " Software task",
|
||||
};
|
||||
|
||||
let v = Vec::new();
|
||||
let cfgs = match ctxt {
|
||||
Context::HardwareTask(t) => {
|
||||
&app.hardware_tasks[t].cfgs
|
||||
// ...
|
||||
}
|
||||
Context::SoftwareTask(t) => {
|
||||
&app.software_tasks[t].cfgs
|
||||
// ...
|
||||
}
|
||||
_ => &v,
|
||||
};
|
||||
|
||||
let core = if ctxt.is_init() {
|
||||
Some(quote!(core: rtic::export::Peripherals,))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let priority = if ctxt.is_init() {
|
||||
None
|
||||
} else {
|
||||
Some(quote!(priority: &#lt rtic::export::Priority))
|
||||
};
|
||||
|
||||
let internal_context_name = util::internal_task_ident(name, "Context");
|
||||
|
||||
items.push(quote!(
|
||||
/// Execution context
|
||||
#(#cfgs)*
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct #internal_context_name<#lt> {
|
||||
#(#fields,)*
|
||||
}
|
||||
|
||||
#(#cfgs)*
|
||||
impl<#lt> #internal_context_name<#lt> {
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub unsafe fn new(#core #priority) -> Self {
|
||||
#internal_context_name {
|
||||
#(#values,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
module_items.push(quote!(
|
||||
#[doc(inline)]
|
||||
#(#cfgs)*
|
||||
pub use super::#internal_context_name as Context;
|
||||
));
|
||||
|
||||
if let Context::SoftwareTask(..) = ctxt {
|
||||
let spawnee = &app.software_tasks[name];
|
||||
let priority = spawnee.args.priority;
|
||||
let t = util::spawn_t_ident(priority);
|
||||
let cfgs = &spawnee.cfgs;
|
||||
let (args, tupled, untupled, ty) = util::regroup_inputs(&spawnee.inputs);
|
||||
let args = &args;
|
||||
let tupled = &tupled;
|
||||
let fq = util::fq_ident(name);
|
||||
let rq = util::rq_ident(priority);
|
||||
let inputs = util::inputs_ident(name);
|
||||
|
||||
let device = &extra.device;
|
||||
let enum_ = util::interrupt_ident();
|
||||
let interrupt = &analysis
|
||||
.interrupts
|
||||
.get(&priority)
|
||||
.expect("RTIC-ICE: interrupt identifer not found")
|
||||
.0;
|
||||
|
||||
let internal_spawn_ident = util::internal_task_ident(name, "spawn");
|
||||
|
||||
// Spawn caller
|
||||
items.push(quote!(
|
||||
|
||||
/// Spawns the task directly
|
||||
#(#cfgs)*
|
||||
pub fn #internal_spawn_ident(#(#args,)*) -> Result<(), #ty> {
|
||||
let input = #tupled;
|
||||
|
||||
unsafe {
|
||||
if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) {
|
||||
(&mut *#inputs
|
||||
.get_mut())
|
||||
.get_unchecked_mut(usize::from(index))
|
||||
.as_mut_ptr()
|
||||
.write(input);
|
||||
|
||||
rtic::export::interrupt::free(|_| {
|
||||
(&mut *#rq.get_mut()).enqueue_unchecked((#t::#name, index));
|
||||
});
|
||||
|
||||
rtic::pend(#device::#enum_::#interrupt);
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(input)
|
||||
}
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
module_items.push(quote!(
|
||||
#[doc(inline)]
|
||||
#(#cfgs)*
|
||||
pub use super::#internal_spawn_ident as spawn;
|
||||
));
|
||||
|
||||
// Schedule caller
|
||||
for (_, monotonic) in &app.monotonics {
|
||||
let instants = util::monotonic_instants_ident(name, &monotonic.ident);
|
||||
let monotonic_name = monotonic.ident.to_string();
|
||||
|
||||
let tq = util::tq_ident(&monotonic.ident.to_string());
|
||||
let t = util::schedule_t_ident();
|
||||
let m = &monotonic.ident;
|
||||
let cfgs = &monotonic.cfgs;
|
||||
let m_ident = util::monotonic_ident(&monotonic_name);
|
||||
let m_isr = &monotonic.args.binds;
|
||||
let enum_ = util::interrupt_ident();
|
||||
let spawn_handle_string = format!("{}::SpawnHandle", m);
|
||||
|
||||
let (enable_interrupt, pend) = if &*m_isr.to_string() == "SysTick" {
|
||||
(
|
||||
quote!(core::mem::transmute::<_, rtic::export::SYST>(()).enable_interrupt()),
|
||||
quote!(rtic::export::SCB::set_pendst()),
|
||||
)
|
||||
} else {
|
||||
let rt_err = util::rt_err_ident();
|
||||
(
|
||||
quote!(rtic::export::NVIC::unmask(#rt_err::#enum_::#m_isr)),
|
||||
quote!(rtic::pend(#rt_err::#enum_::#m_isr)),
|
||||
)
|
||||
};
|
||||
|
||||
let tq_marker = &util::timer_queue_marker_ident();
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
// items.push(quote!(#[doc = #doc]));
|
||||
let internal_spawn_handle_ident =
|
||||
util::internal_monotonics_ident(name, m, "SpawnHandle");
|
||||
let internal_spawn_at_ident = util::internal_monotonics_ident(name, m, "spawn_at");
|
||||
let internal_spawn_after_ident =
|
||||
util::internal_monotonics_ident(name, m, "spawn_after");
|
||||
|
||||
if monotonic.args.default {
|
||||
module_items.push(quote!(
|
||||
#(#cfgs)*
|
||||
pub use #m::spawn_after;
|
||||
#(#cfgs)*
|
||||
pub use #m::spawn_at;
|
||||
#(#cfgs)*
|
||||
pub use #m::SpawnHandle;
|
||||
));
|
||||
}
|
||||
module_items.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#(#cfgs)*
|
||||
pub mod #m {
|
||||
pub use super::super::#internal_spawn_after_ident as spawn_after;
|
||||
pub use super::super::#internal_spawn_at_ident as spawn_at;
|
||||
pub use super::super::#internal_spawn_handle_ident as SpawnHandle;
|
||||
}
|
||||
));
|
||||
|
||||
items.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#(#cfgs)*
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct #internal_spawn_handle_ident {
|
||||
#[doc(hidden)]
|
||||
marker: u32,
|
||||
}
|
||||
|
||||
#(#cfgs)*
|
||||
impl core::fmt::Debug for #internal_spawn_handle_ident {
|
||||
#[doc(hidden)]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct(#spawn_handle_string).finish()
|
||||
}
|
||||
}
|
||||
|
||||
#(#cfgs)*
|
||||
impl #internal_spawn_handle_ident {
|
||||
pub fn cancel(self) -> Result<#ty, ()> {
|
||||
rtic::export::interrupt::free(|_| unsafe {
|
||||
let tq = &mut *#tq.get_mut();
|
||||
if let Some((_task, index)) = tq.cancel_marker(self.marker) {
|
||||
// Get the message
|
||||
let msg = (&*#inputs
|
||||
.get())
|
||||
.get_unchecked(usize::from(index))
|
||||
.as_ptr()
|
||||
.read();
|
||||
// Return the index to the free queue
|
||||
(&mut *#fq.get_mut()).split().0.enqueue_unchecked(index);
|
||||
|
||||
Ok(msg)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Reschedule after
|
||||
#[inline]
|
||||
#(#cfgs)*
|
||||
pub fn reschedule_after(
|
||||
self,
|
||||
duration: <#m as rtic::Monotonic>::Duration
|
||||
) -> Result<Self, ()> {
|
||||
self.reschedule_at(monotonics::#m::now() + duration)
|
||||
}
|
||||
|
||||
/// Reschedule at
|
||||
#(#cfgs)*
|
||||
pub fn reschedule_at(
|
||||
self,
|
||||
instant: <#m as rtic::Monotonic>::Instant
|
||||
) -> Result<Self, ()> {
|
||||
rtic::export::interrupt::free(|_| unsafe {
|
||||
let marker = #tq_marker.get().read();
|
||||
#tq_marker.get_mut().write(marker.wrapping_add(1));
|
||||
|
||||
let tq = (&mut *#tq.get_mut());
|
||||
|
||||
tq.update_marker(self.marker, marker, instant, || #pend).map(|_| #name::#m::SpawnHandle { marker })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns the task after a set duration relative to the current time
|
||||
///
|
||||
/// This will use the time `Instant::new(0)` as baseline if called in `#[init]`,
|
||||
/// so if you use a non-resetable timer use `spawn_at` when in `#[init]`
|
||||
#(#cfgs)*
|
||||
#[allow(non_snake_case)]
|
||||
pub fn #internal_spawn_after_ident(
|
||||
duration: <#m as rtic::Monotonic>::Duration
|
||||
#(,#args)*
|
||||
) -> Result<#name::#m::SpawnHandle, #ty>
|
||||
{
|
||||
let instant = monotonics::#m::now();
|
||||
|
||||
#internal_spawn_at_ident(instant + duration #(,#untupled)*)
|
||||
}
|
||||
|
||||
#(#cfgs)*
|
||||
/// Spawns the task at a fixed time instant
|
||||
#[allow(non_snake_case)]
|
||||
pub fn #internal_spawn_at_ident(
|
||||
instant: <#m as rtic::Monotonic>::Instant
|
||||
#(,#args)*
|
||||
) -> Result<#name::#m::SpawnHandle, #ty> {
|
||||
unsafe {
|
||||
let input = #tupled;
|
||||
if let Some(index) = rtic::export::interrupt::free(|_| (&mut *#fq.get_mut()).dequeue()) {
|
||||
(&mut *#inputs
|
||||
.get_mut())
|
||||
.get_unchecked_mut(usize::from(index))
|
||||
.as_mut_ptr()
|
||||
.write(input);
|
||||
|
||||
(&mut *#instants
|
||||
.get_mut())
|
||||
.get_unchecked_mut(usize::from(index))
|
||||
.as_mut_ptr()
|
||||
.write(instant);
|
||||
|
||||
rtic::export::interrupt::free(|_| {
|
||||
let marker = #tq_marker.get().read();
|
||||
let nr = rtic::export::NotReady {
|
||||
instant,
|
||||
index,
|
||||
task: #t::#name,
|
||||
marker,
|
||||
};
|
||||
|
||||
#tq_marker.get_mut().write(#tq_marker.get().read().wrapping_add(1));
|
||||
|
||||
let tq = &mut *#tq.get_mut();
|
||||
|
||||
tq.enqueue_unchecked(
|
||||
nr,
|
||||
|| #enable_interrupt,
|
||||
|| #pend,
|
||||
(&mut *#m_ident.get_mut()).as_mut());
|
||||
|
||||
Ok(#name::#m::SpawnHandle { marker })
|
||||
})
|
||||
} else {
|
||||
Err(input)
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if items.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(
|
||||
#(#items)*
|
||||
#[allow(non_snake_case)]
|
||||
#(#cfgs)*
|
||||
#[doc = #doc]
|
||||
pub mod #name {
|
||||
#(#module_items)*
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::ast::App;
|
||||
|
||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
||||
|
||||
/// Generates code that runs before `#[init]`
|
||||
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
|
||||
let mut stmts = vec![];
|
||||
|
||||
let rt_err = util::rt_err_ident();
|
||||
|
||||
// Disable interrupts -- `init` must run with interrupts disabled
|
||||
stmts.push(quote!(rtic::export::interrupt::disable();));
|
||||
|
||||
// Populate the FreeQueue
|
||||
for (name, task) in &app.software_tasks {
|
||||
let cap = task.args.capacity;
|
||||
let cfgs = &task.cfgs;
|
||||
let fq_ident = util::fq_ident(name);
|
||||
|
||||
stmts.push(quote!(
|
||||
#(#cfgs)*
|
||||
(0..#cap).for_each(|i| (&mut *#fq_ident.get_mut()).enqueue_unchecked(i));
|
||||
));
|
||||
}
|
||||
|
||||
stmts.push(quote!(
|
||||
// To set the variable in cortex_m so the peripherals cannot be taken multiple times
|
||||
let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into();
|
||||
));
|
||||
|
||||
let device = &extra.device;
|
||||
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
|
||||
|
||||
// check that all dispatchers exists in the `Interrupt` enumeration regardless of whether
|
||||
// they are used or not
|
||||
let interrupt = util::interrupt_ident();
|
||||
for name in app.args.extern_interrupts.keys() {
|
||||
stmts.push(quote!(let _ = #rt_err::#interrupt::#name;));
|
||||
}
|
||||
|
||||
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
|
||||
|
||||
// Unmask interrupts and set their priorities
|
||||
for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| {
|
||||
if util::is_exception(&task.args.binds) {
|
||||
// We do exceptions in another pass
|
||||
None
|
||||
} else {
|
||||
Some((&task.args.priority, &task.args.binds))
|
||||
}
|
||||
})) {
|
||||
let es = format!(
|
||||
"Maximum priority used by interrupt vector '{}' is more than supported by hardware",
|
||||
name
|
||||
);
|
||||
// Compile time assert that this priority is supported by the device
|
||||
stmts.push(quote!(
|
||||
const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
|
||||
));
|
||||
|
||||
stmts.push(quote!(
|
||||
core.NVIC.set_priority(
|
||||
#rt_err::#interrupt::#name,
|
||||
rtic::export::logical2hw(#priority, #nvic_prio_bits),
|
||||
);
|
||||
));
|
||||
|
||||
// NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended
|
||||
// interrupt is implementation defined
|
||||
stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);));
|
||||
}
|
||||
|
||||
// Set exception priorities
|
||||
for (name, priority) in app.hardware_tasks.values().filter_map(|task| {
|
||||
if util::is_exception(&task.args.binds) {
|
||||
Some((&task.args.binds, task.args.priority))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
let es = format!(
|
||||
"Maximum priority used by interrupt vector '{}' is more than supported by hardware",
|
||||
name
|
||||
);
|
||||
// Compile time assert that this priority is supported by the device
|
||||
stmts.push(quote!(
|
||||
const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
|
||||
));
|
||||
|
||||
stmts.push(quote!(core.SCB.set_priority(
|
||||
rtic::export::SystemHandler::#name,
|
||||
rtic::export::logical2hw(#priority, #nvic_prio_bits),
|
||||
);));
|
||||
}
|
||||
|
||||
// Initialize monotonic's interrupts
|
||||
for (_, monotonic) in &app.monotonics {
|
||||
let priority = if let Some(prio) = monotonic.args.priority {
|
||||
quote! { #prio }
|
||||
} else {
|
||||
quote! { (1 << #nvic_prio_bits) }
|
||||
};
|
||||
let binds = &monotonic.args.binds;
|
||||
|
||||
let name = &monotonic.ident;
|
||||
let es = format!(
|
||||
"Maximum priority used by monotonic '{}' is more than supported by hardware",
|
||||
name
|
||||
);
|
||||
// Compile time assert that this priority is supported by the device
|
||||
stmts.push(quote!(
|
||||
const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); };
|
||||
));
|
||||
|
||||
let mono_type = &monotonic.ty;
|
||||
|
||||
if &*binds.to_string() == "SysTick" {
|
||||
stmts.push(quote!(
|
||||
core.SCB.set_priority(
|
||||
rtic::export::SystemHandler::SysTick,
|
||||
rtic::export::logical2hw(#priority, #nvic_prio_bits),
|
||||
);
|
||||
|
||||
// Always enable monotonic interrupts if they should never be off
|
||||
if !<#mono_type as rtic::Monotonic>::DISABLE_INTERRUPT_ON_EMPTY_QUEUE {
|
||||
core::mem::transmute::<_, rtic::export::SYST>(())
|
||||
.enable_interrupt();
|
||||
}
|
||||
));
|
||||
} else {
|
||||
stmts.push(quote!(
|
||||
core.NVIC.set_priority(
|
||||
#rt_err::#interrupt::#binds,
|
||||
rtic::export::logical2hw(#priority, #nvic_prio_bits),
|
||||
);
|
||||
|
||||
// Always enable monotonic interrupts if they should never be off
|
||||
if !<#mono_type as rtic::Monotonic>::DISABLE_INTERRUPT_ON_EMPTY_QUEUE {
|
||||
rtic::export::NVIC::unmask(#rt_err::#interrupt::#binds);
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
stmts
|
||||
}
|
|
@ -1,196 +0,0 @@
|
|||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::{analyze::Ownership, ast::App};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Generates `static` variables and shared resource proxies
|
||||
pub fn codegen(
|
||||
app: &App,
|
||||
analysis: &Analysis,
|
||||
extra: &Extra,
|
||||
) -> (
|
||||
// mod_app -- the `static` variables behind the proxies
|
||||
Vec<TokenStream2>,
|
||||
// mod_resources -- the `resources` module
|
||||
TokenStream2,
|
||||
) {
|
||||
let mut mod_app = vec![];
|
||||
let mut mod_resources = vec![];
|
||||
|
||||
for (name, res) in &app.shared_resources {
|
||||
let cfgs = &res.cfgs;
|
||||
let ty = &res.ty;
|
||||
let mangled_name = &util::static_shared_resource_ident(name);
|
||||
|
||||
let attrs = &res.attrs;
|
||||
|
||||
// late resources in `util::link_section_uninit`
|
||||
// unless user specifies custom link section
|
||||
let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) {
|
||||
None
|
||||
} else {
|
||||
Some(util::link_section_uninit())
|
||||
};
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
mod_app.push(quote!(
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
#section
|
||||
static #mangled_name: rtic::RacyCell<core::mem::MaybeUninit<#ty>> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit());
|
||||
));
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
|
||||
let shared_name = util::need_to_lock_ident(name);
|
||||
|
||||
if !res.properties.lock_free {
|
||||
mod_resources.push(quote!(
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#(#cfgs)*
|
||||
pub struct #shared_name<'a> {
|
||||
priority: &'a Priority,
|
||||
}
|
||||
|
||||
#(#cfgs)*
|
||||
impl<'a> #shared_name<'a> {
|
||||
#[inline(always)]
|
||||
pub unsafe fn new(priority: &'a Priority) -> Self {
|
||||
#shared_name { priority }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub unsafe fn priority(&self) -> &Priority {
|
||||
self.priority
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
let ptr = quote!(
|
||||
#(#cfgs)*
|
||||
#mangled_name.get_mut() as *mut _
|
||||
);
|
||||
|
||||
let ceiling = match analysis.ownerships.get(name) {
|
||||
Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority,
|
||||
Some(Ownership::Contended { ceiling }) => *ceiling,
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!());
|
||||
|
||||
mod_app.push(util::impl_mutex(
|
||||
extra,
|
||||
cfgs,
|
||||
true,
|
||||
&shared_name,
|
||||
"e!(#ty),
|
||||
ceiling,
|
||||
&ptr,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mod_resources = if mod_resources.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(mod shared_resources {
|
||||
use rtic::export::Priority;
|
||||
|
||||
#(#mod_resources)*
|
||||
})
|
||||
};
|
||||
|
||||
// Computing mapping of used interrupts to masks
|
||||
let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id));
|
||||
|
||||
let mut prio_to_masks = HashMap::new();
|
||||
let device = &extra.device;
|
||||
let mut uses_exceptions_with_resources = false;
|
||||
|
||||
let mut mask_ids = Vec::new();
|
||||
|
||||
for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| {
|
||||
if !util::is_exception(&task.args.binds) {
|
||||
Some((&task.args.priority, &task.args.binds))
|
||||
} else {
|
||||
// If any resource to the exception uses non-lock-free or non-local resources this is
|
||||
// not allwed on thumbv6.
|
||||
uses_exceptions_with_resources = uses_exceptions_with_resources
|
||||
|| task
|
||||
.args
|
||||
.shared_resources
|
||||
.iter()
|
||||
.map(|(ident, access)| {
|
||||
if access.is_exclusive() {
|
||||
if let Some(r) = app.shared_resources.get(ident) {
|
||||
!r.properties.lock_free
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.any(|v| v);
|
||||
|
||||
None
|
||||
}
|
||||
})) {
|
||||
#[allow(clippy::or_fun_call)]
|
||||
let v = prio_to_masks.entry(priority - 1).or_insert(Vec::new());
|
||||
v.push(quote!(#device::Interrupt::#name as u32));
|
||||
mask_ids.push(quote!(#device::Interrupt::#name as u32));
|
||||
}
|
||||
|
||||
// Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts
|
||||
|
||||
let mut mask_arr = Vec::new();
|
||||
// NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec
|
||||
for i in 0..3 {
|
||||
let v = if let Some(v) = prio_to_masks.get(&i) {
|
||||
v.clone()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
mask_arr.push(quote!(
|
||||
rtic::export::create_mask([#(#v),*])
|
||||
));
|
||||
}
|
||||
|
||||
// Generate a constant for the number of chunks needed by Mask.
|
||||
let chunks_name = util::priority_mask_chunks_ident();
|
||||
mod_app.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]);
|
||||
));
|
||||
|
||||
let masks_name = util::priority_masks_ident();
|
||||
mod_app.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*];
|
||||
));
|
||||
|
||||
if uses_exceptions_with_resources {
|
||||
mod_app.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic();
|
||||
));
|
||||
}
|
||||
|
||||
(mod_app, mod_resources)
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::{ast::App, Context};
|
||||
|
||||
use crate::{
|
||||
analyze::Analysis,
|
||||
check::Extra,
|
||||
codegen::{local_resources_struct, module, shared_resources_struct, util},
|
||||
};
|
||||
|
||||
pub fn codegen(
|
||||
app: &App,
|
||||
analysis: &Analysis,
|
||||
extra: &Extra,
|
||||
) -> (
|
||||
// mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors
|
||||
Vec<TokenStream2>,
|
||||
// root_software_tasks -- items that must be placed in the root of the crate:
|
||||
// - `${task}Locals` structs
|
||||
// - `${task}Resources` structs
|
||||
// - `${task}` modules
|
||||
Vec<TokenStream2>,
|
||||
// user_software_tasks -- the `#[task]` functions written by the user
|
||||
Vec<TokenStream2>,
|
||||
) {
|
||||
let mut mod_app = vec![];
|
||||
let mut root = vec![];
|
||||
let mut user_tasks = vec![];
|
||||
|
||||
for (name, task) in &app.software_tasks {
|
||||
let inputs = &task.inputs;
|
||||
let cfgs = &task.cfgs;
|
||||
let (_, _, _, input_ty) = util::regroup_inputs(inputs);
|
||||
|
||||
let cap = task.args.capacity;
|
||||
let cap_lit = util::capacity_literal(cap as usize);
|
||||
let cap_lit_p1 = util::capacity_literal(cap as usize + 1);
|
||||
|
||||
// Create free queues and inputs / instants buffers
|
||||
let fq = util::fq_ident(name);
|
||||
|
||||
#[allow(clippy::redundant_closure)]
|
||||
let (fq_ty, fq_expr, mk_uninit): (_, _, Box<dyn Fn() -> Option<_>>) = {
|
||||
(
|
||||
quote!(rtic::export::SCFQ<#cap_lit_p1>),
|
||||
quote!(rtic::export::Queue::new()),
|
||||
Box::new(|| Some(util::link_section_uninit())),
|
||||
)
|
||||
};
|
||||
mod_app.push(quote!(
|
||||
// /// Queue version of a free-list that keeps track of empty slots in
|
||||
// /// the following buffers
|
||||
#(#cfgs)*
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[doc(hidden)]
|
||||
static #fq: rtic::RacyCell<#fq_ty> = rtic::RacyCell::new(#fq_expr);
|
||||
));
|
||||
|
||||
let elems = &(0..cap)
|
||||
.map(|_| quote!(core::mem::MaybeUninit::uninit()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (_, monotonic) in &app.monotonics {
|
||||
let instants = util::monotonic_instants_ident(name, &monotonic.ident);
|
||||
let mono_type = &monotonic.ty;
|
||||
let cfgs = &monotonic.cfgs;
|
||||
|
||||
let uninit = mk_uninit();
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
mod_app.push(quote!(
|
||||
#uninit
|
||||
// /// Buffer that holds the instants associated to the inputs of a task
|
||||
// #[doc = #doc]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[doc(hidden)]
|
||||
#(#cfgs)*
|
||||
static #instants:
|
||||
rtic::RacyCell<[core::mem::MaybeUninit<<#mono_type as rtic::Monotonic>::Instant>; #cap_lit]> =
|
||||
rtic::RacyCell::new([#(#elems,)*]);
|
||||
));
|
||||
}
|
||||
|
||||
let uninit = mk_uninit();
|
||||
let inputs_ident = util::inputs_ident(name);
|
||||
mod_app.push(quote!(
|
||||
#uninit
|
||||
// /// Buffer that holds the inputs of a task
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[doc(hidden)]
|
||||
#(#cfgs)*
|
||||
static #inputs_ident: rtic::RacyCell<[core::mem::MaybeUninit<#input_ty>; #cap_lit]> =
|
||||
rtic::RacyCell::new([#(#elems,)*]);
|
||||
));
|
||||
|
||||
// `${task}Resources`
|
||||
let mut shared_needs_lt = false;
|
||||
let mut local_needs_lt = false;
|
||||
|
||||
// `${task}Locals`
|
||||
if !task.args.local_resources.is_empty() {
|
||||
let (item, constructor) = local_resources_struct::codegen(
|
||||
Context::SoftwareTask(name),
|
||||
&mut local_needs_lt,
|
||||
app,
|
||||
);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
if !task.args.shared_resources.is_empty() {
|
||||
let (item, constructor) = shared_resources_struct::codegen(
|
||||
Context::SoftwareTask(name),
|
||||
&mut shared_needs_lt,
|
||||
app,
|
||||
);
|
||||
|
||||
root.push(item);
|
||||
|
||||
mod_app.push(constructor);
|
||||
}
|
||||
|
||||
if !&task.is_extern {
|
||||
let context = &task.context;
|
||||
let attrs = &task.attrs;
|
||||
let cfgs = &task.cfgs;
|
||||
let stmts = &task.stmts;
|
||||
let user_task_doc = format!(" User SW task {name}");
|
||||
user_tasks.push(quote!(
|
||||
#[doc = #user_task_doc]
|
||||
#(#attrs)*
|
||||
#(#cfgs)*
|
||||
#[allow(non_snake_case)]
|
||||
fn #name(#context: #name::Context #(,#inputs)*) {
|
||||
use rtic::Mutex as _;
|
||||
use rtic::mutex::prelude::*;
|
||||
|
||||
#(#stmts)*
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
root.push(module::codegen(
|
||||
Context::SoftwareTask(name),
|
||||
shared_needs_lt,
|
||||
local_needs_lt,
|
||||
app,
|
||||
analysis,
|
||||
extra,
|
||||
));
|
||||
}
|
||||
|
||||
(mod_app, root, user_tasks)
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use rtic_syntax::ast::App;
|
||||
|
||||
use crate::{analyze::Analysis, check::Extra, codegen::util};
|
||||
|
||||
/// Generates timer queues and timer queue handlers
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn codegen(app: &App, analysis: &Analysis, _extra: &Extra) -> Vec<TokenStream2> {
|
||||
let mut items = vec![];
|
||||
|
||||
if !app.monotonics.is_empty() {
|
||||
// Generate the marker counter used to track for `cancel` and `reschedule`
|
||||
let tq_marker = util::timer_queue_marker_ident();
|
||||
items.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
static #tq_marker: rtic::RacyCell<u32> = rtic::RacyCell::new(0);
|
||||
));
|
||||
|
||||
let t = util::schedule_t_ident();
|
||||
|
||||
// Enumeration of `schedule`-able tasks
|
||||
{
|
||||
let variants = app
|
||||
.software_tasks
|
||||
.iter()
|
||||
.map(|(name, task)| {
|
||||
let cfgs = &task.cfgs;
|
||||
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#name
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// For future use
|
||||
// let doc = "Tasks that can be scheduled".to_string();
|
||||
items.push(quote!(
|
||||
// #[doc = #doc]
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum #t {
|
||||
#(#variants,)*
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for (_, monotonic) in &app.monotonics {
|
||||
let monotonic_name = monotonic.ident.to_string();
|
||||
let tq = util::tq_ident(&monotonic_name);
|
||||
let t = util::schedule_t_ident();
|
||||
let mono_type = &monotonic.ty;
|
||||
let cfgs = &monotonic.cfgs;
|
||||
let m_ident = util::monotonic_ident(&monotonic_name);
|
||||
|
||||
// Static variables and resource proxy
|
||||
{
|
||||
// For future use
|
||||
// let doc = &format!("Timer queue for {}", monotonic_name);
|
||||
let cap: usize = app
|
||||
.software_tasks
|
||||
.iter()
|
||||
.map(|(_name, task)| task.args.capacity as usize)
|
||||
.sum();
|
||||
let n = util::capacity_literal(cap);
|
||||
let tq_ty = quote!(rtic::export::TimerQueue<#mono_type, #t, #n>);
|
||||
|
||||
// For future use
|
||||
// let doc = format!(" RTIC internal: {}:{}", file!(), line!());
|
||||
items.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#(#cfgs)*
|
||||
static #tq: rtic::RacyCell<#tq_ty> =
|
||||
rtic::RacyCell::new(rtic::export::TimerQueue(rtic::export::SortedLinkedList::new_u16()));
|
||||
));
|
||||
|
||||
let mono = util::monotonic_ident(&monotonic_name);
|
||||
// For future use
|
||||
// let doc = &format!("Storage for {}", monotonic_name);
|
||||
|
||||
items.push(quote!(
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#(#cfgs)*
|
||||
static #mono: rtic::RacyCell<Option<#mono_type>> = rtic::RacyCell::new(None);
|
||||
));
|
||||
}
|
||||
|
||||
// Timer queue handler
|
||||
{
|
||||
let enum_ = util::interrupt_ident();
|
||||
let rt_err = util::rt_err_ident();
|
||||
|
||||
let arms = app
|
||||
.software_tasks
|
||||
.iter()
|
||||
.map(|(name, task)| {
|
||||
let cfgs = &task.cfgs;
|
||||
let priority = task.args.priority;
|
||||
let rq = util::rq_ident(priority);
|
||||
let rqt = util::spawn_t_ident(priority);
|
||||
|
||||
// The interrupt that runs the task dispatcher
|
||||
let interrupt = &analysis.interrupts.get(&priority).expect("RTIC-ICE: interrupt not found").0;
|
||||
|
||||
let pend = {
|
||||
quote!(
|
||||
rtic::pend(#rt_err::#enum_::#interrupt);
|
||||
)
|
||||
};
|
||||
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#t::#name => {
|
||||
rtic::export::interrupt::free(|_| (&mut *#rq.get_mut()).split().0.enqueue_unchecked((#rqt::#name, index)));
|
||||
|
||||
#pend
|
||||
}
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let cfgs = &monotonic.cfgs;
|
||||
let bound_interrupt = &monotonic.args.binds;
|
||||
let disable_isr = if &*bound_interrupt.to_string() == "SysTick" {
|
||||
quote!(core::mem::transmute::<_, rtic::export::SYST>(()).disable_interrupt())
|
||||
} else {
|
||||
quote!(rtic::export::NVIC::mask(#rt_err::#enum_::#bound_interrupt))
|
||||
};
|
||||
|
||||
items.push(quote!(
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
#(#cfgs)*
|
||||
unsafe fn #bound_interrupt() {
|
||||
while let Some((task, index)) = rtic::export::interrupt::free(|_|
|
||||
if let Some(mono) = (&mut *#m_ident.get_mut()).as_mut() {
|
||||
(&mut *#tq.get_mut()).dequeue(|| #disable_isr, mono)
|
||||
} else {
|
||||
// We can only use the timer queue if `init` has returned, and it
|
||||
// writes the `Some(monotonic)` we are accessing here.
|
||||
core::hint::unreachable_unchecked()
|
||||
})
|
||||
{
|
||||
match task {
|
||||
#(#arms)*
|
||||
}
|
||||
}
|
||||
|
||||
rtic::export::interrupt::free(|_| if let Some(mono) = (&mut *#m_ident.get_mut()).as_mut() {
|
||||
mono.on_interrupt();
|
||||
});
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
items
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::quote;
|
||||
use rtic_syntax::{ast::App, Context};
|
||||
use syn::{Attribute, Ident, LitInt, PatType};
|
||||
|
||||
use crate::check::Extra;
|
||||
|
||||
const RTIC_INTERNAL: &str = "__rtic_internal";
|
||||
|
||||
/// Turns `capacity` into an unsuffixed integer literal
|
||||
pub fn capacity_literal(capacity: usize) -> LitInt {
|
||||
LitInt::new(&capacity.to_string(), Span::call_site())
|
||||
}
|
||||
|
||||
/// Identifier for the free queue
|
||||
pub fn fq_ident(task: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("{}_FQ", task))
|
||||
}
|
||||
|
||||
/// Generates a `Mutex` implementation
|
||||
pub fn impl_mutex(
|
||||
extra: &Extra,
|
||||
cfgs: &[Attribute],
|
||||
resources_prefix: bool,
|
||||
name: &Ident,
|
||||
ty: &TokenStream2,
|
||||
ceiling: u8,
|
||||
ptr: &TokenStream2,
|
||||
) -> TokenStream2 {
|
||||
let (path, priority) = if resources_prefix {
|
||||
(quote!(shared_resources::#name), quote!(self.priority()))
|
||||
} else {
|
||||
(quote!(#name), quote!(self.priority))
|
||||
};
|
||||
|
||||
let device = &extra.device;
|
||||
let masks_name = priority_masks_ident();
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
impl<'a> rtic::Mutex for #path<'a> {
|
||||
type T = #ty;
|
||||
|
||||
#[inline(always)]
|
||||
fn lock<RTIC_INTERNAL_R>(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R {
|
||||
/// Priority ceiling
|
||||
const CEILING: u8 = #ceiling;
|
||||
|
||||
unsafe {
|
||||
rtic::export::lock(
|
||||
#ptr,
|
||||
#priority,
|
||||
CEILING,
|
||||
#device::NVIC_PRIO_BITS,
|
||||
&#masks_name,
|
||||
f,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API)
|
||||
pub fn inputs_ident(task: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("{}_INPUTS", task))
|
||||
}
|
||||
|
||||
/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
|
||||
pub fn monotonic_instants_ident(task: &Ident, monotonic: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("{}_{}_INSTANTS", task, monotonic))
|
||||
}
|
||||
|
||||
pub fn interrupt_ident() -> Ident {
|
||||
let span = Span::call_site();
|
||||
Ident::new("interrupt", span)
|
||||
}
|
||||
|
||||
pub fn timer_queue_marker_ident() -> Ident {
|
||||
mark_internal_name("TIMER_QUEUE_MARKER")
|
||||
}
|
||||
|
||||
/// Whether `name` is an exception with configurable priority
|
||||
pub fn is_exception(name: &Ident) -> bool {
|
||||
let s = name.to_string();
|
||||
|
||||
matches!(
|
||||
&*s,
|
||||
"MemoryManagement"
|
||||
| "BusFault"
|
||||
| "UsageFault"
|
||||
| "SecureFault"
|
||||
| "SVCall"
|
||||
| "DebugMonitor"
|
||||
| "PendSV"
|
||||
| "SysTick"
|
||||
)
|
||||
}
|
||||
|
||||
/// Mark a name as internal
|
||||
pub fn mark_internal_name(name: &str) -> Ident {
|
||||
Ident::new(&format!("{}_{}", RTIC_INTERNAL, name), Span::call_site())
|
||||
}
|
||||
|
||||
/// Generate an internal identifier for monotonics
|
||||
pub fn internal_monotonics_ident(task: &Ident, monotonic: &Ident, ident_name: &str) -> Ident {
|
||||
mark_internal_name(&format!("{}_{}_{}", task, monotonic, ident_name,))
|
||||
}
|
||||
|
||||
/// Generate an internal identifier for tasks
|
||||
pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident {
|
||||
mark_internal_name(&format!("{}_{}", task, ident_name))
|
||||
}
|
||||
|
||||
fn link_section_index() -> usize {
|
||||
static INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
INDEX.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Add `link_section` attribute
|
||||
pub fn link_section_uninit() -> TokenStream2 {
|
||||
let section = format!(".uninit.rtic{}", link_section_index());
|
||||
|
||||
quote!(#[link_section = #section])
|
||||
}
|
||||
|
||||
// Regroups the inputs of a task
|
||||
//
|
||||
// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`]
|
||||
pub fn regroup_inputs(
|
||||
inputs: &[PatType],
|
||||
) -> (
|
||||
// args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
|
||||
Vec<TokenStream2>,
|
||||
// tupled e.g. `_0`, `(_0, _1)`
|
||||
TokenStream2,
|
||||
// untupled e.g. &[`_0`], &[`_0`, `_1`]
|
||||
Vec<TokenStream2>,
|
||||
// ty e.g. `Foo`, `(i32, i64)`
|
||||
TokenStream2,
|
||||
) {
|
||||
if inputs.len() == 1 {
|
||||
let ty = &inputs[0].ty;
|
||||
|
||||
(
|
||||
vec![quote!(_0: #ty)],
|
||||
quote!(_0),
|
||||
vec![quote!(_0)],
|
||||
quote!(#ty),
|
||||
)
|
||||
} else {
|
||||
let mut args = vec![];
|
||||
let mut pats = vec![];
|
||||
let mut tys = vec![];
|
||||
|
||||
for (i, input) in inputs.iter().enumerate() {
|
||||
let i = Ident::new(&format!("_{}", i), Span::call_site());
|
||||
let ty = &input.ty;
|
||||
|
||||
args.push(quote!(#i: #ty));
|
||||
|
||||
pats.push(quote!(#i));
|
||||
|
||||
tys.push(quote!(#ty));
|
||||
}
|
||||
|
||||
let tupled = {
|
||||
let pats = pats.clone();
|
||||
quote!((#(#pats,)*))
|
||||
};
|
||||
let ty = quote!((#(#tys,)*));
|
||||
(args, tupled, pats, ty)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ident for the name of the task
|
||||
pub fn get_task_name(ctxt: Context, app: &App) -> Ident {
|
||||
let s = match ctxt {
|
||||
Context::Init => app.init.name.to_string(),
|
||||
Context::Idle => app.idle.as_ref().unwrap().name.to_string(),
|
||||
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||
};
|
||||
|
||||
Ident::new(&s, Span::call_site())
|
||||
}
|
||||
|
||||
/// Generates a pre-reexport identifier for the "shared resources" struct
|
||||
pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident {
|
||||
let mut s = match ctxt {
|
||||
Context::Init => app.init.name.to_string(),
|
||||
Context::Idle => app.idle.as_ref().unwrap().name.to_string(),
|
||||
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||
};
|
||||
|
||||
s.push_str("SharedResources");
|
||||
|
||||
mark_internal_name(&s)
|
||||
}
|
||||
|
||||
/// Generates a pre-reexport identifier for the "local resources" struct
|
||||
pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident {
|
||||
let mut s = match ctxt {
|
||||
Context::Init => app.init.name.to_string(),
|
||||
Context::Idle => app.idle.as_ref().unwrap().name.to_string(),
|
||||
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
|
||||
};
|
||||
|
||||
s.push_str("LocalResources");
|
||||
|
||||
mark_internal_name(&s)
|
||||
}
|
||||
|
||||
/// Generates an identifier for a ready queue
|
||||
///
|
||||
/// There may be several task dispatchers, one for each priority level.
|
||||
/// The ready queues are SPSC queues
|
||||
pub fn rq_ident(priority: u8) -> Ident {
|
||||
mark_internal_name(&format!("P{}_RQ", priority))
|
||||
}
|
||||
|
||||
/// Generates an identifier for the `enum` of `schedule`-able tasks
|
||||
pub fn schedule_t_ident() -> Ident {
|
||||
Ident::new("SCHED_T", Span::call_site())
|
||||
}
|
||||
|
||||
/// Generates an identifier for the `enum` of `spawn`-able tasks
|
||||
///
|
||||
/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue
|
||||
/// for each of these `T` enums
|
||||
pub fn spawn_t_ident(priority: u8) -> Ident {
|
||||
Ident::new(&format!("P{}_T", priority), Span::call_site())
|
||||
}
|
||||
|
||||
/// Suffixed identifier
|
||||
pub fn suffixed(name: &str) -> Ident {
|
||||
let span = Span::call_site();
|
||||
Ident::new(name, span)
|
||||
}
|
||||
|
||||
/// Generates an identifier for a timer queue
|
||||
pub fn tq_ident(name: &str) -> Ident {
|
||||
mark_internal_name(&format!("TQ_{}", name))
|
||||
}
|
||||
|
||||
/// Generates an identifier for monotonic timer storage
|
||||
pub fn monotonic_ident(name: &str) -> Ident {
|
||||
mark_internal_name(&format!("MONOTONIC_STORAGE_{}", name))
|
||||
}
|
||||
|
||||
pub fn static_shared_resource_ident(name: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("shared_resource_{}", name))
|
||||
}
|
||||
|
||||
/// Generates an Ident for the number of 32 bit chunks used for Mask storage.
|
||||
pub fn priority_mask_chunks_ident() -> Ident {
|
||||
mark_internal_name("MASK_CHUNKS")
|
||||
}
|
||||
|
||||
pub fn priority_masks_ident() -> Ident {
|
||||
mark_internal_name("MASKS")
|
||||
}
|
||||
|
||||
pub fn static_local_resource_ident(name: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("local_resource_{}", name))
|
||||
}
|
||||
|
||||
pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident {
|
||||
mark_internal_name(&format!("local_{}_{}", task_name, name))
|
||||
}
|
||||
|
||||
pub fn need_to_lock_ident(name: &Ident) -> Ident {
|
||||
Ident::new(&format!("{}_that_needs_to_be_locked", name), name.span())
|
||||
}
|
||||
|
||||
/// The name to get better RT flag errors
|
||||
pub fn rt_err_ident() -> Ident {
|
||||
Ident::new(
|
||||
"you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml",
|
||||
Span::call_site(),
|
||||
)
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// NOTE these tests are specific to the Cortex-M port; `rtic-syntax` has a more extensive test suite
|
||||
// that tests functionality common to all the RTIC ports
|
||||
|
||||
mod single;
|
|
@ -1,40 +0,0 @@
|
|||
use quote::quote;
|
||||
use rtic_syntax::Settings;
|
||||
|
||||
#[test]
|
||||
fn analyze() {
|
||||
let mut settings = Settings::default();
|
||||
settings.parse_extern_interrupt = true;
|
||||
let (app, analysis) = rtic_syntax::parse2(
|
||||
// First interrupt is assigned to the highest priority dispatcher
|
||||
quote!(device = pac, dispatchers = [B, A]),
|
||||
quote!(
|
||||
mod app {
|
||||
#[shared]
|
||||
struct Shared {}
|
||||
|
||||
#[local]
|
||||
struct Local {}
|
||||
|
||||
#[init]
|
||||
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||
(Shared {}, Local {}, init::Monotonics())
|
||||
}
|
||||
|
||||
#[task(priority = 1)]
|
||||
fn a(_: a::Context) {}
|
||||
|
||||
#[task(priority = 2)]
|
||||
fn b(_: b::Context) {}
|
||||
}
|
||||
),
|
||||
settings,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let analysis = crate::analyze::app(analysis, &app);
|
||||
let interrupts = &analysis.interrupts;
|
||||
assert_eq!(interrupts.len(), 2);
|
||||
assert_eq!(interrupts[&2].0.to_string(), "B");
|
||||
assert_eq!(interrupts[&1].0.to_string(), "A");
|
||||
}
|
2
rtic-common/.gitignore
vendored
Normal file
2
rtic-common/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
Cargo.lock
|
||||
target/
|
16
rtic-common/CHANGELOG.md
Normal file
16
rtic-common/CHANGELOG.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
For each category, *Added*, *Changed*, *Fixed* add new entries at the top!
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
## [v1.0.0] - 2023-xx-xx
|
24
rtic-common/Cargo.toml
Normal file
24
rtic-common/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "rtic-common"
|
||||
version = "1.0.0-alpha.0"
|
||||
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"The Real-Time Interrupt-driven Concurrency developers",
|
||||
"Emil Fresk <emil.fresk@gmail.com>",
|
||||
"Henrik Tjäder <henrik@tjaders.com>",
|
||||
"Jorge Aparicio <jorge@japaric.io>",
|
||||
"Per Lindgren <per.lindgren@ltu.se>",
|
||||
]
|
||||
categories = ["concurrency", "embedded", "no-std", "asynchronous"]
|
||||
description = "rtic-common lib TODO"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
critical-section = "1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
testing = ["critical-section/std"]
|
26
rtic-common/src/dropper.rs
Normal file
26
rtic-common/src/dropper.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
//! A drop implementation runner.
|
||||
|
||||
/// Runs a closure on drop.
|
||||
pub struct OnDrop<F: FnOnce()> {
|
||||
f: core::mem::MaybeUninit<F>,
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> OnDrop<F> {
|
||||
/// Make a new droppper given a closure.
|
||||
pub fn new(f: F) -> Self {
|
||||
Self {
|
||||
f: core::mem::MaybeUninit::new(f),
|
||||
}
|
||||
}
|
||||
|
||||
/// Make it not run drop.
|
||||
pub fn defuse(self) {
|
||||
core::mem::forget(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.f.as_ptr().read()() }
|
||||
}
|
||||
}
|
13
rtic-common/src/lib.rs
Normal file
13
rtic-common/src/lib.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
//! Crate
|
||||
|
||||
#![no_std]
|
||||
#![deny(missing_docs)]
|
||||
//deny_warnings_placeholder_for_ci
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
pub mod dropper;
|
||||
pub mod wait_queue;
|
||||
pub mod waker_registration;
|
275
rtic-common/src/wait_queue.rs
Normal file
275
rtic-common/src/wait_queue.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
//! ...
|
||||
|
||||
use core::marker::PhantomPinned;
|
||||
use core::pin::Pin;
|
||||
use core::ptr::null_mut;
|
||||
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
||||
use core::task::Waker;
|
||||
use critical_section as cs;
|
||||
|
||||
/// A helper definition of a wait queue.
|
||||
pub type WaitQueue = LinkedList<Waker>;
|
||||
|
||||
/// A FIFO linked list for a wait queue.
|
||||
pub struct LinkedList<T> {
|
||||
head: AtomicPtr<Link<T>>, // UnsafeCell<*mut Link<T>>
|
||||
tail: AtomicPtr<Link<T>>,
|
||||
}
|
||||
|
||||
impl<T> LinkedList<T> {
|
||||
/// Create a new linked list.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
head: AtomicPtr::new(null_mut()),
|
||||
tail: AtomicPtr::new(null_mut()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> LinkedList<T> {
|
||||
const R: Ordering = Ordering::Relaxed;
|
||||
|
||||
/// Pop the first element in the queue.
|
||||
pub fn pop(&self) -> Option<T> {
|
||||
cs::with(|_| {
|
||||
// Make sure all previous writes are visible
|
||||
core::sync::atomic::fence(Ordering::SeqCst);
|
||||
|
||||
let head = self.head.load(Self::R);
|
||||
|
||||
// SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link
|
||||
if let Some(head_ref) = unsafe { head.as_ref() } {
|
||||
// Move head to the next element
|
||||
self.head.store(head_ref.next.load(Self::R), Self::R);
|
||||
|
||||
// We read the value at head
|
||||
let head_val = head_ref.val.clone();
|
||||
|
||||
let tail = self.tail.load(Self::R);
|
||||
if head == tail {
|
||||
// The queue is empty
|
||||
self.tail.store(null_mut(), Self::R);
|
||||
}
|
||||
|
||||
if let Some(next_ref) = unsafe { head_ref.next.load(Self::R).as_ref() } {
|
||||
next_ref.prev.store(null_mut(), Self::R);
|
||||
}
|
||||
|
||||
// Clear the pointers in the node.
|
||||
head_ref.next.store(null_mut(), Self::R);
|
||||
head_ref.prev.store(null_mut(), Self::R);
|
||||
head_ref.is_popped.store(true, Self::R);
|
||||
|
||||
return Some(head_val);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Put an element at the back of the queue.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The link must live until it is removed from the queue.
|
||||
pub unsafe fn push(&self, link: Pin<&Link<T>>) {
|
||||
cs::with(|_| {
|
||||
// Make sure all previous writes are visible
|
||||
core::sync::atomic::fence(Ordering::SeqCst);
|
||||
|
||||
let tail = self.tail.load(Self::R);
|
||||
|
||||
// SAFETY: This datastructure does not move the underlying value.
|
||||
let link = link.get_ref();
|
||||
|
||||
if let Some(tail_ref) = unsafe { tail.as_ref() } {
|
||||
// Queue is not empty
|
||||
link.prev.store(tail, Self::R);
|
||||
self.tail.store(link as *const _ as *mut _, Self::R);
|
||||
tail_ref.next.store(link as *const _ as *mut _, Self::R);
|
||||
} else {
|
||||
// Queue is empty
|
||||
self.tail.store(link as *const _ as *mut _, Self::R);
|
||||
self.head.store(link as *const _ as *mut _, Self::R);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if the queue is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.head.load(Self::R).is_null()
|
||||
}
|
||||
}
|
||||
|
||||
/// A link in the linked list.
|
||||
pub struct Link<T> {
|
||||
pub(crate) val: T,
|
||||
next: AtomicPtr<Link<T>>,
|
||||
prev: AtomicPtr<Link<T>>,
|
||||
is_popped: AtomicBool,
|
||||
_up: PhantomPinned,
|
||||
}
|
||||
|
||||
impl<T: Clone> Link<T> {
|
||||
const R: Ordering = Ordering::Relaxed;
|
||||
|
||||
/// Create a new link.
|
||||
pub const fn new(val: T) -> Self {
|
||||
Self {
|
||||
val,
|
||||
next: AtomicPtr::new(null_mut()),
|
||||
prev: AtomicPtr::new(null_mut()),
|
||||
is_popped: AtomicBool::new(false),
|
||||
_up: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if this link has been poped from the list.
|
||||
pub fn is_popped(&self) -> bool {
|
||||
self.is_popped.load(Self::R)
|
||||
}
|
||||
|
||||
/// Remove this link from a linked list.
|
||||
pub fn remove_from_list(&self, list: &LinkedList<T>) {
|
||||
cs::with(|_| {
|
||||
// Make sure all previous writes are visible
|
||||
core::sync::atomic::fence(Ordering::SeqCst);
|
||||
|
||||
let prev = self.prev.load(Self::R);
|
||||
let next = self.next.load(Self::R);
|
||||
self.is_popped.store(true, Self::R);
|
||||
|
||||
match unsafe { (prev.as_ref(), next.as_ref()) } {
|
||||
(None, None) => {
|
||||
// Not in the list or alone in the list, check if list head == node address
|
||||
let sp = self as *const _;
|
||||
|
||||
if sp == list.head.load(Ordering::Relaxed) {
|
||||
list.head.store(null_mut(), Self::R);
|
||||
list.tail.store(null_mut(), Self::R);
|
||||
}
|
||||
}
|
||||
(None, Some(next_ref)) => {
|
||||
// First in the list
|
||||
next_ref.prev.store(null_mut(), Self::R);
|
||||
list.head.store(next, Self::R);
|
||||
}
|
||||
(Some(prev_ref), None) => {
|
||||
// Last in the list
|
||||
prev_ref.next.store(null_mut(), Self::R);
|
||||
list.tail.store(prev, Self::R);
|
||||
}
|
||||
(Some(prev_ref), Some(next_ref)) => {
|
||||
// Somewhere in the list
|
||||
|
||||
// Connect the `prev.next` and `next.prev` with each other to remove the node
|
||||
prev_ref.next.store(next, Self::R);
|
||||
next_ref.prev.store(prev, Self::R);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<T: core::fmt::Debug + Clone> LinkedList<T> {
|
||||
fn print(&self) {
|
||||
cs::with(|_| {
|
||||
// Make sure all previous writes are visible
|
||||
core::sync::atomic::fence(Ordering::SeqCst);
|
||||
|
||||
let mut head = self.head.load(Self::R);
|
||||
let tail = self.tail.load(Self::R);
|
||||
|
||||
println!(
|
||||
"List - h = 0x{:x}, t = 0x{:x}",
|
||||
head as usize, tail as usize
|
||||
);
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
// SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link
|
||||
while let Some(head_ref) = unsafe { head.as_ref() } {
|
||||
println!(
|
||||
" {}: {:?}, s = 0x{:x}, n = 0x{:x}, p = 0x{:x}",
|
||||
i,
|
||||
head_ref.val,
|
||||
head as usize,
|
||||
head_ref.next.load(Ordering::Relaxed) as usize,
|
||||
head_ref.prev.load(Ordering::Relaxed) as usize
|
||||
);
|
||||
|
||||
head = head_ref.next.load(Self::R);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<T: core::fmt::Debug + Clone> Link<T> {
|
||||
fn print(&self) {
|
||||
cs::with(|_| {
|
||||
// Make sure all previous writes are visible
|
||||
core::sync::atomic::fence(Ordering::SeqCst);
|
||||
|
||||
println!("Link:");
|
||||
|
||||
println!(
|
||||
" val = {:?}, n = 0x{:x}, p = 0x{:x}",
|
||||
self.val,
|
||||
self.next.load(Ordering::Relaxed) as usize,
|
||||
self.prev.load(Ordering::Relaxed) as usize
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn linked_list() {
|
||||
let wq = LinkedList::<u32>::new();
|
||||
|
||||
let i1 = Link::new(10);
|
||||
let i2 = Link::new(11);
|
||||
let i3 = Link::new(12);
|
||||
let i4 = Link::new(13);
|
||||
let i5 = Link::new(14);
|
||||
|
||||
unsafe { wq.push(Pin::new_unchecked(&i1)) };
|
||||
unsafe { wq.push(Pin::new_unchecked(&i2)) };
|
||||
unsafe { wq.push(Pin::new_unchecked(&i3)) };
|
||||
unsafe { wq.push(Pin::new_unchecked(&i4)) };
|
||||
unsafe { wq.push(Pin::new_unchecked(&i5)) };
|
||||
|
||||
wq.print();
|
||||
|
||||
wq.pop();
|
||||
i1.print();
|
||||
|
||||
wq.print();
|
||||
|
||||
i4.remove_from_list(&wq);
|
||||
|
||||
wq.print();
|
||||
|
||||
// i1.remove_from_list(&wq);
|
||||
// wq.print();
|
||||
|
||||
println!("i2");
|
||||
i2.remove_from_list(&wq);
|
||||
wq.print();
|
||||
|
||||
println!("i3");
|
||||
i3.remove_from_list(&wq);
|
||||
wq.print();
|
||||
|
||||
println!("i5");
|
||||
i5.remove_from_list(&wq);
|
||||
wq.print();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue