diff --git a/.github/workflows/rp2040_hal.yml b/.github/workflows/rp2040_hal.yml index 52b75c24a..30aae2a75 100644 --- a/.github/workflows/rp2040_hal.yml +++ b/.github/workflows/rp2040_hal.yml @@ -2,6 +2,7 @@ on: [push, pull_request] name: Check rp2040-hal env: PACKAGE: rp2040-hal + TARGET: thumbv6m-none-eabi jobs: build: runs-on: ubuntu-20.04 @@ -9,12 +10,12 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - target: thumbv6m-none-eabi + target: ${{ env.TARGET }} - name: Install cargo-hack run: | curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin - name: Build rp2040-hal - run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=thumbv6m-none-eabi + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} - name: Build rp2040-hal-macros run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature test: @@ -23,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - target: thumbv6m-none-eabi + target: ${{ env.TARGET }} - name: Install cargo-hack run: | curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin @@ -42,7 +43,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: nightly-2024-01-30 - target: thumbv6m-none-eabi + target: ${{ env.TARGET }} - name: Install cargo-hack run: | curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin @@ -50,7 +51,7 @@ jobs: run: | curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps - name: Run cargo-udeps on rp2040-hal - run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature --target=thumbv6m-none-eabi + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature --target=${TARGET} - name: Run cargo-udeps on rp2040-hal-macros run: cd ${PACKAGE}-macros && cargo hack udeps --optional-deps --each-feature msrv: @@ -61,14 +62,14 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: 1.77 - target: thumbv6m-none-eabi + target: ${{ env.TARGET }} - name: Install cargo-hack run: | curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin - name: Use older version of regex run: cd ${PACKAGE}-examples && cargo update -p regex --precise 1.9.3 - name: Build rp2040-hal (on MSRV) - run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=thumbv6m-none-eabi + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} - name: Build rp2040-hal-macros (on MSRV) run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature fmt: @@ -92,7 +93,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - target: thumbv6m-none-eabi + target: ${{ env.TARGET }} components: clippy - name: Run cargo clippy - run: cd ${PACKAGE} && cargo clippy --target=thumbv6m-none-eabi + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/.github/workflows/rp2040_hal_examples.yml b/.github/workflows/rp2040_hal_examples.yml index bdf8e46f5..19889aec9 100644 --- a/.github/workflows/rp2040_hal_examples.yml +++ b/.github/workflows/rp2040_hal_examples.yml @@ -2,6 +2,7 @@ on: [push, pull_request] name: Check rp2040-hal-examples env: PACKAGE: rp2040-hal-examples + TARGET: thumbv6m-none-eabi jobs: build: runs-on: ubuntu-20.04 @@ -9,12 +10,9 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - target: thumbv6m-none-eabi - - name: Install cargo-hack - run: | - curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + target: ${{ env.TARGET }} - name: Build - run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + run: cd ${PACKAGE} && cargo build udeps: runs-on: ubuntu-20.04 steps: @@ -22,15 +20,12 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: nightly-2024-01-30 - target: thumbv6m-none-eabi - - name: Install cargo-hack - run: | - curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + target: ${{ env.TARGET }} - name: Install cargo-udeps run: | curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps - name: Run cargo-udeps - run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature + run: cd ${PACKAGE} && cargo udeps msrv: name: Verify build on MSRV runs-on: ubuntu-20.04 @@ -39,14 +34,11 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: 1.77 - target: thumbv6m-none-eabi - - name: Install cargo-hack - run: | - curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + target: ${{ env.TARGET }} - name: Use older version of regex run: cd ${PACKAGE} && cargo update -p regex --precise 1.9.3 - name: Build on MSRV - run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + run: cd ${PACKAGE} && cargo build fmt: runs-on: ubuntu-20.04 env: @@ -66,7 +58,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - target: thumbv6m-none-eabi + target: ${{ env.TARGET }} components: clippy - name: Run cargo clippy run: cd ${PACKAGE} && cargo clippy diff --git a/.github/workflows/rp235x_hal_arm.yml b/.github/workflows/rp235x_hal_arm.yml new file mode 100644 index 000000000..b1a167c50 --- /dev/null +++ b/.github/workflows/rp235x_hal_arm.yml @@ -0,0 +1,99 @@ +on: [push, pull_request] +name: Check rp235x-hal on Arm +env: + PACKAGE: rp235x-hal + TARGET: thumbv8m.main-none-eabihf +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build rp235x-hal + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test rp235x-hal + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests --features critical-section-impl + - name: Test rp235x-hal docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc --features critical-section-impl + - name: Test rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --tests --each-feature + - name: Test rp235x-hal-macros docs + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --doc --each-feature + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-01-30 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps on rp235x-hal + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature --target=${TARGET} + - name: Run cargo-udeps on rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Use older version of regex + run: cd ${PACKAGE}-examples && cargo update -p regex --precise 1.9.3 + - name: Build rp235x-hal (on MSRV) + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros (on MSRV) + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format of rp235x-hal + run: cd ${PACKAGE} && cargo fmt -- --check + - name: Check format of rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/.github/workflows/rp235x_hal_examples_arm.yml b/.github/workflows/rp235x_hal_examples_arm.yml new file mode 100644 index 000000000..63b13f931 --- /dev/null +++ b/.github/workflows/rp235x_hal_examples_arm.yml @@ -0,0 +1,64 @@ +on: [push, pull_request] +name: Check rp235x-hal-examples on Arm +env: + PACKAGE: rp235x-hal-examples + TARGET: thumbv8m.main-none-eabihf +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Build + run: cd ${PACKAGE} && cargo build --target=${TARGET} + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-01-30 + target: ${{ env.TARGET }} + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps + run: cd ${PACKAGE} && cargo udeps --target=${TARGET} + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77 + target: ${{ env.TARGET }} + - name: Use older version of regex + run: cd ${PACKAGE} && cargo update -p regex --precise 1.9.3 + - name: Build on MSRV + run: cd ${PACKAGE} && cargo build --target=${TARGET} + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/.github/workflows/rp235x_hal_examples_riscv.yml b/.github/workflows/rp235x_hal_examples_riscv.yml new file mode 100644 index 000000000..7f86fe2c1 --- /dev/null +++ b/.github/workflows/rp235x_hal_examples_riscv.yml @@ -0,0 +1,66 @@ +on: [push, pull_request] +name: Check rp235x-hal-examples on RISC-V +env: + PACKAGE: rp235x-hal-examples + TARGET: riscv32imac-unknown-none-elf +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Build + run: | + cd ${PACKAGE} + cat riscv_examples.txt | while read example; do + echo "Building $example" + cargo build --target=${TARGET} --bin $example + done + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77 + target: ${{ env.TARGET }} + - name: Use older version of regex + run: cd ${PACKAGE} && cargo update -p regex --precise 1.9.3 + - name: Build on MSRV + run: | + cd ${PACKAGE} + cat riscv_examples.txt | while read example; do + echo "Building $example" + cargo build --target=${TARGET} --bin $example + done + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: | + cd ${PACKAGE} + cat riscv_examples.txt | while read example; do + echo "Checking $example" + cargo clippy --target=${TARGET} --bin $example + done diff --git a/.github/workflows/rp235x_hal_riscv.yml b/.github/workflows/rp235x_hal_riscv.yml new file mode 100644 index 000000000..4d5e85146 --- /dev/null +++ b/.github/workflows/rp235x_hal_riscv.yml @@ -0,0 +1,99 @@ +on: [push, pull_request] +name: Check rp235x-hal on RISC-V +env: + PACKAGE: rp235x-hal + TARGET: riscv32imac-unknown-none-elf +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build rp235x-hal + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test rp235x-hal + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests --features critical-section-impl + - name: Test rp235x-hal docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc --features critical-section-impl + - name: Test rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --tests --each-feature + - name: Test rp235x-hal-macros docs + run: cd ${PACKAGE}-macros && cargo hack test --optional-deps --doc --each-feature + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-01-30 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps on rp235x-hal + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature --target=${TARGET} + - name: Run cargo-udeps on rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77 + target: ${{ env.TARGET }} + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Use older version of regex + run: cd ${PACKAGE}-examples && cargo update -p regex --precise 1.9.3 + - name: Build rp235x-hal (on MSRV) + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature --target=${TARGET} + - name: Build rp235x-hal-macros (on MSRV) + run: cd ${PACKAGE}-macros && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format of rp235x-hal + run: cd ${PACKAGE} && cargo fmt -- --check + - name: Check format of rp235x-hal-macros + run: cd ${PACKAGE}-macros && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + target: ${{ env.TARGET }} + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy --target=${TARGET} diff --git a/.github/workflows/rp_hal_common.yml b/.github/workflows/rp_hal_common.yml new file mode 100644 index 000000000..aa26194d2 --- /dev/null +++ b/.github/workflows/rp_hal_common.yml @@ -0,0 +1,77 @@ +on: [push, pull_request] +name: Check rp-hal-common +env: + PACKAGE: rp-hal-common +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Test + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --tests + - name: Test docs + run: cd ${PACKAGE} && cargo hack test --optional-deps --each-feature --doc + udeps: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-01-30 + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Install cargo-udeps + run: | + curl -sSL https://github.com/est31/cargo-udeps/releases/download/v0.1.45/cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - --strip-components=2 -C ~/.cargo/bin ./cargo-udeps-v0.1.45-x86_64-unknown-linux-gnu/cargo-udeps + - name: Run cargo-udeps + run: cd ${PACKAGE} && cargo hack udeps --optional-deps --each-feature + msrv: + name: Verify build on MSRV + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.77 + - name: Install cargo-hack + run: | + curl -sSL https://github.com/taiki-e/cargo-hack/releases/download/v0.6.17/cargo-hack-x86_64-unknown-linux-gnu.tar.gz | tar xvzf - -C ~/.cargo/bin + - name: Build on MSRV + run: cd ${PACKAGE} && cargo hack build --optional-deps --each-feature + fmt: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: Check format + run: cd ${PACKAGE} && cargo fmt -- --check + clippy: + runs-on: ubuntu-20.04 + env: + RUSTFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - name: Run cargo clippy + run: cd ${PACKAGE} && cargo clippy diff --git a/format.bat b/format.bat index c22532265..44d08a1e5 100644 --- a/format.bat +++ b/format.bat @@ -1,6 +1,10 @@ rem Formats all the files in the repo -cargo fmt --manifest-path rp2040-hal\Cargo.toml -- --check -cargo fmt --manifest-path rp2040-hal-macros\Cargo.toml -- --check -cargo fmt --manifest-path rp2040-hal-examples\Cargo.toml -- --check -cargo fmt --manifest-path on-target-tests\Cargo.toml -- --check +cargo fmt --manifest-path rp2040-hal\Cargo.toml +cargo fmt --manifest-path rp2040-hal-macros\Cargo.toml +cargo fmt --manifest-path rp2040-hal-examples\Cargo.toml +cargo fmt --manifest-path on-target-tests\Cargo.toml +cargo fmt --manifest-path rp235x-hal\Cargo.toml +cargo fmt --manifest-path rp235x-hal-macros\Cargo.toml +cargo fmt --manifest-path rp235x-hal-examples\Cargo.toml +cargo fmt --manifest-path rp-hal-common\Cargo.toml diff --git a/format.sh b/format.sh index 3f141459a..ecf8adbda 100755 --- a/format.sh +++ b/format.sh @@ -2,7 +2,11 @@ # Formats all the files in the repo -cargo fmt --manifest-path rp2040-hal/Cargo.toml -- --check -cargo fmt --manifest-path rp2040-hal-macros/Cargo.toml -- --check -cargo fmt --manifest-path rp2040-hal-examples/Cargo.toml -- --check -cargo fmt --manifest-path on-target-tests/Cargo.toml -- --check +cargo fmt --manifest-path rp2040-hal/Cargo.toml +cargo fmt --manifest-path rp2040-hal-macros/Cargo.toml +cargo fmt --manifest-path rp2040-hal-examples/Cargo.toml +cargo fmt --manifest-path on-target-tests/Cargo.toml +cargo fmt --manifest-path rp235x-hal/Cargo.toml +cargo fmt --manifest-path rp235x-hal-macros/Cargo.toml +cargo fmt --manifest-path rp235x-hal-examples/Cargo.toml +cargo fmt --manifest-path rp-hal-common/Cargo.toml diff --git a/rp-binary-info/README.md b/rp-binary-info/README.md index 18541f865..68bcfcb06 100644 --- a/rp-binary-info/README.md +++ b/rp-binary-info/README.md @@ -11,5 +11,5 @@ license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). Unless you explicitly state otherwise, any contribution intentionally submitted -for inclus`ion in the work by you, as defined in the Apache-2.0 license, shall be +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/rp-binary-info/src/macros.rs b/rp-binary-info/src/macros.rs index 0689ddb54..7431cf4eb 100644 --- a/rp-binary-info/src/macros.rs +++ b/rp-binary-info/src/macros.rs @@ -56,6 +56,19 @@ macro_rules! rp_program_name { }; } +/// Generate a static item containing the `CARGO_BIN_NAME` as the program name, +/// and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_cargo_bin_name { + () => { + $crate::env!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_NAME, + "CARGO_BIN_NAME" + ) + }; +} + /// Generate a static item containing the program version, and return its /// [`EntryAddr`](super::EntryAddr). #[macro_export] @@ -82,7 +95,7 @@ macro_rules! rp_cargo_version { }; } -/// Generate a static item containing the program url, and return its +/// Generate a static item containing the program URL, and return its /// [`EntryAddr`](super::EntryAddr). #[macro_export] macro_rules! rp_program_url { @@ -95,6 +108,19 @@ macro_rules! rp_program_url { }; } +/// Generate a static item containing the `CARGO_PKG_HOMEPAGE` as the program URL, +/// and return its [`EntryAddr`](super::EntryAddr). +#[macro_export] +macro_rules! rp_cargo_homepage_url { + () => { + $crate::env!( + $crate::consts::TAG_RASPBERRY_PI, + $crate::consts::ID_RP_PROGRAM_URL, + "CARGO_PKG_HOMEPAGE" + ) + }; +} + /// Generate a static item containing the program description, and return its /// [`EntryAddr`](super::EntryAddr). #[macro_export] diff --git a/rp-binary-info/src/types.rs b/rp-binary-info/src/types.rs index 319d6e2c3..d2b192e32 100644 --- a/rp-binary-info/src/types.rs +++ b/rp-binary-info/src/types.rs @@ -4,7 +4,7 @@ /// file/ELF file/Pico in Bootloader Mode to give you useful metadata about your /// program. /// -/// It should be placed in the first 512 bytes of flash, so use your `memory.x` +/// It should be placed in the first 4096 bytes of flash, so use your `memory.x` /// to insert a section between `.text` and `.vector_table` and put a static /// value of this type in that section. #[repr(C)] diff --git a/rp-hal-common/Cargo.toml b/rp-hal-common/Cargo.toml new file mode 100644 index 000000000..08b49e347 --- /dev/null +++ b/rp-hal-common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["The rp-rs Developers"] +description = "Shared HAL code for the Raspberry Pi microcontrollers" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +license = "MIT OR Apache-2.0" +name = "rp-hal-common" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.77" +version = "0.1.0" + +# DO NOT LIST ANY PAC CRATES OR ARCHITECTURE CRATES HERE +[dependencies] +fugit = "0.3.7" diff --git a/rp-hal-common/LICENSE-APACHE b/rp-hal-common/LICENSE-APACHE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/rp-hal-common/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp-hal-common/LICENSE-MIT b/rp-hal-common/LICENSE-MIT new file mode 100644 index 000000000..6e052e35b --- /dev/null +++ b/rp-hal-common/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp-hal-common/NOTICE b/rp-hal-common/NOTICE new file mode 100644 index 000000000..790ecb167 --- /dev/null +++ b/rp-hal-common/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp-hal-common/README.md b/rp-hal-common/README.md new file mode 100644 index 000000000..f9f4e0921 --- /dev/null +++ b/rp-hal-common/README.md @@ -0,0 +1,15 @@ +# `rp-hal-common` + +Code and types useful to both rp2040-hal and rp235x-hal. + +# License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rp-hal-common/src/lib.rs b/rp-hal-common/src/lib.rs new file mode 100644 index 000000000..7c41ebe03 --- /dev/null +++ b/rp-hal-common/src/lib.rs @@ -0,0 +1,11 @@ +//! Common HAL code +//! +//! This library contains types and functions which are shared between the +//! RP2040 HAL and the RP235x HAL. +//! +//! You shouldn't include anything here which requires either the `cortex-m` +//! crate, or a PAC. + +#![no_std] + +pub mod uart; diff --git a/rp-hal-common/src/uart/common_configs.rs b/rp-hal-common/src/uart/common_configs.rs new file mode 100644 index 000000000..0e5bdba75 --- /dev/null +++ b/rp-hal-common/src/uart/common_configs.rs @@ -0,0 +1,45 @@ +//! Common UART configurations + +use fugit::HertzU32; + +use super::{DataBits, StopBits, UartConfig}; + +/// 9600 baud, 8 data bits, no parity, 1 stop bit +pub const _9600_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(9600), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 19200 baud, 8 data bits, no parity, 1 stop bit +pub const _19200_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(19200), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 38400 baud, 8 data bits, no parity, 1 stop bit +pub const _38400_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(38400), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 57600 baud, 8 data bits, no parity, 1 stop bit +pub const _57600_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(57600), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; + +/// 115200 baud, 8 data bits, no parity, 1 stop bit +pub const _115200_8_N_1: UartConfig = UartConfig { + baudrate: HertzU32::from_raw(115200), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, +}; diff --git a/rp-hal-common/src/uart/mod.rs b/rp-hal-common/src/uart/mod.rs new file mode 100644 index 000000000..3a4918acf --- /dev/null +++ b/rp-hal-common/src/uart/mod.rs @@ -0,0 +1,6 @@ +//! Shared code and types for Raspberry Pi Silicon UARTS + +pub mod common_configs; +mod utils; + +pub use utils::*; diff --git a/rp-hal-common/src/uart/utils.rs b/rp-hal-common/src/uart/utils.rs new file mode 100644 index 000000000..a9ad7cb26 --- /dev/null +++ b/rp-hal-common/src/uart/utils.rs @@ -0,0 +1,88 @@ +//! Useful UART types + +use fugit::HertzU32; + +/// Data bits +pub enum DataBits { + /// 5 bits + Five, + /// 6 bits + Six, + /// 7 bits + Seven, + /// 8 bits + Eight, +} + +/// Stop bits +pub enum StopBits { + /// 1 bit + One, + /// 2 bits + Two, +} + +/// Parity +/// +/// The "none" state of parity is represented with the Option type (None). +pub enum Parity { + /// Odd parity + Odd, + /// Even parity + Even, +} + +/// A struct holding the configuration for an UART device. +/// +/// The `Default` implementation implements the following values: +/// ```ignore +/// # // can't actually create this with the non_exhaustive attribute +/// UartConfig { +/// baudrate: Baud(115_200), +/// data_bits: DataBits::Eight, +/// stop_bits: StopBits::One, +/// parity: None, +///} +/// ``` +#[non_exhaustive] +pub struct UartConfig { + /// The baudrate the uart will run at. + pub baudrate: HertzU32, + + /// The amount of data bits the uart should be configured to. + pub data_bits: DataBits, + + /// The amount of stop bits the uart should be configured to. + pub stop_bits: StopBits, + + /// The parity that this uart should have + pub parity: Option, +} + +impl UartConfig { + /// Create a new instance of UartConfig + pub const fn new( + baudrate: HertzU32, + data_bits: DataBits, + parity: Option, + stop_bits: StopBits, + ) -> UartConfig { + UartConfig { + baudrate, + data_bits, + stop_bits, + parity, + } + } +} + +impl Default for UartConfig { + fn default() -> Self { + Self { + baudrate: HertzU32::from_raw(115_200), + data_bits: DataBits::Eight, + stop_bits: StopBits::One, + parity: None, + } + } +} diff --git a/rp2040-hal-examples/src/bin/adc.rs b/rp2040-hal-examples/src/bin/adc.rs index 7dffb0c54..7e9c34bc6 100644 --- a/rp2040-hal-examples/src/bin/adc.rs +++ b/rp2040-hal-examples/src/bin/adc.rs @@ -1,7 +1,7 @@ //! # ADC Example //! //! This application demonstrates how to read ADC samples from the temperature -//! sensor and pin and output them to the UART on pins 1 and 2 at 9600 baud. +//! sensor and pin and output them to the UART on pins 1 and 2 at 115200 baud. //! //! It may need to be adapted to your particular board layout and/or pin assignment. //! @@ -95,7 +95,7 @@ fn main() -> ! { // Create a UART driver let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) .enable( - UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), clocks.peripheral_clock.freq(), ) .unwrap(); diff --git a/rp2040-hal-examples/src/bin/alloc.rs b/rp2040-hal-examples/src/bin/alloc.rs index d4c465cde..76c4d7ef3 100644 --- a/rp2040-hal-examples/src/bin/alloc.rs +++ b/rp2040-hal-examples/src/bin/alloc.rs @@ -6,7 +6,7 @@ //! the on-board LED. It may need to be adapted to your particular board layout //! and/or pin assignment. //! -//! While blinkin the LED, it will continuously push to a `Vec`, which will +//! While blinking the LED, it will continuously push to a `Vec`, which will //! eventually lead to a panic due to an out of memory condition. //! //! See the top-level `README.md` file for Copyright and licence details. @@ -58,7 +58,7 @@ const XTAL_FREQ_HZ: u32 = 12_000_000u32; /// as soon as all global variables are initialised. /// /// The function configures the RP2040 peripherals, then blinks the LED in an -/// infinite loop. +/// infinite loop where the duration indicates how many items were allocated. #[entry] fn main() -> ! { { diff --git a/rp2040-hal-examples/src/bin/gpio_in_out.rs b/rp2040-hal-examples/src/bin/gpio_in_out.rs index f95b6c640..2b25b64a6 100644 --- a/rp2040-hal-examples/src/bin/gpio_in_out.rs +++ b/rp2040-hal-examples/src/bin/gpio_in_out.rs @@ -1,6 +1,6 @@ -//! # GPIO 'Blinky' Example +//! # GPIO In/Out Example //! -//! This application demonstrates how to control a GPIO pin on the RP2040. +//! This application demonstrates how to control GPIO pins on the RP2040. //! //! It may need to be adapted to your particular board layout and/or pin assignment. //! diff --git a/rp2040-hal-examples/src/bin/rom_funcs.rs b/rp2040-hal-examples/src/bin/rom_funcs.rs index e7e70c394..dc2b984c8 100644 --- a/rp2040-hal-examples/src/bin/rom_funcs.rs +++ b/rp2040-hal-examples/src/bin/rom_funcs.rs @@ -91,7 +91,7 @@ fn main() -> ! { ); let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) .enable( - UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), clocks.peripheral_clock.freq(), ) .unwrap(); diff --git a/rp2040-hal-examples/src/bin/uart.rs b/rp2040-hal-examples/src/bin/uart.rs index 1eaf17211..bb291223c 100644 --- a/rp2040-hal-examples/src/bin/uart.rs +++ b/rp2040-hal-examples/src/bin/uart.rs @@ -91,7 +91,7 @@ fn main() -> ! { ); let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) .enable( - UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), clocks.peripheral_clock.freq(), ) .unwrap(); diff --git a/rp2040-hal-examples/src/bin/uart_dma.rs b/rp2040-hal-examples/src/bin/uart_dma.rs index 5f9008083..77f65d02b 100644 --- a/rp2040-hal-examples/src/bin/uart_dma.rs +++ b/rp2040-hal-examples/src/bin/uart_dma.rs @@ -91,7 +91,7 @@ fn main() -> ! { ); let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) .enable( - UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), clocks.peripheral_clock.freq(), ) .unwrap(); diff --git a/rp2040-hal-examples/src/bin/watchdog.rs b/rp2040-hal-examples/src/bin/watchdog.rs index c8112b26e..139100f69 100644 --- a/rp2040-hal-examples/src/bin/watchdog.rs +++ b/rp2040-hal-examples/src/bin/watchdog.rs @@ -98,7 +98,7 @@ fn main() -> ! { watchdog.feed(); } - // Blink 10 times per second, not feeding the watchdog. + // Blink 5 times per second, not feeding the watchdog. // The processor should reset in 1.05 seconds, or 5 blinks time loop { led_pin.set_low().unwrap(); diff --git a/rp2040-hal-macros/.gitignore b/rp2040-hal-macros/.gitignore new file mode 100644 index 000000000..ff47c2d77 --- /dev/null +++ b/rp2040-hal-macros/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp2040-hal-macros/README.md b/rp2040-hal-macros/README.md index de3532087..dc587979b 100644 --- a/rp2040-hal-macros/README.md +++ b/rp2040-hal-macros/README.md @@ -17,5 +17,5 @@ license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). Unless you explicitly state otherwise, any contribution intentionally submitted -for inclus`ion in the work by you, as defined in the Apache-2.0 license, shall be +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index c1a07286a..858ca79f9 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -36,6 +36,7 @@ paste = "1.0" pio = "0.2.0" rand_core = "0.6.3" rp-binary-info = { version = "0.1.0", path = "../rp-binary-info" } +rp-hal-common = {version="0.1.0", path="../rp-hal-common"} rp2040-hal-macros = {version = "0.1.0", path = "../rp2040-hal-macros"} rp2040-pac = {version = "0.6.0", features = ["critical-section"]} usb-device = "0.3" diff --git a/rp2040-hal/src/adc.rs b/rp2040-hal/src/adc.rs index 1a5ba5457..2da0aaf80 100644 --- a/rp2040-hal/src/adc.rs +++ b/rp2040-hal/src/adc.rs @@ -1,6 +1,6 @@ //! Analog-Digital Converter (ADC) //! -//! See [Chapter 4 Section 9](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) of the datasheet for more details +//! See [Chapter 4 Section 9](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details //! //! ## Usage //! diff --git a/rp2040-hal/src/clocks/mod.rs b/rp2040-hal/src/clocks/mod.rs index 014f0a1b8..f2c4c88ef 100644 --- a/rp2040-hal/src/clocks/mod.rs +++ b/rp2040-hal/src/clocks/mod.rs @@ -60,7 +60,7 @@ //! # } //! ``` //! -//! See [Chapter 2 Section 15](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! See [Chapter 2 Section 15](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details use core::{convert::Infallible, marker::PhantomData}; use fugit::{HertzU32, RateExtU32}; diff --git a/rp2040-hal/src/i2c.rs b/rp2040-hal/src/i2c.rs index 18d91287f..dfa06c349 100644 --- a/rp2040-hal/src/i2c.rs +++ b/rp2040-hal/src/i2c.rs @@ -1,6 +1,6 @@ //! Inter-Integrated Circuit (I2C) bus //! -//! See [Chapter 4 Section 3](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! See [Chapter 4 Section 3](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details //! //! ## Usage //! ```no_run diff --git a/rp2040-hal/src/lib.rs b/rp2040-hal/src/lib.rs index 73efe6d17..a71cc0a95 100644 --- a/rp2040-hal/src/lib.rs +++ b/rp2040-hal/src/lib.rs @@ -35,6 +35,8 @@ //! `rtic_monotonic::Monotonic` based on the RP2040 timer peripheral //! * **i2c-write-iter** - //! Implement `i2c_write_iter` traits for `I2C<_, _, Controller>`. +//! * **binary-info** - +//! Include a `static` variable containing picotool compatible binary info. #![warn(missing_docs)] #![no_std] diff --git a/rp2040-hal/src/pio.rs b/rp2040-hal/src/pio.rs index 0656bc4f6..214db8dec 100644 --- a/rp2040-hal/src/pio.rs +++ b/rp2040-hal/src/pio.rs @@ -1,5 +1,7 @@ //! Programmable IO (PIO) -//! See [Chapter 3 of the datasheet](https://rptl.io/rp2040-datasheet#section_pio) for more details. +//! +//! See [Chapter 3 of the datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf#section_pio) for more details. + use core::ops::Deref; use pio::{Instruction, InstructionOperands, Program, SideSet, Wrap}; diff --git a/rp2040-hal/src/pll.rs b/rp2040-hal/src/pll.rs index 2255cd39b..786e2eb74 100644 --- a/rp2040-hal/src/pll.rs +++ b/rp2040-hal/src/pll.rs @@ -1,5 +1,6 @@ //! Phase-Locked Loops (PLL) -// See [Chapter 2 Section 18](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! +//! See [Chapter 2 Section 18](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. use core::{ convert::Infallible, diff --git a/rp2040-hal/src/resets.rs b/rp2040-hal/src/resets.rs index 0c34a0e7b..cb091d265 100644 --- a/rp2040-hal/src/resets.rs +++ b/rp2040-hal/src/resets.rs @@ -1,5 +1,7 @@ //! Subsystem Resets -// See [Chapter 2 Section 14](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! +//! See [Chapter 2 Section 14](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. + mod private { pub trait SubsystemReset { fn reset_bring_up(&self, resets: &mut crate::pac::RESETS); diff --git a/rp2040-hal/src/rosc.rs b/rp2040-hal/src/rosc.rs index d05025295..abad4f3e9 100644 --- a/rp2040-hal/src/rosc.rs +++ b/rp2040-hal/src/rosc.rs @@ -1,6 +1,6 @@ //! Ring Oscillator (ROSC) //! -//! See [Chapter 2 Section 17](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! See [Chapter 2 Section 17](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details //! //! In addition to its obvious role as a clock source, [`RingOscillator`] can also be used as a random number source //! for the [`rand`] crate: diff --git a/rp2040-hal/src/spi.rs b/rp2040-hal/src/spi.rs index f8c8590bf..0969b1e6b 100644 --- a/rp2040-hal/src/spi.rs +++ b/rp2040-hal/src/spi.rs @@ -3,7 +3,7 @@ //! [`Spi`] is the main struct exported by this module, representing a configured Spi bus. See its //! docs for more information on its type parameters. //! -//! See [Chapter 4 Section 4](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! See [Chapter 4 Section 4](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details //! //! ## Usage //! diff --git a/rp2040-hal/src/ssi.rs b/rp2040-hal/src/ssi.rs index 382a68b44..f536cdbaa 100644 --- a/rp2040-hal/src/ssi.rs +++ b/rp2040-hal/src/ssi.rs @@ -1,3 +1,5 @@ //! Synchronous Serial Interface (SSI) -// See [Chapter 4 Section 10](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! +//! See [Chapter 4 Section 10](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. + // TODO diff --git a/rp2040-hal/src/timer.rs b/rp2040-hal/src/timer.rs index b9b49f03c..b6edbde37 100644 --- a/rp2040-hal/src/timer.rs +++ b/rp2040-hal/src/timer.rs @@ -6,7 +6,7 @@ //! //! Each of the 4 alarms can match on the lower 32 bits of Counter and trigger an interrupt. //! -//! See [Chapter 4 Section 6](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) of the datasheet for more details. +//! See [Chapter 4 Section 6](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details. use core::sync::atomic::{AtomicU8, Ordering}; use fugit::{MicrosDurationU32, MicrosDurationU64, TimerInstantU64}; diff --git a/rp2040-hal/src/uart/common_configs.rs b/rp2040-hal/src/uart/common_configs.rs index 3515e72d6..b36445413 100644 --- a/rp2040-hal/src/uart/common_configs.rs +++ b/rp2040-hal/src/uart/common_configs.rs @@ -1,43 +1,2 @@ -use fugit::HertzU32; - -use super::{DataBits, StopBits, UartConfig}; - -/// 9600 baud, 8 data bits, no parity, 1 stop bit -pub const _9600_8_N_1: UartConfig = UartConfig { - baudrate: HertzU32::from_raw(9600), - data_bits: DataBits::Eight, - stop_bits: StopBits::One, - parity: None, -}; - -/// 19200 baud, 8 data bits, no parity, 1 stop bit -pub const _19200_8_N_1: UartConfig = UartConfig { - baudrate: HertzU32::from_raw(19200), - data_bits: DataBits::Eight, - stop_bits: StopBits::One, - parity: None, -}; - -/// 38400 baud, 8 data bits, no parity, 1 stop bit -pub const _38400_8_N_1: UartConfig = UartConfig { - baudrate: HertzU32::from_raw(38400), - data_bits: DataBits::Eight, - stop_bits: StopBits::One, - parity: None, -}; - -/// 57600 baud, 8 data bits, no parity, 1 stop bit -pub const _57600_8_N_1: UartConfig = UartConfig { - baudrate: HertzU32::from_raw(57600), - data_bits: DataBits::Eight, - stop_bits: StopBits::One, - parity: None, -}; - -/// 115200 baud, 8 data bits, no parity, 1 stop bit -pub const _115200_8_N_1: UartConfig = UartConfig { - baudrate: HertzU32::from_raw(115200), - data_bits: DataBits::Eight, - stop_bits: StopBits::One, - parity: None, -}; +#[doc(inline)] +pub use rp_hal_common::uart::common_configs::*; diff --git a/rp2040-hal/src/uart/mod.rs b/rp2040-hal/src/uart/mod.rs index 5b1f65223..c7b4d734d 100644 --- a/rp2040-hal/src/uart/mod.rs +++ b/rp2040-hal/src/uart/mod.rs @@ -1,6 +1,6 @@ //! Universal Asynchronous Receiver Transmitter (UART) //! -//! See [Chapter 4 Section 2](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) of the datasheet for more details +//! See [Chapter 4 Section 2](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details //! //! ## Usage //! diff --git a/rp2040-hal/src/uart/utils.rs b/rp2040-hal/src/uart/utils.rs index 81841ef3b..74abc90a6 100644 --- a/rp2040-hal/src/uart/utils.rs +++ b/rp2040-hal/src/uart/utils.rs @@ -3,7 +3,9 @@ use crate::pac::{uart0::RegisterBlock, UART0, UART1}; use crate::resets::SubsystemReset; use crate::typelevel::Sealed; use core::ops::Deref; -use fugit::HertzU32; + +#[doc(inline)] +pub use rp_hal_common::uart::{DataBits, Parity, StopBits, UartConfig}; /// Error type for UART operations. #[derive(Debug)] @@ -68,79 +70,6 @@ impl Sealed for Enabled {} impl State for Disabled {} impl Sealed for Disabled {} -/// Data bits -pub enum DataBits { - /// 5 bits - Five, - /// 6 bits - Six, - /// 7 bits - Seven, - /// 8 bits - Eight, -} - -/// Stop bits -pub enum StopBits { - /// 1 bit - One, - /// 2 bits - Two, -} - -/// Parity -/// The "none" state of parity is represented with the Option type (None). -pub enum Parity { - /// Odd parity - Odd, - /// Even parity - Even, -} - -/// A struct holding the configuration for an UART device. -/// -/// The `Default` implementation implements the following values: -/// ```ignore -/// # // can't actually create this with the non_exhaustive attribute -/// UartConfig { -/// baudrate: Baud(115_200), -/// data_bits: DataBits::Eight, -/// stop_bits: StopBits::One, -/// parity: None, -///} -/// ``` -#[non_exhaustive] -pub struct UartConfig { - /// The baudrate the uart will run at. - pub baudrate: HertzU32, - - /// The amount of data bits the uart should be configured to. - pub data_bits: DataBits, - - /// The amount of stop bits the uart should be configured to. - pub stop_bits: StopBits, - - /// The parity that this uart should have - pub parity: Option, -} - -impl UartConfig { - /// Create a new instance of UartConfig - pub const fn new( - baudrate: HertzU32, - data_bits: DataBits, - parity: Option, - stop_bits: StopBits, - ) -> UartConfig { - UartConfig { - baudrate, - data_bits, - stop_bits, - parity, - } - } -} - /// Rx/Tx FIFO Watermark /// /// Determine the FIFO level that trigger DMA/Interrupt @@ -162,14 +91,3 @@ pub enum FifoWatermark { /// Trigger when 28 bytes are (Rx: filled / Tx: available) Bytes28, } - -impl Default for UartConfig { - fn default() -> Self { - Self { - baudrate: HertzU32::from_raw(115_200), - data_bits: DataBits::Eight, - stop_bits: StopBits::One, - parity: None, - } - } -} diff --git a/rp2040-hal/src/usb.rs b/rp2040-hal/src/usb.rs index 3f2f9f375..1e5e9dc1a 100644 --- a/rp2040-hal/src/usb.rs +++ b/rp2040-hal/src/usb.rs @@ -1,5 +1,7 @@ //! Universal Serial Bus (USB) -// See [Chapter 4 Section 1](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! +//! See [Chapter 4 Section 1](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. +//! //! ## Usage //! //! Initialize the Usb Bus forcing the VBUS detection. diff --git a/rp2040-hal/src/watchdog.rs b/rp2040-hal/src/watchdog.rs index 97e30fa1e..cfa11a74b 100644 --- a/rp2040-hal/src/watchdog.rs +++ b/rp2040-hal/src/watchdog.rs @@ -4,7 +4,7 @@ //! processor if software gets stuck in an infinite loop. The programmer must periodically write a value to the watchdog to //! stop it from reaching zero. //! -//! See [Chapter 4 Section 7](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) of the datasheet for more details +//! See [Chapter 4 Section 7](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) of the datasheet for more details //! //! ## Usage //! ```no_run diff --git a/rp2040-hal/src/xosc.rs b/rp2040-hal/src/xosc.rs index 1c2ec68ac..8fb244796 100644 --- a/rp2040-hal/src/xosc.rs +++ b/rp2040-hal/src/xosc.rs @@ -1,5 +1,6 @@ //! Crystal Oscillator (XOSC) -// See [Chapter 2 Section 16](https://datasheets.raspberrypi.org/rp2040/rp2040_datasheet.pdf) for more details +//! +//! See [Chapter 2 Section 16](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) for more details. use core::{convert::Infallible, ops::RangeInclusive}; diff --git a/rp235x-hal-examples/.cargo/config.toml b/rp235x-hal-examples/.cargo/config.toml new file mode 100644 index 000000000..1f5f243d7 --- /dev/null +++ b/rp235x-hal-examples/.cargo/config.toml @@ -0,0 +1,81 @@ +# +# Cargo Configuration for the https://github.com/rp-rs/rp-hal.git repository. +# +# You might want to make a similar file in your own repository if you are +# writing programs for Raspberry Silicon microcontrollers. +# + +[build] +# Set the default target to match the Cortex-M33 in the RP2350 +target = "thumbv8m.main-none-eabihf" + +# This is the hard-float ABI for Arm mode. +# +# The FPU is enabled by default, and float function arguments use FPU +# registers. +[target.thumbv8m.main-none-eabihf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Tlink.x tells the linker to use link.x as a linker script. +# This is usually provided by the cortex-m-rt crate, and by default the +# version in that crate will include a file called `memory.x` which describes +# the particular memory layout for your specific chip. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" + +# This is the soft-float ABI for Arm mode. +# +# The FPU is disabled by default, and float function arguments use integer +# registers. Only useful for making the `float_test` example give really bad +# results on the `f32` benchmark. +[target.thumbv8m.main-none-eabi] +# Pass some extra options to rustc. See above for descriptions. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" + +# This is the soft-float ABI for RISC-V mode. +# +# Hazard 3 does not have an FPU and so float function arguments use integer +# registers. +[target.riscv32imac-unknown-none-elf] +# Pass some extra options to rustc, some of which get passed on to the linker. +# +# * linker argument --nmagic turns off page alignment of sections (which saves +# flash space) +# * linker argument -Trp235x_riscv.x also tells the linker to use +# `rp235x_riscv.x` as a linker script. This adds in RP2350 RISC-V specific +# things that the riscv-rt crate's `link.x` requires and then includes +# `link.x` automatically. This is the reverse of how we do it on Cortex-M. +# * linker argument -Tdefmt.x also tells the linker to use `defmt.x` as a +# secondary linker script. This is required to make defmt_rtt work. +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Trp235x_riscv.x", + "-C", "link-arg=-Tdefmt.x", +] + +# Use picotool for loading. +# +# Load an elf, skipping unchanged flash sectors, verify it, and execute it +runner = "picotool load -u -v -x -t elf" diff --git a/rp235x-hal-examples/.gitignore b/rp235x-hal-examples/.gitignore new file mode 100644 index 000000000..ff47c2d77 --- /dev/null +++ b/rp235x-hal-examples/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp235x-hal-examples/Cargo.toml b/rp235x-hal-examples/Cargo.toml new file mode 100644 index 000000000..4df1b14d1 --- /dev/null +++ b/rp235x-hal-examples/Cargo.toml @@ -0,0 +1,37 @@ +[package] +authors = ["The rp-rs Developers"] +categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] +description = "Examples for the rp235x-hal crate" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +keywords = ["embedded", "hal", "raspberry-pi", "rp235x", "rp2350", "embedded-hal"] +license = "MIT OR Apache-2.0" +name = "rp235x-hal-examples" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.77" +version = "0.1.0" + +[dependencies] +cortex-m = "0.7.2" +cortex-m-rt = "0.7" +cortex-m-rtic = "1.1.4" +critical-section = {version = "1.0.0"} +defmt = "0.3" +defmt-rtt = "0.4.0" +dht-sensor = "0.2.1" +embedded-alloc = "0.5.1" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} +fugit = "0.3.6" +futures = {version = "0.3.30", default-features = false, features = ["async-await"]} +hd44780-driver = "0.4.0" +heapless = "0.8.0" +nb = "1.0" +nostd_async = {version = "0.6.1", features = ["cortex_m"]} +panic-halt = "0.2.0" +pio = "0.2.0" +pio-proc = "0.2.0" +rp235x-hal = {path = "../rp235x-hal", version = "0.2.0", features = ["binary-info", "critical-section-impl", "rt", "defmt"]} +usb-device = "0.3.2" +usbd-serial = "0.2.2" diff --git a/rp235x-hal-examples/LICENSE-APACHE b/rp235x-hal-examples/LICENSE-APACHE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/rp235x-hal-examples/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp235x-hal-examples/LICENSE-MIT b/rp235x-hal-examples/LICENSE-MIT new file mode 100644 index 000000000..6e052e35b --- /dev/null +++ b/rp235x-hal-examples/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp235x-hal-examples/NOTICE b/rp235x-hal-examples/NOTICE new file mode 100644 index 000000000..790ecb167 --- /dev/null +++ b/rp235x-hal-examples/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp235x-hal-examples/README.md b/rp235x-hal-examples/README.md new file mode 100644 index 000000000..6395e2c4c --- /dev/null +++ b/rp235x-hal-examples/README.md @@ -0,0 +1,172 @@ + +
+

+ + Logo + + +

rp-hal

+ +

+ Rust Examples for the Raspberry Silicon RP235x family Microcontrollers +
+ View the examples » +
+
+ Explore the API docs + · + Report a Bug + · + Chat on Matrix +

+

+ + + + +
+

Table of Contents

+
    +
  1. Introduction
  2. +
  3. Getting Started
  4. +
  5. Roadmap
  6. +
  7. Contributing
  8. +
  9. License
  10. +
  11. Contact
  12. +
  13. Acknowledgements
  14. +
+
+ + +## Introduction + +The `rp235x-hal` package is a library crate of high-level Rust drivers for the +Raspberry Silicon RP235x family of microcontrollers. This folder contains a +collection of non-board specific example programs for you to study. + +We also provide a series of [*Board Support Package* (BSP) crates][BSPs], which +take the HAL crate and pre-configure the pins according to a specific PCB +design. If you are using one of the supported boards, you should use one of +those crates in preference, and return here to see documentation about specific +peripherals on the RP235x and how to use them. See the `boards` folder in +https://github.com/rp-rs/rp-hal-boards/ for more details. + +[BSPs]: https://github.com/rp-rs/rp-hal-boards/ + + +## Getting Started + +To build all the examples, first grab a copy of the source code: + +```console +$ git clone https://github.com/rp-rs/rp-hal.git +``` + +Then use `rustup` to grab the Rust Standard Library for the appropriate targets. There are two targets because the RP2350 has both an Arm mode and a RISC-V mode. + +```console +$ rustup target add thumbv8m.main-none-eabihf +$ rustup target add riscv32imac-unknown-none-elf +``` + +To build all the examples for Arm mode, run: + +```console +$ cargo build --target=thumbv8m.main-none-eabihf + Compiling rp235x-hal-examples v0.1.0 (/home/user/rp-hal/rp235x-hal-examples) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.53s +$ cargo build --bin blinky + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s +$ file ./target/thumbv8m.main-none-eabihf/debug/blinky +./target/thumbv8m.main-none-eabihf/debug/blinky: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped +``` + +You can also 'run' an example, which thanks to our supplied +[`.cargo/config.toml`](./.cargo/config.toml) will invoke Raspberry Pi's +`picotool` to copy it to an RP235x in USB Bootloader mode. You should install +that if you don't have it already, from +. + +```console +$ cargo run --bin blinky --target=thumbv8m.main-none-eabihf + Compiling rp235x-hal v0.10.0 (/home/user/rp-hal/rp235x-hal) + Compiling rp235x-hal-examples v0.1.0 (/home/user/rp-hal/rp235x-hal-examples) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.62s + Running `picotool load -u -v -x -t elf target/thumbv8m.main-none-eabihf/debug/blinky` +Family id 'rp2350-arm-s' can be downloaded in absolute space: + 00000000->02000000 +Loading into Flash: [==============================] 100% +Verifying Flash: [==============================] 100% + OK + +The device was rebooted to start the application. +``` + +It is currently possible to build *some* of the examples in RISC-V mode. See +[`riscv_examples.txt`](./riscv_examples.txt) for a list of the examples known to +work. The missing ones probably rely on interrupts, or some other thing we +haven't ported to work in RISC-V mode yet. + +```console +$ cargo build --bin blinky --target=riscv32imac-unknown-none-elf + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s +$ file ./target/riscv32imac-unknown-none-elf/debug/blinky +./target/riscv32imac-unknown-none-elf/debug/blinky: ELF 32-bit LSB executable, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), statically linked, with debug_info, not stripped +$ cargo run --bin blinky --target=riscv32imac-unknown-none-elf + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s + Running `picotool load -u -v -x -t elf target/riscv32imac-unknown-none-elf/debug/blinky` +Family id 'rp2350-riscv' can be downloaded in absolute space: + 00000000->02000000 +Loading into Flash: [==============================] 100% +Verifying Flash: [==============================] 100% + OK + +The device was rebooted to start the application. +``` + + +## Roadmap + +NOTE The HAL is under active development, and so are these examples. As such, it +is likely to remain volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp-hal/issues) for a list of +proposed features (and known issues). + + +## Contributing + +Contributions are what make the open source community such an amazing place to +be learn, inspire, and create. Any contributions you make are **greatly +appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + +## Contact + +* Project Link: [https://github.com/rp-rs/rp-hal/issues](https://github.com/rp-rs/rp-hal/issues) +* Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) + + +## Acknowledgements + +* [Othneil Drew's README template](https://github.com/othneildrew) diff --git a/rp235x-hal-examples/build.rs b/rp235x-hal-examples/build.rs new file mode 100644 index 000000000..5cc06c04d --- /dev/null +++ b/rp235x-hal-examples/build.rs @@ -0,0 +1,27 @@ +//! Set up linker scripts for the rp235x-hal examples + +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + println!("cargo:rustc-link-search={}", out.display()); + + // The file `memory.x` is loaded by cortex-m-rt's `link.x` script, which + // is what we specify in `.cargo/config.toml` for Arm builds + let memory_x = include_bytes!("memory.x"); + let mut f = File::create(out.join("memory.x")).unwrap(); + f.write_all(memory_x).unwrap(); + println!("cargo:rerun-if-changed=memory.x"); + + // The file `rp235x_riscv.x` is what we specify in `.cargo/config.toml` for + // RISC-V builds + let rp235x_riscv_x = include_bytes!("rp235x_riscv.x"); + let mut f = File::create(out.join("rp235x_riscv.x")).unwrap(); + f.write_all(rp235x_riscv_x).unwrap(); + println!("cargo:rerun-if-changed=rp235x_riscv.x"); + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/rp235x-hal-examples/memory.x b/rp235x-hal-examples/memory.x new file mode 100644 index 000000000..1e155038e --- /dev/null +++ b/rp235x-hal-examples/memory.x @@ -0,0 +1,76 @@ +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.start_block) + SIZEOF(.start_block); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; + +SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + +} INSERT AFTER .uninit; + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + diff --git a/rp235x-hal-examples/riscv_examples.txt b/rp235x-hal-examples/riscv_examples.txt new file mode 100644 index 000000000..9dc23f7b0 --- /dev/null +++ b/rp235x-hal-examples/riscv_examples.txt @@ -0,0 +1,29 @@ +adc +adc_fifo_dma +adc_fifo_poll +alloc +arch_flip +binary_info_demo +blinky +block_loop +dht11 +gpio_dyn_pin_array +gpio_in_out +i2c +lcd_display +mem_to_mem_dma +pio_blink +pio_dma +pio_proc_blink +pio_side_set +pio_synchronized +pwm_blink +pwm_blink_embedded_hal_1 +rom_funcs +rosc_as_system_clock +spi +spi_dma +uart +uart_dma +usb +watchdog diff --git a/rp235x-hal-examples/rp235x_riscv.x b/rp235x-hal-examples/rp235x_riscv.x new file mode 100644 index 000000000..ac355eab4 --- /dev/null +++ b/rp235x-hal-examples/rp235x_riscv.x @@ -0,0 +1,252 @@ +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut _heap_size }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol is not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all `32`-byte aligned. These alignments are assumed by the RAM + initialization routine. There's also a second benefit: `32`-byte aligned boundaries + means that you won't see "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +PROVIDE(_stext = ORIGIN(FLASH)); +PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); +PROVIDE(_max_hart_id = 0); +PROVIDE(_hart_stack_size = 2K); +PROVIDE(_heap_size = 0); + +PROVIDE(InstructionMisaligned = ExceptionHandler); +PROVIDE(InstructionFault = ExceptionHandler); +PROVIDE(IllegalInstruction = ExceptionHandler); +PROVIDE(Breakpoint = ExceptionHandler); +PROVIDE(LoadMisaligned = ExceptionHandler); +PROVIDE(LoadFault = ExceptionHandler); +PROVIDE(StoreMisaligned = ExceptionHandler); +PROVIDE(StoreFault = ExceptionHandler); +PROVIDE(UserEnvCall = ExceptionHandler); +PROVIDE(SupervisorEnvCall = ExceptionHandler); +PROVIDE(MachineEnvCall = ExceptionHandler); +PROVIDE(InstructionPageFault = ExceptionHandler); +PROVIDE(LoadPageFault = ExceptionHandler); +PROVIDE(StorePageFault = ExceptionHandler); + +PROVIDE(SupervisorSoft = DefaultHandler); +PROVIDE(MachineSoft = DefaultHandler); +PROVIDE(SupervisorTimer = DefaultHandler); +PROVIDE(MachineTimer = DefaultHandler); +PROVIDE(SupervisorExternal = DefaultHandler); +PROVIDE(MachineExternal = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultInterruptHandler); +PROVIDE(ExceptionHandler = DefaultExceptionHandler); + +/* # Pre-initialization function */ +/* If the user overrides this using the `#[pre_init]` attribute or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = default_pre_init); + +/* A PAC/HAL defined routine that should initialize custom interrupt controller if needed. */ +PROVIDE(_setup_interrupts = default_setup_interrupts); + +/* # Multi-processing hook function + fn _mp_hook() -> bool; + + This function is called from all the harts and must return true only for one hart, + which will perform memory initialization. For other harts it must return false + and implement wake-up in platform-dependent way (e.g. after waiting for a user interrupt). +*/ +PROVIDE(_mp_hook = default_mp_hook); + +/* # Start trap function override + By default uses the riscv crates default trap handler + but by providing the `_start_trap` symbol external crates can override. +*/ +PROVIDE(_start_trap = default_start_trap); + +SECTIONS +{ + .text.dummy (NOLOAD) : + { + /* This section is intended to make _stext address work */ + . = ABSOLUTE(_stext); + } > FLASH + + .text _stext : + { + /* Put reset handler first in .text section so it ends up as the entry */ + /* point of the program. */ + KEEP(*(.init)); + KEEP(*(.init.rust)); + . = ALIGN(4); + __start_block_addr = .; + KEEP(*(.start_block)); + . = ALIGN(4); + *(.trap); + *(.trap.rust); + *(.text.abort); + *(.text .text.*); + . = ALIGN(4); + } > FLASH + + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH + + .rodata : ALIGN(4) + { + *(.srodata .srodata.*); + *(.rodata .rodata.*); + + /* 4-byte align the end (VMA) of this section. + This is required by LLD to ensure the LMA of the following .data + section will have the correct alignment. */ + . = ALIGN(4); + } > FLASH + + .data : ALIGN(32) + { + _sidata = LOADADDR(.data); + __sidata = LOADADDR(.data); + _sdata = .; + __sdata = .; + /* Must be called __global_pointer$ for linker relaxations to work. */ + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.* .sdata2 .sdata2.*); + *(.data .data.*); + . = ALIGN(32); + _edata = .; + __edata = .; + } > RAM AT > FLASH + + .bss (NOLOAD) : ALIGN(32) + { + _sbss = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(32); + _ebss = .; + } > RAM + + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + + /* fictitious region that represents the memory available for the heap */ + .heap (NOLOAD) : + { + _sheap = .; + . += _heap_size; + . = ALIGN(4); + _eheap = .; + } > RAM + + /* fictitious region that represents the memory available for the stack */ + .stack (NOLOAD) : + { + _estack = .; + . = ABSOLUTE(_stack_start); + _sstack = .; + } > RAM + + /* fake output .got section */ + /* Dynamic relocations are unsupported. This section is only used to detect + relocatable code in the input files and raise an error if relocatable code + is found */ + .got (INFO) : + { + KEEP(*(.got .got.*)); + } + + .eh_frame (INFO) : { KEEP(*(.eh_frame)) } + .eh_frame_hdr (INFO) : { *(.eh_frame_hdr) } +} + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + + +/* Do not exceed this mark in the error messages above | */ +ASSERT(ORIGIN(FLASH) % 4 == 0, " +ERROR(riscv-rt): the start of the FLASH must be 4-byte aligned"); + +ASSERT(ORIGIN(RAM) % 32 == 0, " +ERROR(riscv-rt): the start of the RAM must be 32-byte aligned"); + +ASSERT(_stext % 4 == 0, " +ERROR(riscv-rt): `_stext` must be 4-byte aligned"); + +ASSERT(_sdata % 32 == 0 && _edata % 32 == 0, " +BUG(riscv-rt): .data is not 32-byte aligned"); + +ASSERT(_sidata % 32 == 0, " +BUG(riscv-rt): the LMA of .data is not 32-byte aligned"); + +ASSERT(_sbss % 32 == 0 && _ebss % 32 == 0, " +BUG(riscv-rt): .bss is not 32-byte aligned"); + +ASSERT(_sheap % 4 == 0, " +BUG(riscv-rt): start of .heap is not 4-byte aligned"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " +ERROR(riscv-rt): The .text section must be placed inside the FLASH region. +Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); + +ASSERT(SIZEOF(.stack) > (_max_hart_id + 1) * _hart_stack_size, " +ERROR(riscv-rt): .stack section is too small for allocating stacks for all the harts. +Consider changing `_max_hart_id` or `_hart_stack_size`."); + +ASSERT(SIZEOF(.got) == 0, " +.got section detected in the input files. Dynamic relocations are not +supported. If you are linking to C code compiled using the `gcc` crate +then modify your build script to compile the C code _without_ the +-fPIC flag. See the documentation of the `gcc::Config.fpic` method for +details."); + +/* Do not exceed this mark in the error messages above | */ + diff --git a/rp235x-hal-examples/src/bin/adc.rs b/rp235x-hal-examples/src/bin/adc.rs new file mode 100644 index 000000000..76aad66d7 --- /dev/null +++ b/rp235x-hal-examples/src/bin/adc.rs @@ -0,0 +1,131 @@ +//! # ADC Example +//! +//! This application demonstrates how to read ADC samples from the temperature +//! sensor and pin and output them to the UART on pins 1 and 2 at 115200 baud. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal::delay::DelayNs; +use embedded_hal_0_2::adc::OneShot; +use hal::fugit::RateExtU32; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then prints the temperature +/// in an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC example\r\n"); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26).unwrap(); + loop { + // Read the raw ADC counts from the temperature sensor channel. + let temp_sens_adc_counts: u16 = adc.read(&mut temperature_sensor).unwrap(); + let pin_adc_counts: u16 = adc.read(&mut adc_pin_0).unwrap(); + writeln!( + uart, + "ADC readings: Temperature: {temp_sens_adc_counts:02} Pin: {pin_adc_counts:02}\r\n" + ) + .unwrap(); + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/adc_fifo_dma.rs b/rp235x-hal-examples/src/bin/adc_fifo_dma.rs new file mode 100644 index 000000000..74f1e0e10 --- /dev/null +++ b/rp235x-hal-examples/src/bin/adc_fifo_dma.rs @@ -0,0 +1,193 @@ +//! # ADC FIFO DMA Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! and reading them from the FIFO by using a DMA transfer. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::singleton; + +// Some things we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use fugit::RateExtU32; +use hal::dma::{single_buffer, DMAExt}; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then prints the temperature +/// in an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer1(pac.TIMER1, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC FIFO DMA example\r\n"); + + // Initialize DMA + let dma = pac.DMA.split(&mut pac.RESETS); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + // we'll capture 1000 samples in total (500 per channel) + // NOTE: when calling `shift_8bit` below, the type here must be changed from `u16` to `u8` + let buf_for_samples = singleton!(: [u16; 1000] = [0; 1000]).unwrap(); + + // Configure free-running mode: + let mut adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + // sample the temperature sensor first + .set_channel(&mut temperature_sensor) + // then alternate between GPIO26 and the temperature sensor + .round_robin((&adc_pin_0, &temperature_sensor)) + // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) + //.shift_8bit() + // Enable DMA transfers for the FIFO + .enable_dma() + // Create the FIFO, but don't start it just yet + .start_paused(); + + // Start a DMA transfer (must happen before resuming the ADC FIFO) + let dma_transfer = + single_buffer::Config::new(dma.ch0, adc_fifo.dma_read_target(), buf_for_samples).start(); + + // Resume the FIFO to start capturing + adc_fifo.resume(); + + // initialize a timer, to measure the total sampling time (printed below) + let timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // NOTE: in a real-world program, instead of calling `wait` now, you would probably: + // 1. Enable one of the DMA interrupts for the channel (e.g. `dma.ch0.enable_irq0()`) + // 2. Set up a handler for the respective `DMA_IRQ_*` interrupt + // 3. Call `wait` only within that interrupt, which will be fired once the transfer is complete. + + // the DMA unit takes care of shuffling data from the FIFO into the buffer. + // We just sit here and wait... 😴 + let (_ch, _adc_read_target, buf_for_samples) = dma_transfer.wait(); + + // ^^^ the three results here (channel, adc::DmaReadTarget, write target) can be reused + // right away to start another transfer. + + let time_taken = timer.get_counter(); + + uart.write_full_blocking(b"Done sampling, printing results:\r\n"); + + // Stop free-running mode (the returned `adc` can be reused for future captures) + let _adc = adc_fifo.stop(); + + // Print the measured values + for i in 0..500 { + writeln!( + uart, + "Temp:\t{}\tPin\t{}\r", + buf_for_samples[i * 2], + buf_for_samples[i * 2 + 1] + ) + .unwrap(); + } + + writeln!(uart, "Sampling took: {}\r", time_taken).unwrap(); + + loop { + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC FIFO DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/adc_fifo_irq.rs b/rp235x-hal-examples/src/bin/adc_fifo_irq.rs new file mode 100644 index 000000000..5553a9c38 --- /dev/null +++ b/rp235x-hal-examples/src/bin/adc_fifo_irq.rs @@ -0,0 +1,199 @@ +//! # ADC FIFO Interrupt Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! using the FIFO interrupt. +//! +//! It utilizes `rtic` (cortex-m-rtic crate) to safely share peripheral access between +//! initialization code and interrupt handlers. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +#[rtic::app(device = hal::pac)] +mod app { + use core::fmt::Write; + use fugit::RateExtU32; + use hal::Clock; + use rp235x_hal as hal; + + /// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. + /// Adjust if your board has a different frequency + const XTAL_FREQ_HZ: u32 = 12_000_000u32; + + // This example will capture 1000 samples to `shared.buf`. + // When it is done, it will stop the ADC, set `shared.done` to true, + // print the result and loop forever. + + const SAMPLE_COUNT: usize = 1000; + + type Uart = hal::uart::UartPeripheral< + hal::uart::Enabled, + hal::pac::UART0, + ( + hal::gpio::Pin, + hal::gpio::Pin, + ), + >; + + #[shared] + struct Shared { + done: bool, + buf: [u16; SAMPLE_COUNT], + uart: Uart, + } + + #[local] + struct Local { + adc_fifo: Option>, + } + + #[init(local = [adc: Option = None])] + fn init(c: init::Context) -> (Shared, Local, init::Monotonics) { + // Soft-reset does not release the hardware spinlocks + // Release them now to avoid a deadlock after debug or watchdog reset + unsafe { + hal::sio::spinlock_reset(); + } + + let mut resets = c.device.RESETS; + let mut watchdog = hal::Watchdog::new(c.device.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + c.device.XOSC, + c.device.CLOCKS, + c.device.PLL_SYS, + c.device.PLL_USB, + &mut resets, + &mut watchdog, + ) + .unwrap(); + let sio = hal::Sio::new(c.device.SIO); + let pins = hal::gpio::Pins::new( + c.device.IO_BANK0, + c.device.PADS_BANK0, + sio.gpio_bank0, + &mut resets, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let uart = hal::uart::UartPeripheral::new(c.device.UART0, uart_pins, &mut resets) + .enable( + hal::uart::UartConfig::new( + 115200.Hz(), + hal::uart::DataBits::Eight, + None, + hal::uart::StopBits::One, + ), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // the ADC is put into a local, to gain static lifetime + *c.local.adc = Some(hal::Adc::new(c.device.ADC, &mut resets)); + let adc = c.local.adc.as_mut().unwrap(); + + let mut adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + uart.write_full_blocking(b"ADC FIFO interrupt example\r\n"); + + let adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + .set_channel(&mut adc_pin_0) + .enable_interrupt(1) + .start(); + + ( + Shared { + done: false, + buf: [0; SAMPLE_COUNT], + uart, + }, + Local { + adc_fifo: Some(adc_fifo), + }, + init::Monotonics(), + ) + } + + #[idle(shared = [done, buf, uart])] + fn idle(mut c: idle::Context) -> ! { + loop { + let finished = (&mut c.shared.done, &mut c.shared.buf, &mut c.shared.uart).lock( + |done, buf, uart| { + if *done { + for sample in buf { + writeln!(uart, "Sample: {}\r", sample).unwrap(); + } + writeln!(uart, "All done, going to sleep 😴\r").unwrap(); + true + } else { + false + } + }, + ); + + if finished { + break; + } + } + + #[allow(clippy::empty_loop)] + loop {} + } + + #[task( + binds = ADC_IRQ_FIFO, + priority = 1, + shared = [done, buf], + local = [adc_fifo, counter: usize = 0] + )] + fn adc_irq_fifo(mut c: adc_irq_fifo::Context) { + let sample = c.local.adc_fifo.as_mut().unwrap().read(); + let i = *c.local.counter; + c.shared.buf.lock(|buf| buf[i] = sample); + *c.local.counter += 1; + + if *c.local.counter == SAMPLE_COUNT { + c.local.adc_fifo.take().unwrap().stop(); + c.shared.done.lock(|done| *done = true); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC FIFO IRQ Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/adc_fifo_poll.rs b/rp235x-hal-examples/src/bin/adc_fifo_poll.rs new file mode 100644 index 000000000..ad4ec5cb1 --- /dev/null +++ b/rp235x-hal-examples/src/bin/adc_fifo_poll.rs @@ -0,0 +1,198 @@ +//! # ADC FIFO Example +//! +//! This application demonstrates how to read ADC samples in free-running mode, +//! and reading them from the FIFO by polling the fifo's `len()`. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use fugit::RateExtU32; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then prints the temperature +/// in an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // UART TX (characters sent from pico) on pin 1 (GPIO0) and RX (on pin 2 (GPIO1) + let uart_pins = ( + pins.gpio0.into_function::(), + pins.gpio1.into_function::(), + ); + + // Create a UART driver + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Write to the UART + uart.write_full_blocking(b"ADC FIFO poll example\r\n"); + + // Enable ADC + let mut adc = hal::Adc::new(pac.ADC, &mut pac.RESETS); + + // Enable the temperature sense channel + let mut temperature_sensor = adc.take_temp_sensor().unwrap(); + + // Configure GPIO26 as an ADC input + let adc_pin_0 = hal::adc::AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); + + // Configure free-running mode: + let mut adc_fifo = adc + .build_fifo() + // Set clock divider to target a sample rate of 1000 samples per second (1ksps). + // The value was calculated by `(48MHz / 1ksps) - 1 = 47999.0`. + // Please check the `clock_divider` method documentation for details. + .clock_divider(47999, 0) + // sample the temperature sensor first + .set_channel(&mut temperature_sensor) + // then alternate between GPIO26 and the temperature sensor + .round_robin((&adc_pin_0, &temperature_sensor)) + // Uncomment this line to produce 8-bit samples, instead of 12 bit (lower bits are discarded) + //.shift_8bit() + // start sampling + .start(); + + // we'll capture 1000 samples in total (500 per channel) + let mut temp_samples = [0; 500]; + let mut pin_samples = [0; 500]; + let mut i = 0; + + // initialize a timer, to measure the total sampling time (printed below) + let timer = hal::Timer::new_timer1(pac.TIMER1, &mut pac.RESETS, &clocks); + + loop { + // busy-wait until the FIFO contains at least two samples: + while adc_fifo.len() < 2 {} + + // fetch two values from the fifo + let temp_result = adc_fifo.read(); + let pin_result = adc_fifo.read(); + + // uncomment this line, to trigger an "underrun" condition + //let _extra_sample = adc_fifo.read(); + + if adc_fifo.is_over() { + // samples were pushed into the fifo faster they were read + uart.write_full_blocking(b"FIFO overrun!\r\n"); + } + if adc_fifo.is_under() { + // we tried to read samples more quickly than they were pushed into the fifo + uart.write_full_blocking(b"FIFO underrun!\r\n"); + } + + temp_samples[i] = temp_result; + pin_samples[i] = pin_result; + + i += 1; + + // uncomment this line to trigger an "overrun" condition + //delay.delay_ms(1000); + + if i == 500 { + break; + } + } + + let time_taken = timer.get_counter(); + + uart.write_full_blocking(b"Done sampling, printing results:\r\n"); + + // Stop free-running mode (the returned `adc` can be reused for future captures) + let _adc = adc_fifo.stop(); + + // Print the measured values + for i in 0..500 { + writeln!( + uart, + "Temp:\t{}\tPin\t{}\r", + temp_samples[i], pin_samples[i] + ) + .unwrap(); + } + + writeln!(uart, "Sampling took: {}\r", time_taken).unwrap(); + + loop { + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"ADC FIFO Poll Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/alloc.rs b/rp235x-hal-examples/src/bin/alloc.rs new file mode 100644 index 000000000..9c5b3d233 --- /dev/null +++ b/rp235x-hal-examples/src/bin/alloc.rs @@ -0,0 +1,124 @@ +//! # Alloc Example +//! +//! Uses alloc to create a Vec. +//! +//! This will blink an LED attached to GP25, which is the pin the Pico uses for +//! the on-board LED. It may need to be adapted to your particular board layout +//! and/or pin assignment. +//! +//! While blinking the LED, it will continuously push to a `Vec`, which will +//! eventually lead to a panic due to an out of memory condition. +//! +//! See the `Cargo.toml` file for Copyright and licence details. + +#![no_std] +#![no_main] + +extern crate alloc; + +use alloc::vec::Vec; +use embedded_alloc::Heap; + +#[global_allocator] +static ALLOCATOR: Heap = Heap::empty(); + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2350 peripherals, then blinks the LED in an +/// infinite loop where the duration indicates how many items were allocated. +#[hal::entry] +fn main() -> ! { + { + use core::mem::MaybeUninit; + const HEAP_SIZE: usize = 1024; + static mut HEAP: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + unsafe { ALLOCATOR.init(HEAP.as_ptr() as usize, HEAP_SIZE) } + } + + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + + let mut xs = Vec::new(); + xs.push(1); + + // Blink the LED at 1 Hz + loop { + led_pin.set_high().unwrap(); + let len = xs.len() as u32; + timer.delay_ms(100 * len); + xs.push(1); + led_pin.set_low().unwrap(); + timer.delay_ms(100 * len); + xs.push(1); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Memory Allocation Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/arch_flip.rs b/rp235x-hal-examples/src/bin/arch_flip.rs new file mode 100644 index 000000000..f79c6beea --- /dev/null +++ b/rp235x-hal-examples/src/bin/arch_flip.rs @@ -0,0 +1,110 @@ +//! # Architecture Flip example +//! +//! This application demonstrates running both Arm and RISC-V code on the same +//! chip, using partitions. +//! +//! You need a partition table with one partition for Arm (marked NO-BOOT for +//! RISC-V) and one partition for RISC-V (marked NO-BOOT for Arm). +//! +//! It will run for a few seconds, and then reboot into the other. You can tell +//! the difference because they have different blink rates. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + #[cfg(target_arch = "arm")] + let (delay_ms, arch) = (500, hal::reboot::RebootArch::Riscv); + + #[cfg(not(target_arch = "arm"))] + let (delay_ms, arch) = (250, hal::reboot::RebootArch::Arm); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + for _ in 0..10 { + led_pin.set_high().unwrap(); + timer.delay_ms(delay_ms); + led_pin.set_low().unwrap(); + timer.delay_ms(delay_ms); + } + + // Do an asynchronous reset into the bootloader, but flipping the architecture + hal::reboot::reboot(hal::reboot::RebootKind::Normal, arch); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Architecture Flip Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/binary_info_demo.rs b/rp235x-hal-examples/src/bin/binary_info_demo.rs new file mode 100644 index 000000000..a0cb55835 --- /dev/null +++ b/rp235x-hal-examples/src/bin/binary_info_demo.rs @@ -0,0 +1,102 @@ +//! # GPIO 'Blinky' Example, with Binary Info +//! +//! This application demonstrates how to control a GPIO pin on the RP2350, and +//! includes some picotool-compatible metadata. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::binary_info; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(500); + led_pin.set_low().unwrap(); + timer.delay_ms(500); + } +} + +/// This is a list of references to our table entries +/// +/// They must be in the `.bi_entries` section as we tell picotool the start and +/// end addresses of that section. +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 7] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"A GPIO blinky with extra metadata."), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), + hal::binary_info::rp_pico_board!(c"pico2"), + // An example with a non-Raspberry-Pi tag + hal::binary_info::int!(binary_info::make_tag(b"JP"), 0x0000_0001, 0x12345678), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/blinky.rs b/rp235x-hal-examples/src/bin/blinky.rs new file mode 100644 index 000000000..69472b90e --- /dev/null +++ b/rp235x-hal-examples/src/bin/blinky.rs @@ -0,0 +1,93 @@ +//! # GPIO 'Blinky' Example +//! +//! This application demonstrates how to control a GPIO pin on the rp235x. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(500); + led_pin.set_low().unwrap(); + timer.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/block_loop.rs b/rp235x-hal-examples/src/bin/block_loop.rs new file mode 100644 index 000000000..c2bc96274 --- /dev/null +++ b/rp235x-hal-examples/src/bin/block_loop.rs @@ -0,0 +1,118 @@ +//! # An example application with two Blocks. +//! +//! Our first block is an *Image Definition* Block and it points at our second +//! Block which contains a placeholder item for the purposes of this example. +//! The placeholder isn't useful per se, but it allows us to show how to +//! construct two Blocks in a loop without having to use `picotool` to modify +//! the application after compilation and linking. +//! +//! See [Section +//! 5.1.5.2](https://rptl.io/rp2350-datasheet#bootrom-concept-block-loop) in the +//! RP2350 datasheet for more information about Blocks and Block Loops. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +// The linker script exports these symbols. +extern "C" { + /// This value is at an address equal to the difference between the start block and end block + static start_to_end: u32; + /// This value is at an address equal to the difference between the end block and start block + static end_to_start: u32; +} + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static START_IMAGE_DEF: hal::block::ImageDef = + hal::block::ImageDef::secure_exe().with_offset(unsafe { core::ptr::addr_of!(start_to_end) }); + +/// A second Block, and the end of the program in flash +#[link_section = ".end_block"] +#[used] +pub static END_IMAGE_DEF: hal::block::Block<1> = + // Put a placeholder item in the block. + hal::block::Block::new([hal::block::item_ignored()]) + .with_offset(unsafe { core::ptr::addr_of!(end_to_start) }); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + loop { + led_pin.set_high().unwrap(); + timer.delay_ms(250); + led_pin.set_low().unwrap(); + timer.delay_ms(250); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!( + c"Blinks an LED, contains a Block Loop with two Blocks" + ), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/dht11.rs b/rp235x-hal-examples/src/bin/dht11.rs new file mode 100644 index 000000000..0a033871b --- /dev/null +++ b/rp235x-hal-examples/src/bin/dht11.rs @@ -0,0 +1,102 @@ +//! # DHT11 Example +//! +//! This application demonstrates how to read a DHT11 sensor on the rp235x. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! In this example, the DHT11 data pin should be connected to GPIO28. +//! +//! NOTE: The DHT11 driver only works reliably when compiled in release mode. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +use dht_sensor::{dht11, DhtReading}; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, assigns GPIO 28 to the +/// DHT11 driver, and takes a single measurement. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Use GPIO 28 as an InOutPin + let mut pin = hal::gpio::InOutPin::new(pins.gpio28); + let _ = pin.set_high(); + + // Perform a sensor reading + let _measurement = dht11::Reading::read(&mut delay, &mut pin); + + // In this case, we just ignore the result. A real application + // would do something with the measurement. + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"DHT11 Sensor Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/float_test.rs b/rp235x-hal-examples/src/bin/float_test.rs new file mode 100644 index 000000000..3cf65478d --- /dev/null +++ b/rp235x-hal-examples/src/bin/float_test.rs @@ -0,0 +1,324 @@ +//! # DCP Example +//! +//! This example only works on Arm. +//! +//! This application demonstrates performing floating point operations +//! using the *Double Co-Processor*. Note that by default, DCP acceleration +//! is enabled. To see how slow software floating point operations are, +//! build this example with `--no-default-features`. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! Typical output (with default features): +//! +//! ```text +//! Floating Point Test +//! Testing software f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.048638031340549406 +//! End : acc=48.63803134055, took 23.8 cycles per op +//! Testing DCP f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.048638031340549406 +//! End : acc=48.63803134055, took 37.9 cycles per op +//! Testing FPU f32 addition +//! Running 1000 loops for f32 +//! Start: acc=0, arg=0.04863803 +//! End : acc=48.63764, took 3.5 cycles per op +//! Testing software f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.048638031340549406, arg=1.0001 +//! End : acc=0.053753069001926, took 40.8 cycles per op +//! Testing DCP f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.048638031340549406, arg=1.0001 +//! End : acc=0.053753069001926, took 54.7 cycles per op +//! Testing FPU f32 multiplication +//! Running 1000 loops for f32 +//! Start: acc=0.04863803, arg=1.0001 +//! End : acc=0.05375396, took 3.4 cycles per op +//! Rebooting now +//! PANIC: +//! PanicInfo { payload: Any { .. }, message: Some(Finished!), location: Location { file: "rp235x-hal/examples/float_test.rs", line: 166, col: 5 }, can_unwind: true, force_no_backtrace: false } +//! ``` +//! +//! Typical output (with no default features): +//! +//! ```text +//! Floating Point Test +//! Testing software f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.8188217810460334 +//! End : acc=818.8217810460395, took 151.3 cycles per op +//! Testing DCP f64 addition +//! Running 1000 loops for f64 +//! Start: acc=0, arg=0.8188217810460334 +//! End : acc=818.8217810460395, took 38.0 cycles per op +//! Testing FPU f32 addition +//! Running 1000 loops for f32 +//! Start: acc=0, arg=0.8188218 +//! End : acc=818.82947, took 3.4 cycles per op +//! Testing software f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.8188217810460334, arg=1.0001 +//! End : acc=0.9049334951218081, took 123.0 cycles per op +//! Testing DCP f64 multiplication +//! Running 1000 loops for f64 +//! Start: acc=0.8188217810460334, arg=1.0001 +//! End : acc=0.9049334951218081, took 55.0 cycles per op +//! Testing FPU f32 multiplication +//! Running 1000 loops for f32 +//! Start: acc=0.8188218, arg=1.0001 +//! End : acc=0.9049483, took 3.4 cycles per op +//! Rebooting now +//! PANIC: +//! PanicInfo { payload: Any { .. }, message: Some(Finished!), location: Location { file: "rp235x-hal/examples/float_test.rs", line: 166, col: 5 }, can_unwind: true, force_no_backtrace: false } +//! ``` +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::{ + gpio, + uart::{DataBits, StopBits, UartConfig, UartPeripheral}, +}; + +use cortex_m_rt::exception; + +// Some things we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use hal::Clock; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct GlobalUart { + inner: critical_section::Mutex>>, +} + +type MyUart = UartPeripheral< + hal::uart::Enabled, + hal::pac::UART0, + ( + gpio::Pin, + gpio::Pin, + ), +>; + +static GLOBAL_UART: GlobalUart = GlobalUart { + inner: critical_section::Mutex::new(core::cell::RefCell::new(None)), +}; + +impl core::fmt::Write for &GlobalUart { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + critical_section::with(|cs| { + let mut cell = self.inner.borrow_ref_mut(cs); + let uart = cell.as_mut().unwrap(); + uart.write_str(s) + }) + } +} + +impl GlobalUart { + fn init(&self, uart: MyUart) { + critical_section::with(|cs| { + self.inner.borrow(cs).replace(Some(uart)); + }); + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + let mut cp = cortex_m::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + GLOBAL_UART.init(uart); + + writeln!(&GLOBAL_UART, "\n\nFloating Point Test").unwrap(); + + // Optionally start a DCP operation, but not finish it, to see if the save/restore fires. + // + // If you uncomment this, you'll observe the DCP operations take ~46 cycles longer + // to do all the stacking/restoring of DCP state. + + // unsafe { + // let val0 = 0; + // let val1 = 0; + // core::arch::asm!( + // "mcrr p4,#1,{0},{1},c0 // WXUP {0},{1} - write {1}|{0} to xm and xe", + // in(reg) val0, + // in(reg) val1 + // ); + // } + + cp.DCB.enable_trace(); + cp.DWT.enable_cycle_counter(); + + let int_seed = get_random_seed(); + // make up a random number between 0 and 1 so the optimiser can't cheat and + // we are forced to do the maths + let seed = (int_seed & u128::from(u64::MAX)) as f64 / u64::MAX as f64; + + writeln!(&GLOBAL_UART, "Testing aeabi f64 addition").unwrap(); + benchmark(0.0, seed, |x, y| x + y, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing DCP f64 addition").unwrap(); + benchmark(0.0, seed, hal::dcp::dadd, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing aeabi f32 addition").unwrap(); + benchmark(0.0, seed as f32, |x, y| x + y, &cp.DWT); + + writeln!(&GLOBAL_UART, "Testing aeabi f64 multiplication").unwrap(); + benchmark(seed, 1.0001f64, |x, y| x * y, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing DCP f64 multiplication").unwrap(); + benchmark(seed, 1.0001f64, hal::dcp::dmul, &cp.DWT); + writeln!(&GLOBAL_UART, "Testing aeabi f32 multiplication").unwrap(); + benchmark(seed as f32, 1.0001f32, |x, y| x * y, &cp.DWT); + + writeln!(&GLOBAL_UART, "Rebooting now").unwrap(); + + panic!("Finished!"); +} + +/// Use the SysInfo API to get a boot-time generated random number. +fn get_random_seed() -> u128 { + let mut buffer = [0u32; 5]; + let sys_info_mask = 0x0010; + unsafe { hal::rom_data::get_sys_info(buffer.as_mut_ptr(), buffer.len(), sys_info_mask) }; + let mut result: u128 = 0; + for word in &buffer[1..] { + result <<= 32; + result |= u128::from(*word); + } + result +} + +/// Run the given operation in a loop. +/// +/// Uses the DWT to calculate elapsed CPU cycles. +fn benchmark(acc: T, arg: T, mut f: F, dwt: &cortex_m::peripheral::DWT) +where + T: core::fmt::Display + core::marker::Copy, + F: FnMut(T, T) -> T, +{ + const LOOPS: u16 = 1000; + writeln!( + &GLOBAL_UART, + "Running {} loops for {}", + LOOPS, + core::any::type_name::() + ) + .unwrap(); + let mut acc: T = acc; + writeln!(&GLOBAL_UART, "Start: acc={}, arg={}", acc, arg).unwrap(); + + let start_count = dwt.cyccnt.read(); + for _ in 0..LOOPS { + acc = f(acc, arg); + } + let end_count = dwt.cyccnt.read(); + + let delta = end_count.wrapping_sub(start_count); + let cycles_per_op = delta as f32 / LOOPS as f32; + writeln!( + &GLOBAL_UART, + "End : acc={acc}, took {cycles_per_op:.1} cycles per op" + ) + .unwrap(); +} + +#[exception] +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + let _ = writeln!(&GLOBAL_UART, "HARD FAULT:\n{:#?}", ef); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(&GLOBAL_UART, "PANIC:\n{:?}", info); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Floating Point Test"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/gpio_dyn_pin_array.rs b/rp235x-hal-examples/src/bin/gpio_dyn_pin_array.rs new file mode 100644 index 000000000..0ca06f868 --- /dev/null +++ b/rp235x-hal-examples/src/bin/gpio_dyn_pin_array.rs @@ -0,0 +1,138 @@ +//! # GPIO Dynamic Pin Type Mode Example +//! +//! This application demonstrates how to put GPIO pins into their Dynamic Pin type on the rp235x. +//! +//! Usually, the type of each pin is different (which allows the type system to catch misuse). +//! But this stops you storing the pins in an array, or allowing a struct to take any pin. +//! This mode is also referred to as "Erased", "Downgraded", "Degraded", or "Dynamic". +//! +//! In order to see the result of this program, you will need to put LEDs and a current limiting +//! resistor on each of GPIO 2, 3, 4, 5. +//! The other side of the LED + resistor pair should be connected to GND. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::gpio::{DynPinId, FunctionSioOutput, Pin, PullNone, PullUp}; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // We will use the rp235x timer peripheral as our delay source + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // To put pins into an array we have to convert them to Dynamically Typed pins. + // This means they'll carry their pin and bank numbers around with them at run time, + // rather than relying on the Type of the pin to track that. + let mut pinarray: [Pin; 4] = [ + pins.gpio2 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio3 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio4 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + pins.gpio5 + .into_push_pull_output() + .into_pull_type() + .into_dyn_pin(), + ]; + + // Also set a pin as a dynamic input. We won't use this, it is just to demonstrate that + // pins can have other functions and still be Dynamically typed. + let _in_pin = pins.gpio23.into_floating_input().into_dyn_pin(); + + // You can also let the target type set the pin mode, using the type system to guide it. + // Once again, we're not going to use this array. The only reason it is here is to demonstrate a less verbose way to set pin modes + let mut _type_coerce: [Pin; 1] = + [pins.gpio22.reconfigure().into_dyn_pin()]; + + // Light one LED at a time. Start at GPIO2 and go through to GPIO5, then reverse. + loop { + for led in pinarray.iter_mut() { + led.set_high().unwrap(); + timer.delay_ms(50); + led.set_low().unwrap(); + } + for led in pinarray.iter_mut().rev() { + led.set_high().unwrap(); + timer.delay_ms(50); + led.set_low().unwrap(); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"GPIO Dynamic Pin Array Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/gpio_in_out.rs b/rp235x-hal-examples/src/bin/gpio_in_out.rs new file mode 100644 index 000000000..06ab24e80 --- /dev/null +++ b/rp235x-hal-examples/src/bin/gpio_in_out.rs @@ -0,0 +1,97 @@ +//! # GPIO In/Out Example +//! +//! This application demonstrates how to control GPIO pins on the RP2040. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::InputPin; +use embedded_hal::digital::OutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let _clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output + let mut out_pin = pins.gpio25.into_push_pull_output(); + + // Configure GPIO 23 as an input + let mut in_pin = pins.gpio23.into_pull_down_input(); + + // Output is the opposite of the input + loop { + if in_pin.is_low().unwrap() { + out_pin.set_high().unwrap(); + } else { + out_pin.set_low().unwrap(); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"GPIO Input/Output Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/gpio_irq_example.rs b/rp235x-hal-examples/src/bin/gpio_irq_example.rs new file mode 100644 index 000000000..03baf9886 --- /dev/null +++ b/rp235x-hal-examples/src/bin/gpio_irq_example.rs @@ -0,0 +1,189 @@ +//! # GPIO IRQ Example +//! +//! This application demonstrates use of GPIO Interrupts. +//! It is also intended as a general introduction to interrupts with rp235x. +//! +//! Each GPIO can be triggered on the input being high (LevelHigh), being low (LevelLow) +//! starting high and then going low (EdgeLow) or starting low and becoming high (EdgeHigh) +//! +//! In this example, we trigger on EdgeLow. Our input pin configured to be pulled to the high logic-level +//! via an internal pullup resistor. This resistor is quite weak, so you can bring the logic level back to low +//! via an external jumper wire or switch. +//! Whenever we see the edge transition, we will toggle the output on GPIO25 - this is the LED pin on a Pico. +//! +//! Note that this demo does not perform any [software debouncing](https://en.wikipedia.org/wiki/Switch#Contact_bounce). +//! You can fix that through hardware, or you could disable the button interrupt in the interrupt and re-enable it +//! some time later using one of the Alarms of the Timer peripheral - this is left as an exercise for the reader. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::StatefulOutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// Some short-cuts to useful types +use core::cell::RefCell; +use critical_section::Mutex; +use hal::gpio; + +// The GPIO interrupt type we're going to generate +use gpio::Interrupt::EdgeLow; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +// Pin types quickly become very long! +// We'll create some type aliases using `type` to help with that + +/// This pin will be our output - it will drive an LED if you run this on a Pico +type LedPin = gpio::Pin; + +/// This pin will be our interrupt source. +/// It will trigger an interrupt if pulled to ground (via a switch or jumper wire) +type ButtonPin = gpio::Pin; + +/// Since we're always accessing these pins together we'll store them in a tuple. +/// Giving this tuple a type alias means we won't need to use () when putting them +/// inside an Option. That will be easier to read. +type LedAndButton = (LedPin, ButtonPin); + +/// This how we transfer our Led and Button pins into the Interrupt Handler. +/// We'll have the option hold both using the LedAndButton type. +/// This will make it a bit easier to unpack them later. +static GLOBAL_PINS: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let _clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO 25 as an output to drive our LED. + // we can use reconfigure() instead of into_pull_up_input() + // since the variable we're pushing it into has that type + let led = pins.gpio25.reconfigure(); + + // Set up the GPIO pin that will be our input + let in_pin = pins.gpio26.reconfigure(); + + // Trigger on the 'falling edge' of the input pin. + // This will happen as the button is being pressed + in_pin.set_interrupt_enabled(EdgeLow, true); + + // Give away our pins by moving them into the `GLOBAL_PINS` variable. + // We won't need to access them in the main thread again + critical_section::with(|cs| { + GLOBAL_PINS.borrow(cs).replace(Some((led, in_pin))); + }); + + // Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::IO_IRQ_BANK0); + } + + loop { + // interrupts handle everything else in this example. + hal::arch::wfi(); + } +} + +#[interrupt] +fn IO_IRQ_BANK0() { + // The `#[interrupt]` attribute covertly converts this to `&'static mut Option` + static mut LED_AND_BUTTON: Option = None; + + // This is one-time lazy initialisation. We steal the variables given to us + // via `GLOBAL_PINS`. + if LED_AND_BUTTON.is_none() { + critical_section::with(|cs| { + *LED_AND_BUTTON = GLOBAL_PINS.borrow(cs).take(); + }); + } + + // Need to check if our Option contains our pins + if let Some(gpios) = LED_AND_BUTTON { + // borrow led and button by *destructuring* the tuple + // these will be of type `&mut LedPin` and `&mut ButtonPin`, so we don't have + // to move them back into the static after we use them + let (led, button) = gpios; + // Check if the interrupt source is from the pushbutton going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if button.interrupt_status(EdgeLow) { + // toggle can't fail, but the embedded-hal traits always allow for it + // we can discard the return value by assigning it to an unnamed variable + let _ = led.toggle(); + + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + button.clear_interrupt(EdgeLow); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"GPIO IRQ Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/i2c.rs b/rp235x-hal-examples/src/bin/i2c.rs new file mode 100644 index 000000000..3b9627867 --- /dev/null +++ b/rp235x-hal-examples/src/bin/i2c.rs @@ -0,0 +1,108 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an rp235x. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal_0_2::blocking::i2c::Write; +use hal::fugit::RateExtU32; +use hal::gpio::{FunctionI2C, Pin}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then performs a single I²C +/// write to a fixed address. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, _> = pins.gpio18.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, _> = pins.gpio19.reconfigure(); + // let not_an_scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::i2c1( + pac.I2C1, + sda_pin, + scl_pin, // Try `not_an_scl_pin` here + 400.kHz(), + &mut pac.RESETS, + &clocks.system_clock, + ); + + // Write three bytes to the I²C device with 7-bit address 0x2C + i2c.write(0x2Cu8, &[1, 2, 3]).unwrap(); + + // Demo finish - just loop until reset + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"I²C Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/i2c_async.rs b/rp235x-hal-examples/src/bin/i2c_async.rs new file mode 100644 index 000000000..8b104bc99 --- /dev/null +++ b/rp235x-hal-examples/src/bin/i2c_async.rs @@ -0,0 +1,133 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP235x. +//! in an Async environment. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use hal::{ + fugit::RateExtU32, + gpio::bank0::{Gpio20, Gpio21}, + gpio::{FunctionI2C, Pin, PullUp}, + i2c::Controller, + pac::interrupt, + Clock, I2C, +}; + +// Import required types & traits. +use embedded_hal_async::i2c::I2c; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Bind the interrupt handler with the peripheral +#[interrupt] +unsafe fn I2C0_IRQ() { + use hal::async_utils::AsyncPeripheral; + I2C::::on_interrupt(); +} + +/// The function configures the RP235x peripherals, then performs a single I²C +/// write to a fixed address. +async fn demo() { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio21.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::new_controller( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + + // Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler. + // Each core has its own NVIC so these needs to executed from the core where the IRQ are + // expected. + unsafe { + cortex_m::peripheral::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ); + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ); + } + + // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C + i2c.write(0x76u8, &[1, 2, 3]).await.unwrap(); + + // Demo finish - just loop until reset + core::future::pending().await +} + +/// Entry point to our bare-metal application. +#[hal::entry] +fn main() -> ! { + let runtime = nostd_async::Runtime::new(); + let mut task = nostd_async::Task::new(demo()); + let handle = task.spawn(&runtime); + handle.join(); + unreachable!() +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"I²C Async Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/i2c_async_cancelled.rs b/rp235x-hal-examples/src/bin/i2c_async_cancelled.rs new file mode 100644 index 000000000..f9b069389 --- /dev/null +++ b/rp235x-hal-examples/src/bin/i2c_async_cancelled.rs @@ -0,0 +1,153 @@ +//! # I²C Example +//! +//! This application demonstrates how to talk to I²C devices with an RP235x. +//! in an Async environment. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +use core::task::Poll; +use embedded_hal_async::i2c::I2c; +use futures::FutureExt; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use hal::{ + fugit::RateExtU32, + gpio::bank0::{Gpio20, Gpio21}, + gpio::{FunctionI2C, Pin, PullUp}, + i2c::Controller, + pac::interrupt, + Clock, I2C, +}; + +use defmt_rtt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[interrupt] +unsafe fn I2C0_IRQ() { + use hal::async_utils::AsyncPeripheral; + I2C::::on_interrupt(); +} + +/// The function configures the RP235x peripherals, then performs a single I²C +/// write to a fixed address. +async fn demo() { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio20.reconfigure(); + let scl_pin: Pin<_, FunctionI2C, PullUp> = pins.gpio21.reconfigure(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let mut i2c = hal::I2C::new_controller( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.system_clock.freq(), + ); + + // Unmask the interrupt in the NVIC to let the core wake up & enter the interrupt handler. + unsafe { + cortex_m::peripheral::NVIC::unpend(hal::pac::Interrupt::I2C0_IRQ); + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::I2C0_IRQ); + } + + let mut cnt = 0; + let timeout = core::future::poll_fn(|cx| { + cx.waker().wake_by_ref(); + if cnt == 1 { + Poll::Ready(()) + } else { + cnt += 1; + Poll::Pending + } + }); + + let mut v = [0; 32]; + v.iter_mut().enumerate().for_each(|(i, v)| *v = i as u8); + + // Asynchronously write three bytes to the I²C device with 7-bit address 0x2C + futures::select_biased! { + r = i2c.write(0x76u8, &v).fuse() => r.unwrap(), + _ = timeout.fuse() => { + defmt::info!("Timed out."); + } + } + i2c.write(0x76u8, &v).await.unwrap(); + + // Demo finish - just loop until reset + core::future::pending().await +} + +/// Entry point to our bare-metal application. +#[hal::entry] +fn main() -> ! { + let runtime = nostd_async::Runtime::new(); + let mut task = nostd_async::Task::new(demo()); + let handle = task.spawn(&runtime); + handle.join(); + unreachable!() +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"I²C Async Cancellation Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/lcd_display.rs b/rp235x-hal-examples/src/bin/lcd_display.rs new file mode 100644 index 000000000..b94301e7a --- /dev/null +++ b/rp235x-hal-examples/src/bin/lcd_display.rs @@ -0,0 +1,118 @@ +//! # LCD Display Example +//! +//! In this example, the rp235x is configured to drive a small two-line +//! alphanumeric LCD using the +//! [HD44780](https://crates.io/crates/hd44780-driver) driver. +//! +//! It drives the LCD by pushing data out of six GPIO pins. It may need to be +//! adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Our LCD driver +use hd44780_driver as hd44780; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, writes to the LCD, then goes +/// to sleep. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Create the LCD driver from some GPIO pins + let mut lcd = hd44780::HD44780::new_4bit( + pins.gpio16.into_push_pull_output(), // Register Select + pins.gpio17.into_push_pull_output(), // Enable + pins.gpio18.into_push_pull_output(), // d4 + pins.gpio19.into_push_pull_output(), // d5 + pins.gpio20.into_push_pull_output(), // d6 + pins.gpio21.into_push_pull_output(), // d7 + &mut delay, + ) + .unwrap(); + + // Clear the screen + lcd.reset(&mut delay).unwrap(); + lcd.clear(&mut delay).unwrap(); + + // Write to the top line + lcd.write_str("rp-hal on", &mut delay).unwrap(); + + // Move the cursor + lcd.set_cursor_pos(40, &mut delay).unwrap(); + + // Write more more text + lcd.write_str("HD44780!", &mut delay).unwrap(); + + // Do nothing - we're finished + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"LCD Display Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/mem_to_mem_dma.rs b/rp235x-hal-examples/src/bin/mem_to_mem_dma.rs new file mode 100644 index 000000000..8748289cc --- /dev/null +++ b/rp235x-hal-examples/src/bin/mem_to_mem_dma.rs @@ -0,0 +1,95 @@ +//! # Memory to memory DMA transfer Example +//! +//! This application demonstrates how to use DMA to transfer data from memory to memory buffers. +//! +//! See the `Cargo.toml` file for Copyright and licence details. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::singleton; + +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; + +use hal::dma::{single_buffer, DMAExt}; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Setup clocks and the watchdog. + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Use DMA to transfer some bytes (single buffering). + let tx_buf = singleton!(: [u8; 16] = [0x42; 16]).unwrap(); + let rx_buf = singleton!(: [u8; 16] = [0; 16]).unwrap(); + + // Use a single_buffer to read from tx_buf and write into rx_buf + let transfer = single_buffer::Config::new(dma.ch0, tx_buf, rx_buf).start(); + // Wait for both DMA channels to finish + let (_ch, tx_buf, rx_buf) = transfer.wait(); + + // Compare buffers to see if the data was transferred correctly + // Slow blink on success, fast on failure + let delay_ms = if tx_buf == rx_buf { 1000 } else { 100 }; + + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(delay_ms); + led_pin.set_low().unwrap(); + delay.delay_ms(delay_ms); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Memory-Memory DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs b/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs new file mode 100644 index 000000000..50a901dc3 --- /dev/null +++ b/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs @@ -0,0 +1,180 @@ +//! # Multicore FIFO + GPIO 'Blinky' Example +//! +//! This application demonstrates FIFO communication between the CPU cores on the rp235x. +//! Core 0 will calculate and send a delay value to Core 1, which will then wait that long +//! before toggling the LED. +//! Core 0 will wait for Core 1 to complete this task and send an acknowledgement value. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::StatefulOutputPin; +use hal::clocks::Clock; +use hal::multicore::{Multicore, Stack}; +use hal::sio::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Value to indicate that Core 1 has completed its task +const CORE1_TASK_COMPLETE: u32 = 0xEE; + +/// Stack for core 1 +/// +/// Core 0 gets its stack via the normal route - any memory not used by static values is +/// reserved for stack and initialised by cortex-m-rt. +/// To get the same for Core 1, we would need to compile everything seperately and +/// modify the linker file for both programs, and that's quite annoying. +/// So instead, core1.spawn takes a [usize] which gets used for the stack. +/// NOTE: We use the `Stack` struct here to ensure that it has 32-byte alignment, which allows +/// the stack guard to take up the least amount of usable RAM. +static mut CORE1_STACK: Stack<4096> = Stack::new(); + +fn core1_task(sys_freq: u32) -> ! { + let mut pac = unsafe { hal::pac::Peripherals::steal() }; + let core = unsafe { cortex_m::Peripherals::steal() }; + + let mut sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = cortex_m::delay::Delay::new(core.SYST, sys_freq); + loop { + let input = sio.fifo.read(); + if let Some(word) = input { + delay.delay_ms(word); + led_pin.toggle().unwrap(); + sio.fifo.write_blocking(CORE1_TASK_COMPLETE); + }; + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let sys_freq = clocks.system_clock.freq().to_Hz(); + + // The single-cycle I/O block controls our GPIO pins + let mut sio = hal::sio::Sio::new(pac.SIO); + + let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); + let cores = mc.cores(); + let core1 = &mut cores[1]; + let _test = core1.spawn(unsafe { &mut CORE1_STACK.mem }, move || { + core1_task(sys_freq) + }); + + /// How much we adjust the LED period every cycle + const LED_PERIOD_INCREMENT: i32 = 2; + + /// The minimum LED toggle interval we allow for. + const LED_PERIOD_MIN: i32 = 0; + + /// The maximum LED toggle interval period we allow for. Keep it reasonably short so it's easy to see. + const LED_PERIOD_MAX: i32 = 100; + + // Our current LED period. It starts at the shortest period, which is the highest blink frequency + let mut led_period: i32 = LED_PERIOD_MIN; + + // The direction we're incrementing our LED period. + // Since we start at the minimum value, start by counting up + let mut count_up = true; + + loop { + if count_up { + // Increment our period + led_period += LED_PERIOD_INCREMENT; + + // Change direction of increment if we hit the limit + if led_period > LED_PERIOD_MAX { + led_period = LED_PERIOD_MAX; + count_up = false; + } + } else { + // Decrement our period + led_period -= LED_PERIOD_INCREMENT; + + // Change direction of increment if we hit the limit + if led_period < LED_PERIOD_MIN { + led_period = LED_PERIOD_MIN; + count_up = true; + } + } + + // It should not be possible for led_period to go negative, but let's ensure that. + if led_period < 0 { + led_period = 0; + } + + // Send the new delay time to Core 1. We convert it + sio.fifo.write(led_period as u32); + + // Sleep until Core 1 sends a message to tell us it is done + let ack = sio.fifo.read_blocking(); + if ack != CORE1_TASK_COMPLETE { + // In a real application you might want to handle the case + // where the CPU sent the wrong message - we're going to + // ignore it here. + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Multicore FIFO Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/multicore_polyblink.rs b/rp235x-hal-examples/src/bin/multicore_polyblink.rs new file mode 100644 index 000000000..a8ccfe9a1 --- /dev/null +++ b/rp235x-hal-examples/src/bin/multicore_polyblink.rs @@ -0,0 +1,136 @@ +//! # Multicore Blinking Example +//! +//! This application blinks two LEDs on GPIOs 2 and 3 at different rates (3Hz +//! and 4Hz respectively.) +//! +//! See the `Cargo.toml` file for Copyright and licence details. + +#![no_std] +#![no_main] + +use cortex_m::delay::Delay; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::clocks::Clock; +use hal::gpio::Pins; +use hal::multicore::{Multicore, Stack}; +use hal::sio::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Some things we need +use embedded_hal::digital::StatefulOutputPin; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// The frequency at which core 0 will blink its LED (Hz). +const CORE0_FREQ: u32 = 3; +/// The frequency at which core 1 will blink its LED (Hz). +const CORE1_FREQ: u32 = 4; +/// The delay between each toggle of core 0's LED (us). +const CORE0_DELAY: u32 = 1_000_000 / CORE0_FREQ; +/// The delay between each toggle of core 1's LED (us). +const CORE1_DELAY: u32 = 1_000_000 / CORE1_FREQ; + +/// Stack for core 1 +/// +/// Core 0 gets its stack via the normal route - any memory not used by static +/// values is reserved for stack and initialised by cortex-m-rt. +/// To get the same for Core 1, we would need to compile everything seperately +/// and modify the linker file for both programs, and that's quite annoying. +/// So instead, core1.spawn takes a [usize] which gets used for the stack. +/// NOTE: We use the `Stack` struct here to ensure that it has 32-byte +/// alignment, which allows the stack guard to take up the least amount of +/// usable RAM. +static mut CORE1_STACK: Stack<4096> = Stack::new(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + let core = cortex_m::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Set up the GPIO pins + let mut sio = Sio::new(pac.SIO); + let pins = Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + let mut led1 = pins.gpio2.into_push_pull_output(); + let mut led2 = pins.gpio3.into_push_pull_output(); + + // Set up the delay for the first core. + let sys_freq = clocks.system_clock.freq().to_Hz(); + let mut delay = Delay::new(core.SYST, sys_freq); + + // Start up the second core to blink the second LED + let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); + let cores = mc.cores(); + let core1 = &mut cores[1]; + core1 + .spawn(unsafe { &mut CORE1_STACK.mem }, move || { + // Get the second core's copy of the `CorePeripherals`, which are per-core. + // Unfortunately, `cortex-m` doesn't support this properly right now, + // so we have to use `steal`. + let core = unsafe { cortex_m::Peripherals::steal() }; + // Set up the delay for the second core. + let mut delay = Delay::new(core.SYST, sys_freq); + // Blink the second LED. + loop { + led2.toggle().unwrap(); + delay.delay_us(CORE1_DELAY) + } + }) + .unwrap(); + + // Blink the first LED. + loop { + led1.toggle().unwrap(); + delay.delay_us(CORE0_DELAY) + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Multicore Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pio_blink.rs b/rp235x-hal-examples/src/bin/pio_blink.rs new file mode 100644 index 000000000..e814dd8d6 --- /dev/null +++ b/rp235x-hal-examples/src/bin/pio_blink.rs @@ -0,0 +1,92 @@ +//! This example toggles the GPIO25 pin, using a PIO program. +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then blinks an LED using the PIO peripheral. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + const MAX_DELAY: u8 = 31; + let mut a = pio::Assembler::<32>::new(); + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + // Set pin as Out + a.set(pio::SetDestination::PINDIRS, 1); + // Define begin of program loop + a.bind(&mut wrap_target); + // Set pin low + a.set_with_delay(pio::SetDestination::PINS, 0, MAX_DELAY); + // Set pin high + a.set_with_delay(pio::SetDestination::PINS, 1, MAX_DELAY); + // Define end of program loop + a.bind(&mut wrap_source); + // The labels wrap_target and wrap_source, as set above, + // define a loop which is executed repeatedly by the PIO + // state machine. + let program = a.assemble_with_wrap(wrap_source, wrap_target); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (sm, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(led_pin_id, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pio_dma.rs b/rp235x-hal-examples/src/bin/pio_dma.rs new file mode 100644 index 000000000..63add2e26 --- /dev/null +++ b/rp235x-hal-examples/src/bin/pio_dma.rs @@ -0,0 +1,134 @@ +//! This example shows how to read from and write to PIO using DMA. +//! +//! If a LED is connected to that pin, like on a Pico board, it will continously output "HELLO +//! WORLD" in morse code. The example also tries to read the data back. If reading the data fails, +//! the message will only be shown once, and then the LED remains dark. +//! +//! See the `Cargo.toml` file for Copyright and licence details. +#![no_std] +#![no_main] + +use hal::singleton; + +use rp235x_hal as hal; + +use hal::dma::{double_buffer, single_buffer, DMAExt}; +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::sio::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // HELLO WORLD in morse code: + // .... . .-.. .-.. --- / .-- --- .-. .-.. -.. + #[allow(clippy::unusual_byte_groupings)] + let message = [ + 0b10101010_00100010_11101010_00101110, + 0b10100011_10111011_10000000_10111011, + 0b10001110_11101110_00101110_10001011, + 0b10101000_11101010_00000000_00000000, + ]; + + // Define a PIO program which reads data from the TX FIFO bit by bit, configures the LED + // according to the data, and then writes the data back to the RX FIFO. + let program = pio_proc::pio_asm!( + ".wrap_target", + " out x, 1", + " mov pins, x", + " in x, 1 [13]", + ".wrap" + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (mut sm, rx, tx) = hal::pio::PIOBuilder::from_installed_program(installed) + .out_pins(led_pin_id, 1) + .clock_divisor_fixed_point(0, 0) // as slow as possible (0 is interpreted as 65536) + .autopull(true) + .autopush(true) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + let dma = pac.DMA.split(&mut pac.RESETS); + + // Transfer a single message via DMA. + let tx_buf = singleton!(: [u32; 4] = message).unwrap(); + let rx_buf = singleton!(: [u32; 4] = [0; 4]).unwrap(); + let tx_transfer = single_buffer::Config::new(dma.ch0, tx_buf, tx).start(); + let rx_transfer = single_buffer::Config::new(dma.ch1, rx, rx_buf).start(); + let (ch0, tx_buf, tx) = tx_transfer.wait(); + let (ch1, rx, rx_buf) = rx_transfer.wait(); + for i in 0..rx_buf.len() { + if rx_buf[i] != tx_buf[i] { + // The data did not match, abort. + #[allow(clippy::empty_loop)] + loop {} + } + } + + // Chain some buffers together for continuous transfers + let tx_buf2 = singleton!(: [u32; 4] = message).unwrap(); + let rx_buf2 = singleton!(: [u32; 4] = [0; 4]).unwrap(); + let tx_transfer = double_buffer::Config::new((ch0, ch1), tx_buf, tx).start(); + let mut tx_transfer = tx_transfer.read_next(tx_buf2); + let rx_transfer = double_buffer::Config::new((dma.ch2, dma.ch3), rx, rx_buf).start(); + let mut rx_transfer = rx_transfer.write_next(rx_buf2); + loop { + // When a transfer is done we immediately enqueue the buffers again. + if tx_transfer.is_done() { + let (tx_buf, next_tx_transfer) = tx_transfer.wait(); + tx_transfer = next_tx_transfer.read_next(tx_buf); + } + if rx_transfer.is_done() { + let (rx_buf, next_rx_transfer) = rx_transfer.wait(); + for i in 0..rx_buf.len() { + if rx_buf[i] != message[i] { + // The data did not match, abort. + #[allow(clippy::empty_loop)] + loop {} + } + } + rx_transfer = next_rx_transfer.write_next(rx_buf); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Blinky with DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pio_proc_blink.rs b/rp235x-hal-examples/src/bin/pio_proc_blink.rs new file mode 100644 index 000000000..711701cdd --- /dev/null +++ b/rp235x-hal-examples/src/bin/pio_proc_blink.rs @@ -0,0 +1,80 @@ +//! This example toggles the GPIO25 pin, using a PIO program compiled via pio_proc::pio!(). +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + ".wrap_target", + "set pins, 1 [31]", + "set pins, 0 [31]", + ".wrap" + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (mut sm, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(led_pin_id, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Proc-macro Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pio_side_set.rs b/rp235x-hal-examples/src/bin/pio_side_set.rs new file mode 100644 index 000000000..c036a6cf1 --- /dev/null +++ b/rp235x-hal-examples/src/bin/pio_side_set.rs @@ -0,0 +1,83 @@ +//! This example toggles the GPIO25 pin, using a PIO program compiled via pio_proc::pio!(). +//! +//! If a LED is connected to that pin, like on a Pico board, the LED should blink. +//! +//! This example makes use of side setting. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure LED pin for Pio0. + let led: Pin<_, FunctionPio0, _> = pins.gpio25.into_function(); + // PIN id for use inside of PIO + let led_pin_id = led.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + ".side_set 1", // each instruction may set 1 bit + ".wrap_target", + " nop side 1 [15]", + " nop side 0 [15]", + ".wrap", + ); + + // Initialize and start PIO + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let installed = pio.install(&program.program).unwrap(); + let (int, frac) = (0, 0); // as slow as possible (0 is interpreted as 65536) + let (mut sm, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .side_set_pin_base(led_pin_id) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); + sm.start(); + + // PIO runs in background, independently from CPU + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Side-set Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pio_synchronized.rs b/rp235x-hal-examples/src/bin/pio_synchronized.rs new file mode 100644 index 000000000..b64839fba --- /dev/null +++ b/rp235x-hal-examples/src/bin/pio_synchronized.rs @@ -0,0 +1,117 @@ +//! This example toggles the GPIO0 and GPIO1 pins, with each controlled from a +//! separate PIO state machine. +//! +//! Despite running in separate state machines, the clocks are synchronized at +//! the rise and fall times will be simultaneous. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::gpio::{FunctionPio0, Pin}; +use hal::pio::PIOExt; +use hal::Sio; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + let sio = Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // configure pins for Pio0. + let gp0: Pin<_, FunctionPio0, _> = pins.gpio0.into_function(); + let gp1: Pin<_, FunctionPio0, _> = pins.gpio1.into_function(); + + // PIN id for use inside of PIO + let pin0 = gp0.id().num; + let pin1 = gp1.id().num; + + // Define some simple PIO program. + let program = pio_proc::pio_asm!( + " +.wrap_target + set pins, 1 [31] + set pins, 0 [31] +.wrap + " + ); + + // Initialize and start PIO + let (mut pio, sm0, sm1, _, _) = pac.PIO0.split(&mut pac.RESETS); + // I'm "measuring" the phase offset between the two pins by connecting + // then through a LED. If there is a clock offset, there will be a + // short time with a voltage between the pins, so the LED will flash up. + // With a slow clock this is not visible, so use a reasonably fast clock. + let (int, frac) = (256, 0); + + let installed = pio.install(&program.program).unwrap(); + let (mut sm0, _, _) = hal::pio::PIOBuilder::from_installed_program( + // Safety: We won't uninstall the program, ever + unsafe { installed.share() }, + ) + .set_pins(pin0, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm0); + // The GPIO pin needs to be configured as an output. + sm0.set_pindirs([(pin0, hal::pio::PinDir::Output)]); + + let (mut sm1, _, _) = hal::pio::PIOBuilder::from_installed_program(installed) + .set_pins(pin1, 1) + .clock_divisor_fixed_point(int, frac) + .build(sm1); + // The GPIO pin needs to be configured as an output. + sm1.set_pindirs([(pin1, hal::pio::PinDir::Output)]); + + // Start both SMs at the same time + let group = sm0.with(sm1).sync().start(); + hal::arch::delay(10_000_000); + + // Stop both SMs at the same time + let group = group.stop(); + hal::arch::delay(10_000_000); + + // Start them again and extract the individual state machines + let (sm1, sm2) = group.start().free(); + hal::arch::delay(10_000_000); + + // Stop the two state machines separately + let _sm1 = sm1.stop(); + hal::arch::delay(10_000_000); + let _sm2 = sm2.stop(); + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PIO Synchronisation Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/powman_test.rs b/rp235x-hal-examples/src/bin/powman_test.rs new file mode 100644 index 000000000..80a517ee0 --- /dev/null +++ b/rp235x-hal-examples/src/bin/powman_test.rs @@ -0,0 +1,326 @@ +//! # POWMAN Example +//! +//! This application demonstrates talking to the POWMAN peripheral. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use rp235x_hal::{ + self as hal, gpio, pac, + powman::{AotClockSource, FractionalFrequency, Powman}, + uart::{DataBits, StopBits, UartConfig, UartPeripheral}, +}; + +use cortex_m_rt::exception; +use pac::interrupt; + +// Some traits we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use hal::Clock; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +struct GlobalUart { + inner: critical_section::Mutex>>, +} + +type MyUart = UartPeripheral< + hal::uart::Enabled, + pac::UART0, + ( + gpio::Pin, + gpio::Pin, + ), +>; + +static GLOBAL_UART: GlobalUart = GlobalUart { + inner: critical_section::Mutex::new(core::cell::RefCell::new(None)), +}; + +impl core::fmt::Write for &GlobalUart { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + critical_section::with(|cs| { + let mut cell = self.inner.borrow_ref_mut(cs); + let uart = cell.as_mut().unwrap(); + uart.write_str(s) + }) + } +} + +impl GlobalUart { + fn init(&self, uart: MyUart) { + critical_section::with(|cs| { + self.inner.borrow(cs).replace(Some(uart)); + }); + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + let mut cp = cortex_m::Peripherals::take().unwrap(); + + // Enable the cycle counter + cp.DCB.enable_trace(); + cp.DWT.enable_cycle_counter(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + GLOBAL_UART.init(uart); + + let mut powman = Powman::new(pac.POWMAN, None); + + _ = writeln!(&GLOBAL_UART, "\n\nPOWMAN Test"); + + print_aot_status(&mut powman); + _ = writeln!(&GLOBAL_UART, "AOT time: 0x{:016x}", powman.aot_get_time()); + + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::POWMAN_IRQ_TIMER); + } + + _ = writeln!(&GLOBAL_UART, "Starting AOT..."); + powman.aot_start(); + // we don't know what oscillator we're on, so give it time to start whatever it is + cortex_m::asm::delay(150_000); + print_aot_status(&mut powman); + rollover_test(&mut powman); + loop_test(&mut powman); + alarm_test(&mut powman); + + let source = AotClockSource::Xosc(FractionalFrequency::from_hz(12_000_000)); + _ = writeln!(&GLOBAL_UART, "Switching AOT to {}", source); + powman.aot_set_clock(source).expect("selecting XOSC"); + print_aot_status(&mut powman); + loop_test(&mut powman); + + let source = AotClockSource::Lposc(FractionalFrequency::from_hz(32768)); + _ = writeln!(&GLOBAL_UART, "Switching AOT to {}", source); + powman.aot_set_clock(source).expect("selecting LPOSC"); + print_aot_status(&mut powman); + loop_test(&mut powman); + + _ = writeln!(&GLOBAL_UART, "Rebooting now"); + + panic!("Finished!"); +} + +fn print_aot_status(powman: &mut Powman) { + if powman.aot_is_running() { + _ = writeln!( + &GLOBAL_UART, + "AOT is running on {}", + powman.aot_get_clock().unwrap() + ); + } else { + _ = writeln!(&GLOBAL_UART, "AOT is not running."); + } +} + +/// Check we can roll-over a 32-bit boundary +fn rollover_test(powman: &mut Powman) { + let start_loop = 0x0000_0000_FFFF_FF00u64; + let end_loop = 0x0000_0001_0000_00FFu64; + _ = writeln!( + &GLOBAL_UART, + "Setting AOT to 0x{:016x} and waiting for rollover...", + start_loop + ); + powman.aot_stop(); + powman.aot_set_time(start_loop); + powman.aot_start(); + + let mut last = 0; + loop { + let now = powman.aot_get_time(); + if now == end_loop { + break; + } else if now < last || now > end_loop { + panic!("bad AOT read {}!", now); + } + last = now; + } + _ = writeln!(&GLOBAL_UART, "Rollover test complete - no glitches found",); + _ = writeln!(&GLOBAL_UART, "Clearing AOT..."); + powman.aot_clear(); + _ = writeln!(&GLOBAL_UART, "AOT time: 0x{:016x}", powman.aot_get_time()); +} + +/// In this function, we see how long it takes to pass a certain number of ticks. +fn loop_test(powman: &mut Powman) { + let start_loop = 0; + let end_loop = 2_000; // 2 seconds + _ = writeln!( + &GLOBAL_UART, + "Setting AOT to 0x{:016x} and benchmarking...", + start_loop + ); + powman.aot_stop(); + powman.aot_set_time(start_loop); + powman.aot_start(); + + let start_clocks = cortex_m::peripheral::DWT::cycle_count(); + loop { + let now = powman.aot_get_time(); + if now == end_loop { + break; + } + } + let end_clocks = cortex_m::peripheral::DWT::cycle_count(); + // Compare our AOT against our CPU clock speed + let delta_clocks = end_clocks.wrapping_sub(start_clocks) as u64; + let delta_ticks = end_loop - start_loop; + let cycles_per_tick = delta_clocks / delta_ticks; + // Assume we're running at 150 MHz + let ms_per_tick = (cycles_per_tick as f32 * 1000.0) / 150_000_000.0; + let percent = ((ms_per_tick - 1.0) / 1.0) * 100.0; + _ = writeln!( + &GLOBAL_UART, + "Loop complete ... {delta_ticks} ticks in {delta_clocks} CPU clock cycles = {cycles_per_tick} cycles/tick ~= {ms_per_tick} ms/tick ({percent:.3}%)", + ) + ; +} + +/// Test the alarm function +fn alarm_test(powman: &mut Powman) { + let start_time = 0x1_0000; + let alarm_time = start_time + 3000; // alarm is 3 seconds in the future + _ = writeln!( + &GLOBAL_UART, + "Setting AOT for {} ms in the future...", + alarm_time - start_time + ); + + powman.aot_stop(); + powman.aot_set_time(start_time); + powman.aot_alarm_clear(); + powman.aot_set_alarm(alarm_time); + powman.aot_alarm_interrupt_enable(); + powman.aot_alarm_enable(); + powman.aot_start(); + + _ = writeln!( + &GLOBAL_UART, + "Sleeping until alarm (* = wakeup, ! = POWMAN interrupt)...", + ); + while !powman.aot_alarm_ringing() { + cortex_m::asm::wfe(); + _ = write!(&GLOBAL_UART, "*",); + } + + _ = writeln!( + &GLOBAL_UART, + "\nAlarm fired at 0x{:016x}. Clearing alarm.", + powman.aot_get_time() + ); + + powman.aot_alarm_clear(); + + if powman.aot_alarm_ringing() { + panic!("Alarm did not clear!"); + } + + _ = writeln!(&GLOBAL_UART, "Alarm cleared OK"); +} + +#[interrupt] +#[allow(non_snake_case)] +fn POWMAN_IRQ_TIMER() { + Powman::static_aot_alarm_interrupt_disable(); + _ = write!(&GLOBAL_UART, "!"); + cortex_m::asm::sev(); +} + +#[exception] +unsafe fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + let _ = writeln!(&GLOBAL_UART, "HARD FAULT:\n{:#?}", ef); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(&GLOBAL_UART, "PANIC:\n{:?}", info); + + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"POWMAN Test Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pwm_blink.rs b/rp235x-hal-examples/src/bin/pwm_blink.rs new file mode 100644 index 000000000..34eae8d29 --- /dev/null +++ b/rp235x-hal-examples/src/bin/pwm_blink.rs @@ -0,0 +1,124 @@ +//! # PWM Blink Example +//! +//! If you have an LED connected to pin 25, it will fade the LED using the PWM +//! peripheral. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::pwm::SetDutyCycle; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); +/// The minimum PWM value (i.e. LED brightness) we want +const LOW: u16 = 0; + +/// The maximum PWM value (i.e. LED brightness) we want +const HIGH: u16 = 25000; + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then fades the LED in an +/// infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Output channel B on PWM4 to GPIO 25 + let channel = &mut pwm.channel_b; + channel.output_to(pins.gpio25); + + // Infinite loop, fading LED up and down + loop { + // Ramp brightness up + for i in LOW..=HIGH { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + // Ramp brightness down + for i in (LOW..=HIGH).rev() { + delay.delay_us(8); + let _ = channel.set_duty_cycle(i); + } + + delay.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PWM Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs b/rp235x-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs new file mode 100644 index 000000000..6695d872e --- /dev/null +++ b/rp235x-hal-examples/src/bin/pwm_blink_embedded_hal_1.rs @@ -0,0 +1,124 @@ +//! # PWM Blink Example +//! +//! If you have an LED connected to pin 25, it will fade the LED using the PWM +//! peripheral. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::pwm::SetDutyCycle; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// The minimum PWM value (i.e. LED brightness) we want +const LOW: u16 = 0; + +/// The maximum PWM value (i.e. LED brightness) we want +const HIGH: u16 = 25000; + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2350 peripherals, then fades the LED in an +/// infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // The delay object lets us wait for specified amounts of time (in + // milliseconds) + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Init PWMs + let mut pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM4 + let pwm = &mut pwm_slices.pwm4; + pwm.set_ph_correct(); + pwm.enable(); + + // Output channel B on PWM4 to GPIO 25 + let channel = &mut pwm.channel_b; + channel.output_to(pins.gpio25); + + // Infinite loop, fading LED up and down + loop { + // Ramp brightness up + for i in LOW..=HIGH { + delay.delay_us(8); + channel.set_duty_cycle(i).unwrap(); + } + + // Ramp brightness down + for i in (LOW..=HIGH).rev() { + delay.delay_us(8); + channel.set_duty_cycle(i).unwrap(); + } + + delay.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PWM Blinky Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/pwm_irq_input.rs b/rp235x-hal-examples/src/bin/pwm_irq_input.rs new file mode 100644 index 000000000..75c428df0 --- /dev/null +++ b/rp235x-hal-examples/src/bin/pwm_irq_input.rs @@ -0,0 +1,222 @@ +//! # PWM IRQ Input Example +//! +//! Read a 5V 50Hz PWM servo input signal from gpio pin 1 and turn the LED on when +//! the input signal is high ( > 1600 us duty pulse width ) and off when low ( < 1400 us ). +//! +//! This signal is commonly used with radio control model systems and small servos. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::digital::OutputPin; + +// Our interrupt macro +use hal::pac::interrupt; + +// Shorter alias for gpio and pwm modules +use hal::gpio; +use hal::pwm; + +// Some short-cuts to useful types for sharing data with the interrupt handlers +use core::cell::RefCell; +use critical_section::Mutex; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// 50 Hz PWM servo signals have a pulse width between 1000 us and 2000 us with +/// 1500 us as the centre point. us is the abbreviation for micro seconds. + +/// The PWM threshold value for turning off the LED in us +const LOW_US: u16 = 1475; + +/// The PWM threshold value for turning on the LED in us +const HIGH_US: u16 = 1525; + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Pin types quickly become very long! +/// We'll create some type aliases using `type` to help with that + +/// This pin will be our output - it will drive an LED if you run this on a Pico +type LedPin = gpio::Pin, gpio::PullNone>; + +/// This pin will be our input for a 50 Hz servo PWM signal +type InputPwmPin = gpio::Pin; + +/// This will be our PWM Slice - it will interpret the PWM signal from the pin +type PwmSlice = pwm::Slice; + +/// Since we're always accessing these pins together we'll store them in a tuple. +/// Giving this tuple a type alias means we won't need to use () when putting them +/// inside an Option. That will be easier to read. +type LedInputAndPwm = (LedPin, InputPwmPin, PwmSlice); + +/// This how we transfer our LED pin, input pin and PWM slice into the Interrupt Handler. +/// We'll have the option hold both using the LedAndInput type. +/// This will make it a bit easier to unpack them later. +static GLOBAL_PINS: Mutex>> = Mutex::new(RefCell::new(None)); + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then fades the LED in an +/// infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Init PWMs + let pwm_slices = pwm::Slices::new(pac.PWM, &mut pac.RESETS); + + // Configure PWM0 slice + // The PWM slice clock should only run when the input is high (InputHighRunning) + let mut pwm: pwm::Slice<_, pwm::InputHighRunning> = pwm_slices.pwm0.into_mode(); + + // Divide the 125 MHz system clock by 125 to give a 1 MHz PWM slice clock (1 us per tick) + pwm.set_div_int(125); + pwm.enable(); + + // Connect to GPI O1 as the input to channel B on PWM0 + let input_pin = pins.gpio1.reconfigure(); + let channel = &mut pwm.channel_b; + channel.set_enabled(true); + + // Enable an interrupt whenever GPI O1 goes from high to low (the end of a pulse) + input_pin.set_interrupt_enabled(gpio::Interrupt::EdgeLow, true); + + // Configure GPIO 25 as an output to drive our LED. + // we can use reconfigure() instead of into_pull_up_input() + // since the variable we're pushing it into has that type + let led = pins.gpio25.reconfigure(); + + // Give away our pins by moving them into the `GLOBAL_PINS` variable. + // We won't need to access them in the main thread again + critical_section::with(|cs| { + GLOBAL_PINS.borrow(cs).replace(Some((led, input_pin, pwm))); + }); + + // Unmask the IO_BANK0 IRQ so that the NVIC interrupt controller + // will jump to the interrupt function when the interrupt occurs. + // We do this last so that the interrupt can't go off while + // it is in the middle of being configured + unsafe { + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::IO_IRQ_BANK0); + } + + loop { + // interrupts handle everything else in this example. + hal::arch::wfi(); + } +} + +#[interrupt] +fn IO_IRQ_BANK0() { + // The `#[interrupt]` attribute covertly converts this to `&'static mut Option` + static mut LED_INPUT_AND_PWM: Option = None; + + // This is one-time lazy initialisation. We steal the variables given to us + // via `GLOBAL_PINS`. + if LED_INPUT_AND_PWM.is_none() { + critical_section::with(|cs| { + *LED_INPUT_AND_PWM = GLOBAL_PINS.borrow(cs).take(); + }); + } + + // Need to check if our Option contains our pins and pwm slice + // borrow led, input and pwm by *destructuring* the tuple + // these will be of type `&mut LedPin`, `&mut InputPwmPin` and `&mut PwmSlice`, so we + // don't have to move them back into the static after we use them + if let Some((led, input, pwm)) = LED_INPUT_AND_PWM { + // Check if the interrupt source is from the input pin going from high-to-low. + // Note: this will always be true in this example, as that is the only enabled GPIO interrupt source + if input.interrupt_status(gpio::Interrupt::EdgeLow) { + // Read the width of the last pulse from the PWM Slice counter + let pulse_width_us = pwm.get_counter(); + + // if the PWM signal indicates low, turn off the LED + if pulse_width_us < LOW_US { + // set_low can't fail, but the embedded-hal traits always allow for it + // we can discard the Result + let _ = led.set_low(); + } + // if the PWM signal indicates high, turn on the LED + else if pulse_width_us > HIGH_US { + // set_high can't fail, but the embedded-hal traits always allow for it + // we can discard the Result + let _ = led.set_high(); + } + + // If the PWM signal was in the dead-zone between LOW and HIGH, don't change the LED's + // state. The dead-zone avoids the LED flickering rapidly when receiving a signal close + // to the mid-point, 1500 us in this case. + + // Reset the pwm counter back to 0, ready for the next pulse + pwm.set_counter(0); + + // Our interrupt doesn't clear itself. + // Do that now so we don't immediately jump back to this interrupt handler. + input.clear_interrupt(gpio::Interrupt::EdgeLow); + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"PWM IRQ Input Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/rom_funcs.rs b/rp235x-hal-examples/src/bin/rom_funcs.rs new file mode 100644 index 000000000..79b73f7fc --- /dev/null +++ b/rp235x-hal-examples/src/bin/rom_funcs.rs @@ -0,0 +1,377 @@ +//! # 'ROM Functions' Example +//! +//! This application demonstrates how to call functions in the rp235x's boot ROM. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +use hal::fugit::RateExtU32; +use hal::Clock; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function::(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function::(), + ); + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + _ = writeln!(uart); + _ = writeln!(uart, "rom_func example for RP2350"); + _ = writeln!(uart, "==========================="); + _ = writeln!(uart); + + _ = writeln!(uart, "CPU Secure Mode: {}", hal::rom_data::is_secure_mode()); + + _ = writeln!( + uart, + "ROM Git Revision: 0x{:x}", + hal::rom_data::git_revision() + ); + + _ = writeln!( + uart, + "Partition Table Info @ {:?}", + hal::rom_data::partition_table_pointer() + ); + + _ = writeln!( + uart, + "ROM Version: A{}", + hal::rom_data::rom_version_number() + ); + + get_otp_data(&mut uart, &mut pac.OTP_DATA); + get_otp_data_raw(&mut uart, &mut pac.OTP_DATA_RAW); + get_sys_info_chip_info(&mut uart); + get_sys_info_cpu_info(&mut uart); + get_sys_info_flash_dev_info(&mut uart); + get_sys_info_boot_random(&mut uart); + get_sys_info_start_block(&mut uart); + get_partition_table_info(&mut uart); + + const WORDS_PER_SCREEN_LINE: usize = 16; + _ = writeln!(uart, "Reading OTP:"); + _ = write!(uart, "Page Adr: "); + for col in 0..WORDS_PER_SCREEN_LINE { + _ = write!(uart, " {:04x}", col); + } + // These are the factory programmed pages + for page in [0, 1, 62, 63] { + for row in 0..hal::otp::NUM_ROWS_PER_PAGE { + let index = page * hal::otp::NUM_ROWS_PER_PAGE + row; + if row == 0 { + _ = write!(uart, "\nP{:02} {:04x}: ", page, index); + } else if (index % WORDS_PER_SCREEN_LINE) == 0 { + _ = write!(uart, "\n {:04x}: ", index); + } + match hal::otp::read_ecc_word(index) { + Ok(0) => { + _ = write!(uart, " ----"); + } + Ok(word) => { + _ = write!(uart, " {:04x}", word); + } + Err(hal::otp::Error::InvalidPermissions) => { + _ = write!(uart, " xxxx"); + } + Err(hal::otp::Error::InvalidIndex) => { + _ = write!(uart, " ????"); + } + } + } + } + _ = writeln!(uart); + + _ = writeln!(uart, "Reading raw OTP:"); + _ = write!(uart, "Page Adr: "); + for col in 0..WORDS_PER_SCREEN_LINE { + _ = write!(uart, " {:06x}", col); + } + // These are the factory programmed pages + for page in [0, 1, 62, 63] { + for row in 0..hal::otp::NUM_ROWS_PER_PAGE { + let index = page * hal::otp::NUM_ROWS_PER_PAGE + row; + if row == 0 { + _ = write!(uart, "\nP{:02} {:04x}: ", page, index); + } else if (index % WORDS_PER_SCREEN_LINE) == 0 { + _ = write!(uart, "\n {:04x}: ", index); + } + match hal::otp::read_raw_word(index) { + Ok(0) => { + _ = write!(uart, " ------"); + } + Ok(word) => { + _ = write!(uart, " {:06x}", word); + } + Err(hal::otp::Error::InvalidPermissions) => { + _ = write!(uart, " xxxxxx"); + } + Err(hal::otp::Error::InvalidIndex) => { + _ = write!(uart, " ??????"); + } + } + } + } + _ = writeln!(uart); + + // Do an asynchronous reset in 2000ms time, into the bootloader. + hal::reboot::reboot( + hal::reboot::RebootKind::BootSel { + msd_disabled: false, + picoboot_disabled: false, + }, + hal::reboot::RebootArch::Normal, + ); +} + +/// Read OTP using the PAC +fn get_otp_data(uart: &mut T, otp_data: &mut hal::pac::OTP_DATA) +where + T: core::fmt::Write, +{ + _ = writeln!(uart, "Reading OTP_DATA"); + let package_id = (otp_data.chipid1().read().chipid1().bits() as u32) << 16 + | otp_data.chipid0().read().chipid0().bits() as u32; + let device_id = (otp_data.chipid3().read().chipid3().bits() as u32) << 16 + | otp_data.chipid2().read().chipid2().bits() as u32; + _ = writeln!(uart, "\tRP2350 Package ID: {:#010x}", package_id); + _ = writeln!(uart, "\tRP2350 Device ID : {:#010x}", device_id); +} + +/// Read OTP in raw mode using the PAC +/// +/// Currently this doesn't work due to SVD issues. +fn get_otp_data_raw(uart: &mut T, otp_data_raw: &mut hal::pac::OTP_DATA_RAW) +where + T: core::fmt::Write, +{ + _ = writeln!(uart, "Reading OTP_DATA_RAW"); + _ = writeln!( + uart, + "\tRP2350 Package ID: {:#010x} {:#010x}", + otp_data_raw.chipid0().read().bits(), + otp_data_raw.chipid1().read().bits() + ); + _ = writeln!( + uart, + "\tRP2350 Device ID : {:#010x} {:#010x}", + otp_data_raw.chipid2().read().bits(), + otp_data_raw.chipid3().read().bits() + ); +} + +/// Run get_sys_info with 0x0001 +fn get_sys_info_chip_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let mut buffer = [0u32; 16]; + let result = unsafe { hal::rom_data::get_sys_info(buffer.as_mut_ptr(), buffer.len(), 0x0001) }; + _ = writeln!(uart, "get_sys_info(CHIP_INFO/0x0001) -> {}", result); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + _ = writeln!(uart, "\tRP2350 Package ID: {:#010x}", buffer[1]); + _ = writeln!(uart, "\tRP2350 Device ID : {:#010x}", buffer[2]); + _ = writeln!(uart, "\tRP2350 Wafer ID : {:#010x}", buffer[3]); +} + +/// Run get_sys_info with 0x0004 +fn get_sys_info_cpu_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let mut buffer = [0u32; 16]; + let result = unsafe { hal::rom_data::get_sys_info(buffer.as_mut_ptr(), buffer.len(), 0x0004) }; + _ = writeln!(uart, "get_sys_info(CPU_INFO/0x0004) -> {}", result); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + _ = writeln!( + uart, + "\tCPU Architecture: {}", + match buffer[1] { + 0 => "Arm", + 1 => "RISC-V", + _ => "Unknown", + } + ); +} + +/// Run get_sys_info with 0x0008 +fn get_sys_info_flash_dev_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let mut buffer = [0u32; 16]; + let result = unsafe { hal::rom_data::get_sys_info(buffer.as_mut_ptr(), buffer.len(), 0x0008) }; + _ = writeln!(uart, "get_sys_info(FLASH_DEV_INFO/0x0008) -> {}", result); + let size_lookup = |value| match value { + 0 => "None", + 1 => "8K", + 2 => "16K", + 3 => "32K", + 4 => "64K", + 5 => "128K", + 6 => "256K", + 7 => "512K", + 8 => "1M", + 9 => "2M", + 10 => "4M", + 11 => "8M", + 12 => "16M", + _ => "Unknown", + }; + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + _ = writeln!(uart, "\tCS0 Size: {}", size_lookup((buffer[1] >> 8) & 15)); + _ = writeln!(uart, "\tCS1 Size: {}", size_lookup((buffer[1] >> 12) & 15)); +} + +/// Run get_sys_info with 0x0010 +fn get_sys_info_boot_random(uart: &mut T) +where + T: core::fmt::Write, +{ + let mut buffer = [0u32; 16]; + let result = unsafe { hal::rom_data::get_sys_info(buffer.as_mut_ptr(), buffer.len(), 0x0010) }; + _ = writeln!(uart, "get_sys_info(BOOT_RANDOM/0x0010) -> {}", result); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + let a = buffer[1]; + let b = buffer[2]; + let c = buffer[3]; + let d = buffer[4]; + _ = writeln!(uart, "\tA random number: 0x{a:08x}{b:08x}{c:08x}{d:08x}"); +} + +/// Run get_sys_info with 0x0040; +fn get_sys_info_start_block(uart: &mut T) +where + T: core::fmt::Write, +{ + let mut buffer = [0u32; 16]; + let result = unsafe { hal::rom_data::get_sys_info(buffer.as_mut_ptr(), buffer.len(), 0x0040) }; + _ = writeln!(uart, "get_sys_info(start_block/0x0040) -> {}", result); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + _ = writeln!(uart, "\tBoot Info: {:08x?}", &buffer[1..result as usize]); +} + +/// Run get_partition_table_info +fn get_partition_table_info(uart: &mut T) +where + T: core::fmt::Write, +{ + let mut buffer = [0u32; 16]; + let result = unsafe { + hal::rom_data::get_partition_table_info(buffer.as_mut_ptr(), buffer.len(), 0x0001) + }; + _ = writeln!( + uart, + "get_partition_table_info(PT_INFO/0x0001) -> {}", + result + ); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + let partition_count = buffer[1] & 0xFF; + let has_partition_table = (buffer[1] & (1 << 8)) != 0; + let unpart = hal::block::UnpartitionedSpace::from_raw(buffer[2], buffer[3]); + _ = writeln!( + uart, + "\tNum Partitions: {} (Has Partition Table? {})", + partition_count, has_partition_table + ); + _ = writeln!(uart, "\tUnpartitioned Space: {}", unpart); + for part_idx in 0..partition_count { + let result = unsafe { + hal::rom_data::get_partition_table_info( + buffer.as_mut_ptr(), + buffer.len(), + (part_idx << 24) | 0x8010, + ) + }; + _ = writeln!( + uart, + "get_partition_table_info(PARTITION_LOCATION_AND_FLAGS|SINGLE_PARTITION/0x8010) -> {}", + result + ); + _ = writeln!(uart, "\tSupported Flags: {:#06x}", buffer[0]); + let part = hal::block::Partition::from_raw(buffer[1], buffer[2]); + _ = writeln!(uart, "\tPartition {}: {}", part_idx, part); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Tests ROM functions and reading OTP"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/rosc_as_system_clock.rs b/rp235x-hal-examples/src/bin/rosc_as_system_clock.rs new file mode 100644 index 000000000..96d54d971 --- /dev/null +++ b/rp235x-hal-examples/src/bin/rosc_as_system_clock.rs @@ -0,0 +1,337 @@ +//! # ROSC as system clock Example +//! +//! This application demonstrates how to use the ROSC as the system clock on the rp235x. +//! +//! It shows setting the frequency of the ROSC to a measured known frequency, and contains +//! helper functions to configure the ROSC drive strength to reach a desired target frequency. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use fugit::{HertzU32, RateExtU32}; +use hal::clocks::{Clock, ClockSource, ClocksManager, StoppableClock}; +use hal::pac::rosc::ctrl::FREQ_RANGE_A; +use hal::pac::{CLOCKS, ROSC}; +use hal::rosc::RingOscillator; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function attempts to find appropriate settings for the RingOscillator to reach a target +/// frequency, and then logs the actual attained frequency. +/// +/// The main reasons you'd want to use this is for power-saving in applications where precise +/// timings are not critical (you don't need to use USB peripherals for example). +/// Using the ROSC as the system clock allows under-clocking or over-clocking rp235x, and +/// it also can allow fast waking from a dormant state on the order of µs, which the XOSC cannot do. +/// +/// A motivating application for this was a flir lepton thermal camera module which +/// makes thermal images available via SPI at a rate of around 9Hz. Using the rp235xs ROSC, we +/// are able to clock out the thermal image via SPI and then enter dormant mode until the next vsync +/// interrupt wakes us again, saving some power. +#[hal::entry] +fn main() -> ! { + // Set target rosc frequency to 150Mhz + // Setting frequencies can be a matter of a bit of trial and error to see what + // actual frequencies you can easily hit. In practice, the lowest achieved with this method + // is around 32Mhz, and it seems to be able to ramp up to around 230Mhz + let desired_rosc_freq: HertzU32 = 150_000_000.Hz(); + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + led_pin.set_low().unwrap(); + + // Setup the crystal oscillator to do accurate measurements against + let xosc = hal::xosc::setup_xosc_blocking(pac.XOSC, XTAL_FREQ_HZ.Hz()).unwrap(); + + // Find appropriate settings for the desired ring oscillator frequency. + let measured_rosc_frequency = + find_target_rosc_frequency(&pac.ROSC, &pac.CLOCKS, desired_rosc_freq); + let rosc = RingOscillator::new(pac.ROSC); + + // Now initialise the ROSC with the reached frequency and set it as the system clock. + let rosc = rosc.initialize_with_freq(measured_rosc_frequency); + + let mut clocks = ClocksManager::new(pac.CLOCKS); + clocks + .system_clock + .configure_clock(&rosc, rosc.get_freq()) + .unwrap(); + + clocks + .peripheral_clock + .configure_clock(&clocks.system_clock, clocks.system_clock.freq()) + .unwrap(); + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Now we can disable the crystal oscillator and run off the ring oscillator, for power savings. + let _xosc_disabled = xosc.disable(); + // You may also wish to disable other clocks/peripherals that you don't need. + clocks.usb_clock.disable(); + clocks.gpio_output0_clock.disable(); + clocks.gpio_output1_clock.disable(); + clocks.gpio_output2_clock.disable(); + clocks.gpio_output3_clock.disable(); + clocks.adc_clock.disable(); + + // Check that desired frequency is close to the frequency speed. + // If it is, turn the LED on. If not, blink the LED. + let got_to_within_1_mhz_of_target = desired_rosc_freq + .to_kHz() + .abs_diff(measured_rosc_frequency.to_kHz()) + < 1000; + + if got_to_within_1_mhz_of_target { + // Now it's possible to easily take the ROSC dormant, to be woken by an external interrupt. + led_pin.set_high().unwrap(); + loop { + hal::arch::wfi(); + } + } else { + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(500); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } + } +} + +/// Measure the actual speed of the ROSC at the current freq_range and drive strength config +fn rosc_frequency_count_hz(clocks: &CLOCKS) -> HertzU32 { + // Wait for the frequency counter to be ready + while clocks.fc0_status().read().running().bit_is_set() { + hal::arch::nop(); + } + + // Set the speed of the reference clock in kHz. + clocks + .fc0_ref_khz() + .write(|w| unsafe { w.fc0_ref_khz().bits(XTAL_FREQ_HZ / 1000) }); + + // Corresponds to a 1ms test time, which seems to give good enough accuracy + clocks + .fc0_interval() + .write(|w| unsafe { w.fc0_interval().bits(10) }); + + // We don't really care about the min/max, so these are just set to min/max values. + clocks + .fc0_min_khz() + .write(|w| unsafe { w.fc0_min_khz().bits(0) }); + clocks + .fc0_max_khz() + .write(|w| unsafe { w.fc0_max_khz().bits(0xffffffff) }); + + // To measure rosc directly we use the value 0x03. + clocks + .fc0_src() + .write(|w| unsafe { w.fc0_src().bits(0x03) }); + + // Wait until the measurement is ready + while clocks.fc0_status().read().done().bit_is_clear() { + hal::arch::nop(); + } + + let speed_hz = clocks.fc0_result().read().khz().bits() * 1000; + speed_hz.Hz() +} + +/// Resets ROSC frequency range and stages drive strength, then increases the frequency range, +/// drive strength bits, and finally divider in order to try to come close to the desired target +/// frequency, returning the final measured ROSC frequency attained. +fn find_target_rosc_frequency( + rosc: &ROSC, + clocks: &CLOCKS, + target_frequency: HertzU32, +) -> HertzU32 { + reset_rosc_operating_frequency(rosc); + let mut div = 1; + let mut measured_rosc_frequency; + loop { + measured_rosc_frequency = rosc_frequency_count_hz(clocks); + // If it has overshot the target frequency, increase the divider and continue. + if measured_rosc_frequency > target_frequency { + div += 1; + set_rosc_div(rosc, div); + } else { + break; + } + } + loop { + measured_rosc_frequency = rosc_frequency_count_hz(clocks); + if measured_rosc_frequency > target_frequency { + // And probably want to step it down a notch? + break; + } + let can_increase = increase_drive_strength(rosc); + if !can_increase { + let can_increase_range = increase_freq_range(rosc); + if !can_increase_range { + break; + } + } + } + measured_rosc_frequency +} + +fn set_rosc_div(rosc: &ROSC, div: u32) { + assert!(div <= 32); + let div = if div == 32 { 0 } else { div }; + rosc.div().write(|w| unsafe { w.bits(0xaa0 + div) }); +} + +fn reset_rosc_operating_frequency(rosc: &ROSC) { + // Set divider to 1 + set_rosc_div(rosc, 1); + rosc.ctrl().write(|w| w.freq_range().low()); + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); +} + +fn read_freq_stage(rosc: &ROSC, stage: u8) -> u8 { + match stage { + 0 => rosc.freqa().read().ds0().bits(), + 1 => rosc.freqa().read().ds1().bits(), + 2 => rosc.freqa().read().ds2().bits(), + 3 => rosc.freqa().read().ds3().bits(), + 4 => rosc.freqb().read().ds4().bits(), + 5 => rosc.freqb().read().ds5().bits(), + 6 => rosc.freqb().read().ds6().bits(), + 7 => rosc.freqb().read().ds7().bits(), + _ => panic!("invalid frequency drive strength stage"), + } +} + +/// Increase the ROSC drive strength bits for the current freq_range +fn increase_drive_strength(rosc: &ROSC) -> bool { + const MAX_STAGE_DRIVE: u8 = 3; + // Assume div is 1, and freq_range is high + let mut stages: [u8; 8] = [0; 8]; + for (stage_index, stage) in stages.iter_mut().enumerate() { + *stage = read_freq_stage(rosc, stage_index as u8) + } + let num_stages_at_drive_level = match rosc.ctrl().read().freq_range().variant() { + Some(FREQ_RANGE_A::LOW) => 8, + Some(FREQ_RANGE_A::MEDIUM) => 6, + Some(FREQ_RANGE_A::HIGH) => 4, + Some(FREQ_RANGE_A::TOOHIGH) => panic!("Don't use TOOHIGH freq_range"), + None => { + // Start out at initial unset drive stage + return false; + } + }; + let mut next_i = 0; + for (index, x) in stages[0..num_stages_at_drive_level].windows(2).enumerate() { + if x[1] < x[0] { + next_i = index + 1; + break; + } + } + if stages[next_i] < MAX_STAGE_DRIVE { + stages[next_i] += 1; + let min = *stages[0..num_stages_at_drive_level] + .iter() + .min() + .unwrap_or(&0); + for stage in &mut stages[num_stages_at_drive_level..] { + *stage = min; + } + write_freq_stages(rosc, &stages); + true + } else { + false + } +} + +/// Sets the `freqa` and `freqb` ROSC drive strength stage registers. +fn write_freq_stages(rosc: &ROSC, stages: &[u8; 8]) { + let passwd: u32 = 0x9696 << 16; + let mut freq_a = passwd; + let mut freq_b = passwd; + for (stage_index, stage) in stages.iter().enumerate().take(4) { + freq_a |= ((*stage & 0x07) as u32) << (stage_index * 4); + } + for (stage_index, stage) in stages.iter().enumerate().skip(4) { + freq_b |= ((*stage & 0x07) as u32) << ((stage_index - 4) * 4); + } + rosc.freqa().write(|w| unsafe { w.bits(freq_a) }); + rosc.freqb().write(|w| unsafe { w.bits(freq_b) }); +} + +/// Increase the rosc frequency range up to the next step. +/// Returns a boolean to indicate whether the frequency was increased. +fn increase_freq_range(rosc: &ROSC) -> bool { + match rosc.ctrl().read().freq_range().variant() { + None => { + // Initial unset frequency range, move to LOW frequency range + rosc.ctrl().write(|w| w.freq_range().low()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::LOW) => { + // Transition from LOW to MEDIUM frequency range + rosc.ctrl().write(|w| w.freq_range().medium()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::MEDIUM) => { + // Transition from MEDIUM to HIGH frequency range + rosc.ctrl().write(|w| w.freq_range().high()); + // Reset all the drive strength bits. + write_freq_stages(rosc, &[0, 0, 0, 0, 0, 0, 0, 0]); + true + } + Some(FREQ_RANGE_A::HIGH) | Some(FREQ_RANGE_A::TOOHIGH) => { + // Already in the HIGH frequency range, and can't increase + false + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Ring Oscillator Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/spi.rs b/rp235x-hal-examples/src/bin/spi.rs new file mode 100644 index 000000000..04d5aae62 --- /dev/null +++ b/rp235x-hal-examples/src/bin/spi.rs @@ -0,0 +1,131 @@ +//! # SPI Example +//! +//! This application demonstrates how to use the SPI Driver to talk to a remote +//! SPI device. +//! +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal_0_2::prelude::*; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then performs some example +/// SPI transactions, then goes to sleep. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // These are implicitly used by the spi driver if they are in the correct mode + let spi_mosi = pins.gpio7.into_function::(); + let spi_miso = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + let spi_bus = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let mut spi_bus = spi_bus.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16.MHz(), + embedded_hal::spi::MODE_0, + ); + + // Write out 0, ignore return value + if spi_bus.write(&[0]).is_ok() { + // SPI write was successful + }; + + // write 50, then check the return + let send_success = spi_bus.send(50); + match send_success { + Ok(_) => { + // We succeeded, check the read value + if let Ok(_x) = spi_bus.read() { + // We got back `x` in exchange for the 0x50 we sent. + }; + } + Err(_) => todo!(), + } + + // Do a read+write at the same time. Data in `buffer` will be replaced with + // the data read from the SPI device. + let mut buffer: [u8; 4] = [1, 2, 3, 4]; + let transfer_success = spi_bus.transfer(&mut buffer); + #[allow(clippy::single_match)] + match transfer_success { + Ok(_) => {} // Handle success + Err(_) => {} // handle errors + }; + + loop { + hal::arch::wfi(); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"SPI Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/spi_dma.rs b/rp235x-hal-examples/src/bin/spi_dma.rs new file mode 100644 index 000000000..d78704ae0 --- /dev/null +++ b/rp235x-hal-examples/src/bin/spi_dma.rs @@ -0,0 +1,123 @@ +//! # SPI DMA Example +//! +//! This application demonstrates how to use DMA for SPI transfers. +//! +//! The application expects the MISO and MOSI pins to be wired together so that it is able to check +//! whether the data was sent and received correctly. +//! +//! See the `Cargo.toml` file for Copyright and licence details. +#![no_std] +#![no_main] + +use rp235x_hal as hal; + +use hal::singleton; + +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use hal::clocks::Clock; +use hal::dma::{bidirectional, DMAExt}; +use hal::fugit::RateExtU32; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +#[hal::entry] +fn main() -> ! { + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Setup clocks and the watchdog. + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // Setup the pins. + let sio = hal::sio::Sio::new(pac.SIO); + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Set up our SPI pins into the correct mode + let spi_mosi = pins.gpio7.into_function::(); + let spi_miso = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16_000_000u32.Hz(), + embedded_hal::spi::MODE_0, + ); + + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + // Configure GPIO25 as an output + let mut led_pin = pins.gpio25.into_push_pull_output(); + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Use DMA to transfer some bytes (single buffering). + let tx_buf = singleton!(: [u8; 16] = [0x42; 16]).unwrap(); + let rx_buf = singleton!(: [u8; 16] = [0; 16]).unwrap(); + + // Use BidirectionalConfig to simultaneously write to spi from tx_buf and read into rx_buf + let transfer = bidirectional::Config::new((dma.ch0, dma.ch1), tx_buf, spi, rx_buf).start(); + // Wait for both DMA channels to finish + let ((_ch0, _ch1), tx_buf, _spi, rx_buf) = transfer.wait(); + + // Compare buffers to see if the data was transferred correctly + for i in 0..rx_buf.len() { + if rx_buf[i] != tx_buf[i] { + // Fast blink on error + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(100); + led_pin.set_low().unwrap(); + delay.delay_ms(100); + } + } + } + + // Slow blink on success + loop { + led_pin.set_high().unwrap(); + delay.delay_ms(500); + led_pin.set_low().unwrap(); + delay.delay_ms(500); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"SPI DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/uart.rs b/rp235x-hal-examples/src/bin/uart.rs new file mode 100644 index 000000000..a5bfb4125 --- /dev/null +++ b/rp235x-hal-examples/src/bin/uart.rs @@ -0,0 +1,163 @@ +//! # UART Example +//! +//! This application demonstrates how to use the UART Driver to talk to a serial +//! connection. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +use hal::gpio; + +// Some things we need +use core::fmt::Write; +use embedded_hal::delay::DelayNs; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig, ValidatedPinRx, ValidatedPinTx}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart0_pins = ( + // UART TX (characters sent from rp235x) on pin 4 (GPIO2) in Aux mode + pins.gpio2.into_function(), + // UART RX (characters received by rp235x) on pin 5 (GPIO3) in Aux mode + pins.gpio3.into_function(), + ); + let mut uart0 = hal::uart::UartPeripheral::new(pac.UART0, uart0_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Set UART1 up with some dynamic pins + + // UART TX (characters sent from rp235x) on pin 6 (GPIO4) + // + // We erase the type-state and make a dynamically typed pin. this is useful + // if you want to store it in a struct, or pass it as an argument to a + // library. + let mut uart1_tx = pins + .gpio4 + .reconfigure::() + .into_dyn_pin(); + // try and put it into UART mode (the type of the variable doesn't change) + if uart1_tx.try_set_function(gpio::DynFunction::Uart).is_err() { + panic!("Can't set pin as UART") + } + // wrap it, to prove to the UartPeripheral that it *is* in Uart mode + let Ok(uart1_tx) = ValidatedPinTx::validate(uart1_tx, &pac.UART1) else { + panic!("Can't use pin for UART 1 TX") + }; + + // UART RX (characters received by rp235x) on pin 7 (GPIO5) + // + // We erase the type-state and make a dynamically typed pin. this is useful + // if you want to store it in a struct, or pass it as an argument to a + // library. + let mut uart1_rx = pins + .gpio5 + .reconfigure::() + .into_dyn_pin(); + // try and put it into UART mode + if uart1_rx.try_set_function(gpio::DynFunction::Uart).is_err() { + panic!("Can't set pin as UART") + } + // wrap it, to prove to the UartPeripheral that it *is* in Uart mode + let Ok(uart1_rx) = ValidatedPinRx::validate(uart1_rx, &pac.UART1) else { + panic!("Can't use pin for UART 1 RX") + }; + // make a UART with our dynamic pin types + let uart1_pins = (uart1_tx, uart1_rx); + let mut uart1 = hal::uart::UartPeripheral::new(pac.UART1, uart1_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + uart0.write_full_blocking(b"UART example on UART0\r\n"); + uart1.write_full_blocking(b"UART example on UART1\r\n"); + + let mut value = 0u32; + loop { + writeln!(uart0, "UART0 says value: {value:02}\r").unwrap(); + writeln!(uart1, "UART1 says value: {value:02}\r").unwrap(); + delay.delay_ms(1000); + value += 1 + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"UART Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/uart_dma.rs b/rp235x-hal-examples/src/bin/uart_dma.rs new file mode 100644 index 000000000..e6b603c1c --- /dev/null +++ b/rp235x-hal-examples/src/bin/uart_dma.rs @@ -0,0 +1,142 @@ +//! # UART DMA Example +//! +//! This application demonstrates how to use the UART peripheral with the +//! DMA controller. +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +use hal::singleton; +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use hal::clocks::Clock; +use hal::dma::DMAExt; +use hal::fugit::RateExtU32; + +// UART related types +use hal::uart::{DataBits, StopBits, UartConfig}; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let uart_pins = ( + // UART TX (characters sent from rp235x) on pin 1 (GPIO0) + pins.gpio0.into_function(), + // UART RX (characters received by rp235x) on pin 2 (GPIO1) + pins.gpio1.into_function(), + ); + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + // Initialize DMA. + let dma = pac.DMA.split(&mut pac.RESETS); + + uart.write_full_blocking(b"\r\n\r\nUART DMA echo example\r\n\r\n"); + + // In order to use DMA we need to split the UART into a RX (receive) and TX (transmit) pair + let (rx, tx) = uart.split(); + + // We can still write to the tx side of the UART after splitting + tx.write_full_blocking(b"Regular UART write\r\n"); + + // And we can DMA from a buffer into the UART + let teststring = b"DMA UART write\r\n"; + let tx_transfer = hal::dma::single_buffer::Config::new(dma.ch0, teststring, tx).start(); + + // Wait for the DMA transfer to finish so we can reuse the tx and the dma channel + let (ch0, _teststring, tx) = tx_transfer.wait(); + + // Let's test DMA RX into a buffer. + tx.write_full_blocking(b"Waiting for you to type 5 letters...\r\n"); + let rx_buf = singleton!(: [u8; 5] = [0; 5]).unwrap(); + let rx_transfer = hal::dma::single_buffer::Config::new(ch0, rx, rx_buf).start(); + let (ch0, rx, rx_buf) = rx_transfer.wait(); + + // Echo back the 5 characters the user typed + tx.write_full_blocking(b"You wrote \""); + tx.write_full_blocking(rx_buf); + tx.write_full_blocking(b"\"\r\n"); + + // Now just keep echoing anything that is received back out of TX + tx.write_full_blocking(b"Now echoing any character you write...\r\n"); + let _tx_transfer = hal::dma::single_buffer::Config::new(ch0, rx, tx).start(); + + loop { + // everything should be handled by DMA, nothing else to do + delay.delay_ms(1000); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"UART DMA Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/usb.rs b/rp235x-hal-examples/src/bin/usb.rs new file mode 100644 index 000000000..51e93a417 --- /dev/null +++ b/rp235x-hal-examples/src/bin/usb.rs @@ -0,0 +1,153 @@ +//! # Pico USB Serial Example +//! +//! Creates a USB Serial device on a Pico board, with the USB driver running in +//! the main thread. +//! +//! This will create a USB Serial device echoing anything it receives. Incoming +//! ASCII characters are converted to upercase, so you can tell it is working +//! and not just local-echo! +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::fmt::Write; +use heapless::String; + +// USB Device support +use usb_device::{class_prelude::*, prelude::*}; + +// USB Communications Class Device support +use usbd_serial::SerialPort; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then writes to the UART in +/// an infinite loop. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // Set up the USB driver + let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( + pac.USB, + pac.USB_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + + // Set up the USB Communications Class Device driver + let mut serial = SerialPort::new(&usb_bus); + + // Create a USB device with a fake VID and PID + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .strings(&[StringDescriptors::default() + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST")]) + .unwrap() + .device_class(2) // from: https://www.usb.org/defined-class-codes + .build(); + + let mut said_hello = false; + loop { + // A welcome message at the beginning + if !said_hello && timer.get_counter().ticks() >= 2_000_000 { + said_hello = true; + let _ = serial.write(b"Hello, World!\r\n"); + + let time = timer.get_counter().ticks(); + let mut text: String<64> = String::new(); + writeln!(&mut text, "Current timer ticks: {}", time).unwrap(); + + // This only works reliably because the number of bytes written to + // the serial port is smaller than the buffers available to the USB + // peripheral. In general, the return value should be handled, so that + // bytes not transferred yet don't get lost. + let _ = serial.write(text.as_bytes()); + } + + // Check for new data + if usb_dev.poll(&mut [&mut serial]) { + let mut buf = [0u8; 64]; + match serial.read(&mut buf) { + Err(_e) => { + // Do nothing + } + Ok(0) => { + // Do nothing + } + Ok(count) => { + // Convert to upper case + buf.iter_mut().take(count).for_each(|b| { + b.make_ascii_uppercase(); + }); + // Send back to the host + let mut wr_ptr = &buf[..count]; + while !wr_ptr.is_empty() { + match serial.write(wr_ptr) { + Ok(len) => wr_ptr = &wr_ptr[len..], + // On error, just drop unwritten data. + // One possible error is Err(WouldBlock), meaning the USB + // write buffer is full. + Err(_) => break, + }; + } + } + } + } + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"USB Serial Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/vector_table.rs b/rp235x-hal-examples/src/bin/vector_table.rs new file mode 100644 index 000000000..f975c1966 --- /dev/null +++ b/rp235x-hal-examples/src/bin/vector_table.rs @@ -0,0 +1,196 @@ +//! # RAM Vector Table example +//! +//! This application demonstrates how to create a new Interrupt Vector Table in RAM. +//! To demonstrate the extra utility of this, we also replace an entry in the Vector Table +//! with a new one. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use core::cell::RefCell; +use critical_section::Mutex; +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::StatefulOutputPin; +use hal::fugit::MicrosDurationU32; +use hal::timer::Alarm; + +use hal::pac::interrupt; +use hal::vector_table::VectorTable; + +// Memory that will hold our vector table in RAM +static mut RAM_VTABLE: VectorTable = VectorTable::new(); + +// Give our LED and Alarm a type alias to make it easier to refer to them +type LedAndAlarm = ( + hal::gpio::Pin, + hal::timer::Alarm0, +); + +// Place our LED and Alarm type in a static variable, so we can access it from interrupts +static mut LED_AND_ALARM: Mutex>> = Mutex::new(RefCell::new(None)); + +// Period that each of the alarms will be set for - 1 second and 300ms respectively +const SLOW_BLINK_INTERVAL_US: MicrosDurationU32 = MicrosDurationU32::secs(1); +const FAST_BLINK_INTERVAL_US: MicrosDurationU32 = MicrosDurationU32::millis(300); + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. If there is an LED connected to that pin, it will blink. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Need to make a reference to the Peripheral Base at this scope to avoid confusing the borrow checker + let ppb = &mut pac.PPB; + unsafe { + // Copy the vector table that cortex_m_rt produced into the RAM vector table + RAM_VTABLE.init(ppb); + // Replace the function that is called on Alarm0 interrupts with a new one + RAM_VTABLE.register_handler( + hal::pac::Interrupt::TIMER0_IRQ_0 as usize, + timer_irq0_replacement, + ); + } + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .ok() + .unwrap(); + + // Create simple delay + let mut delay = hal::Timer::new_timer1(pac.TIMER1, &mut pac.RESETS, &clocks); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure GPIO25 as an output + let led_pin = pins.gpio25.into_push_pull_output(); + + let mut timer = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + critical_section::with(|cs| { + let mut alarm = timer.alarm_0().unwrap(); + // Schedule an alarm in 1 second + let _ = alarm.schedule(SLOW_BLINK_INTERVAL_US); + // Enable generating an interrupt on alarm + alarm.enable_interrupt(); + // Move alarm into ALARM, so that it can be accessed from interrupts + unsafe { + LED_AND_ALARM.borrow(cs).replace(Some((led_pin, alarm))); + } + }); + // Unmask the timer0 IRQ so that it will generate an interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(hal::pac::Interrupt::TIMER0_IRQ_0); + } + + // After 5 seconds, switch to our modified vector rable + delay.delay_ms(5000); + unsafe { + critical_section::with(|_| { + RAM_VTABLE.activate(ppb); + }); + } + + loop { + // Wait for an interrupt to fire before doing any more work + hal::arch::wfi(); + } +} + +// Regular interrupt handler for Alarm0. The `interrupt` macro will perform some transformations to ensure +// that this interrupt entry ends up in the vector table. +#[interrupt] +fn TIMER0_IRQ_0() { + critical_section::with(|cs| { + // Temporarily take our LED_AND_ALARM + let ledalarm = unsafe { LED_AND_ALARM.borrow(cs).take() }; + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after SLOW_BLINK_INTERVAL_US have passed (1 second) + let _ = alarm.schedule(SLOW_BLINK_INTERVAL_US); + // Blink the LED so we know we hit this interrupt + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + unsafe { + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + } + }); +} + +// This is the function we will use to replace TIMER_IRQ_0 in our RAM Vector Table +extern "C" fn timer_irq0_replacement() { + critical_section::with(|cs| { + let ledalarm = unsafe { LED_AND_ALARM.borrow(cs).take() }; + if let Some((mut led, mut alarm)) = ledalarm { + // Clear the alarm interrupt or this interrupt service routine will keep firing + alarm.clear_interrupt(); + // Schedule a new alarm after FAST_BLINK_INTERVAL_US have passed (300 milliseconds) + let _ = alarm.schedule(FAST_BLINK_INTERVAL_US); + led.toggle().unwrap(); + // Return LED_AND_ALARM into our static variable + unsafe { + LED_AND_ALARM + .borrow(cs) + .replace_with(|_| Some((led, alarm))); + } + } + }); +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Interrupt Vector Table Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-examples/src/bin/watchdog.rs b/rp235x-hal-examples/src/bin/watchdog.rs new file mode 100644 index 000000000..51e186bed --- /dev/null +++ b/rp235x-hal-examples/src/bin/watchdog.rs @@ -0,0 +1,114 @@ +//! # Watchdog Example +//! +//! This application demonstrates how to use the rp235x Watchdog. +//! +//! It may need to be adapted to your particular board layout and/or pin assignment. +//! +//! See the `Cargo.toml` file for Copyright and license details. + +#![no_std] +#![no_main] + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// Alias for our HAL crate +use rp235x_hal as hal; + +// Some things we need +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use hal::fugit::ExtU32; + +/// Tell the Boot ROM about our application +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +/// External high-speed crystal on the Raspberry Pi Pico 2 board is 12 MHz. +/// Adjust if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Entry point to our bare-metal application. +/// +/// The `#[hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the rp235x peripherals, then toggles a GPIO pin in +/// an infinite loop. After a period of time, the watchdog will kick in to reset +/// the CPU. +#[hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = hal::pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + let mut delay = hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure an LED so we can show the current state of the watchdog + let mut led_pin = pins.gpio25.into_push_pull_output(); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led_pin.set_high().unwrap(); + delay.delay_ms(2000); + + // Set to watchdog to reset if it's not reloaded within 1.05 seconds, and start it + watchdog.start(1_050.millis()); + + // Blink once a second for 5 seconds, refreshing the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led_pin.set_low().unwrap(); + delay.delay_ms(500); + led_pin.set_high().unwrap(); + delay.delay_ms(500); + watchdog.feed(); + } + + // Blink 5 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds, or 5 blinks time + loop { + led_pin.set_low().unwrap(); + delay.delay_ms(100); + led_pin.set_high().unwrap(); + delay.delay_ms(100); + } +} + +/// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 5] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"Watchdog Example"), + hal::binary_info::rp_cargo_homepage_url!(), + hal::binary_info::rp_program_build_attribute!(), +]; + +// End of file diff --git a/rp235x-hal-macros/.gitignore b/rp235x-hal-macros/.gitignore new file mode 100644 index 000000000..ff47c2d77 --- /dev/null +++ b/rp235x-hal-macros/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp235x-hal-macros/Cargo.toml b/rp235x-hal-macros/Cargo.toml new file mode 100644 index 000000000..6bef22073 --- /dev/null +++ b/rp235x-hal-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +description = "Macros used by rp235x-hal" +license = "MIT OR Apache-2.0" +name = "rp235x-hal-macros" +readme = "README.md" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/rp-rs/rp-hal" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" + +[dependencies.syn] +features = ["full"] +version = "2.0" + diff --git a/rp235x-hal-macros/LICENSE-APACHE b/rp235x-hal-macros/LICENSE-APACHE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/rp235x-hal-macros/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/rp235x-hal-macros/LICENSE-MIT b/rp235x-hal-macros/LICENSE-MIT new file mode 100644 index 000000000..6e052e35b --- /dev/null +++ b/rp235x-hal-macros/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/rp235x-hal-macros/NOTICE b/rp235x-hal-macros/NOTICE new file mode 100644 index 000000000..790ecb167 --- /dev/null +++ b/rp235x-hal-macros/NOTICE @@ -0,0 +1,3 @@ +Copyright (c) 2021-2024 The rp-rs Developers + +Originally published at https://github.com/rp-rs/rp-hal diff --git a/rp235x-hal-macros/README.md b/rp235x-hal-macros/README.md new file mode 100644 index 000000000..e07be925d --- /dev/null +++ b/rp235x-hal-macros/README.md @@ -0,0 +1,22 @@ +# `rp235x-hal-macros` + +Macros used by rp235x-hal. + +## Entry macro + +Extension of the `cortex-m-rt` `#[entry]` with rp235x specific initialization code. + +Currently, it just unlocks all spinlocks before calling the entry function, and +sets up the Double Precision and GPIO Co-Procesors. + +# License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/rp235x-hal-macros/src/lib.rs b/rp235x-hal-macros/src/lib.rs new file mode 100644 index 000000000..ad2a255f0 --- /dev/null +++ b/rp235x-hal-macros/src/lib.rs @@ -0,0 +1,76 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{parse, parse_macro_input, Item, ItemFn, Stmt}; + +#[proc_macro_attribute] +pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { + let mut f = parse_macro_input!(input as ItemFn); + + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + + let clear_locks: TokenStream = quote!(unsafe { + const SIO_BASE: u32 = 0xd0000000; + const SPINLOCK0_PTR: *mut u32 = (SIO_BASE + 0x100) as *mut u32; + const SPINLOCK_COUNT: usize = 32; + for i in 0..SPINLOCK_COUNT { + SPINLOCK0_PTR.wrapping_add(i).write_volatile(1); + } + #[cfg(target_arch = "arm")] + { + // Enable the Double-Co-Pro and the GPIO Co-Pro in the CPACR register. + // We have to do this early, before there's a chance we might call + // any accelerated functions. + const SCB_CPACR_PTR: *mut u32 = 0xE000_ED88 as *mut u32; + const SCB_CPACR_FULL_ACCESS: u32 = 0b11; + // Do a R-M-W, because the FPU enable is here and that's already been enabled + let mut temp = SCB_CPACR_PTR.read_volatile(); + // DCP Co-Pro is 4, two-bits per entry + temp |= SCB_CPACR_FULL_ACCESS << (4 * 2); + // GPIO Co-Pro is 0, two-bits per entry + temp |= SCB_CPACR_FULL_ACCESS << (0 * 2); + SCB_CPACR_PTR.write_volatile(temp); + // Don't allow any DCP code to be moved before this fence. + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + } + }) + .into(); + let clear_locks = parse_macro_input!(clear_locks as Stmt); + + // statics must stay first so cortex_m_rt::entry still finds them + let stmts = insert_after_static(f.block.stmts, clear_locks); + f.block.stmts = stmts; + + quote!( + #[rp235x_hal::arch_entry] + #f + ) + .into() +} + +/// Insert new statements after initial block of statics +fn insert_after_static(stmts: impl IntoIterator, insert: Stmt) -> Vec { + let mut istmts = stmts.into_iter(); + let mut stmts = vec![]; + for stmt in istmts.by_ref() { + match stmt { + Stmt::Item(Item::Static(var)) => { + stmts.push(Stmt::Item(Item::Static(var))); + } + _ => { + stmts.push(insert); + stmts.push(stmt); + break; + } + } + } + stmts.extend(istmts); + + stmts +} diff --git a/rp235x-hal/.gitignore b/rp235x-hal/.gitignore new file mode 100644 index 000000000..ff47c2d77 --- /dev/null +++ b/rp235x-hal/.gitignore @@ -0,0 +1,11 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/rp235x-hal/CHANGELOG.md b/rp235x-hal/CHANGELOG.md new file mode 100644 index 000000000..65fab2dbd --- /dev/null +++ b/rp235x-hal/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Changed + +- First version + diff --git a/rp235x-hal/Cargo.toml b/rp235x-hal/Cargo.toml new file mode 100644 index 000000000..a4ba69722 --- /dev/null +++ b/rp235x-hal/Cargo.toml @@ -0,0 +1,101 @@ +[package] +authors = ["The rp-rs Developers"] +categories = ["embedded", "hardware-support", "no-std", "no-std::no-alloc"] +description = "A Rust Embeded-HAL impl for the RP2350 microcontroller" +edition = "2021" +homepage = "https://github.com/rp-rs/rp-hal" +keywords = ["embedded", "hal", "raspberry-pi", "rp2350", "embedded-hal"] +license = "MIT OR Apache-2.0" +name = "rp235x-hal" +repository = "https://github.com/rp-rs/rp-hal" +rust-version = "1.77" +version = "0.2.0" + +[package.metadata.docs.rs] +features = ["rt", "defmt", "rtic-monotonic"] +targets = ["thumbv8m.main-none-eabihf"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# Non-optional dependencies. Keep these sorted by name. +bitfield = "0.14.0" +critical-section = "1.0.0" +embedded-dma = "0.2.0" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +embedded-hal-nb = "1.0.0" +embedded-io = "0.6.1" +embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} +frunk = {version = "0.4.1", default-features = false} +fugit = "0.3.6" +gcd = ">=2.1,<3.0" +itertools = {version = "0.13.0", default-features = false} +nb = "1.0" +paste = "1.0" +pio = "0.2.0" +rand_core = "0.6.3" +rp-binary-info = {version = "0.1.0", path = "../rp-binary-info"} +rp-hal-common = {version = "0.1.0", path = "../rp-hal-common"} +rp235x-hal-macros = {version = "0.1.0", path = "../rp235x-hal-macros"} +rp235x-pac = {version = "0.1.0", features = ["critical-section", "rt"]} +sha2-const-stable = "0.1" +usb-device = "0.3.2" +vcell = "0.1" +void = {version = "1.0.2", default-features = false} + +# Optional dependencies. Keep these sorted by name. +defmt = {version = ">=0.2.0, <0.4", optional = true} +i2c-write-iter = {version = "1.0.0", features = ["async"], optional = true} +rtic-monotonic = {version = "1.0.0", optional = true} + +[target.'thumbv8m.main-none-eabihf'.dependencies] +cortex-m = "0.7.2" +cortex-m-rt = "0.7" + +[target.riscv32imac-unknown-none-elf.dependencies] +riscv = "0.11" +riscv-rt = "0.12" + +[dev-dependencies] +# Non-optional dependencies. Keep these sorted by name. +pio-proc = "0.2.0" +rand = {version = "0.8.5", default-features = false} + +# Optional dependencies. Keep these sorted by name. +# None + +[features] +# Enable the boot-up code from the arch runtime +rt = ["rp235x-pac/rt"] + +# Memoize(cache) ROM function pointers on first use to improve performance +rom-func-cache = [] + +# critical section that is safe for multicore use +critical-section-impl = ["critical-section/restore-state-u8"] + +# Implement `defmt::Format` for several types. +defmt = ["dep:defmt"] + +# Implement `rtic_monotonic::Monotonic` based on the RP235x timer peripheral +rtic-monotonic = ["dep:rtic-monotonic"] + +# Implement `i2c-write-iter` traits +i2c-write-iter = ["dep:i2c-write-iter"] + +# Use DCP to accelerate some (but not all) f64 operations. +# +# If you really want to save every last micro-amp, and know you aren't doing any +# f64 operations, you can disable this feature (which is on by default) and then +# manually disable the DCP by either clearing the bits we set for you in the +# CPACR register, or changing the #[entry] macro to not set those bits. +# +# Almost everyone will want this on, but we let the BSPs make that choice. +dcp-fast-f64 = [] + +# Add a binary-info header block containing picotool-compatible metadata. +# +# Requires 'rt' so that the vector table is correctly sized and therefore the +# header is within reach of picotool. +binary-info = ["rt", "rp-binary-info/binary-info"] diff --git a/rp235x-hal/README.md b/rp235x-hal/README.md new file mode 100644 index 000000000..1e3318f0d --- /dev/null +++ b/rp235x-hal/README.md @@ -0,0 +1,133 @@ + +
+

+ + Logo + + +

rp-hal

+ +

+ High-level Rust drivers for the Raspberry Silicon RP2350 Microcontroller +
+ Explore the API docs » +
+
+ View Demos + · + Report a Bug + · + Chat on Matrix +

+

+ + + + +
+

Table of Contents

+
    +
  1. Introduction
  2. +
  3. Getting Started
  4. +
  5. Roadmap
  6. +
  7. Contributing
  8. +
  9. License
  10. +
  11. Contact
  12. +
  13. Acknowledgements
  14. +
+
+ + +## Introduction + +This is the `rp235x-hal` package - a library crate of high-level Rust drivers +for the Raspberry Silicon RP2350 microcontroller, along with a collection of +non-board specific example programs for you to study. You should use this crate +in your application if you want to write code for the RP2350 microcontroller. +The *HAL* in the name standards for *Hardware Abstraction Layer*, and comes from +the fact that many of the drivers included implement the generic +hardware-abstraction interfaces defined in the Rust Embedded Working Group's +[embedded-hal](https://github.com/rust-embedded/embedded-hal) crate. + +We also provide a series of [*Board Support Package* (BSP) crates][BSPs], which take +this HAL crate and pre-configure the pins according to a specific PCB design. If +you are using one of the supported boards, you should use one of those crates in +preference, and return here to see documentation about specific peripherals on +the RP2350 and how to use them. See the `boards` folder in +https://github.com/rp-rs/rp-hal-boards/ for more details. + +[BSPs]: https://github.com/rp-rs/rp-hal-boards/ + +Some of the source code herein refers to the "RP2350 Datasheet". This can be +found at . + + +## Getting Started + +To include this crate in your project, amend your `Cargo.toml` file to include + +```toml +rp235x-hal = "*" +``` + +To obtain a copy of the source code (e.g. if you want to propose a bug-fix or +new feature, or simply to study the code), run: + +```console +$ git clone https://github.com/rp-rs/rp-hal.git +``` + +For details on how to program an RP2350 microcontroller, see the [top-level +rp-hal README](https://github.com/rp-rs/rp-hal/). + + +## Roadmap + +NOTE This HAL is under active development. As such, it is likely to remain +volatile until a 1.0.0 release. + +See the [open issues](https://github.com/rp-rs/rp-hal/issues) for a list of +proposed features (and known issues). + +### Implemented traits + +This crate aims to implement all traits from embedded-hal, both version +0.2 and 1.0. They can be used at the same time, so you can upgrade drivers +incrementally. + + +## Contributing + +Contributions are what make the open source community such an amazing place to +be learn, inspire, and create. Any contributions you make are **greatly +appreciated**. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + + +## License + +The contents of this repository are dual-licensed under the _MIT OR Apache 2.0_ +License. That means you can choose either the MIT license or the Apache 2.0 +license when you re-use this code. See [`LICENSE-MIT`](./LICENSE-MIT) or +[`LICENSE-APACHE`](./LICENSE-APACHE) for more information on each specific +license. Our Apache 2.0 notices can be found in [`NOTICE`](./NOTICE). + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + + +## Contact + +* Project Link: [https://github.com/rp-rs/rp-hal/issues](https://github.com/rp-rs/rp-hal/issues) +* Matrix: [#rp-rs:matrix.org](https://matrix.to/#/#rp-rs:matrix.org) + + +## Acknowledgements + +* [Othneil Drew's README template](https://github.com/othneildrew) diff --git a/rp235x-hal/src/adc.rs b/rp235x-hal/src/adc.rs new file mode 100644 index 000000000..7401dd5f1 --- /dev/null +++ b/rp235x-hal/src/adc.rs @@ -0,0 +1,950 @@ +//! Analog-Digital Converter (ADC) +//! +//! See [Section 12.4](https://rptl.io/rp2350-datasheet#section_adc) of the datasheet for more details +//! +//! ## Usage +//! +//! Capture ADC reading from a pin: + +//! ```no_run +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! use rp235x_hal::{self as hal, adc::Adc, adc::AdcPin, gpio::Pins, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Configure one of the pins as an ADC input +//! let mut adc_pin_0 = AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); +//! // Read the ADC counts from the ADC channel +//! let pin_adc_counts: u16 = adc.read(&mut adc_pin_0).unwrap(); +//! ``` +//! +//! Capture ADC reading from temperature sensor. Note that this needs conversion to be a real-world temperature. +//! +//! ```no_run +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! use rp235x_hal::{self as hal, adc::Adc, gpio::Pins, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! // Read the ADC counts from the ADC channel +//! let temperature_adc_counts: u16 = adc.read(&mut temperature_sensor).unwrap(); +//! ``` +//! +//! See [examples/adc.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/adc.rs) and +//! [pimoroni_pico_explorer_showcase.rs](https://github.com/rp-rs/rp-hal-boards/tree/main/boards/pimoroni-pico-explorer/examples/pimoroni_pico_explorer_showcase.rs) for more complete examples +//! +//! ### Free running mode with FIFO +//! +//! In free-running mode the ADC automatically captures samples in regular intervals. +//! The samples are written to a FIFO, from which they can be retrieved. +//! +//! ```no_run +//! # use rp235x_hal::{self as hal, adc::Adc, gpio::Pins, pac, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! +//! // Configure & start capturing to the fifo: +//! let mut fifo = adc +//! .build_fifo() +//! .clock_divider(0, 0) // sample as fast as possible (500ksps. This is the default) +//! .set_channel(&mut temperature_sensor) +//! .start(); +//! +//! loop { +//! if fifo.len() > 0 { +//! // Read one captured ADC sample from the FIFO: +//! let temperature_adc_counts: u16 = fifo.read(); +//! } +//! } +//! ``` +//! See [examples/adc_fifo_poll.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/adc_fifo_poll.rs) for a more complete example. +//! +//! ### Using DMA +//! +//! When the ADC is in free-running mode, it's possible to use DMA to transfer data from the FIFO elsewhere, without having to read the FIFO manually. +//! +//! This requires a number of steps: +//! 1. Build an `AdcFifo`, with DMA enabled ([`AdcFifoBuilder::enable_dma`]) +//! 2. Use [`AdcFifoBuilder::prepare`] instead of [`AdcFifoBuilder::start`], so that the FIFO is created in `paused` state +//! 3. Start a DMA transfer ([`dma::single_buffer::Transfer`], [`dma::double_buffer::Transfer`], ...), using the [`AdcFifo::dma_read_target`] as the source (`from` parameter) +//! 4. Finally unpause the FIFO by calling [`AdcFifo::resume`], to start capturing +//! +//! Example: +//! ```no_run +//! use rp235x_hal::{self as hal, singleton, adc::Adc, gpio::Pins, pac, Sio, dma::{single_buffer, DMAExt}}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new(peripherals.IO_BANK0, peripherals.PADS_BANK0, sio.gpio_bank0, &mut peripherals.RESETS); +//! let dma = peripherals.DMA.split(&mut peripherals.RESETS); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Enable the temperature sensor +//! let mut temperature_sensor = adc.take_temp_sensor().unwrap(); +//! +//! // Configure & start capturing to the fifo: +//! let mut fifo = adc.build_fifo() +//! .clock_divider(0, 0) // sample as fast as possible (500ksps. This is the default) +//! .set_channel(&mut temperature_sensor) +//! .enable_dma() +//! .prepare(); +//! +//! // Set up a buffer, where the samples should be written: +//! let buf = singleton!(: [u16; 500] = [0; 500]).unwrap(); +//! +//! // Start DMA transfer +//! let transfer = single_buffer::Config::new(dma.ch0, fifo.dma_read_target(), buf).start(); +//! +//! // Resume the FIFO to start capturing +//! fifo.resume(); +//! +//! // Wait for the transfer to complete: +//! let (ch, adc_read_target, buf) = transfer.wait(); +//! +//! // do something with `buf` (it now contains 500 samples read from the ADC) +//! //... +//! ``` +//! //! See [examples/adc_fifo_dma.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/adc_fifo_dma.rs) for a more complete example. +//! +//! ### Free running mode without FIFO +//! +//! While free-running mode is usually used in combination with a FIFO, there are +//! use cases where it can be used without. For example, if you want to be able to +//! get the latest available sample at any point in time, and without waiting 96 ADC clock +//! cycles (2µs). +//! +//! In this case, you can just enable free-running mode on it's own. The ADC will +//! continuously do ADC conversions. The ones not read will just be discarded, but it's +//! always possible to read the latest value, without additional delay: +//! +//! ```no_run +//! use rp235x_hal::{self as hal, adc::Adc, adc::AdcPin, gpio::Pins, Sio}; +//! // Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +//! use embedded_hal_0_2::adc::OneShot; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! // Enable adc +//! let mut adc = Adc::new(peripherals.ADC, &mut peripherals.RESETS); +//! // Configure one of the pins as an ADC input +//! let mut adc_pin_0 = AdcPin::new(pins.gpio26.into_floating_input()).unwrap(); +//! // Enable free-running mode +//! adc.free_running(&adc_pin_0); +//! // Read the ADC counts from the ADC channel whenever necessary +//! loop { +//! let pin_adc_counts: u16 = adc.read_single(); +//! // Do time critical stuff +//! } +//! ``` + +use core::convert::Infallible; +use core::marker::PhantomData; +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal_0_2::adc::{Channel, OneShot}; + +use crate::{ + dma, + gpio::{ + bank0::{Gpio26, Gpio27, Gpio28, Gpio29}, + AnyPin, DynBankId, DynPinId, Function, OutputEnableOverride, Pin, PullType, ValidFunction, + }, + pac::{dma::ch::ch_ctrl_trig::TREQ_SEL_A, ADC, RESETS}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +const TEMPERATURE_SENSOR_CHANNEL: u8 = 4; + +/// The pin was invalid for the requested operation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct InvalidPinError; + +/// A pin locked in use with the ADC. +pub struct AdcPin

+where + P: AnyPin, +{ + pin: P, + saved_output_disable: bool, + saved_input_enable: bool, +} + +impl

AdcPin

+where + P: AnyPin, +{ + /// Captures the pin to be used with an ADC and disables its digital circuitry. + pub fn new(pin: P) -> Result { + let pin_id = pin.borrow().id(); + if (26..=29).contains(&pin_id.num) && pin_id.bank == DynBankId::Bank0 { + let mut p = pin.into(); + let (od, ie) = (p.get_output_disable(), p.get_input_enable()); + p.set_output_enable_override(OutputEnableOverride::Disable); + p.set_input_enable(false); + Ok(Self { + pin: P::from(p), + saved_output_disable: od, + saved_input_enable: ie, + }) + } else { + Err(InvalidPinError) + } + } + + /// Release the pin and restore its digital circuitry's state. + pub fn release(self) -> P { + let mut p = self.pin.into(); + p.set_output_disable(self.saved_output_disable); + p.set_input_enable(self.saved_input_enable); + P::from(p) + } + + /// Returns the ADC channel of this AdcPin. + pub fn channel(&self) -> u8 { + let pin_id = self.pin.borrow().id(); + // Self::new() makes sure that this is a valid channel number + pin_id.num - 26 + } +} + +/// Trait for entities that can be used as ADC channels. +/// +/// This is implemented by [`AdcPin`] and by [`TempSense`]. +/// The trait is sealed and can't be implemented in other crates. +pub trait AdcChannel: Sealed { + /// Get the channel id used to configure the ADC peripheral. + fn channel(&self) -> u8; +} + +impl Sealed for AdcPin

{} +impl AdcChannel for AdcPin

{ + fn channel(&self) -> u8 { + self.channel() + } +} + +impl Sealed for TempSense {} +impl AdcChannel for TempSense { + fn channel(&self) -> u8 { + TEMPERATURE_SENSOR_CHANNEL + } +} + +macro_rules! channel { + ($pin:ident, $channel:expr) => { + impl Channel for AdcPin> + where + $pin: crate::gpio::ValidFunction, + { + type ID = u8; // ADC channels are identified numerically + + fn channel() -> u8 { + $channel + } + } + }; +} + +channel!(Gpio26, 0); +channel!(Gpio27, 1); +channel!(Gpio28, 2); +channel!(Gpio29, 3); + +impl Channel for AdcPin> +where + DynPinId: crate::gpio::ValidFunction, +{ + type ID = (); // ADC channels are identified at run time + fn channel() {} +} + +/// Internal temperature sensor type +pub struct TempSense { + __private: (), +} + +impl Channel for TempSense { + type ID = u8; // ADC channels are identified numerically + + fn channel() -> u8 { + TEMPERATURE_SENSOR_CHANNEL + } +} + +/// Analog to Digital Convertor (ADC). +/// +/// Represents an ADC within the RP2350. Each ADC has multiple channels, and each +/// channel is either associated with a specific GPIO pin or attached to the internal +/// temperature sensor. You should put the relevant pin into ADC mode by creating an +/// [`AdcPin`] object with it, or you can put the ADC into `Temperature Sensing Mode` +/// by calling [`Adc::take_temp_sensor()`]. Either way, the resulting objects can be +/// passed to the [`OneShot::read()`][a] trait method to actually do the read. +/// +/// [a]: embedded_hal_0_2::adc::OneShot::read +pub struct Adc { + device: ADC, +} + +impl Adc { + /// Create new adc struct and bring up adc + pub fn new(device: ADC, resets: &mut RESETS) -> Self { + device.reset_bring_down(resets); + device.reset_bring_up(resets); + + // Enable adc + device.cs().write(|w| w.en().set_bit()); + + // Wait for adc ready + while !device.cs().read().ready().bit_is_set() {} + + Self { device } + } + + /// Free underlying register block + pub fn free(self) -> ADC { + self.device + } + + /// Read the most recently sampled ADC value + /// + /// This function does not wait for the current conversion to finish. + /// If a conversion is still in progress, it returns the result of the + /// previous one. + /// + /// It also doesn't trigger a new conversion. + pub fn read_single(&self) -> u16 { + self.device.result().read().result().bits() + } + + /// Enable temperature sensor, returns a channel to use. + /// + /// This can only be done once before calling [`Adc::disable_temp_sensor()`]. If the sensor has already + /// been enabled, this method will panic. + #[deprecated( + note = "This method may panic, use `take_temp_sensor()` instead.", + since = "0.9.0" + )] + pub fn enable_temp_sensor(&mut self) -> TempSense { + self.take_temp_sensor() + .expect("Temp sensor is already enabled.") + } + + /// Enable temperature sensor, returns a channel to use + /// + /// If the sensor has already been enabled, this method returns `None`. + pub fn take_temp_sensor(&mut self) -> Option { + let mut disabled = false; + self.device.cs().modify(|r, w| { + disabled = r.ts_en().bit_is_clear(); + // if bit was already set, this is a nop + w.ts_en().set_bit() + }); + disabled.then_some(TempSense { __private: () }) + } + + /// Disable temperature sensor, consumes channel + pub fn disable_temp_sensor(&mut self, _: TempSense) { + self.device.cs().modify(|_, w| w.ts_en().clear_bit()); + } + + /// Start configuring free-running mode, and set up the FIFO + /// + /// The [`AdcFifoBuilder`] returned by this method can be used + /// to configure capture options, like sample rate, channels to + /// capture from etc. + /// + /// Capturing is started by calling [`AdcFifoBuilder::start`], which + /// returns an [`AdcFifo`] to read from. + pub fn build_fifo(&mut self) -> AdcFifoBuilder<'_, u16> { + AdcFifoBuilder { + adc: self, + marker: PhantomData, + } + } + + /// Enable free-running mode by setting the start_many flag. + pub fn free_running(&mut self, pin: &dyn AdcChannel) { + self.device.cs().modify(|_, w| { + unsafe { + w.ainsel().bits(pin.channel()); + } + w.start_many().set_bit(); + w + }); + } + + /// Disable free-running mode by unsetting the start_many flag. + pub fn stop(&mut self) { + self.device.cs().modify(|_, w| w.start_many().clear_bit()); + } + + fn inner_read(&mut self, chan: u8) -> u16 { + self.wait_ready(); + + self.device + .cs() + .modify(|_, w| unsafe { w.ainsel().bits(chan).start_once().set_bit() }); + + self.wait_ready(); + + self.read_single() + } + + /// Wait for the ADC to become ready. + /// + /// Also returns immediately if start_many is set, to avoid indefinite blocking. + pub fn wait_ready(&self) { + while !self.is_ready_or_free_running() { + core::hint::spin_loop(); + } + } + + fn is_ready_or_free_running(&self) -> bool { + let cs = self.device.cs().read(); + cs.ready().bit_is_set() || cs.start_many().bit_is_set() + } + + /// Returns true if the ADC is ready for the next conversion. + /// + /// This implies that any previous conversion has finished. + pub fn is_ready(&self) -> bool { + self.device.cs().read().ready().bit_is_set() + } +} + +// Implementation for TempSense and type-checked pins +impl OneShot for Adc +where + WORD: From, + SRC: Channel, +{ + type Error = Infallible; + + fn read(&mut self, _pin: &mut SRC) -> nb::Result { + let chan = SRC::channel(); + + Ok(self.inner_read(chan).into()) + } +} + +// Implementation for dyn-pins +impl OneShot>> for Adc +where + WORD: From, + F: Function, + M: PullType, + DynPinId: ValidFunction, + AdcPin>: Channel, +{ + type Error = Infallible; + + fn read(&mut self, pin: &mut AdcPin>) -> nb::Result { + Ok(self.inner_read(pin.channel()).into()) + } +} + +/// Used to configure & build an [`AdcFifo`] +/// +/// See [`Adc::build_fifo`] for details, as well as the `adc_fifo_*` [examples](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal/examples). +pub struct AdcFifoBuilder<'a, Word> { + adc: &'a mut Adc, + marker: PhantomData, +} + +impl<'a, Word> AdcFifoBuilder<'a, Word> { + /// Manually set clock divider to control sample rate + /// + /// The ADC is tied to the USB clock, normally running at 48MHz. ADC + /// conversion happens at 96 cycles per sample, so with the dividers both + /// set to 0 (the default) the sample rate will be `48MHz / 96 = 500ksps`. + /// + /// Setting the `int` and / or `frac` dividers will hold off between + /// samples, leading to an effective rate of: + /// + /// ```text + /// rate = 48MHz / (1 + int + (frac / 256)) + /// ``` + /// + /// To determine the required `int` and `frac` values for a given target + /// rate, use these equations: + /// + /// ```text + /// int = floor((48MHz / rate) - 1) + /// frac = round(256 * ((48MHz / rate) - 1 - int)) + /// ``` + /// + /// Some examples: + /// + /// | Target rate | `int` | `frac` | + /// |-------------|---------|--------| + /// | 1000sps | `47999` | `0` | + /// | 1024sps | `46874` | `0` | + /// | 1337sps | `35900` | `70` | + /// | 4096sps | `11717` | `192` | + /// | 96ksps | `499` | `0` | + /// + /// Since each conversion takes 96 cycles, setting `int` to anything below + /// 96 does not make a difference, and leads to the same result as setting + /// it to 0. + /// + /// The lowest possible rate is 732.41Hz, attainable by setting `int = + /// 0xFFFF, frac = 0xFF`. + /// + /// For more details, please refer to [Section + /// 12.4.3.2](https://rptl.io/rp2350-datasheet#section_adc) in the RP2350 + /// datasheet. + pub fn clock_divider(self, int: u16, frac: u8) -> Self { + self.adc + .device + .div() + .modify(|_, w| unsafe { w.int().bits(int).frac().bits(frac) }); + self + } + + /// Select ADC input channel to sample from + /// + /// If round-robin mode is used, this will only affect the first sample. + /// + /// The given `pin` can either be one of the ADC inputs (GPIO26-28) or the + /// internal temperature sensor (retrieved via [`Adc::take_temp_sensor`]). + pub fn set_channel(self, pin: &mut P) -> Self { + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.ainsel().bits(pin.channel()) }); + self + } + + /// Set channels to use for round-robin mode + /// + /// Takes a tuple of channels, like `(&mut adc_pin, &mut temp_sense)`. + /// + /// **NOTE:** *The order in which the channels are specified has no effect! + /// Channels are always sampled in increasing order, by their channel number (Channel 0, Channel 1, ...).* + pub fn round_robin>(self, selected_channels: T) -> Self { + let RoundRobin(bits) = selected_channels.into(); + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.rrobin().bits(bits as u16) }); + self + } + + /// Enable the FIFO interrupt ([`ADC_IRQ_FIFO`](crate::pac::Interrupt::ADC_IRQ_FIFO)) + /// + /// It will be triggered whenever there are at least `threshold` samples waiting in the FIFO. + pub fn enable_interrupt(self, threshold: u8) -> Self { + self.adc.device.inte().modify(|_, w| w.fifo().set_bit()); + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.thresh().bits(threshold) }); + self + } + + /// Shift values to produce 8 bit samples (discarding the lower 4 bits). + /// + /// Normally the ADC uses 12 bits of precision, packed into a u16. + /// Shifting the values loses some precision, but produces smaller samples. + /// + /// When this method has been called, the resulting fifo's `read` method returns u8. + pub fn shift_8bit(self) -> AdcFifoBuilder<'a, u8> { + self.adc.device.fcs().modify(|_, w| w.shift().set_bit()); + AdcFifoBuilder { + adc: self.adc, + marker: PhantomData, + } + } + + /// Enable DMA for the FIFO. + /// + /// This must be called to be able to transfer data from the ADC using a DMA transfer. + /// + /// **NOTE:** *this method sets the FIFO interrupt threshold to `1`, which is required for DMA transfers to work. + /// The threshold is the same one as set by [`AdcFifoBuilder::enable_interrupt`]. If you want to enable FIFO + /// interrupts, but also use DMA, the `threshold` parameter passed to `enable_interrupt` *must* be set to `1` as well.* + pub fn enable_dma(self) -> Self { + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.dreq_en().set_bit().thresh().bits(1) }); + self + } + + /// Enable ADC FIFO and start free-running conversion + /// + /// Use the returned [`AdcFifo`] instance to access the captured data. + /// + /// To stop capturing, call [`AdcFifo::stop`]. + /// + /// Note: if you plan to use the FIFO for DMA transfers, [`AdcFifoBuilder::prepare`] instead. + pub fn start(self) -> AdcFifo<'a, Word> { + self.adc.device.fcs().modify(|_, w| w.en().set_bit()); + self.adc.device.cs().modify(|_, w| w.start_many().set_bit()); + AdcFifo { + adc: self.adc, + marker: PhantomData, + } + } + + /// Enable ADC FIFO, but do not start conversion yet + /// + /// Same as [`AdcFifoBuilder::start`], except the FIFO is initially paused. + /// + /// Use [`AdcFifo::resume`] to start conversion. + pub fn start_paused(self) -> AdcFifo<'a, Word> { + self.adc.device.fcs().modify(|_, w| w.en().set_bit()); + self.adc + .device + .cs() + .modify(|_, w| w.start_many().clear_bit()); + AdcFifo { + adc: self.adc, + marker: PhantomData, + } + } + + /// Alias for [`AdcFifoBuilder::start_paused`]. + #[deprecated(note = "Use `start_paused()` instead.", since = "0.10.0")] + pub fn prepare(self) -> AdcFifo<'a, Word> { + self.start_paused() + } +} + +/// Represents the ADC fifo +/// +/// Constructed by [`AdcFifoBuilder::start`], which is accessible through [`Adc::build_fifo`]. +pub struct AdcFifo<'a, Word> { + adc: &'a mut Adc, + marker: PhantomData, +} + +impl<'a, Word> AdcFifo<'a, Word> { + #[allow(clippy::len_without_is_empty)] + /// Returns the number of elements currently in the fifo + pub fn len(&mut self) -> u8 { + self.adc.device.fcs().read().level().bits() + } + + /// Check if there was a fifo overrun + /// + /// An overrun happens when the fifo is filled up faster than `read` is called to consume it. + /// + /// This function also clears the `over` bit if it was set. + pub fn is_over(&mut self) -> bool { + let over = self.adc.device.fcs().read().over().bit(); + if over { + self.adc + .device + .fcs() + .modify(|_, w| w.over().clear_bit_by_one()); + } + over + } + + /// Check if there was a fifo underrun + /// + /// An underrun happens when `read` is called on an empty fifo (`len() == 0`). + /// + /// This function also clears the `under` bit if it was set. + pub fn is_under(&mut self) -> bool { + let under = self.adc.device.fcs().read().under().bit(); + if under { + self.adc + .device + .fcs() + .modify(|_, w| w.under().clear_bit_by_one()); + } + under + } + + /// Read the most recently sampled ADC value + /// + /// Returns the most recently sampled value, bypassing the FIFO. + /// + /// This can be used if you want to read samples occasionally, but don't + /// want to incur the 96 cycle delay of a one-off read. + /// + /// Example: + /// ```ignore + /// // start continuously sampling values: + /// let mut fifo = adc.build_fifo().set_channel(&mut adc_pin).start(); + /// + /// loop { + /// do_something_timing_critical(); + /// + /// // read the most recent value: + /// if fifo.read_single() > THRESHOLD { + /// led.set_high().unwrap(); + /// } else { + /// led.set_low().unwrap(); + /// } + /// } + /// + /// // stop sampling, when it's no longer needed + /// fifo.stop(); + /// ``` + /// + /// Note that when round-robin sampling is used, there is no way + /// to tell from which channel this sample came. + pub fn read_single(&mut self) -> u16 { + self.adc.read_single() + } + + /// Returns `true` if conversion is currently paused. + /// + /// While paused, no samples will be added to the FIFO. + /// + /// There may be existing samples in the FIFO though, or a conversion may still be in progress. + pub fn is_paused(&mut self) -> bool { + self.adc.device.cs().read().start_many().bit_is_clear() + } + + /// Temporarily pause conversion + /// + /// This method stops ADC conversion, but leaves everything else configured. + /// + /// No new samples are captured until [`AdcFifo::resume`] is called. + /// + /// Note that existing samples can still be read from the FIFO, and can possibly + /// cause interrupts and DMA transfer progress until the FIFO is emptied. + pub fn pause(&mut self) { + self.adc + .device + .cs() + .modify(|_, w| w.start_many().clear_bit()); + } + + /// Resume conversion after it was paused + /// + /// There are two situations when it makes sense to use this method: + /// - After having called [`AdcFifo::pause`] on an AdcFifo + /// - If the FIFO was initialized using [`AdcFifoBuilder::prepare`]. + /// + /// Calling this method when conversion is already running has no effect. + pub fn resume(&mut self) { + self.adc.device.cs().modify(|_, w| w.start_many().set_bit()); + } + + /// Clears the FIFO, removing all values + /// + /// Reads and discards values from the FIFO until it is empty. + /// + /// This only makes sense to use while the FIFO is paused (see [`AdcFifo::pause`]). + pub fn clear(&mut self) { + while self.len() > 0 { + self.read_from_fifo(); + } + } + + /// Stop capturing in free running mode. + /// + /// Resets all capture options that can be set via [`AdcFifoBuilder`] to + /// their defaults. + /// + /// Returns the underlying [`Adc`], to be reused. + pub fn stop(mut self) -> &'a mut Adc { + // stop capture and clear channel selection + self.adc + .device + .cs() + .modify(|_, w| unsafe { w.start_many().clear_bit().rrobin().bits(0).ainsel().bits(0) }); + // disable fifo interrupt + self.adc.device.inte().modify(|_, w| w.fifo().clear_bit()); + // Wait for one more conversion, then drain remaining values from fifo. + // This MUST happen *after* the interrupt is disabled, but + // *before* `thresh` is modified. Otherwise if `INTS.FIFO = 1`, + // the interrupt will be fired one more time. + // The only way to clear `INTS.FIFO` is for `FCS.LEVEL` to go + // below `FCS.THRESH`, which requires `FCS.THRESH` not to be 0. + while self.adc.device.cs().read().ready().bit_is_clear() {} + self.clear(); + // disable fifo, reset threshold to 0 and disable DMA + self.adc + .device + .fcs() + .modify(|_, w| unsafe { w.en().clear_bit().thresh().bits(0).dreq_en().clear_bit() }); + // reset clock divider + self.adc + .device + .div() + .modify(|_, w| unsafe { w.int().bits(0).frac().bits(0) }); + self.adc + } + + /// Block until a ADC_IRQ_FIFO interrupt occurs + /// + /// Interrupts must be enabled ([`AdcFifoBuilder::enable_interrupt`]), or else this methods blocks forever. + pub fn wait_for_interrupt(&mut self) { + while self.adc.device.intr().read().fifo().bit_is_clear() {} + } + + fn read_from_fifo(&mut self) -> u16 { + self.adc.device.fifo().read().val().bits() + } + + /// Returns a read-target for initiating DMA transfers + /// + /// The [`DmaReadTarget`] returned by this function can be used to initiate DMA transfers + /// reading from the ADC. + pub fn dma_read_target(&self) -> DmaReadTarget { + DmaReadTarget(self.adc.device.fifo().as_ptr() as u32, PhantomData) + } + + /// Trigger a single conversion + /// + /// Ignored when in [`Adc::free_running`] mode. + pub fn trigger(&mut self) { + self.adc.device.cs().modify(|_, w| w.start_once().set_bit()); + } + + /// Check if ADC is ready for the next conversion trigger + /// + /// Not useful when in [`Adc::free_running`] mode. + pub fn is_ready(&self) -> bool { + self.adc.device.cs().read().ready().bit_is_set() + } +} + +impl<'a> AdcFifo<'a, u16> { + /// Read a single value from the fifo (u16 version, not shifted) + pub fn read(&mut self) -> u16 { + self.read_from_fifo() + } +} + +impl<'a> AdcFifo<'a, u8> { + /// Read a single value from the fifo (u8 version, shifted) + /// + /// Also see [`AdcFifoBuilder::shift_8bit`]. + pub fn read(&mut self) -> u8 { + self.read_from_fifo() as u8 + } +} + +/// Represents a [`dma::ReadTarget`] for the [`AdcFifo`] +/// +/// If [`AdcFifoBuilder::shift_8bit`] was called when constructing the FIFO, +/// `Word` will be `u8`, otherwise it will be `u16`. +pub struct DmaReadTarget(u32, PhantomData); + +/// Safety: rx_address_count points to a register which is always a valid +/// read target. +unsafe impl dma::ReadTarget for DmaReadTarget { + type ReceivedWord = Word; + + fn rx_treq() -> Option { + Some(TREQ_SEL_A::ADC.into()) + } + + fn rx_address_count(&self) -> (u32, u32) { + (self.0, u32::MAX) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl dma::EndlessReadTarget for DmaReadTarget {} + +/// Internal struct representing values for the `CS.RROBIN` register. +/// +/// See [`AdcFifoBuilder::round_robin`], for usage example. +pub struct RoundRobin(u8); + +impl From<&PIN> for RoundRobin { + fn from(pin: &PIN) -> Self { + Self(1 << pin.channel()) + } +} + +impl From<(&A, &B)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, +{ + fn from(pins: (&A, &B)) -> Self { + Self(1 << pins.0.channel() | 1 << pins.1.channel()) + } +} + +impl From<(&A, &B, &C)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, +{ + fn from(pins: (&A, &B, &C)) -> Self { + Self(1 << pins.0.channel() | 1 << pins.1.channel() | 1 << pins.2.channel()) + } +} + +impl From<(&A, &B, &C, &D)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, + D: AdcChannel, +{ + fn from(pins: (&A, &B, &C, &D)) -> Self { + Self( + 1 << pins.0.channel() + | 1 << pins.1.channel() + | 1 << pins.2.channel() + | 1 << pins.3.channel(), + ) + } +} + +impl From<(&A, &B, &C, &D, &E)> for RoundRobin +where + A: AdcChannel, + B: AdcChannel, + C: AdcChannel, + D: AdcChannel, + E: AdcChannel, +{ + fn from(pins: (&A, &B, &C, &D, &E)) -> Self { + Self( + 1 << pins.0.channel() + | 1 << pins.1.channel() + | 1 << pins.2.channel() + | 1 << pins.3.channel() + | 1 << pins.4.channel(), + ) + } +} diff --git a/rp235x-hal/src/arch.rs b/rp235x-hal/src/arch.rs new file mode 100644 index 000000000..a87fe9bc6 --- /dev/null +++ b/rp235x-hal/src/arch.rs @@ -0,0 +1,138 @@ +//! Portable in-line assembly +//! +//! Replaces `cortex_m::asm` with things that work on RISC-V and Arm. + +#[cfg(all(target_arch = "arm", target_os = "none"))] +mod inner { + pub use cortex_m::asm::{delay, dsb, nop, sev, wfe, wfi}; + pub use cortex_m::interrupt::{disable as interrupt_disable, enable as interrupt_enable}; + + /// Are interrupts current enabled? + pub fn interrupts_enabled() -> bool { + cortex_m::register::primask::read().is_active() + } + + /// Run the closure without interrupts + /// + /// No critical-section token because we haven't blocked the second core + pub fn interrupt_free(f: F) -> T + where + F: FnOnce() -> T, + { + let active = interrupts_enabled(); + if active { + interrupt_disable(); + } + let t = f(); + if active { + unsafe { + interrupt_enable(); + } + } + t + } +} + +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +mod inner { + pub use riscv::asm::{delay, nop, wfi}; + pub use riscv::interrupt::machine::{ + disable as interrupt_disable, enable as interrupt_enable, free as interrupt_free, + }; + + /// Send Event + #[inline(always)] + pub fn sev() { + unsafe { + // This is how h3.unblock is encoded. + core::arch::asm!("slt x0, x0, x1"); + } + } + + /// Wait for Event + /// + /// This is the interrupt-safe version of WFI. + pub fn wfe() { + let active = interrupts_enabled(); + if active { + interrupt_disable(); + } + wfi(); + if active { + unsafe { + interrupt_enable(); + } + } + } + + /// Data Synchronization Barrier + #[inline(always)] + pub fn dsb() { + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + unsafe { core::arch::asm!("fence", options(nostack, preserves_flags)) }; + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + } + + /// Are interrupts current enabled? + #[inline(always)] + pub fn interrupts_enabled() -> bool { + riscv::register::mstatus::read().mie() + } +} + +#[cfg(not(all(any(target_arch = "arm", target_arch = "riscv32"), target_os = "none")))] +mod inner { + /// Placeholder function to disable interrupts + pub fn interrupt_disable() {} + /// Placeholder function to enable interrupts + pub fn interrupt_enable() {} + /// Placeholder function to check if interrupts are enabled + pub fn interrupts_enabled() -> bool { + false + } + /// Placeholder function to wait for an event + pub fn wfe() {} + /// Placeholder function to do nothing + pub fn nop() {} + /// Placeholder function to emit a data synchronisation barrier + pub fn dsb() {} + /// Placeholder function to run a closure with interrupts disabled + pub fn interrupt_free(f: F) -> T + where + F: FnOnce() -> T, + { + f() + } + /// Placeholder function to wait for some clock cycles + pub fn delay(_: u32) {} + /// Placeholder function to emit an event + pub fn sev() {} +} + +pub use inner::*; + +/// Create a static variable which we can grab a mutable reference to exactly once. +#[macro_export] +macro_rules! singleton { + ($name:ident: $ty:ty = $expr:expr) => {{ + static mut $name: (::core::mem::MaybeUninit<$ty>, ::core::sync::atomic::AtomicBool) = + (::core::mem::MaybeUninit::uninit(), ::core::sync::atomic::AtomicBool::new(false)); + + #[allow(unsafe_code)] + if unsafe { $name.1.compare_exchange(false, true, ::core::sync::atomic::Ordering::SeqCst, ::core::sync::atomic::Ordering::SeqCst).is_ok() } { + // If we get here, the bool was false and we were the ones who set it to true. + // So we have exclusive access. + let expr = $expr; + #[allow(unsafe_code)] + unsafe { + $name.0 = ::core::mem::MaybeUninit::new(expr); + Some(&mut *$name.0.as_mut_ptr()) + } + } else { + None + } + }}; + (: $ty:ty = $expr:expr) => { + $crate::singleton!(VAR: $ty = $expr) + }; +} diff --git a/rp235x-hal/src/async_utils.rs b/rp235x-hal/src/async_utils.rs new file mode 100644 index 000000000..3bfee7233 --- /dev/null +++ b/rp235x-hal/src/async_utils.rs @@ -0,0 +1,143 @@ +//! Commonly used in async implementations. + +use core::{marker::PhantomData, task::Poll}; + +pub(crate) mod sealed { + use core::{cell::Cell, task::Waker}; + use critical_section::Mutex; + + pub trait Wakeable { + /// Returns the waker associated with driver instance. + fn waker() -> &'static IrqWaker; + } + + /// This type wraps a `Waker` in a `Mutex>`. + /// + /// While `critical_section::Mutex` intregrates nicely with RefCell, RefCell adds a borrow + /// counter that is not necessary for this usecase. + /// + /// This type is kept sealed to prevent user from mistakenly messing with the waker such as + /// clearing it while the driver is parked. + pub struct IrqWaker { + waker: Mutex>>, + } + + impl Default for IrqWaker { + fn default() -> Self { + Self::new() + } + } + + impl IrqWaker { + pub const fn new() -> Self { + Self { + waker: Mutex::new(Cell::new(None)), + } + } + pub fn wake(&self) { + critical_section::with(|cs| { + if let Some(waker) = self.waker.borrow(cs).take() { + Waker::wake(waker); + } + }); + } + pub fn register(&self, waker: &Waker) { + critical_section::with(|cs| { + self.waker.borrow(cs).replace(Some(waker.clone())); + }); + } + pub fn clear(&self) { + critical_section::with(|cs| { + self.waker.borrow(cs).take(); + }); + } + } +} + +/// Marks driver instances that can be bound to an interrupt to wake async tasks. +pub trait AsyncPeripheral: sealed::Wakeable { + /// Signals the driver of an interrupt. + fn on_interrupt(); +} + +#[must_use = "Future do nothing unless they are polled on."] +pub(crate) struct CancellablePollFn<'periph, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + CancelFn: FnMut(&mut Periph), +{ + periph: &'periph mut Periph, + poll: PFn, + enable_irq: EnIrqFn, + cancel: CancelFn, + done: bool, + // captures F's return type. + phantom: PhantomData, +} +impl<'p, Periph, PFn, EnIrqFn, CancelFn, OutputTy> + CancellablePollFn<'p, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + PFn: FnMut(&mut Periph) -> Poll, + EnIrqFn: FnMut(&mut Periph), + CancelFn: FnMut(&mut Periph), +{ + pub(crate) fn new( + periph: &'p mut Periph, + poll: PFn, + enable_irq: EnIrqFn, + cancel: CancelFn, + ) -> Self { + Self { + periph, + poll, + enable_irq, + cancel, + done: false, + phantom: PhantomData, + } + } +} + +impl core::future::Future + for CancellablePollFn<'_, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + PFn: FnMut(&mut Periph) -> Poll, + EnIrqFn: FnMut(&mut Periph), + CancelFn: FnMut(&mut Periph), +{ + type Output = OutputTy; + + fn poll(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + // SAFETY: We are not moving anything. + let Self { + ref mut periph, + poll: ref mut is_ready, + enable_irq: ref mut setup_flags, + ref mut done, + .. + } = unsafe { self.get_unchecked_mut() }; + let r = (is_ready)(periph); + if r.is_pending() { + Periph::waker().register(cx.waker()); + (setup_flags)(periph); + } else { + *done = true; + } + r + } +} +impl<'periph, Periph, PFn, EnIrqFn, CancelFn, OutputTy> Drop + for CancellablePollFn<'periph, Periph, PFn, EnIrqFn, CancelFn, OutputTy> +where + Periph: sealed::Wakeable, + CancelFn: FnMut(&mut Periph), +{ + fn drop(&mut self) { + if !self.done { + Periph::waker().clear(); + (self.cancel)(self.periph); + } + } +} diff --git a/rp235x-hal/src/atomic_register_access.rs b/rp235x-hal/src/atomic_register_access.rs new file mode 100644 index 000000000..b429bab5c --- /dev/null +++ b/rp235x-hal/src/atomic_register_access.rs @@ -0,0 +1,40 @@ +//! Provide atomic access to peripheral registers +//! +//! This feature is not available for all peripherals. +//! See [Section 2.1.3][section_2_1_3] of the RP2350 datasheet for details. +//! +//! [section_2_1_3]: https://rptl.io/rp2350-datasheet#atomic-rwtype + +use core::ptr::write_volatile; + +/// Perform atomic bitmask set operation on register +/// +/// See [Section 2.1.3][section_2_1_3] of the RP2350 datasheet for details. +/// +/// [section_2_1_3]: https://rptl.io/rp2350-datasheet#atomic-rwtype +/// +/// # Safety +/// +/// In addition to the requirements of [core::ptr::write_volatile], +/// `register` must point to a register providing atomic aliases. +#[inline] +pub(crate) unsafe fn write_bitmask_set(register: *mut u32, bits: u32) { + let alias = (register as usize + 0x2000) as *mut u32; + write_volatile(alias, bits); +} + +/// Perform atomic bitmask clear operation on register +/// +/// See [Section 2.1.3][section_2_1_3] of the RP2350 datasheet for details. +/// +/// [section_2_1_3]: https://rptl.io/rp2350-datasheet#atomic-rwtype +/// +/// # Safety +/// +/// In addition to the requirements of [core::ptr::write_volatile], +/// `register` must point to a register providing atomic aliases. +#[inline] +pub(crate) unsafe fn write_bitmask_clear(register: *mut u32, bits: u32) { + let alias = (register as usize + 0x3000) as *mut u32; + write_volatile(alias, bits); +} diff --git a/rp235x-hal/src/block.rs b/rp235x-hal/src/block.rs new file mode 100644 index 000000000..7421dd7b0 --- /dev/null +++ b/rp235x-hal/src/block.rs @@ -0,0 +1,1092 @@ +//! Support for the RP235x Boot ROM's "Block" structures +//! +//! Blocks contain pointers, to form Block Loops. +//! +//! The `IMAGE_DEF` Block (here the `ImageDef` type) tells the ROM how to boot a +//! firmware image. The `PARTITION_TABLE` Block (here the `PartitionTable` type) +//! tells the ROM how to divide the flash space up into partitions. + +// These all have a 1 byte size + +/// An item ID for encoding a Vector Table address +pub const ITEM_1BS_VECTOR_TABLE: u8 = 0x03; + +/// An item ID for encoding a Rolling Window Delta +pub const ITEM_1BS_ROLLING_WINDOW_DELTA: u8 = 0x05; + +/// An item ID for encoding a Signature +pub const ITEM_1BS_SIGNATURE: u8 = 0x09; + +/// An item ID for encoding a Salt +pub const ITEM_1BS_SALT: u8 = 0x0c; + +/// An item ID for encoding an Image Type +pub const ITEM_1BS_IMAGE_TYPE: u8 = 0x42; + +/// An item ID for encoding the image's Entry Point +pub const ITEM_1BS_ENTRY_POINT: u8 = 0x44; + +/// An item ID for encoding the definition of a Hash +pub const ITEM_2BS_HASH_DEF: u8 = 0x47; + +/// An item ID for encoding a Version +pub const ITEM_1BS_VERSION: u8 = 0x48; + +/// An item ID for encoding a Hash +pub const ITEM_1BS_HASH_VALUE: u8 = 0x4b; + +// These all have a 2-byte size + +/// An item ID for encoding a Load Map +pub const ITEM_2BS_LOAD_MAP: u8 = 0x06; + +/// An item ID for encoding a Partition Table +pub const ITEM_2BS_PARTITION_TABLE: u8 = 0x0a; + +/// An item ID for encoding a placeholder entry that is ignored +/// +/// Allows a Block to not be empty. +pub const ITEM_2BS_IGNORED: u8 = 0xfe; + +/// An item ID for encoding the special last item in a Block +/// +/// It records how long the Block is. +pub const ITEM_2BS_LAST: u8 = 0xff; + +// Options for ITEM_1BS_IMAGE_TYPE + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as invalid +pub const IMAGE_TYPE_INVALID: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as an executable +pub const IMAGE_TYPE_EXE: u16 = 0x0001; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as data +pub const IMAGE_TYPE_DATA: u16 = 0x0002; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as unspecified +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_NS: u16 = 0x0010; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_S: u16 = 0x0020; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as Arm +pub const IMAGE_TYPE_EXE_CPU_ARM: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as RISC-V +pub const IMAGE_TYPE_EXE_CPU_RISCV: u16 = 0x0100; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2040 +pub const IMAGE_TYPE_EXE_CHIP_RP2040: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2350 +pub const IMAGE_TYPE_EXE_CHIP_RP2350: u16 = 0x1000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the image as Try Before You Buy. +/// +/// This means the image must be marked as 'Bought' with the ROM before the +/// watchdog times out the trial period, otherwise it is erased and the previous +/// image will be booted. +pub const IMAGE_TYPE_TBYB: u16 = 0x8000; + +/// This is the magic Block Start value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_START` +const BLOCK_MARKER_START: u32 = 0xffffded3; + +/// This is the magic Block END value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_END` +const BLOCK_MARKER_END: u32 = 0xab123579; + +/// An Image Definition has one item in it - an [`ITEM_1BS_IMAGE_TYPE`] +pub type ImageDef = Block<1>; + +/// A Block as understood by the Boot ROM. +/// +/// This could be an Image Definition, or a Partition Table, or maybe some other +/// kind of block. +/// +/// It contains within the special start and end markers the Boot ROM is looking +/// for. +#[derive(Debug)] +#[repr(C)] +pub struct Block { + marker_start: u32, + items: [u32; N], + length: u32, + offset: *const u32, + marker_end: u32, +} + +unsafe impl Sync for Block {} + +impl Block { + /// Construct a new Binary Block, with the given items. + /// + /// The length, and the Start and End markers are added automatically. The + /// Block Loop pointer initially points to itself. + pub const fn new(items: [u32; N]) -> Block { + Block { + marker_start: BLOCK_MARKER_START, + items, + length: item_last(N as u16), + // offset from this block to next block in loop. By default + // we form a Block Loop with a single Block in it. + offset: core::ptr::null(), + marker_end: BLOCK_MARKER_END, + } + } + + /// Change the Block Loop offset value. + /// + /// This method isn't that useful because you can't evaluate the difference + /// between two pointers in a const context as the addresses aren't assigned + /// until long after the const evaluator has run. + /// + /// If you think you need this method, you might want to set a unique random + /// value here and swap it for the real offset as a post-processing step. + pub const fn with_offset(self, offset: *const u32) -> Block { + Block { offset, ..self } + } +} + +impl Block<0> { + /// Construct an empty block. + pub const fn empty() -> Block<0> { + Block::new([]) + } + + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<1> { + Block::new([word]) + } +} + +impl Block<1> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<2> { + Block::new([self.items[0], word]) + } +} + +impl Block<2> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<3> { + Block::new([self.items[0], self.items[1], word]) + } +} + +impl ImageDef { + /// Construct a new IMAGE_DEF Block, for an EXE with the given security and + /// architecture. + pub const fn arch_exe(security: Security, architecture: Architecture) -> Self { + Self::new([item_image_type_exe(security, architecture)]) + } + + /// Construct a new IMAGE_DEF Block, for an EXE with the given security. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn exe(security: Security) -> Self { + if cfg!(all(target_arch = "riscv32", target_os = "none")) { + Self::arch_exe(security, Architecture::Riscv) + } else { + Self::arch_exe(security, Architecture::Arm) + } + } + + /// Construct a new IMAGE_DEF Block, for a Non-Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn non_secure_exe() -> Self { + Self::exe(Security::NonSecure) + } + + /// Construct a new IMAGE_DEF Block, for a Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn secure_exe() -> Self { + Self::exe(Security::Secure) + } +} + +/// We make our partition table this fixed size. +pub const PARTITION_TABLE_MAX_ITEMS: usize = 128; + +/// Describes a unpartitioned space +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct UnpartitionedSpace { + permissions_and_location: u32, + permissions_and_flags: u32, +} + +impl UnpartitionedSpace { + /// Create a new unpartitioned space. + /// + /// It defaults to no permissions. + pub const fn new() -> Self { + Self { + permissions_and_location: 0, + permissions_and_flags: 0, + } + } + + /// Create a new unpartition space from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PT_INFO`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | permission as u32, + permissions_and_location: self.permissions_and_location | permission as u32, + } + } + + /// Set a flag + pub const fn with_flag(self, flag: UnpartitionedFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: UnpartitionedFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for UnpartitionedSpace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a Partition +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Partition { + permissions_and_location: u32, + permissions_and_flags: u32, + id: Option, + extra_families: [u32; 4], + extra_families_len: usize, + name: [u8; 128], +} + +impl Partition { + const FLAGS_HAS_ID: u32 = 0b1; + const FLAGS_LINK_TYPE_A_PARTITION: u32 = 0b01 << 1; + const FLAGS_LINK_TYPE_OWNER: u32 = 0b10 << 1; + const FLAGS_LINK_MASK: u32 = 0b111111 << 1; + const FLAGS_HAS_NAME: u32 = 0b1 << 12; + const FLAGS_HAS_EXTRA_FAMILIES_SHIFT: u8 = 7; + const FLAGS_HAS_EXTRA_FAMILIES_MASK: u32 = 0b11 << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT; + + /// Create a new partition, with the given start and end sectors. + /// + /// It defaults to no permissions. + pub const fn new(first_sector: u16, last_sector: u16) -> Self { + // 0x2000 sectors of 4 KiB is 32 MiB, which is the total XIP area + assert!(first_sector < 0x2000); + assert!(last_sector < 0x2000); + assert!(first_sector <= last_sector); + Self { + permissions_and_location: (last_sector as u32) << 13 | first_sector as u32, + permissions_and_flags: 0, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Create a new partition from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PARTITION_LOCATION_AND_FLAGS`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_location: self.permissions_and_location | permission as u32, + permissions_and_flags: self.permissions_and_flags | permission as u32, + ..self + } + } + + /// Set the name of the partition + pub const fn with_name(self, name: &str) -> Self { + let mut new_name = [0u8; 128]; + let name = name.as_bytes(); + let mut idx = 0; + new_name[0] = name.len() as u8; + while idx < name.len() { + new_name[idx + 1] = name[idx]; + idx += 1; + } + Self { + name: new_name, + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_NAME, + ..self + } + } + + /// Set the extra families for the partition. + /// + /// You can supply up to four. + pub const fn with_extra_families(self, extra_families: &[u32]) -> Self { + assert!(extra_families.len() <= 4); + let mut new_extra_families = [0u32; 4]; + let mut idx = 0; + while idx < extra_families.len() { + new_extra_families[idx] = extra_families[idx]; + idx += 1; + } + Self { + extra_families: new_extra_families, + extra_families_len: extra_families.len(), + permissions_and_flags: (self.permissions_and_flags + & !Self::FLAGS_HAS_EXTRA_FAMILIES_MASK) + | (extra_families.len() as u32) << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT, + ..self + } + } + + /// Set the ID + pub const fn with_id(self, id: u64) -> Self { + Self { + id: Some(id), + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_ID, + ..self + } + } + + /// Add a link + pub const fn with_link(self, link: Link) -> Self { + let mut new_flags = self.permissions_and_flags & !Self::FLAGS_LINK_MASK; + match link { + Link::Nothing => {} + Link::ToA { partition_idx } => { + assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_A_PARTITION; + new_flags |= (partition_idx as u32) << 3; + } + Link::ToOwner { partition_idx } => { + assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_OWNER; + new_flags |= (partition_idx as u32) << 3; + } + } + Self { + permissions_and_flags: new_flags, + ..self + } + } + + /// Set a flag + pub const fn with_flag(self, flag: PartitionFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Get which extra families are allowed in this partition + pub fn get_extra_families(&self) -> &[u32] { + &self.extra_families[0..self.extra_families_len] + } + + /// Get the name of the partition + /// + /// Returns `None` if there's no name, or the name is not valid UTF-8. + pub fn get_name(&self) -> Option<&str> { + let len = self.name[0] as usize; + if len == 0 { + None + } else { + core::str::from_utf8(&self.name[1..=len]).ok() + } + } + + /// Get the ID + pub fn get_id(&self) -> Option { + self.id + } + + /// Check if this partition is linked + pub fn get_link(&self) -> Link { + if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_A_PARTITION) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToA { partition_idx } + } else if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_OWNER) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToOwner { partition_idx } + } else { + Link::Nothing + } + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: PartitionFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for Partition { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a partition table. +/// +/// Don't store this as a static - make sure you convert it to a block. +#[derive(Clone)] +pub struct PartitionTableBlock { + /// This must look like a block, including the 1 word header and the 3 word footer. + contents: [u32; PARTITION_TABLE_MAX_ITEMS], + /// This value doesn't include the 1 word header or the 3 word footer + num_items: usize, +} + +impl PartitionTableBlock { + /// Create an empty Block, big enough for a partition table. + /// + /// At a minimum you need to call [`Self::add_partition_item`]. + pub const fn new() -> PartitionTableBlock { + let mut contents = [0; PARTITION_TABLE_MAX_ITEMS]; + contents[0] = BLOCK_MARKER_START; + contents[1] = item_last(0); + contents[2] = 0; + contents[3] = BLOCK_MARKER_END; + PartitionTableBlock { + contents, + num_items: 0, + } + } + + /// Add a partition to the partition table + pub const fn add_partition_item( + self, + unpartitioned: UnpartitionedSpace, + partitions: &[Partition], + ) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item header space (we fill this in later) + let header_idx = idx; + new_table.contents[idx] = 0; + idx += 1; + + // 2. unpartitioned space flags + // + // (the location of unpartition space is not recorded here - it is + // inferred because the unpartitioned space is where the partitions are + // not) + new_table.contents[idx] = unpartitioned.permissions_and_flags; + idx += 1; + + // 3. partition info + + let mut partition_no = 0; + while partition_no < partitions.len() { + // a. permissions_and_location (4K units) + new_table.contents[idx] = partitions[partition_no].permissions_and_location; + idx += 1; + + // b. permissions_and_flags + new_table.contents[idx] = partitions[partition_no].permissions_and_flags; + idx += 1; + + // c. ID + if let Some(id) = partitions[partition_no].id { + new_table.contents[idx] = id as u32; + new_table.contents[idx + 1] = (id >> 32) as u32; + idx += 2; + } + + // d. Extra Families + let mut extra_families_idx = 0; + while extra_families_idx < partitions[partition_no].extra_families_len { + new_table.contents[idx] = + partitions[partition_no].extra_families[extra_families_idx]; + idx += 1; + extra_families_idx += 1; + } + + // e. Name + let mut name_idx = 0; + while name_idx < partitions[partition_no].name[0] as usize { + let name_chunk = [ + partitions[partition_no].name[name_idx], + partitions[partition_no].name[name_idx + 1], + partitions[partition_no].name[name_idx + 2], + partitions[partition_no].name[name_idx + 3], + ]; + new_table.contents[idx] = u32::from_le_bytes(name_chunk); + name_idx += 4; + idx += 1; + } + + partition_no += 1; + } + + let len = idx - header_idx; + new_table.contents[header_idx] = + item_generic_2bs(partitions.len() as u8, len as u16, ITEM_2BS_PARTITION_TABLE); + + // 7. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a version number to the partition table + pub const fn with_version(self, major: u16, minor: u16) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item + new_table.contents[idx] = item_generic_2bs(0, 2, ITEM_1BS_VERSION); + idx += 1; + new_table.contents[idx] = (major as u32) << 16 | minor as u32; + idx += 1; + + // 2. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a a SHA256 hash of the Block + /// + /// Adds a `HASH_DEF` covering all the previous items in the Block, and a + /// `HASH_VALUE` with a SHA-256 hash of them. + pub const fn with_sha256(self) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. HASH_DEF says what is hashed + new_table.contents[idx] = item_generic_2bs(1, 2, ITEM_2BS_HASH_DEF); + idx += 1; + // we're hashing all the previous contents - including this line. + new_table.contents[idx] = (idx + 1) as u32; + idx += 1; + + // calculate hash over prior contents + let input = unsafe { + core::slice::from_raw_parts(new_table.contents.as_ptr() as *const u8, idx * 4) + }; + let hash: [u8; 32] = sha2_const_stable::Sha256::new().update(input).finalize(); + + // 2. HASH_VALUE contains the hash + new_table.contents[idx] = item_generic_2bs(0, 9, ITEM_1BS_HASH_VALUE); + idx += 1; + + let mut hash_idx = 0; + while hash_idx < hash.len() { + new_table.contents[idx] = u32::from_le_bytes([ + hash[hash_idx], + hash[hash_idx + 1], + hash[hash_idx + 2], + hash[hash_idx + 3], + ]); + idx += 1; + hash_idx += 4; + } + + // 3. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } +} + +impl Default for PartitionTableBlock { + fn default() -> Self { + Self::new() + } +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum PartitionFlag { + NotBootableArm = 1 << 9, + NotBootableRiscv = 1 << 10, + Uf2DownloadAbNonBootableOwnerAffinity = 1 << 11, + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum UnpartitionedFlag { + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyAbsolute = 1 << 15, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Kinds of linked partition +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Link { + /// Not linked to anything + Nothing, + /// This is a B partition - link to our A partition. + ToA { + /// The index of our matching A partition. + partition_idx: u8, + }, + /// Link to the partition that owns this one. + ToOwner { + /// The idx of our owner + partition_idx: u8, + }, +} + +/// Permissions that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum Permission { + /// Can be read in Secure Mode + /// + /// Corresponds to `PERMISSION_S_R_BITS` in the Pico SDK + SecureRead = 1 << 26, + /// Can be written in Secure Mode + /// + /// Corresponds to `PERMISSION_S_W_BITS` in the Pico SDK + SecureWrite = 1 << 27, + /// Can be read in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_R_BITS` in the Pico SDK + NonSecureRead = 1 << 28, + /// Can be written in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_W_BITS` in the Pico SDK + NonSecureWrite = 1 << 29, + /// Can be read in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_R_BITS` in the Pico SDK + BootRead = 1 << 30, + /// Can be written in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_W_BITS` in the Pico SDK + BootWrite = 1 << 31, +} + +impl Permission { + /// Is this permission bit set this in this bitmask? + pub const fn is_in(self, mask: u32) -> bool { + (mask & (self as u32)) != 0 + } +} + +/// The supported RP2350 CPU architectures +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Architecture { + /// Core is in Arm Cortex-M33 mode + Arm, + /// Core is in RISC-V / Hazard3 mode + Riscv, +} + +/// The kinds of Secure Boot we support +#[derive(Debug, Copy, Clone)] +pub enum Security { + /// Security mode not given + Unspecified, + /// Start in Non-Secure mode + NonSecure, + /// Start in Secure mode + Secure, +} + +/// Make an item containing a tag, 1 byte length and two extra bytes. +/// +/// The `command` arg should contain `1BS` +pub const fn item_generic_1bs(value: u16, length: u8, command: u8) -> u32 { + ((value as u32) << 16) | ((length as u32) << 8) | (command as u32) +} + +/// Make an item containing a tag, 2 byte length and one extra byte. +/// +/// The `command` arg should contain `2BS` +pub const fn item_generic_2bs(value: u8, length: u16, command: u8) -> u32 { + ((value as u32) << 24) | ((length as u32) << 8) | (command as u32) +} + +/// Create Image Type item, of type IGNORED. +pub const fn item_ignored() -> u32 { + item_generic_2bs(0, 1, ITEM_2BS_IGNORED) +} + +/// Create Image Type item, of type INVALID. +pub const fn item_image_type_invalid() -> u32 { + let value = IMAGE_TYPE_INVALID; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type DATA. +pub const fn item_image_type_data() -> u32 { + let value = IMAGE_TYPE_DATA; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type EXE. +pub const fn item_image_type_exe(security: Security, arch: Architecture) -> u32 { + let mut value = IMAGE_TYPE_EXE | IMAGE_TYPE_EXE_CHIP_RP2350; + + match arch { + Architecture::Arm => { + value |= IMAGE_TYPE_EXE_CPU_ARM; + } + Architecture::Riscv => { + value |= IMAGE_TYPE_EXE_CPU_RISCV; + } + } + + match security { + Security::Unspecified => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED, + Security::NonSecure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_NS, + Security::Secure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_S, + } + + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create a Block Last item. +pub const fn item_last(length: u16) -> u32 { + item_generic_2bs(0, length, ITEM_2BS_LAST) +} + +/// Create a Vector Table item. +/// +/// This is only allowed on Arm systems. +pub const fn item_vector_table(table_ptr: u32) -> [u32; 2] { + [item_generic_1bs(0, 2, ITEM_1BS_VECTOR_TABLE), table_ptr] +} + +/// Create an Entry Point item. +pub const fn item_entry_point(entry_point: u32, initial_sp: u32) -> [u32; 3] { + [ + item_generic_1bs(0, 3, ITEM_1BS_ENTRY_POINT), + entry_point, + initial_sp, + ] +} + +/// Create an Rolling Window item. +/// +/// The delta is the number of bytes into the image that 0x10000000 should +/// be mapped. +pub const fn item_rolling_window(delta: u32) -> [u32; 2] { + [item_generic_1bs(0, 3, ITEM_1BS_ROLLING_WINDOW_DELTA), delta] +} + +#[cfg(test)] +mod test { + use super::*; + + /// I used this JSON, with `picotool partition create`: + /// + /// ```json + /// { + /// "version": [1, 0], + /// "unpartitioned": { + /// "families": ["absolute"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// "partitions": [ + /// { + /// "name": "A", + /// "id": 0, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// { + /// "name": "B", + /// "id": 1, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// }, + /// "link": ["a", 0] + /// } + /// ] + /// } + /// ``` + #[test] + fn make_hashed_partition_table() { + let table = PartitionTableBlock::new() + .add_partition_item( + UnpartitionedSpace::new() + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_flag(UnpartitionedFlag::AcceptsDefaultFamilyAbsolute), + &[ + Partition::new(2, 512) + .with_id(0) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("A"), + Partition::new(513, 1023) + .with_id(1) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_link(Link::ToA { partition_idx: 0 }) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("B"), + ], + ) + .with_version(1, 0) + .with_sha256(); + let expected = &[ + 0xffffded3, // start + 0x02000c0a, // Item = PARTITION_TABLE + 0xfc008000, // Unpartitioned Space - permissions_and_flags + 0xfc400002, // Partition 0 - permissions_and_location (512 * 4096, 2 * 4096) + 0xfc061001, // permissions_and_flags HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000000, // ID + 0x00000000, // ID + 0x00004101, // Name ("A") + 0xfc7fe201, // Partition 1 - permissions_and_location (1023 * 4096, 513 * 4096) + 0xfc061003, // permissions_and_flags LINKA(0) | HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000001, // ID + 0x00000000, // ID + 0x00004201, // Name ("B") + 0x00000248, // Item = Version + 0x00010000, // 0, 1 + 0x01000247, // HASH_DEF with 2 words, and SHA256 hash + 0x00000011, // 17 words hashed + 0x0000094b, // HASH_VALUE with 9 words + 0x1945cdad, // Hash word 0 + 0x6b5f9773, // Hash word 1 + 0xe2bf39bd, // Hash word 2 + 0xb243e599, // Hash word 3 + 0xab2f0e9a, // Hash word 4 + 0x4d5d6d0b, // Hash word 5 + 0xf973050f, // Hash word 6 + 0x5ab6dadb, // Hash word 7 + 0x000019ff, // Last Item + 0x00000000, // Block Loop Next Offset + 0xab123579, // End + ]; + assert_eq!( + &table.contents[..29], + expected, + "{:#010x?}\n != \n{:#010x?}", + &table.contents[0..29], + expected, + ); + } +} diff --git a/rp235x-hal/src/clocks/clock_sources.rs b/rp235x-hal/src/clocks/clock_sources.rs new file mode 100644 index 000000000..ccf1398d5 --- /dev/null +++ b/rp235x-hal/src/clocks/clock_sources.rs @@ -0,0 +1,118 @@ +//! Available clocks + +use super::*; +use crate::{ + gpio::{ + bank0::{Gpio20, Gpio22}, + FunctionClock, Pin, PullNone, PullType, + }, + lposc::LowPowerOscillator, + pll::{Locked, PhaseLockedLoop}, + rosc::{Enabled as RingOscillatorEnabled, RingOscillator}, + typelevel::Sealed, + xosc::{CrystalOscillator, Stable}, +}; +use pac::{PLL_SYS, PLL_USB}; + +/// System PLL. +pub(crate) type PllSys = PhaseLockedLoop; +impl Sealed for PllSys {} +impl ClockSource for PllSys { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +/// USB PLL. +pub(crate) type PllUsb = PhaseLockedLoop; +impl Sealed for PllUsb {} +impl ClockSource for PllUsb { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +// The USB Clock Generator is a clock source +impl ClockSource for UsbClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The ADC Clock Generator is a clock source +impl ClockSource for AdcClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The HSTX Clock Generator is a clock source +impl ClockSource for HstxClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The System Clock Generator is a clock source +impl ClockSource for SystemClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The Reference Clock Generator is a clock source +impl ClockSource for ReferenceClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The Peripheral Clock Generator is a clock source +impl ClockSource for PeripheralClock { + fn get_freq(&self) -> HertzU32 { + self.frequency + } +} + +// The Low Power Oscillator is a clock source +pub(crate) type LpOsc = LowPowerOscillator; +impl ClockSource for LpOsc { + fn get_freq(&self) -> HertzU32 { + 32768.Hz() + } +} + +// The Crystal Oscillator +pub(crate) type Xosc = CrystalOscillator; +impl Sealed for Xosc {} +impl ClockSource for Xosc { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +// The Ring Oscillator +pub(crate) type Rosc = RingOscillator; +impl Sealed for Rosc {} +// We are assuming the second output is never phase shifted (see 2.17.4) +impl ClockSource for RingOscillator { + fn get_freq(&self) -> HertzU32 { + self.operating_frequency() + } +} + +// GPIN0 +pub(crate) type GPin0 = Pin; +impl ClockSource for GPin0 { + fn get_freq(&self) -> HertzU32 { + todo!() + } +} + +// GPIN1 +pub(crate) type GPin1 = Pin; +impl ClockSource for Pin { + fn get_freq(&self) -> HertzU32 { + todo!() + } +} diff --git a/rp235x-hal/src/clocks/macros.rs b/rp235x-hal/src/clocks/macros.rs new file mode 100644 index 000000000..954c6240c --- /dev/null +++ b/rp235x-hal/src/clocks/macros.rs @@ -0,0 +1,431 @@ +macro_rules! clocks { + ( + $( + $(#[$attr:meta])* + struct $name:ident { + init_freq: $init_freq:expr, + reg: $reg:ident, + $(src: {$($src:ident: $src_variant:ident),*},)? + auxsrc: {$($auxsrc:ident: $aux_variant:ident),*} + $(, div: $div:tt)? + } + )* + + ) => { + + $crate::paste::paste!{ + /// Abstraction layer providing Clock Management. + pub struct ClocksManager { + clocks: CLOCKS, + $( + #[doc = "`" $name "` field"] + pub [<$name:snake>]: $name, + )* + } + + impl ClocksManager { + /// Exchanges CLOCKS block against Self. + pub fn new(mut clocks_block: CLOCKS) -> Self { + // Disable resus that may be enabled from previous software + unsafe { + clocks_block.clk_sys_resus_ctrl().write_with_zero(|w| w); + } + + let shared_clocks = ShareableClocks::new(&mut clocks_block); + ClocksManager { + clocks: clocks_block, + $( + [<$name:snake>]: $name { + shared_dev: shared_clocks, + frequency: $init_freq.Hz(), + }, + )* + } + } + } + } + + $( + clock!( + $(#[$attr])* + struct $name { + reg: $reg, + $(src: {$($src: $src_variant),*},)? + auxsrc: {$($auxsrc: $aux_variant),*} + $(, div: $div )? + } + ); + )* + }; +} + +macro_rules! clock { + { + $(#[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + src: {$($src:ident: $src_variant:ident),*}, + auxsrc: {$($auxsrc:ident: $aux_variant:ident),*} + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $aux_variant),*}) + } + + divisible_clock!($name, $reg); + + $crate::paste::paste!{ + $(impl ValidSrc<$name> for $src { + fn is_aux(&self) -> bool{ + false + } + fn variant(&self) -> [<$reg:camel SrcType>] { + [<$reg:camel SrcType>]::Src($crate::pac::clocks::[<$reg _ctrl>]::SRC_A::$src_variant) + } + })* + + impl GlitchlessClock for $name { + type Clock = Self; + + fn await_select(&self, clock_token: &ChangingClockToken) -> nb::Result<(), Infallible> { + let shared_dev = unsafe { self.shared_dev.get() }; + + let selected = shared_dev.[<$reg _selected>]().read().bits(); + if selected != 1 << clock_token.clock_nr { + return Err(nb::Error::WouldBlock); + } + + Ok(()) + } + } + + #[doc = "Holds register value for ClockSource for `" $name "`"] + pub enum [<$reg:camel SrcType>] { + #[doc = "Contains a valid clock source register value that is to be used to set a clock as glitchless source for `" $name "`"] + Src($crate::pac::clocks::[<$reg _ctrl>]::SRC_A), + #[doc = "Contains a valid clock source register value that is to be used to set a clock as aux source for `" $name "`"] + Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A) + } + + impl [<$reg:camel SrcType>] { + fn get_clock_id(&self) -> u8 { + match self { + Self::Src(v) => *v as u8, + Self::Aux(v) => *v as u8, + } + } + + fn unwrap_src(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::SRC_A{ + match self { + Self::Src(v) => *v, + Self::Aux(_) => panic!(), + } + } + + fn unwrap_aux(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A { + match self { + Self::Src(_) => panic!(), + Self::Aux(v) => *v + } + } + } + + impl $name { + /// Reset clock back to its reset source + pub fn reset_source_await(&mut self) -> nb::Result<(), Infallible> { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_, w| { + w.src().variant(self.get_default_clock_source()) + }); + + use fugit::RateExtU32; + self.frequency = 12.MHz(); //TODO Get actual clock source.. Most likely 12 MHz though + + self.await_select(&ChangingClockToken{clock_nr:0, clock: PhantomData::}) + } + + fn set_src>(&mut self, src: &S)-> ChangingClockToken<$name> { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_,w| { + w.src().variant(src.variant().unwrap_src()) + }); + + ChangingClockToken { + clock: PhantomData::<$name>, + clock_nr: src.variant().get_clock_id(), + } + } + + fn set_self_aux_src(&mut self) -> ChangingClockToken<$name> { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.src().variant(self.get_aux_source()) + }); + + ChangingClockToken{ + clock: PhantomData::<$name>, + clock_nr: $crate::pac::clocks::clk_ref_ctrl::SRC_A::CLKSRC_CLK_REF_AUX as u8, + } + } + } + + impl Clock for $name { + type Variant = [<$reg:camel SrcType>]; + + #[doc = "Get operating frequency for `" $name "`"] + fn freq(&self) -> HertzU32 { + self.frequency + } + + #[doc = "Configure `" $name "`"] + fn configure_clock>(&mut self, src: &S, freq: HertzU32) -> Result<(), ClockError>{ + let src_freq: HertzU32 = src.get_freq().into(); + + if freq.gt(&src_freq){ + return Err(ClockError::CantIncreaseFreq); + } + + let div = fractional_div(src_freq.to_Hz(), freq.to_Hz()).ok_or(ClockError::FrequencyTooLow)?; + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > self.get_div() { + self.set_div(div); + } + + + // Set aux mux first, and then glitchless mux if this self has one + let token = if src.is_aux() { + // If switching a glitchless slice (ref or sys) to an aux source, switch + // away from aux *first* to avoid passing glitches when changing aux mux. + // Assume (!!!) glitchless source 0 is no faster than the aux source. + nb::block!(self.reset_source_await()).unwrap(); + + self.set_aux(src); + self.set_self_aux_src() + } else { + self.set_src(src) + }; + + nb::block!(self.await_select(&token)).unwrap(); + + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + self.set_div(div); + + // Store the configured frequency + use fugit::RateExtU32; + self.frequency = fractional_div(src_freq.to_Hz(), div).ok_or(ClockError::FrequencyTooHigh)?.Hz(); + + Ok(()) + } + } + } + }; + { + $( #[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + auxsrc: {$($auxsrc:ident: $variant:ident),*}, + div: false + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $variant),*}) + } + + // Just to match proper divisible clocks so we don't have to do something special in configure function + impl ClockDivision for $name { + fn set_div(&mut self, _: u32) {} + fn get_div(&self) -> u32 {1} + } + + stoppable_clock!($name, $reg); + }; + { + $( #[$attr:meta])* + struct $name:ident { + reg: $reg:ident, + auxsrc: {$($auxsrc:ident: $variant:ident),*} + } + } => { + base_clock!{ + $(#[$attr])* + ($name, $reg, auxsrc={$($auxsrc: $variant),*}) + } + + divisible_clock!($name, $reg); + stoppable_clock!($name, $reg); + }; +} + +macro_rules! divisible_clock { + ($name:ident, $reg:ident) => { + $crate::paste::paste! { + impl ClockDivision for $name { + fn set_div(&mut self, div: u32) { + unsafe { self.shared_dev.get() }.[<$reg _div>]().modify(|_, w| unsafe { + w.bits(div); + w + }); + } + fn get_div(&self) -> u32 { + unsafe { self.shared_dev.get() }.[<$reg _div>]().read().bits() + } + } + } + }; +} + +macro_rules! stoppable_clock { + ($name:ident, $reg:ident) => { + $crate::paste::paste!{ + #[doc = "Holds register value for ClockSource for `" $name "`"] + pub enum [<$reg:camel SrcType>] { + #[doc = "Contains a valid clock source register value that is to be used to set a clock as aux source for `" $name "`"] + Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A) + } + + impl [<$reg:camel SrcType>] { + fn unwrap_aux(&self) -> $crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A { + match self { + Self::Aux(v) => *v + } + } + } + + impl StoppableClock for $name { + /// Enable the clock + fn enable(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.enable().set_bit() + }); + } + + /// Disable the clock cleanly + fn disable(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.enable().clear_bit() + }); + } + + /// Disable the clock asynchronously + fn kill(&mut self) { + unsafe { self.shared_dev.get() }.[<$reg _ctrl>]().modify(|_, w| { + w.kill().set_bit() + }); + } + } + + impl Clock for $name { + type Variant = [<$reg:camel SrcType>]; + + #[doc = "Get operating frequency for `" $name "`"] + fn freq(&self) -> HertzU32 { + self.frequency + } + + #[doc = "Configure `" $name "`"] + fn configure_clock>(&mut self, src: &S, freq: HertzU32) -> Result<(), ClockError>{ + let src_freq: HertzU32 = src.get_freq().into(); + + if freq.gt(&src_freq){ + return Err(ClockError::CantIncreaseFreq); + } + + let div = fractional_div(src_freq.to_Hz(), freq.to_Hz()).ok_or(ClockError::FrequencyTooLow)?; + + // If increasing divisor, set divisor before source. Otherwise set source + // before divisor. This avoids a momentary overspeed when e.g. switching + // to a faster source and increasing divisor to compensate. + if div > self.get_div() { + self.set_div(div); + } + + // If no glitchless mux, cleanly stop the clock to avoid glitches + // propagating when changing aux mux. Note it would be a really bad idea + // to do this on one of the glitchless clocks (clk_sys, clk_ref). + + // Disable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same position. + self.disable(); + if self.frequency > HertzU32::Hz(0) { + // Delay for 3 cycles of the target clock, for ENABLE propagation. + // Note XOSC_COUNT is not helpful here because XOSC is not + // necessarily running, nor is timer... so, 3 cycles per loop: + let sys_freq = 125_000_000; // TODO get actual sys_clk frequency + let delay_cyc = sys_freq / self.frequency.to_Hz() + 1u32; + crate::arch::delay(delay_cyc); + } + + // Set aux mux first, and then glitchless mux if this self has one + self.set_aux(src); + + // Enable clock. On clk_ref and clk_sys this does nothing, + // all other clocks have the ENABLE bit in the same posi + self.enable(); + + // Now that the source is configured, we can trust that the user-supplied + // divisor is a safe value. + self.set_div(div); + + // Store the configured frequency + use fugit::RateExtU32; + self.frequency = fractional_div(src_freq.to_Hz(), div).ok_or(ClockError::FrequencyTooHigh)?.Hz(); + + Ok(()) + } + } + } + }; +} + +macro_rules! base_clock { + { + $(#[$attr:meta])* + ($name:ident, $reg:ident, auxsrc={$($auxsrc:ident: $variant:ident),*}) + } => { + $crate::paste::paste!{ + + $(impl ValidSrc<$name> for $auxsrc { + + fn is_aux(&self) -> bool{ + true + } + fn variant(&self) -> [<$reg:camel SrcType>] { + [<$reg:camel SrcType>]::Aux($crate::pac::clocks::[<$reg _ctrl>]::AUXSRC_A::$variant) + } + })* + + $(#[$attr])* + pub struct $name { + shared_dev: ShareableClocks, + frequency: HertzU32, + } + + impl $name { + fn set_aux>(&mut self, src: &S) { + let shared_dev = unsafe { self.shared_dev.get() }; + + shared_dev.[<$reg _ctrl>]().modify(|_,w| { + w.auxsrc().variant(src.variant().unwrap_aux()) + }); + } + } + + impl Sealed for $name {} + + impl From<&$name> for HertzU32 + { + fn from(value: &$name) -> HertzU32 { + value.frequency + } + } + } + }; +} diff --git a/rp235x-hal/src/clocks/mod.rs b/rp235x-hal/src/clocks/mod.rs new file mode 100644 index 000000000..f9d0c6534 --- /dev/null +++ b/rp235x-hal/src/clocks/mod.rs @@ -0,0 +1,746 @@ +//! Clocks (CLOCKS) +//! +//! +//! +//! ## Usage simple +//! ```no_run +//! use rp235x_hal::{self as hal, clocks::init_clocks_and_plls, watchdog::Watchdog}; +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! peripherals.XOSC, +//! peripherals.CLOCKS, +//! peripherals.PLL_SYS, +//! peripherals.PLL_USB, +//! &mut peripherals.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! ``` +//! +//! ## Usage extended +//! ```no_run +//! use fugit::RateExtU32; +//! use rp235x_hal::{clocks::{Clock, ClocksManager, ClockSource, InitError}, gpio::Pins, self as hal, pll::{common_configs::{PLL_SYS_150MHZ, PLL_USB_48MHZ}, setup_pll_blocking}, Sio, watchdog::Watchdog, xosc::setup_xosc_blocking}; +//! +//! # fn func() -> Result<(), InitError> { +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! // Enable the xosc +//! let xosc = setup_xosc_blocking(peripherals.XOSC, XOSC_CRYSTAL_FREQ.Hz()).map_err(InitError::XoscErr)?; +//! +//! // Start tick in watchdog +//! watchdog.enable_tick_generation((XOSC_CRYSTAL_FREQ / 1_000_000) as u16); +//! +//! let mut clocks = ClocksManager::new(peripherals.CLOCKS); +//! +//! // Configure PLLs +//! // REF FBDIV VCO POSTDIV +//! // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 5 / 2 = 150MHz +//! // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz +//! let pll_sys = setup_pll_blocking(peripherals.PLL_SYS, xosc.operating_frequency().into(), PLL_SYS_150MHZ, &mut clocks, &mut peripherals.RESETS).map_err(InitError::PllError)?; +//! let pll_usb = setup_pll_blocking(peripherals.PLL_USB, xosc.operating_frequency().into(), PLL_USB_48MHZ, &mut clocks, &mut peripherals.RESETS).map_err(InitError::PllError)?; +//! +//! // Configure clocks +//! // CLK_REF = XOSC (12MHz) / 1 = 12MHz +//! clocks.reference_clock.configure_clock(&xosc, xosc.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK SYS = PLL SYS (150MHz) / 1 = 150MHz +//! clocks.system_clock.configure_clock(&pll_sys, pll_sys.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK USB = PLL USB (48MHz) / 1 = 48MHz +//! clocks.usb_clock.configure_clock(&pll_usb, pll_usb.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz +//! clocks.adc_clock.configure_clock(&pll_usb, pll_usb.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK HSTX = PLL SYS (150MHz) / 1 = 150MHz +//! clocks.hstx_clock.configure_clock(&pll_sys, pll_sys.get_freq()).map_err(InitError::ClockError)?; +//! +//! // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable +//! // Normally choose clk_sys or clk_usb +//! clocks.peripheral_clock.configure_clock(&clocks.system_clock, clocks.system_clock.freq()).map_err(InitError::ClockError)?; +//! # Ok(()) +//! # } +//! ``` +//! +//! See [Chapter 8](https://rptl.io/rp2350-datasheet#section_clocks) for more details. + +use core::{convert::Infallible, marker::PhantomData}; +use fugit::{HertzU32, RateExtU32}; + +use crate::{ + pac::{self, CLOCKS, PLL_SYS, PLL_USB, RESETS, XOSC}, + pll::{ + common_configs::{PLL_SYS_150MHZ, PLL_USB_48MHZ}, + setup_pll_blocking, Error as PllError, Locked, PhaseLockedLoop, + }, + typelevel::Sealed, + watchdog::Watchdog, + xosc::{setup_xosc_blocking, CrystalOscillator, Error as XoscError, Stable}, +}; + +#[macro_use] +mod macros; +mod clock_sources; + +use clock_sources::PllSys; + +use self::clock_sources::{GPin0, GPin1, LpOsc, PllUsb, Rosc, Xosc}; + +bitfield::bitfield! { + /// Bit field mapping clock enable bits. + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Default)] + pub struct ClockGate(u64); + /// Clock gate to the clock controller. + pub sys_clock, set_sys_clock: 0; + /// Clock gate the hardware access control registers + pub sys_accessctrl, set_sys_accessctrl: 1; + /// Clock gate to the adc analog logic. + pub adc_adc, set_adc_adc: 2; + /// Clock gate to the adc peripheral. + pub sys_adc, set_sys_adc: 3; + /// Clock gate the Boot RAM + pub sys_bootram, set_sys_bootram: 4; + /// Clock gate the memory bus controller. + pub sys_busctrl, set_sys_busctrl: 5; + /// Clock gate the memory bus fabric. + pub sys_busfabric, set_sys_busfabric: 6; + /// Clock gate the dma controller. + pub sys_dma, set_sys_dma: 7; + /// Clock gate the Glitch Detector peripheral + pub sys_glitch_detector, set_sys_glitch_detector: 8; + /// Clock gate the High-Speed TX peripheral + pub hstx, set_hstx: 9; + /// Clock gate the High-Speed TX peripheral + pub sys_hstx, set_sys_hstx: 10; + /// Clock gate I2C0. + pub sys_i2c0, set_sys_i2c0: 11; + /// Clock gate I2C1. + pub sys_i2c1, set_sys_i2c1: 12; + /// Clock gate the IO controller. + pub sys_io, set_sys_io: 13; + /// Clock gate the JTAG peripheral. + pub sys_jtag, set_sys_jtag: 14; + /// Clock gate the OTP (one-time programmable memory) peripheral + pub ref_otp, set_ref_otp: 15; + /// Clock gate the OTP (one-time programmable memory) peripheral + pub sys_otp, set_sys_otp: 16; + /// Clock gate pad controller. + pub sys_pads, set_sys_pads: 17; + /// Clock gate PIO0 peripheral. + pub sys_pio0, set_sys_pio0: 18; + /// Clock gate PIO1 peripheral. + pub sys_pio1, set_sys_pio1: 19; + /// Clock gate PIO2 peripheral. + pub sys_pio2, set_sys_pio2: 20; + /// Clock gate the system PLL. + pub sys_pll_sys, set_sys_pll_sys: 21; + /// Clock gate the USB PLL. + pub sys_pll_usb, set_sys_pll_usb: 22; + /// Clock gate the Power Manager + pub ref_powman, set_ref_powman: 23; + /// Clock gate the Power Manager + pub sys_powman, set_sys_powman: 24; + /// Clock gate PWM peripheral. + pub sys_pwm, set_sys_pwm: 25; + /// Clock gate the reset controller. + pub sys_resets, set_sys_resets: 26; + /// Clock gate the ROM. + pub sys_rom, set_sys_rom: 27; + /// Clock gate the ROSC controller (not the rosc itself). + pub sys_rosc, set_sys_rosc: 28; + /// Clock gate the Power State Machine + pub sys_psm, set_sys_psm: 29; + /// Clock gate the SHA256 peripheral + pub sys_sha256, set_sys_sha256: 30; + /// Clock gate the SIO controller. + pub sys_sio, set_sys_sio: 31; + + /// Clock gate SPI0's baud generation. + pub peri_spi0, set_peri_spi0: 32; + /// Clock gate SPI0's controller.. + pub sys_spi0, set_sys_spi0: 33; + /// Clock gate SPI1's baud generation. + pub peri_spi1, set_peri_spi1: 34; + /// Clock gate SPI1's controller.. + pub sys_spi1, set_sys_spi1: 35; + /// Clock gate SRAM0. + pub sys_sram0, set_sys_sram0: 36; + /// Clock gate SRAM1. + pub sys_sram1, set_sys_sram1: 37; + /// Clock gate SRAM2. + pub sys_sram2, set_sys_sram2: 38; + /// Clock gate SRAM3. + pub sys_sram3, set_sys_sram3: 39; + /// Clock gate SRAM4. + pub sys_sram4, set_sys_sram4: 40; + /// Clock gate SRAM5. + pub sys_sram5, set_sys_sram5: 41; + /// Clock gate SRAM6 + pub sys_sram6, set_sys_sram6: 42; + /// Clock gate SRAM7 + pub sys_sram7, set_sys_sram7: 43; + /// Clock gate SRAM8 + pub sys_sram8, set_sys_sram8: 44; + /// Clock gate SRAM9 + pub sys_sram9, set_sys_sram9: 45; + /// Clock gate the system configuration controller. + pub sys_syscfg, set_sys_syscfg: 46; + /// Clock gate the system information peripheral. + pub sys_sysinfo, set_sys_sysinfo: 47; + /// Clock gate the test bench manager. + pub sys_tbman, set_sys_tbman: 48; + /// Clock gate the tick generator + pub ref_ticks, set_ref_ticks: 49; + /// Clock gate the tick generator + pub sys_ticks, set_sys_ticks: 50; + /// Clock gate the Timer 0 peripheral. + pub sys_timer0, set_sys_timer0: 51; + /// Clock gate the Timer 1 peripheral. + pub sys_timer1, set_sys_timer1: 52; + /// Clock gate the TrustZone Random Number Generator + pub sys_trng, set_sys_trng: 53; + /// Clock gate UART0's baud generation. + pub peri_uart0, set_peri_uart0: 54; + /// Clock gate UART0's controller. + pub sys_uart0, set_sys_uart0: 55; + /// Clock gate UART1's baud generation. + pub peri_uart1, set_peri_uart1: 56; + /// Clock gate UART1's controller. + pub sys_uart1, set_sys_uart1: 57; + /// Clock gate the USB controller. + pub sys_usbctrl, set_sys_usbctrl: 58; + /// Clock gate the USB logic. + pub usb, set_usb: 59; + /// Clock gate the Watchdog controller. + pub sys_watchdog, set_sys_watchdog: 60; + /// .Clock gate the XIP controller. + pub sys_xip, set_sys_xip: 61; + /// Clock gate the XOSC controller (not xosc itself). + pub sys_xosc, set_sys_xosc: 62; +} + +impl core::fmt::Debug for ClockGate { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ClockGate") + .field("sys_clock", &self.sys_clock()) + .field("sys_accessctrl", &self.sys_accessctrl()) + .field("adc_adc", &self.adc_adc()) + .field("sys_adc", &self.sys_adc()) + .field("sys_bootram", &self.sys_bootram()) + .field("sys_busctrl", &self.sys_busctrl()) + .field("sys_busfabric", &self.sys_busfabric()) + .field("sys_dma", &self.sys_dma()) + .field("sys_glitch_detector", &self.sys_glitch_detector()) + .field("hstx", &self.hstx()) + .field("sys_hstx", &self.sys_hstx()) + .field("sys_i2c0", &self.sys_i2c0()) + .field("sys_i2c1", &self.sys_i2c1()) + .field("sys_io", &self.sys_io()) + .field("sys_jtag", &self.sys_jtag()) + .field("ref_otp", &self.ref_otp()) + .field("sys_otp", &self.sys_otp()) + .field("sys_pads", &self.sys_pads()) + .field("sys_pio0", &self.sys_pio0()) + .field("sys_pio1", &self.sys_pio1()) + .field("sys_pio2", &self.sys_pio2()) + .field("sys_pll_sys", &self.sys_pll_sys()) + .field("sys_pll_usb", &self.sys_pll_usb()) + .field("ref_powman", &self.ref_powman()) + .field("sys_powman", &self.sys_powman()) + .field("sys_pwm", &self.sys_pwm()) + .field("sys_resets", &self.sys_resets()) + .field("sys_rom", &self.sys_rom()) + .field("sys_rosc", &self.sys_rosc()) + .field("sys_psm", &self.sys_psm()) + .field("sys_sha256", &self.sys_sha256()) + .field("sys_sio", &self.sys_sio()) + .field("peri_spi0", &self.peri_spi0()) + .field("sys_spi0", &self.sys_spi0()) + .field("peri_spi1", &self.peri_spi1()) + .field("sys_spi1", &self.sys_spi1()) + .field("sys_sram0", &self.sys_sram0()) + .field("sys_sram1", &self.sys_sram1()) + .field("sys_sram2", &self.sys_sram2()) + .field("sys_sram3", &self.sys_sram3()) + .field("sys_sram4", &self.sys_sram4()) + .field("sys_sram5", &self.sys_sram5()) + .field("sys_sram6", &self.sys_sram6()) + .field("sys_sram7", &self.sys_sram7()) + .field("sys_sram8", &self.sys_sram8()) + .field("sys_sram9", &self.sys_sram9()) + .field("sys_syscfg", &self.sys_syscfg()) + .field("sys_sysinfo", &self.sys_sysinfo()) + .field("sys_tbman", &self.sys_tbman()) + .field("ref_ticks", &self.ref_ticks()) + .field("sys_ticks", &self.sys_ticks()) + .field("sys_timer0", &self.sys_timer0()) + .field("sys_timer1", &self.sys_timer1()) + .field("sys_trng", &self.sys_trng()) + .field("peri_uart0", &self.peri_uart0()) + .field("sys_uart0", &self.sys_uart0()) + .field("peri_uart1", &self.peri_uart1()) + .field("sys_uart1", &self.sys_uart1()) + .field("sys_usbctrl", &self.sys_usbctrl()) + .field("usb", &self.usb()) + .field("sys_watchdog", &self.sys_watchdog()) + .field("sys_xip", &self.sys_xip()) + .field("sys_xosc", &self.sys_xosc()) + .finish() + } +} + +#[derive(Copy, Clone)] +/// Provides refs to the CLOCKS block. +struct ShareableClocks { + _internal: (), +} + +impl ShareableClocks { + fn new(_clocks: &mut CLOCKS) -> Self { + ShareableClocks { _internal: () } + } + + unsafe fn get(&self) -> &pac::clocks::RegisterBlock { + &*CLOCKS::ptr() + } +} + +/// Something when wrong setting up the clock +#[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockError { + /// The frequency desired is higher than the source frequency + CantIncreaseFreq, + /// The desired frequency is to high (would overflow an u32) + FrequencyTooHigh, + /// The desired frequency is too low (divider can't reach the desired value) + FrequencyTooLow, +} + +/// For clocks +pub trait Clock: Sealed + Sized { + /// Enum with valid source clocks register values for `Clock` + type Variant; + + /// Get operating frequency + fn freq(&self) -> HertzU32; + + /// Configure this clock based on a clock source and desired frequency + fn configure_clock>( + &mut self, + src: &S, + freq: HertzU32, + ) -> Result<(), ClockError>; +} + +/// For clocks with a divider +trait ClockDivision { + /// Set integer divider value. + fn set_div(&mut self, div: u32); + /// Get integer diveder value. + fn get_div(&self) -> u32; +} + +/// Clock with glitchless source +trait GlitchlessClock { + /// Self type to hand to ChangingClockToken + type Clock: Clock; + + /// Await switching clock sources without glitches. Needs a token that is returned when setting + fn await_select( + &self, + clock_token: &ChangingClockToken, + ) -> nb::Result<(), Infallible>; +} + +/// Token which can be used to await the glitchless switch +pub struct ChangingClockToken { + clock_nr: u8, + clock: PhantomData, +} + +/// For clocks that can be disabled +pub trait StoppableClock: Sealed { + /// Enables the clock. + fn enable(&mut self); + + /// Disables the clock. + fn disable(&mut self); + + /// Kills the clock. + fn kill(&mut self); +} + +/// Trait for things that can be used as clock source +pub trait ClockSource: Sealed { + /// Get the operating frequency for this source + /// + /// Used to determine the divisor + fn get_freq(&self) -> HertzU32; +} + +/// Trait to contrain which ClockSource is valid for which Clock +pub trait ValidSrc: Sealed + ClockSource { + /// Is this a ClockSource for src or aux? + fn is_aux(&self) -> bool; + /// Get register value for this ClockSource + fn variant(&self) -> C::Variant; +} + +clocks! { + /// GPIO Output 0 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput0Clock { + init_freq: 0, + reg: clk_gpout0, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// GPIO Output 1 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput1Clock { + init_freq: 0, + reg: clk_gpout1, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// GPIO Output 2 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput2Clock { + init_freq: 0, + reg: clk_gpout2, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// GPIO Output 3 Clock Generator + /// + /// Clock output to GPIO. Can be used to clock external devices or debug on + /// chip clocks with a logic analyser or oscilloscope. + struct GpioOutput3Clock { + init_freq: 0, + reg: clk_gpout3, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1, + PllUsb: CLKSRC_PLL_USB, + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC, + SystemClock: CLK_SYS, + UsbClock: CLK_USB, + AdcClock: CLK_ADC, + ReferenceClock: CLK_REF, + PeripheralClock: CLK_PERI, + HstxClock: CLK_HSTX + /* OTP_CLK2FC */ + } + } + /// Reference clock that is always running 6 - 12MHz unless in DORMANT mode. + /// + /// Runs from Ring Oscillator (ROSC) at power-up but can be switched to + /// Crystal Oscillator (XOSC) for more accuracy. + struct ReferenceClock { + init_freq: 12_000_000, + // Starts from ROSC which actually varies with input voltage etc, + // but 12 MHz seems to be a good value + reg: clk_ref, + src: { + Rosc: ROSC_CLKSRC_PH, + /* Aux: CLKSRC_CLK_REF_AUX, */ + Xosc: XOSC_CLKSRC, + LpOsc: LPOSC_CLKSRC + }, + auxsrc: { + PllUsb:CLKSRC_PLL_USB, + GPin0:CLKSRC_GPIN0, + GPin1:CLKSRC_GPIN1 + /* CLKSRC_PLL_USB_PRIMARY_REF_OPCG */ + } + } + /// System clock that is always running unless in DORMANT mode. + /// + /// Runs from clk_ref at power-up but is typically switched to a PLL. + struct SystemClock { + init_freq: 12_000_000, // ref_clk is 12 MHz + reg: clk_sys, + src: { + ReferenceClock: CLK_REF, + SystemClock: CLKSRC_CLK_SYS_AUX + }, + auxsrc: { + PllSys: CLKSRC_PLL_SYS, + PllUsb: CLKSRC_PLL_USB, + Rosc: ROSC_CLKSRC, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } + /// Peripheral clock. + /// + /// Typically runs from 12 - 150MHz clk_sys but allows peripherals to run at + /// a consistent speed if clk_sys is changed by software. + struct PeripheralClock { + init_freq: 12_000_000, // sys_clk is 12 MHz + reg: clk_peri, + auxsrc: { + SystemClock: CLK_SYS, + PllSys: CLKSRC_PLL_SYS, + PllUsb:CLKSRC_PLL_USB, + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + }, + div: false + } + /// HSTX (High-Speed Transmitter) Clock + struct HstxClock { + init_freq: 0, + reg: clk_hstx, + auxsrc: { + SystemClock: CLK_SYS, + PllUsb: CLKSRC_PLL_USB, + PllSys: CLKSRC_PLL_SYS, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } + /// USB reference clock. + /// + /// Must be 48MHz. + struct UsbClock { + init_freq: 0, + reg: clk_usb, + auxsrc: { + PllUsb: CLKSRC_PLL_USB, + PllSys: CLKSRC_PLL_SYS, + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } + /// ADC reference clock. + /// + /// Must be 48MHz. + struct AdcClock { + init_freq: 0, + reg: clk_adc, + auxsrc: { + PllUsb: CLKSRC_PLL_USB, + PllSys: CLKSRC_PLL_SYS, + Rosc: ROSC_CLKSRC_PH, + Xosc: XOSC_CLKSRC, + GPin0: CLKSRC_GPIN0, + GPin1: CLKSRC_GPIN1 + } + } +} + +impl SystemClock { + fn get_default_clock_source(&self) -> pac::clocks::clk_sys_ctrl::SRC_A { + pac::clocks::clk_sys_ctrl::SRC_A::CLK_REF + } + + fn get_aux_source(&self) -> pac::clocks::clk_sys_ctrl::SRC_A { + pac::clocks::clk_sys_ctrl::SRC_A::CLKSRC_CLK_SYS_AUX + } +} + +impl ReferenceClock { + fn get_default_clock_source(&self) -> pac::clocks::clk_ref_ctrl::SRC_A { + pac::clocks::clk_ref_ctrl::SRC_A::ROSC_CLKSRC_PH + } + + fn get_aux_source(&self) -> pac::clocks::clk_ref_ctrl::SRC_A { + pac::clocks::clk_ref_ctrl::SRC_A::CLKSRC_CLK_REF_AUX + } +} + +impl ClocksManager { + /// Initialize the clocks to a sane default + pub fn init_default( + &mut self, + + xosc: &CrystalOscillator, + pll_sys: &PhaseLockedLoop, + pll_usb: &PhaseLockedLoop, + ) -> Result<(), ClockError> { + // Configure clocks + // CLK_REF = XOSC (12MHz) / 1 = 12MHz + self.reference_clock + .configure_clock(xosc, xosc.get_freq())?; + + // CLK SYS = PLL SYS (150MHz) / 1 = 150MHz + self.system_clock + .configure_clock(pll_sys, pll_sys.get_freq())?; + + // CLK USB = PLL USB (48MHz) / 1 = 48MHz + self.usb_clock + .configure_clock(pll_usb, pll_usb.get_freq())?; + + // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz + self.adc_clock + .configure_clock(pll_usb, pll_usb.get_freq())?; + + // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable + // Normally choose clk_sys or clk_usb + self.peripheral_clock + .configure_clock(&self.system_clock, self.system_clock.freq()) + } + + /// Configure the clocks staying ON during deep-sleep. + pub fn configure_sleep_enable(&mut self, clock_gate: ClockGate) { + self.clocks + .sleep_en0() + .write(|w| unsafe { w.bits(clock_gate.0 as u32) }); + self.clocks + .sleep_en1() + .write(|w| unsafe { w.bits((clock_gate.0 >> 32) as u32) }); + } + + /// Read the clock gate configuration while the device is in its (deep) sleep state. + pub fn sleep_enable(&self) -> ClockGate { + ClockGate( + (u64::from(self.clocks.sleep_en1().read().bits()) << 32) + | u64::from(self.clocks.sleep_en0().read().bits()), + ) + } + + /// Read the clock gate configuration while the device is in its wake state. + pub fn wake_enable(&self) -> ClockGate { + ClockGate( + (u64::from(self.clocks.wake_en1().read().bits()) << 32) + | u64::from(self.clocks.wake_en0().read().bits()), + ) + } + + /// Releases the CLOCKS block + pub fn free(self) -> CLOCKS { + self.clocks + } +} + +/// Possible init errors +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InitError { + /// Something went wrong setting up the Xosc + XoscErr(XoscError), + /// Something went wrong setting up the Pll + PllError(PllError), + /// Something went wrong setting up the Clocks + ClockError(ClockError), +} + +/// Initialize the clocks and plls according to the reference implementation +pub fn init_clocks_and_plls( + xosc_crystal_freq: u32, + xosc_dev: XOSC, + clocks_dev: CLOCKS, + pll_sys_dev: PLL_SYS, + pll_usb_dev: PLL_USB, + resets: &mut RESETS, + watchdog: &mut Watchdog, +) -> Result { + let xosc = setup_xosc_blocking(xosc_dev, xosc_crystal_freq.Hz()).map_err(InitError::XoscErr)?; + + // Configure watchdog tick generation to tick over every microsecond + watchdog.enable_tick_generation((xosc_crystal_freq / 1_000_000) as u16); + + let mut clocks = ClocksManager::new(clocks_dev); + + let pll_sys = setup_pll_blocking( + pll_sys_dev, + xosc.operating_frequency(), + PLL_SYS_150MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + let pll_usb = setup_pll_blocking( + pll_usb_dev, + xosc.operating_frequency(), + PLL_USB_48MHZ, + &mut clocks, + resets, + ) + .map_err(InitError::PllError)?; + + clocks + .init_default(&xosc, &pll_sys, &pll_usb) + .map_err(InitError::ClockError)?; + Ok(clocks) +} + +// Calculates (numerator<<16)/denominator. +fn fractional_div(numerator: u32, denominator: u32) -> Option { + let numerator: u64 = u64::from(numerator) * 65536; + let denominator: u64 = u64::from(denominator); + let result = numerator / denominator; + result.try_into().ok() +} diff --git a/rp235x-hal/src/critical_section_impl.rs b/rp235x-hal/src/critical_section_impl.rs new file mode 100644 index 000000000..abe0a8e83 --- /dev/null +++ b/rp235x-hal/src/critical_section_impl.rs @@ -0,0 +1,91 @@ +use core::sync::atomic::{AtomicU8, Ordering}; + +struct RpSpinlockCs; +critical_section::set_impl!(RpSpinlockCs); + +/// Marker value to indicate no-one has the lock. +/// +/// Initialising `LOCK_OWNER` to 0 means cheaper static initialisation so it's the best choice +const LOCK_UNOWNED: u8 = 0; + +/// Indicates which core owns the lock so that we can call critical_section recursively. +/// +/// 0 = no one has the lock, 1 = core0 has the lock, 2 = core1 has the lock +static LOCK_OWNER: AtomicU8 = AtomicU8::new(LOCK_UNOWNED); + +/// Marker value to indicate that we already owned the lock when we started the `critical_section`. +/// +/// Since we can't take the spinlock when we already have it, we need some other way to keep track of `critical_section` ownership. +/// `critical_section` provides a token for communicating between `acquire` and `release` so we use that. +/// If we're the outermost call to `critical_section` we use the values 0 and 1 to indicate we should release the spinlock and set the interrupts back to disabled and enabled, respectively. +/// The value 2 indicates that we aren't the outermost call, and should not release the spinlock or re-enable interrupts in `release` +const LOCK_ALREADY_OWNED: u8 = 2; + +unsafe impl critical_section::Impl for RpSpinlockCs { + unsafe fn acquire() -> u8 { + RpSpinlockCs::acquire() + } + + unsafe fn release(token: u8) { + RpSpinlockCs::release(token); + } +} + +impl RpSpinlockCs { + unsafe fn acquire() -> u8 { + // Store the initial interrupt state and current core id in stack variables + let interrupts_active = crate::arch::interrupts_enabled(); + // We reserved 0 as our `LOCK_UNOWNED` value, so add 1 to core_id so we get 1 for core0, 2 for core1. + let core = crate::Sio::core() as u8 + 1_u8; + // Do we already own the spinlock? + if LOCK_OWNER.load(Ordering::Acquire) == core { + // We already own the lock, so we must have called acquire within a critical_section. + // Return the magic inner-loop value so that we know not to re-enable interrupts in release() + LOCK_ALREADY_OWNED + } else { + // Spin until we get the lock + loop { + // Need to disable interrupts to ensure that we will not deadlock + // if an interrupt enters critical_section::Impl after we acquire the lock + crate::arch::interrupt_disable(); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Read the spinlock reserved for `critical_section` + if let Some(lock) = crate::sio::Spinlock31::try_claim() { + // We just acquired the lock. + // 1. Forget it, so we don't immediately unlock + core::mem::forget(lock); + // 2. Store which core we are so we can tell if we're called recursively + LOCK_OWNER.store(core, Ordering::Relaxed); + break; + } + // We didn't get the lock, enable interrupts if they were enabled before we started + if interrupts_active { + crate::arch::interrupt_enable(); + } + } + // If we broke out of the loop we have just acquired the lock + // As the outermost loop, we want to return the interrupt status to restore later + interrupts_active as _ + } + } + + unsafe fn release(token: u8) { + // Did we already own the lock at the start of the `critical_section`? + if token != LOCK_ALREADY_OWNED { + // No, it wasn't owned at the start of this `critical_section`, so this core no longer owns it. + // Set `LOCK_OWNER` back to `LOCK_UNOWNED` to ensure the next critical section tries to obtain the spinlock instead + LOCK_OWNER.store(LOCK_UNOWNED, Ordering::Relaxed); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Release the spinlock to allow others to enter critical_section again + crate::sio::Spinlock31::release(); + // Re-enable interrupts if they were enabled when we first called acquire() + // We only do this on the outermost `critical_section` to ensure interrupts stay disabled + // for the whole time that we have the lock + if token != 0 { + crate::arch::interrupt_enable(); + } + } + } +} diff --git a/rp235x-hal/src/dcp.rs b/rp235x-hal/src/dcp.rs new file mode 100644 index 000000000..0abe04cac --- /dev/null +++ b/rp235x-hal/src/dcp.rs @@ -0,0 +1,159 @@ +//! Double-Co-Pro Support +//! +//! The Double-Co-Pro is a very simple sort-of-FPU. It doesn't execute Arm FPU +//! instructions like the Cortex-M33F's built-in FPU does. But it can perform +//! some basic operations that can greatly speed up a pure-software FPU library. +//! We can: +//! +//! * copy 64-bit values in to the co-pro with an `mrrc` instruction +//! * copy 64-bit values out of the co-pro with an `mcrr` instruction +//! * trigger the co-pro to do a thing with a `cdp` instruction +//! +//! We provide some functions here which can do accelerated FPU operations. The +//! assembly is largely take from the Raspberry Pi Pico SDK. +//! +//! If you enable the `dcp-fast-f64` feature, we will replace the standard +//! `__aeabi_dadd` and `__aeabi_dmul` functions with versions that complete +//! around 3x to 6x faster. + +/// Perform a fast add of two f64 values, using the DCP. +pub fn dadd(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_add(x, y) } +} + +#[cfg(feature = "dcp-fast-f64")] +#[no_mangle] +/// Replace the built-in `__aeabi_dadd` function with one that uses the DCP. +extern "aapcs" fn __aeabi_dadd(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_add(x, y) } +} + +/// Perform a fast multiply of two f64 values, using the DCP. +pub fn dmul(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_mul(x, y) } +} + +#[cfg(feature = "dcp-fast-f64")] +#[no_mangle] +/// Replace the built-in `__aeabi_dmul` function with one that uses the DCP. +extern "aapcs" fn __aeabi_dmul(x: f64, y: f64) -> f64 { + unsafe { _aapcs_dcp_mul(x, y) } +} + +extern "aapcs" { + fn _aapcs_dcp_mul(x: f64, y: f64) -> f64; + fn _aapcs_dcp_add(x: f64, y: f64) -> f64; +} + +core::arch::global_asm!( + r#" + .syntax unified + .cpu cortex-m33 + .cfi_sections .debug_frame + .thumb + "#, + // Do a fast f64 multiply on the DCP. + // + // Assumes the DCP has been enabled in the CPACR register. + // + // * First f64 is in r0,r1 + // * Second f64 is in r2,r3 + // * Result is in r0,r1 + // + // This function is Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + // SPDX-License-Identifier: BSD-3-Clause + r#" + .global _aapcs_dcp_mul + .type _aapcs_dcp_mul,%function + .cfi_startproc + .p2align 2 + 1: + push {{lr}} // Save LR + bl generic_save_state // Run the save/restore routine + b 1f // Jump forward to run the routine + _aapcs_dcp_mul: + mrc2 p4,#0,apsr_nzcv,c0,c0,#1 // PCMP apsr_nzcv - If DCP is running, set the Negative flag + bmi 1b // Branch back if minus (i.e. negative flag is set) + 1: // Usually we fall right through, which is fast + push {{r4,r14}} // Save the callee-save temp registers we are using + mcrr p4,#1,r0,r1,c0 // WXUP r0,r1 - write r1|r0 to xm and xe + mcrr p4,#1,r2,r3,c1 // WYUP r2,r3 - write r3|r2 to ym and ye + mrrc p4,#0,r0,r1,c4 // RXMS r0,r1,0 - read xm Q62-s + mrrc p4,#0,r2,r3,c5 // RYMS r2,r3,0 - read xy Q62-s + umull r4,r12,r0,r2 // Unsigned Long Multiply r12|r4 := r0*r2 + movs r14,#0 // r14 = 0 + umlal r12,r14,r0,r3 // Unsigned Long Multiply with Accumulate r14|r12 += r0*r3 + umlal r12,r14,r1,r2 // Unsigned Long Multiply with Accumulate r14|r12 += r1*r2 + mcrr p4,#2,r4,r12,c0 // WXMS r4,r12 - set xm bit 0 if r12|r4 is 0, or 1 if r12|r4 is non-zero + movs r4,#0 // r4 = 0 + umlal r14,r4,r1,r3 // Unsigned Long Multiply with Accumulate r4|r14 += r1*r3 + mcrr p4,#3,r14,r4,c0 // WXMO r14,r4 - write xm direct OR into b0, add exponents, XOR signs + cdp p4,#8,c0,c0,c0,#1 // NRDD - normalise and round double-precision + mrrc p4,#5,r0,r1,c0 // RDDM r0,r1 - read DMUL result packed from xm and xe + pop {{r4,lr}} // Restore the callee-save temp registers we are using + bx lr // Return to caller + .cfi_endproc + .size _aapcs_dcp_mul, . - _aapcs_dcp_mul + "#, + // Do a fast f64 add on the DCP. + // + // Assumes the DCP has been enabled in the CPACR register. + // + // * First f64 is in r0,r1 + // * Second f64 is in r2,r3 + // * Result is in r0,r1 + // + // This function is Copyright (c) 2024 Raspberry Pi (Trading) Ltd. + // SPDX-License-Identifier: BSD-3-Clause + r#" + .global _aapcs_dcp_add + .type _aapcs_dcp_add,%function + .cfi_startproc + .p2align 2 + 1: + push {{lr}} // Save LR + bl generic_save_state // Run the save/restore routine + b 1f // Jump forward to run the routine + _aapcs_dcp_add: + mrc2 p4,#0,apsr_nzcv,c0,c0,#1 // PCMP apsr_nzcv - If DCP is running, set the Negative flag + bmi 1b // Branch back if minus (i.e. negative flag is set) + 1: // Usually we fall right through, which is fast + mcrr p4,#1,r0,r1,c0 // WXUP r0,r1 - write r1|r0 to xm and xe + mcrr p4,#1,r2,r3,c1 // WYUP r2,r3 - write r3|r2 to ym and ye + cdp p4,#0,c0,c0,c1,#0 // ADD0 - compare X-Y, set status + cdp p4,#1,c0,c0,c1,#0 // ADD1 - xm := ±xm+±ym>>s or ±ym+±xm>>s + cdp p4,#8,c0,c0,c0,#1 // NRDD - normalise and round double-precision + mrrc p4,#1,r0,r1,c0 // RDDA r0,r1 - read DADD result packed from xm and xe + bx lr // Return to caller (or to state restore routine) + .cfi_endproc + .size _aapcs_dcp_add, . - _aapcs_dcp_add + "#, + // This code grabs the DCP state and pushes it to the stack. + // It then does a subroutine call back to where it came from + // and when that subroutine finishes (the original function we were + // trying to call), then we return here and restore the DCP state before + // actually returning to the caller. + r#" + .thumb_func + generic_save_state: + sub sp, #24 + push {{r0, r1}} + // save DCP state here + mrrc2 p4,#0,r0,r1,c8 // PXMD r0, r1 + strd r0, r1, [sp, #8 + 0] + mrrc2 p4,#0,r0,r1,c9 // PYMD r0, r1 + strd r0, r1, [sp, #8 + 8] + mrrc p4,#0,r0,r1,c10 // REFD r0, r1 + strd r0, r1, [sp, #8 + 16] + pop {{r0, r1}} + blx lr // briefly visit the function we're wrapping + // now restore the DCP state from the stack + pop {{r12, r14}} + mcrr p4,#0,r12,r14,c0 // WXMD r12, r14 + pop {{r12, r14}} + mcrr p4,#0,r12,r14,c1 // WYMD r12, r14 + pop {{r12, r14}} + mcrr p4,#0,r12,r14,c2 // WEFD r12, r14 + pop {{pc}} + "#, +); diff --git a/rp235x-hal/src/dma/bidirectional.rs b/rp235x-hal/src/dma/bidirectional.rs new file mode 100644 index 000000000..f6894d446 --- /dev/null +++ b/rp235x-hal/src/dma/bidirectional.rs @@ -0,0 +1,160 @@ +//! Bidirectional DMA transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::{ChannelConfig, SingleChannel}, + Pace, ReadTarget, WriteTarget, +}; + +/// DMA configuration for sending and receiving data simultaneously +pub struct Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + bidi: BIDI, + to: TO, + from_pace: Pace, + to_pace: Pace, + bswap: bool, +} + +impl Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + /// Create a DMA configuration for sending and receiving data simultaneously + pub fn new(ch: (CH1, CH2), from: FROM, bidi: BIDI, to: TO) -> Config { + Config { + ch, + from, + bidi, + to, + bswap: false, + from_pace: Pace::PreferSink, + to_pace: Pace::PreferSink, + } + } + + #[allow(clippy::wrong_self_convention)] + /// Set the transfer pacing for the DMA transfer from the source + pub fn from_pace(&mut self, pace: Pace) { + self.from_pace = pace; + } + + #[allow(clippy::wrong_self_convention)] + /// Set the transfer pacing for the DMA transfer to the target + pub fn to_pace(&mut self, pace: Pace) { + self.to_pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch.0.config( + &self.from, + &mut self.bidi, + self.from_pace, + self.bswap, + None, + false, + ); + self.ch.1.config( + &self.bidi, + &mut self.to, + self.to_pace, + self.bswap, + None, + false, + ); + self.ch.0.start_both(&mut self.ch.1); + + Transfer { + ch: self.ch, + from: self.from, + bidi: self.bidi, + to: self.to, + } + } +} + +/// Instance of a bidirectional DMA transfer +pub struct Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + bidi: BIDI, + to: TO, +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + BIDI: ReadTarget + WriteTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for either channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + let a = self.ch.0.check_irq0(); + let b = self.ch.1.check_irq0(); + a | b + } + + /// Check if an interrupt is pending for either channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + let a = self.ch.0.check_irq1(); + let b = self.ch.1.check_irq1(); + a | b + } + + /// Check if the transfer is completed + pub fn is_done(&self) -> bool { + let a = self.ch.1.ch().ch_ctrl_trig().read().busy().bit_is_set(); + let b = self.ch.0.ch().ch_ctrl_trig().read().busy().bit_is_set(); + !(a | b) + } + + /// Block until transfer is complete + pub fn wait(self) -> ((CH1, CH2), FROM, BIDI, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // TODO: Use a tuple type? + ((self.ch.0, self.ch.1), self.from, self.bidi, self.to) + } +} diff --git a/rp235x-hal/src/dma/double_buffer.rs b/rp235x-hal/src/dma/double_buffer.rs new file mode 100644 index 000000000..17f2933e9 --- /dev/null +++ b/rp235x-hal/src/dma/double_buffer.rs @@ -0,0 +1,316 @@ +//! Double-buffered DMA Transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::ChannelConfig, single_channel::SingleChannel, EndlessReadTarget, + EndlessWriteTarget, Pace, ReadTarget, WriteTarget, +}; + +/// Configuration for double-buffered DMA transfer +pub struct Config { + ch: (CH1, CH2), + from: FROM, + to: TO, + bswap: bool, + pace: Pace, +} + +impl Config +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Create a new configuration for double-buffered DMA transfer + pub fn new(ch: (CH1, CH2), from: FROM, to: TO) -> Config { + Config { + ch, + from, + to, + bswap: false, + pace: Pace::PreferSource, + } + } + + /// Sets the (preferred) pace for the DMA transfers. + /// + /// Usually, the code will automatically configure the correct pace, but + /// peripheral-to-peripheral transfers require the user to manually select whether the source + /// or the sink shall be queried for the pace signal. + pub fn pace(&mut self, pace: Pace) { + self.pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + // TODO: Do we want to call any callbacks to configure source/sink? + + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch + .0 + .config(&self.from, &mut self.to, self.pace, self.bswap, None, true); + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: false, + } + } +} + +/// State for a double-buffered read +pub struct ReadNext(BUF); +/// State for a double-buffered write +pub struct WriteNext(BUF); + +/// Instance of a double-buffered DMA transfer +pub struct Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + ch: (CH1, CH2), + from: FROM, + to: TO, + pace: Pace, + bswap: bool, + state: STATE, + second_ch: bool, +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for the active channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + if self.second_ch { + self.ch.1.check_irq0() + } else { + self.ch.0.check_irq0() + } + } + + /// Check if an interrupt is pending for the active channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + if self.second_ch { + self.ch.1.check_irq1() + } else { + self.ch.0.check_irq1() + } + } + + /// Check if the transfer is completed + pub fn is_done(&self) -> bool { + if self.second_ch { + !self.ch.1.ch().ch_ctrl_trig().read().busy().bit_is_set() + } else { + !self.ch.0.ch().ch_ctrl_trig().read().busy().bit_is_set() + } + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, +{ + /// Block until transfer completed + pub fn wait(self) -> (CH1, CH2, FROM, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // TODO: Use a tuple type? + (self.ch.0, self.ch.1, self.from, self.to) + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, +{ + /// Perform the next read of a double-buffered sequence + pub fn read_next>( + mut self, + buf: BUF, + ) -> Transfer> { + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the _other_ DMA channel, but do not start it yet. + if self.second_ch { + self.ch + .0 + .config(&buf, &mut self.to, self.pace, self.bswap, None, false); + } else { + self.ch + .1 + .config(&buf, &mut self.to, self.pace, self.bswap, None, false); + } + + // Chain the first channel to the second. + if self.second_ch { + self.ch.1.set_chain_to_enabled(&mut self.ch.0); + } else { + self.ch.0.set_chain_to_enabled(&mut self.ch.1); + } + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: ReadNext(buf), + second_ch: self.second_ch, + } + } +} + +impl Transfer +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget + EndlessReadTarget, + TO: WriteTarget, +{ + /// Perform the next write of a double-buffered sequence + pub fn write_next>( + mut self, + mut buf: BUF, + ) -> Transfer> { + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the _other_ DMA channel, but do not start it yet. + if self.second_ch { + self.ch + .0 + .config(&self.from, &mut buf, self.pace, self.bswap, None, false); + } else { + self.ch + .1 + .config(&self.from, &mut buf, self.pace, self.bswap, None, false); + } + + // Chain the first channel to the second. + if self.second_ch { + self.ch.1.set_chain_to_enabled(&mut self.ch.0); + } else { + self.ch.0.set_chain_to_enabled(&mut self.ch.1); + } + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: WriteNext(buf), + second_ch: self.second_ch, + } + } +} + +impl Transfer> +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget + EndlessWriteTarget, + NEXT: ReadTarget, +{ + /// Block until the the transfer is complete + pub fn wait(self) -> (FROM, Transfer) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Invert second_ch as now the other channel is the "active" channel. + ( + self.from, + Transfer { + ch: self.ch, + from: self.state.0, + to: self.to, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: !self.second_ch, + }, + ) + } +} + +impl Transfer> +where + CH1: SingleChannel, + CH2: SingleChannel, + FROM: ReadTarget + EndlessReadTarget, + TO: WriteTarget, + NEXT: WriteTarget, +{ + /// Block until transfer is complete + pub fn wait(self) -> (TO, Transfer) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Invert second_ch as now the other channel is the "active" channel. + ( + self.to, + Transfer { + ch: self.ch, + from: self.from, + to: self.state.0, + pace: self.pace, + bswap: self.bswap, + state: (), + second_ch: !self.second_ch, + }, + ) + } +} diff --git a/rp235x-hal/src/dma/mod.rs b/rp235x-hal/src/dma/mod.rs new file mode 100644 index 000000000..f87918968 --- /dev/null +++ b/rp235x-hal/src/dma/mod.rs @@ -0,0 +1,327 @@ +//! Direct memory access (DMA). +//! +//! The DMA unit of the rp235x seems very simplistic at first when compared to other MCUs. For +//! example, the individual DMA channels do not support chaining multiple buffers. However, within +//! certain limits, the DMA engine supports a wide range of transfer and buffer types, often by +//! combining multiple DMA channels: +//! +//! * Simple RX/TX transfers filling a single buffer or transferring data from one peripheral to +//! another. +//! * RX/TX transfers that use multiple chained buffers: These transfers require two channels to +//! be combined, where the first DMA channel configures the second DMA channel. An example for +//! this transfer type can be found in the datasheet. +//! * Repeated transfers from/to a set of buffers: By allocating one channel per buffer and +//! chaining the channels together, continuous transfers to a set of ring buffers can be +//! achieved. Note, however, that the MCU manually needs to reconfigure the DMA units unless the +//! buffer addresses and sizes are aligned, in which case the ring buffer functionality of the +//! DMA engine can be used. Even then, however, at least two DMA channels are required as a +//! channel cannot be chained to itself. +//! +//! This API tries to provide three types of buffers: Single buffers, double-buffered transfers +//! where the user can specify the next buffer while the previous is being transferred, and +//! automatic continuous ring buffers consisting of two aligned buffers being read or written +//! alternatingly. + +use core::marker::PhantomData; +use embedded_dma::{ReadBuffer, WriteBuffer}; + +use crate::{ + pac::{self, DMA}, + resets::SubsystemReset, + typelevel::Sealed, +}; +// Export these types for easier use by external code +pub use crate::dma::single_channel::SingleChannel; + +// Bring in our submodules +pub mod bidirectional; +pub mod double_buffer; +pub mod single_buffer; +mod single_channel; + +/// DMA unit. +pub trait DMAExt: Sealed { + /// Splits the DMA unit into its individual channels. + fn split(self, resets: &mut pac::RESETS) -> Channels; + /// Splits the DMA unit into its individual channels with runtime ownership + fn dyn_split(self, resets: &mut pac::RESETS) -> DynChannels; +} + +/// DMA channel. +pub struct Channel { + _phantom: PhantomData, +} + +/// DMA channel identifier. +pub trait ChannelIndex: Sealed { + /// Numerical index of the DMA channel (0..11). + fn id() -> u8; +} + +macro_rules! channels { + ( + $($CHX:ident: ($chX:ident, $x:expr),)+ + ) => { + impl DMAExt for DMA { + fn split(self, resets: &mut pac::RESETS) -> Channels { + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + Channels { + $( + $chX: Channel { + _phantom: PhantomData, + }, + )+ + } + } + + fn dyn_split(self, resets: &mut pac::RESETS) -> DynChannels{ + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + DynChannels { + $( + $chX: Some(Channel { + _phantom: PhantomData, + }), + )+ + } + } + } + + impl Sealed for DMA {} + + /// Set of DMA channels. + pub struct Channels { + $( + /// DMA channel. + pub $chX: Channel<$CHX>, + )+ + } + $( + /// DMA channel identifier. + pub struct $CHX; + impl ChannelIndex for $CHX { + fn id() -> u8 { + $x + } + } + + impl Sealed for $CHX {} + )+ + + /// Set of DMA channels with runtime ownership. + pub struct DynChannels { + $( + /// DMA channel. + pub $chX: Option>, + )+ + } + } +} + +channels! { + CH0: (ch0, 0), + CH1: (ch1, 1), + CH2: (ch2, 2), + CH3: (ch3, 3), + CH4: (ch4, 4), + CH5: (ch5, 5), + CH6: (ch6, 6), + CH7: (ch7, 7), + CH8: (ch8, 8), + CH9: (ch9, 9), + CH10:(ch10, 10), + CH11:(ch11, 11), +} + +trait ChannelRegs { + unsafe fn ptr() -> *const pac::dma::CH; + fn regs(&self) -> &pac::dma::CH; +} + +impl ChannelRegs for Channel { + unsafe fn ptr() -> *const pac::dma::CH { + (*pac::DMA::ptr()).ch(CH::id() as usize) + } + + fn regs(&self) -> &pac::dma::CH { + unsafe { &*Self::ptr() } + } +} + +/// Trait which is implemented by anything that can be read via DMA. +/// +/// # Safety +/// +/// The implementing type must be safe to use for DMA reads. This means: +/// +/// - The range returned by rx_address_count must point to a valid address, +/// and if rx_increment is true, count must fit into the allocated buffer. +/// - As long as no `&mut self` method is called on the implementing object: +/// - `rx_address_count` must always return the same value, if called multiple +/// times. +/// - The memory specified by the pointer and size returned by `rx_address_count` +/// must not be freed during the transfer it is used in as long as `self` is not dropped. +pub unsafe trait ReadTarget { + /// Type which is transferred in a single DMA transfer. + type ReceivedWord; + + /// Returns the DREQ number for this data source (`None` for memory buffers). + fn rx_treq() -> Option; + + /// Returns the address and the maximum number of words that can be transferred from this data + /// source in a single DMA operation. + /// + /// For peripherals, the count should likely be u32::MAX. If a data source implements + /// EndlessReadTarget, it is suitable for infinite transfers from or to ring buffers. Note that + /// ring buffers designated for endless transfers, but with a finite buffer size, should return + /// the size of their individual buffers here. + /// + /// # Safety + /// + /// This function has the same safety guarantees as `ReadBuffer::read_buffer`. + fn rx_address_count(&self) -> (u32, u32); + + /// Returns whether the address shall be incremented after each transfer. + fn rx_increment(&self) -> bool; +} + +/// Marker which signals that `rx_address_count()` can be called multiple times. +/// +/// The DMA code will never call `rx_address_count()` to request more than two buffers to configure +/// two DMA channels. In the case of peripherals, the function can always return the same values. +pub trait EndlessReadTarget: ReadTarget {} + +/// Safety: ReadBuffer and ReadTarget have the same safety requirements. +unsafe impl ReadTarget for B { + type ReceivedWord = ::Word; + + fn rx_treq() -> Option { + None + } + + fn rx_address_count(&self) -> (u32, u32) { + let (ptr, len) = unsafe { self.read_buffer() }; + (ptr as u32, len as u32) + } + + fn rx_increment(&self) -> bool { + true + } +} + +/// Trait which is implemented by anything that can be written via DMA. +/// +/// # Safety +/// +/// The implementing type must be safe to use for DMA writes. This means: +/// +/// - The range returned by tx_address_count must point to a valid address, +/// and if tx_increment is true, count must fit into the allocated buffer. +/// - As long as no other `&mut self` method is called on the implementing object: +/// - `tx_address_count` must always return the same value, if called multiple +/// times. +/// - The memory specified by the pointer and size returned by `tx_address_count` +/// must not be freed during the transfer it is used in as long as `self` is not dropped. +pub unsafe trait WriteTarget { + /// Type which is transferred in a single DMA transfer. + type TransmittedWord; + + /// Returns the DREQ number for this data sink (`None` for memory buffers). + fn tx_treq() -> Option; + + /// Returns the address and the maximum number of words that can be transferred from this data + /// source in a single DMA operation. + /// + /// See `ReadTarget::rx_address_count` for a complete description of the semantics of this + /// function. + fn tx_address_count(&mut self) -> (u32, u32); + + /// Returns whether the address shall be incremented after each transfer. + fn tx_increment(&self) -> bool; +} + +/// Marker which signals that `tx_address_count()` can be called multiple times. +/// +/// The DMA code will never call `tx_address_count()` to request more than two buffers to configure +/// two DMA channels. In the case of peripherals, the function can always return the same values. +pub trait EndlessWriteTarget: WriteTarget {} + +/// Safety: WriteBuffer and WriteTarget have the same safety requirements. +unsafe impl WriteTarget for B { + type TransmittedWord = ::Word; + + fn tx_treq() -> Option { + None + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let (ptr, len) = unsafe { self.write_buffer() }; + (ptr as u32, len as u32) + } + + fn tx_increment(&self) -> bool { + true + } +} + +/// Pacing for DMA transfers. +/// +/// Generally, while memory-to-memory DMA transfers can operate at maximum possible throughput, +/// transfers involving peripherals commonly have to wait for data to be available or for available +/// space in write queues. This type defines whether the sink or the source shall pace the transfer +/// for peripheral-to-peripheral transfers. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pace { + /// The DREQ signal from the source is used, if available. If not, the sink's DREQ signal is + /// used. + PreferSource, + /// The DREQ signal from the sink is used, if available. If not, the source's DREQ signal is + /// used. + PreferSink, + // TODO: Timers? +} + +/// Error during DMA configuration. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DMAError { + /// Buffers were not aligned to their size even though they needed to be. + Alignment, + /// An illegal configuration (i.e., buffer sizes not suitable for a memory-to-memory transfer) + /// was specified. + IllegalConfig, +} + +/// Constraint on transfer size types +pub trait TransferSize: Sealed { + /// Actual type of transfer + type Type; +} + +/// DMA transfer in bytes (u8) +#[derive(Debug, Copy, Clone)] +pub struct Byte; +/// DMA transfer in half words (u16) +#[derive(Debug, Copy, Clone)] +pub struct HalfWord; +/// DMA transfer in words (u32) +#[derive(Debug, Copy, Clone)] +pub struct Word; + +impl Sealed for Byte {} +impl Sealed for HalfWord {} +impl Sealed for Word {} + +impl TransferSize for Byte { + type Type = u8; +} +impl TransferSize for HalfWord { + type Type = u16; +} +impl TransferSize for Word { + type Type = u32; +} diff --git a/rp235x-hal/src/dma/single_buffer.rs b/rp235x-hal/src/dma/single_buffer.rs new file mode 100644 index 000000000..a4c657ecf --- /dev/null +++ b/rp235x-hal/src/dma/single_buffer.rs @@ -0,0 +1,149 @@ +//! Single-buffered or peripheral-peripheral DMA Transfers + +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::{ + single_channel::ChannelConfig, single_channel::SingleChannel, Pace, ReadTarget, WriteTarget, +}; + +/// Configuration for single-buffered DMA transfer +pub struct Config { + ch: CH, + from: FROM, + to: TO, + pace: Pace, + bswap: bool, +} + +impl Config +where + CH: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Create a new configuration for single-buffered DMA transfer + pub fn new(ch: CH, from: FROM, to: TO) -> Config { + Config { + ch, + from, + to, + pace: Pace::PreferSource, + bswap: false, + } + } + + /// Sets the (preferred) pace for the DMA transfers. + /// + /// Usually, the code will automatically configure the correct pace, but + /// peripheral-to-peripheral transfers require the user to manually select whether the source + /// or the sink shall be queried for the pace signal. + pub fn pace(&mut self, pace: Pace) { + self.pace = pace; + } + + /// Enable/disable byteswapping for the DMA transfers, default value is false. + /// + /// For byte data, this has no effect. For halfword data, the two bytes of + /// each halfword are swapped. For word data, the four bytes of each word + /// are swapped to reverse order. + /// + /// This is a convenient way to change the (half-)words' byte endianness on the fly. + pub fn bswap(&mut self, bswap: bool) { + self.bswap = bswap; + } + + /// Start the DMA transfer + pub fn start(mut self) -> Transfer { + // TODO: Do we want to call any callbacks to configure source/sink? + + // Make sure that memory contents reflect what the user intended. + // TODO: How much of the following is necessary? + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + // Configure the DMA channel and start it. + self.ch + .config(&self.from, &mut self.to, self.pace, self.bswap, None, true); + + Transfer { + ch: self.ch, + from: self.from, + to: self.to, + } + } +} + +// TODO: Drop for most of these structs +/// Instance of a single-buffered DMA transfer +pub struct Transfer { + ch: CH, + from: FROM, + to: TO, +} + +impl Transfer +where + CH: SingleChannel, + FROM: ReadTarget, + TO: WriteTarget, +{ + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + pub fn check_irq0(&mut self) -> bool { + self.ch.check_irq0() + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + pub fn check_irq1(&mut self) -> bool { + self.ch.check_irq1() + } + + /// Check if the transfer has completed. + pub fn is_done(&self) -> bool { + !self.ch.ch().ch_ctrl_trig().read().busy().bit_is_set() + } + + /// Block until the transfer is complete, returning the channel and targets + pub fn wait(self) -> (CH, FROM, TO) { + while !self.is_done() {} + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + (self.ch, self.from, self.to) + } + + /// Aborts the current transfer, returning the channel and targets + pub fn abort(mut self) -> (CH, FROM, TO) { + let irq0_was_enabled = self.ch.is_enabled_irq0(); + let irq1_was_enabled = self.ch.is_enabled_irq1(); + self.ch.disable_irq0(); + self.ch.disable_irq1(); + + let chan_abort = unsafe { &*crate::pac::DMA::ptr() }.chan_abort(); + let abort_mask = (1 << self.ch.id()) as u16; + + chan_abort.write(|w| unsafe { w.chan_abort().bits(abort_mask) }); + + while chan_abort.read().bits() != 0 {} + + while !self.is_done() {} + + self.ch.check_irq0(); + self.ch.check_irq1(); + + if irq0_was_enabled { + self.ch.enable_irq0(); + } + + if irq1_was_enabled { + self.ch.enable_irq1(); + } + + // Make sure that memory contents reflect what the user intended. + crate::arch::dsb(); + compiler_fence(Ordering::SeqCst); + + (self.ch, self.from, self.to) + } +} diff --git a/rp235x-hal/src/dma/single_channel.rs b/rp235x-hal/src/dma/single_channel.rs new file mode 100644 index 000000000..664fb1958 --- /dev/null +++ b/rp235x-hal/src/dma/single_channel.rs @@ -0,0 +1,263 @@ +use crate::pac::DMA; + +use super::{Channel, ChannelIndex, Pace, ReadTarget, WriteTarget}; +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::ChannelRegs, + typelevel::Sealed, +}; +use core::mem; + +/// Trait which implements low-level functionality for transfers using a single DMA channel. +pub trait SingleChannel: Sealed { + /// Returns the registers associated with this DMA channel. + /// + /// In the case of channel pairs, this returns the first channel. + fn ch(&self) -> &crate::pac::dma::CH; + /// Returns the index of the DMA channel. + fn id(&self) -> u8; + + #[deprecated(note = "Renamed to enable_irq0")] + /// Enables the DMA_IRQ_0 signal for this channel. + fn listen_irq0(&mut self) { + self.enable_irq0(); + } + + /// Enables the DMA_IRQ_0 signal for this channel. + fn enable_irq0(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*DMA::ptr()).inte0().as_ptr(), 1 << self.id()); + } + } + + /// Check if the DMA_IRQ_0 signal for this channel is enabled. + fn is_enabled_irq0(&mut self) -> bool { + unsafe { ((*DMA::ptr()).inte0().read().bits() & (1 << self.id())) != 0 } + } + + #[deprecated(note = "Renamed to disable_irq0")] + /// Disables the DMA_IRQ_0 signal for this channel. + fn unlisten_irq0(&mut self) { + self.disable_irq0(); + } + + /// Disables the DMA_IRQ_0 signal for this channel. + fn disable_irq0(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*DMA::ptr()).inte0().as_ptr(), 1 << self.id()); + } + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + fn check_irq0(&mut self) -> bool { + // Safety: The following is race-free as we only ever clear the bit for this channel. + // Nobody else modifies that bit. + unsafe { + let status = (*DMA::ptr()).ints0().read().bits(); + if (status & (1 << self.id())) != 0 { + // Clear the interrupt. + (*DMA::ptr()).ints0().write(|w| w.bits(1 << self.id())); + true + } else { + false + } + } + } + + #[deprecated(note = "Renamed to enable_irq1")] + /// Enables the DMA_IRQ_1 signal for this channel. + fn listen_irq1(&mut self) { + self.enable_irq1(); + } + + /// Enables the DMA_IRQ_1 signal for this channel. + fn enable_irq1(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*DMA::ptr()).inte1().as_ptr(), 1 << self.id()); + } + } + + /// Check if the DMA_IRQ_1 signal for this channel is enabled. + fn is_enabled_irq1(&mut self) -> bool { + unsafe { ((*DMA::ptr()).inte1().read().bits() & (1 << self.id())) != 0 } + } + + #[deprecated(note = "Renamed to disable_irq1")] + /// Disables the DMA_IRQ_1 signal for this channel. + fn unlisten_irq1(&mut self) { + self.disable_irq1(); + } + + /// Disables the DMA_IRQ_1 signal for this channel. + fn disable_irq1(&mut self) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*DMA::ptr()).inte1().as_ptr(), 1 << self.id()); + } + } + + /// Check if an interrupt is pending for this channel and clear the corresponding pending bit + fn check_irq1(&mut self) -> bool { + // Safety: The following is race-free as we only ever clear the bit for this channel. + // Nobody else modifies that bit. + unsafe { + let status = (*DMA::ptr()).ints1().read().bits(); + if (status & (1 << self.id())) != 0 { + // Clear the interrupt. + (*DMA::ptr()).ints1().write(|w| w.bits(1 << self.id())); + true + } else { + false + } + } + } +} + +impl SingleChannel for Channel { + fn ch(&self) -> &crate::pac::dma::CH { + self.regs() + } + + fn id(&self) -> u8 { + CH::id() + } +} + +impl Sealed for Channel {} + +impl SingleChannel for (Channel, Channel) { + fn ch(&self) -> &crate::pac::dma::CH { + self.0.regs() + } + + fn id(&self) -> u8 { + CH1::id() + } +} + +pub(crate) trait ChannelConfig { + fn config( + &mut self, + from: &FROM, + to: &mut TO, + pace: Pace, + bswap: bool, + chain_to: Option, + start: bool, + ) where + FROM: ReadTarget, + TO: WriteTarget; + + fn set_chain_to_enabled(&mut self, other: &mut CH); + fn start(&mut self); + fn start_both(&mut self, other: &mut CH); +} + +// rp235x's DMA engine only works with certain word sizes. Make sure that other +// word sizes will fail to compile. +struct IsValidWordSize { + w: core::marker::PhantomData, +} + +impl IsValidWordSize { + const OK: usize = { + match mem::size_of::() { + 1 | 2 | 4 => 0, // ok + _ => panic!("Unsupported DMA word size"), + } + }; +} + +impl ChannelConfig for CH { + fn config( + &mut self, + from: &FROM, + to: &mut TO, + pace: Pace, + bswap: bool, + chain_to: Option, + start: bool, + ) where + FROM: ReadTarget, + TO: WriteTarget, + { + // Configure the DMA channel. + let _ = IsValidWordSize::::OK; + + let (src, src_count) = from.rx_address_count(); + let src_incr = from.rx_increment(); + let (dest, dest_count) = to.tx_address_count(); + let dest_incr = to.tx_increment(); + const TREQ_UNPACED: u8 = 0x3f; + let treq = match pace { + Pace::PreferSource => FROM::rx_treq().or_else(TO::tx_treq).unwrap_or(TREQ_UNPACED), + Pace::PreferSink => TO::tx_treq().or_else(FROM::rx_treq).unwrap_or(TREQ_UNPACED), + }; + let len = u32::min(src_count, dest_count); + self.ch().ch_al1_ctrl().write(|w| unsafe { + w.data_size().bits(mem::size_of::() as u8 >> 1); + w.incr_read().bit(src_incr); + w.incr_write().bit(dest_incr); + w.treq_sel().bits(treq); + w.bswap().bit(bswap); + w.chain_to().bits(chain_to.unwrap_or_else(|| self.id())); + w.en().bit(true); + w + }); + self.ch().ch_read_addr().write(|w| unsafe { w.bits(src) }); + self.ch().ch_trans_count().write(|w| unsafe { + w.count().bits(len); + w.mode().normal(); + w + }); + if start { + self.ch() + .ch_al2_write_addr_trig() + .write(|w| unsafe { w.bits(dest) }); + } else { + self.ch().ch_write_addr().write(|w| unsafe { w.bits(dest) }); + } + } + + fn set_chain_to_enabled(&mut self, other: &mut CH2) { + // We temporarily pause the channel when setting CHAIN_TO, to prevent any race condition + // that could occur, as we need to check afterwards whether the channel was successfully + // chained to this channel or whether this channel was already completed. If we did not + // pause this channel, we could get into a situation where both channels completed in quick + // succession, yet we did not notice, as the situation is not distinguishable from one + // where the second channel was not started at all. + + self.ch().ch_al1_ctrl().modify(|_, w| unsafe { + w.chain_to().bits(other.id()); + w.en().clear_bit(); + w + }); + if self.ch().ch_al1_ctrl().read().busy().bit_is_set() { + // This channel is still active, so just continue. + self.ch().ch_al1_ctrl().modify(|_, w| w.en().set_bit()); + } else { + // This channel has already finished, so just start the other channel directly. + other.start(); + } + } + + fn start(&mut self) { + // Safety: The write does not interfere with any other writes, it only affects this + // channel. + unsafe { &*crate::pac::DMA::ptr() } + .multi_chan_trigger() + .write(|w| unsafe { w.bits(1 << self.id()) }); + } + + fn start_both(&mut self, other: &mut CH2) { + // Safety: The write does not interfere with any other writes, it only affects this + // channel and other (which we have an exclusive borrow of). + let channel_flags = 1 << self.id() | 1 << other.id(); + unsafe { &*crate::pac::DMA::ptr() } + .multi_chan_trigger() + .write(|w| unsafe { w.bits(channel_flags) }); + } +} diff --git a/rp235x-hal/src/gpio/func.rs b/rp235x-hal/src/gpio/func.rs new file mode 100644 index 000000000..544330fed --- /dev/null +++ b/rp235x-hal/src/gpio/func.rs @@ -0,0 +1,226 @@ +use core::marker::PhantomData; + +use paste::paste; + +use super::pin::DynBankId; + +pub(crate) mod func_sealed { + use super::DynFunction; + + pub trait Function { + fn from(f: DynFunction) -> Self; + fn as_dyn(&self) -> DynFunction; + } +} + +/// Type-level `enum` for pin function. +pub trait Function: func_sealed::Function {} + +/// Describes the function currently assigned to a pin with a dynamic type. +/// +/// A 'pin' on the RP2350 can be connected to different parts of the chip +/// internally - for example, it could be configured as a GPIO pin and connected +/// to the SIO block, or it could be configured as a UART pin and connected to +/// the UART block. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DynFunction { + /// The 'XIP' (or Execute-in-place) function, which means talking to the QSPI Flash. + Xip, + /// The 'SPI' (or serial-peripheral-interface) function. + Spi, + /// The 'UART' (or serial-port) function. + Uart, + /// The 'I2C' (or inter-integrated circuit) function. This is sometimes also called TWI (for + /// two-wire-interface). + I2c, + /// The 'PWM' (or pulse-width-modulation) function. + Pwm, + /// The 'SIO' (or single-cycle input-output) function. This is the function to use for + /// 'manually' controlling the GPIO. + Sio(DynSioConfig), + /// The 'PIO' (or programmable-input-output) function for the PIO0 peripheral block. + Pio0, + /// The 'PIO' (or programmable-input-output) function for the PIO1 peripheral block. + Pio1, + /// The 'PIO' (or programmable-input-output) function for the PIO2 peripheral block. + Pio2, + /// The 'Clock' function. This can be used to input or output clock references to or from the + /// rp235x. + Clock, + /// The 'USB' function. Only VBUS detect, VBUS enable and overcurrent detect are configurable. + /// Other USB io have dedicated pins. + Usb, + /// The auxilliary UART function lets you use a UART CTS as UART TX and a + /// UART RTS pin as UART RX. + UartAux, + /// The 'Null' function for unused pins. + Null, +} + +/// Value-level `enum` for SIO configuration. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DynSioConfig { + /// Pin is configured as Input. + Input, + /// Pin is configured as Output. + Output, +} + +impl Function for DynFunction {} +impl func_sealed::Function for DynFunction { + #[inline] + fn from(f: DynFunction) -> Self { + f + } + + #[inline] + fn as_dyn(&self) -> DynFunction { + *self + } +} + +macro_rules! pin_func { + ($($fn:ident $(as $alias:ident)?),*) => { + $(paste! { + /// Type-level `variant` for pin [`Function`]. + pub struct [](pub(super) ()); + impl Function for [] {} + impl func_sealed::Function for [] { + #[inline] + fn from(_f: DynFunction) -> Self { + Self(()) + } + #[inline] + fn as_dyn(&self) -> DynFunction { + DynFunction::[<$fn>] + } + } + $( + #[doc = "Alias to [`Function" $fn "`]."] + pub type [] = []; + )? + })* + }; +} +pin_func!(Xip, Spi, Uart, I2c as I2C, Pwm, Pio0, Pio1, Pio2, Clock, Usb, UartAux, Null); + +//============================================================================== +// SIO sub-types +//============================================================================== + +/// Type-level `variant` for pin [`Function`]. +pub struct FunctionSio(PhantomData); +impl Function for FunctionSio {} +impl func_sealed::Function for FunctionSio { + fn from(_f: DynFunction) -> Self { + FunctionSio(PhantomData) + } + fn as_dyn(&self) -> DynFunction { + DynFunction::Sio(C::DYN) + } +} +/// Alias to [`FunctionSio`]. +pub type FunctionSioInput = FunctionSio; +/// Alias to [`FunctionSio`]. +pub type FunctionSioOutput = FunctionSio; + +/// Type-level `enum` for SIO configuration. +pub trait SioConfig { + #[allow(missing_docs)] + const DYN: DynSioConfig; +} + +/// Type-level `variant` for SIO configuration. +pub enum SioInput {} +impl SioConfig for SioInput { + #[allow(missing_docs)] + const DYN: DynSioConfig = DynSioConfig::Input; +} +/// Type-level `variant` for SIO configuration. +pub enum SioOutput {} +impl SioConfig for SioOutput { + #[allow(missing_docs)] + const DYN: DynSioConfig = DynSioConfig::Output; +} + +//============================================================================== +// Pin to function mapping +//============================================================================== + +/// Error type for invalid function conversion. +pub struct InvalidFunction; + +/// Marker of valid pin -> function combination. +/// +/// Where `impl ValidFunction for I` reads as `F is a valid function implemented for the pin I`. +pub trait ValidFunction: super::pin::PinId {} + +impl DynFunction { + pub(crate) fn is_valid(&self, id: &P) -> bool { + use DynBankId::*; + use DynFunction::*; + + let dyn_pin = id.as_dyn(); + match (self, dyn_pin.bank, dyn_pin.num) { + (Xip, Bank0, _) => false, + (Clock, _, 0..=19 | 26..=29) => false, + (UartAux, _, n) if (n & 0x02) == 0 => false, + (_, Bank0, 0..=29) => true, + + (Xip | Sio(_), Qspi, 0..=5) => true, + (_, Qspi, 0..=5) => false, + + _ => unreachable!(), + } + } +} +impl ValidFunction for P {} +macro_rules! pin_valid_func { + ($bank:ident as $prefix:ident, [$head:ident $(, $func:ident)*], [$($name:tt),+]) => { + pin_valid_func!($bank as $prefix, [$($func),*], [$($name),+]); + paste::paste!{$( + impl ValidFunction<[]> for super::pin::[<$bank:lower>]::[<$prefix $name>] {} + )+} + }; + ($bank:ident as $prefix:ident, [], [$($name:tt),+]) => {}; +} +pin_valid_func!( + bank0 as Gpio, + [Spi, Uart, I2c, Pwm, Pio0, Pio1, Pio2, Usb, Null], + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29 + ] +); + +pin_valid_func!( + bank0 as Gpio, + [UartAux], + [2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23, 26, 27] +); +pin_valid_func!(bank0 as Gpio, [Clock], [20, 21, 22, 23, 24, 25]); +pin_valid_func!(qspi as Qspi, [Xip, Null], [Sclk, Sd0, Sd1, Sd2, Sd3, Ss]); + +macro_rules! pin_valid_func_sio { + ($bank:ident as $prefix:ident, [$($name:tt),+]) => { + paste::paste!{$( + impl ValidFunction> for super::pin::[<$bank:lower>]::[<$prefix $name>] {} + )+} + }; +} + +#[rustfmt::skip] +pin_valid_func_sio!( + bank0 as Gpio, + [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 + ] +); + +pin_valid_func_sio!(qspi as Qspi, [Sclk, Sd0, Sd1, Sd2, Sd3, Ss]); diff --git a/rp235x-hal/src/gpio/mod.rs b/rp235x-hal/src/gpio/mod.rs new file mode 100644 index 000000000..0bd3b1652 --- /dev/null +++ b/rp235x-hal/src/gpio/mod.rs @@ -0,0 +1,1644 @@ +//! General Purpose Input and Output (GPIO) +//! +//! ## Basic usage +//! ```no_run +//! use embedded_hal::digital::{InputPin, OutputPin}; +//! use rp235x_hal::{ +//! self as hal, clocks::init_clocks_and_plls, gpio::Pins, watchdog::Watchdog, Sio, +//! }; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! peripherals.XOSC, +//! peripherals.CLOCKS, +//! peripherals.PLL_SYS, +//! peripherals.PLL_USB, +//! &mut peripherals.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! +//! let mut pac = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(pac.SIO); +//! let pins = rp235x_hal::gpio::Pins::new( +//! pac.IO_BANK0, +//! pac.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut pac.RESETS, +//! ); +//! // Set a pin to drive output +//! let mut output_pin = pins.gpio25.into_push_pull_output(); +//! // Drive output to 3.3V +//! output_pin.set_high().unwrap(); +//! // Drive output to 0V +//! output_pin.set_low().unwrap(); +//! // Set a pin to input +//! let mut input_pin = pins.gpio24.into_floating_input(); +//! // pinstate will be true if the pin is above 2V +//! let pinstate = input_pin.is_high().unwrap(); +//! // pinstate_low will be true if the pin is below 1.15V +//! let pinstate_low = input_pin.is_low().unwrap(); +//! // you'll want to pull-up or pull-down a switch if it's not done externally +//! let button_pin = pins.gpio23.into_pull_down_input(); +//! let button2_pin = pins.gpio22.into_pull_up_input(); +//! ``` +//! See [examples/gpio_in_out.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/gpio_in_out.rs) for a more practical example + +// Design Notes: +// +// - The user must not be able to instantiate by themselves nor obtain an instance of the Type-level +// structure. +// - non-typestated features (overrides, irq configuration, pads' output disable, pad's input +// enable, drive strength, schmitt, slew rate, sio's in sync bypass) are considered somewhat +// advanced usage of the pin (relative to reading/writing a gpio) and it is the responsibility of +// the user to make sure these are in a correct state when converting and passing the pin around. + +pub use embedded_hal::digital::PinState; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + pac, + sio::Sio, + typelevel::{self, Sealed}, +}; + +mod func; +pub(crate) mod pin; +mod pin_group; +mod pull; + +pub use func::*; +pub use pin::{DynBankId, DynPinId, PinId}; +pub use pin_group::PinGroup; +pub use pull::*; + +/// The amount of current that a pin can drive when used as an output. +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum OutputDriveStrength { + /// 2 mA + TwoMilliAmps, + /// 4 mA + FourMilliAmps, + /// 8 mA + EightMilliAmps, + /// 12 mA + TwelveMilliAmps, +} + +/// The slew rate of a pin when used as an output. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum OutputSlewRate { + /// Slew slow + Slow, + /// Slew fast + Fast, +} + +/// Interrupt kind. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum Interrupt { + /// While low + LevelLow, + /// While high + LevelHigh, + /// On falling edge + EdgeLow, + /// On rising edge + EdgeHigh, +} +impl Interrupt { + fn mask(&self) -> u32 { + match self { + Interrupt::LevelLow => 0b0001, + Interrupt::LevelHigh => 0b0010, + Interrupt::EdgeLow => 0b0100, + Interrupt::EdgeHigh => 0b1000, + } + } +} + +/// Interrupt override state. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum InterruptOverride { + /// Don't invert the interrupt. + Normal = 0, + /// Invert the interrupt. + Invert = 1, + /// Drive interrupt low. + AlwaysLow = 2, + /// Drive interrupt high. + AlwaysHigh = 3, +} + +/// Input override state. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum InputOverride { + /// Don't invert the peripheral input. + Normal = 0, + /// Invert the peripheral input. + Invert = 1, + /// Drive peripheral input low. + AlwaysLow = 2, + /// Drive peripheral input high. + AlwaysHigh = 3, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// Output enable override state. +pub enum OutputEnableOverride { + /// Use the original output enable signal from selected peripheral. + Normal = 0, + /// Invert the output enable signal from selected peripheral. + Invert = 1, + /// Disable output. + Disable = 2, + /// Enable output. + Enable = 3, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +/// Output override state. +pub enum OutputOverride { + /// Use the original output signal from selected peripheral. + DontInvert = 0, + /// Invert the output signal from selected peripheral. + Invert = 1, + /// Drive output low. + AlwaysLow = 2, + /// Drive output high. + AlwaysHigh = 3, +} + +/// Represents a pin, with a given ID (e.g. Gpio3), a given function (e.g. FunctionUart) and a given pull type +/// (e.g. pull-down). +pub struct Pin { + id: I, + function: F, + pull_type: P, +} + +/// Create a new pin instance. +/// +/// # Safety +/// The uniqueness of the pin is not verified. User must make sure no other instance of that specific +/// pin exists at the same time. +pub unsafe fn new_pin(id: DynPinId) -> Pin { + use pac::io_bank0::gpio::gpio_ctrl::FUNCSEL_A; + use pin::pin_sealed::PinIdOps; + + let funcsel = id + .io_ctrl() + .read() + .funcsel() + .variant() + .expect("Invalid funcsel read from register."); + let function = match funcsel { + FUNCSEL_A::JTAG => DynFunction::Xip, + FUNCSEL_A::SPI => DynFunction::Spi, + FUNCSEL_A::UART => DynFunction::Uart, + FUNCSEL_A::I2C => DynFunction::I2c, + FUNCSEL_A::PWM => DynFunction::Pwm, + FUNCSEL_A::SIO => { + let mask = id.mask(); + let cfg = if id.sio_oe().read().bits() & mask == mask { + DynSioConfig::Output + } else { + DynSioConfig::Input + }; + DynFunction::Sio(cfg) + } + FUNCSEL_A::PIO0 => DynFunction::Pio0, + FUNCSEL_A::PIO1 => DynFunction::Pio1, + FUNCSEL_A::PIO2 => DynFunction::Pio2, + FUNCSEL_A::GPCK => DynFunction::Clock, + FUNCSEL_A::USB => DynFunction::Usb, + FUNCSEL_A::UART_AUX => DynFunction::UartAux, + FUNCSEL_A::NULL => DynFunction::Null, + }; + let pad = id.pad_ctrl().read(); + let pull_type = match (pad.pue().bit_is_set(), pad.pde().bit_is_set()) { + (true, true) => DynPullType::BusKeep, + (true, false) => DynPullType::Up, + (false, true) => DynPullType::Down, + (false, false) => DynPullType::None, + }; + + Pin { + id, + function, + pull_type, + } +} + +impl Pin { + /// Pin ID. + pub fn id(&self) -> DynPinId { + self.id.as_dyn() + } + + /// # Safety + /// This method does not check if the pin is actually configured as the target function or pull + /// mode. This may lead to inconsistencies between the type-state and the actual state of the + /// pin's configuration. + pub unsafe fn into_unchecked(self) -> Pin { + Pin { + id: self.id, + function: F2::from(self.function.as_dyn()), + pull_type: P2::from(self.pull_type.as_dyn()), + } + } + + /// Convert the pin from one state to the other. + pub fn reconfigure(self) -> Pin + where + F2: func::Function, + P2: PullType, + I: func::ValidFunction, + { + self.into_function().into_pull_type() + } + + /// Convert the pin function. + #[deprecated( + note = "Misleading name `mode` when it changes the `function`. Please use `into_function` instead.", + since = "0.9.0" + )] + pub fn into_mode(self) -> Pin + where + F2: func::Function, + I: func::ValidFunction, + { + self.into_function() + } + + /// Convert the pin function. + pub fn into_function(self) -> Pin + where + F2: func::Function, + I: func::ValidFunction, + { + // Thanks to type-level validation, we know F2 is valid for I + let prev_function = self.function.as_dyn(); + let function = F2::from(prev_function); + let new_function = function.as_dyn(); + + if prev_function != new_function { + pin::set_function(&self.id, new_function); + } + + Pin { + function, + id: self.id, + pull_type: self.pull_type, + } + } + + /// Convert the pin pull type. + pub fn into_pull_type(self) -> Pin { + let prev_pull_type = self.pull_type.as_dyn(); + let pull_type = M2::from(prev_pull_type); + let new_pull_type = pull_type.as_dyn(); + + if prev_pull_type != new_pull_type { + pin::set_pull_type(&self.id, new_pull_type); + } + + Pin { + pull_type, + id: self.id, + function: self.function, + } + } + + /// Erase the Pin ID type check. + pub fn into_dyn_pin(self) -> Pin { + Pin { + id: self.id.as_dyn(), + function: self.function, + pull_type: self.pull_type, + } + } + + /// Get the pin's pull type. + pub fn pull_type(&self) -> DynPullType { + self.pull_type.as_dyn() + } + + //============================================================================== + // Typical pin conversions. + //============================================================================== + + /// Disable the pin and set it to float + #[inline] + pub fn into_floating_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Disable the pin and set it to pull down + #[inline] + pub fn into_pull_down_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Disable the pin and set it to pull up + #[inline] + pub fn into_pull_up_disabled(self) -> Pin + where + I: ValidFunction, + { + self.reconfigure() + } + + /// Configure the pin to operate as a floating input + #[inline] + pub fn into_floating_input(self) -> Pin, PullNone> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a pulled down input + #[inline] + pub fn into_pull_down_input(self) -> Pin, PullDown> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a pulled up input + #[inline] + pub fn into_pull_up_input(self) -> Pin, PullUp> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a bus keep input + #[inline] + pub fn into_bus_keep_input(self) -> Pin, PullBusKeep> + where + I: ValidFunction>, + { + self.into_function().into_pull_type() + } + + /// Configure the pin to operate as a push-pull output. + /// + /// If you want to specify the initial pin state, use [`Pin::into_push_pull_output_in_state`]. + #[inline] + pub fn into_push_pull_output(self) -> Pin, P> + where + I: ValidFunction>, + { + self.into_function() + } + + /// Configure the pin to operate as a push-pull output, specifying an initial + /// state which is applied immediately. + #[inline] + pub fn into_push_pull_output_in_state( + mut self, + state: PinState, + ) -> Pin, P> + where + I: ValidFunction>, + { + match state { + PinState::High => self._set_high(), + PinState::Low => self._set_low(), + } + self.into_push_pull_output() + } + + /// Configure the pin to operate as a readable push pull output. + /// + /// If you want to specify the initial pin state, use [`Pin::into_readable_output_in_state`]. + #[inline] + #[deprecated( + note = "All gpio are readable, use `.into_push_pull_output()` instead.", + since = "0.9.0" + )] + pub fn into_readable_output(self) -> Pin, P> + where + I: ValidFunction>, + { + self.into_function() + } + + /// Configure the pin to operate as a readable push pull output, specifying an initial + /// state which is applied immediately. + #[inline] + #[deprecated( + note = "All gpio are readable, use `.into_push_pull_output_in_state()` instead.", + since = "0.9.0" + )] + pub fn into_readable_output_in_state(self, state: PinState) -> Pin, P> + where + I: ValidFunction>, + { + self.into_push_pull_output_in_state(state) + } + + //============================================================================== + // methods available for all pins. + //============================================================================== + + // ======================= + // Pad related methods + + /// Get the current drive strength of the pin. + #[inline] + pub fn get_drive_strength(&self) -> OutputDriveStrength { + use pac::pads_bank0::gpio::DRIVE_A; + match self.id.pad_ctrl().read().drive().variant() { + DRIVE_A::_2M_A => OutputDriveStrength::TwoMilliAmps, + DRIVE_A::_4M_A => OutputDriveStrength::FourMilliAmps, + DRIVE_A::_8M_A => OutputDriveStrength::EightMilliAmps, + DRIVE_A::_12M_A => OutputDriveStrength::TwelveMilliAmps, + } + } + + /// Set the drive strength for the pin. + #[inline] + pub fn set_drive_strength(&mut self, strength: OutputDriveStrength) { + use pac::pads_bank0::gpio::DRIVE_A; + let variant = match strength { + OutputDriveStrength::TwoMilliAmps => DRIVE_A::_2M_A, + OutputDriveStrength::FourMilliAmps => DRIVE_A::_4M_A, + OutputDriveStrength::EightMilliAmps => DRIVE_A::_8M_A, + OutputDriveStrength::TwelveMilliAmps => DRIVE_A::_12M_A, + }; + self.id.pad_ctrl().modify(|_, w| w.drive().variant(variant)) + } + + /// Get the slew rate for the pin. + #[inline] + pub fn get_slew_rate(&self) -> OutputSlewRate { + if self.id.pad_ctrl().read().slewfast().bit_is_set() { + OutputSlewRate::Fast + } else { + OutputSlewRate::Slow + } + } + + /// Set the slew rate for the pin. + #[inline] + pub fn set_slew_rate(&mut self, rate: OutputSlewRate) { + self.id + .pad_ctrl() + .modify(|_, w| w.slewfast().bit(OutputSlewRate::Fast == rate)); + } + + /// Get whether the schmitt trigger (hysteresis) is enabled. + #[inline] + pub fn get_schmitt_enabled(&self) -> bool { + self.id.pad_ctrl().read().schmitt().bit_is_set() + } + + /// Enable/Disable the schmitt trigger. + #[inline] + pub fn set_schmitt_enabled(&self, enable: bool) { + self.id.pad_ctrl().modify(|_, w| w.schmitt().bit(enable)); + } + + /// Get the state of the digital output circuitry of the pad. + #[inline] + pub fn get_output_disable(&mut self) -> bool { + self.id.pad_ctrl().read().od().bit_is_set() + } + + /// Set the digital output circuitry of the pad. + #[inline] + pub fn set_output_disable(&mut self, disable: bool) { + self.id.pad_ctrl().modify(|_, w| w.od().bit(disable)); + } + + /// Get the state of the digital input circuitry of the pad. + #[inline] + pub fn get_input_enable(&mut self) -> bool { + self.id.pad_ctrl().read().ie().bit_is_set() + } + + /// Set the digital input circuitry of the pad. + #[inline] + pub fn set_input_enable(&mut self, enable: bool) { + self.id.pad_ctrl().modify(|_, w| w.ie().bit(enable)); + } + + // ======================= + // IO related methods + + /// Get the input override. + #[inline] + pub fn get_input_override(&self) -> InputOverride { + use pac::io_bank0::gpio::gpio_ctrl::INOVER_A; + match self.id.io_ctrl().read().inover().variant() { + INOVER_A::NORMAL => InputOverride::Normal, + INOVER_A::INVERT => InputOverride::Invert, + INOVER_A::LOW => InputOverride::AlwaysLow, + INOVER_A::HIGH => InputOverride::AlwaysHigh, + } + } + + /// Set the input override. + #[inline] + pub fn set_input_override(&mut self, override_value: InputOverride) { + use pac::io_bank0::gpio::gpio_ctrl::INOVER_A; + let variant = match override_value { + InputOverride::Normal => INOVER_A::NORMAL, + InputOverride::Invert => INOVER_A::INVERT, + InputOverride::AlwaysLow => INOVER_A::LOW, + InputOverride::AlwaysHigh => INOVER_A::HIGH, + }; + self.id.io_ctrl().modify(|_, w| w.inover().variant(variant)); + } + + /// Get the output enable override. + #[inline] + pub fn get_output_enable_override(&self) -> OutputEnableOverride { + use pac::io_bank0::gpio::gpio_ctrl::OEOVER_A; + match self.id.io_ctrl().read().oeover().variant() { + OEOVER_A::NORMAL => OutputEnableOverride::Normal, + OEOVER_A::INVERT => OutputEnableOverride::Invert, + OEOVER_A::DISABLE => OutputEnableOverride::Disable, + OEOVER_A::ENABLE => OutputEnableOverride::Enable, + } + } + + /// Set the output enable override. + #[inline] + pub fn set_output_enable_override(&mut self, override_value: OutputEnableOverride) { + use pac::io_bank0::gpio::gpio_ctrl::OEOVER_A; + let variant = match override_value { + OutputEnableOverride::Normal => OEOVER_A::NORMAL, + OutputEnableOverride::Invert => OEOVER_A::INVERT, + OutputEnableOverride::Disable => OEOVER_A::DISABLE, + OutputEnableOverride::Enable => OEOVER_A::ENABLE, + }; + self.id.io_ctrl().modify(|_, w| w.oeover().variant(variant)); + } + + /// Get the output override. + #[inline] + pub fn get_output_override(&self) -> OutputOverride { + use pac::io_bank0::gpio::gpio_ctrl::OUTOVER_A; + match self.id.io_ctrl().read().outover().variant() { + OUTOVER_A::NORMAL => OutputOverride::DontInvert, + OUTOVER_A::INVERT => OutputOverride::Invert, + OUTOVER_A::LOW => OutputOverride::AlwaysLow, + OUTOVER_A::HIGH => OutputOverride::AlwaysHigh, + } + } + + /// Set the output override. + #[inline] + pub fn set_output_override(&mut self, override_value: OutputOverride) { + use pac::io_bank0::gpio::gpio_ctrl::OUTOVER_A; + let variant = match override_value { + OutputOverride::DontInvert => OUTOVER_A::NORMAL, + OutputOverride::Invert => OUTOVER_A::INVERT, + OutputOverride::AlwaysLow => OUTOVER_A::LOW, + OutputOverride::AlwaysHigh => OUTOVER_A::HIGH, + }; + self.id + .io_ctrl() + .modify(|_, w| w.outover().variant(variant)); + } + + /// Get the interrupt override. + #[inline] + pub fn get_interrupt_override(&self) -> InterruptOverride { + use pac::io_bank0::gpio::gpio_ctrl::IRQOVER_A; + match self.id.io_ctrl().read().irqover().variant() { + IRQOVER_A::NORMAL => InterruptOverride::Normal, + IRQOVER_A::INVERT => InterruptOverride::Invert, + IRQOVER_A::LOW => InterruptOverride::AlwaysLow, + IRQOVER_A::HIGH => InterruptOverride::AlwaysHigh, + } + } + + /// Set the interrupt override. + #[inline] + pub fn set_interrupt_override(&mut self, override_value: InterruptOverride) { + use pac::io_bank0::gpio::gpio_ctrl::IRQOVER_A; + let variant = match override_value { + InterruptOverride::Normal => IRQOVER_A::NORMAL, + InterruptOverride::Invert => IRQOVER_A::INVERT, + InterruptOverride::AlwaysLow => IRQOVER_A::LOW, + InterruptOverride::AlwaysHigh => IRQOVER_A::HIGH, + }; + self.id + .io_ctrl() + .modify(|_, w| w.irqover().variant(variant)); + } + + // ======================= + // SIO related methods + + #[inline] + #[allow(clippy::bool_comparison)] // more explicit this way + pub(crate) fn _is_low(&self) -> bool { + let mask = self.id.mask(); + self.id.sio_in().read().bits() & mask == 0 + } + + #[inline] + #[allow(clippy::bool_comparison)] // more explicit this way + pub(crate) fn _is_high(&self) -> bool { + !self._is_low() + } + + #[inline] + pub(crate) fn _set_low(&mut self) { + let mask = self.id.mask(); + self.id.sio_out_clr().write(|w| unsafe { w.bits(mask) }); + } + + #[inline] + pub(crate) fn _set_high(&mut self) { + let mask = self.id.mask(); + self.id.sio_out_set().write(|w| unsafe { w.bits(mask) }); + } + + #[inline] + pub(crate) fn _toggle(&mut self) { + let mask = self.id.mask(); + self.id.sio_out_xor().write(|w| unsafe { w.bits(mask) }); + } + + #[inline] + pub(crate) fn _is_set_low(&self) -> bool { + let mask = self.id.mask(); + self.id.sio_out().read().bits() & mask == 0 + } + + #[inline] + pub(crate) fn _is_set_high(&self) -> bool { + !self._is_set_low() + } + + // ======================= + // Interrupt related methods + + /// Clear interrupt. + #[inline] + pub fn clear_interrupt(&mut self, interrupt: Interrupt) { + let (reg, offset) = self.id.intr(); + let mask = interrupt.mask(); + reg.write(|w| unsafe { w.bits(mask << offset) }); + } + + /// Interrupt status. + #[inline] + pub fn interrupt_status(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_ints(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Is interrupt enabled. + #[inline] + pub fn is_interrupt_enabled(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_inte(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Enable or disable interrupt. + #[inline] + pub fn set_interrupt_enabled(&self, interrupt: Interrupt, enabled: bool) { + let (reg, offset) = self.id.proc_inte(Sio::core()); + let mask = interrupt.mask(); + unsafe { + if enabled { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Is interrupt forced. + #[inline] + pub fn is_interrupt_forced(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.proc_intf(Sio::core()); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Force or release interrupt. + #[inline] + pub fn set_interrupt_forced(&self, interrupt: Interrupt, forced: bool) { + let (reg, offset) = self.id.proc_intf(Sio::core()); + let mask = interrupt.mask(); + unsafe { + if forced { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Dormant wake status. + #[inline] + pub fn dormant_wake_status(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_ints(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Is dormant wake enabled. + #[inline] + pub fn is_dormant_wake_enabled(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_inte(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Enable or disable dormant wake. + #[inline] + pub fn set_dormant_wake_enabled(&self, interrupt: Interrupt, enabled: bool) { + let (reg, offset) = self.id.dormant_wake_inte(); + let mask = interrupt.mask(); + unsafe { + if enabled { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Is dormant wake forced. + #[inline] + pub fn is_dormant_wake_forced(&self, interrupt: Interrupt) -> bool { + let (reg, offset) = self.id.dormant_wake_intf(); + let mask = interrupt.mask(); + (reg.read().bits() >> offset) & mask == mask + } + + /// Force dormant wake. + #[inline] + pub fn set_dormant_wake_forced(&mut self, interrupt: Interrupt, forced: bool) { + let (reg, offset) = self.id.dormant_wake_intf(); + let mask = interrupt.mask(); + unsafe { + if forced { + write_bitmask_set(reg.as_ptr(), mask << offset); + } else { + write_bitmask_clear(reg.as_ptr(), mask << offset); + } + } + } + + /// Return a wrapper that implements InputPin. + /// + /// This allows to read from the pin independent of the selected function. + /// Depending on the pad configuration, reading from the pin may not return a + /// meaningful result. + /// + /// Calling this function does not set the pad's input enable bit. + pub fn as_input(&self) -> AsInputPin { + AsInputPin(self) + } +} +impl Pin, P> { + /// Is bypass enabled + #[inline] + pub fn is_sync_bypass(&self) -> bool { + let mask = self.id.mask(); + self.id.proc_in_by_pass().read().bits() & mask == mask + } + + /// Bypass the input sync stages. + /// + /// This saves two clock cycles in the input signal's path at the risks of introducing metastability. + #[inline] + pub fn set_sync_bypass(&mut self, bypass: bool) { + let mask = self.id.mask(); + let reg = self.id.proc_in_by_pass(); + unsafe { + if bypass { + write_bitmask_set(reg.as_ptr(), mask); + } else { + write_bitmask_clear(reg.as_ptr(), mask); + } + } + } +} +impl Pin { + /// Try to return to a type-checked pin id. + /// + /// This method may fail if the target pin id differs from the current dynamic one. + pub fn try_into_pin(self) -> Result, Self> { + if P2::ID == self.id { + Ok(Pin { + id: P2::new(), + function: self.function, + pull_type: self.pull_type, + }) + } else { + Err(self) + } + } + + /// Try to change the pin's function. + pub fn try_into_function(self) -> Result, Pin> + where + F2: func::Function, + { + // Thanks to type-level validation, we know F2 is valid for I + let prev_function = self.function.as_dyn(); + let function = F2::from(prev_function); + let function_as_dyn = function.as_dyn(); + + use func_sealed::Function; + if function_as_dyn.is_valid(&self.id) { + if function_as_dyn != prev_function.as_dyn() { + pin::set_function(&self.id, function_as_dyn); + } + Ok(Pin { + function, + id: self.id, + pull_type: self.pull_type, + }) + } else { + Err(self) + } + } +} +impl Pin { + /// Try to set the pin's function. + /// + /// This method may fail if the requested function is not supported by the pin, eg `FunctionXiP` + /// on a gpio from `Bank0`. + pub fn try_set_function(&mut self, function: DynFunction) -> Result<(), func::InvalidFunction> { + use func_sealed::Function; + if !function.is_valid(&self.id) { + return Err(func::InvalidFunction); + } else if function != self.function.as_dyn() { + pin::set_function(&self.id, function); + self.function = function; + } + Ok(()) + } + + /// Gets the pin's function. + pub fn function(&self) -> DynFunction { + use func_sealed::Function; + self.function.as_dyn() + } +} + +impl Pin { + /// Set the pin's pull type. + pub fn set_pull_type(&mut self, pull_type: DynPullType) { + if pull_type != self.pull_type { + pin::set_pull_type(&self.id, pull_type); + self.pull_type = pull_type; + } + } +} + +/// Wrapper providing input pin functions for GPIO pins independent of the configured mode. +pub struct AsInputPin<'a, I: PinId, F: func::Function, P: PullType>(&'a Pin); + +//============================================================================== +// Embedded-HAL +//============================================================================== + +/// GPIO error type. +pub type Error = core::convert::Infallible; + +impl embedded_hal_0_2::digital::v2::OutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high(); + Ok(()) + } +} + +/// Deprecated: Instead of implicitly implementing InputPin for function SioOutput, +/// use `pin.as_input()` to get access to input values independent of the selected function. +impl embedded_hal_0_2::digital::v2::InputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +impl<'a, I: PinId, F: func::Function, P: PullType> embedded_hal_0_2::digital::v2::InputPin + for AsInputPin<'a, I, F, P> +{ + type Error = core::convert::Infallible; + + fn is_high(&self) -> Result { + Ok(self.0._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.0._is_low()) + } +} + +impl embedded_hal_0_2::digital::v2::StatefulOutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + fn is_set_high(&self) -> Result { + Ok(self._is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self._is_set_low()) + } +} + +impl embedded_hal_0_2::digital::v2::ToggleableOutputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle(); + Ok(()) + } +} +impl embedded_hal_0_2::digital::v2::InputPin for Pin, P> +where + I: PinId, + P: PullType, +{ + type Error = Error; + + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +//============================================================================== +// Pins +//============================================================================== + +/// Default type state of a pin after reset of the pads, io and sio. +pub trait DefaultTypeState: crate::typelevel::Sealed { + /// Default function. + type Function: Function; + /// Default pull type. + type PullType: PullType; +} + +// Clear input enable for pins of bank0. +// Pins 26-29 are ADC pins. If the pins are connected to an analog input, +// the signal level may not be valid for a digital input. Therefore, input +// should be disabled by default. +// For the other GPIO pins, the same setting is applied for consistency. +macro_rules! reset_ie { + ( Bank0, $pads:ident ) => { + for id in (0..=29) { + $pads.gpio(id).modify(|_, w| w.ie().clear_bit()); + } + }; + ( Qspi, $pads:ident ) => {}; +} + +macro_rules! gpio { + ( $bank:ident:$prefix:ident, [ $(($id:expr, $pull_type:ident, $func:ident)),* ] ) => { + paste::paste!{ + #[doc = "Pin bank " [<$bank>] ] + pub mod [<$bank:snake>] { + use $crate::pac::{[],[]}; + use crate::sio::[]; + use super::{Pin, pin, pull, func}; + $(pub use super::pin::[<$bank:lower>]::[<$prefix $id>];)* + + $( + impl super::DefaultTypeState for [<$prefix $id>] { + type Function = super::[]; + type PullType = super::[]; + } + )* + gpio!(struct: $bank $prefix $([<$prefix $id>], $id, $func, $pull_type),*); + + impl Pins { + /// Take ownership of the PAC peripherals and SIO slice and split it into discrete [`Pin`]s + /// + /// This clears the input-enable flag for all Bank0 pads. + pub fn new(io : [], pads: [], sio: [], reset : &mut $crate::pac::RESETS) -> Self { + use $crate::resets::SubsystemReset; + pads.reset_bring_down(reset); + io.reset_bring_down(reset); + + { + use $crate::gpio::pin::DynBankId; + // SAFETY: this function owns the whole bank that will be affected. + let sio = unsafe { &*$crate::pac::SIO::PTR }; + if DynBankId::$bank == DynBankId::Bank0 { + unsafe { + // Put the low pins back to defaults + sio.gpio_oe().reset(); + sio.gpio_out().reset(); + // only reset the GPIOs, not the QSPI bank pins + sio.gpio_hi_oe_clr().write(|w| { + w.bits(0x0000_FFFF); + w + }); + sio.gpio_hi_out_clr().write(|w| { + w.bits(0x0000_FFFF); + w + }); + } + } else { + // only reset the QSPI bank pins not the GPIOs + unsafe { + sio.gpio_hi_oe_clr().write(|w| { + w.bits(0xFFF0_0000); + w + }); + sio.gpio_hi_out_clr().write(|w| { + w.bits(0xFFF0_0000); + w + }); + } + } + } + + io.reset_bring_up(reset); + pads.reset_bring_up(reset); + reset_ie!($bank, pads); + gpio!(members: io, pads, sio, $(([<$prefix $id>], $func, $pull_type)),+) + } + } + } + } + }; + (struct: $bank:ident $prefix:ident $($PXi:ident, $id:expr, $func:ident, $pull_type:ident),*) => { + paste::paste!{ + /// Collection of all the individual [`Pin`]s + pub struct Pins { + _io: [], + _pads: [], + _sio: [], + $( + #[doc = "Pin " [<$PXi>] ] + pub [<$PXi:snake>]: Pin]::[<$prefix $id>] , func::[], pull::[]>, + )* + } + } + }; + (members: $io:ident, $pads:ident, $sio:ident, $(($PXi:ident, $func:ident, $pull_type:ident)),+) => { + paste::paste!{ + Self { + _io: $io, + _pads: $pads, + _sio: $sio, + $( + [<$PXi:snake>]: Pin { + id: [<$PXi>] (()), + function: func::[] (()), + pull_type: pull::[] (()) + }, + )+ + } + } + }; +} + +gpio!( + Bank0: Gpio, + [ + (0, Down, Null), + (1, Down, Null), + (2, Down, Null), + (3, Down, Null), + (4, Down, Null), + (5, Down, Null), + (6, Down, Null), + (7, Down, Null), + (8, Down, Null), + (9, Down, Null), + (10, Down, Null), + (11, Down, Null), + (12, Down, Null), + (13, Down, Null), + (14, Down, Null), + (15, Down, Null), + (16, Down, Null), + (17, Down, Null), + (18, Down, Null), + (19, Down, Null), + (20, Down, Null), + (21, Down, Null), + (22, Down, Null), + (23, Down, Null), + (24, Down, Null), + (25, Down, Null), + (26, Down, Null), + (27, Down, Null), + (28, Down, Null), + (29, Down, Null), + (30, Down, Null), + (31, Down, Null), + (32, Down, Null), + (33, Down, Null), + (34, Down, Null), + (35, Down, Null), + (36, Down, Null), + (37, Down, Null), + (38, Down, Null), + (39, Down, Null), + (40, Down, Null), + (41, Down, Null), + (42, Down, Null), + (43, Down, Null), + (44, Down, Null), + (45, Down, Null), + (46, Down, Null), + (47, Down, Null) + ] +); + +gpio!( + Qspi: Qspi, + [ + (UsbDp, None, Null), + (UsbDm, None, Null), + (Sclk, Down, Null), + (Ss, Up, Null), + (Sd0, None, Null), + (Sd1, None, Null), + (Sd2, None, Null), + (Sd3, None, Null) + ] +); + +pub use bank0::Pins; + +//============================================================================== +// AnyPin +//============================================================================== + +/// Type class for [`Pin`] types. +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Pin`] types. See the `AnyKind` documentation for more details on the +/// pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +/// [type class]: crate::typelevel#type-classes +pub trait AnyPin: Sealed +where + Self: typelevel::Sealed, + Self: typelevel::Is>, +{ + /// [`PinId`] of the corresponding [`Pin`] + type Id: PinId; + /// [`func::Function`] of the corresponding [`Pin`] + type Function: func::Function; + /// [`PullType`] of the corresponding [`Pin`] + type Pull: PullType; +} + +impl Sealed for Pin +where + I: PinId, + F: func::Function, + P: PullType, +{ +} + +impl AnyPin for Pin +where + I: PinId, + F: func::Function, + P: PullType, +{ + type Id = I; + type Function = F; + type Pull = P; +} + +/// Type alias to recover the specific [`Pin`] type from an implementation of [`AnyPin`]. +/// +/// See the [`AnyKind`] documentation for more details on the pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +pub type SpecificPin

= Pin<

::Id,

::Function,

::Pull>; + +//============================================================================== +// bsp_pins helper macro +//============================================================================== + +/// Helper macro to give meaningful names to GPIO pins +/// +/// The normal [`Pins`] struct names each [`Pin`] according to its [`PinId`]. +/// However, BSP authors would prefer to name each [`Pin`] according to its +/// function. This macro defines a new `Pins` struct with custom field names +/// for each [`Pin`]. +/// +/// # Example +/// Calling the macro like this: +/// ```rust +/// use rp235x_hal::bsp_pins; +/// bsp_pins! { +/// #[cfg(feature = "gpio")] +/// Gpio0 { +/// /// Doc gpio0 +/// name: gpio0, +/// aliases: { FunctionPio0, PullNone: PioPin } +/// }, +/// Gpio1 { +/// name: led, +/// aliases: { FunctionPwm, PullDown: LedPwm } +/// }, +/// } +/// ``` +/// +/// Is roughly equivalent to the following source code (excluding the docs strings below): +/// ``` +/// use ::rp235x_hal as hal; +/// use hal::gpio; +/// pub struct Pins { +/// /// Doc gpio0 +/// #[cfg(feature = "gpio")] +/// pub gpio0: gpio::Pin< +/// gpio::bank0::Gpio0, +/// ::Function, +/// ::PullType, +/// >, +/// pub led: gpio::Pin< +/// gpio::bank0::Gpio1, +/// ::Function, +/// ::PullType, +/// >, +/// } +/// impl Pins { +/// #[inline] +/// pub fn new( +/// io: hal::pac::IO_BANK0, +/// pads: hal::pac::PADS_BANK0, +/// sio: hal::sio::SioGpioBank0, +/// reset: &mut hal::pac::RESETS, +/// ) -> Self { +/// let mut pins = gpio::Pins::new(io, pads, sio, reset); +/// Self { +/// #[cfg(feature = "gpio")] +/// gpio0: pins.gpio0, +/// led: pins.gpio1, +/// } +/// } +/// } +/// pub type PioPin = gpio::Pin; +/// pub type LedPwm = gpio::Pin; +/// ``` +#[macro_export] +macro_rules! bsp_pins { + ( + $( + $( #[$id_cfg:meta] )* + $Id:ident { + $( #[$name_doc:meta] )* + name: $name:ident $(,)? + $( + aliases: { + $( + $( #[$alias_cfg:meta] )* + $Function:ty, $PullType:ident: $Alias:ident + ),+ + } + )? + } $(,)? + )+ + ) => { + $crate::paste::paste! { + + /// BSP replacement for the HAL + /// [`Pins`](rp235x_hal::gpio::Pins) type + /// + /// This type is intended to provide more meaningful names for the + /// given pins. + /// + /// To enable specific functions of the pins you can use the + /// [rp235x_hal::gpio::pin::Pin::into_function] function with + /// one of: + /// - [rp235x_hal::gpio::FunctionI2C] + /// - [rp235x_hal::gpio::FunctionPwm] + /// - [rp235x_hal::gpio::FunctionSpi] + /// - [rp235x_hal::gpio::FunctionXip] + /// - [rp235x_hal::gpio::FunctionPio0] + /// - [rp235x_hal::gpio::FunctionPio1] + /// - [rp235x_hal::gpio::FunctionPio2] + /// - [rp235x_hal::gpio::FunctionUart] + /// - [rp235x_hal::gpio::FunctionUartAux] + /// + /// like this: + ///```no_run + /// use rp235x_hal::{pac, gpio::{bank0::Gpio12, Pin, Pins}, sio::Sio}; + /// + /// let mut peripherals = hal::pac::Peripherals::take().unwrap(); + /// let sio = Sio::new(peripherals.SIO); + /// let pins = Pins::new(peripherals.IO_BANK0,peripherals.PADS_BANK0,sio.gpio_bank0, &mut peripherals.RESETS); + /// + /// let _spi_sclk = pins.gpio2.into_function::(); + /// let _spi_mosi = pins.gpio3.into_function::(); + /// let _spi_miso = pins.gpio4.into_function::(); + ///``` + /// + /// **See also [rp235x_hal::gpio] for more in depth information about this**! + pub struct Pins { + $( + $( #[$id_cfg] )* + $( #[$name_doc] )* + pub $name: $crate::gpio::Pin< + $crate::gpio::bank0::$Id, + <$crate::gpio::bank0::$Id as $crate::gpio::DefaultTypeState>::Function, + <$crate::gpio::bank0::$Id as $crate::gpio::DefaultTypeState>::PullType, + >, + )+ + } + + impl Pins { + /// Take ownership of the PAC [`PORT`] and split it into + /// discrete [`Pin`]s. + /// + /// This struct serves as a replacement for the HAL [`Pins`] + /// struct. It is intended to provide more meaningful names for + /// each [`Pin`] in a BSP. Any [`Pin`] not defined by the BSP is + /// dropped. + /// + /// [`Pin`](rp235x_hal::gpio::Pin) + /// [`Pins`](rp235x_hal::gpio::Pins) + #[inline] + pub fn new(io : $crate::pac::IO_BANK0, pads: $crate::pac::PADS_BANK0, sio: $crate::sio::SioGpioBank0, reset : &mut $crate::pac::RESETS) -> Self { + let mut pins = $crate::gpio::Pins::new(io,pads,sio,reset); + Self { + $( + $( #[$id_cfg] )* + $name: pins.[<$Id:lower>], + )+ + } + } + } + $( + $( #[$id_cfg] )* + $crate::bsp_pins!(@aliases, $( $( $( #[$alias_cfg] )* $Id $Function $PullType $Alias )+ )? ); + )+ + } + }; + ( @aliases, $( $( $( #[$attr:meta] )* $Id:ident $Function:ident $PullType:ident $Alias:ident )+ )? ) => { + $crate::paste::paste! { + $( + $( + $( #[$attr] )* + /// Alias for a configured [`Pin`](rp235x_hal::gpio::Pin) + pub type $Alias = $crate::gpio::Pin< + $crate::gpio::bank0::$Id, + $crate::gpio::$Function, + $crate::gpio::$PullType + >; + )+ + )? + } + }; +} + +//============================================================================== +// InOutPin +//============================================================================== + +/// A wrapper [`AnyPin`]`` emulating open-drain function. +/// +/// This wrapper implements both InputPin and OutputPin, to simulate an open-drain pin as needed for +/// example by the wire protocol the DHT11 sensor speaks. +/// +/// +pub struct InOutPin { + inner: Pin, +} + +impl InOutPin { + /// Create a new wrapper + pub fn new(inner: T) -> InOutPin + where + T::Id: ValidFunction, + { + let mut inner = inner.into(); + inner.set_output_enable_override(OutputEnableOverride::Disable); + + // into Pin<_, FunctionSioOutput, _> + let inner = inner.into_push_pull_output_in_state(PinState::Low); + + Self { inner } + } +} + +impl InOutPin +where + T: AnyPin, + T::Id: ValidFunction, +{ + /// Releases the pin reverting to its previous function. + pub fn release(self) -> T { + // restore the previous typestate first + let mut inner = self.inner.reconfigure(); + // disable override + inner.set_output_enable_override(OutputEnableOverride::Normal); + // typelevel-return + T::from(inner) + } +} + +impl embedded_hal_0_2::digital::v2::InputPin for InOutPin { + type Error = Error; + fn is_high(&self) -> Result { + self.inner.is_high() + } + + fn is_low(&self) -> Result { + self.inner.is_low() + } +} + +impl embedded_hal_0_2::digital::v2::OutputPin for InOutPin { + type Error = Error; + fn set_low(&mut self) -> Result<(), Error> { + // The pin is already set to output low but this is inhibited by the override. + self.inner + .set_output_enable_override(OutputEnableOverride::Enable); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Error> { + // To set the open-drain pin to high, just disable the output driver by configuring the + // output override. That way, the DHT11 can still pull the data line down to send its response. + self.inner + .set_output_enable_override(OutputEnableOverride::Disable); + Ok(()) + } +} + +mod eh1 { + use embedded_hal::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin}; + + use super::{ + func, AnyPin, AsInputPin, Error, FunctionSio, InOutPin, OutputEnableOverride, Pin, PinId, + PullType, SioConfig, SioInput, SioOutput, + }; + + impl ErrorType for Pin, P> + where + I: PinId, + P: PullType, + S: SioConfig, + { + type Error = Error; + } + + impl OutputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high(); + Ok(()) + } + } + + impl StatefulOutputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn is_set_high(&mut self) -> Result { + Ok(self._is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok(self._is_set_low()) + } + + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle(); + Ok(()) + } + } + + impl InputPin for Pin, P> + where + I: PinId, + P: PullType, + { + fn is_high(&mut self) -> Result { + Ok(self._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self._is_low()) + } + } + + impl<'a, I, F, P> ErrorType for AsInputPin<'a, I, F, P> + where + I: PinId, + F: func::Function, + P: PullType, + { + type Error = Error; + } + + impl<'a, I: PinId, F: func::Function, P: PullType> InputPin for AsInputPin<'a, I, F, P> { + fn is_high(&mut self) -> Result { + Ok(self.0._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self.0._is_low()) + } + } + + impl ErrorType for InOutPin + where + I: AnyPin, + { + type Error = Error; + } + + impl OutputPin for InOutPin + where + I: AnyPin, + { + fn set_low(&mut self) -> Result<(), Self::Error> { + // The pin is already set to output low but this is inhibited by the override. + self.inner + .set_output_enable_override(OutputEnableOverride::Enable); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + // To set the open-drain pin to high, just disable the output driver by configuring the + // output override. That way, the DHT11 can still pull the data line down to send its response. + self.inner + .set_output_enable_override(OutputEnableOverride::Disable); + Ok(()) + } + } + + impl InputPin for InOutPin + where + I: AnyPin, + { + fn is_high(&mut self) -> Result { + Ok(self.inner._is_high()) + } + + fn is_low(&mut self) -> Result { + Ok(self.inner._is_low()) + } + } +} diff --git a/rp235x-hal/src/gpio/pin.rs b/rp235x-hal/src/gpio/pin.rs new file mode 100644 index 000000000..bc669297f --- /dev/null +++ b/rp235x-hal/src/gpio/pin.rs @@ -0,0 +1,214 @@ +//! ## Note 1 +//! +//! QSPI registers are ordered differently on pads, io & SIO: +//! - PADS: sclk, sd0, sd1, sd2, sd3, ss +//! - IO bank: sclk, ss, sd0, sd1, sd2, sd3 +//! - SIO: sclk, ss, sd0, sd1, sd2, sd3 +//! +//! This HAL will use the order shared by IO bank & SIO. The main reason for that being the bit +//! shift operation used in SIO and interrupt related registers. +//! +//! ## Note 2 +//! +//! The SWD and SWCLK pin only appear on the pad control and cannot be used as gpio. +//! They are therefore absent from this implementation. +//! +//! ## Note 3 +//! +//! Dues to limitations in svd2rust and svdtools (and their shared dependencies) it is not possible +//! to fully express the relations between the gpio registers in the different banks on the rp235x +//! at the PAC level. +//! +//! These limitations are respectively: +//! - Inability to derive register with different reset values +//! - Inability to derive from path including clusters and/or arrays +//! +//! This modules bridges that gap by adding a trait definition per register type and implementing it +//! for each of the relevant registers. + +use crate::typelevel::Sealed; + +use super::{DynFunction, DynPullType}; + +pub(crate) mod pin_sealed; + +/// Value-level `enum` for the pin's bank. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum DynBankId { + /// GPIO Pins bank + Bank0, + /// QSPI Pins bank + Qspi, +} + +/// Type-level `enum` for the pin's bank ID. +pub trait BankId: Sealed {} + +/// Type-level `variant` of `BankId` +pub struct BankBank0; +impl Sealed for BankBank0 {} +impl BankId for BankBank0 {} + +/// Type-level `variant` of `BankId` +pub struct BankQspi; +impl Sealed for BankQspi {} +impl BankId for BankQspi {} + +/// Type-level `enum` for the pin Id (pin number + bank). +pub trait PinId: pin_sealed::PinIdOps { + /// This pin as a `DynPinId`. + fn as_dyn(&self) -> DynPinId; +} + +/// Value-level representation for the pin (bank + id). +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct DynPinId { + /// Pin bank. + pub bank: DynBankId, + /// Pin number. + pub num: u8, +} +impl PinId for DynPinId { + #[inline] + fn as_dyn(&self) -> DynPinId { + *self + } +} + +macro_rules! pin_ids { + ($bank:ident: $($id:expr;$name:ident),*) => { + pin_ids!($bank as $bank: $($id;$name),*); + }; + ($bank:ident as $prefix:ident: $($id:tt),*) => { + pin_ids!($bank as $prefix: $($id;$id),*); + }; + ($bank:ident as $prefix:ident: $($id:expr;$name:tt),*) => { + paste::paste!{ + $( + #[doc = "Type level variant for the pin `" $name "` in bank `" $prefix "`."] + pub struct [<$prefix $name>] (pub(crate) ()); + impl crate::typelevel::Sealed for [<$prefix $name>] {} + impl PinId for [<$prefix $name>] { + #[inline] + fn as_dyn(&self) -> DynPinId { + DynPinId { + bank: DynBankId::$bank, + num: $id + } + } + } + impl pin_sealed::TypeLevelPinId for [<$prefix $name>] { + type Bank = []; + + const ID: DynPinId = DynPinId { + bank: DynBankId::$bank, + num: $id + }; + + fn new() -> Self { + Self(()) + } + } + )* + } + }; +} + +/// The bank with all the GPIOs +/// +/// GPIOs 0 through 29 are available in all package variants. GPIOs 30 through +/// 47 are available only on the QFN-80 (RP2350B) package. +pub mod bank0 { + use super::{pin_sealed, BankBank0, DynBankId, DynPinId, PinId}; + pin_ids!( + Bank0 as Gpio: + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10,11,12,13,14,15,16,17,18,19, + 20,21,22,23,24,25,26,27,28,29, + 30,31,32,33,34,35,36,37,38,39, + 40,41,42,43,44,45,46,47 + ); +} + +/// The bank with the QSPI related pins. +/// +/// These should all have IDs over 32, because they are all controlled by the +/// hi registers. +pub mod qspi { + use super::{pin_sealed, BankQspi, DynBankId, DynPinId, PinId}; + pin_ids!(Qspi: 56;UsbDp, 57;UsbDm, 58;Sclk, 59;Ss, 60;Sd0, 61;Sd1, 62;Sd2, 63;Sd3); +} + +pub(crate) fn set_function(pin: &P, function: DynFunction) { + use crate::pac::io_bank0::gpio::gpio_ctrl::FUNCSEL_A; + let funcsel = match function { + // The XIP function has a value of 0, which on bank0 is called JTAG. + DynFunction::Xip => FUNCSEL_A::JTAG, + DynFunction::Spi => FUNCSEL_A::SPI, + DynFunction::Uart => FUNCSEL_A::UART, + DynFunction::I2c => FUNCSEL_A::I2C, + DynFunction::Pwm => FUNCSEL_A::PWM, + DynFunction::Sio(sio) => { + let mask = pin.mask(); + match sio { + crate::gpio::DynSioConfig::Input => { + pin.sio_oe_clr().write(|w| unsafe { w.bits(mask) }); + } + crate::gpio::DynSioConfig::Output => { + pin.sio_oe_set().write(|w| unsafe { w.bits(mask) }); + } + } + + FUNCSEL_A::SIO + } + DynFunction::Pio0 => FUNCSEL_A::PIO0, + DynFunction::Pio1 => FUNCSEL_A::PIO1, + DynFunction::Pio2 => FUNCSEL_A::PIO2, + DynFunction::Clock => FUNCSEL_A::GPCK, + DynFunction::Usb => FUNCSEL_A::USB, + DynFunction::UartAux => FUNCSEL_A::UART_AUX, + DynFunction::Null => FUNCSEL_A::NULL, + }; + + if funcsel != FUNCSEL_A::NULL { + pin.pad_ctrl().modify(|_, w| { + // Set input enable on, output disable off + // RP2350: input enable defaults to off, so this is important! + w.ie().set_bit(); + w.od().clear_bit(); + // RP2350: remove pad isolation now a function is wired up + w.iso().clear_bit(); + w + }); + } else { + pin.pad_ctrl().modify(|_, w| { + // Set input enable off, output disable on + w.ie().clear_bit(); + w.od().set_bit(); + // RP2350: isolate pad + w.iso().set_bit(); + w + }); + } + + // Zero all fields apart from fsel; we want this IO to do what the peripheral tells it. + // This doesn't affect e.g. pullup/pulldown, as these are in pad controls. + unsafe { + pin.io_ctrl() + .write_with_zero(|w| w.funcsel().variant(funcsel)); + } +} + +pub(crate) fn set_pull_type(pin: &P, pull_type: DynPullType) { + let (pue, pde) = match pull_type { + DynPullType::None => (false, false), + DynPullType::Up => (true, false), + DynPullType::Down => (false, true), + DynPullType::BusKeep => (true, true), + }; + + pin.pad_ctrl() + .modify(|_, w| w.pue().bit(pue).pde().bit(pde)); +} diff --git a/rp235x-hal/src/gpio/pin/pin_sealed.rs b/rp235x-hal/src/gpio/pin/pin_sealed.rs new file mode 100644 index 000000000..12ec0c9f8 --- /dev/null +++ b/rp235x-hal/src/gpio/pin/pin_sealed.rs @@ -0,0 +1,279 @@ +use super::{DynBankId, DynPinId}; +use crate::{pac, sio::CoreId}; + +pub trait TypeLevelPinId: super::PinId { + type Bank: super::BankId; + + const ID: DynPinId; + + fn new() -> Self; +} + +pub trait PinIdOps { + /// Get the pin's 32-bit bit mask. + /// + /// Note there are more than 32 GPIOs so some have the same mask. + /// + /// The other methods in this trait will return a reference to the relevant + /// high or low register. + fn mask(&self) -> u32; + /// Is this GPIO in the high set (i.e. >= 32)? + fn is_hi(&self) -> bool; + /// Get the GPIO status register, either hi or low. + fn io_status(&self) -> &pac::io_bank0::gpio::GPIO_STATUS; + /// Get the GPIO control register, either hi or low. + fn io_ctrl(&self) -> &pac::io_bank0::gpio::GPIO_CTRL; + /// Get the Pad control register, either hi or low. + fn pad_ctrl(&self) -> &pac::pads_bank0::GPIO; + + /// Get the SIO Input register, either hi or low. + fn sio_in(&self) -> &pac::sio::GPIO_IN; + /// Get the SIO Output register, either hi or low. + fn sio_out(&self) -> &pac::sio::GPIO_OUT; + /// Get the SIO Output Set register, either hi or low. + fn sio_out_set(&self) -> &pac::sio::GPIO_OUT_SET; + /// Get the SIO Output Clear register, either hi or low. + fn sio_out_clr(&self) -> &pac::sio::GPIO_OUT_CLR; + /// Get the SIO Output XOR register, either hi or low. + fn sio_out_xor(&self) -> &pac::sio::GPIO_OUT_XOR; + /// Get the SIO Output Enable register, either hi or low. + fn sio_oe(&self) -> &pac::sio::GPIO_OE; + /// Get the SIO Output Enable Set register, either hi or low. + fn sio_oe_set(&self) -> &pac::sio::GPIO_OE_SET; + /// Get the SIO Output Enable Clear register, either hi or low. + fn sio_oe_clr(&self) -> &pac::sio::GPIO_OE_CLR; + /// Get the SIO Output Enable XOR register, either hi or low. + fn sio_oe_xor(&self) -> &pac::sio::GPIO_OE_XOR; + /// Get the SYSCFG Input Synchroniser Bypass enable register, either hi or lo + fn proc_in_by_pass(&self) -> &pac::syscfg::PROC_IN_SYNC_BYPASS; + + /// Raw Interrupt state. + /// + /// Get the raw interrupt register, and which bit in that register this GPIO + /// corresponds to. + fn intr(&self) -> (&pac::io_bank0::INTR, usize); + /// Interrupt Status for the given Core. + /// + /// Get the interrupt status register, and which bit in that register this + /// pin corresponds to. + fn proc_ints(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTS, usize); + /// Interrupt Enable for the given Core. + /// + /// Get the interrupt enable register, and which bit in that register this + /// pin corresponds to. + fn proc_inte(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTE, usize); + /// Interrupt Force for the given Core. + /// + /// Get the interrupt force register, and which bit in that register this + /// pin corresponds to. + fn proc_intf(&self, proc: CoreId) -> (&pac::io_bank0::PROC0_INTF, usize); + /// Interrupt Status for Dormant Wake. + /// + /// Get the interrupt status register, and which bit in that register this + /// pin corresponds to. + fn dormant_wake_ints(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTS, usize); + /// Interrupt Enable for Dormant Wake. + /// + /// Get the interrupt enable register, and which bit in that register this + /// pin corresponds to. + fn dormant_wake_inte(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTE, usize); + /// Interrupt Force for Dormant Wake. + /// + /// Get the interrupt force register, and which bit in that register this + /// pin corresponds to. + fn dormant_wake_intf(&self) -> (&pac::io_bank0::DORMANT_WAKE_INTF, usize); +} + +macro_rules! accessor_fns { + (sio $reg:ident) => { + paste::paste! { + fn [](&self) -> &$crate::pac::sio::[] { + let pin = self.as_dyn(); + unsafe { + let sio = &*$crate::pac::SIO::PTR; + match (self.is_hi(), pin.bank) { + // The first 32 GPIOs are in have registers called `gpio_xxx` + (false, DynBankId::Bank0) => sio.[](), + // The rest are controlled by `gpio_hi_xxx` + _ => core::mem::transmute::<&$crate::pac::sio::[],&$crate::pac::sio::[]>(sio.[]()), + } + } + } + } + }; + (io $reg:ident) => { + paste::paste! { + fn [](&self) -> &$crate::pac::io_bank0::gpio::[] { + let pin = self.as_dyn(); + match pin.bank { + DynBankId::Bank0 => { + let gpio = unsafe { &*$crate::pac::IO_BANK0::PTR }; + gpio.gpio(usize::from(pin.num)).[]() + } + DynBankId::Qspi => unsafe { + let qspi = &*$crate::pac::IO_QSPI::PTR; + core::mem::transmute::<&$crate::pac::io_qspi::gpio_qspi::[], &$crate::pac::io_bank0::gpio::[]>(qspi.gpio_qspi(usize::from(pin.num)).[]()) + }, + } + } + } + }; + (int $reg:ident) => { + paste::paste! { + fn [](&self, proc: CoreId) -> (&$crate::pac::io_bank0::[], usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*$crate::pac::IO_BANK0::PTR; + match proc { + CoreId::Core0 => bank.[](usize::from(index)), + CoreId::Core1 => core::mem::transmute::<&$crate::pac::io_bank0::[], &$crate::pac::io_bank0::[]>(bank.[](usize::from(index))), + } + } + DynBankId::Qspi => { + let bank = &*$crate::pac::IO_QSPI::PTR; + match proc { + CoreId::Core0 => core::mem::transmute::<&$crate::pac::io_qspi::[], &$crate::pac::io_bank0::[]>(bank.[]()), + CoreId::Core1 => core::mem::transmute::<&$crate::pac::io_qspi::[], &$crate::pac::io_bank0::[]>(bank.[]()), + } + } + }; + (reg, usize::from(offset)) + } + } + } + }; + (dormant $reg:ident) => { + paste::paste! { + fn [< dormant_wake_ $reg:lower>](&self) -> (&$crate::pac::io_bank0::[< DORMANT_WAKE_ $reg:upper >], usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*$crate::pac::IO_BANK0::PTR; + bank.[< dormant_wake_ $reg:lower>](usize::from(index)) + } + DynBankId::Qspi => { + let bank = &*$crate::pac::IO_QSPI::PTR; + core::mem::transmute::<&$crate::pac::io_qspi::[< DORMANT_WAKE_ $reg:upper >], &$crate::pac::io_bank0::[< DORMANT_WAKE_ $reg:upper >]>(bank.[< dormant_wake_ $reg:lower>]()) + } + }; + (reg, usize::from(offset)) + } + } + } + }; +} +impl PinIdOps for T +where + T: super::PinId, +{ + fn mask(&self) -> u32 { + let mask_bit = self.as_dyn().num % 32; + 1 << mask_bit + } + + fn is_hi(&self) -> bool { + self.as_dyn().num >= 32 + } + + fn pad_ctrl(&self) -> &pac::pads_bank0::GPIO { + let pin = self.as_dyn(); + match pin.bank { + DynBankId::Bank0 => { + let gpio = unsafe { &*pac::PADS_BANK0::PTR }; + gpio.gpio(usize::from(pin.num)) + } + DynBankId::Qspi => unsafe { + let qspi = &*pac::PADS_QSPI::PTR; + use rp235x_pac::{generic::Reg, pads_bank0, pads_qspi}; + match pin.num { + 0 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sclk()), + 1 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_ss()), + 2 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd0()), + 3 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd1()), + 4 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd2()), + 5 => core::mem::transmute::< + &Reg, + &Reg, + >(qspi.gpio_qspi_sd3()), + _ => unreachable!("Invalid QSPI bank pin number."), + } + }, + } + } + accessor_fns!(io ctrl); + accessor_fns!(io status); + + accessor_fns!(sio in); + accessor_fns!(sio out); + accessor_fns!(sio out_set); + accessor_fns!(sio out_clr); + accessor_fns!(sio out_xor); + accessor_fns!(sio oe); + accessor_fns!(sio oe_set); + accessor_fns!(sio oe_clr); + accessor_fns!(sio oe_xor); + + fn proc_in_by_pass(&self) -> &pac::syscfg::PROC_IN_SYNC_BYPASS { + let pin = self.as_dyn(); + unsafe { + let syscfg = &*pac::SYSCFG::PTR; + match (pin.is_hi(), pin.bank) { + (false, DynBankId::Bank0) => syscfg.proc_in_sync_bypass(), + _ => core::mem::transmute::< + &pac::syscfg::PROC_IN_SYNC_BYPASS_HI, + &pac::syscfg::PROC_IN_SYNC_BYPASS, + >(syscfg.proc_in_sync_bypass_hi()), + } + } + } + + fn intr(&self) -> (&pac::io_bank0::INTR, usize) { + let pin = self.as_dyn(); + let (index, offset) = (pin.num / 8, pin.num % 8 * 4); + unsafe { + let reg = match pin.bank { + DynBankId::Bank0 => { + let bank = &*pac::IO_BANK0::PTR; + bank.intr(usize::from(index)) + } + DynBankId::Qspi => { + // Here, index will always be 0 as there are only 6 GPIOs in + // this bank. Transmuting the io_qspi::INTR type is OK + // because it has the same layout as the io_bank0::INTR type. + let bank = &*pac::IO_QSPI::PTR; + core::mem::transmute::<&pac::io_qspi::INTR, &pac::io_bank0::INTR>(bank.intr()) + } + }; + + (reg, usize::from(offset)) + } + } + + accessor_fns!(int ints); + accessor_fns!(int inte); + accessor_fns!(int intf); + + accessor_fns!(dormant ints); + accessor_fns!(dormant inte); + accessor_fns!(dormant intf); +} diff --git a/rp235x-hal/src/gpio/pin_group.rs b/rp235x-hal/src/gpio/pin_group.rs new file mode 100644 index 000000000..5fe3a7dc9 --- /dev/null +++ b/rp235x-hal/src/gpio/pin_group.rs @@ -0,0 +1,190 @@ +//! Pin Groups +//! +//! Lets you set multiple GPIOs simultaneously. + +use embedded_hal::digital::PinState; +use frunk::{hlist::Plucker, HCons, HNil}; + +use crate::typelevel::Sealed; + +use super::{ + pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionSio, FunctionSioInput, FunctionSioOutput, Pin, + PinId, PullType, SioConfig, +}; + +/// Generate a read mask for a pin list. +pub trait ReadPinHList: Sealed { + /// Generate a mask for a pin list. + fn read_mask(&self) -> u32; +} +impl ReadPinHList for HNil { + fn read_mask(&self) -> u32 { + 0 + } +} +impl ReadPinHList for HCons { + fn read_mask(&self) -> u32 { + (1 << self.head.borrow().id().num) | self.tail.read_mask() + } +} + +/// Generate a write mask for a pin list. +pub trait WritePinHList: Sealed { + /// Generate a mask for a pin list. + fn write_mask(&self) -> u32; +} +impl WritePinHList for HNil { + fn write_mask(&self) -> u32 { + 0 + } +} +impl WritePinHList + for HCons, T> +{ + fn write_mask(&self) -> u32 { + // This is an input pin, so don't include it in write_mask + self.tail.write_mask() + } +} +impl WritePinHList + for HCons, T> +{ + fn write_mask(&self) -> u32 { + (1 << self.head.id().num) | self.tail.write_mask() + } +} + +/// A group of pins to be controlled together and guaranty single cycle control of several pins. +/// +/// ```no_run +/// # macro_rules! defmt { ($($a:tt)*) => {}} +/// use rp235x_hal::{ +/// self as hal, +/// gpio::{bank0::Gpio12, Pin, PinGroup, PinState, Pins}, +/// sio::Sio, +/// }; +/// +/// let mut peripherals = hal::pac::Peripherals::take().unwrap(); +/// let sio = Sio::new(peripherals.SIO); +/// let pins = Pins::new( +/// peripherals.IO_BANK0, +/// peripherals.PADS_BANK0, +/// sio.gpio_bank0, +/// &mut peripherals.RESETS, +/// ); +/// +/// let group = PinGroup::new(); +/// let group = group.add_pin(pins.gpio0.into_pull_up_input()); +/// let mut group = group.add_pin(pins.gpio4.into_push_pull_output_in_state(PinState::High)); +/// +/// defmt!("Group's state is: {}", group.read()); +/// group.toggle(); +/// defmt!("Group's state is: {}", group.read()); +/// ``` +pub struct PinGroup(T); +impl PinGroup { + /// Creates an empty pin group. + pub fn new() -> Self { + PinGroup(HNil) + } + + /// Add a pin to the group. + pub fn add_pin(self, pin: P) -> PinGroup> + where + C: SioConfig, + P: AnyPin>, + P::Id: TypeLevelPinId, + { + PinGroup(HCons { + head: pin, + tail: self.0, + }) + } +} +impl PinGroup> +where + H::Id: TypeLevelPinId, + H: AnyPin, +{ + /// Add a pin to the group. + pub fn add_pin(self, pin: P) -> PinGroup>> + where + C: SioConfig, + P: AnyPin>, + P::Id: TypeLevelPinId::Bank>, + { + PinGroup(HCons { + head: pin, + tail: self.0, + }) + } + + /// Pluck a pin from the group. + #[allow(clippy::type_complexity)] + pub fn remove_pin( + self, + ) -> (P, PinGroup< as Plucker>::Remainder>) + where + HCons: Plucker, + { + let (p, rest): (P, _) = self.0.pluck(); + (p, PinGroup(rest)) + } +} +impl PinGroup> +where + HCons: ReadPinHList + WritePinHList, + H: AnyPin, +{ + /// Read the whole group at once. + /// + /// The returned value is a bit field where each pin populates its own index. Therefore, there + /// might be "holes" in the value. Unoccupied bits will always read as 0. + /// + /// For example, if the group contains Gpio1 and Gpio3, a read may yield: + /// ```text + /// 0b0000_0000__0000_0000__0000_0000__0000_1010 + /// This is Gpio3 ↑↑↑ + /// Gpio2 is not used || + /// This is Gpio1 | + /// ``` + pub fn read(&self) -> u32 { + let mask = self.0.read_mask(); + crate::sio::Sio::read_bank0() & mask + } + + /// Write this set of pins all at the same time. + /// + /// This only affects output pins. Input pins in the + /// set are ignored. + pub fn set(&mut self, state: PinState) { + use super::pin::pin_sealed::PinIdOps; + let mask = self.0.write_mask(); + let head_id = self.0.head.borrow().id(); + if state == PinState::Low { + head_id.sio_out_clr().write(|w| unsafe { w.bits(mask) }); + } else { + head_id.sio_out_set().write(|w| unsafe { w.bits(mask) }); + } + } + + /// Toggles this set of pins all at the same time. + /// + /// This only affects output pins. Input pins in the + /// set are ignored. + pub fn toggle(&mut self) { + use super::pin::pin_sealed::PinIdOps; + let mask = self.0.write_mask(); + self.0 + .head + .borrow() + .id() + .sio_out_xor() + .write(|w| unsafe { w.bits(mask) }); + } +} +impl Default for PinGroup { + fn default() -> Self { + Self::new() + } +} diff --git a/rp235x-hal/src/gpio/pull.rs b/rp235x-hal/src/gpio/pull.rs new file mode 100644 index 000000000..fcd1d9b26 --- /dev/null +++ b/rp235x-hal/src/gpio/pull.rs @@ -0,0 +1,63 @@ +use paste::paste; + +pub(crate) mod pull_sealed { + use super::DynPullType; + + pub trait PullType { + fn from(pm: DynPullType) -> Self; + fn as_dyn(&self) -> DynPullType; + } +} +/// Type-level `enum` for pull resistor types. +pub trait PullType: pull_sealed::PullType {} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +/// Value-level `enum` for pull resistor types. +pub enum DynPullType { + /// No pull enabled. + None, + /// Enable pull up. + Up, + /// Enable pull down. + Down, + /// This enables bus-keep mode. + /// + /// This is not documented in the datasheet but described in the + /// [c-sdk](https://github.com/raspberrypi/pico-sdk/blob/e7267f99febc70486923e17a8210088af058c915/src/rp2_common/hardware_gpio/gpio.c#L53) + /// as: + /// + /// > […] on rp235x, setting both pulls enables a "bus keep" function, + /// > i.e. weak pull to whatever is current high/low state of GPIO. + BusKeep, +} + +impl PullType for DynPullType {} +impl pull_sealed::PullType for DynPullType { + fn from(pm: DynPullType) -> Self { + pm + } + + fn as_dyn(&self) -> DynPullType { + *self + } +} + +macro_rules! pin_pull_type { + ($($pull_type:ident),*) => { + $(paste! { + /// Type-level `variant` of [`PullType`]. + pub struct [](pub(super) ()); + impl PullType for [] {} + impl pull_sealed::PullType for [] { + fn from(_pm: DynPullType) -> Self { + Self(()) + } + fn as_dyn(&self) -> DynPullType { + DynPullType::[<$pull_type>] + } + } + })* + }; +} +pin_pull_type!(None, Up, Down, BusKeep); diff --git a/rp235x-hal/src/i2c.rs b/rp235x-hal/src/i2c.rs new file mode 100644 index 000000000..462724749 --- /dev/null +++ b/rp235x-hal/src/i2c.rs @@ -0,0 +1,415 @@ +//! Inter-Integrated Circuit (I2C) bus +//! +//! See [Section 12.2](https://rptl.io/rp2350-datasheet#section_i2c) for more details +//! +//! ## Usage +//! ```no_run +//! use fugit::RateExtU32; +//! use rp235x_hal::{self as hal, gpio::Pins, i2c::I2C, Sio}; +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! +//! let mut i2c = I2C::i2c1( +//! peripherals.I2C1, +//! pins.gpio18.reconfigure(), // sda +//! pins.gpio19.reconfigure(), // scl +//! 400.kHz(), +//! &mut peripherals.RESETS, +//! 125_000_000.Hz(), +//! ); +//! +//! // Scan for devices on the bus by attempting to read from them +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Read; +//! for i in 0..=127u8 { +//! let mut readbuf: [u8; 1] = [0; 1]; +//! let result = i2c.read(i, &mut readbuf); +//! if let Ok(d) = result { +//! // Do whatever work you want to do with found devices +//! // writeln!(uart, "Device found at address{:?}", i).unwrap(); +//! } +//! } +//! +//! // Write some data to a device at 0x2c +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_Write; +//! i2c.write(0x2Cu8, &[1, 2, 3]).unwrap(); +//! +//! // Write and then read from a device at 0x3a +//! use embedded_hal_0_2::prelude::_embedded_hal_blocking_i2c_WriteRead; +//! let mut readbuf: [u8; 1] = [0; 1]; +//! i2c.write_read(0x2Cu8, &[1, 2, 3], &mut readbuf).unwrap(); +//! ``` +//! +//! See [examples/i2c.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/i2c.rs) +//! for a complete example +//! +//! ## Async Usage +//! +//! See [examples/i2c_async.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/i2c_async.rs) +//! and [examples/i2c_async_irq.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/i2c_async_irq.rs) +//! for complete examples. + +use core::{marker::PhantomData, ops::Deref}; +use fugit::HertzU32; +use rp235x_pac::i2c0::ic_con::IC_10BITADDR_SLAVE_A; + +use crate::{ + gpio::{bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionI2c, PullUp}, + pac::{ + i2c0::{ic_con::IC_10BITADDR_MASTER_A, RegisterBlock}, + I2C0, I2C1, RESETS, + }, + resets::SubsystemReset, + typelevel::Sealed, +}; + +mod controller; +pub mod peripheral; + +/// Pac I2C device +pub trait I2cDevice: Deref + SubsystemReset + Sealed { + /// Index of the peripheral. + const ID: usize; +} +impl Sealed for I2C0 {} +impl I2cDevice for I2C0 { + const ID: usize = 0; +} +impl Sealed for I2C1 {} +impl I2cDevice for I2C1 { + const ID: usize = 1; +} + +/// Marks valid/supported address types +pub trait ValidAddress: + Into + embedded_hal::i2c::AddressMode + embedded_hal_0_2::blocking::i2c::AddressMode + Copy +{ + /// Variant for the IC_CON.10bitaddr_master field + const BIT_ADDR_M: IC_10BITADDR_MASTER_A; + /// Variant for the IC_CON.10bitaddr_slave field + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A; + + /// Validates the address against address ranges supported by the hardware. + fn is_valid(self) -> Result<(), Error>; +} +impl ValidAddress for u8 { + const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_7BITS; + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_7BITS; + + fn is_valid(self) -> Result<(), Error> { + if self >= 0x80 { + Err(Error::AddressOutOfRange(self.into())) + } else { + Ok(()) + } + } +} +impl ValidAddress for u16 { + const BIT_ADDR_M: IC_10BITADDR_MASTER_A = IC_10BITADDR_MASTER_A::ADDR_10BITS; + const BIT_ADDR_S: IC_10BITADDR_SLAVE_A = IC_10BITADDR_SLAVE_A::ADDR_10BITS; + + fn is_valid(self) -> Result<(), Error> { + Ok(()) + } +} + +/// I2C error +#[non_exhaustive] +pub enum Error { + /// I2C abort with error + Abort(u32), + /// User passed in a read buffer that was 0 length + /// + /// This is a limitation of the rp235x I2C peripheral. + /// If the slave ACKs its address, the I2C peripheral must read + /// at least one byte before sending the STOP condition. + InvalidReadBufferLength, + /// User passed in a write buffer that was 0 length + /// + /// This is a limitation of the rp235x I2C peripheral. + /// If the slave ACKs its address, the I2C peripheral must write + /// at least one byte before sending the STOP condition. + InvalidWriteBufferLength, + /// Target i2c address is out of range + AddressOutOfRange(u16), + /// Target i2c address is reserved + AddressReserved(u16), +} + +impl core::fmt::Debug for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use embedded_hal::i2c::Error as _; + match self { + Error::InvalidReadBufferLength => write!(fmt, "InvalidReadBufferLength"), + Error::InvalidWriteBufferLength => write!(fmt, "InvalidWriteBufferLength"), + Error::AddressOutOfRange(addr) => write!(fmt, "AddressOutOfRange({:x})", addr), + Error::AddressReserved(addr) => write!(fmt, "AddressReserved({:x})", addr), + Error::Abort(_) => { + write!(fmt, "{:?}", self.kind()) + } + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Error { + fn format(&self, fmt: defmt::Formatter) { + use embedded_hal::i2c::Error as _; + match self { + Error::InvalidReadBufferLength => defmt::write!(fmt, "InvalidReadBufferLength"), + Error::InvalidWriteBufferLength => defmt::write!(fmt, "InvalidWriteBufferLength"), + Error::AddressOutOfRange(addr) => defmt::write!(fmt, "AddressOutOfRange({:x})", addr), + Error::AddressReserved(addr) => defmt::write!(fmt, "AddressReserved({:x})", addr), + Error::Abort(_) => { + defmt::write!(fmt, "{}", defmt::Debug2Format(&self.kind())) + } + } + } +} + +impl embedded_hal::i2c::Error for Error { + fn kind(&self) -> embedded_hal::i2c::ErrorKind { + match &self { + Error::Abort(v) if v & 1<<12 != 0 // ARB_LOST + => embedded_hal::i2c::ErrorKind::ArbitrationLoss, + Error::Abort(v) if v & 1<<7 != 0 // ABRT_SBYTE_ACKDET + => embedded_hal::i2c::ErrorKind::Bus, + Error::Abort(v) if v & 1<<6 != 0 // ABRT_HS_ACKDET + => embedded_hal::i2c::ErrorKind::Bus, + Error::Abort(v) if v & 1<<4 != 0 // ABRT_GCALL_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<3 != 0 // ABRT_TXDATA_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Data), + Error::Abort(v) if v & 1<<2 != 0 // ABRT_10ADDR2_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<1 != 0 // ABRT_10ADDR1_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + Error::Abort(v) if v & 1<<0 != 0 // ABRT_7B_ADDR_NOACK + => embedded_hal::i2c::ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Address), + _ => embedded_hal::i2c::ErrorKind::Other, + } + } +} + +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Marker for PinId that can serve as " $p] + pub trait []: Sealed {} + + #[doc = "Valid " $p] + pub trait []: Sealed {} + + impl [] for T + where + T: AnyPin, + T::Id: [], + { + } + + #[doc = "A runtime validated " $p " pin for I2C."] + pub struct [](P, PhantomData); + impl Sealed for [] {} + impl [] for [] {} + impl [] + where + P: AnyPin, + S: I2cDevice, + { + /// Validate a pin's function on a i2c peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that I2C."] + pub fn validate(p: P, _u: &S) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, S::ID)) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} + +pin_validation!(Scl, Sda); + +macro_rules! valid_pins { + ($($i2c:ident: { + sda: [$($sda:ident),*], + scl: [$($scl:ident),*] + }),*) => { + $( + $(impl ValidPinIdSda<$i2c> for $sda {})* + $(impl ValidPinIdScl<$i2c> for $scl {})* + )* + + const SDA: &[(u8, usize)] = &[$($(($sda::ID.num, $i2c::ID)),*),*]; + const SCL: &[(u8, usize)] = &[$($(($scl::ID.num, $i2c::ID)),*),*]; + }; +} +valid_pins! { + I2C0: { + sda: [Gpio0, Gpio4, Gpio8, Gpio12, Gpio16, Gpio20, Gpio24, Gpio28], + scl: [Gpio1, Gpio5, Gpio9, Gpio13, Gpio17, Gpio21, Gpio25, Gpio29] + }, + I2C1: { + sda: [Gpio2, Gpio6, Gpio10, Gpio14, Gpio18, Gpio22, Gpio26], + scl: [Gpio3, Gpio7, Gpio11, Gpio15, Gpio19, Gpio23, Gpio27] + } +} + +/// Operational mode of the I2C peripheral. +pub trait I2CMode: Sealed { + /// Indicates whether this mode is Controller or Peripheral. + const IS_CONTROLLER: bool; +} + +/// Marker for an I2C peripheral operating as a controller. +pub struct Controller {} +impl Sealed for Controller {} +impl I2CMode for Controller { + const IS_CONTROLLER: bool = true; +} + +/// Marker for an I2C block operating as a peripehral. +pub struct Peripheral { + state: peripheral::State, +} +impl Sealed for Peripheral {} +impl I2CMode for Peripheral { + const IS_CONTROLLER: bool = false; +} + +/// I2C peripheral +pub struct I2C { + i2c: I2C, + pins: Pins, + mode: Mode, +} + +impl I2C +where + Block: SubsystemReset + Deref, +{ + /// Releases the I2C peripheral and associated pins + #[allow(clippy::type_complexity)] + pub fn free(self, resets: &mut RESETS) -> (Block, (Sda, Scl)) { + self.i2c.reset_bring_down(resets); + + (self.i2c, self.pins) + } +} + +impl, PINS, Mode> I2C { + /// Depth of the TX FIFO. + pub const TX_FIFO_DEPTH: u8 = 16; + + /// Depth of the RX FIFO. + pub const RX_FIFO_DEPTH: u8 = 16; + + /// Number of bytes currently in the RX FIFO + #[inline] + pub fn rx_fifo_used(&self) -> u8 { + self.i2c.ic_rxflr().read().rxflr().bits() + } + + /// Remaining capacity in the RX FIFO + #[inline] + pub fn rx_fifo_available(&self) -> u8 { + Self::RX_FIFO_DEPTH - self.rx_fifo_used() + } + + /// RX FIFO is empty + #[inline] + pub fn rx_fifo_empty(&self) -> bool { + self.i2c.ic_status().read().rfne().bit_is_clear() + } + + /// Number of bytes currently in the TX FIFO + #[inline] + pub fn tx_fifo_used(&self) -> u8 { + self.i2c.ic_txflr().read().txflr().bits() + } + + /// Remaining capacity in the TX FIFO + #[inline] + pub fn tx_fifo_available(&self) -> u8 { + Self::TX_FIFO_DEPTH - self.tx_fifo_used() + } + + /// TX FIFO is at capacity + #[inline] + pub fn tx_fifo_full(&self) -> bool { + self.i2c.ic_status().read().tfnf().bit_is_clear() + } +} + +macro_rules! hal { + ($($I2CX:ident: ($i2cX:ident),)+) => { + $( + impl I2C<$I2CX, (Sda, Scl)> { + /// Configures the I2C peripheral to work in master mode + pub fn $i2cX( + i2c: $I2CX, + sda_pin: Sda, + scl_pin: Scl, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into, + Sda: ValidPinSda<$I2CX> + AnyPin, + Scl: ValidPinScl<$I2CX> + AnyPin, + SystemF: Into, + { + Self::new_controller(i2c, sda_pin, scl_pin, freq.into(), resets, system_clock.into()) + } + + $crate::paste::paste! { + /// Configures the I2C peripheral to work in master mode + /// + /// This function can be called without activating internal pull-ups on the I2C pins. + /// It should only be used if external pull-ups are provided. + pub fn [<$i2cX _with_external_pull_up>]( + i2c: $I2CX, + sda_pin: Sda, + scl_pin: Scl, + freq: F, + resets: &mut RESETS, + system_clock: SystemF) -> Self + where + F: Into, + Sda: ValidPinSda<$I2CX>, + Scl: ValidPinScl<$I2CX>, + SystemF: Into, + { + Self::new_controller(i2c, sda_pin, scl_pin, freq.into(), resets, system_clock.into()) + } + } + } + + impl $crate::async_utils::sealed::Wakeable for I2C<$I2CX, P, M> { + fn waker() -> &'static $crate::async_utils::sealed::IrqWaker { + static WAKER: $crate::async_utils::sealed::IrqWaker = + $crate::async_utils::sealed::IrqWaker::new(); + &WAKER + } + } + )+ + } +} +hal! { + I2C0: (i2c0), + I2C1: (i2c1), +} diff --git a/rp235x-hal/src/i2c/controller.rs b/rp235x-hal/src/i2c/controller.rs new file mode 100644 index 000000000..bc244ab4a --- /dev/null +++ b/rp235x-hal/src/i2c/controller.rs @@ -0,0 +1,499 @@ +use core::{ops::Deref, task::Poll}; +use embedded_hal_0_2::blocking::i2c::{Read, Write, WriteIter, WriteIterRead, WriteRead}; +use fugit::HertzU32; + +use embedded_hal::i2c as eh1; + +use crate::{ + i2c::{Controller, Error, ValidAddress, ValidPinScl, ValidPinSda, I2C}, + pac::{i2c0::RegisterBlock as Block, RESETS}, + resets::SubsystemReset, +}; + +pub(crate) mod non_blocking; + +impl I2C +where + T: SubsystemReset + Deref, + Sda: ValidPinSda, + Scl: ValidPinScl, +{ + /// Configures the I2C peripheral to work in controller mode + pub fn new_controller( + i2c: T, + sda_pin: Sda, + scl_pin: Scl, + freq: HertzU32, + resets: &mut RESETS, + system_clock: HertzU32, + ) -> Self { + let freq = freq.to_Hz(); + assert!(freq <= 1_000_000); + assert!(freq > 0); + + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable().write(|w| w.enable().disabled()); + + // select controller mode & speed + i2c.ic_con().modify(|_, w| { + w.speed().fast(); + w.master_mode().enabled(); + w.ic_slave_disable().slave_disabled(); + w.ic_restart_en().enabled(); + w.tx_empty_ctrl().enabled() + }); + + // Clear FIFO threshold + i2c.ic_tx_tl().write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl().write(|w| unsafe { w.rx_tl().bits(0) }); + + let freq_in = system_clock.to_Hz(); + + // There are some subtleties to I2C timing which we are completely ignoring here + // See: https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let period = (freq_in + freq / 2) / freq; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + assert!(hcnt <= 0xffff); + assert!(lcnt <= 0xffff); + assert!(hcnt >= 8); + assert!(lcnt >= 8); + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA signal to + // bridge the undefined region of the falling edge of SCL. A smaller hold + // time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if freq < 1000000 { + // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) + // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 10000000) + 1 + } else { + // fast mode plus requires a clk_in > 32MHz + assert!(freq_in >= 32_000_000); + + // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) + // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. + // Add 1 to avoid division truncation. + ((freq_in * 3) / 25000000) + 1 + }; + assert!(sda_tx_hold_count <= lcnt - 2); + + unsafe { + i2c.ic_fs_scl_hcnt() + .write(|w| w.ic_fs_scl_hcnt().bits(hcnt as u16)); + i2c.ic_fs_scl_lcnt() + .write(|w| w.ic_fs_scl_lcnt().bits(lcnt as u16)); + // spike filter duration + i2c.ic_fs_spklen().write(|w| { + let ticks = if lcnt < 16 { 1 } else { (lcnt / 16) as u8 }; + w.ic_fs_spklen().bits(ticks) + }); + // sda hold time + i2c.ic_sda_hold() + .modify(|_r, w| w.ic_sda_tx_hold().bits(sda_tx_hold_count as u16)); + + // make TX_EMPTY raise when the tx fifo is not full + i2c.ic_tx_tl() + .write(|w| w.tx_tl().bits(Self::TX_FIFO_DEPTH)); + // make RX_FULL raise when the rx fifo contains at least 1 byte + i2c.ic_rx_tl().write(|w| w.rx_tl().bits(0)); + // Enable clock stretching. + // Will hold clock when: + // - receiving and rx fifo is full + // - writing and tx fifo is empty + i2c.ic_con() + .modify(|_, w| w.rx_fifo_full_hld_ctrl().enabled()); + } + + // Enable I2C block + i2c.ic_enable().write(|w| w.enable().enabled()); + + Self { + i2c, + pins: (sda_pin, scl_pin), + mode: Controller {}, + } + } +} + +impl, PINS> I2C { + fn validate_buffer( + &mut self, + first: bool, + buf: &mut core::iter::Peekable, + err: Error, + ) -> Result<(), Error> + where + U: Iterator, + { + if buf.peek().is_some() { + Ok(()) + } else { + if !first { + self.abort(); + } + Err(err) + } + } + + fn setup(&mut self, addr: A) -> Result<(), Error> { + addr.is_valid()?; + + self.i2c.ic_enable().write(|w| w.enable().disabled()); + self.i2c + .ic_con() + .modify(|_, w| w.ic_10bitaddr_master().variant(A::BIT_ADDR_M)); + + let addr = addr.into(); + self.i2c + .ic_tar() + .write(|w| unsafe { w.ic_tar().bits(addr) }); + self.i2c.ic_enable().write(|w| w.enable().enabled()); + Ok(()) + } + + #[inline] + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let abort_reason = self.i2c.ic_tx_abrt_source().read().bits(); + if abort_reason != 0 { + // Note clearing the abort flag also clears the reason, and + // this instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + self.i2c.ic_clr_tx_abrt().read(); + Err(Error::Abort(abort_reason)) + } else { + Ok(()) + } + } + + #[inline] + fn poll_tx_not_full(&mut self) -> Poll> { + self.read_and_clear_abort_reason()?; + if !self.tx_fifo_full() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + #[inline] + fn poll_tx_empty(&mut self) -> Poll<()> { + if self.i2c.ic_raw_intr_stat().read().tx_empty().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + } + + #[inline] + fn poll_stop_detected(&mut self) -> Poll<()> { + if self.i2c.ic_raw_intr_stat().read().stop_det().is_inactive() { + Poll::Pending + } else { + Poll::Ready(()) + } + } + + #[inline] + fn abort(&mut self) { + self.i2c.ic_enable().modify(|_, w| w.abort().set_bit()); + while self.i2c.ic_enable().read().abort().bit_is_set() { + crate::arch::nop() + } + while self.i2c.ic_raw_intr_stat().read().tx_abrt().bit_is_clear() { + crate::arch::nop() + } + // clear tx_abort interrupt flags (might have already been clear by irq) + self.i2c.ic_clr_tx_abrt().read(); + // clear tx_abrt_source by reading it + self.i2c.ic_tx_abrt_source().read(); + } + + fn read_internal( + &mut self, + first_transaction: bool, + buffer: &mut [u8], + do_stop: bool, + ) -> Result<(), Error> { + self.validate_buffer( + first_transaction, + &mut buffer.iter().peekable(), + Error::InvalidReadBufferLength, + )?; + + let lastindex = buffer.len() - 1; + let mut first_byte = true; + for (i, byte) in buffer.iter_mut().enumerate() { + let last_byte = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + while self.i2c.ic_status().read().tfnf().bit_is_clear() {} + + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + + w.stop().bit(do_stop && last_byte); + w.cmd().read() + }); + + while self.i2c.ic_rxflr().read().bits() == 0 { + self.read_and_clear_abort_reason()?; + } + + *byte = self.i2c.ic_data_cmd().read().dat().bits(); + } + + Ok(()) + } + + fn write_internal( + &mut self, + first_transaction: bool, + bytes: impl IntoIterator, + do_stop: bool, + ) -> Result<(), Error> { + let mut peekable = bytes.into_iter().peekable(); + self.validate_buffer( + first_transaction, + &mut peekable, + Error::InvalidWriteBufferLength, + )?; + + let mut abort_reason = Ok(()); + let mut first_byte = true; + 'outer: while let Some(byte) = peekable.next() { + if self.tx_fifo_full() { + // wait for more room in the fifo + loop { + match self.poll_tx_not_full() { + Poll::Pending => continue, + Poll::Ready(Ok(())) => break, + Poll::Ready(r) => { + abort_reason = r; + break 'outer; + } + } + } + } + + // else enqueue + let last = peekable.peek().is_none(); + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + w.stop().bit(do_stop && last); + unsafe { w.dat().bits(byte) } + }); + } + + if abort_reason.is_err() { + // Wait until the transmission of the address/data from the internal + // shift register has completed. + while self.poll_tx_empty().is_pending() {} + abort_reason = self.read_and_clear_abort_reason(); + } + + if abort_reason.is_err() || do_stop { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + while self.poll_stop_detected().is_pending() {} + self.i2c.ic_clr_stop_det().read().clr_stop_det(); + } + // Note: the hardware issues a STOP automatically on an abort condition. + // Note: the hardware also clears RX FIFO as well as TX on abort + + abort_reason + } +} + +impl, PINS> I2C { + /// Writes bytes to slave with address `address` + /// + /// # I2C Events (contract) + /// + /// Same as the `write` method + pub fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Error> + where + B: IntoIterator, + { + self.setup(address)?; + self.write_internal(true, bytes, true) + } + + /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a + /// single transaction* + /// + /// # I2C Events (contract) + /// + /// Same as the `write_read` method + pub fn write_iter_read( + &mut self, + address: A, + bytes: B, + buffer: &mut [u8], + ) -> Result<(), Error> + where + B: IntoIterator, + { + self.setup(address)?; + + self.write_internal(true, bytes, false)?; + self.read_internal(false, buffer, true) + } + + /// Execute the provided operations on the I2C bus (iterator version). + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + fn transaction<'op: 'iter, 'iter, A: ValidAddress>( + &mut self, + address: A, + operations: impl IntoIterator>, + ) -> Result<(), Error> { + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + eh1::Operation::Read(buf) => self.read_internal(first, buf, last)?, + eh1::Operation::Write(buf) => { + self.write_internal(first, buf.iter().cloned(), last)? + } + } + first = false; + } + Ok(()) + } + + #[cfg(feature = "i2c-write-iter")] + fn transaction_iter<'op, A, O, B>(&mut self, address: A, operations: O) -> Result<(), Error> + where + A: ValidAddress, + O: IntoIterator>, + B: IntoIterator, + { + use i2c_write_iter::Operation; + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + Operation::Read(buf) => self.read_internal(first, buf, last)?, + Operation::WriteIter(buf) => self.write_internal(first, buf, last)?, + } + first = false; + } + Ok(()) + } +} + +impl, PINS> Read for I2C { + type Error = Error; + + fn read(&mut self, addr: A, buffer: &mut [u8]) -> Result<(), Error> { + self.setup(addr)?; + self.read_internal(true, buffer, true) + } +} +impl, PINS> WriteRead for I2C { + type Error = Error; + + fn write_read(&mut self, addr: A, tx: &[u8], rx: &mut [u8]) -> Result<(), Error> { + self.setup(addr)?; + + self.write_internal(true, tx.iter().cloned(), false)?; + self.read_internal(false, rx, true) + } +} + +impl, PINS> Write for I2C { + type Error = Error; + + fn write(&mut self, addr: A, tx: &[u8]) -> Result<(), Error> { + self.setup(addr)?; + self.write_internal(true, tx.iter().cloned(), true) + } +} + +impl, PINS> WriteIter for I2C { + type Error = Error; + + fn write(&mut self, address: A, bytes: B) -> Result<(), Self::Error> + where + B: IntoIterator, + { + self.write_iter(address, bytes) + } +} + +impl, PINS> WriteIterRead + for I2C +{ + type Error = Error; + + fn write_iter_read( + &mut self, + address: A, + bytes: B, + buffer: &mut [u8], + ) -> Result<(), Self::Error> + where + B: IntoIterator, + { + self.write_iter_read(address, bytes, buffer) + } +} + +impl, PINS> eh1::ErrorType for I2C { + type Error = Error; +} + +impl, PINS> eh1::I2c for I2C { + fn transaction( + &mut self, + address: A, + operations: &mut [eh1::Operation<'_>], + ) -> Result<(), Self::Error> { + self.transaction(address, operations.iter_mut()) + } +} + +#[cfg(feature = "i2c-write-iter")] +impl, PINS> + i2c_write_iter::I2cIter for I2C +{ + fn transaction_iter<'a, O, B>(&mut self, address: A, operations: O) -> Result<(), Self::Error> + where + O: IntoIterator>, + B: IntoIterator, + { + self.transaction_iter(address, operations) + } +} diff --git a/rp235x-hal/src/i2c/controller/non_blocking.rs b/rp235x-hal/src/i2c/controller/non_blocking.rs new file mode 100644 index 000000000..b3bd6933f --- /dev/null +++ b/rp235x-hal/src/i2c/controller/non_blocking.rs @@ -0,0 +1,347 @@ +use core::{ops::Deref, task::Poll}; +use embedded_hal_async::i2c::{AddressMode, Operation}; + +use crate::{ + async_utils::{sealed::Wakeable, AsyncPeripheral, CancellablePollFn as CPFn}, + i2c::{Controller, Error, ValidAddress, I2C}, + pac::i2c0::RegisterBlock, +}; + +macro_rules! impl_async_traits { + ($i2c:path) => { + impl

AsyncPeripheral for I2C<$i2c, P, Controller> + where + Self: $crate::async_utils::sealed::Wakeable, + { + fn on_interrupt() { + unsafe { + // This is equivalent to stealing from pac::Peripherals + let i2c = &*<$i2c>::ptr(); + + // Mask all interrupt flags. This does not clear the flags. + // Clearing is done by the driver after it wakes up. + i2c.ic_intr_mask().write_with_zero(|w| w); + } + // interrupts are now masked, we can wake the task and return from this handler. + Self::waker().wake(); + } + } + }; +} + +impl_async_traits!(rp235x_pac::I2C0); +impl_async_traits!(rp235x_pac::I2C1); + +enum TxEmptyConfig { + Empty, + NotFull, +} + +impl I2C +where + T: Deref, + Self: AsyncPeripheral, +{ + /// `tx_empty`: true to unmask tx_empty + #[inline] + fn unmask_intr(&mut self, tx_empty: bool) { + unsafe { + self.i2c.ic_intr_mask().write_with_zero(|w| { + w.m_tx_empty() + .bit(tx_empty) + .m_rx_full() + .disabled() + .m_tx_abrt() + .disabled() + .m_stop_det() + .disabled() + }); + } + } + #[inline] + fn configure_tx_empty(&mut self, cfg: TxEmptyConfig) { + self.i2c + .ic_tx_tl() + // SAFETY: we are within [0; TX_FIFO_DEPTH) + .write(|w| unsafe { + w.tx_tl().bits(match cfg { + TxEmptyConfig::Empty => 1, + TxEmptyConfig::NotFull => Self::TX_FIFO_DEPTH - 1, + }) + }); + } + + #[inline] + fn unmask_tx_empty(&mut self) { + self.configure_tx_empty(TxEmptyConfig::Empty); + self.unmask_intr(true) + } + + #[inline] + fn unmask_tx_not_full(&mut self) { + self.configure_tx_empty(TxEmptyConfig::NotFull); + self.unmask_intr(true) + } + + #[inline] + fn unmask_stop_det(&mut self) { + self.unmask_intr(false); + } + + #[inline] + fn poll_rx_not_empty_or_abrt(&mut self) -> Poll> { + self.read_and_clear_abort_reason()?; + if self.i2c.ic_raw_intr_stat().read().rx_full().bit_is_set() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + } + + #[inline] + fn cancel(&mut self) { + unsafe { + self.i2c.ic_intr_mask().write_with_zero(|w| w); + } + + self.abort(); + } + + async fn non_blocking_read_internal( + &mut self, + first_transaction: bool, + buffer: &mut [u8], + do_stop: bool, + ) -> Result<(), Error> { + self.validate_buffer( + first_transaction, + &mut buffer.iter().peekable(), + Error::InvalidReadBufferLength, + )?; + + let lastindex = buffer.len() - 1; + let mut first_byte = true; + for (i, byte) in buffer.iter_mut().enumerate() { + let last_byte = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + // cannot abort during read, so ignore the result + let _ = CPFn::new( + self, + Self::poll_tx_not_full, + Self::unmask_tx_not_full, + Self::cancel, + ) + .await; + + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + + w.stop().bit(do_stop && last_byte); + w.cmd().read() + }); + + CPFn::new( + self, + Self::poll_rx_not_empty_or_abrt, + Self::unmask_tx_empty, + Self::cancel, + ) + .await?; + + *byte = self.i2c.ic_data_cmd().read().dat().bits(); + } + + Ok(()) + } + + async fn non_blocking_write_internal( + &mut self, + first_transaction: bool, + bytes: impl IntoIterator, + do_stop: bool, + ) -> Result<(), Error> { + let mut peekable = bytes.into_iter().peekable(); + self.validate_buffer( + first_transaction, + &mut peekable, + Error::InvalidWriteBufferLength, + )?; + + let mut abort_reason = Ok(()); + let mut first_byte = true; + while let Some(byte) = peekable.next() { + if self.tx_fifo_full() { + // wait for more room in the fifo + abort_reason = CPFn::new( + self, + Self::poll_tx_not_full, + Self::unmask_tx_not_full, + Self::cancel, + ) + .await; + if abort_reason.is_err() { + break; + } + } + + // else enqueue + let last = peekable.peek().is_none(); + self.i2c.ic_data_cmd().write(|w| { + if first_byte { + if !first_transaction { + w.restart().enable(); + } + first_byte = false; + } + w.stop().bit(do_stop && last); + unsafe { w.dat().bits(byte) } + }); + } + + if abort_reason.is_err() { + // Wait until the transmission of the address/data from the internal + // shift register has completed. + CPFn::new( + self, + Self::poll_tx_empty, + Self::unmask_tx_empty, + Self::cancel, + ) + .await; + abort_reason = self.read_and_clear_abort_reason(); + } + + if abort_reason.is_err() || do_stop { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + CPFn::new( + self, + Self::poll_stop_detected, + Self::unmask_stop_det, + Self::cancel, + ) + .await; + self.i2c.ic_clr_stop_det().read().clr_stop_det(); + } + // Note: the hardware issues a STOP automatically on an abort condition. + // Note: the hardware also clears RX FIFO as well as TX on abort + + abort_reason + } + + /// Writes to the i2c bus consuming bytes for the given iterator. + pub async fn write_iter_async(&mut self, address: A, bytes: U) -> Result<(), super::Error> + where + U: IntoIterator, + A: ValidAddress, + { + self.setup(address)?; + self.non_blocking_write_internal(true, bytes, true).await + } + + /// Writes to the i2c bus consuming bytes for the given iterator. + pub async fn write_iter_read_async( + &mut self, + address: A, + bytes: U, + read: &mut [u8], + ) -> Result<(), Error> + where + U: IntoIterator, + A: ValidAddress, + { + self.setup(address)?; + self.non_blocking_write_internal(true, bytes, false).await?; + self.non_blocking_read_internal(false, read, true).await + } + + /// Writes to the i2c bus taking operations from and iterator, writing from iterator of bytes, + /// reading to slices of bytes. + #[cfg(feature = "i2c-write-iter")] + pub async fn transaction_iter_async<'b, A, O, B>( + &mut self, + address: A, + operations: O, + ) -> Result<(), super::Error> + where + A: ValidAddress, + O: IntoIterator>, + B: IntoIterator, + { + self.setup(address)?; + + let mut first = true; + let mut operations = operations.into_iter().peekable(); + while let Some(operation) = operations.next() { + let last = operations.peek().is_none(); + match operation { + i2c_write_iter::Operation::Read(buf) => { + self.non_blocking_read_internal(first, buf, last).await? + } + i2c_write_iter::Operation::WriteIter(buf) => { + self.non_blocking_write_internal(first, buf, last).await? + } + } + first = false; + } + Ok(()) + } +} + +impl embedded_hal_async::i2c::I2c for I2C +where + Self: AsyncPeripheral, + A: ValidAddress + AddressMode, + T: Deref, +{ + async fn transaction( + &mut self, + addr: A, + operations: &mut [Operation<'_>], + ) -> Result<(), Error> { + self.setup(addr)?; + + let mut first = true; + let mut operations = operations.iter_mut().peekable(); + while let Some(op) = operations.next() { + let last = operations.peek().is_none(); + match op { + Operation::Read(buffer) => { + self.non_blocking_read_internal(first, buffer, last).await?; + } + Operation::Write(buffer) => { + self.non_blocking_write_internal(first, buffer.iter().cloned(), last) + .await?; + } + } + first = false; + } + Ok(()) + } +} + +#[cfg(feature = "i2c-write-iter")] +impl i2c_write_iter::non_blocking::I2cIter for I2C +where + Self: AsyncPeripheral, + A: 'static + ValidAddress + AddressMode, + T: Deref, +{ + async fn transaction_iter<'a, O, B>( + &mut self, + address: A, + operations: O, + ) -> Result<(), Self::Error> + where + O: IntoIterator>, + B: IntoIterator, + { + self.transaction_iter_async(address, operations).await + } +} diff --git a/rp235x-hal/src/i2c/peripheral.rs b/rp235x-hal/src/i2c/peripheral.rs new file mode 100644 index 000000000..27c8450d7 --- /dev/null +++ b/rp235x-hal/src/i2c/peripheral.rs @@ -0,0 +1,320 @@ +//! # I2C Peripheral (slave) implementation +//! +//! The RP2350 I2C block can behave as a peripheral node on an I2C bus. +//! +//! In order to handle peripheral transactions this driver exposes an iterator streaming I2C event +//! that the usercode must handle to properly handle the I2C communitation. See [`Event`] for a +//! list of events to handle. +//! +//! Although [`Start`](Event::Start), [`Restart`](Event::Restart) and [`Stop`](Event::Stop) +//! events may not require any action on the device, [`TransferRead`](Event::TransferRead) and +//! [`TransferWrite`](Event::TransferWrite) require some action: +//! +//! - [`TransferRead`](Event::TransferRead): A controller is attempting to read from this peripheral. +//! The I2C block holds the SCL line low (clock stretching) until data is pushed to the transmission +//! FIFO by the user application using [`write`](I2C::write). +//! Data remaining in the FIFO when the bus constroller stops the transfer are ignored & the fifo +//! is flushed. +//! - [`TransferWrite`](Event::TransferWrite): A controller is sending data to this peripheral. +//! The I2C block holds the SCL line (clock stretching) until there is room for more data in the +//! Rx FIFO using [`read`](I2C::read). +//! Data are automatically acknowledged by the I2C block and it is not possible to NACK incoming +//! data coming to the RP2350. +//! +//! ## Warning +//! +//! `Start`, `Restart` and `Stop` events may not be reported before or after a write operations. +//! This is because several write operation may take place and complete before the core has time to +//! react. All the data sent will be stored in the peripheral FIFO but it will not be possible to +//! identify between which bytes should the start/restart/stop events precicely took place. +//! +//! Because a Read operation will always cause a pause waiting for the firmware's input, a `Start` +//! (or `Restart` if the peripheral is already active) will always be reported. However, this does +//! not mean no other event occurred in the mean time. +//! +//! For example, let's consider the following sequence: +//! +//! `Start, Write, Stop, Start, Write, Restart, Read, Stop.` +//! +//! Depending on the firmware's and bus' speed, this driver may only report: +//! - `Start, Write, Restart, Read, Stop` + +use core::{ops::Deref, task::Poll}; + +use embedded_hal::i2c::AddressMode; + +use super::{Peripheral, ValidAddress, ValidPinScl, ValidPinSda, I2C}; +use crate::{ + async_utils::{sealed::Wakeable, AsyncPeripheral, CancellablePollFn}, + pac::{i2c0::RegisterBlock, RESETS}, + resets::SubsystemReset, +}; + +/// I2C bus events +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event { + /// Start condition has been detected. + Start, + /// Restart condition has been detected. + Restart, + /// The controller requests data. + TransferRead, + /// The controller sends data. + TransferWrite, + /// Stop condition detected. + Stop, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum State { + Idle, + Active, + Read, + Write, +} + +impl I2C +where + T: SubsystemReset + Deref, + Sda: ValidPinSda, + Scl: ValidPinScl, +{ + /// Configures the I2C peripheral to work in peripheral mode + /// + /// The bus *MUST* be idle when this method is called. + #[allow(clippy::type_complexity)] + pub fn new_peripheral_event_iterator( + i2c: T, + sda_pin: Sda, + scl_pin: Scl, + resets: &mut RESETS, + addr: A, + ) -> Self { + i2c.reset_bring_down(resets); + i2c.reset_bring_up(resets); + + i2c.ic_enable().write(|w| w.enable().disabled()); + + // TODO: Validate address? + let addr = addr.into(); + // SAFETY: Only address by this function. IC_SAR spec filters out bits 15:10. + // Any value is valid for the controller. They may not be for the bus itself though. + i2c.ic_sar().write(|w| unsafe { w.ic_sar().bits(addr) }); + // select peripheral mode & speed + i2c.ic_con().modify(|_, w| { + // run in fast mode + w.speed().fast(); + // setup slave mode + w.master_mode().disabled(); + w.ic_slave_disable().slave_enabled(); + // hold scl when fifo's full + w.rx_fifo_full_hld_ctrl().enabled(); + w.ic_restart_en().enabled(); + w.ic_10bitaddr_slave().variant(A::BIT_ADDR_S); + w + }); + + // Clear FIFO threshold + // SAFETY: Only address by this function. The field is 8bit long. 0 is a valid value. + i2c.ic_tx_tl().write(|w| unsafe { w.tx_tl().bits(0) }); + i2c.ic_rx_tl().write(|w| unsafe { w.rx_tl().bits(0) }); + + i2c.ic_clr_intr().read(); + + let mut me = Self { + i2c, + pins: (sda_pin, scl_pin), + mode: Peripheral { state: State::Idle }, + }; + me.unmask_intr(); + // Enable I2C block + me.i2c.ic_enable().write(|w| w.enable().enabled()); + + me + } +} + +fn unmask_intr(i2c: &RegisterBlock) { + // SAFETY: 0 is a valid value meaning all irq masked. + // This operation is atomic, `write_with_zero` only writes to the register. + unsafe { + i2c.ic_intr_mask().write_with_zero(|w| { + // Only keep these IRQ enabled. + w.m_start_det() + .disabled() + .m_rd_req() + .disabled() + .m_rx_full() + .disabled() + .m_stop_det() + .disabled() + }); + } +} + +/// SAFETY: Takes a non-mutable reference to RegisterBlock but mutates its `ic_intr_mask` register. +unsafe fn mask_intr(i2c: &RegisterBlock) { + // 0 is a valid value and means all flag masked. + unsafe { i2c.ic_intr_mask().write_with_zero(|w| w) } +} + +impl, PINS> I2C { + fn unmask_intr(&mut self) { + unmask_intr(&self.i2c) + } + fn mask_intr(&mut self) { + // SAFETY: We are the only owner of this register block. + unsafe { mask_intr(&self.i2c) } + } + + /// Push up to `usize::min(TX_FIFO_SIZE, buf.len())` bytes to the TX FIFO. + /// Returns the number of bytes pushed to the FIFO. Note this does *not* reflect how many bytes + /// are effectively received by the controller. + pub fn write(&mut self, buf: &[u8]) -> usize { + // just in case, clears previous tx abort. + self.i2c.ic_clr_tx_abrt().read(); + + let mut sent = 0; + for &b in buf.iter() { + if self.tx_fifo_full() { + break; + } + + // SAFETY: dat field is 8bits long. All values are valid. + self.i2c.ic_data_cmd().write(|w| unsafe { w.dat().bits(b) }); + sent += 1; + } + // serve a pending read request + self.i2c.ic_clr_rd_req().read(); + sent + } + + /// Pull up to `usize::min(RX_FIFO_SIZE, buf.len())` bytes from the RX FIFO. + pub fn read(&mut self, buf: &mut [u8]) -> usize { + buf.iter_mut().zip(self).map(|(b, r)| *b = r).count() + } +} + +/// This allows I2C to be used with `core::iter::Extend`. +impl, PINS> Iterator for I2C { + type Item = u8; + + fn next(&mut self) -> Option { + if self.rx_fifo_empty() { + None + } else { + Some(self.i2c.ic_data_cmd().read().dat().bits()) + } + } +} +impl, PINS> I2C { + /// Returns the next i2c event if any. + pub fn next_event(&mut self) -> Option { + let stat = self.i2c.ic_raw_intr_stat().read(); + + match self.mode.state { + State::Idle if stat.start_det().bit_is_set() => { + self.i2c.ic_clr_start_det().read(); + self.mode.state = State::Active; + Some(Event::Start) + } + State::Active if stat.rd_req().bit_is_set() => { + // Clearing `rd_req` is used by the hardware to detect when the I2C block can stop + // stretching the clock and start process the data pushed to the FIFO (if any). + // This is done in `Self::write`. + self.mode.state = State::Read; + // If stop_det is set at this point we know that it is related to a previous request, + // It cannot be due the end of the current request as SCL is held low while waiting + // for user input. + if stat.stop_det().bit_is_set() { + self.i2c.ic_clr_stop_det().read(); + } + Some(Event::TransferRead) + } + State::Active if !self.rx_fifo_empty() => { + self.mode.state = State::Write; + Some(Event::TransferWrite) + } + + State::Read if stat.rd_req().bit_is_set() => Some(Event::TransferRead), + State::Write if !self.rx_fifo_empty() => Some(Event::TransferWrite), + + State::Read | State::Write if stat.restart_det().bit_is_set() => { + self.i2c.ic_clr_restart_det().read(); + self.i2c.ic_clr_start_det().read(); + self.mode.state = State::Active; + Some(Event::Restart) + } + + _ if stat.stop_det().bit_is_set() => { + self.i2c.ic_clr_stop_det().read(); + self.i2c.ic_clr_tx_abrt().read(); + self.mode.state = State::Idle; + Some(Event::Stop) + } + + _ => None, + } + } +} + +macro_rules! impl_wakeable { + ($i2c:ty) => { + impl AsyncPeripheral for I2C<$i2c, PINS, Peripheral> + where + I2C<$i2c, PINS, Peripheral>: $crate::async_utils::sealed::Wakeable, + { + /// Wakes an async task (if any) & masks irqs + fn on_interrupt() { + unsafe { + // This is equivalent to stealing from pac::Peripherals + let i2c = &*<$i2c>::ptr(); + + mask_intr(i2c); + } + + // interrupts are now masked, we can wake the task and return from this handler. + Self::waker().wake(); + } + } + }; +} +impl_wakeable!(rp235x_pac::I2C0); +impl_wakeable!(rp235x_pac::I2C1); + +impl I2C +where + I2C: AsyncPeripheral, + T: Deref, +{ + /// Asynchronously waits for an Event. + pub async fn wait_next(&mut self) -> Event { + loop { + if let Some(evt) = self.next_event() { + return evt; + } + + CancellablePollFn::new( + self, + |me| { + let stat = me.i2c.ic_raw_intr_stat().read(); + if stat.start_det().bit_is_set() + || stat.restart_det().bit_is_set() + || stat.stop_det().bit_is_set() + || stat.rd_req().bit_is_set() + || stat.rx_full().bit_is_set() + { + Poll::Ready(()) + } else { + Poll::Pending + } + }, + Self::unmask_intr, + Self::mask_intr, + ) + .await; + } + } +} diff --git a/rp235x-hal/src/lib.rs b/rp235x-hal/src/lib.rs new file mode 100644 index 000000000..788741ba5 --- /dev/null +++ b/rp235x-hal/src/lib.rs @@ -0,0 +1,169 @@ +//! HAL for the Raspberry Pi RP235x microcontrollers +//! +//! This is an implementation of the [`embedded-hal`](https://crates.io/crates/embedded-hal) +//! traits for the RP235x microcontrollers +//! +//! NOTE This HAL is still under active development. This API will remain volatile until 1.0.0 +//! +//! # Crate features +//! +//! * **critical-section-impl** - +//! critical section that is safe for multicore use +//! * **defmt** - +//! Implement `defmt::Format` for several types. +//! * **embedded_hal_1** - +//! Support alpha release of embedded-hal +//! * **rom-func-cache** - +//! Memoize(cache) ROM function pointers on first use to improve performance +//! * **rt** - +//! Minimal startup / runtime for Cortex-M microcontrollers +//! * **rtic-monotonic** - +//! Implement `rtic_monotonic::Monotonic` based on the RP2350 timer peripheral +//! * **i2c-write-iter** - +//! Implement `i2c_write_iter` traits for `I2C<_, _, Controller>`. +//! * **binary-info** - +//! Include a `static` variable containing picotool compatible binary info. + +#![recursion_limit = "256"] +#![warn(missing_docs)] +#![no_std] + +#[doc(hidden)] +pub use paste; + +/// Re-export of the PAC +pub use rp235x_pac as pac; + +pub mod adc; +pub mod arch; +#[macro_use] +pub mod async_utils; +pub(crate) mod atomic_register_access; +pub use rp_binary_info as binary_info; +pub mod block; +pub mod clocks; +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub mod dcp; +pub mod dma; +pub mod gpio; +pub mod i2c; +pub mod lposc; +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub mod multicore; +pub mod otp; +pub mod pio; +pub mod pll; +pub mod powman; +pub mod prelude; +pub mod pwm; +pub mod reboot; +pub mod resets; +pub mod rom_data; +pub mod rosc; +pub mod sio; +pub mod spi; +pub mod timer; +pub mod typelevel; +pub mod uart; +pub mod usb; +pub mod vector_table; +pub mod watchdog; +pub mod xosc; + +// Provide access to common datastructures to avoid repeating ourselves +pub use adc::Adc; +pub use clocks::Clock; +pub use i2c::I2C; + +/// Attribute to declare the entry point of the program +/// +/// This is based on and can be used like the [entry attribute from +/// cortex-m-rt](https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.entry.html). +/// +/// It extends that macro with code to unlock all spinlocks at the beginning of +/// `main`. As spinlocks are not automatically unlocked on software resets, this +/// can prevent unexpected deadlocks when running from a debugger. The macro +/// also enables the FPU (CP10) and the Double-Co-Processor (CP4) before we hit +/// main. +pub use rp235x_hal_macros::entry; + +/// Called by the rp235x-specific entry macro +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub use cortex_m_rt::entry as arch_entry; + +/// Called by the rp235x-specific entry macro +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +pub use riscv_rt::entry as arch_entry; + +use sio::CoreId; +pub use sio::Sio; +pub use spi::Spi; +pub use timer::Timer; +pub use watchdog::Watchdog; +// Re-export crates used in rp235x-hal's public API +pub extern crate fugit; + +/// Trigger full reset of the rp235x. +/// +/// Uses the watchdog and the power-on state machine (PSM) to reset all on-chip components. +pub fn reset() -> ! { + unsafe { + crate::arch::interrupt_disable(); + (*pac::PSM::PTR).wdsel().write(|w| w.bits(0x0001ffff)); + (*pac::WATCHDOG::PTR) + .ctrl() + .write(|w| w.trigger().set_bit()); + #[allow(clippy::empty_loop)] + loop {} + } +} + +/// Halt the rp235x. +/// +/// Disables the other core, and parks the current core in an +/// infinite loop with interrupts disabled. +/// +/// Doesn't stop other subsystems, like the DMA controller. +/// +/// When called from core1, core0 will be kept forced off, which +/// likely breaks debug connections. You may need to reboot with +/// BOOTSEL pressed to reboot into a debuggable state. +pub fn halt() -> ! { + unsafe { + crate::arch::interrupt_disable(); + // Stop other core + match crate::Sio::core() { + CoreId::Core0 => { + // Stop core 1. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc1().set_bit()); + while !(*pac::PSM::PTR).frce_off().read().proc1().bit_is_set() { + crate::arch::nop(); + } + // Restart core 1. Without this, most debuggers will fail connecting. + // It will loop indefinitely in BOOTROM, as nothing + // will trigger the wakeup sequence. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc1().clear_bit()); + } + CoreId::Core1 => { + // Stop core 0. + (*pac::PSM::PTR) + .frce_off() + .modify(|_, w| w.proc0().set_bit()); + // We cannot restart core 0 here, as it would just boot into main. + // So the best we can do is to keep core 0 disabled, which may break + // further debug connections. + } + }; + + // Keep current core running, so debugging stays possible + loop { + crate::arch::wfe() + } + } +} diff --git a/rp235x-hal/src/lposc.rs b/rp235x-hal/src/lposc.rs new file mode 100644 index 000000000..10f94cfad --- /dev/null +++ b/rp235x-hal/src/lposc.rs @@ -0,0 +1,31 @@ +//! Low Power Oscillator (ROSC) +//! +//! See [Section +//! 8.4](https://rptl.io/rp2350-datasheet) for +//! more details +//! +//! The on-chip 32kHz Low Power Oscillator requires no external +//! components. It starts automatically when the always-on domain is powered and +//! provides a clock for the power manager and a tick for the Real-Time Clock +//! (RTC) when the switched-core power domain is powered off. + +use crate::{pac::powman::LPOSC, typelevel::Sealed}; + +/// A Low Power Oscillator. +pub struct LowPowerOscillator { + device: LPOSC, +} + +impl LowPowerOscillator { + /// Creates a new LowPowerOscillator from the underlying device. + pub fn new(dev: LPOSC) -> Self { + LowPowerOscillator { device: dev } + } + + /// Releases the underlying device. + pub fn free(self) -> LPOSC { + self.device + } +} + +impl Sealed for LowPowerOscillator {} diff --git a/rp235x-hal/src/multicore.rs b/rp235x-hal/src/multicore.rs new file mode 100644 index 000000000..db49a6a33 --- /dev/null +++ b/rp235x-hal/src/multicore.rs @@ -0,0 +1,275 @@ +//! Multicore support +//! +//! This module handles setup of the 2nd cpu core on the rp235x, which we refer to as core1. +//! It provides functionality for setting up the stack, and starting core1. +//! +//! The entrypoint for core1 can be any function that never returns, including closures. +//! +//! # Usage +//! +//! ```no_run +//! use rp235x_hal::{ +//! gpio::Pins, +//! multicore::{Multicore, Stack}, +//! pac, +//! sio::Sio, +//! }; +//! +//! static mut CORE1_STACK: Stack<4096> = Stack::new(); +//! +//! fn core1_task() { +//! loop {} +//! } +//! +//! fn main() -> ! { +//! let mut pac = hal::pac::Peripherals::take().unwrap(); +//! let mut sio = Sio::new(pac.SIO); +//! // Other init code above this line +//! let mut mc = Multicore::new(&mut pac.PSM, &mut pac.PPB, &mut sio.fifo); +//! let cores = mc.cores(); +//! let core1 = &mut cores[1]; +//! let _test = core1.spawn(unsafe { &mut CORE1_STACK.mem }, core1_task); +//! // The rest of your application below this line +//! # loop {} +//! } +//! ``` +//! +//! For inter-processor communications, see [`crate::sio::SioFifo`] and [`crate::sio::Spinlock0`] +//! +//! For a detailed example, see [examples/multicore_fifo_blink.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/multicore_fifo_blink.rs) + +use core::mem::ManuallyDrop; +use core::sync::atomic::compiler_fence; +use core::sync::atomic::Ordering; + +use crate::pac; +use crate::Sio; + +/// Errors for multicore operations. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Operation is invalid on this core. + InvalidCore, + /// Core was unresponsive to commands. + Unresponsive, +} + +#[inline(always)] +fn install_stack_guard(_stack_limit: *mut usize) { + // TBD Cortex-M33 MPU stack guard stuff. + // See the RP2040 code. +} + +#[inline(always)] +fn core1_setup(stack_limit: *mut usize) { + install_stack_guard(stack_limit); + // TODO: irq priorities +} + +/// Multicore execution management. +pub struct Multicore<'p> { + cores: [Core<'p>; 2], +} + +/// Data type for a properly aligned stack of N 32-bit (usize) words +#[repr(C, align(32))] +pub struct Stack { + /// Memory to be used for the stack + pub mem: [usize; SIZE], +} + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +impl Stack { + /// Construct a stack of length SIZE, initialized to 0 + pub const fn new() -> Stack { + Stack { mem: [0; SIZE] } + } +} + +impl<'p> Multicore<'p> { + /// Create a new |Multicore| instance. + pub fn new( + psm: &'p mut pac::PSM, + ppb: &'p mut pac::PPB, + sio: &'p mut crate::sio::SioFifo, + ) -> Self { + Self { + cores: [ + Core { inner: None }, + Core { + inner: Some((psm, ppb, sio)), + }, + ], + } + } + + /// Get the available |Core| instances. + pub fn cores(&mut self) -> &'p mut [Core] { + &mut self.cores + } +} + +/// A handle for controlling a logical core. +pub struct Core<'p> { + inner: Option<( + &'p mut pac::PSM, + &'p mut pac::PPB, + &'p mut crate::sio::SioFifo, + )>, +} + +impl<'p> Core<'p> { + /// Get the id of this core. + pub fn id(&self) -> u8 { + match self.inner { + None => 0, + Some(..) => 1, + } + } + + /// Spawn a function on this core. + /// + /// The closure should not return. It is currently defined as `-> ()` because `-> !` is not yet + /// stable. + /// + /// Core 1 will be reset from core 0 in order to spawn another task. + /// + /// Resetting a single core of a running program can have undesired consequences. Deadlocks are + /// likely if the core being reset happens to be inside a critical section. + /// It may even break safety assumptions of some unsafe code. So, be careful when calling this method + /// more than once. + pub fn spawn(&mut self, stack: &'static mut [usize], entry: F) -> Result<(), Error> + where + F: FnOnce() + Send + 'static, + { + if let Some((psm, ppb, fifo)) = self.inner.as_mut() { + // The first two ignored `u64` parameters are there to take up all of the registers, + // which means that the rest of the arguments are taken from the stack, + // where we're able to put them from core 0. + extern "C" fn core1_startup( + _: u64, + _: u64, + entry: *mut ManuallyDrop, + stack_limit: *mut usize, + ) -> ! { + core1_setup(stack_limit); + + let entry = unsafe { ManuallyDrop::take(&mut *entry) }; + + // make sure the preceding read doesn't get reordered past the following fifo write + compiler_fence(Ordering::SeqCst); + + // Signal that it's safe for core 0 to get rid of the original value now. + // + // We don't have any way to get at core 1's SIO without using `Peripherals::steal` right now, + // since svd2rust doesn't really support multiple cores properly. + let peripherals = unsafe { pac::Peripherals::steal() }; + let mut sio = Sio::new(peripherals.SIO); + sio.fifo.write_blocking(1); + + entry(); + loop { + crate::arch::wfe() + } + } + + // Reset the core + // TODO: resetting without prior check that the core is actually stowed is not great. + // But there does not seem to be any obvious way to check that. A marker flag could be + // set from this method and cleared for the wrapper after `entry` returned. But doing + // so wouldn't be zero cost. + psm.frce_off().modify(|_, w| w.proc1().set_bit()); + while !psm.frce_off().read().proc1().bit_is_set() { + crate::arch::nop(); + } + psm.frce_off().modify(|_, w| w.proc1().clear_bit()); + + // Set up the stack + // AAPCS requires in 6.2.1.2 that the stack is 8bytes aligned., we may need to trim the + // array size to guaranty that the base of the stack (the end of the array) meets that requirement. + // The start of the array does not need to be aligned. + + let mut stack_ptr = stack.as_mut_ptr_range().end; + // on rp235x, usize are 4 bytes, so align_offset(8) on a *mut usize returns either 0 or 1. + let misalignment_offset = stack_ptr.align_offset(8); + + // We don't want to drop this, since it's getting moved to the other core. + let mut entry = ManuallyDrop::new(entry); + + // Push the arguments to `core1_startup` onto the stack. + unsafe { + stack_ptr = stack_ptr.sub(misalignment_offset); + + // Push `stack_limit`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut usize>().write(stack.as_mut_ptr()); + + // Push `entry`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut ManuallyDrop>().write(&mut entry); + } + + // Make sure the compiler does not reorder the stack writes after to after the + // below FIFO writes, which would result in them not being seen by the second + // core. + // + // From the compiler perspective, this doesn't guarantee that the second core + // actually sees those writes. However, we know that the rp235x doesn't have + // memory caches, and writes happen in-order. + compiler_fence(Ordering::Release); + + let vector_table = ppb.vtor().read().bits(); + + // After reset, core 1 is waiting to receive commands over FIFO. + // This is the sequence to have it jump to some code. + let cmd_seq = [ + 0, + 0, + 1, + vector_table as usize, + stack_ptr as usize, + core1_startup:: as usize, + ]; + + let mut seq = 0; + let mut fails = 0; + loop { + let cmd = cmd_seq[seq] as u32; + if cmd == 0 { + fifo.drain(); + crate::arch::sev(); + } + fifo.write_blocking(cmd); + let response = fifo.read_blocking(); + if cmd == response { + seq += 1; + } else { + seq = 0; + fails += 1; + if fails > 16 { + // The second core isn't responding, and isn't going to take the entrypoint, + // so we have to drop it ourselves. + drop(ManuallyDrop::into_inner(entry)); + return Err(Error::Unresponsive); + } + } + if seq >= cmd_seq.len() { + break; + } + } + + // Wait until the other core has copied `entry` before returning. + fifo.read_blocking(); + + Ok(()) + } else { + Err(Error::InvalidCore) + } + } +} diff --git a/rp235x-hal/src/otp.rs b/rp235x-hal/src/otp.rs new file mode 100644 index 000000000..94d904da3 --- /dev/null +++ b/rp235x-hal/src/otp.rs @@ -0,0 +1,71 @@ +//! Interface to the RP2350's One Time Programmable Memory + +/// The ways in which we can fail to read OTP +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The user passed an invalid index to a function. + InvalidIndex, + /// The hardware refused to let us read this word, probably due to + /// read lock set earlier in the boot process. + InvalidPermissions, +} + +/// OTP read address, using automatic Error Correction. +/// +/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or +/// all-ones on permission failure. Only the first 8 KiB is populated. +pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32; + +/// OTP read address, without using any automatic Error Correction. +/// +/// A 32-bit read returns 24-bits of raw data from the OTP word. +pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32; + +/// How many pages in OTP (post error-correction) +pub const NUM_PAGES: usize = 64; + +/// How many rows in one page in OTP (post error-correction) +pub const NUM_ROWS_PER_PAGE: usize = 64; + +/// How many rows in OTP (post error-correction) +pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE; + +/// Read one ECC protected word from the OTP +pub fn read_ecc_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // First do a raw read to check permissions + let _ = read_raw_word(row)?; + // One 32-bit read gets us two rows + let offset = row >> 1; + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_BASE.add(offset).read() }; + if (row & 1) == 0 { + Ok(value as u16) + } else { + Ok((value >> 16) as u16) + } +} + +/// Read one raw word from the OTP +/// +/// You get the 24-bit raw value in the lower part of the 32-bit result. +pub fn read_raw_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // One 32-bit read gets us one row + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() }; + if value == 0xFFFF_FFFF { + Err(Error::InvalidPermissions) + } else { + Ok(value) + } +} diff --git a/rp235x-hal/src/pio.rs b/rp235x-hal/src/pio.rs new file mode 100644 index 000000000..9004f1f0d --- /dev/null +++ b/rp235x-hal/src/pio.rs @@ -0,0 +1,2314 @@ +//! Programmable IO (PIO) +//! +//! See [Chapter 11](https://rptl.io/rp2350-datasheet#section_pio) of the RP2350 +//! datasheet for more details. + +use core::ops::Deref; +use pio::{Instruction, InstructionOperands, Program, SideSet, Wrap}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::{EndlessReadTarget, EndlessWriteTarget, ReadTarget, TransferSize, Word, WriteTarget}, + gpio::{Function, FunctionPio0, FunctionPio1}, + pac::{self, dma::ch::ch_ctrl_trig::TREQ_SEL_A, pio0::RegisterBlock, PIO0, PIO1}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +const PIO_INSTRUCTION_COUNT: usize = 32; + +impl Sealed for PIO0 {} +impl Sealed for PIO1 {} + +/// PIO Instance +pub trait PIOExt: Deref + SubsystemReset + Sized + Send + Sealed { + /// Associated Pin Function. + type PinFunction: Function; + + /// Create a new PIO wrapper and split the state machines into individual objects. + #[allow(clippy::type_complexity)] // Required for symmetry with PIO::free(). + fn split( + self, + resets: &mut crate::pac::RESETS, + ) -> ( + PIO, + UninitStateMachine<(Self, SM0)>, + UninitStateMachine<(Self, SM1)>, + UninitStateMachine<(Self, SM2)>, + UninitStateMachine<(Self, SM3)>, + ) { + self.reset_bring_down(resets); + self.reset_bring_up(resets); + + let sm0 = UninitStateMachine { + block: self.deref(), + sm: self.sm(0), + _phantom: core::marker::PhantomData, + }; + let sm1 = UninitStateMachine { + block: self.deref(), + sm: self.sm(1), + _phantom: core::marker::PhantomData, + }; + let sm2 = UninitStateMachine { + block: self.deref(), + sm: self.sm(2), + _phantom: core::marker::PhantomData, + }; + let sm3 = UninitStateMachine { + block: self.deref(), + sm: self.sm(3), + _phantom: core::marker::PhantomData, + }; + ( + PIO { + used_instruction_space: 0, + pio: self, + }, + sm0, + sm1, + sm2, + sm3, + ) + } + + /// Number of this PIO (0..1). + fn id() -> usize; +} + +impl PIOExt for PIO0 { + type PinFunction = FunctionPio0; + fn id() -> usize { + 0 + } +} +impl PIOExt for PIO1 { + type PinFunction = FunctionPio1; + fn id() -> usize { + 1 + } +} + +#[allow(clippy::upper_case_acronyms)] +/// Programmable IO Block +pub struct PIO { + used_instruction_space: u32, // bit for each PIO_INSTRUCTION_COUNT + pio: P, +} + +impl core::fmt::Debug for PIO

{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("PIO") + .field("used_instruction_space", &self.used_instruction_space) + .field("pio", &"PIO { .. }") + .finish() + } +} + +// Safety: `PIO` only provides access to those registers which are not directly used by +// `StateMachine`. +unsafe impl Send for PIO

{} + +// Safety: `PIO` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl PIO

{ + /// Free this instance. + /// + /// All output pins are left in their current state. + pub fn free( + self, + _sm0: UninitStateMachine<(P, SM0)>, + _sm1: UninitStateMachine<(P, SM1)>, + _sm2: UninitStateMachine<(P, SM2)>, + _sm3: UninitStateMachine<(P, SM3)>, + ) -> P { + // All state machines have already been stopped. + self.pio + } + + /// This PIO's interrupt by index. + pub fn irq(&self) -> Interrupt<'_, P, IRQ> { + struct IRQSanity; + impl IRQSanity { + const CHECK: () = assert!(IRQ <= 1, "IRQ index must be either 0 or 1"); + } + + #[allow(clippy::let_unit_value)] + let _ = IRQSanity::::CHECK; + Interrupt { + block: self.pio.deref(), + _phantom: core::marker::PhantomData, + } + } + + /// This PIO's IRQ0 interrupt. + pub fn irq0(&self) -> Interrupt<'_, P, 0> { + self.irq() + } + + /// This PIO's IRQ1 interrupt. + pub fn irq1(&self) -> Interrupt<'_, P, 1> { + self.irq() + } + + /// Get raw irq flags. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + pub fn get_irq_raw(&self) -> u8 { + self.pio.irq().read().irq().bits() + } + + /// Clear PIO's IRQ flags indicated by the bits. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + // Safety: PIOExt provides exclusive access to the pio.irq register, this must be preserved to + // satisfy Send trait. + pub fn clear_irq(&self, flags: u8) { + self.pio.irq().write(|w| unsafe { w.irq().bits(flags) }); + } + + /// Force PIO's IRQ flags indicated by the bits. + /// + /// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of + /// the IRQ flags. + // Safety: PIOExt provides exclusive access to the pio.irq register, this must be preserved to + // satisfy Send trait. + pub fn force_irq(&self, flags: u8) { + self.pio + .irq_force() + .write(|w| unsafe { w.irq_force().bits(flags) }); + } + + /// Calculates a mask with the `len` right-most bits set. + fn instruction_mask(len: usize) -> u32 { + if len < 32 { + (1 << len) - 1 + } else { + 0xffffffff + } + } + + /// Tries to find an appropriate offset for the instructions, in range 0..=31. + fn find_offset_for_instructions(&self, i: &[u16], origin: Option) -> Option { + if i.len() > PIO_INSTRUCTION_COUNT || i.is_empty() { + None + } else { + let mask = Self::instruction_mask(i.len()); + if let Some(origin) = origin { + if origin as usize > PIO_INSTRUCTION_COUNT - i.len() + || self.used_instruction_space & (mask << origin) != 0 + { + None + } else { + Some(origin) + } + } else { + for i in (0..=32 - (i.len() as u8)).rev() { + if self.used_instruction_space & (mask << i) == 0 { + return Some(i); + } + } + None + } + } + } + + /// Allocates space in instruction memory and installs the program. + /// + /// The function returns a handle to the installed program that can be used + /// to configure a `StateMachine` via `PIOBuilder`. The program can be + /// uninstalled to free instruction memory via `uninstall()` once the state + /// machine using the program has been uninitialized. + /// + /// Note: We use the RP2040 program size constant, but the RP2350 has the + /// same size instruction memory. + /// + /// # Safety + /// + /// `PIOExt` is marked send and should be the only object allowed to access + /// `pio.instr_mem` + pub fn install( + &mut self, + p: &Program<{ pio::RP2040_MAX_PROGRAM_SIZE }>, + ) -> Result, InstallError> { + if let Some(offset) = self.find_offset_for_instructions(&p.code, p.origin) { + p.code + .iter() + .cloned() + .map(|instr| { + if instr & 0b1110_0000_0000_0000 == 0 { + // this is a JMP instruction -> add offset to address + let address = (instr & 0b11111) as u8; + let address = address + offset; + assert!( + address < pio::RP2040_MAX_PROGRAM_SIZE as u8, + "Invalid JMP out of the program after offset addition" + ); + instr & (!0b11111) | address as u16 + } else { + // this is not a JMP instruction -> keep it unchanged + instr + } + }) + .enumerate() + .for_each(|(i, instr)| { + self.pio + .instr_mem(i + offset as usize) + .write(|w| unsafe { w.instr_mem0().bits(instr) }) + }); + self.used_instruction_space |= Self::instruction_mask(p.code.len()) << offset; + Ok(InstalledProgram { + offset, + length: p.code.len() as u8, + side_set: p.side_set, + wrap: p.wrap, + _phantom: core::marker::PhantomData, + }) + } else { + Err(InstallError::NoSpace) + } + } + + /// Removes the specified program from instruction memory, freeing the allocated space. + pub fn uninstall(&mut self, p: InstalledProgram

) { + let instr_mask = Self::instruction_mask(p.length as usize) << p.offset as u32; + self.used_instruction_space &= !instr_mask; + } +} + +/// Handle to a program that was placed in the PIO's instruction memory. +/// +/// Objects of this type can be reused for multiple state machines of the same PIO block to save +/// memory if multiple state machines are supposed to perform the same function (for example, if +/// one PIO block is used to implement multiple I2C busses). +/// +/// `PIO::uninstall(program)` can be used to free the space occupied by the program once it is no +/// longer used. +/// +/// # Examples +/// +/// ```no_run +/// use rp235x_hal::{self as hal, pio::PIOBuilder, pio::PIOExt}; +/// let mut peripherals = hal::pac::Peripherals::take().unwrap(); +/// let (mut pio, sm0, _, _, _) = peripherals.PIO0.split(&mut peripherals.RESETS); +/// // Install a program in instruction memory. +/// let program = pio_proc::pio_asm!( +/// ".wrap_target", +/// "set pins, 1 [31]", +/// "set pins, 0 [31]", +/// ".wrap" +/// ) +/// .program; +/// let installed = pio.install(&program).unwrap(); +/// // Configure a state machine to use the program. +/// let (sm, rx, tx) = PIOBuilder::from_installed_program(installed).build(sm0); +/// // Uninitialize the state machine again, freeing the program. +/// let (sm, installed) = sm.uninit(rx, tx); +/// // Uninstall the program to free instruction memory. +/// pio.uninstall(installed); +/// ``` +/// +/// # Safety +/// +/// Objects of this type can outlive their `PIO` object. If the PIO block is reinitialized, the API +/// does not prevent the user from calling `uninstall()` when the PIO block does not actually hold +/// the program anymore. The user must therefore make sure that `uninstall()` is only called on the +/// PIO object which was used to install the program. +/// +/// ```ignore +/// let (mut pio, sm0, sm1, sm2, sm3) = pac.PIO0.split(&mut pac.RESETS); +/// // Install a program in instruction memory. +/// let installed = pio.install(&program).unwrap(); +/// // Reinitialize PIO. +/// let pio0 = pio.free(sm0, sm1, sm2, sm3); +/// let (mut pio, _, _, _, _) = pio0.split(&mut pac.RESETS); +/// // Do not do the following, the program is not in instruction memory anymore! +/// pio.uninstall(installed); +/// ``` +#[derive(Debug)] +pub struct InstalledProgram

{ + offset: u8, + length: u8, + side_set: SideSet, + wrap: Wrap, + _phantom: core::marker::PhantomData

, +} + +impl InstalledProgram

{ + /// Change the source and/or target for automatic program wrapping. + /// + /// This replaces the current wrap bounds with a new set. This can be useful if you are running + /// multiple state machines with the same program but using different wrap bounds. + /// + /// # Returns + /// + /// * [`Ok`] containing a new program with the provided wrap bounds + /// * [`Err`] containing the old program if the provided wrap was invalid (outside the bounds of + /// the program length) + pub fn set_wrap(self, wrap: Wrap) -> Result { + if wrap.source < self.length && wrap.target < self.length { + Ok(InstalledProgram { wrap, ..self }) + } else { + Err(self) + } + } + + /// Get the wrap target (entry point) of the installed program. + pub fn wrap_target(&self) -> u8 { + self.offset + self.wrap.target + } + + /// Get the offset the program is installed at. + pub fn offset(&self) -> u8 { + self.offset + } + + /// Clones this program handle so that it can be executed by two state machines at the same + /// time. + /// + /// # Safety + /// + /// This function is marked as unsafe because, once this function has been called, the + /// resulting handle can be used to call `PIO::uninstall()` while the program is still running. + /// + /// The user has to make sure to call `PIO::uninstall()` only once and only after all state + /// machines using the program have been uninitialized. + pub unsafe fn share(&self) -> InstalledProgram

{ + InstalledProgram { + offset: self.offset, + length: self.length, + side_set: self.side_set, + wrap: self.wrap, + _phantom: core::marker::PhantomData, + } + } +} + +/// State machine identifier (without a specified PIO block). +pub trait StateMachineIndex: Send + Sealed { + /// Numerical index of the state machine (0 to 3). + fn id() -> usize; +} + +/// First state machine. +pub struct SM0; +/// Second state machine. +pub struct SM1; +/// Third state machine. +pub struct SM2; +/// Fourth state machine. +pub struct SM3; + +impl StateMachineIndex for SM0 { + fn id() -> usize { + 0 + } +} + +impl Sealed for SM0 {} + +impl StateMachineIndex for SM1 { + fn id() -> usize { + 1 + } +} + +impl Sealed for SM1 {} + +impl StateMachineIndex for SM2 { + fn id() -> usize { + 2 + } +} + +impl Sealed for SM2 {} + +impl StateMachineIndex for SM3 { + fn id() -> usize { + 3 + } +} + +impl Sealed for SM3 {} + +/// Trait to identify a single state machine, as a generic type parameter to `UninitStateMachine`, +/// `InitStateMachine`, etc. +pub trait ValidStateMachine: Sealed { + /// The PIO block to which this state machine belongs. + type PIO: PIOExt; + + /// The index of this state machine (between 0 and 3). + fn id() -> usize; + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8; +} + +/// First state machine of the first PIO block. +pub type PIO0SM0 = (PIO0, SM0); +/// Second state machine of the first PIO block. +pub type PIO0SM1 = (PIO0, SM1); +/// Third state machine of the first PIO block. +pub type PIO0SM2 = (PIO0, SM2); +/// Fourth state machine of the first PIO block. +pub type PIO0SM3 = (PIO0, SM3); +/// First state machine of the second PIO block. +pub type PIO1SM0 = (PIO1, SM0); +/// Second state machine of the second PIO block. +pub type PIO1SM1 = (PIO1, SM1); +/// Third state machine of the second PIO block. +pub type PIO1SM2 = (PIO1, SM2); +/// Fourth state machine of the second PIO block. +pub type PIO1SM3 = (PIO1, SM3); + +impl ValidStateMachine for (P, SM) { + type PIO = P; + fn id() -> usize { + SM::id() + } + fn tx_dreq() -> u8 { + ((P::id() << 3) | SM::id()) as u8 + } + fn rx_dreq() -> u8 { + ((P::id() << 3) | SM::id() | 0x4) as u8 + } +} + +/// Pin State in the PIO +/// +/// Note the GPIO is able to override/invert that. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PinState { + /// Pin in Low state. + High, + /// Pin in Low state. + Low, +} + +/// Pin direction in the PIO +/// +/// Note the GPIO is able to override/invert that. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum PinDir { + /// Pin set as an Input + Input, + /// Pin set as an Output. + Output, +} + +/// PIO State Machine (uninitialized, without a program). +#[derive(Debug)] +pub struct UninitStateMachine { + block: *const RegisterBlock, + sm: *const crate::pac::pio0::SM, + _phantom: core::marker::PhantomData, +} + +// Safety: `UninitStateMachine` only uses atomic accesses to shared registers. +unsafe impl Send for UninitStateMachine {} + +// Safety: `UninitStateMachine` is marked Send so ensure all accesses remain atomic and no new +// concurrent accesses are added. +impl UninitStateMachine { + /// Start and stop the state machine. + fn set_enabled(&mut self, enabled: bool) { + // Bits 3:0 are SM_ENABLE. + let mask = 1 << SM::id(); + if enabled { + self.set_ctrl_bits(mask); + } else { + self.clear_ctrl_bits(mask); + } + } + + fn restart(&mut self) { + // Bits 7:4 are SM_RESTART. + self.set_ctrl_bits(1 << (SM::id() + 4)); + } + + fn reset_clock(&mut self) { + // Bits 11:8 are CLKDIV_RESTART. + self.set_ctrl_bits(1 << (SM::id() + 8)); + } + + // Safety: All ctrl set access should go through this function to ensure atomic access. + fn set_ctrl_bits(&mut self, bits: u32) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_set((*self.block).ctrl().as_ptr(), bits); + } + } + + // Safety: All ctrl clear access should go through this function to ensure atomic access. + fn clear_ctrl_bits(&mut self, bits: u32) { + // Safety: We only use the atomic alias of the register. + unsafe { + write_bitmask_clear((*self.block).ctrl().as_ptr(), bits); + } + } + + // Safety: The Send trait assumes this is the only write to sm_clkdiv + fn set_clock_divisor(&self, int: u16, frac: u8) { + // Safety: This is the only write to this register + unsafe { + self.sm() + .sm_clkdiv() + .write(|w| w.int().bits(int).frac().bits(frac)); + } + } + + unsafe fn sm(&self) -> &crate::pac::pio0::SM { + &*self.sm + } + + unsafe fn pio(&self) -> &RegisterBlock { + &*self.block + } +} + +/// PIO State Machine with an associated program. +pub struct StateMachine { + sm: UninitStateMachine, + program: InstalledProgram, + _phantom: core::marker::PhantomData, +} + +/// Marker for an initialized, but stopped state machine. +pub struct Stopped; +/// Marker for an initialized and running state machine. +pub struct Running; + +/// Id for the PIO's IRQ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PioIRQ { + #[allow(missing_docs)] + Irq0, + #[allow(missing_docs)] + Irq1, +} +impl PioIRQ { + const fn to_index(self) -> usize { + match self { + PioIRQ::Irq0 => 0, + PioIRQ::Irq1 => 1, + } + } +} + +impl StateMachine { + /// Stops the state machine if it is still running and returns its program. + /// + /// The program can be uninstalled to free space once it is no longer used by any state + /// machine. + pub fn uninit( + mut self, + _rx: Rx, + _tx: Tx, + ) -> (UninitStateMachine, InstalledProgram) { + self.sm.set_enabled(false); + (self.sm, self.program) + } + + /// The address of the instruction currently being executed. + pub fn instruction_address(&self) -> u32 { + // Safety: Read only access without side effect + unsafe { self.sm.sm().sm_addr().read().bits() } + } + + #[deprecated(note = "Renamed to exec_instruction")] + /// Execute the instruction immediately. + pub fn set_instruction(&mut self, instruction: u16) { + let instruction = + Instruction::decode(instruction, self.program.side_set).expect("Invalid instruction"); + self.exec_instruction(instruction); + } + + /// Execute the instruction immediately. + /// + /// If an instruction written to INSTR stalls, it is stored in the same instruction latch used + /// by OUT EXEC and MOV EXEC, and will overwrite an in-progress instruction there. If EXEC + /// instructions are used, instructions written to INSTR must not stall. + pub fn exec_instruction(&mut self, instruction: Instruction) { + let instruction = instruction.encode(self.program.side_set); + + // Safety: all accesses to this register are controlled by this instance + unsafe { + self.sm + .sm() + .sm_instr() + .write(|w| w.sm0_instr().bits(instruction)) + } + } + + /// Check if the current instruction is stalled. + pub fn stalled(&self) -> bool { + // Safety: read only access without side effect + unsafe { self.sm.sm().sm_execctrl().read().exec_stalled().bit() } + } + + /// Clear both TX and RX FIFOs + pub fn clear_fifos(&mut self) { + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_shiftctrl = sm.sm_shiftctrl(); + let mut current = false; + // Toggling the FIFO join state clears the fifo + sm_shiftctrl.modify(|r, w| { + current = r.fjoin_rx().bit(); + w.fjoin_rx().bit(!current) + }); + sm_shiftctrl.modify(|_, w| w.fjoin_rx().bit(current)); + } + } + + /// Drain Tx fifo. + pub fn drain_tx_fifo(&mut self) { + // According to the datasheet 3.5.4.2 Page 358: + // + // When autopull is enabled, the behaviour of 'PULL' is altered: it becomes a no-op + // if the OSR is full. This is to avoid a race condition against the system + // DMA. It behaves as a fence: either an autopull has already taken place, in which case + // the 'PULL' has no effect, or the program will stall on the 'PULL' until data becomes + // available in the FIFO. + + // TODO: encode at compile time once pio 0.3.0 is out + const OUT: InstructionOperands = InstructionOperands::OUT { + destination: pio::OutDestination::NULL, + bit_count: 32, + }; + const PULL: InstructionOperands = InstructionOperands::PULL { + if_empty: false, + block: false, + }; + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_instr = sm.sm_instr(); + let fstat = self.sm.pio().fstat(); + + let operands = if sm.sm_shiftctrl().read().autopull().bit_is_set() { + OUT + } else { + PULL + } + .encode(); + + // Safety: sm0_instr may be accessed from SM::exec_instruction. + let mut saved_sideset_count = 0; + sm_pinctrl.modify(|r, w| { + saved_sideset_count = r.sideset_count().bits(); + w.sideset_count().bits(0) + }); + + let mask = 1 << SM::id(); + // white tx fifo is not empty + while (fstat.read().txempty().bits() & mask) == 0 { + sm_instr.write(|w| w.sm0_instr().bits(operands)) + } + + if saved_sideset_count != 0 { + sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count)); + } + } + } + + /// Change the clock divider of a state machine. + /// + /// Changing the clock divider of a running state machine is allowed + /// and guaranteed to not cause any glitches, but the exact timing of + /// clock pulses during the change is not specified. + pub fn set_clock_divisor(&mut self, divisor: f32) { + // sm frequency = clock freq / (CLKDIV_INT + CLKDIV_FRAC / 256) + let int = divisor as u16; + let frac = ((divisor - int as f32) * 256.0) as u8; + + self.sm.set_clock_divisor(int, frac); + } + + /// Change the clock divider of a state machine using a 16.8 fixed point value. + /// + /// Changing the clock divider of a running state machine is allowed + /// and guaranteed to not cause any glitches, but the exact timing of + /// clock pulses during the change is not specified. + pub fn clock_divisor_fixed_point(&mut self, int: u16, frac: u8) { + self.sm.set_clock_divisor(int, frac); + } +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for StateMachine {} + +// Safety: `StateMachine` is marked Send so ensure all accesses remain atomic and no new concurrent +// accesses are added. +impl StateMachine { + /// Starts execution of the selected program. + pub fn start(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(true); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } + } + + /// Sets the pin state for the specified pins. + /// + /// The user has to make sure that they do not select any pins that are in use by any + /// other state machines of the same PIO block. + /// + /// The iterator's item are pairs of `(pin_number, pin_state)`. + pub fn set_pins(&mut self, pins: impl IntoIterator) { + // TODO: turn those three into const once pio 0.3.0 is released + let set_high_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINS, + data: 1, + } + .encode(); + let set_low_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINS, + data: 0, + } + .encode(); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_execctrl = sm.sm_execctrl(); + let sm_instr = sm.sm_instr(); + + // sideset_count is implicitly set to 0 when the set_base/set_count are written (rather + // than modified) + let saved_pin_ctrl = sm_pinctrl.read().bits(); + let mut saved_execctrl = 0; + + sm_execctrl.modify(|r, w| { + saved_execctrl = r.bits(); + w.out_sticky().clear_bit() + }); + + for (pin_num, pin_state) in pins { + sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1)); + let instruction = if pin_state == PinState::High { + set_high_instr + } else { + set_low_instr + }; + + sm_instr.write(|w| w.sm0_instr().bits(instruction)) + } + + sm_pinctrl.write(|w| w.bits(saved_pin_ctrl)); + sm_execctrl.write(|w| w.bits(saved_execctrl)); + } + } + + /// Set pin directions. + /// + /// The user has to make sure that they do not select any pins that are in use by any + /// other state machines of the same PIO block. + /// + /// The iterator's item are pairs of `(pin_number, pin_dir)`. + pub fn set_pindirs(&mut self, pindirs: impl IntoIterator) { + // TODO: turn those three into const once pio 0.3.0 is released + let set_output_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINDIRS, + data: 1, + } + .encode(); + let set_input_instr = InstructionOperands::SET { + destination: pio::SetDestination::PINDIRS, + data: 0, + } + .encode(); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = &sm.sm_pinctrl(); + let sm_execctrl = &sm.sm_execctrl(); + let sm_instr = &sm.sm_instr(); + + // sideset_count is implicitly set to 0 when the set_base/set_count are written (rather + // than modified) + let saved_pin_ctrl = sm_pinctrl.read().bits(); + let mut saved_execctrl = 0; + + sm_execctrl.modify(|r, w| { + saved_execctrl = r.bits(); + w.out_sticky().clear_bit() + }); + + for (pin_num, pin_dir) in pindirs { + sm_pinctrl.write(|w| w.set_base().bits(pin_num).set_count().bits(1)); + let instruction = if pin_dir == PinDir::Output { + set_output_instr + } else { + set_input_instr + }; + + sm_instr.write(|w| w.sm0_instr().bits(instruction)) + } + + sm_pinctrl.write(|w| w.bits(saved_pin_ctrl)); + sm_execctrl.write(|w| w.bits(saved_execctrl)); + } + } +} + +impl StateMachine<(P, SM), Stopped> { + /// Restarts the clock dividers for the specified state machines. + /// + /// As a result, the clock will be synchronous for the state machines, which is a precondition + /// for synchronous operation. + /// + /// The function returns an object that, once destructed, restarts the clock dividers. This + /// object allows further state machines to be added if more than two shall be synchronized. + /// + /// # Example + /// + /// ```ignore + /// sm0.synchronize_with(sm1).and_with(sm2); + /// ``` + pub fn synchronize_with<'sm, SM2: StateMachineIndex>( + &'sm mut self, + _other_sm: &'sm mut StateMachine<(P, SM2), Stopped>, + ) -> Synchronize<'sm, (P, SM)> { + let sm_mask = (1 << SM::id()) | (1 << SM2::id()); + Synchronize { sm: self, sm_mask } + } +} + +impl StateMachine<(P, SM), State> { + /// Create a group of state machines, which can be started/stopped synchronously + pub fn with( + self, + other_sm: StateMachine<(P, SM2), State>, + ) -> StateMachineGroup2 { + StateMachineGroup2 { + sm1: self, + sm2: other_sm, + } + } +} + +/// Group of 2 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup2< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, +} + +/// Group of 3 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup3< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, +} + +/// Group of 4 state machines, which can be started/stopped synchronously. +pub struct StateMachineGroup4< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, +> { + sm1: StateMachine<(P, SM1Idx), State>, + sm2: StateMachine<(P, SM2Idx), State>, + sm3: StateMachine<(P, SM3Idx), State>, + sm4: StateMachine<(P, SM4Idx), State>, +} + +impl + StateMachineGroup2 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + ) { + (self.sm1, self.sm2) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM3Idx), State>, + ) -> StateMachineGroup3 { + StateMachineGroup3 { + sm1: self.sm1, + sm2: self.sm2, + sm3: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + State, + > StateMachineGroup3 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3) + } + + /// Add another state machine to the group + pub fn with( + self, + other_sm: StateMachine<(P, SM4Idx), State>, + ) -> StateMachineGroup4 { + StateMachineGroup4 { + sm1: self.sm1, + sm2: self.sm2, + sm3: self.sm3, + sm4: other_sm, + } + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + State, + > StateMachineGroup4 +{ + /// Split the group, releasing the contained state machines + #[allow(clippy::type_complexity)] + pub fn free( + self, + ) -> ( + StateMachine<(P, SM1Idx), State>, + StateMachine<(P, SM2Idx), State>, + StateMachine<(P, SM3Idx), State>, + StateMachine<(P, SM4Idx), State>, + ) { + (self.sm1, self.sm2, self.sm3, self.sm4) + } + + fn mask(&self) -> u32 { + (1 << SM1Idx::id()) | (1 << SM2Idx::id()) | (1 << SM3Idx::id()) | (1 << SM4Idx::id()) + } +} + +impl + StateMachineGroup2 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup2 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup3 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Start grouped state machines + pub fn start(mut self) -> StateMachineGroup4 { + self.sm1.sm.set_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +impl + StateMachineGroup2 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup2 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup2 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + > StateMachineGroup3 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup3 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup3 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + } + } +} + +impl< + P: PIOExt, + SM1Idx: StateMachineIndex, + SM2Idx: StateMachineIndex, + SM3Idx: StateMachineIndex, + SM4Idx: StateMachineIndex, + > StateMachineGroup4 +{ + /// Stop grouped state machines + pub fn stop(mut self) -> StateMachineGroup4 { + self.sm1.sm.clear_ctrl_bits(self.mask()); + StateMachineGroup4 { + sm1: StateMachine { + sm: self.sm1.sm, + program: self.sm1.program, + _phantom: core::marker::PhantomData, + }, + sm2: StateMachine { + sm: self.sm2.sm, + program: self.sm2.program, + _phantom: core::marker::PhantomData, + }, + sm3: StateMachine { + sm: self.sm3.sm, + program: self.sm3.program, + _phantom: core::marker::PhantomData, + }, + sm4: StateMachine { + sm: self.sm4.sm, + program: self.sm4.program, + _phantom: core::marker::PhantomData, + }, + } + } + + /// Sync grouped state machines + pub fn sync(mut self) -> Self { + self.sm1.sm.set_ctrl_bits(self.mask() << 8); + self + } +} + +/// Type which, once destructed, restarts the clock dividers for all selected state machines, +/// effectively synchronizing them. +pub struct Synchronize<'sm, SM: ValidStateMachine> { + sm: &'sm mut StateMachine, + sm_mask: u32, +} + +impl<'sm, P: PIOExt, SM: StateMachineIndex> Synchronize<'sm, (P, SM)> { + /// Adds another state machine to be synchronized. + pub fn and_with( + mut self, + _other_sm: &'sm mut StateMachine<(P, SM2), Stopped>, + ) -> Self { + // Add another state machine index to the mask. + self.sm_mask |= 1 << SM2::id(); + self + } +} + +impl<'sm, SM: ValidStateMachine> Drop for Synchronize<'sm, SM> { + fn drop(&mut self) { + // Restart the clocks of all state machines specified by the mask. + // Bits 11:8 of CTRL contain CLKDIV_RESTART. + let sm_mask = self.sm_mask << 8; + self.sm.sm.set_ctrl_bits(sm_mask); + } +} + +impl StateMachine { + /// Stops execution of the selected program. + pub fn stop(mut self) -> StateMachine { + // Enable SM + self.sm.set_enabled(false); + + StateMachine { + sm: self.sm, + program: self.program, + _phantom: core::marker::PhantomData, + } + } + + /// Restarts the execution of the selected program from its wrap target. + pub fn restart(&mut self) { + // pause the state machine + self.sm.set_enabled(false); + + // Safety: all accesses to these registers are controlled by this instance + unsafe { + let sm = self.sm.sm(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_instr = sm.sm_instr(); + + // save exec_ctrl & make side_set optional + let mut saved_sideset_count = 0; + sm_pinctrl.modify(|r, w| { + saved_sideset_count = r.sideset_count().bits(); + w.sideset_count().bits(0) + }); + + // revert it to its wrap target + let instruction = InstructionOperands::JMP { + condition: pio::JmpCondition::Always, + address: self.program.wrap_target(), + } + .encode(); + sm_instr.write(|w| w.sm0_instr().bits(instruction)); + + // restore exec_ctrl + if saved_sideset_count != 0 { + sm_pinctrl.modify(|_, w| w.sideset_count().bits(saved_sideset_count)); + } + + // clear osr/isr + self.sm.restart(); + } + + // unpause the state machine + self.sm.set_enabled(true); + } +} + +/// PIO RX FIFO handle. +pub struct Rx { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<(SM, RxSize)>, +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for Rx {} + +// Safety: `Rx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl Rx { + unsafe fn block(&self) -> &pac::pio0::RegisterBlock { + &*self.block + } + + /// Gets the FIFO's address. + /// + /// This is useful if you want to DMA from this peripheral. + /// + /// NB: You are responsible for using the pointer correctly and not + /// underflowing the buffer. + pub fn fifo_address(&self) -> *const u32 { + // Safety: returning the address is safe as such. The user is responsible for any + // dereference ops at that address. + unsafe { self.block().rxf(SM::id()).as_ptr() } + } + + /// Gets the FIFO's `DREQ` value. + /// + /// This is a value between 0 and 39. Each FIFO on each state machine on + /// each PIO has a unique value. + pub fn dreq_value(&self) -> u8 { + if self.block as usize == 0x5020_0000usize { + TREQ_SEL_A::PIO0_RX0 as u8 + (SM::id() as u8) + } else { + TREQ_SEL_A::PIO1_RX0 as u8 + (SM::id() as u8) + } + } + + /// Get the next element from RX FIFO. + /// + /// Returns `None` if the FIFO is empty. + pub fn read(&mut self) -> Option { + if self.is_empty() { + return None; + } + + // Safety: The register is unique to this Rx instance. + Some(unsafe { core::ptr::read_volatile(self.fifo_address()) }) + } + + /// Enable/Disable the autopush feature of the state machine. + // Safety: This register is read by Rx, this is the only write. + pub fn enable_autopush(&mut self, enable: bool) { + // Safety: only instance reading/writing to autopush bit and no other write to this + // register + unsafe { + self.block() + .sm(SM::id()) + .sm_shiftctrl() + .modify(|_, w| w.autopush().bit(enable)) + } + } + + /// Indicate if the rx FIFO is empty + pub fn is_empty(&self) -> bool { + // Safety: Read only access without side effect + unsafe { self.block().fstat().read().rxempty().bits() & (1 << SM::id()) != 0 } + } + + /// Indicate if the rx FIFO is full + pub fn is_full(&self) -> bool { + // Safety: Read only access without side effect + unsafe { self.block().fstat().read().rxfull().bits() & (1 << SM::id()) != 0 } + } + + /// Enable RX FIFO not empty interrupt. + /// + /// This interrupt is raised when the RX FIFO is not empty, i.e. one could read more data from it. + pub fn enable_rx_not_empty_interrupt(&self, id: PioIRQ) { + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Disable RX FIFO not empty interrupt. + pub fn disable_rx_not_empty_interrupt(&self, id: PioIRQ) { + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Force RX FIFO not empty interrupt. + pub fn force_rx_not_empty_interrupt(&self, id: PioIRQ, state: bool) { + let action = if state { + write_bitmask_set + } else { + write_bitmask_clear + }; + // Safety: Atomic write to a single bit owned by this instance + unsafe { + action( + self.block().sm_irq(id.to_index()).irq_intf().as_ptr(), + 1 << SM::id(), + ); + } + } + + /// Set the transfer size used in DMA transfers. + pub fn transfer_size(self, size: RSZ) -> Rx { + let _ = size; + Rx { + block: self.block, + _phantom: core::marker::PhantomData, + } + } +} + +// Safety: This only reads from the state machine fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl ReadTarget for Rx { + type ReceivedWord = RxSize::Type; + + fn rx_treq() -> Option { + Some(SM::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + ( + unsafe { &*self.block }.rxf(SM::id()).as_ptr() as u32, + u32::MAX, + ) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl EndlessReadTarget for Rx {} + +/// PIO TX FIFO handle. +pub struct Tx { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<(SM, TxSize)>, +} + +// Safety: All shared register accesses are atomic. +unsafe impl Send for Tx {} + +// Safety: `Tx` is marked Send so ensure all accesses remain atomic and no new concurrent accesses +// are added. +impl Tx { + unsafe fn block(&self) -> &pac::pio0::RegisterBlock { + &*self.block + } + + fn write_generic(&mut self, value: T) -> bool { + if !self.is_full() { + // Safety: Only accessed by this instance (unless DMA is used). + unsafe { + let reg_ptr = self.fifo_address() as *mut T; + reg_ptr.write_volatile(value); + } + true + } else { + false + } + } + + /// Gets the FIFO's address. + /// + /// This is useful if you want to DMA to this peripheral. + /// + /// NB: You are responsible for using the pointer correctly and not + /// overflowing the buffer. + pub fn fifo_address(&self) -> *const u32 { + // Safety: The only access to this register + unsafe { self.block().txf(SM::id()).as_ptr() } + } + + /// Gets the FIFO's `DREQ` value. + /// + /// This is a value between 0 and 39. Each FIFO on each state machine on + /// each PIO has a unique value. + pub fn dreq_value(&self) -> u8 { + if self.block as usize == 0x5020_0000usize { + TREQ_SEL_A::PIO0_TX0 as u8 + (SM::id() as u8) + } else { + TREQ_SEL_A::PIO1_TX0 as u8 + (SM::id() as u8) + } + } + + /// Write a u32 value to TX FIFO. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + pub fn write(&mut self, value: u32) -> bool { + self.write_generic(value) + } + + /// Write a replicated u8 value to TX FIFO. + /// + /// Memory mapped register writes that are smaller than 32bits will trigger + /// "Narrow IO Register Write" behaviour in rp235x - the value written will + /// be replicated to the rest of the register as described in + /// [RP2350 Datasheet: 2.1.5. - Narrow IO Register Writes][section_2_1_5] + /// + /// + /// This 8bit write will set all 4 bytes of the FIFO to `value` + /// Eg: if you write `0xBA` the value written to the the FIFO will be + /// `0xBABABABA` + /// + /// If you wish to write an 8bit number without replication, + /// use `write(my_u8 as u32)` instead. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + /// + /// [section_2_1_5]: + pub fn write_u8_replicated(&mut self, value: u8) -> bool { + self.write_generic(value) + } + + /// Write a replicated 16bit value to TX FIFO. + /// + /// Memory mapped register writes that are smaller than 32bits will trigger + /// "Narrow IO Register Write" behaviour in rp235x - the value written will + /// be replicated to the rest of the register as described in + /// [RP2350 Datasheet: 2.1.5. - Narrow IO Register Writes][section_2_1_5] + /// + /// This 16bit write will set both the upper and lower half of the FIFO entry to `value`. + /// + /// For example, if you write `0xC0DA` the value written to the FIFO will be + /// `0xC0DAC0DA` + /// + /// If you wish to write a 16bit number without replication, + /// use `write(my_u16 as u32)` instead. + /// + /// Returns `true` if the value was written to FIFO, `false` otherwise. + /// + /// [section_2_1_5]: + pub fn write_u16_replicated(&mut self, value: u16) -> bool { + self.write_generic(value) + } + + /// Checks if the state machine has stalled on empty TX FIFO during a blocking PULL, or an OUT + /// with autopull enabled. + /// + /// **Note this is a sticky flag and may not reflect the current state of the machine.** + pub fn has_stalled(&self) -> bool { + let mask = 1 << SM::id(); + // Safety: read-only access without side-effect + unsafe { self.block().fdebug().read().txstall().bits() & mask == mask } + } + + /// Clears the `tx_stalled` flag. + pub fn clear_stalled_flag(&self) { + let mask = 1 << SM::id(); + + // Safety: These bits are WC, only the one corresponding to this SM is set. + unsafe { + self.block().fdebug().write(|w| w.txstall().bits(mask)); + } + } + + /// Indicate if the tx FIFO is empty + pub fn is_empty(&self) -> bool { + // Safety: read-only access without side-effect + unsafe { self.block().fstat().read().txempty().bits() & (1 << SM::id()) != 0 } + } + + /// Indicate if the tx FIFO is full + pub fn is_full(&self) -> bool { + // Safety: read-only access without side-effect + unsafe { self.block().fstat().read().txfull().bits() & (1 << SM::id()) != 0 } + } + + /// Enable TX FIFO not full interrupt. + /// + /// This interrupt is raised when the TX FIFO is not full, i.e. one could push more data to it. + pub fn enable_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Disable TX FIFO not full interrupt. + pub fn disable_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_clear( + self.block().sm_irq(id.to_index()).irq_inte().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Force TX FIFO not full interrupt. + pub fn force_tx_not_full_interrupt(&self, id: PioIRQ) { + // Safety: Atomic access to the register. Bit only modified by this Tx + unsafe { + write_bitmask_set( + self.block().sm_irq(id.to_index()).irq_intf().as_ptr(), + 1 << (SM::id() + 4), + ); + } + } + + /// Set the transfer size used in DMA transfers. + pub fn transfer_size(self, size: RSZ) -> Tx { + let _ = size; + Tx { + block: self.block, + _phantom: core::marker::PhantomData, + } + } +} + +// Safety: This only writes to the state machine fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl WriteTarget for Tx { + type TransmittedWord = TxSize::Type; + + fn tx_treq() -> Option { + Some(SM::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + ( + unsafe { &*self.block }.txf(SM::id()).as_ptr() as u32, + u32::MAX, + ) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl EndlessWriteTarget for Tx {} + +/// PIO Interrupt controller. +#[derive(Debug)] +pub struct Interrupt<'a, P: PIOExt, const IRQ: usize> { + block: *const RegisterBlock, + _phantom: core::marker::PhantomData<&'a P>, +} + +// Safety: `Interrupt` provides exclusive access to interrupt registers. +unsafe impl<'a, P: PIOExt, const IRQ: usize> Send for Interrupt<'a, P, IRQ> {} + +// Safety: `Interrupt` is marked Send so ensure all accesses remain atomic and no new concurrent +// accesses are added. +// `Interrupt` provides exclusive access to `irq_intf` to `irq_inte` for it's state machine, this +// must remain true to satisfy Send. +impl<'a, P: PIOExt, const IRQ: usize> Interrupt<'a, P, IRQ> { + /// Enable interrupts raised by state machines. + /// + /// The PIO peripheral has 4 outside visible interrupts that can be raised by the state machines. Note that this + /// does not correspond with the state machine index; any state machine can raise any one of the four interrupts. + pub fn enable_sm_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << (id + 8)); + } + } + + /// Disable interrupts raised by state machines. + /// + /// See [`Self::enable_sm_interrupt`] for info about the index. + pub fn disable_sm_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << (id + 8)); + } + } + + /// Force state machine interrupt. + /// + /// Note that this doesn't affect the state seen by the state machine. For that, see [`PIO::force_irq`]. + /// + /// + /// + /// See [`Self::enable_sm_interrupt`] for info about the index. + pub fn force_sm_interrupt(&self, id: u8, set: bool) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + if set { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << (id + 8)); + } else { + write_bitmask_clear(self.irq().irq_intf().as_ptr(), 1 << (id + 8)); + } + } + } + + /// Enable TX FIFO not full interrupt. + /// + /// Each of the 4 state machines have their own TX FIFO. This interrupt is raised when the TX FIFO is not full, i.e. + /// one could push more data to it. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn enable_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << (id + 4)); + } + } + + /// Disable TX FIFO not full interrupt. + /// + /// See [`Self::enable_tx_not_full_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn disable_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << (id + 4)); + } + } + + /// Force TX FIFO not full interrupt. + /// + /// See [`Self::enable_tx_not_full_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn force_tx_not_full_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << (id + 4)); + } + } + + /// Enable RX FIFO not empty interrupt. + /// + /// Each of the 4 state machines have their own RX FIFO. This interrupt is raised when the RX FIFO is not empty, + /// i.e. one could read more data from it. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn enable_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_inte().as_ptr(), 1 << id); + } + } + + /// Disable RX FIFO not empty interrupt. + /// + /// See [`Self::enable_rx_not_empty_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn disable_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_clear(self.irq().irq_inte().as_ptr(), 1 << id); + } + } + + /// Force RX FIFO not empty interrupt. + /// + /// See [`Self::enable_rx_not_empty_interrupt`] for info about the index. + #[deprecated( + since = "0.7.0", + note = "Use the dedicated method on the state machine" + )] + pub fn force_rx_not_empty_interrupt(&self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + // Safety: Atomic write to a single bit owned by this instance + unsafe { + write_bitmask_set(self.irq().irq_intf().as_ptr(), 1 << id); + } + } + + /// Get the raw interrupt state. + /// + /// This is the state of the interrupts without interrupt masking and forcing. + pub fn raw(&self) -> InterruptState { + InterruptState( + // Safety: Read only access without side effect + unsafe { self.block().intr().read().bits() }, + ) + } + + /// Get the interrupt state. + /// + /// This is the state of the interrupts after interrupt masking and forcing. + pub fn state(&self) -> InterruptState { + InterruptState( + // Safety: Read only access without side effect + unsafe { self.irq().irq_ints().read().bits() }, + ) + } + + unsafe fn block(&self) -> &RegisterBlock { + &*self.block + } + + unsafe fn irq(&self) -> &crate::pac::pio0::SM_IRQ { + self.block().sm_irq(IRQ) + } +} + +/// Provides easy access for decoding PIO's interrupt state. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct InterruptState(u32); + +macro_rules! raw_interrupt_accessor { + ($name:ident, $doc:literal, $idx:expr) => { + #[doc = concat!("Check whether interrupt ", $doc, " has been raised.")] + pub fn $name(self) -> bool { + self.0 & (1 << $idx) != 0 + } + }; +} +impl InterruptState { + raw_interrupt_accessor!(sm0_rx_not_empty, "SM0_RXNEMPTY", 0); + raw_interrupt_accessor!(sm1_rx_not_empty, "SM1_RXNEMPTY", 1); + raw_interrupt_accessor!(sm2_rx_not_empty, "SM2_RXNEMPTY", 2); + raw_interrupt_accessor!(sm3_rx_not_empty, "SM3_RXNEMPTY", 3); + + raw_interrupt_accessor!(sm0_tx_not_full, "SM0_TXNFULL", 4); + raw_interrupt_accessor!(sm1_tx_not_full, "SM1_TXNFULL", 5); + raw_interrupt_accessor!(sm2_tx_not_full, "SM2_TXNFULL", 6); + raw_interrupt_accessor!(sm3_tx_not_full, "SM3_TXNFULL", 7); + + raw_interrupt_accessor!(sm0, "SM0", 8); + raw_interrupt_accessor!(sm1, "SM1", 9); + raw_interrupt_accessor!(sm2, "SM2", 10); + raw_interrupt_accessor!(sm3, "SM3", 11); +} + +/// Comparison used for `mov x, status` instruction. +#[derive(Debug, Clone, Copy)] +pub enum MovStatusConfig { + /// The `mov x, status` instruction returns all ones if TX FIFO level is below the set status, otherwise all zeros. + Tx(u8), + /// The `mov x, status` instruction returns all ones if RX FIFO level is below the set status, otherwise all zeros. + Rx(u8), + /// The `mov x, status` instruction returns all ones if the indexed IRQ flag is raised, otherwise all-zeroes + Irq(u8), +} + +/// Shift direction for input and output shifting. +#[derive(Debug, Clone, Copy)] +pub enum ShiftDirection { + /// Shift register to left. + Left, + /// Shift register to right. + Right, +} + +impl ShiftDirection { + fn bit(self) -> bool { + match self { + Self::Left => false, + Self::Right => true, + } + } +} + +/// Builder to deploy a fully configured PIO program on one of the state +/// machines. +#[derive(Debug)] +pub struct PIOBuilder

{ + /// Clock divisor. + clock_divisor: (u16, u8), + + /// Program location and configuration. + program: InstalledProgram

, + /// GPIO pin used by `jmp pin` instruction. + jmp_pin: u8, + + /// Continuously assert the most recent OUT/SET to the pins. + out_sticky: bool, + /// Use a bit of OUT data as an auxiliary write enable. + /// + /// When [`out_sticky`](Self::out_sticky) is enabled, setting the bit to 0 deasserts for that instr. + inline_out: Option, + /// Config for `mov x, status` instruction. + mov_status: MovStatusConfig, + + /// Config for FIFO joining. + fifo_join: Buffers, + + /// Number of bits shifted out of `OSR` before autopull or conditional pull will take place. + pull_threshold: u8, + /// Number of bits shifted into `ISR` before autopush or conditional push will take place. + push_threshold: u8, + /// Shift direction for `OUT` instruction. + out_shiftdir: ShiftDirection, + /// Shift direction for `IN` instruction. + in_shiftdir: ShiftDirection, + /// Enable autopull. + autopull: bool, + /// Enable autopush. + autopush: bool, + + /// Number of pins asserted by a `SET`. + set_count: u8, + /// Number of pins asserted by an `OUT PINS`, `OUT PINDIRS` or `MOV PINS` instruction. + out_count: u8, + /// The first pin that is assigned in state machine's `IN` data bus. + in_base: u8, + /// The first pin that is affected by side-set operations. + side_set_base: u8, + /// The first pin that is affected by `SET PINS` or `SET PINDIRS` instructions. + set_base: u8, + /// The first pin that is affected by `OUT PINS`, `OUT PINDIRS` or `MOV PINS` instructions. + out_base: u8, +} + +/// Buffer sharing configuration. +#[derive(Debug, Clone, Copy)] +pub enum Buffers { + /// No sharing. + RxTx, + /// The memory of the RX FIFO is given to the TX FIFO to double its depth. + OnlyTx, + /// The memory of the TX FIFO is given to the RX FIFO to double its depth. + OnlyRx, +} + +/// Errors that occurred during `PIO::install`. +#[derive(Debug)] +pub enum InstallError { + /// There was not enough space for the instructions on the selected PIO. + NoSpace, +} + +impl PIOBuilder

{ + /// Set config settings based on information from the given [`InstalledProgram`]. + /// Additional configuration may be needed in addition to this. + /// + /// Note: This was formerly called `from_program`. The new function has + /// a different default shift direction, `ShiftDirection::Right`, matching + /// the hardware reset value. + pub fn from_installed_program(p: InstalledProgram

) -> Self { + PIOBuilder { + clock_divisor: (1, 0), + program: p, + jmp_pin: 0, + out_sticky: false, + inline_out: None, + mov_status: MovStatusConfig::Tx(0), + fifo_join: Buffers::RxTx, + pull_threshold: 0, + push_threshold: 0, + out_shiftdir: ShiftDirection::Right, + in_shiftdir: ShiftDirection::Right, + autopull: false, + autopush: false, + set_count: 5, + out_count: 0, + in_base: 0, + side_set_base: 0, + set_base: 0, + out_base: 0, + } + } + + /// Set config settings based on information from the given [`InstalledProgram`]. + /// Additional configuration may be needed in addition to this. + /// + /// Note: The shift direction for both input and output shift registers + /// defaults to `ShiftDirection::Left`, which is different from the + /// rp235x reset value. The alternative [`Self::from_installed_program`], + /// fixes this. + #[deprecated( + note = "please use `from_installed_program` instead and update shift direction if necessary" + )] + pub fn from_program(p: InstalledProgram

) -> Self { + PIOBuilder { + clock_divisor: (1, 0), + program: p, + jmp_pin: 0, + out_sticky: false, + inline_out: None, + mov_status: MovStatusConfig::Tx(0), + fifo_join: Buffers::RxTx, + pull_threshold: 0, + push_threshold: 0, + out_shiftdir: ShiftDirection::Left, + in_shiftdir: ShiftDirection::Left, + autopull: false, + autopush: false, + set_count: 5, + out_count: 0, + in_base: 0, + side_set_base: 0, + set_base: 0, + out_base: 0, + } + } + + /// Set the config for when the status register is set to true. + /// + /// See `MovStatusConfig` for more info. + pub fn set_mov_status_config(mut self, mov_status: MovStatusConfig) -> Self { + self.mov_status = mov_status; + + self + } + + /// Set the pins asserted by `SET` instruction. + /// + /// The least-significant bit of `SET` instruction asserts the state of the pin indicated by `base`, the next bit + /// asserts the state of the next pin, and so on up to `count` pins. The pin numbers are considered modulo 32. + pub fn set_pins(mut self, base: u8, count: u8) -> Self { + assert!(count <= 5); + self.set_base = base; + self.set_count = count; + self + } + + /// Set the pins asserted by `OUT` instruction. + /// + /// The least-significant bit of `OUT` instruction asserts the state of the pin indicated by `base`, the next bit + /// asserts the state of the next pin, and so on up to `count` pins. The pin numbers are considered modulo 32. + pub fn out_pins(mut self, base: u8, count: u8) -> Self { + assert!(count <= 32); + self.out_base = base; + self.out_count = count; + self + } + + /// Set the pins used by `IN` instruction. + /// + /// The `IN` instruction reads the least significant bit from the pin indicated by `base`, the next bit from the + /// next pin, and so on. The pin numbers are considered modulo 32. + pub fn in_pin_base(mut self, base: u8) -> Self { + self.in_base = base; + self + } + + /// Set the pin used by `JMP PIN` instruction. + /// + /// When the pin set by this function is high, the jump is taken, otherwise not. + pub fn jmp_pin(mut self, pin: u8) -> Self { + self.jmp_pin = pin; + self + } + + /// Set the pins used by side-set instructions. + /// + /// The least-significant side-set bit asserts the state of the pin indicated by `base`, the next bit asserts the + /// state of the next pin, and so on up to [`pio::SideSet::bits()`] bits as configured in + /// [`pio::Program`]. + pub fn side_set_pin_base(mut self, base: u8) -> Self { + self.side_set_base = base; + self + } + // TODO: Update documentation above. + + /// Set buffer sharing. + /// + /// See [`Buffers`] for more information. + pub fn buffers(mut self, buffers: Buffers) -> Self { + self.fifo_join = buffers; + self + } + + /// Set the clock divisor. + /// + /// The is based on the sys_clk. Set 1 for full speed. A clock divisor of `n` will cause the state machine to run 1 + /// cycle every `n` clock cycles. For small values of `n`, a fractional divisor may introduce unacceptable jitter. + #[deprecated( + since = "0.7.0", + note = "Pulls in floating points. Use the fixed point alternative: clock_divisor_fixed_point" + )] + pub fn clock_divisor(mut self, divisor: f32) -> Self { + self.clock_divisor = (divisor as u16, (divisor * 256.0) as u8); + self + } + + /// The clock is based on the `sys_clk` and will execute an instruction every `int + (frac/256)` ticks. + /// + /// A clock divisor of `n` will cause the state machine to run 1 cycle every `n` clock cycles. If the integer part + /// is 0 then the fractional part must be 0. This is interpreted by the device as the integer 65536. + /// + /// For small values of `int`, a fractional divisor may introduce unacceptable jitter. + pub fn clock_divisor_fixed_point(mut self, int: u16, frac: u8) -> Self { + assert!(int != 0 || frac == 0); + self.clock_divisor = (int, frac); + self + } + + /// Set the output sticky state. + /// + /// When the output is set to be sticky, the PIO hardware continuously asserts the most recent `OUT`/`SET` to the + /// pins. + pub fn out_sticky(mut self, out_sticky: bool) -> Self { + self.out_sticky = out_sticky; + self + } + + /// Set the inline `OUT` enable bit. + /// + /// When set to value, the given bit of `OUT` instruction's data is used as an auxiliary write enable. When used + /// with [`Self::out_sticky`], writes with enable 0 will deassert the latest pin write. + pub fn inline_out(mut self, inline_out: Option) -> Self { + self.inline_out = inline_out; + self + } + + /// Set the autopush state. + /// + /// When autopush is enabled, the `IN` instruction automatically pushes the data once the number of bits reaches + /// threshold set by [`Self::push_threshold`]. + pub fn autopush(mut self, autopush: bool) -> Self { + self.autopush = autopush; + self + } + + /// Set the number of bits pushed into ISR before autopush or conditional push will take place. + pub fn push_threshold(mut self, threshold: u8) -> Self { + self.push_threshold = threshold; + self + } + + /// Set the autopull state. + /// + /// When autopull is enabled, the `OUT` instruction automatically pulls the data once the number of bits reaches + /// threshold set by [`Self::pull_threshold`]. + pub fn autopull(mut self, autopull: bool) -> Self { + self.autopull = autopull; + self + } + + /// Set the number of bits pulled from out of OSR before autopull or conditional pull will take place. + pub fn pull_threshold(mut self, threshold: u8) -> Self { + self.pull_threshold = threshold; + self + } + + /// Set the ISR shift direction for `IN` instruction. + /// + /// For example `ShiftDirection::Right` means that ISR is shifted to right, i.e. data enters from left. + pub fn in_shift_direction(mut self, direction: ShiftDirection) -> Self { + self.in_shiftdir = direction; + self + } + + /// Set the OSR shift direction for `OUT` instruction. + /// + /// For example `ShiftDirection::Right` means that OSR is shifted to right, i.e. data is taken from the right side. + pub fn out_shift_direction(mut self, direction: ShiftDirection) -> Self { + self.out_shiftdir = direction; + self + } + + /// Build the config and deploy it to a StateMachine. + #[allow(clippy::type_complexity)] // The return type cannot really be simplified. + pub fn build( + self, + mut sm: UninitStateMachine<(P, SM)>, + ) -> (StateMachine<(P, SM), Stopped>, Rx<(P, SM)>, Tx<(P, SM)>) { + let offset = self.program.offset; + + // Stop the SM + sm.set_enabled(false); + + // Write all configuration bits + sm.set_clock_divisor(self.clock_divisor.0, self.clock_divisor.1); + + // Safety: Only instance owning the SM + unsafe { + sm.sm().sm_execctrl().write(|w| { + w.side_en().bit(self.program.side_set.optional()); + w.side_pindir().bit(self.program.side_set.pindirs()); + + w.jmp_pin().bits(self.jmp_pin); + + if let Some(inline_out) = self.inline_out { + w.inline_out_en().bit(true); + w.out_en_sel().bits(inline_out); + } else { + w.inline_out_en().bit(false); + } + + w.out_sticky().bit(self.out_sticky); + + w.wrap_top().bits(offset + self.program.wrap.source); + w.wrap_bottom().bits(offset + self.program.wrap.target); + + let n = match self.mov_status { + MovStatusConfig::Tx(n) => { + w.status_sel().txlevel(); + n + } + MovStatusConfig::Rx(n) => { + w.status_sel().rxlevel(); + n + } + MovStatusConfig::Irq(n) => { + w.status_sel().irq(); + n + } + }; + w.status_n().bits(n) + }); + + sm.sm().sm_shiftctrl().write(|w| { + let (fjoin_rx, fjoin_tx) = match self.fifo_join { + Buffers::RxTx => (false, false), + Buffers::OnlyTx => (false, true), + Buffers::OnlyRx => (true, false), + }; + w.fjoin_rx().bit(fjoin_rx); + w.fjoin_tx().bit(fjoin_tx); + + // TODO: Encode 32 as zero, and error on 0 + w.pull_thresh().bits(self.pull_threshold); + w.push_thresh().bits(self.push_threshold); + + w.out_shiftdir().bit(self.out_shiftdir.bit()); + w.in_shiftdir().bit(self.in_shiftdir.bit()); + + w.autopull().bit(self.autopull); + w.autopush().bit(self.autopush) + }); + + sm.sm().sm_pinctrl().write(|w| { + w.sideset_count().bits(self.program.side_set.bits()); + w.set_count().bits(self.set_count); + w.out_count().bits(self.out_count); + + w.in_base().bits(self.in_base); + w.sideset_base().bits(self.side_set_base); + w.set_base().bits(self.set_base); + w.out_base().bits(self.out_base) + }) + } + + // Restart SM and its clock + sm.restart(); + sm.reset_clock(); + + // Set starting location by forcing the state machine to execute a jmp + // to the beginning of the program we loaded in. + let instr = InstructionOperands::JMP { + condition: pio::JmpCondition::Always, + address: offset, + } + .encode(); + // Safety: Only instance owning the SM + unsafe { + sm.sm().sm_instr().write(|w| w.sm0_instr().bits(instr)); + } + + let rx = Rx { + block: sm.block, + _phantom: core::marker::PhantomData, + }; + let tx = Tx { + block: sm.block, + _phantom: core::marker::PhantomData, + }; + ( + StateMachine { + sm, + program: self.program, + _phantom: core::marker::PhantomData, + }, + rx, + tx, + ) + } +} diff --git a/rp235x-hal/src/pll.rs b/rp235x-hal/src/pll.rs new file mode 100644 index 000000000..70ec5d7ff --- /dev/null +++ b/rp235x-hal/src/pll.rs @@ -0,0 +1,335 @@ +//! Phase-Locked Loops (PLL) +//! +//! See [Section 8.6](https://rptl.io/rp2350-datasheet#section_pll) for more details + +use core::{ + convert::{Infallible, TryInto}, + marker::PhantomData, + ops::{Deref, Range, RangeInclusive}, +}; + +use fugit::{HertzU32, RateExtU32}; + +use nb::Error::WouldBlock; + +use crate::{clocks::ClocksManager, pac::RESETS, resets::SubsystemReset, typelevel::Sealed}; + +/// State of the PLL +pub trait State: Sealed {} + +/// PLL is disabled. +pub struct Disabled { + refdiv: u8, + fbdiv: u16, + post_div1: u8, + post_div2: u8, + frequency: HertzU32, +} + +/// PLL is configured, started and locking into its designated frequency. +pub struct Locking { + post_div1: u8, + post_div2: u8, + frequency: HertzU32, +} + +/// PLL is locked : it delivers a steady frequency. +pub struct Locked { + frequency: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Locked {} +impl Sealed for Locked {} +impl State for Locking {} +impl Sealed for Locking {} + +/// Trait to handle both underlying devices from the PAC (PLL_SYS & PLL_USB) +pub trait PhaseLockedLoopDevice: + Deref + SubsystemReset + Sealed +{ +} + +impl PhaseLockedLoopDevice for crate::pac::PLL_SYS {} +impl Sealed for crate::pac::PLL_SYS {} +impl PhaseLockedLoopDevice for crate::pac::PLL_USB {} +impl Sealed for crate::pac::PLL_USB {} + +/// A PLL. +pub struct PhaseLockedLoop { + device: D, + state: S, +} + +impl PhaseLockedLoop { + fn transition(self, state: To) -> PhaseLockedLoop { + PhaseLockedLoop { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> D { + self.device + } +} + +/// Error type for the PLL module. +/// +/// See [Section 8.6](https://rptl.io/rp2350-datasheet) for +/// details on constraints triggering these errors. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Proposed VCO frequency is out of range. + VcoFreqOutOfRange, + + /// Feedback Divider value is out of range. + FeedbackDivOutOfRange, + + /// Post Divider value is out of range. + PostDivOutOfRage, + + /// Reference Frequency is out of range. + RefFreqOutOfRange, + + /// Bad argument : overflows, bad conversion, ... + BadArgument, +} + +/// Parameters for a PLL. +pub struct PLLConfig { + /// Voltage Controlled Oscillator frequency. + pub vco_freq: HertzU32, + + /// Reference divider + pub refdiv: u8, + + /// Post Divider 1 + pub post_div1: u8, + + /// Post Divider 2 + pub post_div2: u8, +} + +/// Common configs for the two PLLs. Both assume the XOSC is cadenced at 12MHz ! +/// +/// See [Section 8.6.2](https://rptl.io/rp2350-datasheet) of the RP2350 datasheet. +pub mod common_configs { + use super::PLLConfig; + use fugit::HertzU32; + + /// Default, nominal configuration for PLL_SYS + pub const PLL_SYS_150MHZ: PLLConfig = PLLConfig { + vco_freq: HertzU32::MHz(1500), + refdiv: 1, + post_div1: 5, + post_div2: 2, + }; + + /// Default, nominal configuration for PLL_USB. + pub const PLL_USB_48MHZ: PLLConfig = PLLConfig { + vco_freq: HertzU32::MHz(1200), + refdiv: 1, + post_div1: 5, + post_div2: 5, + }; +} + +impl PhaseLockedLoop { + /// Instantiates a new Phase-Locked-Loop device. + pub fn new( + dev: D, + xosc_frequency: HertzU32, + config: PLLConfig, + ) -> Result, Error> { + const VCO_FREQ_RANGE: RangeInclusive = HertzU32::MHz(400)..=HertzU32::MHz(1_600); + const POSTDIV_RANGE: Range = 1..7; + const FBDIV_RANGE: Range = 16..320; + + let vco_freq = config.vco_freq; + + if !VCO_FREQ_RANGE.contains(&vco_freq) { + return Err(Error::VcoFreqOutOfRange); + } + + if !POSTDIV_RANGE.contains(&config.post_div1) || !POSTDIV_RANGE.contains(&config.post_div2) + { + return Err(Error::PostDivOutOfRage); + } + + let ref_freq_max_vco = (vco_freq.to_Hz() / 16).Hz(); + let ref_freq_range: Range = HertzU32::MHz(5)..ref_freq_max_vco; + + let ref_freq_hz: HertzU32 = xosc_frequency + .to_Hz() + .checked_div(u32::from(config.refdiv)) + .ok_or(Error::BadArgument)? + .Hz(); + + if !ref_freq_range.contains(&ref_freq_hz) { + return Err(Error::RefFreqOutOfRange); + } + + let fbdiv = vco_freq + .to_Hz() + .checked_div(ref_freq_hz.to_Hz()) + .ok_or(Error::BadArgument)?; + + let fbdiv: u16 = fbdiv.try_into().map_err(|_| Error::BadArgument)?; + + if !FBDIV_RANGE.contains(&fbdiv) { + return Err(Error::FeedbackDivOutOfRange); + } + + let refdiv = config.refdiv; + let post_div1 = config.post_div1; + let post_div2 = config.post_div2; + let frequency: HertzU32 = + (ref_freq_hz * u32::from(fbdiv)) / (u32::from(post_div1) * u32::from(post_div2)); + + Ok(PhaseLockedLoop { + state: Disabled { + refdiv, + fbdiv, + post_div1, + post_div2, + frequency, + }, + device: dev, + }) + } + + /// Configures and starts the PLL : it switches to Locking state. + pub fn initialize(self, resets: &mut crate::pac::RESETS) -> PhaseLockedLoop { + self.device.reset_bring_up(resets); + + // Turn off PLL in case it is already running + self.device.pwr().reset(); + self.device.fbdiv_int().reset(); + + self.device.cs().write(|w| unsafe { + w.refdiv().bits(self.state.refdiv); + w + }); + + self.device.fbdiv_int().write(|w| unsafe { + w.fbdiv_int().bits(self.state.fbdiv); + w + }); + + // Turn on PLL + self.device.pwr().modify(|_, w| { + w.pd().clear_bit(); + w.vcopd().clear_bit(); + w + }); + + let post_div1 = self.state.post_div1; + let post_div2 = self.state.post_div2; + let frequency = self.state.frequency; + + self.transition(Locking { + post_div1, + post_div2, + frequency, + }) + } +} + +/// A token that's given when the PLL is properly locked, so we can safely transition to the next state. +pub struct LockedPLLToken { + _private: PhantomData, +} + +impl PhaseLockedLoop { + /// Awaits locking of the PLL. + pub fn await_lock(&self) -> nb::Result, Infallible> { + if self.device.cs().read().lock().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(LockedPLLToken { + _private: PhantomData, + }) + } + + /// Exchanges a token for a Locked PLL. + pub fn get_locked(self, _token: LockedPLLToken) -> PhaseLockedLoop { + // Set up post dividers + self.device.prim().write(|w| unsafe { + w.postdiv1().bits(self.state.post_div1); + w.postdiv2().bits(self.state.post_div2); + w + }); + + // Turn on post divider + self.device.pwr().modify(|_, w| { + w.postdivpd().clear_bit(); + w + }); + + let frequency = self.state.frequency; + + self.transition(Locked { frequency }) + } +} + +impl PhaseLockedLoop { + /// Get the operating frequency for the PLL + pub fn operating_frequency(&self) -> HertzU32 { + self.state.frequency + } + + /// Shut down the PLL. The returned PLL is configured the same as it was originally. + pub fn disable(self) -> PhaseLockedLoop { + let fbdiv = self.device.fbdiv_int().read().fbdiv_int().bits(); + let refdiv = self.device.cs().read().refdiv().bits(); + let prim = self.device.prim().read(); + let frequency = self.state.frequency; + + self.device.pwr().reset(); + self.device.fbdiv_int().reset(); + + self.transition(Disabled { + refdiv, + fbdiv, + post_div1: prim.postdiv1().bits(), + post_div2: prim.postdiv2().bits(), + frequency, + }) + } +} + +/// Blocking helper method to setup the PLL without going through all the steps. +pub fn setup_pll_blocking( + dev: D, + xosc_frequency: HertzU32, + config: PLLConfig, + clocks: &mut ClocksManager, + resets: &mut RESETS, +) -> Result, Error> { + // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. + nb::block!(clocks.system_clock.reset_source_await()).unwrap(); + + nb::block!(clocks.reference_clock.reset_source_await()).unwrap(); + + start_pll_blocking( + PhaseLockedLoop::new(dev, xosc_frequency.convert(), config)?, + resets, + ) +} + +/// Blocking helper method to (re)start a PLL. +pub fn start_pll_blocking( + disabled_pll: PhaseLockedLoop, + resets: &mut RESETS, +) -> Result, Error> { + let initialized_pll = disabled_pll.initialize(resets); + + let locked_pll_token = nb::block!(initialized_pll.await_lock()).unwrap(); + + Ok(initialized_pll.get_locked(locked_pll_token)) +} diff --git a/rp235x-hal/src/powman.rs b/rp235x-hal/src/powman.rs new file mode 100644 index 000000000..220127cd4 --- /dev/null +++ b/rp235x-hal/src/powman.rs @@ -0,0 +1,662 @@ +//! POWMAN Support +//! +//! The POWMAN peripheral contains a mixture of functionality, including: +//! +//! * [x] An always-on timer (AOT) with alarm +//! * [ ] Voltage Regulator Control +//! * [ ] Brown-out detection +//! * [ ] Low-power oscillator control +//! * [ ] Using as GPIO as a time reference or wake-up signal +//! * [ ] The power-on statemachine, including last-power-on reason +//! +//! See [Section 6.5](https://rptl.io/rp2350-datasheet) of the RP2350 datasheet +//! for more details + +use crate::{ + gpio::{ + bank0::{Gpio12, Gpio14, Gpio20, Gpio22}, + DynPullType, FunctionSioInput, Pin, + }, + pac, +}; + +/// A frequency in kHz, represented as a fixed point 16.16 value +/// +/// The RP2350 needs the frequency in `(integer_kHz, frac_kHz)` where `frac_kHz` +/// goes from 0 to 65535 (and 32768 represents 0.5 kHz). This might seem odd, +/// but the AOT counts in milliseconds, so this is basically *integer number of +/// ticks per millisecond* and *fractional number of ticks per millisecond*. +/// +/// This type represents a frequency converted into that format. +/// +/// The easiest way to construct one is with [`FractionalFrequency::from_hz`]: +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FractionalFrequency(u32); + +impl FractionalFrequency { + /// Create a new Fractional Frequency from any `fugit::Rate`. + pub const fn new( + rate: fugit::Rate, + ) -> FractionalFrequency { + // fugit has a `convert` method, but it has rounding problems. + // So we do it by hand. + let rd_times_ln: u64 = 65536u64 * NOM as u64; + let ld_times_rn: u64 = DENOM as u64 * 1000u64; + let divisor: u64 = gcd::binary_u64(ld_times_rn, rd_times_ln); + let rd_times_ln: u64 = rd_times_ln / divisor; + let ld_times_rn: u64 = ld_times_rn / divisor; + let raw = rate.raw() as u64 * rd_times_ln; + let raw = (raw + (ld_times_rn / 2)) / ld_times_rn; + FractionalFrequency(raw as u32) + } + + /// Create a new Fractional Frequency from an integer number in Hertz + /// + /// ```rust + /// # use rp235x_hal::powman::{AotClockSource, FractionalFrequency}; + /// let source = AotClockSource::Xosc(FractionalFrequency::from_hz(12_000_000)); + /// ``` + pub const fn from_hz(hz: u32) -> FractionalFrequency { + let hz: fugit::HertzU32 = fugit::HertzU32::from_raw(hz); + FractionalFrequency::new(hz) + } + + /// Convert to an integer value in Hz + /// + /// Note: this involves a loss of precision + pub const fn as_int_hz(self) -> u32 { + let (i, f) = self.to_registers(); + (i as u32 * 1000) + ((f as u32 * 1000) / 65536) + } + + /// Convert to an floating point value in Hz + pub fn as_float_hz(self) -> f32 { + let (i, f) = self.to_registers(); + (i as f32 + (f as f32) / 65536.0) * 1000.0 + } + + /// Construct a fractional frequency from the raw register contents + const fn from_registers(i: u16, f: u16) -> FractionalFrequency { + let raw = (i as u32) << 16 | (f as u32); + FractionalFrequency(raw) + } + + /// Convert to the raw register values + const fn to_registers(self) -> (u16, u16) { + let raw = self.0; + ((raw >> 16) as u16, raw as u16) + } +} + +impl core::fmt::Display for FractionalFrequency { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} Hz", self.as_float_hz()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FractionalFrequency { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{=u32} Hz", self.as_int_hz()) + } +} + +/// Specify which clock source the POWMAN Always-On-Timer is clocked from. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AotClockSource { + /// On-Chip Crystal Oscillator (XOSC). + /// + /// When the chip core is powered, the tick source can be switched the + /// on-chip crystal oscillator for greater precision. + Xosc(FractionalFrequency), + /// On-Chip Low-Power Oscillator (LPOSC). + /// + /// The LPOSC frequency is not precise and may vary with voltage and + /// temperature + Lposc(FractionalFrequency), + /// External GPIO pin input to the Low-Power Oscillator (LPOSC). + /// + /// Uses the pin provided when the POWMAN driver was constructed. + GpioLpOsc(FractionalFrequency), + /// External 1 kHz GPIO pin. + /// + /// Uses the pin provided when the POWMAN driver was constructed. + Gpio1kHz, + /// External 1 Hz GPIO pin + /// + /// Uses the pin provided when the POWMAN driver was constructed. + Gpio1Hz, +} + +/// The ways in which frequency conversion can fail +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ClockSourceError { + /// Tried to construct an LPOSC frequency but it was too high + InvalidFrequency(FractionalFrequency), + /// Tried to pick an external clock source with no GPIO pin + MissingGpioPin, +} + +impl AotClockSource { + /// Create a clock source, using the XOSC running at the given frequency. + pub const fn new_xosc(freq: FractionalFrequency) -> AotClockSource { + AotClockSource::Xosc(freq) + } + + /// Create a clock source, using the LPOSC running at the given frequency. + /// + /// Gives an error if the frequency is above 256 kHz. + pub const fn new_lposc(freq: FractionalFrequency) -> Result { + if freq.0 >= 0x0100_0000 { + // LPOSC only goes up to 256 kHz + return Err(ClockSourceError::InvalidFrequency(freq)); + } + Ok(AotClockSource::Lposc(freq)) + } +} + +impl core::fmt::Display for AotClockSource { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + AotClockSource::Xosc(frac) => { + write!(f, "Crystal @ {}", frac) + } + AotClockSource::Lposc(frac) => { + write!(f, "On-Chip LowPower @ {}", frac) + } + AotClockSource::GpioLpOsc(frac) => { + write!(f, "GPIO LowPower @ {}", frac) + } + AotClockSource::Gpio1kHz => write!(f, "GPIO 1kHz"), + AotClockSource::Gpio1Hz => write!(f, "GPIO 1 Hz"), + } + } +} + +/// A bit in the POWMAN.TIMER register. +/// +/// We can't use the svd2rust API because we need to also set a key. +#[repr(u16)] +enum TimerBit { + Run = 1 << 1, + Clear = 1 << 2, + // 3 is reserved + AlarmEnable = 1 << 4, + // TIMER_POWERUP_ON_ALARM = 1 << 5, + AlarmRinging = 1 << 6, + // 7 is reserved + UseLposc = 1 << 8, + UseXosc = 1 << 9, + Use1khz = 1 << 10, + // 11:12 is reserved + Use1hz = 1 << 13, +} + +/// Describes a pin we can use as an AOT clock input +pub enum ClockPin { + /// Using GPIO 12 as clock input + Pin12(Pin), + /// Using GPIO 14 as clock input + Pin14(Pin), + /// Using GPIO 20 as clock input + Pin20(Pin), + /// Using GPIO 22 as clock input + Pin22(Pin), +} + +/// The Power Management device +pub struct Powman { + device: pac::POWMAN, + aot_clock_pin: Option, +} + +impl Powman { + const KEY_VALUE: u32 = 0x5AFE_0000; + const EXT_TIME_REF_DRIVE_LPCLK: u16 = 1 << 4; + const INT_TIMER: u32 = 1 << 1; + + /// Create a new AOT from the from the underlying POWMAN device. + pub fn new(device: pac::POWMAN, aot_clock_pin: Option) -> Powman { + // we set this once, and can ignore it throughout the rest of the driver + let ext_time_ref = device.ext_time_ref().as_ptr(); + match aot_clock_pin { + None => {} + Some(ClockPin::Pin12(_)) => { + unsafe { Self::powman_write(ext_time_ref, 0) }; + } + Some(ClockPin::Pin14(_)) => { + unsafe { Self::powman_write(ext_time_ref, 2) }; + } + Some(ClockPin::Pin20(_)) => { + unsafe { Self::powman_write(ext_time_ref, 1) }; + } + Some(ClockPin::Pin22(_)) => { + unsafe { Self::powman_write(ext_time_ref, 3) }; + } + } + + Powman { + device, + aot_clock_pin, + } + } + + /// Releases the underlying device. + pub fn free(self) -> (pac::POWMAN, Option) { + (self.device, self.aot_clock_pin) + } + + /// Start the Always-On-Timer + pub fn aot_start(&mut self) { + self.timer_set_bit(TimerBit::Run); + } + + /// Configure the clock source for the Always-On-Timer + /// + /// If the timer is running, it will be stopped and restarted. + pub fn aot_set_clock(&mut self, source: AotClockSource) -> Result<(), ClockSourceError> { + // only allowed to change the clock speed when the timer is stopped + let was_running = self.aot_is_running(); + if was_running { + self.aot_stop(); + } + match source { + AotClockSource::Xosc(freq) => { + let (int_portion, frac_portion) = freq.to_registers(); + unsafe { + Self::powman_write(self.device.xosc_freq_khz_int().as_ptr(), int_portion); + Self::powman_write(self.device.xosc_freq_khz_frac().as_ptr(), frac_portion); + self.timer_set_bit(TimerBit::UseXosc); + } + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_xosc() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::Lposc(freq) => { + let (int_portion, frac_portion) = freq.to_registers(); + unsafe { + Self::powman_write(self.device.lposc_freq_khz_int().as_ptr(), int_portion); + Self::powman_write(self.device.lposc_freq_khz_frac().as_ptr(), frac_portion); + self.timer_set_bit(TimerBit::UseLposc); + Self::powman_clear_bits( + self.device.ext_time_ref().as_ptr(), + Self::EXT_TIME_REF_DRIVE_LPCLK, + ); + } + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_lposc() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::GpioLpOsc(freq) => { + if self.aot_clock_pin.is_none() { + return Err(ClockSourceError::MissingGpioPin); + } + let (int_portion, frac_portion) = freq.to_registers(); + unsafe { + Self::powman_write(self.device.lposc_freq_khz_int().as_ptr(), int_portion); + Self::powman_write(self.device.lposc_freq_khz_frac().as_ptr(), frac_portion); + self.timer_set_bit(TimerBit::UseLposc); + // Use the selected GPIO to drive the 32kHz low power clock, in place of LPOSC. + Self::powman_set_bits( + self.device.ext_time_ref().as_ptr(), + Self::EXT_TIME_REF_DRIVE_LPCLK, + ); + } + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_gpio_1khz() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::Gpio1kHz => { + if self.aot_clock_pin.is_none() { + return Err(ClockSourceError::MissingGpioPin); + } + self.timer_set_bit(TimerBit::Use1khz); + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_gpio_1khz() != true { + core::hint::spin_loop(); + } + } + } + AotClockSource::Gpio1Hz => { + if self.aot_clock_pin.is_none() { + return Err(ClockSourceError::MissingGpioPin); + } + self.timer_set_bit(TimerBit::Use1hz); + if was_running { + self.aot_start(); + // wait for change + while self.device.timer().read().using_gpio_1hz() != true { + core::hint::spin_loop(); + } + } + } + } + Ok(()) + } + + /// Get the current Always-On-Timer clock source + /// + /// You may get `None` if the AOT is not running. + pub fn aot_get_clock(&mut self) -> Option { + let status = self.device.timer().read(); + if status.using_gpio_1hz().bit_is_set() { + Some(AotClockSource::Gpio1Hz) + } else if status.using_gpio_1khz().bit_is_set() { + Some(AotClockSource::Gpio1kHz) + } else if status.using_lposc().bit_is_set() { + let int_portion = self.device.lposc_freq_khz_int().read().bits() as u16; + let frac_portion = self.device.lposc_freq_khz_frac().read().bits() as u16; + Some(AotClockSource::Lposc(FractionalFrequency::from_registers( + int_portion, + frac_portion, + ))) + } else if status.using_xosc().bit_is_set() { + let int_portion = self.device.xosc_freq_khz_int().read().bits() as u16; + let frac_portion = self.device.xosc_freq_khz_frac().read().bits() as u16; + Some(AotClockSource::Xosc(FractionalFrequency::from_registers( + int_portion, + frac_portion, + ))) + } else { + None + } + } + + /// Stop the Always-On-Timer + pub fn aot_stop(&self) { + self.timer_clear_bit(TimerBit::Run); + } + + /// Is the Always-On-Timer running + pub fn aot_is_running(&self) -> bool { + let status = self.device.timer().read(); + status.run().bit_is_set() + } + + /// Get the time from the Always-On-Timer + pub fn aot_get_time(&self) -> u64 { + // keep reading until we do NOT cross a 32-bit boundary + loop { + let upper1 = self.device.read_time_upper().read().bits(); + let lower = self.device.read_time_lower().read().bits(); + let upper2 = self.device.read_time_upper().read().bits(); + if upper1 == upper2 { + // we did not cross a boundary + return u64::from(upper1) << 32 | u64::from(lower); + } + } + } + + /// Get the alarm time from the Always-On-Timer + pub fn aot_get_alarm(&self) -> u64 { + let alarm0 = self + .device + .alarm_time_15to0() + .read() + .alarm_time_15to0() + .bits() as u64; + let alarm1 = self + .device + .alarm_time_31to16() + .read() + .alarm_time_31to16() + .bits() as u64; + let alarm2 = self + .device + .alarm_time_47to32() + .read() + .alarm_time_47to32() + .bits() as u64; + let alarm3 = self + .device + .alarm_time_63to48() + .read() + .alarm_time_63to48() + .bits() as u64; + alarm3 << 48 | alarm2 << 32 | alarm1 << 16 | alarm0 + } + + /// Clear the Always-On-Timer + /// + /// If the timer is running, it will NOT be stopped. + pub fn aot_clear(&self) { + self.timer_set_bit(TimerBit::Clear); + } + + /// Enable the alarm + pub fn aot_alarm_enable(&self) { + self.timer_set_bit(TimerBit::AlarmEnable); + } + + /// Disable the alarm + /// + /// Note: this is not the same as clearing a ringing alarm. This just makes + /// sure it won't fire in the future. + pub fn aot_alarm_disable(&self) { + self.timer_clear_bit(TimerBit::AlarmEnable); + } + + /// Is the alarm currently ringing? + /// + /// In other words, has it fired since we last cleared it? + pub fn aot_alarm_ringing(&self) -> bool { + self.device.timer().read().alarm().bit_is_set() + } + + /// Clear a ringing alarm + /// + /// Also disables the alarm, otherwise the alarm is likely to go off again + /// right away. + pub fn aot_alarm_clear(&self) { + self.aot_alarm_disable(); + // Early datasheets said write a 1 bit to clear the ringing alarm, but + // they're lying. You have to write a zero. + self.timer_clear_bit(TimerBit::AlarmRinging); + } + + /// Enable the AOT Alarm interrupt + pub fn aot_alarm_interrupt_enable(&self) { + // Doesn't need a key to write + let inte = self.device.inte().as_ptr(); + unsafe { + crate::atomic_register_access::write_bitmask_set(inte, Self::INT_TIMER); + } + } + + /// Disable the AOT Alarm interrupt + pub fn aot_alarm_interrupt_disable(&self) { + // Doesn't need a key to write + let inte = self.device.inte().as_ptr(); + unsafe { + crate::atomic_register_access::write_bitmask_clear(inte, Self::INT_TIMER); + } + } + + /// Disable the AOT Alarm interrupt, from a free function + pub fn static_aot_alarm_interrupt_disable() { + unsafe { + let ptr = pac::POWMAN::PTR as *mut u32; + let inte = ptr.offset(0xe4 >> 2); + crate::atomic_register_access::write_bitmask_clear(inte, Self::INT_TIMER); + } + } + + /// Has the AOT Alarm interrupt fired? + pub fn aot_alarm_interrupt_status(&self) -> bool { + self.device.ints().read().timer().bit_is_set() + } + + /// Set the time in the Always-On-Timer + /// + /// If the timer is running, it will be stopped and restarted. + pub fn aot_set_time(&mut self, time: u64) { + // only allowed to change the time when the timer is stopped + let was_running = self.aot_is_running(); + if was_running { + self.aot_stop(); + } + self.device.set_time_15to0().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_15to0().bits(time as u16); + } + w + }); + self.device.set_time_31to16().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_31to16().bits((time >> 16) as u16); + } + w + }); + self.device.set_time_47to32().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_47to32().bits((time >> 32) as u16); + } + w + }); + self.device.set_time_63to48().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.set_time_63to48().bits((time >> 48) as u16); + } + w + }); + if was_running { + self.aot_start(); + } + } + + /// Set the alarm in the Always-On-Timer and start it. + pub fn aot_set_alarm(&mut self, time: u64) { + self.aot_alarm_disable(); + self.device.alarm_time_15to0().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_15to0().bits(time as u16); + } + w + }); + self.device.alarm_time_31to16().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_31to16().bits((time >> 16) as u16); + } + w + }); + self.device.alarm_time_47to32().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_47to32().bits((time >> 32) as u16); + } + w + }); + self.device.alarm_time_63to48().modify(|_r, w| { + unsafe { + w.bits(Self::KEY_VALUE); + w.alarm_time_63to48().bits((time >> 48) as u16); + } + w + }); + } + + /// Clear the specified bits in the TIMER register. + /// + /// For every `1` bit in `bitmask`, that bit is set to `0` in `POWMAN.TIMER`. + fn timer_clear_bit(&self, bit: TimerBit) { + let reg = self.device.timer().as_ptr(); + // # Safety + // + // svd2rust generated the address for us, so we know it's right + // + // This is an atomic operation, so &self is fine + unsafe { + Self::powman_clear_bits(reg, bit as u16); + } + } + + /// Set the specified bits in the TIMER register. + /// + /// For every `1` bit in `bitmask`, that bit is set to `1` in `POWMAN.TIMER`. + fn timer_set_bit(&self, bit: TimerBit) { + let reg = self.device.timer().as_ptr(); + // # Safety + // + // svd2rust generated the address for us, so we know it's right + // + // This is an atomic operation, so &self is fine + unsafe { + Self::powman_set_bits(reg, bit as u16); + } + } + + /// Write to a POWMAN protected register, using the key + unsafe fn powman_write(addr: *mut u32, bits: u16) { + let bits_with_key: u32 = Self::KEY_VALUE | u32::from(bits); + addr.write_volatile(bits_with_key); + } + + /// Set bits in a POWMAN protected register, using the key + unsafe fn powman_set_bits(addr: *mut u32, bits: u16) { + let bits_with_key: u32 = Self::KEY_VALUE | u32::from(bits); + crate::atomic_register_access::write_bitmask_set(addr, bits_with_key); + } + + /// Clear bits in a POWMAN protected register, using the key + unsafe fn powman_clear_bits(addr: *mut u32, bits: u16) { + let bits_with_key: u32 = Self::KEY_VALUE | u32::from(bits); + crate::atomic_register_access::write_bitmask_clear(addr, bits_with_key); + } +} + +#[cfg(test)] +mod tests { + use crate::fugit::RateExtU32; + + use super::*; + + #[test] + fn test_split_join_12mhz() { + let clock_speed: fugit::HertzU32 = 12_000_000.Hz(); + let freq = FractionalFrequency::new(clock_speed); + // As given in the datasheet as the default values for 12 MHz XOSC + assert_eq!(freq.to_registers(), (0x2ee0, 0x0000)); + assert_eq!(freq.as_int_hz(), 12_000_000); + assert_eq!(freq.as_float_hz(), 12_000_000.0); + let freq2 = FractionalFrequency::from_registers(0x2ee0, 0x0000); + assert_eq!(freq, freq2); + let freq3 = FractionalFrequency::from_hz(12_000_000); + assert_eq!(freq, freq3); + } + + #[test] + fn test_split_32768() { + let clock_speed: fugit::HertzU32 = 32768.Hz(); + let freq = FractionalFrequency::new(clock_speed); + // As given in the datasheet as the default values for 32.768 kHz LPOSC + assert_eq!(freq.to_registers(), (0x20, 0xc49c)); + assert_eq!(freq.as_int_hz(), 32768); + let delta = freq.as_float_hz() - 32768.0; + assert!(delta < 0.01, "{} != 32768.0", freq.as_float_hz()); + let freq2 = FractionalFrequency::from_registers(0x20, 0xc49c); + assert_eq!(freq, freq2); + let freq3 = FractionalFrequency::from_hz(32768); + assert_eq!(freq, freq3); + } +} diff --git a/rp235x-hal/src/prelude.rs b/rp235x-hal/src/prelude.rs new file mode 100644 index 000000000..447c72bea --- /dev/null +++ b/rp235x-hal/src/prelude.rs @@ -0,0 +1,3 @@ +//! Prelude +pub use crate::clocks::Clock as _rphal_clocks_Clock; +pub use crate::pio::PIOExt as _rphal_pio_PIOExt; diff --git a/rp235x-hal/src/pwm/dyn_slice.rs b/rp235x-hal/src/pwm/dyn_slice.rs new file mode 100644 index 000000000..5cf144eef --- /dev/null +++ b/rp235x-hal/src/pwm/dyn_slice.rs @@ -0,0 +1,30 @@ +//! Semi-internal enums mostly used in typelevel magic + +/// Value-level `struct` representing slice IDs +#[derive(PartialEq, Eq, Clone, Copy)] +pub struct DynSliceId { + /// Slice id + pub num: u8, +} + +/// Slice modes +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynSliceMode { + /// Count continuously whenever the slice is enabled + FreeRunning, + /// Count continuously when a high level is detected on the B pin + InputHighRunning, + /// Count once with each rising edge detected on the B pin + CountRisingEdge, + /// Count once with each falling edge detected on the B pin + CountFallingEdge, +} + +/// Channel ids +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynChannelId { + /// Channel A + A, + /// Channel B + B, +} diff --git a/rp235x-hal/src/pwm/mod.rs b/rp235x-hal/src/pwm/mod.rs new file mode 100644 index 000000000..5a9dd7118 --- /dev/null +++ b/rp235x-hal/src/pwm/mod.rs @@ -0,0 +1,1082 @@ +//! Pulse Width Modulation (PWM) +//! +//! First you must create a Slices struct which contains all the pwm slices. +//! +//! ```no_run +//! use rp235x_hal::{ +//! prelude::*, +//! pwm::{InputHighRunning, Slices}, +//! }; +//! +//! let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! +//! // Init PWMs +//! let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! +//! // Configure PWM4 +//! let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); +//! pwm.enable(); +//! +//! // Set to run when b channel is high +//! let pwm = pwm.into_mode::(); +//! ``` +//! +//! Once you have the PWM slice struct, you can add individual pins: +//! +//! ```no_run +//! # use rp235x_hal::{prelude::*, gpio::Pins, Sio, pwm::{InputHighRunning, Slices}}; +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4.into_mode::(); +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! # +//! # let sio = Sio::new(pac.SIO); +//! # let pins = Pins::new( +//! # pac.IO_BANK0, +//! # pac.PADS_BANK0, +//! # sio.gpio_bank0, +//! # &mut pac.RESETS, +//! # ); +//! # +//! use embedded_hal::pwm::SetDutyCycle; +//! +//! // Use B channel (which inputs from GPIO 25) +//! let mut channel_b = pwm.channel_b; +//! let channel_pin_b = channel_b.input_from(pins.gpio25); +//! +//! // Use A channel (which outputs to GPIO 24) +//! let mut channel_a = pwm.channel_a; +//! let channel_pin_a = channel_a.output_to(pins.gpio24); +//! +//! // Set duty cycle +//! channel_a.set_duty_cycle(0x00ff); +//! let max_duty_cycle = channel_a.max_duty_cycle(); +//! channel_a.set_inverted(); // Invert the output +//! channel_a.clr_inverted(); // Don't invert the output +//! ``` +//! +//! The following configuration options are also available: +//! +//! ```no_run +//! # use rp235x_hal::{prelude::*, pwm::Slices}; +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! # let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +//! # let mut pwm = pwm_slices.pwm4; +//! pwm.set_ph_correct(); // Run in phase correct mode +//! pwm.clr_ph_correct(); // Don't run in phase correct mode +//! +//! pwm.set_div_int(1u8); // To set integer part of clock divider +//! pwm.set_div_frac(0u8); // To set fractional part of clock divider +//! +//! pwm.get_top(); // To get the TOP register +//! pwm.set_top(u16::MAX); // To set the TOP register +//! ``` +//! +//! default_config() sets ph_correct to false, the clock divider to 1, does not invert the output, sets top to 65535, and resets the counter. +//! min_config() leaves those registers in the state they were before it was called (Careful, this can lead to unexpected behavior) +//! It's recommended to only call min_config() after calling default_config() on a pin that shares a PWM block. + +use core::convert::Infallible; +use core::marker::PhantomData; + +use embedded_dma::Word; +use embedded_hal::pwm::{ErrorType, SetDutyCycle}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + dma::{EndlessWriteTarget, WriteTarget}, + gpio::{bank0::*, AnyPin, FunctionPwm, Pin, ValidFunction}, + pac::{self, dma::ch::ch_al1_ctrl::TREQ_SEL_A, PWM}, + resets::SubsystemReset, + typelevel::{Is, Sealed}, +}; + +pub mod dyn_slice; +pub use dyn_slice::*; + +mod reg; + +use reg::RegisterInterface; + +/// Used to pin traits to a specific channel (A or B) +pub trait ChannelId: Sealed { + /// Corresponding [`DynChannelId`] + const DYN: DynChannelId; +} + +/// Channel A +/// +/// These are attached to the even gpio pins and can only do PWM output +pub enum A {} + +/// Channel B +/// +/// These are attached to the odd gpio pins and can do PWM output and edge counting for input +pub enum B {} + +impl ChannelId for A { + const DYN: DynChannelId = DynChannelId::A; +} +impl ChannelId for B { + const DYN: DynChannelId = DynChannelId::B; +} +impl Sealed for A {} +impl Sealed for B {} + +/// Counter is free-running, and will count continuously whenever the slice is enabled +pub struct FreeRunning; +/// Count continuously when a high level is detected on the B pin +pub struct InputHighRunning; +/// Count once with each rising edge detected on the B pin +pub struct CountRisingEdge; +/// Count once with each falling edge detected on the B pin +pub struct CountFallingEdge; + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceMode: Sealed + SliceMode {} + +/// Type-level marker for tracking which slice modes are valid for which slices +pub trait ValidSliceInputMode: Sealed + ValidSliceMode {} + +/// Mode for slice +pub trait SliceMode: Sealed + Sized { + /// Corresponding [`DynSliceMode`] + const DYN: DynSliceMode; +} + +impl Sealed for FreeRunning {} +impl SliceMode for FreeRunning { + const DYN: DynSliceMode = DynSliceMode::FreeRunning; +} +impl Sealed for InputHighRunning {} +impl SliceMode for InputHighRunning { + const DYN: DynSliceMode = DynSliceMode::InputHighRunning; +} +impl Sealed for CountRisingEdge {} +impl SliceMode for CountRisingEdge { + const DYN: DynSliceMode = DynSliceMode::CountRisingEdge; +} +impl Sealed for CountFallingEdge {} +impl SliceMode for CountFallingEdge { + const DYN: DynSliceMode = DynSliceMode::CountFallingEdge; +} + +impl ValidSliceMode for FreeRunning {} +impl ValidSliceMode for InputHighRunning {} +impl ValidSliceMode for CountRisingEdge {} +impl ValidSliceMode for CountFallingEdge {} +impl ValidSliceInputMode for InputHighRunning {} +impl ValidSliceInputMode for CountRisingEdge {} +impl ValidSliceInputMode for CountFallingEdge {} + +//============================================================================== +// Slice IDs +//============================================================================== + +/// Type-level `enum` for slice IDs +pub trait SliceId: Sealed { + /// Corresponding [`DynSliceId`] + const DYN: DynSliceId; + /// [`SliceMode`] at reset + type Reset; + + /// Get DREQ number of PWM wrap. + const WRAP_DREQ: u8 = TREQ_SEL_A::PWM_WRAP0 as u8 + Self::DYN.num; +} + +macro_rules! slice_id { + ($Id:ident, $NUM:literal, $reset : ident) => { + $crate::paste::paste! { + #[doc = "Slice ID representing slice " $NUM] + pub enum $Id {} + impl Sealed for $Id {} + impl SliceId for $Id { + type Reset = $reset; + const DYN: DynSliceId = DynSliceId { num: $NUM }; + } + } + }; +} + +//============================================================================== +// AnySlice +//============================================================================== + +/// Type class for [`Slice`] types +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Slice`] types. See the `AnyKind` documentation for more details on the +/// pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +/// [type class]: crate::typelevel#type-classes +pub trait AnySlice +where + Self: Sealed, + Self: Is>, + ::Mode: ValidSliceMode<::Id>, +{ + /// [`SliceId`] of the corresponding [`Slice`] + type Id: SliceId; + /// [`SliceMode`] of the corresponding [`Slice`] + type Mode: SliceMode; +} + +impl Sealed for Slice +where + S: SliceId, + M: ValidSliceMode, +{ +} + +impl AnySlice for Slice +where + S: SliceId, + M: ValidSliceMode, +{ + type Id = S; + type Mode = M; +} + +/// Type alias to recover the specific [`Slice`] type from an implementation of +/// [`AnySlice`] +/// +/// See the [`AnyKind`] documentation for more details on the pattern. +/// +/// [`AnyKind`]: crate::typelevel#anykind-trait-pattern +type SpecificSlice = Slice<::Id, ::Mode>; + +//============================================================================== +// Registers +//============================================================================== + +/// Provide a safe register interface for [`Slice`]s +/// +/// This `struct` takes ownership of a [`SliceId`] and provides an API to +/// access the corresponding registers. +struct Registers { + id: PhantomData, +} + +// [`Registers`] takes ownership of the [`SliceId`], and [`Slice`] guarantees that +// each slice is a singleton, so this implementation is safe. +unsafe impl RegisterInterface for Registers { + #[inline] + fn id(&self) -> DynSliceId { + I::DYN + } +} + +impl Registers { + /// Create a new instance of [`Registers`] + /// + /// # Safety + /// + /// Users must never create two simultaneous instances of this `struct` with + /// the same [`SliceId`] + #[inline] + unsafe fn new() -> Self { + Registers { id: PhantomData } + } + + /// Provide a type-level equivalent for the + /// [`RegisterInterface::change_mode`] method. + #[inline] + fn change_mode>(&mut self) { + RegisterInterface::do_change_mode(self, M::DYN); + } +} + +/// Pwm slice +pub struct Slice +where + I: SliceId, + M: ValidSliceMode, +{ + regs: Registers, + mode: PhantomData, + /// Channel A (always output) + pub channel_a: Channel, + /// Channel B (input or output) + pub channel_b: Channel, +} + +impl Slice +where + I: SliceId, + M: ValidSliceMode, +{ + /// Create a new [`Slice`] + /// + /// # Safety + /// + /// Each [`Slice`] must be a singleton. For a given [`SliceId`], there must be + /// at most one corresponding [`Slice`] in existence at any given time. + /// Violating this requirement is `unsafe`. + #[inline] + pub(crate) unsafe fn new() -> Slice { + Slice { + regs: Registers::new(), + mode: PhantomData, + channel_a: Channel::new(0), + channel_b: Channel::new(0), + } + } + + /// Convert the slice to the requested [`SliceMode`] + #[inline] + pub fn into_mode>(mut self) -> Slice { + if N::DYN != M::DYN { + self.regs.change_mode::(); + } + // Safe because we drop the existing slice + unsafe { Slice::new() } + } + + /// Set a default config for the slice + pub fn default_config(&mut self) { + self.regs.write_ph_correct(false); + self.regs.write_div_int(1); // No divisor + self.regs.write_div_frac(0); // No divisor + self.regs.write_inv_a(false); //Don't invert the channel + self.regs.write_inv_b(false); //Don't invert the channel + self.regs.write_top(0xfffe); // Wrap at 0xfffe, so cc = 0xffff can indicate 100% duty cycle + self.regs.write_ctr(0x0000); //Reset the counter + self.regs.write_cc_a(0); //Default duty cycle of 0% + self.regs.write_cc_b(0); //Default duty cycle of 0% + } + + /// Advance the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn advance_phase(&mut self) { + self.regs.advance_phase() + } + + /// Retard the phase with one count + /// + /// Counter must be running at less than full speed (div_int + div_frac / 16 > 1) + #[inline] + pub fn retard_phase(&mut self) { + self.regs.retard_phase() + } + + /// Enable phase correct mode + #[inline] + pub fn set_ph_correct(&mut self) { + self.regs.write_ph_correct(true) + } + + /// Disables phase correct mode + #[inline] + pub fn clr_ph_correct(&mut self) { + self.regs.write_ph_correct(false) + } + + /// Enable slice + #[inline] + pub fn enable(&mut self) { + self.regs.write_enable(true); + } + + /// Disable slice + #[inline] + pub fn disable(&mut self) { + self.regs.write_enable(false) + } + + /// Sets the integer part of the clock divider + #[inline] + pub fn set_div_int(&mut self, value: u8) { + self.regs.write_div_int(value) + } + + /// Sets the fractional part of the clock divider + #[inline] + pub fn set_div_frac(&mut self, value: u8) { + self.regs.write_div_frac(value) + } + + /// Get the counter register value + #[inline] + pub fn get_counter(&self) -> u16 { + self.regs.read_ctr() + } + + /// Set the counter register value + #[inline] + pub fn set_counter(&mut self, value: u16) { + self.regs.write_ctr(value) + } + + /// Get the top register value + #[inline] + pub fn get_top(&self) -> u16 { + self.regs.read_top() + } + + /// Sets the top register value + /// + /// Don't set this to 0xffff if you need true 100% duty cycle: + /// + /// The CC register, which is used to configure the duty cycle, + /// must be set to TOP + 1 for 100% duty cycle, but also is a + /// 16 bit register. + /// + /// In case you do set TOP to 0xffff, [`SetDutyCycle::set_duty_cycle`] + /// will slightly violate the trait's documentation, as + /// `SetDutyCycle::set_duty_cycle_fully_on` and other calls that + /// should lead to 100% duty cycle will only reach a duty cycle of + /// about 99.998%. + #[inline] + pub fn set_top(&mut self, value: u16) { + self.regs.write_top(value) + } + + /// Create the interrupt bitmask corresponding to this slice + #[inline] + fn bitmask(&self) -> u32 { + 1 << I::DYN.num + } + + /// Enable the PWM_IRQ_WRAP interrupt when this slice overflows. + #[inline] + pub fn enable_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_inte().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Disable the PWM_IRQ_WRAP interrupt for this slice. + #[inline] + pub fn disable_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_inte().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + }; + } + + /// Enable the second PWM_IRQ_WRAP interrupt when this slice overflows. + #[inline] + pub fn enable_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_inte().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Disable the second PWM_IRQ_WRAP interrupt for this slice. + #[inline] + pub fn disable_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_inte().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + }; + } + + /// Did this slice trigger an overflow interrupt? + /// + /// This reports the raw interrupt flag, without considering masking or + /// forcing bits. It may return true even if the interrupt is disabled + /// or false even if the interrupt is forced. + #[inline] + pub fn has_overflown(&self) -> bool { + let mask = self.bitmask(); + unsafe { (*pac::PWM::ptr()).intr().read().bits() & mask == mask } + } + + /// Mark the interrupt handled for this slice. + #[inline] + pub fn clear_interrupt(&mut self) { + unsafe { (*pac::PWM::ptr()).intr().write(|w| w.bits(self.bitmask())) }; + } + + /// Force the interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn force_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_intf().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Clear force interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn clear_force_interrupt0(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq0_intf().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + } + } + + /// Force the second interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn force_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_intf().as_ptr(); + write_bitmask_set(reg, self.bitmask()); + } + } + + /// Clear force second interrupt. This bit is not cleared by hardware and must be manually cleared to + /// stop the interrupt from continuing to be asserted. + #[inline] + pub fn clear_force_interrupt1(&mut self) { + unsafe { + let pwm = &(*pac::PWM::ptr()); + let reg = pwm.irq1_intf().as_ptr(); + write_bitmask_clear(reg, self.bitmask()); + } + } +} + +macro_rules! pwm { + ($PWMX:ident, [ + $($SXi:ident: ($slice:literal, [$($pin_a:ident, $pin_b:ident),*], $i:expr)),+ + ]) => { + $( + slice_id!($SXi, $slice, FreeRunning); + + $( + impl ValidPwmOutputPin<$SXi, A> for $pin_a {} + impl ValidPwmOutputPin<$SXi, B> for $pin_b {} + impl ValidPwmInputPin<$SXi> for $pin_b {} + )* + )+ + + $crate::paste::paste!{ + + /// Collection of all the individual [`Slices`]s + pub struct Slices { + _pwm: $PWMX, + $( + #[doc = "Slice " $SXi] + pub [<$SXi:lower>] : Slice<$SXi,<$SXi as SliceId>::Reset>, + )+ + } + + impl Slices { + /// Take ownership of the PAC peripheral and split it into discrete [`Slice`]s + pub fn new(pwm: $PWMX, reset : &mut crate::pac::RESETS) -> Self { + pwm.reset_bring_up(reset); + unsafe { + Self { + _pwm: pwm, + $( + [<$SXi:lower>]: Slice::new(), + )+ + } + } + } + } + } + } +} + +pwm! { + PWM, [ + Pwm0: (0, [Gpio0, Gpio1, Gpio16, Gpio17], 0), + Pwm1: (1, [Gpio2, Gpio3, Gpio18, Gpio19], 1), + Pwm2: (2, [Gpio4, Gpio5, Gpio20, Gpio21], 2), + Pwm3: (3, [Gpio6, Gpio7, Gpio22, Gpio23], 3), + Pwm4: (4, [Gpio8, Gpio9, Gpio24, Gpio25], 4), + Pwm5: (5, [Gpio10, Gpio11, Gpio26, Gpio27], 5), + Pwm6: (6, [Gpio12, Gpio13, Gpio28, Gpio29], 6), + Pwm7: (7, [Gpio14, Gpio15], 7) + ] +} + +/// Marker trait for valid input pins (Channel B only) +pub trait ValidPwmInputPin: ValidFunction + Sealed {} +/// Marker trait for valid output pins +pub trait ValidPwmOutputPin: ValidFunction + Sealed {} + +impl Slices { + /// Free the pwm registers from the pwm hal struct while consuming it. + pub fn free(self) -> PWM { + self._pwm + } + + /// Enable multiple slices at the same time to make their counters sync up. + /// + /// You still need to call `slice` to get an actual slice + pub fn enable_simultaneous(&mut self, bits: u8) { + // Enable multiple slices at the same time + unsafe { + let reg = self._pwm.en().as_ptr(); + write_bitmask_set(reg, bits as u32); + } + } + + // /// Get pwm slice based on gpio pin + // pub fn borrow_mut_from_pin< + // S: SliceId, + // C: ChannelId, + // G: PinId + BankPinId + ValidPwmOutputPin, + // PM: PinMode + ValidPinMode, + // SM: ValidSliceMode, + // >(&mut self, _: &Pin) -> &mut Slice{ + // match S::DYN { + // DynSliceId{num} if num == 0 => &mut self.pwm0, + // DynSliceId{num} if num == 1 => &mut self.pwm1, + // DynSliceId{num} if num == 2 => &mut self.pwm2, + // DynSliceId{num} if num == 3 => &mut self.pwm3, + // DynSliceId{num} if num == 4 => &mut self.pwm4, + // DynSliceId{num} if num == 5 => &mut self.pwm5, + // DynSliceId{num} if num == 6 => &mut self.pwm6, + // DynSliceId{num} if num == 7 => &mut self.pwm7, + // _ => unreachable!() + // } + // } +} + +/// A Channel from the Pwm subsystem. +/// +/// Its attached to one of the eight slices and can be an A or B side channel +pub struct Channel { + regs: Registers, + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle: u16, + enabled: bool, +} + +impl Channel { + pub(super) unsafe fn new(duty_cycle: u16) -> Self { + Channel { + regs: Registers::new(), + slice_mode: PhantomData, + channel_id: PhantomData, + duty_cycle, // stores the duty cycle while the channel is disabled + enabled: true, + } + } +} + +impl Sealed for Channel {} + +impl embedded_hal_0_2::PwmPin for Channel { + type Duty = u16; + + fn disable(&mut self) { + self.set_enabled(false); + } + + fn enable(&mut self) { + self.set_enabled(true); + } + + fn get_duty(&self) -> Self::Duty { + if self.enabled { + self.regs.read_cc_a() + } else { + self.duty_cycle + } + } + + fn get_max_duty(&self) -> Self::Duty { + SetDutyCycle::max_duty_cycle(self) + } + + fn set_duty(&mut self, duty: Self::Duty) { + let _ = SetDutyCycle::set_duty_cycle(self, duty); + } +} + +impl embedded_hal_0_2::PwmPin for Channel { + type Duty = u16; + + fn disable(&mut self) { + self.set_enabled(false); + } + + fn enable(&mut self) { + self.set_enabled(true); + } + + fn get_duty(&self) -> Self::Duty { + if self.enabled { + self.regs.read_cc_b() + } else { + self.duty_cycle + } + } + + fn get_max_duty(&self) -> Self::Duty { + SetDutyCycle::max_duty_cycle(self) + } + + fn set_duty(&mut self, duty: Self::Duty) { + let _ = SetDutyCycle::set_duty_cycle(self, duty); + } +} + +impl ErrorType for Channel { + type Error = Infallible; +} + +impl SetDutyCycle for Channel { + fn max_duty_cycle(&self) -> u16 { + self.regs.read_top().saturating_add(1) + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.duty_cycle = duty; + if self.enabled { + self.regs.write_cc_a(duty) + } + Ok(()) + } +} + +impl ErrorType for Channel { + type Error = Infallible; +} + +impl SetDutyCycle for Channel { + fn max_duty_cycle(&self) -> u16 { + self.regs.read_top().saturating_add(1) + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.duty_cycle = duty; + if self.enabled { + self.regs.write_cc_b(duty) + } + Ok(()) + } +} + +impl Channel { + /// Enable or disable the PWM channel + pub fn set_enabled(&mut self, enable: bool) { + if enable && !self.enabled { + // Restore the duty cycle. + self.regs.write_cc_a(self.duty_cycle); + self.enabled = true; + } else if !enable && self.enabled { + // We can't disable it without disturbing the other channel so this + // just sets the duty cycle to zero. + self.duty_cycle = self.regs.read_cc_a(); + self.regs.write_cc_a(0); + self.enabled = false; + } + } + + /// Capture a gpio pin and use it as pwm output for channel A + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } + + /// Invert channel output + #[inline] + pub fn set_inverted(&mut self) { + self.regs.write_inv_a(true) + } + + /// Stop inverting channel output + #[inline] + pub fn clr_inverted(&mut self) { + self.regs.write_inv_a(false) + } +} + +impl Channel { + /// Enable or disable the PWM channel + pub fn set_enabled(&mut self, enable: bool) { + if enable && !self.enabled { + // Restore the duty cycle. + self.regs.write_cc_b(self.duty_cycle); + self.enabled = true; + } else if !enable && self.enabled { + // We can't disable it without disturbing the other channel so this + // just sets the duty cycle to zero. + self.duty_cycle = self.regs.read_cc_b(); + self.regs.write_cc_b(0); + self.enabled = false; + } + } + + /// Capture a gpio pin and use it as pwm output for channel B + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } + + /// Invert channel output + #[inline] + pub fn set_inverted(&mut self) { + self.regs.write_inv_b(true) + } + + /// Stop inverting channel output + #[inline] + pub fn clr_inverted(&mut self) { + self.regs.write_inv_b(false) + } +} + +impl Channel +where + S::Mode: ValidSliceInputMode, +{ + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmInputPin, + { + pin.into().into_function() + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm output + pub fn output_to(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmOutputPin, + { + pin.into().into_function() + } +} + +impl> Slice { + /// Capture a gpio pin and use it as pwm input for channel B + pub fn input_from(&mut self, pin: P) -> Pin + where + P::Id: ValidPwmInputPin, + { + pin.into().into_function() + } +} + +/// Type representing DMA access to PWM cc register. +/// +/// Both channels are accessed together, because of narrow write replication. +/// +/// ```no_run +/// use rp235x_hal::singleton; +/// use rp235x_hal::dma::{double_buffer, DMAExt}; +/// use rp235x_hal::pwm::{CcFormat, SliceDmaWrite, Slices}; +/// +/// let mut pac = rp235x_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// let buf = singleton!(: [CcFormat; 4] = [CcFormat{a: 0x1000, b: 0x9000}; 4]).unwrap(); +/// let buf2 = singleton!(: [CcFormat; 4] = [CcFormat{a: 0xf000, b: 0x5000}; 4]).unwrap(); +/// +/// let dma = pac.DMA.split(&mut pac.RESETS); +/// +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// +/// let dma_conf = double_buffer::Config::new((dma.ch0, dma.ch1), buf, dma_pwm.cc); +/// ``` +pub struct SliceDmaWriteCc> { + slice: PhantomData, + mode: PhantomData, +} + +/// Type representing DMA access to PWM top register. +/// +/// ```no_run +/// use embedded_hal_0_2::prelude::*; +/// use rp235x_hal::singleton; +/// use rp235x_hal::dma::{double_buffer, DMAExt}; +/// use rp235x_hal::pwm::{SliceDmaWrite, Slices, TopFormat}; +/// +/// +/// let mut pac = rp235x_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// // Just set to something mesurable. +/// pwm.channel_a.set_duty(0x1000); +/// pwm.channel_b.set_duty(0x1000); +/// +/// let buf = singleton!(: [TopFormat; 4] = [TopFormat::new(0x7fff); 4]).unwrap(); +/// let buf2 = singleton!(: [TopFormat; 4] = [TopFormat::new(0xfffe); 4]).unwrap(); +/// +/// let dma = pac.DMA.split(&mut pac.RESETS); +/// +/// // Reserve PWM slice for dma. +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// +/// let dma_conf = double_buffer::Config::new((dma.ch0, dma.ch1), buf, dma_pwm.top); +/// ``` + +pub struct SliceDmaWriteTop> { + slice: PhantomData, + mode: PhantomData, +} + +/// PWM slice while used for DMA writes. +/// ```no_run +/// use rp235x_hal::{ +/// prelude::*, +/// pwm::{SliceDmaWrite, Slices}, +/// }; +/// +/// let mut pac = rp235x_pac::Peripherals::take().unwrap(); +/// +/// // Init PWMs +/// let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS); +/// +/// // Configure PWM4 +/// let mut pwm = pwm_slices.pwm4; +/// pwm.enable(); +/// +/// // Use for DMA usage +/// let dma_pwm = SliceDmaWrite::from(pwm); +/// ``` +pub struct SliceDmaWrite> { + /// Part for top writes. + pub top: SliceDmaWriteTop, + + /// Part for cc writes. + pub cc: SliceDmaWriteCc, + slice: Slice, +} + +impl> From> for SliceDmaWrite { + fn from(value: Slice) -> Self { + Self { + slice: value, + top: SliceDmaWriteTop { + slice: PhantomData, + mode: PhantomData, + }, + cc: SliceDmaWriteCc { + slice: PhantomData, + mode: PhantomData, + }, + } + } +} + +impl> From> for Slice { + fn from(value: SliceDmaWrite) -> Self { + value.slice + } +} + +/// Format for DMA transfers to PWM CC register. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(C)] +#[repr(align(4))] +pub struct CcFormat { + /// CC register part for channel a. + pub a: u16, + /// CC register part for channel b. + pub b: u16, +} + +unsafe impl Word for CcFormat {} + +/// Format for DMA transfers to PWM TOP register. +/// +/// It is forbidden to use it as DMA write destination, +/// it is safe but it might not be compatible with a future use of reserved register fields. +#[derive(Clone, Copy, Eq)] +#[repr(C)] +#[repr(align(4))] +pub struct TopFormat { + /// Valid register part. + pub top: u16, + /// Reserved part. + /// Should always be zero + reserved: u16, +} + +impl PartialEq for TopFormat { + fn eq(&self, other: &TopFormat) -> bool { + self.top == other.top + } +} + +impl TopFormat { + /// Create a valid value. + pub fn new(top: u16) -> Self { + TopFormat { top, reserved: 0 } + } +} + +impl Default for TopFormat { + fn default() -> Self { + Self::new(u16::MAX) + } +} + +unsafe impl Word for TopFormat {} + +/// Safety: tx_address_count points to a register which is always a valid +/// write target. +unsafe impl> WriteTarget for SliceDmaWriteCc { + type TransmittedWord = CcFormat; + + fn tx_treq() -> Option { + Some(S::WRAP_DREQ) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let regs = Registers { + id: PhantomData:: {}, + }; + (regs.ch().cc().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +/// Safety: tx_address_count points to a register which is always a valid +/// write target. +unsafe impl> WriteTarget for SliceDmaWriteTop { + type TransmittedWord = TopFormat; + + fn tx_treq() -> Option { + Some(S::WRAP_DREQ) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + let regs = Registers { + id: PhantomData:: {}, + }; + (regs.ch().top().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl> EndlessWriteTarget for SliceDmaWriteCc {} +impl> EndlessWriteTarget for SliceDmaWriteTop {} diff --git a/rp235x-hal/src/pwm/reg.rs b/rp235x-hal/src/pwm/reg.rs new file mode 100644 index 000000000..20c27009a --- /dev/null +++ b/rp235x-hal/src/pwm/reg.rs @@ -0,0 +1,111 @@ +use crate::{ + pac::{self, pwm::CH}, + pwm::dyn_slice::{DynSliceId, DynSliceMode}, +}; + +/// # Safety +/// +/// Users should only implement the [`id`] function. No default function +/// implementations should be overridden. The implementing type must also have +/// "control" over the corresponding slice ID, i.e. it must guarantee that each +/// slice ID is a singleton +pub(super) unsafe trait RegisterInterface { + /// Provide a [`DynSliceId`] identifying the set of registers controlled by + /// this type. + fn id(&self) -> DynSliceId; + + #[inline] + fn ch(&self) -> &CH { + let num = self.id().num as usize; + unsafe { (*pac::PWM::ptr()).ch(num) } + } + + #[inline] + fn advance_phase(&mut self) { + self.ch().csr().modify(|_, w| w.ph_adv().set_bit()) + } + + #[inline] + fn retard_phase(&mut self) { + self.ch().csr().modify(|_, w| w.ph_ret().set_bit()) + } + + #[inline] + fn do_change_mode(&mut self, mode: DynSliceMode) { + self.ch().csr().modify(|_, w| match mode { + DynSliceMode::FreeRunning => w.divmode().div(), + DynSliceMode::InputHighRunning => w.divmode().level(), + DynSliceMode::CountRisingEdge => w.divmode().rise(), + DynSliceMode::CountFallingEdge => w.divmode().fall(), + }) + } + + #[inline] + fn write_inv_a(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.a_inv().bit(value)); + } + + #[inline] + fn write_inv_b(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.b_inv().bit(value)); + } + + #[inline] + fn write_ph_correct(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.ph_correct().bit(value)); + } + + #[inline] + fn write_enable(&mut self, value: bool) { + self.ch().csr().modify(|_, w| w.en().bit(value)); + } + + #[inline] + fn write_div_int(&mut self, value: u8) { + self.ch() + .div() + .modify(|_, w| unsafe { w.int().bits(value) }); + } + #[inline] + fn write_div_frac(&mut self, value: u8) { + self.ch() + .div() + .modify(|_, w| unsafe { w.frac().bits(value) }); + } + + #[inline] + fn write_ctr(&mut self, value: u16) { + self.ch().ctr().write(|w| unsafe { w.ctr().bits(value) }); + } + + #[inline] + fn read_ctr(&self) -> u16 { + self.ch().ctr().read().ctr().bits() + } + #[inline] + fn write_cc_a(&mut self, value: u16) { + self.ch().cc().modify(|_, w| unsafe { w.a().bits(value) }); + } + #[inline] + fn read_cc_a(&self) -> u16 { + self.ch().cc().read().a().bits() + } + + #[inline] + fn write_cc_b(&mut self, value: u16) { + self.ch().cc().modify(|_, w| unsafe { w.b().bits(value) }); + } + + #[inline] + fn read_cc_b(&self) -> u16 { + self.ch().cc().read().b().bits() + } + #[inline] + fn write_top(&mut self, value: u16) { + self.ch().top().write(|w| unsafe { w.top().bits(value) }); + } + #[inline] + fn read_top(&self) -> u16 { + self.ch().top().read().top().bits() + } +} diff --git a/rp235x-hal/src/reboot.rs b/rp235x-hal/src/reboot.rs new file mode 100644 index 000000000..cb6e48111 --- /dev/null +++ b/rp235x-hal/src/reboot.rs @@ -0,0 +1,87 @@ +//! Functions for rebooting the chip using the ROM. + +/// Types of reboot we support +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RebootKind { + /// A normal reboot + Normal, + /// Boot like BOOTSEL is held down + BootSel { + /// Disable the picoboot interface + picoboot_disabled: bool, + /// Disable the Mass Storage Device interface + msd_disabled: bool, + }, + /// Boot into RAM + Ram { + /// The start of the RAM area to boot from + start_addr: *const u32, + /// The size in bytes of that area + size: usize, + }, + /// Reboot but prefer the flash partition you just wrote + FlashUpdate { + /// The start of the flash area you want to boot from + start_addr: *const u32, + }, + /// Reboot into the given Program Counter and Stack Pointer + PcSp { + /// The new program counter + pc: fn() -> !, + /// The new stack pointer + sp: *const u32, + }, +} + +/// Which architecture should we reboot into +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum RebootArch { + /// No architecture preference + Normal, + /// Prefer to boot into ARM mode + Arm, + /// Prefer to boot into RISC-V mode + Riscv, +} + +/// Reboot the chip +pub fn reboot(kind: RebootKind, arch: RebootArch) -> ! { + let options = match arch { + RebootArch::Normal => 0x0000, + RebootArch::Arm => 0x0010, + RebootArch::Riscv => 0x0020, + }; + + match kind { + RebootKind::Normal => { + #[allow(clippy::identity_op)] + crate::rom_data::reboot(0x0000 | options, 500, 0, 0); + } + RebootKind::BootSel { + picoboot_disabled, + msd_disabled, + } => { + let mut flags = 0; + if picoboot_disabled { + flags |= 2; + } + if msd_disabled { + flags |= 1; + } + crate::rom_data::reboot(0x0002 | options, 500, 0, flags); + } + RebootKind::Ram { start_addr, size } => { + crate::rom_data::reboot(0x0003 | options, 500, start_addr as u32, size as u32); + } + RebootKind::FlashUpdate { start_addr } => { + crate::rom_data::reboot(0x0004 | options, 500, start_addr as u32, 0); + } + RebootKind::PcSp { pc, sp } => { + crate::rom_data::reboot(0x000d | options, 500, pc as usize as u32, sp as u32); + } + } + // Wait for the reboot (might take a few ms - it's asynchronous) + loop { + core::hint::spin_loop(); + } +} diff --git a/rp235x-hal/src/resets.rs b/rp235x-hal/src/resets.rs new file mode 100644 index 000000000..1607110eb --- /dev/null +++ b/rp235x-hal/src/resets.rs @@ -0,0 +1,54 @@ +//! Subsystem Resets +//! +//! See [Chapter 7](https://rptl.io/rp2350-datasheet#section_resets) for more details. + +mod private { + pub trait SubsystemReset { + fn reset_bring_up(&self, resets: &mut crate::pac::RESETS); + fn reset_bring_down(&self, resets: &mut crate::pac::RESETS); + } +} + +pub(crate) use private::SubsystemReset; + +macro_rules! generate_reset { + ($MODULE:ident, $module:ident) => { + impl SubsystemReset for $crate::pac::$MODULE { + fn reset_bring_up(&self, resets: &mut $crate::pac::RESETS) { + resets.reset().modify(|_, w| w.$module().clear_bit()); + while resets.reset_done().read().$module().bit_is_clear() {} + } + fn reset_bring_down(&self, resets: &mut $crate::pac::RESETS) { + resets.reset().modify(|_, w| w.$module().set_bit()); + } + } + }; +} + +// In datasheet order +generate_reset!(USB, usbctrl); +generate_reset!(UART1, uart1); +generate_reset!(UART0, uart0); +generate_reset!(TIMER0, timer0); +generate_reset!(TIMER1, timer1); +generate_reset!(TBMAN, tbman); +generate_reset!(SYSINFO, sysinfo); +generate_reset!(SYSCFG, syscfg); +generate_reset!(SPI1, spi1); +generate_reset!(SPI0, spi0); +generate_reset!(HSTX_CTRL, hstx); +generate_reset!(PWM, pwm); +generate_reset!(PLL_USB, pll_usb); +generate_reset!(PLL_SYS, pll_sys); +generate_reset!(PIO1, pio1); +generate_reset!(PIO0, pio0); +generate_reset!(PADS_QSPI, pads_qspi); +generate_reset!(PADS_BANK0, pads_bank0); +//generate_reset!(JTAG,jtag); // This doesn't seem to have an item in the pac +generate_reset!(IO_QSPI, io_qspi); +generate_reset!(IO_BANK0, io_bank0); +generate_reset!(I2C1, i2c1); +generate_reset!(I2C0, i2c0); +generate_reset!(DMA, dma); +generate_reset!(BUSCTRL, busctrl); +generate_reset!(ADC, adc); diff --git a/rp235x-hal/src/rom_data.rs b/rp235x-hal/src/rom_data.rs new file mode 100644 index 000000000..3879b9635 --- /dev/null +++ b/rp235x-hal/src/rom_data.rs @@ -0,0 +1,749 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for the tag which matches the mask. +type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +#[allow(unused)] +mod rt_flags { + pub const FUNC_RISCV: u32 = 0x0001; + pub const FUNC_RISCV_FAR: u32 = 0x0003; + pub const FUNC_ARM_SEC: u32 = 0x0004; + // reserved for 32-bit pointer: 0x0008 + pub const FUNC_ARM_NONSEC: u32 = 0x0010; + // reserved for 32-bit pointer: 0x0020 + pub const DATA: u32 = 0x0040; + // reserved for 32-bit pointer: 0x0080 + #[cfg(all(target_arch = "arm", target_os = "none"))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC; + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV; +} + +/// Retrieve rom content from a table using a code. +pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_TABLE_LOOKUP_A1.read() as usize + } else { + ROM_TABLE_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// Retrieve rom data content from a table using a code. +pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_DATA_LOOKUP_A1.read() as usize + } else { + ROM_DATA_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the rp235x datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +// **************** 5.5.7 Low-level Flash Commands **************** + +declare_rom_function! { + /// Restore all QSPI pad controls to their default state, and connect the + /// QMI peripheral to the QSPI pads. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn connect_internal_flash() -> () { + crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Initialise the QMI for serial operations (direct mode) + /// + /// Also initialise a basic XIP mode, where the QMI will perform 03h serial + /// read commands at low speed (CLKDIV=12) in response to XIP reads. + /// + /// Then, issue a sequence to the QSPI device on chip select 0, designed to + /// return it from continuous read mode ("XIP mode") and/or QPI mode to a + /// state where it will accept serial commands. This is necessary after + /// system reset to restore the QSPI device to a known state, because + /// resetting RP2350 does not reset attached QSPI devices. It is also + /// necessary when user code, having already performed some + /// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI + /// device to a state where it will accept the serial erase and programming + /// commands issued by the bootrom’s flash access functions. + /// + /// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO, + /// then the XIP exit sequence is also issued to chip select 1. + /// + /// The QSPI device should be accessible for XIP reads after calling this + /// function; the name flash_exit_xip refers to returning the QSPI device + /// from its XIP state to a serial command state. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_exit_xip() -> () { + crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Erase count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the + /// size of the block erased by this command — this function will use the + /// larger block erase where possible, for much higher erase speed. addr + /// must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API, which can be achieved by calling connect_internal_flash() followed + /// by flash_exit_xip(). After the erase, the flash cache should be flushed + /// via flash_flush_cache() to ensure the modified flash data is visible to + /// cached XIP accesses. + /// + /// Finally, the original XIP mode should be restored by copying the saved + /// XIP setup function from bootram into SRAM, and executing it: the bootrom + /// provides a default function which restores the flash mode/clkdiv + /// discovered during flash scanning, and user programs can override this + /// with their own XIP setup function. + /// + /// For the duration of the erase operation, QMI is in direct mode (Section + /// 12.14.5) and attempting to access XIP from DMA, the debugger or the + /// other core will return a bus fault. XIP becomes accessible again once + /// the function returns. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () { + crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Program data to a range of flash storage addresses starting at addr + /// (offset from the start of flash) and count bytes in size. + /// + /// `addr` must be aligned to a 256-byte boundary, and count must be a + /// multiple of 256. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API — see notes on flash_range_erase(). + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () { + crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Flush the entire XIP cache, by issuing an invalidate by set/way + /// maintenance operation to every cache line (Section 4.4.1). + /// + /// This ensures that flash program/erase operations are visible to + /// subsequent cached XIP reads. + /// + /// Note that this unpins pinned cache lines, which may interfere with + /// cache-as-SRAM use of the XIP cache. + /// + /// No other operations are performed. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_flush_cache() -> () { + crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure the QMI to generate a standard 03h serial read command, with + /// 24 address bits, upon each XIP access. + /// + /// This is a slow XIP configuration, but is widely supported. CLKDIV is set + /// to 12. The debugger may call this function to ensure that flash is + /// readable following a program/erase operation. + /// + /// Note that the same setup is performed by flash_exit_xip(), and the + /// RP2350 flash program/erase functions do not leave XIP in an inaccessible + /// state, so calls to this function are largely redundant. It is provided + /// for compatibility with RP2040. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_enter_cmd_xip() -> () { + crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure QMI for one of a small menu of XIP read modes supported by the + /// bootrom. This mode is configured for both memory windows (both chip + /// selects), and the clock divisor is also applied to direct mode. + /// + /// The available modes are: + /// + /// * 0: `03h` serial read: serial address, serial data, no wait cycles + /// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles + /// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles + /// (including MODE bits, which are driven to 0) + /// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles + /// (including MODE bits, which are driven to 0) + /// + /// The XIP write command/format are not configured by this function. When + /// booting from flash, the bootrom tries each of these modes in turn, from + /// 3 down to 0. The first mode that is found to work is remembered, and a + /// default XIP setup function is written into bootram that calls this + /// function (flash_select_xip_read_mode) with the parameters discovered + /// during flash scanning. This can be called at any time to restore the + /// flash parameters discovered during flash boot. + /// + /// All XIP modes configured by the bootrom have an 8-bit serial command + /// prefix, so that the flash can remain in a serial command state, meaning + /// XIP accesses can be mixed more freely with program/erase serial + /// operations. This has a performance penalty, so users can perform their + /// own flash setup after flash boot using continuous read mode or QPI mode + /// to avoid or alleviate the command prefix cost. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () { + crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Restore the QMI address translation registers, ATRANS0 through ATRANS7, + /// to their reset state. This makes the runtime- to-storage address map an + /// identity map, i.e. the mapped and unmapped address are equal, and the + /// entire space is fully mapped. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_reset_address_trans() -> () { + crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** High-level Flash Commands **************** + +declare_rom_function! { + /// Applies the address translation currently configured by QMI address + /// translation registers, ATRANS0 through ATRANS7. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Translating an address outside of the XIP runtime address window, or + /// beyond the bounds of an ATRANSx_SIZE field, returns + /// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage + /// address. Otherwise, return the storage address which QMI would access + /// when presented with the runtime address addr. This is effectively a + /// virtual-to-physical address translation for QMI. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_runtime_to_storage_addr()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Perform a flash read, erase, or program operation. + /// + /// Erase operations must be sector-aligned (4096 bytes) and sector- + /// multiple-sized, and program operations must be page-aligned (256 bytes) + /// and page-multiple-sized; misaligned erase and program operations will + /// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program + /// — is selected by the CFLASH_OP_BITS bitfield of the flags argument. + /// + /// See datasheet section 5.5.8.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_op()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Security Related Functions **************** + +declare_rom_function! { + /// Allow or disallow the specific NS API (note all NS APIs default to + /// disabled). + /// + /// See datasheet section 5.5.9.1 for more details. + /// + /// Supported architectures: ARM-S + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::rt_flags::FUNC_ARM_SEC) + } +} + +declare_rom_function! { + /// Utility method that can be used by secure ARM code to validate a buffer + /// passed to it from Non-secure code. + /// + /// See datasheet section 5.5.9.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn validate_ns_buffer() -> () { + crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Miscellaneous Functions **************** + +declare_rom_function! { + /// Resets the RP2350 and uses the watchdog facility to restart. + /// + /// See datasheet section 5.5.10.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [reboot()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Resets internal bootrom state. + /// + /// See datasheet section 5.5.10.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn bootrom_state_reset(flags: u32) -> () { + crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Set a boot ROM callback. + /// + /// The only supported callback_number is 0 which sets the callback used for + /// the secure_call API. + /// + /// See datasheet section 5.5.10.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 { + crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** System Information Functions **************** + +declare_rom_function! { + /// Fills a buffer with various system information. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_sys_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Fills a buffer with information from the partition table. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_partition_table_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Loads the current partition table from flash, if present. + /// + /// See datasheet section 5.5.11.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 { + crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Writes data from a buffer into OTP, or reads data from OTP into a buffer. + /// + /// See datasheet section 5.5.11.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [otp_access()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Boot Related Functions **************** + +declare_rom_function! { + /// Determines which of the partitions has the "better" IMAGE_DEF. In the + /// case of executable images, this is the one that would be booted. + /// + /// See datasheet section 5.5.12.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Searches a memory region for a launchable image, and executes it if + /// possible. + /// + /// See datasheet section 5.5.12.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Perform an "explicit" buy of an executable launched via an IMAGE_DEF + /// which was "explicit buy" flagged. + /// + /// See datasheet section 5.5.12.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Not yet documented. + /// + /// See datasheet section 5.5.12.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Returns: The index of the B partition of partition A if a partition + /// table is present and loaded, and there is a partition A with a B + /// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND. + /// + /// See datasheet section 5.5.12.5 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_b_partition(partition_a: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Non-secure-specific Functions **************** + +// NB: The "secure_call" function should be here, but it doesn't have a fixed +// function signature as it is designed to let you bounce into any secure +// function from non-secure mode. + +// **************** RISC-V Functions **************** + +declare_rom_function! { + /// Set stack for RISC-V bootrom functions to use. + /// + /// See datasheet section 5.5.14.1 for more details. + /// + /// Supported architectures: RISC-V + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::rt_flags::FUNC_RISCV) + } +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32; + unsafe { ptr.read() } +} + +/// A pointer to the resident partition table info. +/// +/// The resident partition table is the subset of the full partition table that +/// is kept in memory, and used for flash permissions. +pub fn partition_table_pointer() -> *const u32 { + let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32; + unsafe { ptr.read() } +} + +/// Determine if we are in secure mode +/// +/// Returns `true` if we are in secure mode and `false` if we are in non-secure +/// mode. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub fn is_secure_mode() -> bool { + // Look at the start of ROM, which is always readable + #[allow(clippy::zero_ptr)] + let rom_base: *mut u32 = 0x0000_0000 as *mut u32; + // Use the 'tt' instruction to check the permissions for that address + let tt = cortex_m::asm::tt(rom_base); + // Is the secure bit set? => secure mode + (tt & (1 << 22)) != 0 +} + +/// Determine if we are in secure mode +/// +/// Always returns `false` on RISC-V as it is impossible to determine if +/// you are in Machine Mode or User Mode by design. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub fn is_secure_mode() -> bool { + false +} diff --git a/rp235x-hal/src/rosc.rs b/rp235x-hal/src/rosc.rs new file mode 100644 index 000000000..8f8db3f0f --- /dev/null +++ b/rp235x-hal/src/rosc.rs @@ -0,0 +1,152 @@ +//! Ring Oscillator (ROSC) +//! +//! See [Section 8.3](https://rptl.io/rp2350-datasheet#section_rosc) for more details. +//! +//! In addition to its obvious role as a clock source, [`RingOscillator`] can also be used as a random number source +//! for the [`rand`] crate: +//! +//! ```no_run +//! # let mut pac = rp235x_pac::Peripherals::take().unwrap(); +//! use rand::Rng; +//! use rp235x_hal::rosc::RingOscillator; +//! let mut rnd = RingOscillator::new(pac.ROSC).initialize(); +//! let random_value: u32 = rnd.gen(); +//! ``` +//! [`rand`]: https://docs.rs/rand +use fugit::HertzU32; + +use crate::{pac::ROSC, typelevel::Sealed}; + +/// State of the Ring Oscillator (typestate trait) +pub trait State: Sealed {} + +/// ROSC is disabled (typestate) +pub struct Disabled; + +/// ROSC is initialized, ie we've given parameters (typestate) +pub struct Enabled { + freq_hz: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Enabled {} +impl Sealed for Enabled {} + +/// A Ring Oscillator. +pub struct RingOscillator { + device: ROSC, + state: S, +} + +impl RingOscillator { + /// Transitions the oscillator to another state. + fn transition(self, state: To) -> RingOscillator { + RingOscillator { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> ROSC { + self.device + } +} + +impl RingOscillator { + /// Creates a new RingOscillator from the underlying device. + pub fn new(dev: ROSC) -> Self { + RingOscillator { + device: dev, + state: Disabled, + } + } + + /// Initializes the ROSC : frequency range is set, startup delay is calculated and set. + pub fn initialize(self) -> RingOscillator { + self.device.ctrl().write(|w| w.enable().enable()); + + use fugit::RateExtU32; + self.transition(Enabled { + freq_hz: 6_500_000u32.Hz(), + }) + } + + /// Initializes the ROSC with a known frequency. + /// + /// See Sections 8.3.4 "Modifying the frequency", and 8.3.8 "Using the + /// frequency counter" in the [RP2350 datasheet](https://rptl.io/rp2350-datasheet) + /// for guidance on how to do this before initialising the ROSC. Also see + /// `rosc_as_system_clock` example for usage. + pub fn initialize_with_freq(self, known_freq: HertzU32) -> RingOscillator { + self.device.ctrl().write(|w| w.enable().enable()); + self.transition(Enabled { + freq_hz: known_freq, + }) + } +} + +impl RingOscillator { + /// Approx operating frequency of the ROSC in hertz + pub fn operating_frequency(&self) -> HertzU32 { + self.state.freq_hz + } + + /// Disables the ROSC + pub fn disable(self) -> RingOscillator { + self.device.ctrl().modify(|_r, w| w.enable().disable()); + + self.transition(Disabled) + } + + /// Generate random bit based on the Ring oscillator + /// This is not suited for security purposes + pub fn get_random_bit(&self) -> bool { + self.device.randombit().read().randombit().bit() + } + + /// Put the ROSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, ROSC restarts in approximately 1µs. + /// + /// # Safety + /// This method is marked unsafe because prior to switch the ROSC into DORMANT state, + /// PLLs must be stopped and IRQs have to be properly configured. + /// This method does not do any of that, it merely switches the ROSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. + /// + /// See [Section 6.5.3](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + pub unsafe fn dormant(&self) { + //taken from the C SDK + const ROSC_DORMANT_VALUE: u32 = 0x636f6d61; + + self.device.dormant().write(|w| w.bits(ROSC_DORMANT_VALUE)); + } +} + +impl rand_core::RngCore for RingOscillator { + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + for chunk in dest.iter_mut() { + *chunk = 0_u8; + for _ in 0..8 { + *chunk <<= 1; + *chunk ^= self.get_random_bit() as u8; + } + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} diff --git a/rp235x-hal/src/sio.rs b/rp235x-hal/src/sio.rs new file mode 100644 index 000000000..825612c03 --- /dev/null +++ b/rp235x-hal/src/sio.rs @@ -0,0 +1,613 @@ +//! Single Cycle Input and Output (SIO) +//! +//! To be able to partition parts of the SIO block to other modules: +//! +//! ```no_run +//! use rp235x_hal::{self as hal, gpio::Pins, sio::Sio}; +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! ``` +//! +//! And then for example +//! +//! ```no_run +//! # use rp235x_hal::{self as hal, gpio::Pins, sio::Sio}; +//! # let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! # let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! ``` + +use crate::typelevel::Sealed; + +use super::*; +use core::convert::Infallible; + +/// Id of the core. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreId { + #[allow(missing_docs)] + Core0 = 0, + #[allow(missing_docs)] + Core1 = 1, +} + +/// Marker struct for ownership of SIO gpio bank0 +pub struct SioGpioBank0 { + _private: (), +} + +/// Marker struct for ownership of SIO FIFO +pub struct SioFifo { + _private: (), +} + +/// Marker struct for ownership of SIO gpio qspi +pub struct SioGpioQspi { + _private: (), +} + +/// Struct containing ownership markers for managing ownership of the SIO registers. +pub struct Sio { + _sio: pac::SIO, + /// GPIO Bank 0 registers + pub gpio_bank0: SioGpioBank0, + /// GPIO QSPI registers + pub gpio_qspi: SioGpioQspi, + /// Inter-core FIFO + pub fifo: SioFifo, + /// Interpolator 0 + pub interp0: Interp0, + /// Interpolator 1 + pub interp1: Interp1, +} + +impl Sio { + /// Create `Sio` from the PAC. + pub fn new(sio: pac::SIO) -> Self { + Self { + _sio: sio, + gpio_bank0: SioGpioBank0 { _private: () }, + gpio_qspi: SioGpioQspi { _private: () }, + fifo: SioFifo { _private: () }, + interp0: Interp0 { + lane0: Interp0Lane0 { _private: () }, + lane1: Interp0Lane1 { _private: () }, + }, + interp1: Interp1 { + lane0: Interp1Lane0 { _private: () }, + lane1: Interp1Lane1 { _private: () }, + }, + } + } + + /// Reads the whole bank0 at once. + pub fn read_bank0() -> u32 { + unsafe { (*pac::SIO::PTR).gpio_in().read().bits() } + } + + /// Returns whether we are running on Core 0 (`0`) or Core 1 (`1`). + pub fn core() -> CoreId { + // Safety: it is always safe to read this read-only register + match unsafe { (*pac::SIO::ptr()).cpuid().read().bits() as u8 } { + 0 => CoreId::Core0, + 1 => CoreId::Core1, + _ => unreachable!("This MCU only has 2 cores."), + } + } +} + +impl SioFifo { + /// Check if the inter-core FIFO has valid data for reading. + /// + /// Returning `true` means there is valid data, `false` means it is empty + /// and you must not read from it. + pub fn is_read_ready(&mut self) -> bool { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().vld().bit_is_set() + } + + /// Check if the inter-core FIFO is ready to receive data. + /// + /// Returning `true` means there is room, `false` means it is full and you + /// must not write to it. + pub fn is_write_ready(&mut self) -> bool { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().rdy().bit_is_set() + } + + /// Return the FIFO status, as an integer. + pub fn status(&self) -> u32 { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_st().read().bits() + } + + /// Write to the inter-core FIFO. + /// + /// You must ensure the FIFO has space by calling `is_write_ready` + pub fn write(&mut self, value: u32) { + let sio = unsafe { &(*pac::SIO::ptr()) }; + sio.fifo_wr().write(|w| unsafe { w.bits(value) }); + // Fire off an event to the other core. + // This is required as the other core may be `wfe` (waiting for event) + crate::arch::sev(); + } + + /// Read from the inter-core FIFO. + /// + /// Will return `Some(data)`, or `None` if the FIFO is empty. + pub fn read(&mut self) -> Option { + if self.is_read_ready() { + let sio = unsafe { &(*pac::SIO::ptr()) }; + Some(sio.fifo_rd().read().bits()) + } else { + None + } + } + + /// Read from the FIFO until it is empty, throwing the contents away. + pub fn drain(&mut self) { + while self.read().is_some() { + // Retry until FIFO empty + } + } + + /// Push to the FIFO, spinning if there's no space. + pub fn write_blocking(&mut self, value: u32) { + // We busy-wait for the FIFO to have some space + while !self.is_write_ready() { + crate::arch::nop(); + } + + // Write the value to the FIFO - the other core will now be able to + // pop it off its end of the FIFO. + self.write(value); + + // Fire off an event to the other core + crate::arch::sev(); + } + + /// Pop from the FIFO, spinning if there's currently no data. + pub fn read_blocking(&mut self) -> u32 { + // Keep trying until FIFO has data + loop { + // Have we got something? + if let Some(data) = self.read() { + // Yes, return it right away + return data; + } else { + // No, so sleep the CPU. We expect the sending core to `sev` + // on write. + crate::arch::wfe(); + } + } + } +} + +/// This type is just used to limit us to Spinlocks `0..=31` +pub trait SpinlockValid: Sealed {} + +/// Hardware based spinlock. +/// +/// You can claim this lock by calling either [`claim`], [`try_claim`] or +/// [`claim_async`]. These spin-locks are hardware backed, so if you lock +/// e.g. `Spinlock<6>`, then any other part of your application using +/// `Spinlock<6>` will contend for the same lock, without them needing to +/// share a reference or otherwise communicate with each other. +/// +/// When the obtained spinlock goes out of scope, it is automatically unlocked. +/// +/// +/// ```no_run +/// use rp235x_hal::sio::Spinlock0; +/// static mut SOME_GLOBAL_VAR: u32 = 0; +/// +/// /// This function is safe to call from two different cores, but is not safe +/// /// to call from an interrupt routine! +/// fn update_global_var() { +/// // Do not say `let _ = ` here - it will immediately unlock! +/// let _lock = Spinlock0::claim(); +/// // Do your thing here that Core 0 and Core 1 might want to do at the +/// // same time, like update this global variable: +/// unsafe { SOME_GLOBAL_VAR += 1 }; +/// // The lock is dropped here. +/// } +/// ``` +/// +/// **Warning**: These spinlocks are not re-entrant, meaning that the +/// following code will cause a deadlock: +/// +/// ```no_run +/// use rp235x_hal::sio::Spinlock0; +/// let lock_1 = Spinlock0::claim(); +/// let lock_2 = Spinlock0::claim(); // deadlock here +/// ``` +/// +/// **Note:** The `critical-section` implementation uses Spinlock 31. +/// +/// [`claim`]: #method.claim +/// [`try_claim`]: #method.try_claim +/// [`claim_async`]: #method.claim_asyncs +pub struct Spinlock(core::marker::PhantomData<()>) +where + Spinlock: SpinlockValid; + +impl Spinlock +where + Spinlock: SpinlockValid, +{ + /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is + /// already in use somewhere else. + pub fn try_claim() -> Option { + // Safety: We're only reading from this register + let sio = unsafe { &*pac::SIO::ptr() }; + let lock = sio.spinlock(N).read().bits(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + + /// Claim the spinlock, will block the current thread until the lock is available. + /// + /// Note that calling this multiple times in a row will cause a deadlock + pub fn claim() -> Self { + loop { + if let Some(result) = Self::try_claim() { + break result; + } + } + } + + /// Try to claim the spinlock. Will return `WouldBlock` until the spinlock is available. + pub fn claim_async() -> nb::Result { + Self::try_claim().ok_or(nb::Error::WouldBlock) + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + let sio = &*pac::SIO::ptr(); + // Write (any value): release the lock + sio.spinlock(N).write_with_zero(|b| b.bits(1)); + } +} + +impl Drop for Spinlock +where + Spinlock: SpinlockValid, +{ + fn drop(&mut self) { + // This is safe because we own the object, and hence hold the lock. + unsafe { Self::release() } + } +} + +macro_rules! spinlock { + ($first:expr, $($rest:tt),+) => { + spinlock!($first); + spinlock!($($rest),+); + }; + ($id:expr) => { + $crate::paste::paste! { + /// Spinlock number $id + pub type [] = Spinlock<$id>; + impl SpinlockValid for Spinlock<$id> {} + impl Sealed for Spinlock<$id> {} + } + }; +} +spinlock!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30 +); + +/// Spinlock number 31 - used by critical section implementation +#[cfg(feature = "critical-section-impl")] +pub(crate) type Spinlock31 = Spinlock<31>; + +/// Spinlock number 31 - only public if critical-section-impl is not enabled +#[cfg(not(feature = "critical-section-impl"))] +pub type Spinlock31 = Spinlock<31>; + +impl SpinlockValid for Spinlock<31> {} +impl Sealed for Spinlock<31> {} + +/// Returns the current state of the spinlocks. Each index corresponds to the associated spinlock, e.g. if index `5` is set to `true`, it means that [`Spinlock5`] is currently locked. +/// +/// Note that spinlocks can be claimed or released at any point, so this function cannot guarantee the spinlock is actually available right after calling this function. This function is mainly intended for debugging. +pub fn spinlock_state() -> [bool; 32] { + // Safety: we're only reading from a register + let sio = unsafe { &*pac::SIO::ptr() }; + // A bitmap containing the state of all 32 spinlocks (1=locked). + let register = sio.spinlock_st().read().bits(); + let mut result = [false; 32]; + #[allow(clippy::needless_range_loop)] + for i in 0..32 { + result[i] = (register & (1 << i)) > 0; + } + result +} + +/// Free all spinlocks, regardless of their current status +/// +/// rp235x does not release all spinlocks on reset. +/// The C SDK clears these all during entry, and so do we if you call hal::entry! +/// But if someone is using the default cortex-m entry they risk hitting deadlocks so provide *something* to help out +/// +/// # Safety +/// Where possible, you should use the hal::entry macro attribute on main instead of this. +/// You should call this as soon as possible after reset - preferably as the first entry in fn main(), before *ANY* use of spinlocks, atomics, or critical_section +pub unsafe fn spinlock_reset() { + // Using raw pointers to avoid taking peripherals accidently at startup + const SIO_BASE: u32 = 0xd0000000; + const SPINLOCK0_PTR: *mut u32 = (SIO_BASE + 0x100) as *mut u32; + const SPINLOCK_COUNT: usize = 32; + for i in 0..SPINLOCK_COUNT { + SPINLOCK0_PTR.wrapping_add(i).write_volatile(1); + } +} + +/// Configuration struct for one lane of the interpolator +pub struct LaneCtrl { + /// Bit 22 - Only present on INTERP1 on each core. If CLAMP mode is enabled: + /// - LANE0 result is shifted and masked ACCUM0, clamped by a lower bound of + /// BASE0 and an upper bound of BASE1. + /// - Signedness of these comparisons is determined by LANE0_CTRL_SIGNED + pub clamp: bool, + /// Bit 21 - Only present on INTERP0 on each core. If BLEND mode is enabled: + /// + /// - LANE1 result is a linear interpolation between BASE0 and BASE1, controlled + /// by the 8 LSBs of lane 1 shift and mask value (a fractional number between + /// 0 and 255/256ths) + /// - LANE0 result does not have BASE0 added (yields only + /// the 8 LSBs of lane 1 shift+mask value) + /// - FULL result does not have lane 1 shift+mask value added (BASE2 + lane 0 shift+mask) + /// + /// LANE1 SIGNED flag controls whether the interpolation is signed or unsigned. + pub blend: bool, + /// Bits 19:20 - ORed into bits 29:28 of the lane result presented to the processor on the bus. + /// No effect on the internal 32-bit datapath. Handy for using a lane to generate sequence + /// of pointers into flash or SRAM. + pub force_msb: u8, + /// Bit 18 - If 1, mask + shift is bypassed for LANE0 result. This does not affect FULL result. + pub add_raw: bool, + /// Bit 17 - If 1, feed the opposite lane's result into this lane's accumulator on POP. + pub cross_result: bool, + /// Bit 16 - If 1, feed the opposite lane's accumulator into this lane's shift + mask hardware. + /// Takes effect even if ADD_RAW is set (the CROSS_INPUT mux is before the shift+mask bypass) + pub cross_input: bool, + /// Bit 15 - If SIGNED is set, the shifted and masked accumulator value is sign-extended to 32 bits + /// before adding to BASE0, and LANE0 PEEK/POP appear extended to 32 bits when read by processor. + pub signed: bool, + /// Bits 10:14 - The most-significant bit allowed to pass by the mask (inclusive) + /// Setting MSB < LSB may cause chip to turn inside-out + pub mask_msb: u8, + /// Bits 5:9 - The least-significant bit allowed to pass by the mask (inclusive) + pub mask_lsb: u8, + /// Bits 0:4 - Logical right-shift applied to accumulator before masking + pub shift: u8, +} + +impl Default for LaneCtrl { + fn default() -> Self { + Self::new() + } +} + +impl LaneCtrl { + /// Default configuration. Normal operation, unsigned, mask keeps all bits, no shift. + pub const fn new() -> Self { + Self { + clamp: false, + blend: false, + force_msb: 0, + add_raw: false, + cross_result: false, + cross_input: false, + signed: false, + mask_msb: 31, + mask_lsb: 0, + shift: 0, + } + } + + /// encode the configuration to be loaded in the ctrl register of one lane of an interpolator + pub const fn encode(&self) -> u32 { + assert!(!(self.blend && self.clamp)); + assert!(self.force_msb < 0b100); + assert!(self.mask_msb < 0b100000); + assert!(self.mask_lsb < 0b100000); + assert!(self.mask_msb >= self.mask_lsb); + assert!(self.shift < 0b100000); + ((self.clamp as u32) << 22) + | ((self.blend as u32) << 21) + | ((self.force_msb as u32) << 19) + | ((self.add_raw as u32) << 18) + | ((self.cross_result as u32) << 17) + | ((self.cross_input as u32) << 16) + | ((self.signed as u32) << 15) + | ((self.mask_msb as u32) << 10) + | ((self.mask_lsb as u32) << 5) + | (self.shift as u32) + } +} + +///Trait representing the functionality of a single lane of an interpolator. +pub trait Lane: Sealed { + ///Read the lane result, and simultaneously write lane results to both accumulators. + fn pop(&mut self) -> u32; + ///Read the lane result without altering any internal state + fn peek(&self) -> u32; + ///Write a value to the accumulator + fn set_accum(&mut self, v: u32); + ///Read the value from the accumulator + fn get_accum(&self) -> u32; + ///Write a value to the base register + fn set_base(&mut self, v: u32); + ///Read the value from the base register + fn get_base(&self) -> u32; + ///Write to the control register + fn set_ctrl(&mut self, v: u32); + ///Read from the control register + fn get_ctrl(&self) -> u32; + ///Add the value to the accumulator register + fn add_accum(&mut self, v: u32); + ///Read the raw shift and mask value (BASE register not added) + fn read_raw(&self) -> u32; +} + +///Trait representing the functionality of an interpolator. +/// ```no_run +/// use rp235x_hal::{ +/// self as hal, +/// sio::{Lane, LaneCtrl, Sio}, +/// }; +/// let mut peripherals = hal::pac::Peripherals::take().unwrap(); +/// let mut sio = Sio::new(peripherals.SIO); +/// +/// // by having the configuration const, the validity is checked during compilation. +/// const config: u32 = LaneCtrl { +/// mask_msb: 4, // Most significant bit of the mask is bit 4 +/// // By default the least significant bit is bit 0 +/// // this will keep only the 5 least significant bits. +/// // this is equivalent to %32 +/// ..LaneCtrl::new() +/// } +/// .encode(); +/// sio.interp0.get_lane0().set_ctrl(config); +/// sio.interp0.get_lane0().set_accum(0); +/// sio.interp0.get_lane0().set_base(1); // will increment the value by 1 on each call to pop +/// +/// sio.interp0.get_lane0().peek(); // returns 1 +/// sio.interp0.get_lane0().pop(); // returns 1 +/// sio.interp0.get_lane0().pop(); // returns 2 +/// sio.interp0.get_lane0().pop(); // returns 3 +/// ``` +pub trait Interp: Sealed { + ///Read the interpolator result (Result 2 in the datasheet), and simultaneously write lane results to both accumulators. + fn pop(&mut self) -> u32; + ///Read the interpolator result (Result 2 in the datasheet) without altering any internal state + fn peek(&self) -> u32; + ///Write to the interpolator Base register (Base2 in the datasheet) + fn set_base(&mut self, v: u32); + ///Read the interpolator Base register (Base2 in the datasheet) + fn get_base(&self) -> u32; + ///Write the lower 16 bits to BASE0 and the upper bits to BASE1 simultaneously. Each half is sign-extended to 32 bits if that lane's SIGNED flag is set + fn set_base_1and0(&mut self, v: u32); +} + +macro_rules! interpolators { + ( + $($interp:ident : ( $( [ $lane:ident,$lane_id:expr ] ),+ ) ),+ + ) => { + $crate::paste::paste! { + + + $( + $( + #[doc = "The lane " $lane_id " of " $interp] + pub struct [<$interp $lane>]{ + _private: (), + } + impl Lane for [<$interp $lane>]{ + fn pop(&mut self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _pop_ $lane:lower>]().read().bits() + } + fn peek(&self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _peek_ $lane:lower>]().read().bits() + } + fn set_accum(&mut self,v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_accum(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id>]().read().bits() + } + fn set_base(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_base(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base $lane_id>]().read().bits() + } + fn set_ctrl(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _ctrl_lane $lane_id>]().write(|w| unsafe { w.bits(v) }); + } + fn get_ctrl(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _ctrl_lane $lane_id>]().read().bits() + } + fn add_accum(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id _add>]().write(|w| unsafe { w.bits(v) }); + } + fn read_raw(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _accum $lane_id _add>]().read().bits() + } + } + impl Sealed for [<$interp $lane>] {} + )+ + #[doc = "Interpolator " $interp] + pub struct $interp { + $( + [<$lane:lower>]: [<$interp $lane>], + )+ + } + impl $interp{ + $( + /// Lane accessor function + pub fn [](&mut self)->&mut [<$interp $lane>]{ + &mut self.[<$lane:lower>] + } + )+ + } + impl Interp for $interp{ + fn pop(&mut self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _pop_full>]().read().bits() + } + fn peek(&self) ->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _peek_full>]().read().bits() + } + fn set_base(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base2>]().write(|w| unsafe { w.bits(v)}); + } + fn get_base(&self)->u32{ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base2>]().read().bits() + } + fn set_base_1and0(&mut self, v:u32){ + let sio = unsafe { &*pac::SIO::ptr() }; + sio.[<$interp:lower _base_1and0>]().write(|w| unsafe { w.bits(v)}); + } + } + impl Sealed for $interp {} + )+ + } + } + } + +interpolators!( + Interp0 : ([Lane0,0],[Lane1,1]), + Interp1 : ([Lane0,0],[Lane1,1]) +); diff --git a/rp235x-hal/src/spi.rs b/rp235x-hal/src/spi.rs new file mode 100644 index 000000000..0414342df --- /dev/null +++ b/rp235x-hal/src/spi.rs @@ -0,0 +1,588 @@ +//! Serial Peripheral Interface (SPI) +//! +//! [`Spi`] is the main struct exported by this module, representing a configured Spi bus. See its +//! docs for more information on its type parameters. +//! +//! See [Section 12.3](https://rptl.io/rp2350-datasheet#section_spi) for more details. +//! +//! ## Usage +//! +//! ```no_run +//! use embedded_hal::spi::MODE_0; +//! use fugit::RateExtU32; +//! use rp235x_hal::{ +//! self as hal, +//! gpio::{FunctionSpi, Pins}, +//! spi::Spi, +//! Sio, +//! }; +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! +//! let sclk = pins.gpio2.into_function::(); +//! let mosi = pins.gpio3.into_function::(); +//! +//! let spi_device = peripherals.SPI0; +//! let spi_pin_layout = (mosi, sclk); +//! +//! let spi = Spi::<_, _, _, 8>::new(spi_device, spi_pin_layout).init( +//! &mut peripherals.RESETS, +//! 125_000_000u32.Hz(), +//! 16_000_000u32.Hz(), +//! MODE_0, +//! ); +//! ``` + +use core::{convert::Infallible, marker::PhantomData, ops::Deref}; + +use embedded_hal::spi::{self, Phase, Polarity}; +// Support Embedded HAL 0.2 for backwards-compatibility +use embedded_hal_0_2::{blocking::spi as blocking_spi02, spi as spi02}; +use embedded_hal_nb::spi::FullDuplex; +use fugit::{HertzU32, RateExtU32}; + +use crate::{ + dma::{EndlessReadTarget, EndlessWriteTarget, ReadTarget, WriteTarget}, + pac::{self, dma::ch::ch_ctrl_trig::TREQ_SEL_A, RESETS}, + resets::SubsystemReset, + typelevel::Sealed, +}; + +mod pins; +pub use pins::*; + +impl From for FrameFormat { + fn from(f: spi::Mode) -> Self { + Self::MotorolaSpi(f) + } +} + +impl From<&spi::Mode> for FrameFormat { + fn from(f: &spi::Mode) -> Self { + Self::MotorolaSpi(*f) + } +} + +/// SPI frame format +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum FrameFormat { + /// Motorola SPI format. + /// + /// See [Section 12.3.4.9](https://rptl.io/rp2350-datasheet) of the RP2350 + /// datasheet. + MotorolaSpi(spi::Mode), + /// Texas Instruments synchronous serial frame format. + /// + /// See [Section 12.3.4.8](https://rptl.io/rp2350-datasheet) of the RP2350 + /// datasheet. + TexasInstrumentsSynchronousSerial, + /// National Semiconductor Microwire frame format. + /// + /// See [Section 12.3.4.14](https://rptl.io/rp2350-datasheet) of the RP2350 + /// datasheet. + NationalSemiconductorMicrowire, +} + +impl From<&embedded_hal_0_2::spi::Mode> for FrameFormat { + fn from(f: &embedded_hal_0_2::spi::Mode) -> Self { + let embedded_hal_0_2::spi::Mode { polarity, phase } = f; + match (polarity, phase) { + (spi02::Polarity::IdleLow, spi02::Phase::CaptureOnFirstTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_0) + } + (spi02::Polarity::IdleLow, spi02::Phase::CaptureOnSecondTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_1) + } + (spi02::Polarity::IdleHigh, spi02::Phase::CaptureOnFirstTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_2) + } + (spi02::Polarity::IdleHigh, spi02::Phase::CaptureOnSecondTransition) => { + FrameFormat::MotorolaSpi(spi::MODE_3) + } + } + } +} + +impl From for FrameFormat { + fn from(f: embedded_hal_0_2::spi::Mode) -> Self { + From::from(&f) + } +} + +/// State of the SPI +pub trait State: Sealed {} + +/// Spi is disabled +pub struct Disabled { + __private: (), +} + +/// Spi is enabled +pub struct Enabled { + __private: (), +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Enabled {} +impl Sealed for Enabled {} + +/// Pac SPI device +pub trait SpiDevice: Deref + SubsystemReset + Sealed { + /// Index of the peripheral. + const ID: usize; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8; +} + +impl Sealed for pac::SPI0 {} +impl SpiDevice for pac::SPI0 { + const ID: usize = 0; + fn tx_dreq() -> u8 { + TREQ_SEL_A::SPI0_TX.into() + } + fn rx_dreq() -> u8 { + TREQ_SEL_A::SPI0_RX.into() + } +} +impl Sealed for pac::SPI1 {} +impl SpiDevice for pac::SPI1 { + const ID: usize = 1; + fn tx_dreq() -> u8 { + TREQ_SEL_A::SPI1_TX.into() + } + fn rx_dreq() -> u8 { + TREQ_SEL_A::SPI1_RX.into() + } +} + +/// Data size used in spi +pub trait DataSize: Sealed {} + +impl DataSize for u8 {} +impl DataSize for u16 {} +impl Sealed for u8 {} +impl Sealed for u16 {} + +/// Configured Spi bus. +/// +/// This struct implements the `embedded-hal` Spi-related traits. It represents unique ownership +/// of the entire Spi bus of a single configured rp235x Spi peripheral. +/// +/// `Spi` has four generic parameters: +/// +/// - `S`: a typestate for whether the bus is [`Enabled`] or [`Disabled`]. Upon initial creation, +/// the bus is [`Disabled`]. You will then need to initialize it as either a main (master) or sub +/// (slave) device, providing the necessary configuration, at which point it will become [`Enabled`]. +/// - `D`: Which of the concrete Spi peripherals is being used, [`pac::SPI0`] or [`pac::SPI1`] +/// - `P`: Which pins are being used to configure the Spi peripheral `D`. A table of valid +/// pinouts for each Spi peripheral can be found in section X.X.X of the rp235x datasheet. +/// +/// The [`ValidSpiPinout`] trait is implemented for tuples of pin types that follow the layout: +/// +/// - `(Tx, Sck)` (i.e. first the "Tx"/"MOSI" pin, then the "Sck"/"Clock" pin) +/// - `(Tx, Rx, Sck)` (i.e. first "Tx"/"MOSI", then "Rx"/"MISO", then "Sck"/"Clock" pin) +/// +/// If you select an invalid layout, you will get a compile error that `P` does not implement +/// [`ValidSpiPinout`] for your specified [`SpiDevice`] peripheral `D` +/// - `DS`: The "data size", i.e. the number of bits transferred per data frame. Defaults to 8. +/// +/// In most cases you won't have to specify these types manually and can let the compiler infer +/// them for you based on the values you pass in to `new`. If you want to select a different +/// data frame size, you'll need to do that by specifying the `DS` parameter manually. +/// +/// See [the module level docs][self] for an example. +pub struct Spi, const DS: u8 = 8u8> { + device: D, + pins: P, + state: PhantomData, +} + +impl, const DS: u8> Spi { + fn transition(self, _: To) -> Spi { + Spi { + device: self.device, + pins: self.pins, + state: PhantomData, + } + } + + /// Releases the underlying device and pins. + pub fn free(self) -> (D, P) { + (self.device, self.pins) + } + + /// Set device pre-scale and post-div properties to match the given baudrate as + /// closely as possible based on the given peripheral clock frequency. + /// + /// Typically the peripheral clock is set to 125_000_000 Hz. + /// + /// Returns the frequency that we were able to achieve, which may not be exactly + /// the requested baudrate. + pub fn set_baudrate, B: Into>( + &mut self, + peri_frequency: F, + baudrate: B, + ) -> HertzU32 { + let freq_in = peri_frequency.into().to_Hz(); + let baudrate = baudrate.into().to_Hz(); + let mut prescale: u8 = u8::MAX; + let mut postdiv: u8 = 0; + + // Find smallest prescale value which puts output frequency in range of + // post-divide. Prescale is an even number from 2 to 254 inclusive. + for prescale_option in (2u32..=254).step_by(2) { + // We need to use an saturating_mul here because with a high baudrate certain invalid prescale + // values might not fit in u32. However we can be sure those values exceed the max sys_clk frequency + // So clamping a u32::MAX is fine here... + if freq_in < ((prescale_option + 2) * 256).saturating_mul(baudrate) { + prescale = prescale_option as u8; + break; + } + } + + // We might not find a prescale value that lowers the clock freq enough, so we leave it at max + debug_assert_ne!(prescale, u8::MAX); + + // Find largest post-divide which makes output <= baudrate. Post-divide is + // an integer in the range 0 to 255 inclusive. + for postdiv_option in (1..=255u8).rev() { + if freq_in / (prescale as u32 * postdiv_option as u32) > baudrate { + postdiv = postdiv_option; + break; + } + } + + self.device + .sspcpsr() + .write(|w| unsafe { w.cpsdvsr().bits(prescale) }); + self.device + .sspcr0() + .modify(|_, w| unsafe { w.scr().bits(postdiv) }); + + // Return the frequency we were able to achieve + (freq_in / (prescale as u32 * (1 + postdiv as u32))).Hz() + } +} + +impl, const DS: u8> Spi { + /// Create new not initialized Spi bus. Initialize it with [`.init`][Self::init] + /// or [`.init_slave`][Self::init_slave]. + /// + /// Valid pin sets are in the form of `(Tx, Sck)` or `(Tx, Rx, Sck)` + /// + /// If you pins are dynamically identified (`Pin`) they will first need to pass + /// validation using their corresponding [`ValidatedPinXX`](ValidatedPinTx). + pub fn new(device: D, pins: P) -> Spi { + Spi { + device, + pins, + state: PhantomData, + } + } + + /// Set format and datasize + fn set_format(&mut self, data_bits: u8, frame_format: FrameFormat) { + self.device.sspcr0().modify(|_, w| unsafe { + w.dss().bits(data_bits - 1).frf().bits(match &frame_format { + FrameFormat::MotorolaSpi(_) => 0x00, + FrameFormat::TexasInstrumentsSynchronousSerial => 0x01, + FrameFormat::NationalSemiconductorMicrowire => 0x10, + }); + + /* + * Clock polarity (SPO) and clock phase (SPH) are only applicable to + * the Motorola SPI frame format. + */ + if let FrameFormat::MotorolaSpi(ref mode) = frame_format { + w.spo() + .bit(mode.polarity == Polarity::IdleHigh) + .sph() + .bit(mode.phase == Phase::CaptureOnSecondTransition); + } + w + }); + } + + /// Set master/slave + fn set_slave(&mut self, slave: bool) { + if slave { + self.device.sspcr1().modify(|_, w| w.ms().set_bit()); + } else { + self.device.sspcr1().modify(|_, w| w.ms().clear_bit()); + } + } + + fn init_spi, B: Into>( + mut self, + resets: &mut RESETS, + peri_frequency: F, + baudrate: B, + frame_format: FrameFormat, + slave: bool, + ) -> Spi { + self.device.reset_bring_down(resets); + self.device.reset_bring_up(resets); + + self.set_baudrate(peri_frequency, baudrate); + self.set_format(DS, frame_format); + self.set_slave(slave); + // Always enable DREQ signals -- harmless if DMA is not listening + self.device + .sspdmacr() + .modify(|_, w| w.txdmae().set_bit().rxdmae().set_bit()); + + // Finally enable the SPI + self.device.sspcr1().modify(|_, w| w.sse().set_bit()); + + self.transition(Enabled { __private: () }) + } + + /// Initialize the SPI in master mode + pub fn init, B: Into, M: Into>( + self, + resets: &mut RESETS, + peri_frequency: F, + baudrate: B, + frame_format: M, + ) -> Spi { + self.init_spi(resets, peri_frequency, baudrate, frame_format.into(), false) + } + + /// Initialize the SPI in slave mode + pub fn init_slave>( + self, + resets: &mut RESETS, + frame_format: M, + ) -> Spi { + // Use dummy values for frequency and baudrate. + // With both values 0, set_baudrate will set prescale == u8::MAX, which will break if debug assertions are enabled. + // u8::MAX is outside the allowed range 2..=254 for CPSDVSR, which might interfere with proper operation in slave mode. + self.init_spi( + resets, + 1000u32.Hz(), + 1000u32.Hz(), + frame_format.into(), + true, + ) + } +} + +impl, const DS: u8> Spi { + fn is_writable(&self) -> bool { + self.device.sspsr().read().tnf().bit_is_set() + } + fn is_readable(&self) -> bool { + self.device.sspsr().read().rne().bit_is_set() + } + + /// Check if spi is busy transmitting and/or receiving + pub fn is_busy(&self) -> bool { + self.device.sspsr().read().bsy().bit_is_set() + } + + /// Disable the spi to reset its configuration. You'll then need to initialize it again to use + /// it. + pub fn disable(self) -> Spi { + self.device.sspcr1().modify(|_, w| w.sse().clear_bit()); + + self.transition(Disabled { __private: () }) + } +} + +macro_rules! impl_write { + ($type:ident, [$($nr:expr),+]) => { + + $( + impl> spi02::FullDuplex<$type> for Spi { + type Error = Infallible; + + fn read(&mut self) -> Result<$type, nb::Error> { + if !self.is_readable() { + return Err(nb::Error::WouldBlock); + } + + Ok(self.device.sspdr().read().data().bits() as $type) + } + fn send(&mut self, word: $type) -> Result<(), nb::Error> { + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + if !self.is_writable() { + return Err(nb::Error::WouldBlock); + } + + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(word as u16) }); + Ok(()) + } + } + + impl> blocking_spi02::write::Default<$type> for Spi {} + impl> blocking_spi02::transfer::Default<$type> for Spi {} + impl> blocking_spi02::write_iter::Default<$type> for Spi {} + + impl> spi::ErrorType for Spi { + type Error = Infallible; + } + + impl> spi::SpiBus<$type> for Spi { + fn read(&mut self, words: &mut [$type]) -> Result<(), Self::Error> { + for word in words.iter_mut() { + // write empty word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(0) }); + + // read one word + while !self.is_readable() {} + *word = self.device.sspdr().read().data().bits() as $type; + } + Ok(()) + } + + fn write(&mut self, words: &[$type]) -> Result<(), Self::Error> { + for word in words.iter() { + // write one word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(*word as u16) }); + + // drop read wordd + while !self.is_readable() {} + let _ = self.device.sspdr().read().data().bits(); + } + Ok(()) + } + + fn transfer(&mut self, read: &mut [$type], write: &[$type]) -> Result<(), Self::Error>{ + let len = read.len().max(write.len()); + for i in 0..len { + // write one word. Send empty word if buffer is empty. + let wb = write.get(i).copied().unwrap_or(0); + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(wb as u16) }); + + // read one word. Drop extra words if buffer is full. + while !self.is_readable() {} + let rb = self.device.sspdr().read().data().bits() as $type; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + + Ok(()) + } + + fn transfer_in_place(&mut self, words: &mut [$type]) -> Result<(), Self::Error>{ + for word in words.iter_mut() { + // write one word + while !self.is_writable() {} + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(*word as u16) }); + + // read one word + while !self.is_readable() {} + *word = self.device.sspdr().read().data().bits() as $type; + } + + Ok(()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + while self.is_busy() {} + Ok(()) + } + } + + impl> FullDuplex<$type> for Spi { + fn read(&mut self) -> Result<$type, nb::Error> { + if !self.is_readable() { + return Err(nb::Error::WouldBlock); + } + + Ok(self.device.sspdr().read().data().bits() as $type) + } + fn write(&mut self, word: $type) -> Result<(), nb::Error> { + // Write to TX FIFO whilst ignoring RX, then clean up afterward. When RX + // is full, PL022 inhibits RX pushes, and sets a sticky flag on + // push-on-full, but continues shifting. Safe if SSPIMSC_RORIM is not set. + if !self.is_writable() { + return Err(nb::Error::WouldBlock); + } + + self.device + .sspdr() + .write(|w| unsafe { w.data().bits(word as u16) }); + Ok(()) + } + } + + // Safety: This only reads from the RX fifo, so it doesn't + // interact with rust-managed memory. + unsafe impl> ReadTarget for Spi { + type ReceivedWord = $type; + + fn rx_treq() -> Option { + Some(D::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + ( + self.device.sspdr().as_ptr() as u32, + u32::MAX, + ) + } + + fn rx_increment(&self) -> bool { + false + } + } + + impl> EndlessReadTarget for Spi {} + + // Safety: This only writes to the TX fifo, so it doesn't + // interact with rust-managed memory. + unsafe impl> WriteTarget for Spi { + type TransmittedWord = $type; + + fn tx_treq() -> Option { + Some(D::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + ( + self.device.sspdr().as_ptr() as u32, + u32::MAX, + ) + } + + fn tx_increment(&self) -> bool { + false + } + } + + impl> EndlessWriteTarget for Spi {} + )+ + + }; +} + +impl_write!(u8, [4, 5, 6, 7, 8]); +impl_write!(u16, [9, 10, 11, 12, 13, 14, 15, 16]); diff --git a/rp235x-hal/src/spi/pins.rs b/rp235x-hal/src/spi/pins.rs new file mode 100644 index 000000000..7eb96c146 --- /dev/null +++ b/rp235x-hal/src/spi/pins.rs @@ -0,0 +1,140 @@ +use core::marker::PhantomData; + +use crate::{ + gpio::{bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, FunctionSpi}, + pac::{SPI0, SPI1}, + typelevel::{OptionT, OptionTNone, OptionTSome, Sealed}, +}; + +use super::SpiDevice; + +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Indicates a valid " $p " pin for SPI0 or SPI1"] + pub trait []: Sealed {} + + #[doc = "Indicates a valid " $p " pin for SPI0 or SPI1"] + pub trait []: Sealed {} + + impl [] for T + where + T: AnyPin, + T::Id: [], + { + } + + #[doc = "A runtime validated " $p " pin for spi."] + pub struct [](P, PhantomData); + impl Sealed for [] {} + impl [] for [] {} + impl [] + where + P: AnyPin, + S: SpiDevice, + { + /// Validate a pin's function on a spi peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that Spi."] + pub fn validate(p: P, _u: &S) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, S::ID)) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + + #[doc = "Indicates a valid optional " $p " pin for SPI0 or SPI1"] + pub trait []: OptionT {} + + impl [] for OptionTNone {} + impl [] for OptionTSome + where + U: SpiDevice, + T: [], + { + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} +pin_validation!(Tx, Rx, Sck, Cs); + +macro_rules! impl_valid_spi { + ($($spi:ident: { + rx: [$($rx:ident),*], + cs: [$($cs:ident),*], + sck: [$($sck:ident),*], + tx: [$($tx:ident),*], + }),*) => { + $( + $(impl ValidPinIdRx<$spi> for $rx {})* + $(impl ValidPinIdTx<$spi> for $tx {})* + $(impl ValidPinIdSck<$spi> for $sck {})* + $(impl ValidPinIdCs<$spi> for $cs {})* + )* + + const RX: &[(u8, usize)] = &[$($(($rx::ID.num, $spi::ID)),*),*]; + const TX: &[(u8, usize)] = &[$($(($tx::ID.num, $spi::ID)),*),*]; + const SCK: &[(u8, usize)] = &[$($(($sck::ID.num, $spi::ID)),*),*]; + const CS: &[(u8, usize)] = &[$($(($cs::ID.num, $spi::ID)),*),*]; + }; +} + +impl_valid_spi!( + SPI0: { + rx: [Gpio0, Gpio4, Gpio16, Gpio20], + cs: [Gpio1, Gpio5, Gpio17, Gpio21], + sck: [Gpio2, Gpio6, Gpio18, Gpio22], + tx: [Gpio3, Gpio7, Gpio19, Gpio23], + }, + SPI1: { + rx: [Gpio8, Gpio12, Gpio24, Gpio28], + cs: [Gpio9, Gpio13, Gpio25, Gpio29], + sck: [Gpio10, Gpio14, Gpio26], + tx: [Gpio11, Gpio15, Gpio27], + } +); + +/// Declares a valid SPI pinout. +pub trait ValidSpiPinout: Sealed { + #[allow(missing_docs)] + type Rx: ValidOptionRx; + #[allow(missing_docs)] + type Cs: ValidOptionCs; + #[allow(missing_docs)] + type Sck: ValidOptionSck; + #[allow(missing_docs)] + type Tx: ValidOptionTx; +} + +impl ValidSpiPinout for (Tx, Sck) +where + Spi: SpiDevice, + Tx: ValidPinTx, + Sck: ValidPinSck, +{ + type Rx = OptionTNone; + type Cs = OptionTNone; + type Sck = OptionTSome; + type Tx = OptionTSome; +} + +impl ValidSpiPinout for (Tx, Rx, Sck) +where + Spi: SpiDevice, + Tx: ValidPinTx, + Sck: ValidPinSck, + Rx: ValidPinRx, +{ + type Rx = OptionTSome; + type Cs = OptionTNone; + type Sck = OptionTSome; + type Tx = OptionTSome; +} diff --git a/rp235x-hal/src/timer.rs b/rp235x-hal/src/timer.rs new file mode 100644 index 000000000..0caf3ef1f --- /dev/null +++ b/rp235x-hal/src/timer.rs @@ -0,0 +1,639 @@ +//! Timer Peripheral +//! +//! The Timer peripheral on rp235x consists of a 64-bit counter and 4 alarms. +//! The Counter is incremented once per microsecond. It obtains its clock source from the watchdog peripheral, you must enable the watchdog before using this peripheral. +//! Since it would take thousands of years for this counter to overflow you do not need to write logic for dealing with this if using get_counter. +//! +//! Each of the 4 alarms can match on the lower 32 bits of Counter and trigger an interrupt. +//! +//! See [Section 12.8](https://rptl.io/rp2350-datasheet) of the datasheet for more details. + +use core::sync::atomic::{AtomicU8, Ordering}; +use fugit::{MicrosDurationU32, MicrosDurationU64, TimerInstantU64}; + +use crate::{ + atomic_register_access::{write_bitmask_clear, write_bitmask_set}, + clocks::ClocksManager, + pac, + resets::SubsystemReset, + typelevel::Sealed, +}; + +/// Instant type used by the Timer & Alarm methods. +pub type Instant = TimerInstantU64<1_000_000>; + +static ALARMS: AtomicU8 = AtomicU8::new(0x0F); +fn take_alarm(mask: u8) -> bool { + critical_section::with(|_| { + let alarms = ALARMS.load(Ordering::Relaxed); + ALARMS.store(alarms & !mask, Ordering::Relaxed); + (alarms & mask) != 0 + }) +} +fn release_alarm(mask: u8) { + critical_section::with(|_| { + let alarms = ALARMS.load(Ordering::Relaxed); + ALARMS.store(alarms | mask, Ordering::Relaxed); + }); +} + +/// Represents Timer0 +/// +/// But unlike the PAC object, we can copy this one when we duplicate the timer. +#[derive(Clone, Copy)] +pub struct CopyableTimer0 { + _inner: (), +} + +/// Represents Timer1 +/// +/// But unlike the PAC object, we can copy this one when we duplicate the timer. +#[derive(Clone, Copy)] +pub struct CopyableTimer1 { + _inner: (), +} + +/// Trait to handle both underlying devices (TIMER0 and TIMER1) +pub trait TimerDevice: Sealed + Clone + Copy + 'static { + /// Index of the Timer. + const ID: usize; + + /// Get a timer registerblock, pointing at the appropriate timer + fn get_perif() -> &'static pac::timer0::RegisterBlock { + if Self::ID == 0 { + unsafe { &*pac::TIMER0::ptr() } + } else { + unsafe { &*pac::TIMER1::ptr() } + } + } +} + +impl TimerDevice for CopyableTimer0 { + const ID: usize = 0; +} +impl Sealed for CopyableTimer0 {} +impl TimerDevice for CopyableTimer1 { + const ID: usize = 1; +} +impl Sealed for CopyableTimer1 {} + +/// Timer peripheral +// +// This struct logically wraps a `pac::TIMERx`, but doesn't actually store it: +// As after initialization all accesses are read-only anyways, the `pac::TIMER` can +// be summoned unsafely instead. This allows timer to be cloned. +// +// (Alarms do use write operations, but they are local to the respective alarm, and +// those are still owned singletons.) +// +// As the timer peripheral needs to be started first, this struct can only be +// constructed by calling `Timer::new(...)`. +#[derive(Copy, Clone)] +pub struct Timer { + _device: core::marker::PhantomData, +} + +impl Timer { + /// Create a new [`Timer`] using `TIMER0` + /// + /// Make sure that clocks and watchdog are configured, so + /// that timer ticks happen at a frequency of 1MHz. + /// Otherwise, `Timer` won't work as expected. + pub fn new_timer0( + timer: pac::TIMER0, + resets: &mut pac::RESETS, + _clocks: &ClocksManager, + ) -> Self { + timer.reset_bring_down(resets); + timer.reset_bring_up(resets); + Self { + _device: core::marker::PhantomData, + } + } +} + +impl Timer { + /// Create a new [`Timer`] using `TIMER1` + /// + /// Make sure that clocks and watchdog are configured, so + /// that timer ticks happen at a frequency of 1MHz. + /// Otherwise, `Timer` won't work as expected. + pub fn new_timer1( + timer: pac::TIMER1, + resets: &mut pac::RESETS, + _clocks: &ClocksManager, + ) -> Self { + timer.reset_bring_down(resets); + timer.reset_bring_up(resets); + Self { + _device: core::marker::PhantomData, + } + } +} + +impl Timer +where + D: TimerDevice, +{ + /// Get the current counter value. + pub fn get_counter(&self) -> Instant { + // Safety: Only used for reading current timer value + let timer = D::get_perif(); + let mut hi0 = timer.timerawh().read().bits(); + let timestamp = loop { + let low = timer.timerawl().read().bits(); + let hi1 = timer.timerawh().read().bits(); + if hi0 == hi1 { + break (u64::from(hi0) << 32) | u64::from(low); + } + hi0 = hi1; + }; + TimerInstantU64::from_ticks(timestamp) + } + + /// Get the value of the least significant word of the counter. + pub fn get_counter_low(&self) -> u32 { + // Safety: Only used for reading current timer value + let timer = D::get_perif(); + timer.timerawl().read().bits() + } + + /// Initialized a Count Down instance without starting it. + pub fn count_down(&self) -> CountDown<'_, D> { + CountDown { + timer: self, + period: MicrosDurationU64::nanos(0), + next_end: None, + } + } + /// Retrieve a reference to alarm 0. Will only return a value the first time this is called + pub fn alarm_0(&mut self) -> Option> { + take_alarm(1 << 0).then_some(Alarm0(*self)) + } + + /// Retrieve a reference to alarm 1. Will only return a value the first time this is called + pub fn alarm_1(&mut self) -> Option> { + take_alarm(1 << 1).then_some(Alarm1(*self)) + } + + /// Retrieve a reference to alarm 2. Will only return a value the first time this is called + pub fn alarm_2(&mut self) -> Option> { + take_alarm(1 << 2).then_some(Alarm2(*self)) + } + + /// Retrieve a reference to alarm 3. Will only return a value the first time this is called + pub fn alarm_3(&mut self) -> Option> { + take_alarm(1 << 3).then_some(Alarm3(*self)) + } + + /// Pauses execution for at minimum `us` microseconds. + fn delay_us_internal(&self, mut us: u32) { + let mut start = self.get_counter_low(); + // If we knew that the loop ran at least once per timer tick, + // this could be simplified to: + // ``` + // while timer.timelr().read().bits().wrapping_sub(start) <= us { + // crate::arch::nop(); + // } + // ``` + // However, due to interrupts, for `us == u32::MAX`, we could + // miss the moment where the loop should terminate if the loop skips + // a timer tick. + loop { + let now = self.get_counter_low(); + let waited = now.wrapping_sub(start); + if waited >= us { + break; + } + start = now; + us -= waited; + } + } +} + +macro_rules! impl_delay_traits { + ($($t:ty),+) => { + $( + impl embedded_hal_0_2::blocking::delay::DelayUs<$t> for Timer where D: TimerDevice { + fn delay_us(&mut self, us: $t) { + #![allow(unused_comparisons)] + assert!(us >= 0); // Only meaningful for i32 + self.delay_us_internal(us as u32) + } + } + impl embedded_hal_0_2::blocking::delay::DelayMs<$t> for Timer where D: TimerDevice { + fn delay_ms(&mut self, ms: $t) { + #![allow(unused_comparisons)] + assert!(ms >= 0); // Only meaningful for i32 + for _ in 0..ms { + self.delay_us_internal(1000); + } + } + } + )* + } +} + +// The implementation for i32 is a workaround to allow `delay_ms(42)` construction without specifying a type. +impl_delay_traits!(u8, u16, u32, i32); + +impl embedded_hal::delay::DelayNs for Timer +where + D: TimerDevice, +{ + fn delay_ns(&mut self, ns: u32) { + // For now, just use microsecond delay, internally. Of course, this + // might cause a much longer delay than necessary. So a more advanced + // implementation would be desirable for sub-microsecond delays. + let us = ns.div_ceil(1000); + self.delay_us_internal(us) + } + + fn delay_us(&mut self, us: u32) { + self.delay_us_internal(us) + } + + fn delay_ms(&mut self, ms: u32) { + for _ in 0..ms { + self.delay_us_internal(1000); + } + } +} + +/// Implementation of the [`embedded_hal_0_2::timer`] traits using [`rp235x_hal::timer`](crate::timer) counter. +/// +/// There is no Embedded HAL 1.0 equivalent at this time. +/// +/// If all you need is a delay, [`Timer`] does implement [`embedded_hal::delay::DelayNs`]. +/// +/// ## Usage +/// ```no_run +/// use embedded_hal_0_2::timer::{Cancel, CountDown}; +/// use fugit::ExtU32; +/// use rp235x_hal; +/// let mut pac = rp235x_hal::pac::Peripherals::take().unwrap(); +/// // Make sure to initialize clocks, otherwise the timer wouldn't work +/// // properly. Omitted here for terseness. +/// let clocks: rp235x_hal::clocks::ClocksManager = todo!(); +/// // Configure the Timer peripheral in count-down mode +/// let timer = rp235x_hal::Timer::new_timer0(pac.TIMER0, &mut pac.RESETS, &clocks); +/// let mut count_down = timer.count_down(); +/// // Create a count_down timer for 500 milliseconds +/// count_down.start(500.millis()); +/// // Block until timer has elapsed +/// let _ = nb::block!(count_down.wait()); +/// // Restart the count_down timer with a period of 100 milliseconds +/// count_down.start(100.millis()); +/// // Cancel it immediately +/// count_down.cancel(); +/// ``` +pub struct CountDown<'timer, D> +where + D: TimerDevice, +{ + timer: &'timer Timer, + period: MicrosDurationU64, + next_end: Option, +} + +impl embedded_hal_0_2::timer::CountDown for CountDown<'_, D> +where + D: TimerDevice, +{ + type Time = MicrosDurationU64; + + fn start(&mut self, count: T) + where + T: Into, + { + self.period = count.into(); + self.next_end = Some( + self.timer + .get_counter() + .ticks() + .wrapping_add(self.period.to_micros()), + ); + } + + fn wait(&mut self) -> nb::Result<(), void::Void> { + if let Some(end) = self.next_end { + let ts = self.timer.get_counter().ticks(); + if ts >= end { + self.next_end = Some(end.wrapping_add(self.period.to_micros())); + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } else { + panic!("CountDown is not running!"); + } + } +} + +impl embedded_hal_0_2::timer::Periodic for CountDown<'_, D> where D: TimerDevice {} + +impl embedded_hal_0_2::timer::Cancel for CountDown<'_, D> +where + D: TimerDevice, +{ + type Error = &'static str; + + fn cancel(&mut self) -> Result<(), Self::Error> { + if self.next_end.is_none() { + Err("CountDown is not running.") + } else { + self.next_end = None; + Ok(()) + } + } +} + +/// Alarm abstraction. +pub trait Alarm: Sealed { + /// Clear the interrupt flag. + /// + /// The interrupt is unable to trigger a 2nd time until this interrupt is cleared. + fn clear_interrupt(&mut self); + + /// Enable this alarm to trigger an interrupt. + /// + /// After this interrupt is triggered, make sure to clear the interrupt with [clear_interrupt]. + /// + /// [clear_interrupt]: #method.clear_interrupt + fn enable_interrupt(&mut self); + + /// Disable this alarm, preventing it from triggering an interrupt. + fn disable_interrupt(&mut self); + + /// Schedule the alarm to be finished after `countdown`. If [enable_interrupt] is called, + /// this will trigger interrupt whenever this time elapses. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule(&mut self, countdown: MicrosDurationU32) -> Result<(), ScheduleAlarmError>; + + /// Schedule the alarm to be finished at the given timestamp. If [enable_interrupt] is + /// called, this will trigger interrupt whenever this timestamp is reached. + /// + /// The rp235x is unable to schedule an event taking place in more than + /// `u32::MAX` microseconds. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule_at(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError>; + + /// Return true if this alarm is finished. The returned value is undefined if the alarm + /// has not been scheduled yet. + fn finished(&self) -> bool; + + /// Cancel an activated alarm. + fn cancel(&mut self) -> Result<(), ScheduleAlarmError>; +} + +macro_rules! impl_alarm { + ($name:ident { rb: $timer_alarm:ident, int: $int_alarm:ident, int_name: $int_name:tt, armed_bit_mask: $armed_bit_mask: expr }) => { + /// An alarm that can be used to schedule events in the future. Alarms can also be configured to trigger interrupts. + pub struct $name(Timer) + where + D: TimerDevice; + impl $name + where + D: TimerDevice, + { + fn schedule_internal(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError> { + let timestamp_low = (timestamp.ticks() & 0xFFFF_FFFF) as u32; + let timer = D::get_perif(); + + // This lock is for time-criticality + crate::arch::interrupt_free(|| { + let alarm = timer.$timer_alarm(); + + // safety: This is the only code in the codebase that accesses memory address $timer_alarm + alarm.write(|w| unsafe { w.bits(timestamp_low) }); + + // If it is not set, it has already triggered. + let now = self.0.get_counter(); + if now > timestamp && (timer.armed().read().bits() & $armed_bit_mask) != 0 { + // timestamp was set to a value in the past + + // safety: TIMER.armed is a write-clear register, and there can only be + // 1 instance of AlarmN so we can safely atomically clear this bit. + unsafe { + timer.armed().write_with_zero(|w| w.bits($armed_bit_mask)); + crate::atomic_register_access::write_bitmask_set( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + } + } + Ok(()) + }) + } + } + + impl Alarm for $name + where + D: TimerDevice, + { + /// Clear the interrupt flag. This should be called after interrupt ` + #[doc = $int_name] + /// ` is called. + /// + /// The interrupt is unable to trigger a 2nd time until this interrupt is cleared. + fn clear_interrupt(&mut self) { + // safety: TIMER.intr is a write-clear register, so we can atomically clear our interrupt + // by writing its value to this field + // Only one instance of this alarm index can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = D::get_perif(); + crate::atomic_register_access::write_bitmask_clear( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + timer + .intr() + .write_with_zero(|w| w.$int_alarm().clear_bit_by_one()); + } + } + + /// Enable this alarm to trigger an interrupt. This alarm will trigger ` + #[doc = $int_name] + /// `. + /// + /// After this interrupt is triggered, make sure to clear the interrupt with [clear_interrupt]. + /// + /// [clear_interrupt]: #method.clear_interrupt + fn enable_interrupt(&mut self) { + // safety: using the atomic set alias means we can atomically set our interrupt enable bit. + // Only one instance of this alarm can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = D::get_perif(); + let reg = timer.inte().as_ptr(); + write_bitmask_set(reg, $armed_bit_mask); + } + } + + /// Disable this alarm, preventing it from triggering an interrupt. + fn disable_interrupt(&mut self) { + // safety: using the atomic set alias means we can atomically clear our interrupt enable bit. + // Only one instance of this alarm can exist, and only this alarm interacts with this bit + // of the TIMER.inte register + unsafe { + let timer = D::get_perif(); + let reg = timer.inte().as_ptr(); + write_bitmask_clear(reg, $armed_bit_mask); + } + } + + /// Schedule the alarm to be finished after `countdown`. If [enable_interrupt] is called, + /// this will trigger interrupt ` + #[doc = $int_name] + /// ` whenever this time elapses. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule(&mut self, countdown: MicrosDurationU32) -> Result<(), ScheduleAlarmError> { + let timestamp = self.0.get_counter() + countdown; + self.schedule_internal(timestamp) + } + + /// Schedule the alarm to be finished at the given timestamp. If [enable_interrupt] is + /// called, this will trigger interrupt ` + #[doc = $int_name] + /// ` whenever this timestamp is reached. + /// + /// The rp235x is unable to schedule an event taking place in more than + /// `u32::MAX` microseconds. + /// + /// [enable_interrupt]: #method.enable_interrupt + fn schedule_at(&mut self, timestamp: Instant) -> Result<(), ScheduleAlarmError> { + let now = self.0.get_counter(); + let duration = timestamp.ticks().saturating_sub(now.ticks()); + if duration > u32::MAX.into() { + return Err(ScheduleAlarmError::AlarmTooLate); + } + + self.schedule_internal(timestamp) + } + + /// Return true if this alarm is finished. The returned value is undefined if the alarm + /// has not been scheduled yet. + fn finished(&self) -> bool { + // safety: This is a read action and should not have any UB + let timer = D::get_perif(); + let bits: u32 = timer.armed().read().bits(); + (bits & $armed_bit_mask) == 0 + } + + /// Cancel an activated Alarm. No negative effects if it's already disabled. + /// Unlike `timer::cancel` trait, this only cancels the alarm and keeps the timer running + /// if it's already active. + fn cancel(&mut self) -> Result<(), ScheduleAlarmError> { + unsafe { + let timer = D::get_perif(); + timer.armed().write_with_zero(|w| w.bits($armed_bit_mask)); + crate::atomic_register_access::write_bitmask_clear( + timer.intf().as_ptr(), + $armed_bit_mask, + ); + } + + Ok(()) + } + } + + impl Sealed for $name where D: TimerDevice {} + + impl Drop for $name + where + D: TimerDevice, + { + fn drop(&mut self) { + self.disable_interrupt(); + release_alarm($armed_bit_mask) + } + } + }; +} + +/// Errors that can be returned from any of the `AlarmX::schedule` methods. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ScheduleAlarmError { + /// Alarm time is too high. Should not be more than `u32::MAX` in the future. + AlarmTooLate, +} + +impl_alarm!(Alarm0 { + rb: alarm0, + int: alarm_0, + int_name: "TIMER_IRQ_0", + armed_bit_mask: 0b0001 +}); + +impl_alarm!(Alarm1 { + rb: alarm1, + int: alarm_1, + int_name: "TIMER_IRQ_1", + armed_bit_mask: 0b0010 +}); + +impl_alarm!(Alarm2 { + rb: alarm2, + int: alarm_2, + int_name: "TIMER_IRQ_2", + armed_bit_mask: 0b0100 +}); + +impl_alarm!(Alarm3 { + rb: alarm3, + int: alarm_3, + int_name: "TIMER_IRQ_3", + armed_bit_mask: 0b1000 +}); + +/// Support for RTIC monotonic trait. +#[cfg(feature = "rtic-monotonic")] +pub mod monotonic { + use super::{Alarm, Instant, Timer, TimerDevice}; + use fugit::ExtU32; + + /// RTIC Monotonic Implementation + pub struct Monotonic(pub Timer, A); + + impl Monotonic { + /// Creates a new monotonic. + pub const fn new(timer: Timer, alarm: A) -> Self { + Self(timer, alarm) + } + } + impl rtic_monotonic::Monotonic for Monotonic { + type Instant = Instant; + type Duration = fugit::MicrosDurationU64; + + const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false; + + fn now(&mut self) -> Instant { + self.0.get_counter() + } + + fn set_compare(&mut self, instant: Instant) { + // The alarm can only trigger up to 2^32 - 1 ticks in the future. + // So, if `instant` is more than 2^32 - 2 in the future, we use `max_instant` instead. + let max_instant = self.0.get_counter() + 0xFFFF_FFFE.micros(); + let wake_at = core::cmp::min(instant, max_instant); + + // Cannot fail + let _ = self.1.schedule_at(wake_at); + self.1.enable_interrupt(); + } + + fn clear_compare_flag(&mut self) { + self.1.clear_interrupt(); + } + + fn zero() -> Self::Instant { + Instant::from_ticks(0) + } + + unsafe fn reset(&mut self) {} + } +} diff --git a/rp235x-hal/src/typelevel.rs b/rp235x-hal/src/typelevel.rs new file mode 100644 index 000000000..24966fb7c --- /dev/null +++ b/rp235x-hal/src/typelevel.rs @@ -0,0 +1,98 @@ +//! Module supporting type-level programming +//! +//! This is heavily inspired by the work in [`atsamd-rs`](https://github.com/atsamd-rs/atsamd). Please refer to the +//! [documentation](https://docs.rs/atsamd-hal/0.15.1/atsamd_hal/typelevel/index.html) +//! over there for more details. + +mod private { + /// Super trait used to mark traits with an exhaustive set of + /// implementations + pub trait Sealed {} +} + +use core::borrow::{Borrow, BorrowMut}; + +pub(crate) use private::Sealed; + +impl Sealed for (A, B) {} +impl Sealed for (A, B, C) {} +impl Sealed for (A, B, C, D) {} + +impl Sealed for frunk::HNil {} +impl Sealed for frunk::HCons {} + +/// Marker trait for type identity +/// +/// This trait is used as part of the [`AnyKind`] trait pattern. It represents +/// the concept of type identity, because all implementors have +/// `::Type == Self`. When used as a trait bound with a specific +/// type, it guarantees that the corresponding type parameter is exactly the +/// specific type. Stated differently, it guarantees that `T == Specific` in +/// the following example. +/// +/// ```text +/// where T: Is +/// ``` +/// +/// Moreover, the super traits guarantee that any instance of or reference to a +/// type `T` can be converted into the `Specific` type. +/// +/// ``` +/// # use rp235x_hal::typelevel::Is; +/// # struct Specific; +/// fn example(mut any: T) +/// where +/// T: Is, +/// { +/// let specific_mut: &mut Specific = any.borrow_mut(); +/// let specific_ref: &Specific = any.borrow(); +/// let specific: Specific = any.into(); +/// } +/// ``` +/// +/// [`AnyKind`]: https://docs.rs/atsamd-hal/0.15.1/atsamd_hal/typelevel/index.html#anykind-trait-pattern +pub trait Is +where + Self: Sealed, + Self: From>, + Self: Into>, + Self: Borrow>, + Self: BorrowMut>, +{ + #[allow(missing_docs)] + type Type; +} + +/// Type alias for [`Is::Type`] +pub type IsType = ::Type; + +impl Is for T +where + T: Sealed + Borrow + BorrowMut, +{ + type Type = T; +} + +// ===================== +// Type level option +// ===================== + +/// Type-level `enum` for Option. +pub trait OptionT: Sealed { + /// Is this Some or None ? + const IS_SOME: bool; +} + +/// Type-level variant for `OptionT` +pub struct OptionTNone; +impl Sealed for OptionTNone {} +impl OptionT for OptionTNone { + const IS_SOME: bool = false; +} + +/// Type-level variant for `OptionT` +pub struct OptionTSome(pub T); +impl Sealed for OptionTSome {} +impl OptionT for OptionTSome { + const IS_SOME: bool = true; +} diff --git a/rp235x-hal/src/uart/common_configs.rs b/rp235x-hal/src/uart/common_configs.rs new file mode 100644 index 000000000..b36445413 --- /dev/null +++ b/rp235x-hal/src/uart/common_configs.rs @@ -0,0 +1,2 @@ +#[doc(inline)] +pub use rp_hal_common::uart::common_configs::*; diff --git a/rp235x-hal/src/uart/mod.rs b/rp235x-hal/src/uart/mod.rs new file mode 100644 index 000000000..4f5efe932 --- /dev/null +++ b/rp235x-hal/src/uart/mod.rs @@ -0,0 +1,72 @@ +//! Universal Asynchronous Receiver Transmitter (UART) +//! +//! See [Section 12.1](https://rptl.io/rp2350-datasheet#section_uart) of the datasheet for more details. +//! +//! ## Usage +//! +//! See [examples/uart.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/uart.rs) for a more complete example. +//! +//! ```no_run +//! use fugit::RateExtU32; +//! use rp235x_hal::{ +//! self as hal, +//! clocks::init_clocks_and_plls, +//! gpio::{FunctionUart, Pins}, +//! pac, +//! sio::Sio, +//! uart::{self, DataBits, StopBits, UartConfig, UartPeripheral}, +//! watchdog::Watchdog, +//! Clock, +//! }; +//! +//! const XOSC_CRYSTAL_FREQ: u32 = 12_000_000; // Typically found in BSP crates +//! +//! let mut peripherals = hal::pac::Peripherals::take().unwrap(); +//! let sio = Sio::new(peripherals.SIO); +//! let pins = Pins::new( +//! peripherals.IO_BANK0, +//! peripherals.PADS_BANK0, +//! sio.gpio_bank0, +//! &mut peripherals.RESETS, +//! ); +//! let mut watchdog = Watchdog::new(peripherals.WATCHDOG); +//! let mut clocks = init_clocks_and_plls( +//! XOSC_CRYSTAL_FREQ, +//! peripherals.XOSC, +//! peripherals.CLOCKS, +//! peripherals.PLL_SYS, +//! peripherals.PLL_USB, +//! &mut peripherals.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! +//! // Set up UART on GP0 and GP1 (Pico pins 1 and 2) +//! let pins = (pins.gpio0.into_function(), pins.gpio1.into_function()); +//! // Need to perform clock init before using UART or it will freeze. +//! let uart = UartPeripheral::new(peripherals.UART0, pins, &mut peripherals.RESETS) +//! .enable( +//! UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), +//! clocks.peripheral_clock.freq(), +//! ) +//! .unwrap(); +//! +//! uart.write_full_blocking(b"Hello World!\r\n"); +//! ``` + +mod peripheral; +mod pins; +mod reader; +mod utils; +mod writer; + +pub use peripheral::UartPeripheral; +pub use pins::*; +pub use reader::{ReadError, ReadErrorType, Reader}; +pub use utils::*; +pub use writer::Writer; + +/// Common configurations for UART. +#[deprecated(note = "Use UartConfig::new(...) instead.")] +pub mod common_configs; diff --git a/rp235x-hal/src/uart/peripheral.rs b/rp235x-hal/src/uart/peripheral.rs new file mode 100644 index 000000000..89fb74000 --- /dev/null +++ b/rp235x-hal/src/uart/peripheral.rs @@ -0,0 +1,484 @@ +//! Universal Asynchronous Receiver Transmitter - Bi-directional Peripheral Code +//! +//! This module brings together `uart::reader` and `uart::writer` to give a +//! UartPeripheral object that can both read and write. + +use core::{convert::Infallible, fmt}; +use embedded_hal_0_2::serial as eh0; +use fugit::HertzU32; +use nb::Error::{Other, WouldBlock}; + +use crate::{ + pac::{self, uart0::uartlcr_h::W as UART_LCR_H_Writer, Peripherals, UART0, UART1}, + typelevel::OptionT, + uart::*, +}; + +use embedded_hal_nb::serial::{ErrorType, Read, Write}; + +/// An UART Peripheral based on an underlying UART device. +pub struct UartPeripheral> { + device: D, + _state: S, + pins: P, +} + +impl> UartPeripheral { + fn transition(self, state: To) -> UartPeripheral { + UartPeripheral { + device: self.device, + pins: self.pins, + _state: state, + } + } + + /// Releases the underlying device and pins. + pub fn free(self) -> (D, P) { + (self.device, self.pins) + } +} + +impl> UartPeripheral { + /// Creates an UartPeripheral in Disabled state. + pub fn new(device: D, pins: P, resets: &mut pac::RESETS) -> UartPeripheral { + device.reset_bring_down(resets); + device.reset_bring_up(resets); + + UartPeripheral { + device, + _state: Disabled, + pins, + } + } + + /// Enables the provided UART device with the given configuration. + pub fn enable( + self, + config: UartConfig, + frequency: HertzU32, + ) -> Result, Error> { + let (mut device, pins) = self.free(); + configure_baudrate(&mut device, config.baudrate, frequency)?; + + device.uartlcr_h().write(|w| { + // FIFOs are enabled + w.fen().set_bit(); // Leaved here for backward compatibility + set_format(w, &config.data_bits, &config.stop_bits, &config.parity); + w + }); + + // Enable the UART, and the TX,RC,CTS and RTS based on the pins + device.uartcr().write(|w| { + w.uarten().set_bit(); + w.txe().bit(P::Tx::IS_SOME); + w.rxe().bit(P::Rx::IS_SOME); + w.ctsen().bit(P::Cts::IS_SOME); + w.rtsen().bit(P::Rts::IS_SOME); + + w + }); + + device.uartdmacr().write(|w| { + w.txdmae().set_bit(); + w.rxdmae().set_bit(); + w + }); + + Ok(UartPeripheral { + device, + pins, + _state: Enabled, + }) + } +} + +impl> UartPeripheral { + /// Disable this UART Peripheral, falling back to the Disabled state. + pub fn disable(self) -> UartPeripheral { + // Disable the UART, both TX and RX + self.device.uartcr().write(|w| { + w.uarten().clear_bit(); + w.txe().clear_bit(); + w.rxe().clear_bit(); + w.ctsen().clear_bit(); + w.rtsen().clear_bit(); + w + }); + + self.transition(Disabled) + } + + /// Enable/disable the rx/tx FIFO + /// + /// Unfortunately, it's not possible to enable/disable rx/tx + /// independently on this chip + /// Default is false + pub fn set_fifos(&mut self, enable: bool) { + super::reader::set_fifos(&self.device, enable) + } + + /// Set rx FIFO watermark + /// + /// See DS: Table 423 + pub fn set_rx_watermark(&mut self, watermark: FifoWatermark) { + super::reader::set_rx_watermark(&self.device, watermark) + } + + /// Set tx FIFO watermark + /// + /// See DS: Table 423 + pub fn set_tx_watermark(&mut self, watermark: FifoWatermark) { + super::writer::set_tx_watermark(&self.device, watermark) + } + + /// Enables the Receive Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is data in the receive register. + pub fn enable_rx_interrupt(&mut self) { + super::reader::enable_rx_interrupt(&self.device) + } + + /// Enables the Transmit Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. + pub fn enable_tx_interrupt(&mut self) { + super::writer::enable_tx_interrupt(&self.device) + } + + /// Disables the Receive Interrupt. + pub fn disable_rx_interrupt(&mut self) { + super::reader::disable_rx_interrupt(&self.device) + } + + /// Disables the Transmit Interrupt. + pub fn disable_tx_interrupt(&mut self) { + super::writer::disable_tx_interrupt(&self.device) + } + + /// Is there space in the UART TX FIFO for new data to be written? + pub fn uart_is_writable(&self) -> bool { + super::writer::uart_is_writable(&self.device) + } + + /// Is the UART still busy transmitting data? + pub fn uart_is_busy(&self) -> bool { + super::writer::uart_is_busy(&self.device) + } + + /// Is there data in the UART RX FIFO ready to be read? + pub fn uart_is_readable(&self) -> bool { + super::reader::is_readable(&self.device) + } + + /// Writes bytes to the UART. + /// This function writes as long as it can. As soon that the FIFO is full, if : + /// - 0 bytes were written, a WouldBlock Error is returned + /// - some bytes were written, it is deemed to be a success + /// + /// Upon success, the remaining slice is returned. + pub fn write_raw<'d>(&self, data: &'d [u8]) -> nb::Result<&'d [u8], Infallible> { + super::writer::write_raw(&self.device, data) + } + + /// Reads bytes from the UART. + /// This function reads as long as it can. As soon that the FIFO is empty, if : + /// - 0 bytes were read, a WouldBlock Error is returned + /// - some bytes were read, it is deemed to be a success + /// + /// Upon success, it will return how many bytes were read. + pub fn read_raw<'b>(&self, buffer: &'b mut [u8]) -> nb::Result> { + super::reader::read_raw(&self.device, buffer) + } + + /// Writes bytes to the UART. + /// + /// This function blocks until the full buffer has been sent. + pub fn write_full_blocking(&self, data: &[u8]) { + super::writer::write_full_blocking(&self.device, data); + } + + /// Reads bytes from the UART. + /// + /// This function blocks until the full buffer has been received. + pub fn read_full_blocking(&self, buffer: &mut [u8]) -> Result<(), ReadErrorType> { + super::reader::read_full_blocking(&self.device, buffer) + } + + /// Initiates a break + /// + /// If transmitting, this takes effect immediately after the current byte has completed. + /// For proper execution of the break command, this must be held for at least 2 complete frames + /// worth of time. + /// + ///

+ /// + /// # Example + /// + /// ```no_run + /// # use rp235x_hal::uart::{Pins, ValidUartPinout, Enabled, UartPeripheral}; + /// # use rp235x_hal::pac::UART0; + /// # use rp235x_hal::timer::{Timer, CopyableTimer0}; + /// # use rp235x_hal::typelevel::OptionTNone; + /// # use embedded_hal_0_2::blocking::delay::DelayUs; + /// # type PINS = Pins; + /// # fn example(mut serial: UartPeripheral, mut timer: Timer) { + /// serial.lowlevel_break_start(); + /// // at 115_200Bps on 8N1 configuration, 20bits takes (20*10⁶)/115200 = 173.611…μs. + /// timer.delay_us(175); + /// serial.lowlevel_break_stop(); + /// } + /// ``` + pub fn lowlevel_break_start(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().set_bit()); + } + + /// Terminates a break condition. + /// + /// See `lowlevel_break_start` for more details. + pub fn lowlevel_break_stop(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().clear_bit()); + } + + /// Join the reader and writer halves together back into the original Uart peripheral. + /// + /// A reader/writer pair can be obtained by calling [`split`]. + /// + /// [`split`]: #method.split + pub fn join(reader: Reader, writer: Writer) -> Self { + let _ = writer; + Self { + device: reader.device, + _state: Enabled, + pins: reader.pins, + } + } +} + +impl> UartPeripheral { + /// Split this peripheral into a separate reader and writer. + pub fn split(self) -> (Reader, Writer) { + let reader = Reader { + device: self.device, + pins: self.pins, + }; + // Safety: reader and writer will never write to the same address + let device_copy = unsafe { Peripherals::steal().UART0 }; + let writer = Writer { + device: device_copy, + device_marker: core::marker::PhantomData, + pins: core::marker::PhantomData, + }; + (reader, writer) + } +} + +impl> UartPeripheral { + /// Split this peripheral into a separate reader and writer. + pub fn split(self) -> (Reader, Writer) { + let reader = Reader { + device: self.device, + pins: self.pins, + }; + // Safety: reader and writer will never write to the same address + let device_copy = unsafe { Peripherals::steal().UART1 }; + let writer = Writer { + device: device_copy, + device_marker: core::marker::PhantomData, + pins: core::marker::PhantomData, + }; + (reader, writer) + } +} + +/// The PL011 (PrimeCell UART) supports a fractional baud rate divider +/// From the wanted baudrate, we calculate the divider's two parts: integer and fractional parts. +/// Code inspired from the C SDK. +fn calculate_baudrate_dividers( + wanted_baudrate: HertzU32, + frequency: HertzU32, +) -> Result<(u16, u16), Error> { + // See [Section 12.1.7.1](https://rptl.io/rp2350-datasheet#section_uart) + // of the RP2350 datasheet for an explanation of how baudrate is calculated + let baudrate_div = frequency + .to_Hz() + .checked_mul(8) + .and_then(|r| r.checked_div(wanted_baudrate.to_Hz())) + .ok_or(Error::BadArgument)?; + + Ok(match (baudrate_div >> 7, ((baudrate_div & 0x7F) + 1) / 2) { + (0, _) => (1, 0), + + (int_part, _) if int_part >= 65535 => (65535, 0), + + (int_part, frac_part) => (int_part as u16, frac_part as u16), + }) +} + +/// Baudrate configuration. Code loosely inspired from the C SDK. +#[allow(unknown_lints)] +#[allow(clippy::needless_pass_by_ref_mut)] +fn configure_baudrate( + device: &mut U, + wanted_baudrate: HertzU32, + frequency: HertzU32, +) -> Result { + let (baud_div_int, baud_div_frac) = calculate_baudrate_dividers(wanted_baudrate, frequency)?; + + // First we load the integer part of the divider. + device.uartibrd().write(|w| unsafe { + w.baud_divint().bits(baud_div_int); + w + }); + + // Then we load the fractional part of the divider. + device.uartfbrd().write(|w| unsafe { + w.baud_divfrac().bits(baud_div_frac as u8); + w + }); + + // PL011 needs a (dummy) line control register write to latch in the + // divisors. We don't want to actually change LCR contents here. + device.uartlcr_h().modify(|_, w| w); + + Ok(HertzU32::from_raw( + (4 * frequency.to_Hz()) / (64 * baud_div_int as u32 + baud_div_frac as u32), + )) +} + +/// Format configuration. Code loosely inspired from the C SDK. +fn set_format<'w>( + w: &'w mut UART_LCR_H_Writer, + data_bits: &DataBits, + stop_bits: &StopBits, + parity: &Option, +) -> &'w mut UART_LCR_H_Writer { + match parity { + Some(p) => { + w.pen().set_bit(); + match p { + Parity::Odd => w.eps().clear_bit(), + Parity::Even => w.eps().set_bit(), + }; + } + None => { + w.pen().bit(false); + } + }; + + unsafe { + w.wlen().bits(match data_bits { + DataBits::Five => 0b00, + DataBits::Six => 0b01, + DataBits::Seven => 0b10, + DataBits::Eight => 0b11, + }) + }; + + match stop_bits { + StopBits::One => w.stp2().clear_bit(), + StopBits::Two => w.stp2().set_bit(), + }; + + w +} + +impl> eh0::Read for UartPeripheral { + type Error = ReadErrorType; + + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +impl> ErrorType for UartPeripheral { + type Error = ReadErrorType; +} + +impl> Read for UartPeripheral { + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +impl> eh0::Write for UartPeripheral { + type Error = Infallible; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + super::writer::transmit_flushed(&self.device) + } +} + +impl> Write for UartPeripheral { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + super::writer::transmit_flushed(&self.device).map_err(|e| match e { + WouldBlock => WouldBlock, + Other(v) => match v {}, + }) + } +} + +impl> fmt::Write for UartPeripheral { + fn write_str(&mut self, s: &str) -> fmt::Result { + s.bytes() + .try_for_each(|c| nb::block!(self.write(c))) + .map_err(|_| fmt::Error) + } +} + +impl embedded_io::Error for ReadErrorType { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} +impl> embedded_io::ErrorType + for UartPeripheral +{ + type Error = ReadErrorType; +} +impl> embedded_io::Read for UartPeripheral { + fn read(&mut self, buf: &mut [u8]) -> Result { + nb::block!(self.read_raw(buf)).map_err(|e| e.err_type) + } +} +impl> embedded_io::Write for UartPeripheral { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_full_blocking(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(super::writer::transmit_flushed(&self.device)).unwrap(); // Infallible + Ok(()) + } +} diff --git a/rp235x-hal/src/uart/pins.rs b/rp235x-hal/src/uart/pins.rs new file mode 100644 index 000000000..ed1b40a2d --- /dev/null +++ b/rp235x-hal/src/uart/pins.rs @@ -0,0 +1,340 @@ +use core::marker::PhantomData; + +use crate::gpio::{ + bank0::*, pin::pin_sealed::TypeLevelPinId, AnyPin, DynFunction, FunctionUart, FunctionUartAux, + PullType, +}; +use crate::pac::{UART0, UART1}; +use crate::typelevel::{OptionT, OptionTNone, OptionTSome, Sealed}; + +use super::UartDevice; + +// All type level checked pins are inherently valid. +macro_rules! pin_validation { + ($p:ident) => { + paste::paste!{ + #[doc = "Indicates a valid " $p " pin the given UART"] + pub trait []: Sealed {} + + #[doc = "A runtime validated " $p " pin for the given UART."] + pub struct [](P, PhantomData); + + impl Sealed for [] {} + + impl [] + where + P: AnyPin, + U: UartDevice, + { + /// Validate a pin's function on a uart peripheral. + /// + #[doc = "Will err if the pin cannot be used as a " $p " pin for that Uart."] + pub fn validate(p: P, _u: &U) -> Result { + if [<$p:upper>].contains(&(p.borrow().id().num, U::ID, p.borrow().function())) && + p.borrow().id().bank == crate::gpio::DynBankId::Bank0 { + Ok(Self(p, PhantomData)) + } else { + Err(p) + } + } + } + + #[doc = "Indicates a valid optional " $p " pin for UART0 or UART1"] + pub trait []: OptionT {} + + impl [] for OptionTNone {} + impl [] for OptionTSome + where + U: UartDevice, + T: [], + { + } + } + }; + ($($p:ident),*) => { + $( + pin_validation!($p); + )* + }; +} +pin_validation!(Tx, Rx, Cts, Rts); + +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} + +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} +impl

ValidPinTx for crate::gpio::Pin where P: PullType {} + +impl ValidPinTx for ValidatedPinTx +where + P: AnyPin, + UART: UartDevice, +{ +} + +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} + +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} +impl

ValidPinRx for crate::gpio::Pin where P: PullType {} + +impl ValidPinRx for ValidatedPinRx +where + P: AnyPin, + UART: UartDevice, +{ +} + +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} + +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} +impl

ValidPinCts for crate::gpio::Pin where P: PullType {} + +impl ValidPinCts for ValidatedPinCts +where + P: AnyPin, + UART: UartDevice, +{ +} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} + +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} +impl

ValidPinRts for crate::gpio::Pin where P: PullType {} + +impl ValidPinRts for ValidatedPinRts +where + P: AnyPin, + UART: UartDevice, +{ +} + +const TX: &[(u8, usize, DynFunction)] = &[ + (Gpio0::ID.num, UART0::ID, DynFunction::Uart), + (Gpio12::ID.num, UART0::ID, DynFunction::Uart), + (Gpio16::ID.num, UART0::ID, DynFunction::Uart), + (Gpio28::ID.num, UART0::ID, DynFunction::Uart), + (Gpio4::ID.num, UART1::ID, DynFunction::Uart), + (Gpio8::ID.num, UART1::ID, DynFunction::Uart), + (Gpio20::ID.num, UART1::ID, DynFunction::Uart), + (Gpio24::ID.num, UART1::ID, DynFunction::Uart), + (Gpio2::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio14::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio18::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio6::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio10::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio22::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio26::ID.num, UART1::ID, DynFunction::UartAux), +]; + +const RX: &[(u8, usize, DynFunction)] = &[ + (Gpio1::ID.num, UART0::ID, DynFunction::Uart), + (Gpio13::ID.num, UART0::ID, DynFunction::Uart), + (Gpio17::ID.num, UART0::ID, DynFunction::Uart), + (Gpio29::ID.num, UART0::ID, DynFunction::Uart), + (Gpio5::ID.num, UART1::ID, DynFunction::Uart), + (Gpio9::ID.num, UART1::ID, DynFunction::Uart), + (Gpio21::ID.num, UART1::ID, DynFunction::Uart), + (Gpio25::ID.num, UART1::ID, DynFunction::Uart), + (Gpio3::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio15::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio19::ID.num, UART0::ID, DynFunction::UartAux), + (Gpio7::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio11::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio23::ID.num, UART1::ID, DynFunction::UartAux), + (Gpio27::ID.num, UART1::ID, DynFunction::UartAux), +]; + +const CTS: &[(u8, usize, DynFunction)] = &[ + (Gpio2::ID.num, UART0::ID, DynFunction::Uart), + (Gpio14::ID.num, UART0::ID, DynFunction::Uart), + (Gpio18::ID.num, UART0::ID, DynFunction::Uart), + (Gpio6::ID.num, UART1::ID, DynFunction::Uart), + (Gpio10::ID.num, UART1::ID, DynFunction::Uart), + (Gpio22::ID.num, UART1::ID, DynFunction::Uart), + (Gpio26::ID.num, UART1::ID, DynFunction::Uart), +]; + +const RTS: &[(u8, usize, DynFunction)] = &[ + (Gpio3::ID.num, UART0::ID, DynFunction::Uart), + (Gpio15::ID.num, UART0::ID, DynFunction::Uart), + (Gpio19::ID.num, UART0::ID, DynFunction::Uart), + (Gpio7::ID.num, UART1::ID, DynFunction::Uart), + (Gpio11::ID.num, UART1::ID, DynFunction::Uart), + (Gpio23::ID.num, UART1::ID, DynFunction::Uart), + (Gpio27::ID.num, UART1::ID, DynFunction::Uart), +]; + +/// Declares a valid UART pinout. +pub trait ValidUartPinout: Sealed { + #[allow(missing_docs)] + type Rx: ValidOptionRx; + #[allow(missing_docs)] + type Tx: ValidOptionTx; + #[allow(missing_docs)] + type Cts: ValidOptionCts; + #[allow(missing_docs)] + type Rts: ValidOptionRts; +} + +impl ValidUartPinout for (Tx, Rx) +where + Uart: UartDevice, + Tx: ValidPinTx, + Rx: ValidPinRx, +{ + type Tx = OptionTSome; + type Rx = OptionTSome; + type Cts = OptionTNone; + type Rts = OptionTNone; +} + +impl ValidUartPinout for (Tx, Rx, Cts, Rts) +where + Uart: UartDevice, + Tx: ValidPinTx, + Rx: ValidPinRx, + Cts: ValidPinCts, + Rts: ValidPinRts, +{ + type Rx = OptionTSome; + type Tx = OptionTSome; + type Cts = OptionTSome; + type Rts = OptionTSome; +} + +/// Customizable Uart pinout, allowing you to set the pins individually. +/// +/// The following pins are valid UART pins: +/// +/// |UART | TX | RX |CTS (or TX in Aux mode)|RTS (or RX in Aux mode)| +/// |-----|-------------|-------------|-----------------------|-----------------------| +/// |UART0|0, 12, 16, 28|1, 13, 17, 29|2, 14, 18 |3, 15, 19 | +/// |UART1|4, 8, 20, 24 |5, 9, 21, 25 |6, 10, 22, 26 |7, 11, 23, 27 | +/// +/// The RP235x allows you to use CTS pins as TX pins by using the +/// `FunctionUartAux` pin function (instead of `FunctionUart`). The same goes +/// for using RTS pins as RX pins. +/// +/// Every field can be set to [`OptionTNone`] to not configure them. +/// +/// Note that you can also use tuples `(RX, TX)` or `(RX, TX, CTS, RTS)` instead of this type. +/// +/// This struct can either be filled manually or with a builder pattern: +/// +/// ```no_run +/// # use rp235x_hal::uart::{Pins, ValidUartPinout}; +/// # use rp235x_hal::pac::UART0; +/// # let gpio_pins: rp235x_hal::gpio::Pins = unsafe { core::mem::zeroed() }; +/// let pins = Pins::default() +/// .tx(gpio_pins.gpio0.into_function()) +/// .rx(gpio_pins.gpio1.into_function()); +/// +/// fn assert_is_valid_uart0>(_: T) {} +/// +/// assert_is_valid_uart0(pins); +/// ``` +pub struct Pins { + #[allow(missing_docs)] + pub tx: Tx, + #[allow(missing_docs)] + pub rx: Rx, + #[allow(missing_docs)] + pub cts: Cts, + #[allow(missing_docs)] + pub rts: Rts, +} + +impl Default for Pins { + fn default() -> Self { + Self { + tx: OptionTNone, + rx: OptionTNone, + rts: OptionTNone, + cts: OptionTNone, + } + } +} + +impl Pins { + /// Set the TX pin + pub fn tx(self, tx: NewTx) -> Pins, Rx, Cts, Rts> { + Pins { + tx: OptionTSome(tx), + rx: self.rx, + rts: self.rts, + cts: self.cts, + } + } + /// Set the RX pin + pub fn rx(self, rx: NewRx) -> Pins, Cts, Rts> { + Pins { + tx: self.tx, + rx: OptionTSome(rx), + rts: self.rts, + cts: self.cts, + } + } + /// Set the CTS pin + pub fn cts(self, cts: NewCts) -> Pins, Rts> { + Pins { + tx: self.tx, + rx: self.rx, + rts: self.rts, + cts: OptionTSome(cts), + } + } + /// Set the RTS pin + pub fn rts(self, rts: NewRts) -> Pins> { + Pins { + tx: self.tx, + rx: self.rx, + rts: OptionTSome(rts), + cts: self.cts, + } + } +} + +impl Sealed for Pins {} +impl ValidUartPinout for Pins +where + Uart: UartDevice, + Tx: ValidOptionTx, + Rx: ValidOptionRx, + Cts: ValidOptionCts, + Rts: ValidOptionRts, +{ + type Rx = Rx; + type Tx = Tx; + type Cts = Cts; + type Rts = Rts; +} diff --git a/rp235x-hal/src/uart/reader.rs b/rp235x-hal/src/uart/reader.rs new file mode 100644 index 000000000..6126a4fcd --- /dev/null +++ b/rp235x-hal/src/uart/reader.rs @@ -0,0 +1,287 @@ +//! Universal Asynchronous Receiver Transmitter - Receiver Code +//! +//! This module is for receiving data with a UART. + +use super::{FifoWatermark, UartDevice, ValidUartPinout}; +use crate::dma::{EndlessReadTarget, ReadTarget}; +use crate::pac::uart0::RegisterBlock; +use embedded_hal_0_2::serial::Read as Read02; +use nb::Error::*; + +use embedded_hal_nb::serial::{Error, ErrorKind, ErrorType, Read}; + +/// When there's a read error. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub struct ReadError<'err> { + /// The type of error + pub err_type: ReadErrorType, + + /// Reference to the data that was read but eventually discarded because of the error. + pub discarded: &'err [u8], +} + +/// Possible types of read errors. +/// +/// See [Section 12.1.8](https://rptl.io/rp2350-datasheet#section_uart) "UARTDR +/// Register" +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug)] +pub enum ReadErrorType { + /// Triggered when the FIFO (or shift-register) is overflowed. + Overrun, + + /// Triggered when a break is received + Break, + + /// Triggered when there is a parity mismatch between what's received and our settings. + Parity, + + /// Triggered when the received character didn't have a valid stop bit. + Framing, +} + +impl Error for ReadErrorType { + fn kind(&self) -> ErrorKind { + match self { + ReadErrorType::Overrun => ErrorKind::Overrun, + ReadErrorType::Break => ErrorKind::Other, + ReadErrorType::Parity => ErrorKind::Parity, + ReadErrorType::Framing => ErrorKind::FrameFormat, + } + } +} + +pub(crate) fn is_readable(device: &D) -> bool { + device.uartfr().read().rxfe().bit_is_clear() +} + +/// Enable/disable the rx/tx FIFO +/// +/// Unfortunately, it's not possible to enable/disable rx/tx +/// independently on this chip +/// Default is false +pub fn set_fifos(rb: &RegisterBlock, enable: bool) { + if enable { + rb.uartlcr_h().modify(|_r, w| w.fen().set_bit()) + } else { + rb.uartlcr_h().modify(|_r, w| w.fen().clear_bit()) + } +} + +/// Set rx FIFO watermark +/// +/// See DS: Table 423 +pub fn set_rx_watermark(rb: &RegisterBlock, watermark: FifoWatermark) { + let wm = match watermark { + FifoWatermark::Bytes4 => 0, + FifoWatermark::Bytes8 => 1, + FifoWatermark::Bytes16 => 2, + FifoWatermark::Bytes24 => 3, + FifoWatermark::Bytes28 => 4, + }; + rb.uartifls() + .modify(|_r, w| unsafe { w.rxiflsel().bits(wm) }); +} + +/// Enables the Receive Interrupt. +/// +/// The relevant UARTx IRQ will fire when there is data in the receive register. +pub(crate) fn enable_rx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // high enables the interrupt. + + // We set the RX interrupt, and the RX Timeout interrupt. This means + // we will get an interrupt when the RX FIFO level is triggered, or + // when the RX FIFO is non-empty, but 32-bit periods have passed with + // no further data. This means we don't have to interrupt on every + // single byte, but can make use of the hardware FIFO. + rb.uartimsc().modify(|_r, w| { + w.rxim().set_bit(); + w.rtim().set_bit(); + w + }); +} + +/// Disables the Receive Interrupt. +pub(crate) fn disable_rx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // low disables the interrupt. + + rb.uartimsc().modify(|_r, w| { + w.rxim().clear_bit(); + w.rtim().clear_bit(); + w + }); +} + +pub(crate) fn read_raw<'b, D: UartDevice>( + device: &D, + buffer: &'b mut [u8], +) -> nb::Result> { + let mut bytes_read = 0; + + Ok(loop { + if !is_readable(device) { + if bytes_read == 0 { + return Err(WouldBlock); + } else { + break bytes_read; + } + } + + if bytes_read < buffer.len() { + let mut error: Option = None; + + let read = device.uartdr().read(); + + // If multiple status bits are set, report + // the most serious or most specific condition, + // in the following order of precedence: + // overrun > break > parity > framing + if read.oe().bit_is_set() { + error = Some(ReadErrorType::Overrun); + } else if read.be().bit_is_set() { + error = Some(ReadErrorType::Break); + } else if read.pe().bit_is_set() { + error = Some(ReadErrorType::Parity); + } else if read.fe().bit_is_set() { + error = Some(ReadErrorType::Framing); + } + + if let Some(err_type) = error { + return Err(Other(ReadError { + err_type, + discarded: &buffer[..bytes_read], + })); + } + + buffer[bytes_read] = read.data().bits(); + bytes_read += 1; + } else { + break bytes_read; + } + }) +} + +pub(crate) fn read_full_blocking( + device: &D, + buffer: &mut [u8], +) -> Result<(), ReadErrorType> { + let mut offset = 0; + + while offset != buffer.len() { + offset += match read_raw(device, &mut buffer[offset..]) { + Ok(bytes_read) => bytes_read, + Err(e) => match e { + Other(inner) => return Err(inner.err_type), + WouldBlock => continue, + }, + } + } + + Ok(()) +} + +/// Half of an [`UartPeripheral`] that is only capable of reading. Obtained by calling [`UartPeripheral::split()`] +/// +/// [`UartPeripheral`]: struct.UartPeripheral.html +/// [`UartPeripheral::split()`]: struct.UartPeripheral.html#method.split +pub struct Reader> { + pub(super) device: D, + pub(super) pins: P, +} + +impl> Reader { + /// Reads bytes from the UART. + /// This function reads as long as it can. As soon that the FIFO is empty, if : + /// - 0 bytes were read, a WouldBlock Error is returned + /// - some bytes were read, it is deemed to be a success + /// + /// Upon success, it will return how many bytes were read. + pub fn read_raw<'b>(&self, buffer: &'b mut [u8]) -> nb::Result> { + read_raw(&self.device, buffer) + } + + /// Reads bytes from the UART. + /// This function blocks until the full buffer has been received. + pub fn read_full_blocking(&self, buffer: &mut [u8]) -> Result<(), ReadErrorType> { + read_full_blocking(&self.device, buffer) + } + + /// Enables the Receive Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is data in the receive register. + pub fn enable_rx_interrupt(&mut self) { + enable_rx_interrupt(&self.device) + } + + /// Disables the Receive Interrupt. + pub fn disable_rx_interrupt(&mut self) { + disable_rx_interrupt(&self.device) + } +} + +impl> embedded_io::ErrorType for Reader { + type Error = ReadErrorType; +} + +impl> embedded_io::Read for Reader { + fn read(&mut self, buf: &mut [u8]) -> Result { + nb::block!(self.read_raw(buf)).map_err(|e| e.err_type) + } +} + +impl> Read02 for Reader { + type Error = ReadErrorType; + + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} + +// Safety: This only reads from the RX fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl> ReadTarget for Reader { + type ReceivedWord = u8; + + fn rx_treq() -> Option { + Some(D::rx_dreq()) + } + + fn rx_address_count(&self) -> (u32, u32) { + (self.device.uartdr().as_ptr() as u32, u32::MAX) + } + + fn rx_increment(&self) -> bool { + false + } +} + +impl> EndlessReadTarget for Reader {} + +impl> ErrorType for Reader { + type Error = ReadErrorType; +} + +impl> Read for Reader { + fn read(&mut self) -> nb::Result { + let byte: &mut [u8] = &mut [0; 1]; + + match self.read_raw(byte) { + Ok(_) => Ok(byte[0]), + Err(e) => match e { + Other(inner) => Err(Other(inner.err_type)), + WouldBlock => Err(WouldBlock), + }, + } + } +} diff --git a/rp235x-hal/src/uart/utils.rs b/rp235x-hal/src/uart/utils.rs new file mode 100644 index 000000000..ce1bc2c0a --- /dev/null +++ b/rp235x-hal/src/uart/utils.rs @@ -0,0 +1,93 @@ +use crate::pac::dma::ch::ch_ctrl_trig::TREQ_SEL_A; +use crate::pac::{uart0::RegisterBlock, UART0, UART1}; +use crate::resets::SubsystemReset; +use crate::typelevel::Sealed; +use core::ops::Deref; + +#[doc(inline)] +pub use rp_hal_common::uart::{DataBits, Parity, StopBits, UartConfig}; + +/// Error type for UART operations. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Bad argument : when things overflow, ... + BadArgument, +} + +/// State of the UART Peripheral. +pub trait State: Sealed {} + +/// Trait to handle both underlying devices (UART0 & UART1) +pub trait UartDevice: Deref + SubsystemReset + Sealed + 'static { + /// Index of the Uart. + const ID: usize; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 + where + Self: Sized; + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 + where + Self: Sized; +} + +impl UartDevice for UART0 { + const ID: usize = 0; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 { + TREQ_SEL_A::UART0_TX.into() + } + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 { + TREQ_SEL_A::UART0_RX.into() + } +} +impl Sealed for UART0 {} +impl UartDevice for UART1 { + const ID: usize = 1; + + /// The DREQ number for which TX DMA requests are triggered. + fn tx_dreq() -> u8 { + TREQ_SEL_A::UART1_TX.into() + } + /// The DREQ number for which RX DMA requests are triggered. + fn rx_dreq() -> u8 { + TREQ_SEL_A::UART1_RX.into() + } +} +impl Sealed for UART1 {} + +/// UART is enabled. +pub struct Enabled; + +/// UART is disabled. +pub struct Disabled; + +impl State for Enabled {} +impl Sealed for Enabled {} +impl State for Disabled {} +impl Sealed for Disabled {} + +/// Rx/Tx FIFO Watermark +/// +/// Determine the FIFO level that trigger DMA/Interrupt +/// Default is Bytes16, see DS Table 423 and UARTIFLS Register +/// Example of use: +/// uart0.set_fifos(true); // Default is false +/// uart0.set_rx_watermark(hal::uart::FifoWatermark::Bytes8); +/// uart0.enable_rx_interrupt(); +pub enum FifoWatermark { + /// Trigger when 4 bytes are (Rx: filled / Tx: available) + Bytes4, + /// Trigger when 8 bytes are (Rx: filled / Tx: available) + Bytes8, + /// Trigger when 16 bytes are (Rx: filled / Tx: available) + Bytes16, + /// Trigger when 24 bytes are (Rx: filled / Tx: available) + Bytes24, + /// Trigger when 28 bytes are (Rx: filled / Tx: available) + Bytes28, +} diff --git a/rp235x-hal/src/uart/writer.rs b/rp235x-hal/src/uart/writer.rs new file mode 100644 index 000000000..941fc9398 --- /dev/null +++ b/rp235x-hal/src/uart/writer.rs @@ -0,0 +1,290 @@ +//! Universal Asynchronous Receiver Transmitter - Transmitter Code +//! +//! This module is for transmitting data with a UART. + +use super::{FifoWatermark, UartDevice, ValidUartPinout}; +use crate::dma::{EndlessWriteTarget, WriteTarget}; +use crate::pac::uart0::RegisterBlock; +use core::fmt; +use core::{convert::Infallible, marker::PhantomData}; +use embedded_hal_0_2::serial::Write as Write02; +use embedded_hal_nb::serial::{ErrorType, Write}; +use nb::Error::*; + +/// Set tx FIFO watermark +/// +/// See DS: Table 423 +pub fn set_tx_watermark(rb: &RegisterBlock, watermark: FifoWatermark) { + let wm = match watermark { + FifoWatermark::Bytes4 => 4, + FifoWatermark::Bytes8 => 3, + FifoWatermark::Bytes16 => 2, + FifoWatermark::Bytes24 => 1, + FifoWatermark::Bytes28 => 0, + }; + rb.uartifls() + .modify(|_r, w| unsafe { w.txiflsel().bits(wm) }); +} + +/// Returns `Err(WouldBlock)` if the UART is still busy transmitting data. +/// It returns Ok(()) when the TX fifo and the transmit shift register are empty +/// and the last stop bit is sent. +pub(crate) fn transmit_flushed(rb: &RegisterBlock) -> nb::Result<(), Infallible> { + if rb.uartfr().read().busy().bit_is_set() { + Err(WouldBlock) + } else { + Ok(()) + } +} + +/// Returns `true` if the TX FIFO has space, or false if it is full +pub(crate) fn uart_is_writable(rb: &RegisterBlock) -> bool { + rb.uartfr().read().txff().bit_is_clear() +} + +/// Returns `true` if the UART is busy transmitting data, `false` after all +/// bits (including stop bits) have been transmitted. +pub(crate) fn uart_is_busy(rb: &RegisterBlock) -> bool { + rb.uartfr().read().busy().bit_is_set() +} + +/// Writes bytes to the UART. +/// +/// This function writes as long as it can. As soon that the FIFO is full, +/// if: +/// - 0 bytes were written, a WouldBlock Error is returned +/// - some bytes were written, it is deemed to be a success +/// +/// Upon success, the remaining (unwritten) slice is returned. +pub(crate) fn write_raw<'d>( + rb: &RegisterBlock, + data: &'d [u8], +) -> nb::Result<&'d [u8], Infallible> { + let mut bytes_written = 0; + + for c in data { + if !uart_is_writable(rb) { + if bytes_written == 0 { + return Err(WouldBlock); + } else { + return Ok(&data[bytes_written..]); + } + } + + rb.uartdr().write(|w| unsafe { + w.data().bits(*c); + w + }); + + bytes_written += 1; + } + Ok(&data[bytes_written..]) +} + +/// Writes bytes to the UART. +/// +/// This function blocks until the full buffer has been sent. +pub(crate) fn write_full_blocking(rb: &RegisterBlock, data: &[u8]) { + let mut temp = data; + + while !temp.is_empty() { + temp = match write_raw(rb, temp) { + Ok(remaining) => remaining, + Err(WouldBlock) => continue, + Err(_) => unreachable!(), + } + } +} + +/// Enables the Transmit Interrupt. +/// +/// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. +pub(crate) fn enable_tx_interrupt(rb: &RegisterBlock) { + // Access the UART FIFO Level Select. We set the TX FIFO trip level + // to be when it's half-empty.. + + // 2 means '<= 1/2 full'. + rb.uartifls() + .modify(|_r, w| unsafe { w.txiflsel().bits(2) }); + + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // high enables the interrupt. + + // We set the TX interrupt. This means we will get an interrupt when + // the TX FIFO level is triggered. This means we don't have to + // interrupt on every single byte, but can make use of the hardware + // FIFO. + rb.uartimsc().modify(|_r, w| { + w.txim().set_bit(); + w + }); +} + +/// Disables the Transmit Interrupt. +pub(crate) fn disable_tx_interrupt(rb: &RegisterBlock) { + // Access the UART Interrupt Mask Set/Clear register. Setting a bit + // low disables the interrupt. + + rb.uartimsc().modify(|_r, w| { + w.txim().clear_bit(); + w + }); +} + +/// Half of an [`UartPeripheral`] that is only capable of writing. Obtained by calling [`UartPeripheral::split()`] +/// +/// [`UartPeripheral`]: struct.UartPeripheral.html +/// [`UartPeripheral::split()`]: struct.UartPeripheral.html#method.split +pub struct Writer> { + pub(super) device: D, + pub(super) device_marker: PhantomData, + pub(super) pins: PhantomData

, +} + +impl> Writer { + /// Writes bytes to the UART. + /// + /// This function writes as long as it can. As soon that the FIFO is full, + /// if: + /// - 0 bytes were written, a WouldBlock Error is returned + /// - some bytes were written, it is deemed to be a success + /// + /// Upon success, the remaining (unwritten) slice is returned. + pub fn write_raw<'d>(&self, data: &'d [u8]) -> nb::Result<&'d [u8], Infallible> { + write_raw(&self.device, data) + } + + /// Writes bytes to the UART. + /// + /// This function blocks until the full buffer has been sent. + pub fn write_full_blocking(&self, data: &[u8]) { + write_full_blocking(&self.device, data); + } + + /// Enables the Transmit Interrupt. + /// + /// The relevant UARTx IRQ will fire when there is space in the transmit FIFO. + pub fn enable_tx_interrupt(&mut self) { + enable_tx_interrupt(&self.device) + } + + /// Disables the Transmit Interrupt. + pub fn disable_tx_interrupt(&mut self) { + disable_tx_interrupt(&self.device) + } + + /// Initiates a break + /// + /// If transmitting, this takes effect immediately after the current byte has completed. + /// For proper execution of the break command, this must be held for at least 2 complete frames + /// worth of time. + /// + ///

The device won’t be able to send anything while breaking.
+ /// + /// # Example + /// + /// ```no_run + /// # use rp235x_hal::uart::{Pins, ValidUartPinout, Enabled, UartPeripheral}; + /// # use rp235x_hal::pac::UART0; + /// # use rp235x_hal::timer::{Timer, CopyableTimer0}; + /// # use rp235x_hal::typelevel::OptionTNone; + /// # use embedded_hal_0_2::blocking::delay::DelayUs; + /// # type PINS = Pins; + /// # fn example(mut serial: UartPeripheral, mut timer: Timer) { + /// serial.lowlevel_break_start(); + /// // at 115_200Bps on 8N1 configuration, 20bits takes (20*10⁶)/115200 = 173.611…μs. + /// timer.delay_us(175); + /// serial.lowlevel_break_stop(); + /// } + /// ``` + pub fn lowlevel_break_start(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().set_bit()); + } + + /// Terminates a break condition. + /// + /// See `lowlevel_break_start` for more details. + pub fn lowlevel_break_stop(&mut self) { + self.device.uartlcr_h().modify(|_, w| w.brk().clear_bit()); + } +} + +impl> embedded_io::ErrorType for Writer { + type Error = Infallible; +} + +impl> embedded_io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> Result { + self.write_full_blocking(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(transmit_flushed(&self.device)).unwrap(); // Infallible + Ok(()) + } +} + +impl> Write02 for Writer { + type Error = Infallible; + + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + transmit_flushed(&self.device) + } +} + +// Safety: This only writes to the TX fifo, so it doesn't +// interact with rust-managed memory. +unsafe impl> WriteTarget for Writer { + type TransmittedWord = u8; + + fn tx_treq() -> Option { + Some(D::tx_dreq()) + } + + fn tx_address_count(&mut self) -> (u32, u32) { + (self.device.uartdr().as_ptr() as u32, u32::MAX) + } + + fn tx_increment(&self) -> bool { + false + } +} + +impl> EndlessWriteTarget for Writer {} + +impl> ErrorType for Writer { + type Error = Infallible; +} + +impl> Write for Writer { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + if self.write_raw(&[word]).is_err() { + Err(WouldBlock) + } else { + Ok(()) + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + transmit_flushed(&self.device).map_err(|e| match e { + WouldBlock => WouldBlock, + Other(v) => match v {}, + }) + } +} + +impl> fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + s.bytes() + .try_for_each(|c| nb::block!(Write::write(self, c))) + .map_err(|_| fmt::Error) + } +} diff --git a/rp235x-hal/src/usb.rs b/rp235x-hal/src/usb.rs new file mode 100644 index 000000000..4440acc72 --- /dev/null +++ b/rp235x-hal/src/usb.rs @@ -0,0 +1,623 @@ +//! Universal Serial Bus (USB) +//! +//! See [pico_usb_serial.rs](https://github.com/rp-rs/rp-hal-boards/blob/main/boards/rp-pico/examples/pico_usb_serial.rs) for more complete examples +//! +//! +//! ## Enumeration issue with small EP0 max packet size +//! +//! During enumeration Windows hosts send a `StatusOut` after the `DataIn` packet of the first +//! `Get Descriptor` request even if the `DataIn` isn't completed (typically when the `max_packet_size_ep0` +//! is less than 18bytes). The next request is a `Set Address` that expect a `StatusIn`. +//! +//! The issue is that by the time the previous `DataIn` packet is acknowledged and the `StatusOut` +//! followed by `Setup` are received, the usb stack may have already prepared the next `DataIn` payload +//! in the EP0 IN mailbox resulting in the payload being transmitted to the host instead of the +//! `StatusIn` for the `Set Address` request as expected by the host. +//! +//! To avoid that issue, the EP0 In mailbox should be invalidated between the `Setup` packet and the +//! next `StatusIn` initiated by the host. The workaround implemented clears the available bit of the +//! EP0 In endpoint's buffer to stop the device from sending the data instead of the status packet. +//! This workaround has the caveat that the poll function must be called between those two which +//! are only separated by a few microseconds. +//! +//! If the required timing cannot be met, using an maximum packet size of the endpoint 0 above 18bytes +//! (e.g. `.max_packet_size_ep0(64)`) should avoid that issue. + +use core::cell::RefCell; +use critical_section::Mutex; + +use usb_device::{ + bus::{PollResult, UsbBus as UsbBusTrait}, + endpoint::{EndpointAddress, EndpointType}, + Result as UsbResult, UsbDirection, UsbError, +}; + +use crate::{clocks::UsbClock, pac, resets::SubsystemReset}; + +#[allow(clippy::bool_to_int_with_if)] +fn ep_addr_to_ep_buf_ctrl_idx(ep_addr: EndpointAddress) -> usize { + ep_addr.index() * 2 + (if ep_addr.is_in() { 0 } else { 1 }) +} +#[derive(Debug)] +struct Endpoint { + ep_type: EndpointType, + max_packet_size: u16, + buffer_offset: u16, +} +impl Endpoint { + unsafe fn get_buf_parts(&self) -> (*mut u8, usize) { + const DPRAM_BASE: *mut u8 = pac::USB_DPRAM::ptr() as *mut u8; + if self.ep_type == EndpointType::Control { + (DPRAM_BASE.offset(0x100), self.max_packet_size as usize) + } else { + ( + DPRAM_BASE.offset(0x180 + (self.buffer_offset * 64) as isize), + self.max_packet_size as usize, + ) + } + } + + fn get_buf(&self) -> &[u8] { + // SAFETY: + // offset is checked by Inner::ep_allocate. + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts(base as *const _, len) + } + } + + fn get_buf_mut(&mut self) -> &mut [u8] { + // SAFETY: + // offset is checked by Inner::ep_allocate. + unsafe { + let (base, len) = self.get_buf_parts(); + core::slice::from_raw_parts_mut(base, len) + } + } +} + +struct Inner { + ctrl_reg: pac::USB, + ctrl_dpram: pac::USB_DPRAM, + in_endpoints: [Option; 16], + out_endpoints: [Option; 16], + next_offset: u16, + read_setup: bool, + pll: UsbClock, +} +impl Inner { + fn new(ctrl_reg: pac::USB, ctrl_dpram: pac::USB_DPRAM, pll: UsbClock) -> Self { + Self { + ctrl_reg, + ctrl_dpram, + in_endpoints: Default::default(), + out_endpoints: Default::default(), + next_offset: 0, + read_setup: false, + pll, + } + } + + fn ep_allocate( + &mut self, + ep_addr: Option, + ep_dir: UsbDirection, + ep_type: EndpointType, + max_packet_size: u16, + ) -> UsbResult { + let ep_addr = ep_addr + .or_else(|| { + let eps = if ep_dir == UsbDirection::In { + self.in_endpoints.iter() + } else { + self.out_endpoints.iter() + }; + // find free end point + let mut iter = eps.enumerate(); + // reserve ep0 for the control endpoint + if ep_type != EndpointType::Control { + iter.next(); + } + iter.find(|(_, ep)| ep.is_none()) + .map(|(index, _)| EndpointAddress::from_parts(index, ep_dir)) + }) + .ok_or(UsbError::EndpointOverflow)?; + + let is_ep0 = ep_addr.index() == 0; + let is_ctrl_ep = ep_type == EndpointType::Control; + if !(is_ep0 ^ !is_ctrl_ep) { + return Err(UsbError::Unsupported); + } + + let eps = if ep_addr.is_in() { + &mut self.in_endpoints + } else { + &mut self.out_endpoints + }; + let maybe_ep = eps + .get_mut(ep_addr.index()) + .ok_or(UsbError::EndpointOverflow)?; + if maybe_ep.is_some() { + return Err(UsbError::InvalidEndpoint); + } + + // Validate buffer size. From datasheet (4.1.2.5): + // Data Buffers are typically 64 bytes long as this is the max normal packet size for most FS packets. + // For Isochronous endpoints a maximum buffer size of 1023 bytes is supported. + // For other packet types the maximum size is 64 bytes per buffer. + if (!matches!(ep_type, EndpointType::Isochronous { .. }) && max_packet_size > 64) + || max_packet_size > 1023 + { + return Err(UsbError::Unsupported); + } + + if ep_addr.index() == 0 { + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset: 0, // not used on CTRL ep + }); + } else { + // size in 64bytes units. + // NOTE: the compiler is smart enough to recognize /64 as a 6bit right shift so let's + // keep the division here for the sake of clarity + let aligned_sized = (max_packet_size + 63) / 64; + if (self.next_offset + aligned_sized) > (4096 / 64) { + return Err(UsbError::EndpointMemoryOverflow); + } + + let buffer_offset = self.next_offset; + self.next_offset += aligned_sized; + + *maybe_ep = Some(Endpoint { + ep_type, + max_packet_size, + buffer_offset, + }); + } + Ok(ep_addr) + } + + fn ep_reset_all(&mut self) { + self.ctrl_reg + .sie_ctrl() + .modify(|_, w| w.ep0_int_1buf().set_bit()); + // expect ctrl ep to receive on DATA first + self.ctrl_dpram + .ep_buffer_control(0) + .write(|w| w.pid_0().set_bit()); + self.ctrl_dpram + .ep_buffer_control(1) + .write(|w| w.pid_0().set_bit()); + crate::arch::delay(12); + self.ctrl_dpram + .ep_buffer_control(1) + .write(|w| w.available_0().set_bit()); + + for (index, ep) in itertools::interleave( + self.in_endpoints.iter().skip(1), // skip control endpoint + self.out_endpoints.iter().skip(1), // skip control endpoint + ) + .enumerate() + .filter_map(|(i, ep)| ep.as_ref().map(|ep| (i, ep))) + { + use crate::pac::usb_dpram::ep_control::ENDPOINT_TYPE_A; + let ep_type = match ep.ep_type { + EndpointType::Bulk => ENDPOINT_TYPE_A::BULK, + EndpointType::Isochronous { .. } => ENDPOINT_TYPE_A::ISOCHRONOUS, + EndpointType::Control => ENDPOINT_TYPE_A::CONTROL, + EndpointType::Interrupt => ENDPOINT_TYPE_A::INTERRUPT, + }; + // configure + // ep 0 in&out are not part of index (skipped before enumeration) + self.ctrl_dpram.ep_control(index).modify(|_, w| unsafe { + w.endpoint_type().variant(ep_type); + w.interrupt_per_buff().set_bit(); + w.enable().set_bit(); + w.buffer_address().bits(0x180 + (ep.buffer_offset << 6)) + }); + // reset OUT ep and prepare IN ep to accept data + let buf_control = &self.ctrl_dpram.ep_buffer_control(index + 2); + if (index & 1) == 0 { + // first write occur on DATA0 so prepare the pid bit to be flipped + buf_control.write(|w| w.pid_0().set_bit()); + } else { + buf_control.write(|w| unsafe { + w.pid_0().clear_bit(); + w.length_0().bits(ep.max_packet_size) + }); + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + } + } + } + + fn ep_write(&mut self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .in_endpoints + .get_mut(index) + .and_then(Option::as_mut) + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control(index * 2); + if buf_control.read().available_0().bit_is_set() { + return Err(UsbError::WouldBlock); + } + + let ep_buf = ep.get_buf_mut(); + if ep_buf.len() < buf.len() { + return Err(UsbError::BufferOverflow); + } + ep_buf[..buf.len()].copy_from_slice(buf); + + buf_control.modify(|r, w| unsafe { + w.length_0().bits(buf.len() as u16); + w.full_0().set_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + + Ok(buf.len()) + } + + fn ep_read(&mut self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + let index = ep_addr.index(); + let ep = self + .out_endpoints + .get_mut(index) + .and_then(Option::as_mut) + .ok_or(UsbError::InvalidEndpoint)?; + + let buf_control = &self.ctrl_dpram.ep_buffer_control(index * 2 + 1); + let buf_control_val = buf_control.read(); + + let process_setup = index == 0 && self.read_setup; + if process_setup { + // assume we want to read the setup request + // + // the OUT packet will be either data or a status zlp + let len = 8; + let ep_buf = + unsafe { core::slice::from_raw_parts(pac::USB_DPRAM::ptr() as *const u8, len) }; + if len > buf.len() { + return Err(UsbError::BufferOverflow); + } + + buf[..len].copy_from_slice(&ep_buf[..len]); + + // Next packet will be on DATA1 so clear pid_0 so it gets flipped by next buf config + self.ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.pid_0().clear_bit()); + // clear setup request flag + self.ctrl_reg + .sie_status() + .write(|w| w.setup_rec().clear_bit_by_one()); + + // clear any out standing out flag e.g. in case a zlp got discarded + self.ctrl_reg.buff_status().write(|w| unsafe { w.bits(2) }); + + let is_in_request = (buf[0] & 0x80) == 0x80; + let data_length = u16::from(buf[6]) | (u16::from(buf[7]) << 8); + let expect_data_or_zlp = is_in_request || data_length != 0; + + buf_control.modify(|_, w| unsafe { + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().set_bit() + }); + // enable if and only if a dataphase is expected. + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().bit(expect_data_or_zlp)); + + self.read_setup = false; + Ok(len) + } else { + if buf_control_val.full_0().bit_is_clear() { + return Err(UsbError::WouldBlock); + } + let len = buf_control_val.length_0().bits().into(); + if len > buf.len() { + return Err(UsbError::BufferOverflow); + } + + buf[..len].copy_from_slice(&ep.get_buf()[..len]); + // Clear OUT flag once it is read. + self.ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(1 << (index * 2 + 1)) }); + + buf_control.modify(|r, w| unsafe { + w.length_0().bits(ep.max_packet_size); + w.full_0().clear_bit(); + w.pid_0().bit(!r.pid_0().bit()) + }); + if index != 0 || len == ep.max_packet_size.into() { + // only mark as available on the control endpoint if and only if the packet was + // max_packet_size + crate::arch::delay(12); + buf_control.modify(|_, w| w.available_0().set_bit()); + } + Ok(len) + } + } +} + +/// Usb bus +pub struct UsbBus { + inner: Mutex>, +} + +impl UsbBus { + /// Create new usb bus struct and bring up usb as device. + pub fn new( + ctrl_reg: pac::USB, + ctrl_dpram: pac::USB_DPRAM, + pll: UsbClock, + force_vbus_detect_bit: bool, + resets: &mut pac::RESETS, + ) -> Self { + ctrl_reg.reset_bring_down(resets); + ctrl_reg.reset_bring_up(resets); + + unsafe { + let raw_ctrl_reg = + core::slice::from_raw_parts_mut(pac::USB::ptr() as *mut u32, 1 + 0x98 / 4); + raw_ctrl_reg.fill(0); + + let raw_ctrl_pdram = + core::slice::from_raw_parts_mut(pac::USB_DPRAM::ptr() as *mut u32, 1 + 0xfc / 4); + raw_ctrl_pdram.fill(0); + } + + ctrl_reg.usb_muxing().modify(|_, w| { + w.to_phy().set_bit(); + w.softcon().set_bit() + }); + + if force_vbus_detect_bit { + ctrl_reg.usb_pwr().modify(|_, w| { + w.vbus_detect().set_bit(); + w.vbus_detect_override_en().set_bit() + }); + } + ctrl_reg.main_ctrl().modify(|_, w| { + w.sim_timing().clear_bit(); + w.host_ndevice().clear_bit(); + w.controller_en().set_bit() + }); + + Self { + inner: Mutex::new(RefCell::new(Inner::new(ctrl_reg, ctrl_dpram, pll))), + } + } + + /// Generates a resume request on the bus. + pub fn remote_wakeup(&self) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + inner + .ctrl_reg + .sie_ctrl() + .modify(|_, w| w.resume().set_bit()); + }); + } + + /// Stop and free the Usb resources + pub fn free(self, resets: &mut pac::RESETS) -> (pac::USB, pac::USB_DPRAM, UsbClock) { + critical_section::with(|_cs| { + let inner = self.inner.into_inner().into_inner(); + + inner.ctrl_reg.reset_bring_down(resets); + + (inner.ctrl_reg, inner.ctrl_dpram, inner.pll) + }) + } +} + +impl UsbBusTrait for UsbBus { + fn alloc_ep( + &mut self, + ep_dir: UsbDirection, + ep_addr: Option, + ep_type: EndpointType, + max_packet_size: u16, + _interval: u8, + ) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + inner.ep_allocate(ep_addr, ep_dir, ep_type, max_packet_size) + }) + } + + fn enable(&mut self) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + // at this stage ep's are expected to be in their reset state + // TODO: is it worth having a debug_assert for that here? + + // Enable interrupt generation when a buffer is done, when the bus is reset, + // and when a setup packet is received + // this should be sufficient for device mode, will need more for host. + inner.ctrl_reg.inte().modify(|_, w| { + w.buff_status() + .set_bit() + .bus_reset() + .set_bit() + .dev_resume_from_host() + .set_bit() + .dev_suspend() + .set_bit() + .setup_req() + .set_bit() + }); + + // enable pull up to let the host know we exist. + inner + .ctrl_reg + .sie_ctrl() + .modify(|_, w| w.pullup_en().set_bit()); + }) + } + fn reset(&self) { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + // clear reset flag + inner + .ctrl_reg + .sie_status() + .write(|w| w.bus_reset().clear_bit_by_one()); + inner + .ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + // reset all endpoints + inner.ep_reset_all(); + + // Reset address register + inner.ctrl_reg.addr_endp().reset(); + // TODO: reset all endpoints & buffer statuses + }) + } + fn set_device_address(&self, addr: u8) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + inner + .ctrl_reg + .addr_endp() + .modify(|_, w| unsafe { w.address().bits(addr & 0x7F) }); + // reset ep0 + inner + .ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.pid_0().set_bit()); + inner + .ctrl_dpram + .ep_buffer_control(1) + .modify(|_, w| w.pid_0().set_bit()); + }) + } + fn write(&self, ep_addr: EndpointAddress, buf: &[u8]) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_write(ep_addr, buf) + }) + } + fn read(&self, ep_addr: EndpointAddress, buf: &mut [u8]) -> UsbResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + inner.ep_read(ep_addr, buf) + }) + } + fn set_stalled(&self, ep_addr: EndpointAddress, stalled: bool) { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + + if ep_addr.index() == 0 { + inner.ctrl_reg.ep_stall_arm().modify(|_, w| { + if ep_addr.is_in() { + w.ep0_in().bit(stalled) + } else { + w.ep0_out().bit(stalled) + } + }); + } + + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner + .ctrl_dpram + .ep_buffer_control(index) + .modify(|_, w| w.stall().bit(stalled)); + }) + } + fn is_stalled(&self, ep_addr: EndpointAddress) -> bool { + critical_section::with(|cs| { + let inner = self.inner.borrow(cs).borrow_mut(); + let index = ep_addr_to_ep_buf_ctrl_idx(ep_addr); + inner + .ctrl_dpram + .ep_buffer_control(index) + .read() + .stall() + .bit_is_set() + }) + } + fn suspend(&self) {} + fn resume(&self) {} + fn poll(&self) -> PollResult { + critical_section::with(|cs| { + let mut inner = self.inner.borrow(cs).borrow_mut(); + + // check for bus reset and/or suspended states. + let ints = inner.ctrl_reg.ints().read(); + let mut buff_status = inner.ctrl_reg.buff_status().read().bits(); + + if ints.bus_reset().bit_is_set() { + return PollResult::Reset; + } else if buff_status == 0 && ints.setup_req().bit_is_clear() { + if ints.dev_suspend().bit_is_set() { + inner + .ctrl_reg + .sie_status() + .write(|w| w.suspended().clear_bit_by_one()); + return PollResult::Suspend; + } else if ints.dev_resume_from_host().bit_is_set() { + inner + .ctrl_reg + .sie_status() + .write(|w| w.resume().clear_bit_by_one()); + return PollResult::Resume; + } + return PollResult::None; + } + + let (mut ep_out, mut ep_in_complete, mut ep_setup): (u16, u16, u16) = (0, 0, 0); + + // IN Complete shall only be reported once. + inner + .ctrl_reg + .buff_status() + .write(|w| unsafe { w.bits(0x5555_5555) }); + + for i in 0..32u32 { + if buff_status == 0 { + break; + } else if (buff_status & 1) == 1 { + let is_in = (i & 1) == 0; + let ep_idx = i / 2; + if is_in { + ep_in_complete |= 1 << ep_idx; + } else { + ep_out |= 1 << ep_idx; + } + } + buff_status >>= 1; + } + + // check for setup request + if ints.setup_req().bit_is_set() { + // Small max_packet_size_ep0 Work-Around + inner + .ctrl_dpram + .ep_buffer_control(0) + .modify(|_, w| w.available_0().clear_bit()); + + ep_setup |= 1; + inner.read_setup = true; + } + + PollResult::Data { + ep_out, + ep_in_complete, + ep_setup, + } + }) + } + + const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; +} diff --git a/rp235x-hal/src/vector_table.rs b/rp235x-hal/src/vector_table.rs new file mode 100644 index 000000000..2f56cc3b1 --- /dev/null +++ b/rp235x-hal/src/vector_table.rs @@ -0,0 +1,102 @@ +//! Interrupt vector table utilities +//! +//! Provide functionality to switch to another vector table using the +//! Vector Table Offset Register (VTOR) of the Cortex-33 +//! Also provides types and utilities for copying a vector table into RAM + +/// Entry for a Vector in the Interrupt Vector Table. +/// +/// Each entry in the Vector table is a union with usize to allow it to be 0 initialized via const initializer +/// +/// Implementation borrowed from https://docs.rs/cortex-m-rt/0.7.1/cortex_m_rt/index.html#__interrupts +#[derive(Clone, Copy)] +union Vector { + handler: extern "C" fn(), + reserved: usize, +} + +/// Data type for a properly aligned interrupt vector table +/// +/// The VTOR register can only point to a 128 byte offsets - see +/// [Cortex-33 Devices Generic User Guide](https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/system-control-block/vector-table-offset-register?lang=en) - +/// so that is our required alignment. +/// The vector table length depends on the number of interrupts the system supports. +/// The first 16 words are defined in the ARM Cortex-M spec. +/// The Cortex-M33 cores on RP235x have 52 interrupts, of which only 47 are wired to external interrupt +/// signals - but the last 6 can be used for software interrupts so leave room for them +#[repr(C, align(128))] +pub struct VectorTable { + /// SP + Reset vector + 14 exceptions + 52 interrupts = 68 entries (272 bytes) in an rp235x core's VectorTable + table: [Vector; 68], +} + +impl Default for VectorTable { + fn default() -> Self { + Self::new() + } +} + +impl VectorTable { + /// Create a new vector table. All entries will point to 0 - you must call init() + /// on this to copy the current vector table before setting it as active + pub const fn new() -> VectorTable { + VectorTable { + table: [Vector { reserved: 0 }; 68], + } + } + + /// Initialise our vector table by copying the current table on top of it + #[allow(unknown_lints)] + #[allow(clippy::needless_pass_by_ref_mut)] + pub fn init(&mut self, ppb: &mut crate::pac::PPB) { + let mut vector_table = ppb.vtor().read().bits() as *const usize; + for entry in self.table.iter_mut() { + // Safety: + // + // This value must be valid because it's in the current vector table. + *entry = Vector { + reserved: unsafe { vector_table.read() }, + }; + // Safety: + // + // We are iterating through our copy of the vector table, which we + // know is the same size as the real vector table. + unsafe { + vector_table = vector_table.add(1); + } + } + } + + /// Dynamically register a function as being an interrupt handler + pub fn register_handler(&mut self, interrupt_idx: usize, interrupt_fn: extern "C" fn()) { + self.table[16 + interrupt_idx].handler = interrupt_fn; + } + + /// Set the stack pointer address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid stack pointer address + pub unsafe fn set_sp(&mut self, stack_pointer_address: usize) { + self.table[0].reserved = stack_pointer_address; + } + + /// Set the entry-point address in a VectorTable. This will be used on Reset + /// + /// # Safety + /// There is no checking whether this is a valid entry point + pub unsafe fn set_entry(&mut self, entry_address: usize) { + self.table[1].reserved = entry_address; + } + + /// Switch the current core to use this Interrupt Vector Table + /// + /// # Safety + /// Until the vector table has valid entries, activating it will cause an unhandled hardfault! + /// You must call init() first. + #[allow(unknown_lints)] + #[allow(clippy::needless_pass_by_ref_mut)] + pub unsafe fn activate(&mut self, ppb: &mut crate::pac::PPB) { + ppb.vtor() + .write(|w| w.bits(&mut self.table as *mut _ as *mut u32 as u32)); + } +} diff --git a/rp235x-hal/src/watchdog.rs b/rp235x-hal/src/watchdog.rs new file mode 100644 index 000000000..5998dcc7c --- /dev/null +++ b/rp235x-hal/src/watchdog.rs @@ -0,0 +1,230 @@ +//! Watchdog +//! +//! The watchdog is a countdown timer that can restart parts of the chip if it reaches zero. This can be used to restart the +//! processor if software gets stuck in an infinite loop. The programmer must periodically write a value to the watchdog to +//! stop it from reaching zero. +//! +//! See [Section 12.9](https://rptl.io/rp2350-datasheet) of the datasheet for more details +//! +//! ## Usage +//! ```no_run +//! use fugit::ExtU32; +//! use rp235x_hal::{self as hal, clocks::init_clocks_and_plls, watchdog::Watchdog}; +//! let mut pac = hal::pac::Peripherals::take().unwrap(); +//! let mut watchdog = Watchdog::new(pac.WATCHDOG); +//! let _clocks = init_clocks_and_plls( +//! 12_000_000, +//! pac.XOSC, +//! pac.CLOCKS, +//! pac.PLL_SYS, +//! pac.PLL_USB, +//! &mut pac.RESETS, +//! &mut watchdog, +//! ) +//! .ok() +//! .unwrap(); +//! // Set to watchdog to reset if it's not reloaded within 1.05 seconds, and start it +//! watchdog.start(1_050_000.micros()); +//! // Feed the watchdog once per cycle to avoid reset +//! for _ in 1..=10000 { +//! hal::arch::delay(100_000); +//! watchdog.feed(); +//! } +//! // Stop feeding, now we'll reset +//! loop {} +//! ``` +//! See [examples/watchdog.rs](https://github.com/rp-rs/rp-hal/tree/main/rp235x-hal-examples/src/bin/watchdog.rs) for a more complete example + +// Embedded HAL 1.0.0 doesn't have an ADC trait, so use the one from 0.2 +use embedded_hal_0_2::watchdog; +use fugit::MicrosDurationU32; + +use crate::pac::{self, WATCHDOG}; + +/// Watchdog peripheral +pub struct Watchdog { + watchdog: WATCHDOG, + load_value: u32, // decremented by 2 per tick (µs) +} + +#[derive(Debug)] +#[allow(missing_docs)] +/// Scratch registers of the watchdog peripheral +pub enum ScratchRegister { + Scratch0, + Scratch1, + Scratch2, + Scratch3, + Scratch4, + Scratch5, + Scratch6, + Scratch7, +} + +impl Watchdog { + /// Create a new [`Watchdog`] + pub fn new(watchdog: WATCHDOG) -> Self { + Self { + watchdog, + load_value: 0, + } + } + + /// Starts tick generation on all the ticks. + /// + /// # Arguments + /// + /// * `cycles` - Total number of tick cycles before the next tick is generated. + /// It is expected to be the frequency in MHz of clk_ref. + pub fn enable_tick_generation(&mut self, cycles: u16) { + // now we have separate ticks for every destination + // we own the watchdog, so no-one else can be writing to this register + let ticks = unsafe { &*pac::TICKS::ptr() }; + for ticker in ticks.tick_iter() { + // TODO: work out how to rename proc0_cycles to cycles in the SVD patch YAML + ticker + .cycles() + .write(|w| unsafe { w.proc0_cycles().bits(cycles) }); + ticker.ctrl().write(|w| w.enable().set_bit()); + } + } + + /// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode + /// or when JTAG is accessing bus fabric + /// + /// # Arguments + /// + /// * `pause` - If true, watchdog timer will be paused + pub fn pause_on_debug(&mut self, pause: bool) { + self.watchdog.ctrl().write(|w| { + w.pause_dbg0() + .bit(pause) + .pause_dbg1() + .bit(pause) + .pause_jtag() + .bit(pause) + }) + } + + fn load_counter(&self, counter: u32) { + self.watchdog.load().write(|w| unsafe { w.bits(counter) }); + } + + fn enable(&self, bit: bool) { + self.watchdog.ctrl().write(|w| w.enable().bit(bit)) + } + + /// Read a scratch register + pub fn read_scratch(&self, reg: ScratchRegister) -> u32 { + match reg { + ScratchRegister::Scratch0 => self.watchdog.scratch0().read().bits(), + ScratchRegister::Scratch1 => self.watchdog.scratch1().read().bits(), + ScratchRegister::Scratch2 => self.watchdog.scratch2().read().bits(), + ScratchRegister::Scratch3 => self.watchdog.scratch3().read().bits(), + ScratchRegister::Scratch4 => self.watchdog.scratch4().read().bits(), + ScratchRegister::Scratch5 => self.watchdog.scratch5().read().bits(), + ScratchRegister::Scratch6 => self.watchdog.scratch6().read().bits(), + ScratchRegister::Scratch7 => self.watchdog.scratch7().read().bits(), + } + } + + /// Write a scratch register + pub fn write_scratch(&mut self, reg: ScratchRegister, value: u32) { + match reg { + ScratchRegister::Scratch0 => { + self.watchdog.scratch0().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch1 => { + self.watchdog.scratch1().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch2 => { + self.watchdog.scratch2().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch3 => { + self.watchdog.scratch3().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch4 => { + self.watchdog.scratch4().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch5 => { + self.watchdog.scratch5().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch6 => { + self.watchdog.scratch6().write(|w| unsafe { w.bits(value) }) + } + ScratchRegister::Scratch7 => { + self.watchdog.scratch7().write(|w| unsafe { w.bits(value) }) + } + } + } + + /// Configure which hardware will be reset by the watchdog + /// the default is everything except ROSC, XOSC + /// + /// Safety: ensure no other device is writing to psm.wdsel + /// This is easy at the moment, since nothing else uses PSM + unsafe fn configure_wdog_reset_triggers(&self) { + let psm = &*pac::PSM::ptr(); + psm.wdsel().write_with_zero(|w| { + w.bits(0x0001ffff); + w.xosc().clear_bit(); + w.rosc().clear_bit(); + w + }); + } + + /// Set the watchdog counter back to its load value, making sure + /// that the watchdog reboot will not be triggered for the configured + /// period. + pub fn feed(&self) { + self.load_counter(self.load_value) + } + + /// Start the watchdog. This enables a timer which will reboot the + /// rp2350 if [`Watchdog::feed()`] does not get called for the configured period. + pub fn start>(&mut self, period: T) { + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.into().to_micros(); + if delay_us > MAX_PERIOD / 2 { + panic!( + "Period cannot exceed maximum load value of {} ({} microseconds))", + MAX_PERIOD, + MAX_PERIOD / 2 + ); + } + self.load_value = delay_us; + + self.enable(false); + unsafe { + self.configure_wdog_reset_triggers(); + } + self.load_counter(self.load_value); + self.enable(true); + } + + /// Disable the watchdog timer. + pub fn disable(&self) { + self.enable(false) + } +} + +impl watchdog::Watchdog for Watchdog { + fn feed(&mut self) { + (*self).feed() + } +} + +impl watchdog::WatchdogEnable for Watchdog { + type Time = MicrosDurationU32; + + fn start>(&mut self, period: T) { + self.start(period) + } +} + +impl watchdog::WatchdogDisable for Watchdog { + fn disable(&mut self) { + (*self).disable() + } +} diff --git a/rp235x-hal/src/xosc.rs b/rp235x-hal/src/xosc.rs new file mode 100644 index 000000000..e88bd8154 --- /dev/null +++ b/rp235x-hal/src/xosc.rs @@ -0,0 +1,227 @@ +//! Crystal Oscillator (XOSC) +//! +//! See [Section 8.2](https://rptl.io/rp2350-datasheet#section_xosc) for more details. + +use core::{convert::Infallible, ops::RangeInclusive}; + +use fugit::HertzU32; +use nb::Error::WouldBlock; + +use crate::{pac::XOSC, typelevel::Sealed}; + +/// State of the Crystal Oscillator (typestate trait) +pub trait State: Sealed {} + +/// XOSC is disabled (typestate) +pub struct Disabled; + +/// XOSC is initialized but has not yet stabilized (typestate) +pub struct Unstable { + freq_hz: HertzU32, +} + +/// XOSC is stable (typestate) +pub struct Stable { + freq_hz: HertzU32, +} + +impl State for Disabled {} +impl Sealed for Disabled {} +impl State for Unstable {} +impl Sealed for Unstable {} +impl State for Stable {} +impl Sealed for Stable {} + +/// Possible errors when initializing the CrystalOscillator +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Frequency is out of the 1-15MHz range (see datasheet) + FrequencyOutOfRange, + + /// Argument is bad : overflows, ... + BadArgument, +} + +/// Blocking helper method to setup the XOSC without going through all the steps. +/// +/// This uses a startup_delay_multiplier of 64, which is a rather conservative value +/// that should work even if the XOSC starts up slowly. In case you need a fast boot +/// sequence, and your XOSC starts up quickly enough, use [`setup_xosc_blocking_custom_delay`]. +pub fn setup_xosc_blocking( + xosc_dev: XOSC, + frequency: HertzU32, +) -> Result, Error> { + let initialized_xosc = CrystalOscillator::new(xosc_dev).initialize(frequency, 64)?; + + let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap(); + + Ok(initialized_xosc.get_stable(stable_xosc_token)) +} + +/// Blocking helper method to setup the XOSC without going through all the steps. +/// +/// This function allows setting a startup_delay_multiplier to tune the amount of time +/// the chips waits for the XOSC to stabilize. +/// The default value in the C SDK is 1, which should work on the Raspberry Pico, and many +/// third-party boards. +/// [`setup_xosc_blocking`], uses a conservative value of 64, which is the value commonly +/// used on slower-starting oscillators. +pub fn setup_xosc_blocking_custom_delay( + xosc_dev: XOSC, + frequency: HertzU32, + startup_delay_multiplier: u32, +) -> Result, Error> { + let initialized_xosc = + CrystalOscillator::new(xosc_dev).initialize(frequency, startup_delay_multiplier)?; + + let stable_xosc_token = nb::block!(initialized_xosc.await_stabilization()).unwrap(); + + Ok(initialized_xosc.get_stable(stable_xosc_token)) +} + +/// A Crystal Oscillator. +pub struct CrystalOscillator { + device: XOSC, + state: S, +} + +impl CrystalOscillator { + /// Transitions the oscillator to another state. + fn transition(self, state: To) -> CrystalOscillator { + CrystalOscillator { + device: self.device, + state, + } + } + + /// Releases the underlying device. + pub fn free(self) -> XOSC { + self.device + } +} + +impl CrystalOscillator { + /// Creates a new CrystalOscillator from the underlying device. + pub fn new(dev: XOSC) -> Self { + CrystalOscillator { + device: dev, + state: Disabled, + } + } + + /// Initializes the XOSC : frequency range is set, startup delay is calculated and set. + /// Set startup_delay_multiplier to a value > 1 when using a slow-starting oscillator. + pub fn initialize( + self, + frequency: HertzU32, + startup_delay_multiplier: u32, + ) -> Result, Error> { + const ALLOWED_FREQUENCY_RANGE: RangeInclusive = + HertzU32::MHz(1)..=HertzU32::MHz(15); + //1 ms = 10e-3 sec and Freq = 1/T where T is in seconds so 1ms converts to 1000Hz + const STABLE_DELAY_AS_HZ: HertzU32 = HertzU32::Hz(1000); + const DIVIDER: u32 = 256; + + if !ALLOWED_FREQUENCY_RANGE.contains(&frequency) { + return Err(Error::FrequencyOutOfRange); + } + + if startup_delay_multiplier == 0 { + return Err(Error::BadArgument); + } + + self.device.ctrl().write(|w| { + w.freq_range()._1_15mhz(); + w + }); + + //startup_delay = ((freq_hz * STABLE_DELAY) / 256) = ((freq_hz / + // delay_to_hz) / 256) = freq_hz / (delay_to_hz * 256) + // + // See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) + // of the RP2350 datasheet. + // + // We do the calculation first. + let startup_delay = frequency.to_Hz() / (STABLE_DELAY_AS_HZ.to_Hz() * DIVIDER); + let startup_delay = startup_delay.saturating_mul(startup_delay_multiplier); + + //Then we check if it fits into an u16. + let startup_delay: u16 = startup_delay.try_into().map_err(|_| Error::BadArgument)?; + + self.device.startup().write(|w| unsafe { + w.delay().bits(startup_delay); + w + }); + + self.device.ctrl().write(|w| { + w.enable().enable(); + w + }); + + Ok(self.transition(Unstable { freq_hz: frequency })) + } +} + +/// A token that's given when the oscillator is stabilized, and can be exchanged to proceed to the next stage. +pub struct StableOscillatorToken { + _private: (), +} + +impl CrystalOscillator { + /// One has to wait for the startup delay before using the oscillator, ie awaiting stabilization of the XOSC + pub fn await_stabilization(&self) -> nb::Result { + if self.device.status().read().stable().bit_is_clear() { + return Err(WouldBlock); + } + + Ok(StableOscillatorToken { _private: () }) + } + + /// Returns the stabilized oscillator + pub fn get_stable(self, _token: StableOscillatorToken) -> CrystalOscillator { + let freq_hz = self.state.freq_hz; + self.transition(Stable { freq_hz }) + } +} + +impl CrystalOscillator { + /// Operating frequency of the XOSC in hertz + pub fn operating_frequency(&self) -> HertzU32 { + self.state.freq_hz + } + + /// Disables the XOSC + pub fn disable(self) -> CrystalOscillator { + self.device.ctrl().modify(|_r, w| { + w.enable().disable(); + w + }); + + self.transition(Disabled) + } + + /// Put the XOSC in DORMANT state. The method returns after the processor awakens. + /// + /// After waking up from the DORMANT state, XOSC needs to re-stabilise. + /// + /// # Safety + /// This method is marked unsafe because prior to switch the XOSC into DORMANT state, + /// PLLs must be stopped and IRQs have to be properly configured. + /// This method does not do any of that, it merely switches the XOSC to DORMANT state. + /// It should only be called if this oscillator is the clock source for the system clock. + /// + /// See Sectiom 2.16, §5 for details. + pub unsafe fn dormant(self) -> CrystalOscillator { + //taken from the C SDK + const XOSC_DORMANT_VALUE: u32 = 0x636f6d61; + + self.device.dormant().write(|w| { + w.bits(XOSC_DORMANT_VALUE); + w + }); + + let freq_hz = self.state.freq_hz; + self.transition(Unstable { freq_hz }) + } +}