├── .cargo └── config.toml ├── .github └── workflows │ ├── parallel-build.yml │ └── weekly-canary-build.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── _redirects ├── build.sh ├── build_fns.sh ├── cloudflare.sh ├── describe.sh ├── exercise-book ├── book.toml ├── src │ ├── SUMMARY.md │ ├── async-chat │ │ ├── accept_loop.md │ │ ├── all_together.md │ │ ├── clean_shutdown.md │ │ ├── connecting_readers_and_writers.md │ │ ├── final_server_code.md │ │ ├── handling_disconnection.md │ │ ├── implementing_a_client.md │ │ ├── index.md │ │ ├── receiving_messages.md │ │ ├── sending_messages.md │ │ └── specification.md │ ├── building-linux-kernel-driver.md │ ├── calculator.md │ ├── connected-mailbox.md │ ├── fizzbuzz-cheat-sheet.md │ ├── fizzbuzz-match.md │ ├── fizzbuzz.md │ ├── green-yellow-game.md │ ├── img │ │ ├── nrf52840_dk_board.jpg │ │ ├── puzzle_illustration.jpg │ │ ├── usb-all-endpoints.svg │ │ ├── usb-configuration.svg │ │ ├── usb-control.svg │ │ ├── usb-device.svg │ │ ├── usb-endpoint.svg │ │ └── usb-interface.svg │ ├── introduction.md │ ├── iterators.md │ ├── kani-linked-list.md │ ├── multi-threaded-mailbox.md │ ├── nrf52-code-organisation.md │ ├── nrf52-hal-buttons.md │ ├── nrf52-hal-exercise.md │ ├── nrf52-hardware.md │ ├── nrf52-preparation.md │ ├── nrf52-radio-alt-containers.md │ ├── nrf52-radio-binary-size.md │ ├── nrf52-radio-building-program.md │ ├── nrf52-radio-collision-avoidance.md │ ├── nrf52-radio-dongle.md │ ├── nrf52-radio-exercise.md │ ├── nrf52-radio-from-scratch.md │ ├── nrf52-radio-in.md │ ├── nrf52-radio-interrupt-handling.md │ ├── nrf52-radio-link-quality.md │ ├── nrf52-radio-messages.md │ ├── nrf52-radio-next-steps.md │ ├── nrf52-radio-out.md │ ├── nrf52-radio-panicking.md │ ├── nrf52-radio-parts-embedded-program.md │ ├── nrf52-radio-puzzle-help.md │ ├── nrf52-radio-puzzle.md │ ├── nrf52-radio-running-from-vsc.md │ ├── nrf52-radio-setup.md │ ├── nrf52-radio-time.md │ ├── nrf52-radio-using-hal.md │ ├── nrf52-references-resources.md │ ├── nrf52-tools.md │ ├── nrf52-tooltips.md │ ├── nrf52-troubleshoot-cargo-build.md │ ├── nrf52-troubleshoot-cargo-flash.md │ ├── nrf52-troubleshoot-cargo-run-error.md │ ├── nrf52-troubleshoot-cargo-size.md │ ├── nrf52-troubleshoot-dongle-flash.md │ ├── nrf52-troubleshoot-location-info.md │ ├── nrf52-troubleshoot-probe-not-found.md │ ├── nrf52-troubleshoot-rust-analyzer.md │ ├── nrf52-troubleshoot-usb-dongle.md │ ├── nrf52-troubleshooting.md │ ├── nrf52-usb-advanced-next-steps.md │ ├── nrf52-usb-api-documentation.md │ ├── nrf52-usb-configuration-descriptor.md │ ├── nrf52-usb-control-transfers.md │ ├── nrf52-usb-data-stage.md │ ├── nrf52-usb-dealing-with-registers.md │ ├── nrf52-usb-device-descriptor.md │ ├── nrf52-usb-dma.md │ ├── nrf52-usb-endpoint-descriptor.md │ ├── nrf52-usb-event-handling.md │ ├── nrf52-usb-exercise.md │ ├── nrf52-usb-extra-info.md │ ├── nrf52-usb-getting-device-configured.md │ ├── nrf52-usb-hello-world.md │ ├── nrf52-usb-idle-state.md │ ├── nrf52-usb-inspecting-descriptors.md │ ├── nrf52-usb-interface-descriptor.md │ ├── nrf52-usb-interfaces.md │ ├── nrf52-usb-listing-usb-devices.md │ ├── nrf52-usb-rtic-hello.md │ ├── nrf52-usb-set-config.md │ ├── nrf52-usb-setup-stage.md │ ├── nrf52-usb-stack-overflow-protection.md │ ├── nrf52-usb-supporting-standard-requests.md │ ├── nrf52-usb-task-state.md │ ├── nrf52-usb-usb-endpoints.md │ ├── nrf52-usb-usb-enumeration.md │ ├── nrf52-usb-usb-events.md │ ├── nrf52-usb-usb-specification.md │ ├── realtime-v8r-preparation.md │ ├── realtime-v8r-uart.md │ ├── realtime-withoutstd-println.md │ ├── realtime-withoutstd.md │ ├── rustlatin.md │ ├── self-check.md │ ├── shapes.md │ ├── simple-db-knowledge.md │ ├── simple-db-solution.md │ ├── simple-db.md │ ├── tcp-server-log.md │ ├── tcp-server.md │ └── urls-match-result.md └── theme │ └── head.hbs ├── exercise-solutions ├── Cargo.lock ├── Cargo.toml ├── async-chat │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── client.rs │ │ ├── main.rs │ │ └── server.rs ├── calculator │ └── calc │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── connected-mailbox │ ├── Cargo.lock │ ├── Cargo.toml │ ├── simple-db │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── tests │ │ │ └── test.rs │ └── tcp-server │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── fizzbuzz │ ├── Cargo.toml │ └── src │ │ ├── examples │ │ ├── fizzbuzz.rs │ │ └── fizzbuzz_match.rs │ │ └── main.rs ├── green-yellow │ ├── Cargo.toml │ └── src │ │ └── bin │ │ ├── complete.rs │ │ ├── step2.rs │ │ ├── step3.rs │ │ ├── step4.rs │ │ ├── step5.rs │ │ └── step6.rs ├── iterators │ ├── Cargo.toml │ ├── numbers.txt │ └── src │ │ └── main.rs ├── kani-linked-list │ ├── .vscode │ │ └── settings.json │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── multi-threaded-mailbox │ ├── Cargo.lock │ ├── Cargo.toml │ ├── simple-db │ │ ├── Cargo.toml │ │ ├── src │ │ │ └── lib.rs │ │ └── tests │ │ │ └── test.rs │ ├── with-arc │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── with-scoped-threads │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── rustlatin │ ├── step1 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── step2 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── step3 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── step4 │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── shapes-part-1 │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── shapes-part-2 │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── shapes-part-3 │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── simple-db │ ├── step2 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── step4a │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── step4b │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── step4c │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── tcp-server-exercises │ ├── Cargo.toml │ └── src │ │ └── bin │ │ ├── echo-log-arc-mutex.rs │ │ ├── echo-log-scope.rs │ │ └── echo.rs └── urls-match-result │ ├── Cargo.toml │ ├── examples │ ├── step_1.rs │ ├── step_2.rs │ ├── step_3.rs │ ├── step_4.rs │ └── step_5.rs │ └── src │ ├── data │ └── content.txt │ └── main.rs ├── exercise-templates ├── Cargo.lock ├── Cargo.toml ├── async-chat │ ├── step1 │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── client.rs │ │ │ ├── main.rs │ │ │ └── server.rs │ └── step2 │ │ ├── Cargo.toml │ │ └── src │ │ ├── client.rs │ │ ├── main.rs │ │ └── server.rs ├── iterators │ ├── Cargo.toml │ ├── numbers.txt │ └── src │ │ └── main.rs ├── rustlatin │ ├── step1 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── step2 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── step3 │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── step4 │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── tcp-echo-server │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs └── urls-match-result │ ├── Cargo.toml │ └── src │ ├── data │ └── content.txt │ └── main.rs ├── nrf52-code ├── boards │ ├── .gitignore │ ├── dk-solution │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src │ │ │ ├── errata.rs │ │ │ ├── lib.rs │ │ │ ├── peripheral.rs │ │ │ └── usbd.rs │ ├── dk │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src │ │ │ ├── errata.rs │ │ │ ├── lib.rs │ │ │ ├── peripheral.rs │ │ │ └── usbd.rs │ ├── dongle-fw │ │ └── README.md │ └── dongle │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── build.rs │ │ ├── memory.x │ │ └── src │ │ └── lib.rs ├── consts │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── hal-app │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── .vscode │ │ ├── .cortex-debug.registers.state.json │ │ ├── launch.json │ │ └── settings.json │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ ├── blinky.rs │ │ ├── buttons.rs │ │ ├── hello.rs │ │ ├── led.rs │ │ ├── panic.rs │ │ └── stack_overflow.rs │ │ └── lib.rs ├── loopback-fw │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── puzzle-fw │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── main.rs ├── radio-app │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── .vscode │ │ ├── .cortex-debug.registers.state.json │ │ ├── launch.json │ │ └── settings.json │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ ├── blinky.rs │ │ ├── hello.rs │ │ ├── led.rs │ │ ├── panic.rs │ │ ├── radio-puzzle-1.rs │ │ ├── radio-puzzle-2.rs │ │ ├── radio-puzzle-3.rs │ │ ├── radio-puzzle-4.rs │ │ ├── radio-puzzle-5.rs │ │ ├── radio-puzzle-6.rs │ │ ├── radio-puzzle-7.rs │ │ ├── radio-puzzle-solution.rs │ │ ├── radio-puzzle.rs │ │ ├── radio-recv.rs │ │ ├── radio-send.rs │ │ └── stack_overflow.rs │ │ └── lib.rs ├── usb-app-solutions │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── Cargo.lock │ ├── Cargo.toml │ ├── src │ │ ├── bin │ │ │ ├── task-state.rs │ │ │ ├── usb-1.rs │ │ │ ├── usb-2.rs │ │ │ ├── usb-3.rs │ │ │ ├── usb-4.rs │ │ │ └── usb-5.rs │ │ └── lib.rs │ └── traces │ │ ├── linux-configured.txt │ │ ├── linux-enumeration.txt │ │ ├── macos-configured.txt │ │ ├── macos-enumeration.txt │ │ ├── win-configured.txt │ │ └── win-enumeration.txt ├── usb-app │ ├── .cargo │ │ └── config.toml │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── bin │ │ ├── events.rs │ │ ├── hello.rs │ │ ├── rtic-hello.rs │ │ ├── stack_overflow.rs │ │ ├── task-state.rs │ │ ├── usb-1.rs │ │ ├── usb-2.rs │ │ ├── usb-3.rs │ │ ├── usb-4.rs │ │ └── vec.rs │ │ └── lib.rs ├── usb-lib-solutions │ ├── complete │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── get-descriptor-config │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src │ │ │ └── lib.rs │ ├── get-device │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src │ │ │ └── lib.rs │ └── set-config │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src │ │ └── lib.rs └── usb-lib │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ └── lib.rs ├── qemu-code └── uart-driver │ ├── .cargo │ └── config.toml │ ├── .gitignore │ ├── .vscode │ └── settings.json │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── criticalup.toml │ ├── memory.x │ └── src │ ├── lib.rs │ ├── main.rs │ ├── uart_driver.rs │ └── uart_driver_solution.rs ├── tools └── tcp-client │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ └── main.rs └── xtask ├── .cargo └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src ├── main.rs └── tasks.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path xtask/Cargo.toml --" -------------------------------------------------------------------------------- /.github/workflows/weekly-canary-build.yml: -------------------------------------------------------------------------------- 1 | name: Weekly Canary Build 2 | 3 | env: 4 | CARGO_TERM_COLOR: always # We want colors in our CI output 5 | CARGO_INCREMENTAL: 0 # Don't waste time writing out incremental build files 6 | CARGO_PROFILE_TEST_DEBUG: 0 # These are thrown away anyways, don't produce them 7 | 8 | on: 9 | schedule: 10 | - cron: '0 0 * * Mon' 11 | 12 | jobs: 13 | weekly-canary-build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | rust-channel: [stable, beta, nightly] 18 | runs-on: ubuntu-24.04 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Install tools 23 | uses: taiki-e/install-action@v2 24 | with: 25 | tool: mdslides@0.3,mdbook@0.4,mdbook-mermaid@0.12,flip-link@0.1.10 26 | 27 | # `minimal` profile avoids downloading `rustdocs`, `clippy`, etc. 28 | - name: Install targets, update, set default Rust 29 | run: | 30 | rustup set profile minimal 31 | rustup update ${{ matrix.rust-channel }} 32 | rustup default ${{ matrix.rust-channel }} 33 | rustup component add rust-src 34 | rustup component add rustfmt 35 | rustup target add thumbv7em-none-eabihf 36 | 37 | - name: Find slug name 38 | run: | 39 | slug=$(./describe.sh "${GITHUB_REF}") 40 | echo "Building with slug '${slug}'" 41 | echo "slug=${slug}" >> "${GITHUB_ENV}" 42 | 43 | - name: Build and test 44 | env: # Or as an environment variable 45 | HIDDEN_MESSAGE: ${{ secrets.HIDDEN_MESSAGE }} 46 | run: | 47 | ./build.sh "./rust-exercises-${{ env.slug }}" 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | output.zip 3 | exercise-book/book 4 | exercise-templates/target 5 | exercise-solutions/*/target 6 | exercise-templates/Cargo.lock 7 | exercise-solutions/target 8 | exercise-solutions/Cargo.lock 9 | tools/*/target 10 | html/ 11 | mdbook 12 | mdbook-mermaid 13 | *.backup 14 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | / /latest/book 2 | /:version /:version/book 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | 6 | # This file is a helper file to help build and test local builds of this repository. 7 | 8 | # Load all build functions 9 | # All of the build/testing logic should be defined inside build_fns.sh 10 | . ./build_fns.sh 11 | 12 | 13 | # Check the formatting 14 | check_fmt 15 | 16 | # Build and test the solutions 17 | pushd exercise-solutions 18 | test_examples 19 | pushd connected-mailbox 20 | test_standalone 21 | popd 22 | pushd multi-threaded-mailbox 23 | test_standalone 24 | popd 25 | popd 26 | 27 | # Build from source because armv8r-none-eabihf isn't Tier 2 28 | pushd qemu-code 29 | pushd uart-driver 30 | build_qemu_core 31 | popd 32 | popd 33 | 34 | pushd nrf52-code 35 | pushd boards/dk 36 | build_thumbv7em 37 | popd 38 | pushd boards/dk-solution 39 | build_thumbv7em 40 | popd 41 | pushd boards/dongle 42 | build_thumbv7em 43 | popd 44 | pushd radio-app 45 | build_thumbv7em 46 | popd 47 | for i in usb-lib-solutions/*; do 48 | pushd "$i" 49 | build_test_thumbv7em 50 | popd 51 | done 52 | pushd usb-lib 53 | build_thumbv7em 54 | popd 55 | pushd usb-app 56 | build_thumbv7em 57 | popd 58 | pushd usb-app-solutions 59 | build_thumbv7em 60 | popd 61 | pushd consts 62 | build_thumbv7em 63 | popd 64 | pushd puzzle-fw 65 | build_thumbv7em 66 | popd 67 | pushd loopback-fw 68 | build_thumbv7em 69 | popd 70 | popd 71 | 72 | # Only check the templates (they will panic at run-time due to the use of todo!) 73 | pushd exercise-templates 74 | check_templates 75 | popd 76 | 77 | # Build and test the mdbook build 78 | pushd exercise-book 79 | mdbook_test_build 80 | popd 81 | 82 | # Zip the output 83 | zip_output -------------------------------------------------------------------------------- /describe.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | GIVEN_REF=$1 6 | 7 | case "${GIVEN_REF}" in 8 | refs/heads/*) 9 | slug="$(git branch --show)-$(git rev-parse --short HEAD)" 10 | ;; 11 | refs/tags/*) 12 | slug="$(echo "${GIVEN_REF}" | awk '{split($0,a,"/"); print a[3]}')" 13 | ;; 14 | refs/pull/*/merge) 15 | slug="pr-$(echo "${GIVEN_REF}" | awk '{split($0,a,"/"); print a[3]}')-$(git rev-parse --short HEAD)" 16 | ;; 17 | esac 18 | 19 | echo "${slug}" 20 | -------------------------------------------------------------------------------- /exercise-book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Ferrous Systems"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Rust Exercises" 7 | 8 | [preprocessor.mermaid] 9 | command = "mdbook-mermaid" 10 | 11 | [rust] 12 | edition = "2021" 13 | 14 | [output.html.playground] 15 | copyable = true # include the copy button for copying code snippets 16 | editable = true # allow the code editor to work 17 | line-numbers = true # turn on line numbers 18 | -------------------------------------------------------------------------------- /exercise-book/src/async-chat/implementing_a_client.md: -------------------------------------------------------------------------------- 1 | ## Implementing a client 2 | 3 | Since the protocol is line-based, implementing a client for the chat is straightforward: 4 | 5 | * Lines read from stdin should be sent over the socket. 6 | * Lines read from the socket should be echoed to stdout. 7 | 8 | Although async does not significantly affect client performance (as unlike the server, the client interacts solely with one user and only needs limited concurrency), async is still useful for managing concurrency! 9 | 10 | The client has to read from stdin and the socket *simultaneously*. 11 | Programming this with threads is cumbersome, especially when implementing a clean shutdown. 12 | With async, the `select!` macro is all that is needed. 13 | 14 | 15 | ```rust,ignore 16 | # extern crate tokio; 17 | use tokio::{ 18 | io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader}, 19 | net::{TcpStream, ToSocketAddrs}, 20 | }; 21 | 22 | type Result = std::result::Result>; 23 | 24 | // main 25 | async fn run() -> Result<()> { 26 | try_main("127.0.0.1:8080").await 27 | } 28 | 29 | async fn try_main(addr: impl ToSocketAddrs) -> Result<()> { 30 | let stream = TcpStream::connect(addr).await?; 31 | let (reader, mut writer) = stream.into_split(); 32 | 33 | let mut lines_from_server = BufReader::new(reader).lines(); // 2 34 | let mut lines_from_stdin = BufReader::new(stdin()).lines(); // 3 35 | 36 | loop { 37 | tokio::select! { // 4 38 | line = lines_from_server.next_line() => match line { 39 | Ok(Some(line)) => { 40 | println!("{}", line); 41 | }, 42 | Ok(None) => break, 43 | Err(e) => eprintln!("Error {:?}:", e), 44 | }, 45 | line = lines_from_stdin.next_line() => match line { 46 | Ok(Some(line)) => { 47 | writer.write_all(line.as_bytes()).await?; 48 | writer.write_all(b"\n").await?; 49 | }, 50 | Ok(None) => break, 51 | Err(e) => eprintln!("Error {:?}:", e), 52 | } 53 | } 54 | } 55 | Ok(()) 56 | } 57 | ``` 58 | 59 | 1. Here we split `TcpStream` into read and write halves. 60 | 2. We create a stream of lines for the socket. 61 | 2. We create a stream of lines for stdin. 62 | 4. In the main select loop, we print the lines we receive from the server and send the lines we read from the console. 63 | -------------------------------------------------------------------------------- /exercise-book/src/async-chat/index.md: -------------------------------------------------------------------------------- 1 | # Writing an async chat 2 | 3 | Nothing is simpler than creating a chat server, right? 4 | Not quite, chat servers expose you to all the fun of asynchronous programming: 5 | 6 | How will the server handle clients connecting concurrently? 7 | 8 | How will it handle them disconnecting? 9 | 10 | How will it distribute the messages? 11 | 12 | This tutorial explains how to write a chat server in `tokio`. 13 | -------------------------------------------------------------------------------- /exercise-book/src/async-chat/sending_messages.md: -------------------------------------------------------------------------------- 1 | ## Sending Messages 2 | 3 | Now it's time to implement the other half -- sending messages. 4 | As a rule of thumb, only a single task should write to each `TcpStream`. 5 | This way, we also have compartmentalised that activity and automatically serialize all outgoing messages. 6 | So let's create a `connection_writer_loop` task which receives messages over a channel and writes them to the socket. 7 | If Alice and Charley send two messages to Bob at the same time, Bob will see the messages in the same order as they arrive in the channel. 8 | 9 | ```rust,ignore 10 | # extern crate tokio; 11 | # use std::{ 12 | # collections::hash_map::{Entry, HashMap}, 13 | # future::Future, 14 | # }; 15 | # 16 | # use tokio::{ 17 | # io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 18 | # net::{tcp::OwnedWriteHalf, TcpListener, TcpStream, ToSocketAddrs}, 19 | # sync::oneshot, 20 | # task, 21 | # }; 22 | # 23 | # type Result = std::result::Result>; 24 | use tokio::sync::mpsc; // 1 25 | 26 | type Sender = mpsc::UnboundedSender; // 2 27 | type Receiver = mpsc::UnboundedReceiver; 28 | 29 | async fn connection_writer_loop( 30 | messages: &mut Receiver, 31 | stream: &mut OwnedWriteHalf // 3 32 | ) -> Result<()> { 33 | loop { 34 | let msg = messages.recv().await; 35 | match msg { 36 | Some(msg) => stream.write_all(msg.as_bytes()).await?, 37 | None => break, 38 | } 39 | } 40 | Ok(()) 41 | } 42 | ``` 43 | 44 | 1. We will use `mpsc` channels from `tokio`. 45 | 2. For simplicity, we will use `unbounded` channels, and won't be discussing backpressure in this tutorial. 46 | 3. As `connection_loop` and `connection_writer_loop` share the same `TcpStream`, we use splitting. We'll glue this together later. 47 | 48 | ```rust,ignore 49 | # extern crate tokio; 50 | # use tokio::net::TcpStream; 51 | # async fn connection_loop(stream: TcpStream) { 52 | # 53 | use tokio::net::tcp; 54 | let (reader, writer): (tcp::OwnedReadHalf, tcp::OwnedWriteHalf) = stream.into_split(); 55 | # } 56 | ``` 57 | -------------------------------------------------------------------------------- /exercise-book/src/async-chat/specification.md: -------------------------------------------------------------------------------- 1 | # Specification and Getting Started 2 | 3 | ## Specification 4 | 5 | The chat uses a simple text protocol over TCP. 6 | The protocol consists of utf-8 messages, separated by `\n`. 7 | 8 | The client connects to the server and sends login as a first line. 9 | After that, the client can send messages to other clients using the following syntax: 10 | 11 | ```text 12 | login1, login2, ... loginN: message 13 | ``` 14 | 15 | Each of the specified clients then receives a `from login: message` message. 16 | 17 | A possible session might look like this 18 | 19 | ```text 20 | On Alice's computer: | On Bob's computer: 21 | 22 | > alice | > bob 23 | > bob: hello < from alice: hello 24 | | > alice, bob: hi! 25 | < from bob: hi! 26 | < from bob: hi! | 27 | ``` 28 | 29 | The main challenge for the chat server is keeping track of many concurrent connections. 30 | The main challenge for the chat client is managing concurrent outgoing messages, incoming messages and user's typing. 31 | 32 | ## Getting Started 33 | 34 | Let's create a new Cargo project: 35 | 36 | ```bash 37 | $ cargo new a-chat 38 | $ cd a-chat 39 | ``` 40 | 41 | Add the following lines to `Cargo.toml`: 42 | 43 | ```toml 44 | [dependencies] 45 | tokio = { version = "1", features = ["full"] } 46 | ``` 47 | -------------------------------------------------------------------------------- /exercise-book/src/fizzbuzz-cheat-sheet.md: -------------------------------------------------------------------------------- 1 | # Fizzbuzz Cheat Sheet 2 | 3 | This is a syntax cheat sheet to be used with the Fizzbuzz exercise. 4 | 5 | ## Variables 6 | 7 | ```rust 8 | let thing = 42; // an immutable variable 9 | let mut thing = 43; // a mutable variable 10 | ``` 11 | 12 | ## Functions 13 | 14 | ```rust 15 | // a function with one argument, no return. 16 | fn number_crunch(input: u32) { 17 | // function body 18 | } 19 | 20 | // a function with two arguments and a return type. 21 | fn division_machine(dividend: f32, divisor: f32) -> f32 { 22 | // function body 23 | let quotient = dividend / divisor; 24 | 25 | // return line does not have a semi-colon! 26 | quotient 27 | } 28 | 29 | fn main() { 30 | 31 | let cookies = 1000.0_f32; 32 | let cookie_monsters = 1.0_f32; 33 | 34 | // calling a function 35 | let number = division_machine(cookies, cookie_monsters); 36 | } 37 | ``` 38 | 39 | ## `for` loops and ranges 40 | 41 | ```rust 42 | // for loop with end-exclusive range 43 | for i in 0..10 { 44 | // do this 45 | } 46 | 47 | // for loop with end-inclusive range 48 | for j in 0..=10 { 49 | // do that 50 | } 51 | ``` 52 | 53 | ## if - statements 54 | 55 | ```rust 56 | let number = 4; 57 | 58 | if number == 4 { 59 | println!("This happens"); 60 | } else if number == 5 { 61 | println!("Something else happens"); 62 | } else { 63 | println!("Or this happens"); 64 | } 65 | 66 | // condition can be anything that evaluates to a bool 67 | 68 | ``` 69 | 70 | ## Operators (Selection) 71 | 72 | |Operator |Example |Explanation | 73 | |--------- |--------- |--------- | 74 | |`!=` |`expr != expr` |Nonequality comparison | 75 | |`==` |`expr == expr` |Equality comparison | 76 | |`&&` |`expr && expr` |Short-circuiting logical AND | 77 | |`\|\|` |`expr \|\| expr` |Short-circuiting logical OR | 78 | |`%` |`expr % expr` |Arithmetic remainder | 79 | |`/` | `expr / expr` |Arithmetic division | 80 | -------------------------------------------------------------------------------- /exercise-book/src/fizzbuzz-match.md: -------------------------------------------------------------------------------- 1 | # Fizzbuzz with `match` 2 | 3 | In this exercise you will modify your previously written fizzbuzz to use `match` statements instead of `if` statements. 4 | 5 | ## After completing this exercise you are able to 6 | 7 | - use `match` statements 8 | - define a tuple 9 | 10 | ## Prerequisites 11 | 12 | For completing this exercise you need to have 13 | 14 | - a working fizzbuzz 15 | 16 | ## Task 17 | 18 | Rewrite the body of `fn fizzbuzz()` so the different cases are not distinguished with `if` statements, but with pattern matching of a tuple containing the remainders. 19 | 20 | If you need it, we have provided a [complete solution](../../exercise-solutions/fizzbuzz/src/examples/fizzbuzz_match.rs) for this exercise. 21 | 22 | ## Knowledge 23 | 24 | ### Tuple 25 | 26 | A tuple is a collection of values of different types. Tuples are constructed using parentheses (), and each tuple itself is a value with type signature (T1, T2, ...), where T1, T2 are the types of its members. Functions can use tuples to return multiple values, as tuples can hold any number of values, including the `_` placeholder 27 | 28 | ```rust 29 | // A tuple with a bunch of different types. 30 | let long_tuple = (1u8, 2u16, 3u32, 4u64, 31 | -1i8, -2i16, -3i32, -4i64, 32 | 0.1f32, 0.2f64, 33 | 'a', true); 34 | ``` 35 | 36 | ## Step-by-Step-Solution 37 | 38 | We assume you have deleted the entire function body of `fn fizzbuzz()` before you get started. 39 | 40 | ### Step 1: The Tuple 41 | 42 | Define a tuple that consists of the remainder of the integer `i` divided by 3 and the integer `i` divided by 5. 43 | 44 |
45 | Solution 46 | 47 | ```rust 48 | # let i = 10; 49 | let remainders = (i%3, i%5); 50 | ``` 51 | 52 |
53 | 54 | ### Step 2: Add the `match` statement with its arms 55 | 56 | The the for us relevant patterns of the tuple that we match for are a combination of `0` and the placeholder `_` (underscore). `_` stands for any value. Think about what combinations of `0` and `_` represent which rules. Add the `match` arms accordingly. 57 | 58 |
59 | Solution 60 | 61 | ```rust 62 | # fn fizzbuzz(i: i32) -> String { 63 | # let remainders = (i%3, i%5); 64 | 65 | match remainders { 66 | (0, 0) => format!("FizzBuzz"), 67 | (0, _) => format!("Fizz"), 68 | (_, 0) => format!("Buzz"), 69 | (_, _) => format!("{}", i), 70 | } 71 | # } 72 | ``` 73 | 74 |
75 | -------------------------------------------------------------------------------- /exercise-book/src/img/nrf52840_dk_board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ferrous-systems/rust-exercises/1ce932395d3362de61de1774b189942e8ec1bcdf/exercise-book/src/img/nrf52840_dk_board.jpg -------------------------------------------------------------------------------- /exercise-book/src/img/puzzle_illustration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ferrous-systems/rust-exercises/1ce932395d3362de61de1774b189942e8ec1bcdf/exercise-book/src/img/puzzle_illustration.jpg -------------------------------------------------------------------------------- /exercise-book/src/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This book contains a collection of Rust Exercises, written by Ferrous Systems. See [ferrous-systems.com/training](https://ferrous-systems.com/training) for more details or a custom quote. You can view this material on-line at . 4 | 5 | We use these exercises as part of our [Rust Training](https://ferrous-systems.com/training/), but you are welcome to try them for yourself as well. 6 | 7 | ## Source Code 8 | 9 | The source code for this book can be found at . It is open sourced as a contribution to the growth of the Rust language. 10 | 11 | If you wish to fund further development of the course, why not [book a training with us](https://ferrous-systems.com/training/)! 12 | 13 | ## Icons and Formatting we use 14 | 15 | We use Icons to mark different kinds of information in the book: 16 | 17 | - ✅ Call for action 18 | - ❗️ Warnings, Details that require special attention 19 | - 🔎 Knowledge, that gets you deeper into the subject, but you do not have to understand it completely to proceed. 20 | - 💬 Descriptions for Accessibility 21 | 22 | > Note: Notes like this one contain helpful information 23 | 24 | ## Course Material 25 | 26 | We have attempted to make our material as inclusive as possible. This means, that some information is available in several forms, for example as a picture and as a text description. We also use icons so that different kinds of information are visually distinguishable on the first glance. If you are on a course and have accessibility needs that are not covered, please let us know. 27 | 28 | ## License 29 | 30 | [![Creative Commons License](https://i.creativecommons.org/l/by-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-sa/4.0/) 31 | 32 | This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/). 33 | 34 | We encourage the use of this material, under the terms of the above license, in the production and/or delivery of commercial or open-source Rust training programmes. 35 | 36 | Copyright (c) Ferrous Systems, 2024 37 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-hal-exercise.md: -------------------------------------------------------------------------------- 1 | # nRF52 HAL Exercise 2 | 3 | In this exercise you'll learn to: 4 | 5 | - use a HAL to provide features in a BSP 6 | - configure GPIO pins using the nRF52 HAL 7 | 8 | To test your BSP changes, you will modify a small example: `hal-app/src/bin/blinky.rs` 9 | 10 | You will need an nRF52840 Development Kit for this exercise, but not the nRF USB dongle. 11 | 12 | If you haven't completed the Radio Exercise, you should start there, and go at least as far as completing the "Timers and Time" section. 13 | 14 | ## The nRF52840 Development Kit 15 | 16 | This is the larger development board. 17 | 18 | The board has two USB ports: J2 and J3 and an on-board J-Link programmer / debugger -- [there are instructions to identify the ports in a previous section][id-ports]. USB port J2 is the J-Link's USB port. USB port J3 is the nRF52840's USB port. Connect the Development Kit to your computer using the **J2** port. 19 | 20 | [id-ports]: ./nrf52-hardware.md#nrf52840-development-kit-dk 21 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-preparation.md: -------------------------------------------------------------------------------- 1 | # nRF52 Preparation 2 | 3 | This chapter contains information about the nRF52-based exercises, the required hardware and an installation guide. 4 | 5 | ## Required Hardware 6 | 7 | - [nRF52840 Development Kit (DK)](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) 8 | - [nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle) 9 | - 2 micro-USB cables 10 | - ❗️ make sure you're using micro usb cables which can transmit data (some are charging-only; these are not suitable for these exercises) 11 | - 2 corresponding available USB ports on your laptop / PC (you can use a USB hub if you don't have enough ports) 12 | 13 | In our nRF52-focussed exercises we will use both the nRF52840 Development Kit (DK) and the nRF52840 Dongle. We'll mainly develop programs for the DK and use the Dongle to assist with some exercises. 14 | 15 | For the span of these exercises keep the nRF52840 DK connected to your PC using a micro-USB cable. Connect the USB cable to the J2 port on the nRF52840 DK. 16 | 17 | ![Labeled Diagram of the nRF52840 Development Kit (DK)](img/nrf52840_dk_board.jpg) 18 | 19 | ## Starter code 20 | 21 | Project templates and starter code for our trainings can be found at [in this repo](https://github.com/ferrous-systems/rust-exercises). 22 | 23 | ## Required tools 24 | 25 | Please [install the required tools](./nrf52-tools.md) before the lesson starts. 26 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-alt-containers.md: -------------------------------------------------------------------------------- 1 | # Alternative containers 2 | 3 | ## Modify-in-place 4 | 5 | If you solved the puzzle using a `Vec` buffer you can try solving it without the buffer as a stretch goal. You may find the [slice methods][slice] that let you mutate a `Packet`'s data useful, but remember that the first six bytes of your `Packet` will be the random device address - you can't decrypt those! A solution that does not use a `heapless:Vec` buffer can be found in the `src/bin/radio-puzzle-solution-2.rs` file. 6 | 7 | ## Using `liballoc::BTreeMap` 8 | 9 | If you solved the puzzle using a `heapless::Vec` buffer and a `heapless::LinearMap` and you still need something else to try, you could look at the [`Vec`][vec] and [`BTreeMap`][btreemap] types contained within `liballoc`. This will require you to set up a global memory allocator, like [`embedded-alloc`][embedded-alloc]. 10 | 11 | [vec]: https://doc.rust-lang.org/alloc/vec/struct.Vec.html 12 | [btreemap]: https://doc.rust-lang.org/alloc/collections/struct.BTreeMap.html 13 | [embedded-alloc]: https://github.com/rust-embedded/embedded-alloc 14 | [slice]: https://doc.rust-lang.org/std/primitive.slice.html#methods 15 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-building-program.md: -------------------------------------------------------------------------------- 1 | # Building an Embedded Program 2 | 3 | The default in a Cargo project is to compile for the host (native compilation). The [`nrf52-code/radio-app`](../../nrf52-code/radio-app) project has been configured for cross compilation to the ARM Cortex-M4 architecture. This configuration can be seen in the Cargo configuration file (`.cargo/config`): 4 | 5 | ```text 6 | # .cargo/config 7 | [build] 8 | target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 9 | ``` 10 | 11 | The target `thumbv7em-none-eabihf` can be broken down as: 12 | 13 | * `thumbv7em` - we generate instructions for the Armv7E-M architecture running in Thumb-2 mode (actually the only supported mode on this architecture) 14 | * `none` - there is no Operating System 15 | * `eabihf` - use the ARM *Embedded Application Binary Interface*, with *Hard Float* support 16 | * `f32` and `f64` can be passed to functions in FPU registers (like `S0`), instead of in integer registers (like `R0`) 17 | 18 | ✅ Inside the folder [`nrf52-code/radio-app`](../../nrf52-code/radio-app), use the following command to cross compile the program to the ARM Cortex-M4 architecture. 19 | 20 | ```console 21 | cargo build --bin hello 22 | ``` 23 | 24 | The output of the compilation process will be an ELF (Executable and Linkable Format) file. The file will be placed in the `target/thumbv7em-none-eabihf` directory. 25 | 26 | ✅ Run `$ file target/thumbv7em-none-eabihf/debug/hello` and compare if your output is as expected. 27 | 28 | Expected output: 29 | 30 | ```console 31 | $ file target/thumbv7em-none-eabihf/debug/hello 32 | hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped 33 | ``` 34 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-exercise.md: -------------------------------------------------------------------------------- 1 | # nRF52 Radio Exercise 2 | 3 | In this exercise you'll get familiar with: 4 | 5 | - the structure of embedded Rust programs, 6 | - the existing embedded Rust tooling, and 7 | - embedded application development using a Board Support Package (BSP). 8 | 9 | To put these concepts in practice you'll write applications that use the radio functionality of the nRF52840 microcontroller. 10 | 11 | You should have acquired two development boards for your training. We'll use both in the this radio exercise. 12 | 13 | ## The nRF52840 Development Kit 14 | 15 | This is the larger development board. 16 | 17 | The board has two USB ports: J2 and J3 and an on-board J-Link programmer / debugger -- [there are instructions to identify the ports in a previous section][id-ports]. USB port J2 is the J-Link's USB port. USB port J3 is the nRF52840's USB port. Connect the Development Kit to your computer using the **J2** port. 18 | 19 | [id-ports]: ./nrf52-hardware.md#nrf52840-development-kit-dk 20 | 21 | ## The nRF52840 Dongle 22 | 23 | This is the smaller development board. 24 | 25 | The board has the form factor of a USB stick and can be directly connected to one of the USB ports of your PC / laptop. Do **not** connect it just yet. 26 | 27 | ## The nRF52840 28 | 29 | Both development boards have an nRF52840 microcontroller. Here are some details that are relevant to these exercises: 30 | 31 | - single core ARM Cortex-M4 processor clocked at 64 MHz 32 | - 1 MB of Flash (at address `0x0000_0000`) 33 | - 256 KB of RAM (at address `0x2000_0000`) 34 | - IEEE 802.15.4 and BLE (Bluetooth Low Energy) compatible radio 35 | - USB controller (device function) 36 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-in.md: -------------------------------------------------------------------------------- 1 | # Radio In 2 | 3 | In this section we'll explore the `recv_timeout` method of the Radio API. As the name implies, this is used to listen for packets. The method will block the program execution until a packet is received or the specified timeout has expired. We'll continue to use the Dongle in this section; it should be running the `loopback` application; and `cargo xtask serial-term` should also be running in the background. 4 | 5 | The `loopback` application running on the Dongle will broadcast a radio packet after receiving one over channel 20. The contents of this outgoing packet will be the contents of the received one but reversed. 6 | 7 | ✅ Open the [`nrf52-code/radio-app/src/bin/radio-recv.rs`](../../nrf52-code/radio-app/src/bin/radio-recv.rs) file. Make sure that the Dongle and the Radio are set to the same channel. Click the "Run" button. 8 | 9 | The Dongle does not inspect the contents of your packet and does not require them to be ASCII, or UTF-8. It will simply send a packet back containing the same bytes it received, except the bytes will be in reverse order to how you sent it. 10 | 11 | That is, if you send `b"olleh"`, it will send back `b"hello"`. 12 | 13 | The Dongle will respond as soon as it receives a packet. If you insert a delay between the `send` operation and the `recv` operation in the `radio-recv` program this will result in the DK not seeing the Dongle's response. So try this: 14 | 15 | ✅ Add a `timer.wait(x)` call before the `recv_timeout` call, where `x` is `core::time::Duration`; try different lengths of time for `x` and observe what happens. 16 | 17 | Having log statements between `send` and `recv_timeout` can also cause packets to be missed so try to keep those two calls as close to each other as possible and with as little code in between as possible. 18 | 19 | > NOTE Packet loss can always occur in wireless networks, even if the radios are close to each other. The `Radio` API we are using will not detect lost packets because it does not implement IEEE 802.15.4 Acknowledgement Requests. For the next step in the exercise, we will use a new function to handle this for us. For the sake of other radio users, please do ensure you never call `send()` in a tight loop! 20 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-interrupt-handling.md: -------------------------------------------------------------------------------- 1 | # Interrupt handling 2 | 3 | If we haven't covered interrupt handling in your training, the `cortex-m-rt` crate provides attributes to declare exception and interrupt handlers: `#[exception]` and `#[interrupt]`. You can find documentation about these attributes and how to safely share data with interrupt handlers using Mutexes in the ["Concurrency" chapter][concurrency] of the Embedded Rust book. 4 | 5 | Another way to deal with interrupts is to use a framework like Real-Time Interrupt-driven Concurrency ([RTIC]); this framework has a [book] that explains how you can build reactive applications using interrupts. We use this framework in the "USB" exercise. 6 | 7 | [concurrency]: https://rust-embedded.github.io/book/concurrency/index.html 8 | [RTIC]: https://crates.io/crates/cortex-m-rtic 9 | [book]: https://rtic.rs/2/book/en/ 10 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-link-quality.md: -------------------------------------------------------------------------------- 1 | # Link Quality Indicator (LQI) 2 | 3 | ```console 4 | received 7 bytes (CRC=Ok(0x2459), LQI=60) 5 | ``` 6 | 7 | ✅ Now run the `radio-send` program several times with different variations to explore how LQI can be influenced 8 | 9 | - change the distance between the Dongle and the DK -- move the DK closer to or further away from the Dongle. 10 | - change the transmit power 11 | - change the channel 12 | - change the length of the packet 13 | - different combinations of all of the above 14 | 15 | Take note of how LQI changes with these changes. Does packet loss occur in any of these configurations? 16 | 17 | > NOTE if you decide to send many packets in a single program then you should use the `Timer` API to insert a delay of at least five milliseconds between the transmissions. This is required because the Dongle will use the radio medium right after it receives a packet. Not including the delay will result in the Dongle missing packets 18 | 19 | 802.15.4 radios are often used in mesh networks like Wireless Sensors Networks (WSN). The devices, or *nodes*, in these networks can be mobile so the distance between nodes can change in time. To prevent a link between two nodes getting broken due to mobility the LQI metric is used to decide the transmission power -- if the metric degrades power should be increased, etc. At the same time, the nodes in these networks often need to be power efficient (e.g. are battery powered) so the transmission power is often set as low as possible -- again the LQI metric is used to pick an adequate transmission power. 20 | 21 | ## 🔎 802.15.4 compatibility 22 | 23 | The radio API we are using follows the PHY layer of the IEEE 802.15.4 specification, but it's missing MAC level features like addressing (each device gets its own address), opt-in acknowledgment (a transmitted packet must be acknowledged with a response acknowledgment packet; the packet is re-transmitted if the packet is not acknowledged in time). These MAC level features are not implemented *in hardware* (in the nRF52840 Radio peripheral) so they would need to be implemented in software to be fully IEEE 802.15.4 compliant. 24 | 25 | This is not an issue for these exercises but it's something to consider if you would like to continue from here and build a 802.15.4 compliant network API. 26 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-next-steps.md: -------------------------------------------------------------------------------- 1 | # Next Steps 2 | 3 | If you've already completed the main exercise tasks or would like to explore more on your own this section has some suggestions. 4 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-out.md: -------------------------------------------------------------------------------- 1 | # Radio Out 2 | 3 | In this section you'll send radio packets from the DK to the Dongle and get familiar with the different settings of the radio API. 4 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-panicking.md: -------------------------------------------------------------------------------- 1 | # Panicking 2 | 3 | ✅ Open the [`nrf52-code/radio-app/src/bin/panic.rs`](../../nrf52-code/radio-app/src/bin/panic.rs) file and click the "Run" button (or run with `cargo run --bin panic`). 4 | 5 | This program attempts to index an array beyond its length and this results in a panic. 6 | 7 | ```console 8 | $ cargo run --bin panic 9 | Compiling radio_app v0.0.0 (/Users/jonathan/Documents/ferrous-systems/rust-exercises/nrf52-code/radio-app) 10 | Finished `dev` profile [optimized + debuginfo] target(s) in 0.03s 11 | Running `probe-rs run --chip=nRF52840_xxAA --allow-erase-all --log-format=oneline target/thumbv7em-none-eabihf/debug/panic` 12 | Erasing ✔ 100% [####################] 12.00 KiB @ 18.71 KiB/s (took 1s) 13 | Programming ✔ 100% [####################] 12.00 KiB @ 14.22 KiB/s (took 1s) 14 | Finished in 1.49s 15 | 00:00:00.000000 [ERROR] panicked at src/bin/panic.rs:30:13: 16 | index out of bounds: the len is 3 but the index is 3 (radio_app src/lib.rs:8) 17 | `dk::fail()` called; exiting ... 18 | Frame 0: syscall1 @ 0x00000cac inline 19 | /Users/jonathan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cortex-m-semihosting-0.5.0/src/lib.rs:201:13 20 | Frame 1: report_exception @ 0x0000000000000caa inline 21 | /Users/jonathan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cortex-m-semihosting-0.5.0/src/macros.rs:28:9 22 | Frame 2: exit @ 0x0000000000000caa 23 | /Users/jonathan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/cortex-m-semihosting-0.5.0/src/debug.rs:74:25 24 | Frame 3: fail @ 0x0000043e 25 | /Users/jonathan/Documents/ferrous-systems/rust-exercises/nrf52-code/boards/dk/src/lib.rs:456:9 26 | Frame 4: @ 0x000c0a36 27 | ``` 28 | 29 | In `no_std` programs the behavior of panic is defined using the `#[panic_handler]` attribute. In the example, the *panic handler* is defined in the `radio-app/lib.rs` file, but we can change it: 30 | 31 | ✅ Change `radio-app/lib.rs` and change the panic panic handler, like: 32 | 33 | ```rust ignore 34 | #[panic_handler] 35 | fn panic(info: &core::panic::PanicInfo) -> ! { 36 | defmt::error!("Oops!! {}", defmt::Debug2Format(info)); 37 | dk::fail(); 38 | } 39 | ``` 40 | 41 | Now run the program again. Try again, but without printing the `info` variable. Can you print `info` without `defmt::Debug2Format(..)` wrapped around it? Why not? 42 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-parts-embedded-program.md: -------------------------------------------------------------------------------- 1 | # Parts of an Embedded Program 2 | 3 | We will look at the elements that distinguish an embedded Rust program from a desktop program. 4 | 5 | ✅ Open the [`nrf52-code/radio-app`](../../nrf52-code/radio-app) folder in VS Code. 6 | 7 | ```sh 8 | # or use "File > Open Folder" in VS Code 9 | code nrf52-code/radio-app 10 | ``` 11 | 12 | ✅ Then open the [`nrf52-code/radio-app/src/bin/hello.rs`](../../nrf52-code/radio-app/src/bin/hello.rs) file. 13 | 14 | ## Attributes 15 | 16 | In the file, you will find the following attributes: 17 | 18 | ### `#![no_std]` 19 | 20 | The `#![no_std]` language attribute indicates that the program will not make use of the standard library, the `std` crate. Instead it will use the `core` library, a subset of the standard library that does not depend on an underlying operating system (OS). 21 | 22 | ### `#![no_main]` 23 | 24 | The `#![no_main]` language attribute indicates that the program will use a custom entry point instead of the default `fn main() { .. }` one. 25 | 26 | ### `#[entry]` 27 | 28 | The `#[entry]` macro attribute marks the custom entry point of the program. The entry point must be a divergent function whose return type is the never type `!`. The function is not allowed to return; therefore the program is not allowed to terminate. The macro comes from the [cortex-m-rt crate](https://docs.rs/cortex-m-rt/0.7.5/cortex_m_rt/attr.entry.html) and is not part of the Rust language. 29 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-setup.md: -------------------------------------------------------------------------------- 1 | # Radio Setup 2 | 3 | ✅ Open the [`nrf52-code/radio-app/src/bin/radio-send.rs`](../../nrf52-code/radio-app/src/bin/radio-send.rs) file. 4 | 5 | ✅ First run the program `radio-send.rs` as it is. You should see new output in the output of `cargo xtask serial-term`, if you left your Dongle on channel 20. If you change your Dongle's channel to avoid interference, change to the channel to match in `radio-send.rs` before you run it. 6 | 7 | ```console 8 | $ cargo xtask serial-term 9 | deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback-fw 10 | received 5 bytes (CRC=Ok(0xdad9), LQI=53) 11 | ``` 12 | 13 | The program broadcasts a radio packet that contains the 5-byte string `Hello` over channel 20 (which has a center frequency of 2450 MHz). The `loopback` program running on the Dongle is listening to all packets sent over channel 20; every time it receives a new packet it reports its length and the Link Quality Indicator (LQI) metric of the transmission over the USB/serial interface. As the name implies the LQI metric indicates how good the connection between the sender and the receiver is (a higher number means better quality). 14 | 15 | Because of how our firmware generates a *semihosting exception* to tell our flashing tool (`probe-run`) when the firmware has finished running, if you load the `radio-send` firmware and then power-cycle the nRF52840-DK, the firmware will enter a reboot loop and repeatedly send a packet. This is because nothing catches the *semihosting exception* and so the CPU reboots, sends a packet, and then tries another *semihosting exception*. 16 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-radio-time.md: -------------------------------------------------------------------------------- 1 | # Timers and Time 2 | 3 | Next we'll look into the time related APIs exposed by the `dk` HAL. 4 | 5 | ✅ Open the [`nrf52-code/radio-app/src/bin/blinky.rs`](../../nrf52-code/radio-app/src/bin/blinky.rs) file. 6 | 7 | This program will blink (turn on and off) one of the LEDs on the board. The time interval between each toggle operation is one second. This wait time between consecutive operations is generated by the blocking `timer.wait` operation. This function call will block the program execution for the specified [`Duration`] argument. 8 | 9 | [`Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html 10 | 11 | The other time related API exposed by the `dk` HAL is the `dk::uptime` function. This function returns the time that has elapsed since the call to the `dk::init` function. This function is used in the program to log the time of each LED toggle operation. 12 | 13 | ✅ Try changing the `Duration` value passed to `Timer.wait`. Try values larger than one second and smaller than one second. What values of `Duration` make the blinking imperceptible? 14 | 15 | ❗ If you set the duration to below 2ms, try removing the `defmt::println!` command in the loop. Too much logging will fill the logging buffer and cause the loop to slow down, resulting in the blink frequency to reduce after a while. 16 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-references-resources.md: -------------------------------------------------------------------------------- 1 | # References and Resources 2 | 3 | ## Radio Project 4 | 5 | - [nRF52840 Product Specification](https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html) 6 | - The [Embedded Rust Book][embedded rust] is a great learning resource, especially the Concurrency chapter. 7 | - If you are looking to write an interrupt handler, look at the [`#[interrupt]` attribute][interrupt]. All interrupts implemented by the nrf52840 HAL are listed in [`nrf52840-pac/src/lib.rs`][pac]. It is also recommended that you work through the USB exercise to learn about [RTIC][rtic]. 8 | 9 | [pac]: https://github.com/nrf-rs/nrf52840-pac/blob/9558a3ed032b2aec7e57c2f42330f1dee0000a04/src/lib.rs#L167 10 | [interrupt]: https://docs.rs/cortex-m-rt/0.7.5/cortex_m_rt/attr.interrupt.html 11 | [rtic]: https://rtic.rs/2/book/en/ 12 | [embedded rust]: https://rust-embedded.github.io/book/ 13 | 14 | ## USB Project 15 | 16 | - [nRF52840 Product Specification](https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html) 17 | - [Universal Serial Bus (USB) Specification Revision 2.0](https://www.usb.org/document-library/usb-20-specification) 18 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-cargo-build.md: -------------------------------------------------------------------------------- 1 | # `cargo build` fails to link 2 | 3 | If you have configured Cargo to use sccache then you'll need to disable sccache support. Unset the `RUSTC_WRAPPER` variable in your environment *before* opening VS code. Run `cargo clean` from the Cargo workspace you are working from ([`nrf52-code/radio-app`](../../nrf52-code/radio-app) or [`nrf52-code/usb-app`](../../nrf52-code/usb-app)). Then open VS code. 4 | 5 | If you are on Windows and get linking errors like `LNK1201: error writing to program database`, then something in your target folder has become corrupt. A `cargo clean` should fix it. 6 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-cargo-flash.md: -------------------------------------------------------------------------------- 1 | # cargo-flash is not working 2 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-cargo-run-error.md: -------------------------------------------------------------------------------- 1 | # `cargo run` errors 2 | 3 | You may get one of these errors: 4 | 5 | - "Access denied (insufficient permissions)" (seen on macOS) 6 | - "USB error while taking control over USB device: Resource busy" (seen on Linux) 7 | 8 | ```console 9 | $ cargo run --bin usb-4 10 | Running `probe-rs run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/usb-4` 11 | Error: An error specific to a probe type occured: USB error while taking control over USB device: Access denied (insufficient permissions) 12 | 13 | Caused by: 14 | USB error while taking control over USB device: Access denied (insufficient permissions) 15 | ``` 16 | 17 | ```console 18 | $ cargo run --bin usb-4 19 | Running `probe-rs run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/usb-4` 20 | Error: An error specific to a probe type occured: USB error while taking control over USB device: Resource busy 21 | 22 | Caused by: 23 | USB error while taking control over USB device: Resource busy 24 | ``` 25 | 26 | All of them have the same root issue: You have another instance of the `cargo run` process running. 27 | 28 | It is not possible to have two or more instances of `cargo run` running. Terminate the old instance before executing `cargo run`. If you are using VS Code click the garbage icon ("Kill Terminal") on the top right corner of the terminal output window (located on the bottom of the screen). 29 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-cargo-size.md: -------------------------------------------------------------------------------- 1 | # `cargo-size` is not working 2 | 3 | ```console 4 | $ cargo size --bin hello 5 | Failed to execute tool: size 6 | No such file or directory (os error 2) 7 | ``` 8 | 9 | `llvm-tools` is not installed. Install it with `rustup component add llvm-tools` 10 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-dongle-flash.md: -------------------------------------------------------------------------------- 1 | # dongle-flash is not working 2 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-location-info.md: -------------------------------------------------------------------------------- 1 | # `location info is incomplete` error 2 | 3 | Problem: Using cargo run --bin hello from within the [`nrf52-code/radio-app`](../../nrf52-code/radio-app) folder finishes compiling and starts up probe-rs. But then the following error is returned: 4 | 5 | ```sh 6 | Running `probe-rs run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/hello` 7 | (HOST) WARN (BUG) location info is incomplete; it will be omitted from the output 8 | Error: AP ApAddress { dp: Default, ap: 0 } is not a memory AP 9 | The LED5 next to the FTDI chip on the DK goes off for a split second but no program is flashed. 10 | ``` 11 | 12 | Solution: It seems like my nRF52840-DK was shipped with the MCU in some kind of protected state. Using nrfjprog from the nRF command line tools you can run nrfjprog --recover which makes the MCU exit this state and programming etc. using probe-rs works fine again. 13 | 14 | Untested: using [nrf-recover](https://github.com/thalesfragoso/nrf-recover/) may also work. 15 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-probe-not-found.md: -------------------------------------------------------------------------------- 1 | # `no probe was found` error 2 | 3 | You may encounter this error: 4 | 5 | ```console 6 | Running probe-rs run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/hello 7 | Error: no probe was found 8 | ``` 9 | 10 | - It may be caused by the micro-USB cable plugged on the long side of the board, instead of the short side. 11 | - Check that the board is powered on. 12 | - Check that your cable is a data cable and not power-only. 13 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshoot-rust-analyzer.md: -------------------------------------------------------------------------------- 1 | # ▶ Run button, type annotations and syntax highlighting missing / Rust-Analyzer is not working 2 | 3 | If you get no type annotations, no "Run" button and no syntax highlighting this means Rust-Analyzer isn't at work yet. 4 | 5 | Try the following: 6 | 7 | - add something to the file you're currently looking at, delete it again and save. This triggers a re-run. (you can also `touch` the file in question) 8 | - check that you have a single folder open in VS code; this is different from a single-folder VS code workspace. First close all the currently open folders then open a single folder using the 'File > Open Folder' menu. The open folder should be the [`nrf52-code/radio-app`](../../nrf52-code/radio-app) folder for the Radio exercise, the [`nrf52-code/hal-app`](../../nrf52-code/hal-app) folder for the HAL exercise, or the [`nrf52-code/usb-app`](../../nrf52-code/usb-app) folder for the USB exercise. 9 | 10 | - use the latest version of the Rust-Analyzer plugin. If you get a prompt to update the Rust-Analyzer extension when you start VS code accept it. You may also get a prompt about updating the Rust-Analayzer binary; accept that one too. The extension should restart automatically after the update. If it doesn't then close and re-open VS code. 11 | 12 | - You may need to wait a little while Rust-Analyzer analyzes all the crates in the dependency graph. Then you may need to modify and save the currently open file to force Rust-Analyzer to analyze it. 13 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | If you have issues with any of the tools used in this training check out the sections in this chapter. 4 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-api-documentation.md: -------------------------------------------------------------------------------- 1 | # Checking the API documentation 2 | 3 | We'll be using the `dk` Board Support Package. It's good to have its API documentation handy. You can generate the documentation for that crate from the command line: 4 | 5 | ✅ Run the following command from within the [`nrf52-code/usb-app`](../../nrf52-code/usb-app) folder. It will open the generated documentation in your default web browser. Note that if you run it from inside the [`nrf52-code/boards/dk`](../../nrf52-code/boards/dk) folder, you will find a bunch of USB-related documentation missing, because we disable that particular feature by default. 6 | 7 | ```console 8 | cargo doc --open 9 | ``` 10 | 11 | > NOTE: If you are using Safari and the documentation is hard to read due to missing CSS, try opening it in a different browser. 12 | 13 | ✅ Browse to the documentation for the `dk` crate, and look at what is available within the `usbd` module. Some of these functions will be useful later. 14 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-configuration-descriptor.md: -------------------------------------------------------------------------------- 1 | # Configuration descriptor 2 | 3 | ![USB hierarchy diagram showing the relationship between configurations, interfaces and endpoints. The diagram consists of nested rectangles. In this version of the diagram the 'configuration 1' rectangle and the 'bNumInterface' label are highlighted in blue. The outermost rectangle is labeled 'device' and represents the complete USB device. Inside the 'device' rectangle there is one rectangle labeled 'configuration 1'; this rectangle has a 'parallel lines' symbol that indicates there may be more than one configuration instance; the symbol is labeled 'bNumConfigurations=1' indicating that this device has only one configuration. Inside the 'configuration 1' rectangle there are two rectangles labeled 'control endpoint' and 'interface 0'. Inside the 'control endpoint' rectangle there are two rectangles labeled 'endpoint 0 IN' and 'endpoint 0 OUT. The 'interface 0' rectangle has a 'parallel lines' symbol that indicates there may be more than one interface instance; the symbol is labeled 'bNumInterfaces=1' indicating that this configuration has only one interface. Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints.](img/usb-configuration.svg) 4 | 5 | The configuration descriptor describes one of the device configurations to the host. The descriptor contains the following information about a particular configuration: 6 | 7 | - the total length of the configuration: this is the number of bytes required to transfer this configuration descriptor and the interface and endpoint descriptors associated to it 8 | - its number of interfaces -- must be >= 1 9 | - its configuration value -- this is *not* an index and can be any non-zero value 10 | - whether the configuration is self-powered 11 | - whether the configuration supports remote wakeup 12 | - its maximum power consumption 13 | 14 | > The full format of the configuration descriptor is specified in section 9.6.3, Configuration, of the USB specification. 15 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-control-transfers.md: -------------------------------------------------------------------------------- 1 | # USB Control Transfers 2 | 3 | ![USB hierarchy diagram showing the relationship between configurations, interfaces and endpoints. The diagram consists of nested rectangles. In this version of the diagram the 'control endpoint' rectangle is highlighted in blue. The outermost rectangle is labeled 'device' and represents the complete USB device. Inside the 'device' rectangle there is one rectangle labeled 'configuration 1'; this rectangle has a 'parallel lines' symbol that indicates there may be more than one configuration instance; the symbol is labeled 'bNumConfigurations=1' indicating that this device has only one configuration. Inside the 'configuration 1' rectangle there are two rectangles labeled 'control endpoint' and 'interface 0'. Inside the 'control endpoint' rectangle there are two rectangles labeled 'endpoint 0 IN' and 'endpoint 0 OUT. The 'interface 0' rectangle has a 'parallel lines' symbol that indicates there may be more than one interface instance; the symbol is labeled 'bNumInterfaces=1' indicating that this configuration has only one interface. Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints](img/usb-control.svg) 4 | 5 | Before we continue we need to discuss how data transfers work under the USB protocol. 6 | 7 | The *control pipe* handles *control transfers*, a special kind of data transfer used by the host to issue *requests*. A control transfer is a data transfer that occurs in three stages: a SETUP stage, an optional DATA stage and a STATUS stage. The device must handle these requests by either supplying the requested data, or performing the requested action. 8 | 9 | During the SETUP stage the host sends 8 bytes of data that identify the control request. Depending on the issued request there may be a DATA stage or not; during the DATA stage data is transferred either from the device to the host or the other way around. During the STATUS stage the device acknowledges, or not, the whole control request. 10 | 11 | For detailed information about control transfers see [Chapter 4 of USB In a Nutshell](https://www.beyondlogic.org/usbnutshell/usb4.shtml). 12 | 13 | In this exercise, we expect the host to perform a control transfer to find out what kind of device we are. 14 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-endpoint-descriptor.md: -------------------------------------------------------------------------------- 1 | # Endpoint descriptor 2 | 3 | ![USB hierarchy diagram showing the relationship between configurations, interfaces and endpoints. The diagram consists of nested rectangles. In this version of the diagram the endpoint rectangles inside the 'interface 1' rectangle are highlighted in blue. The outermost rectangle is labeled 'device' and represents the complete USB device. Inside the 'device' rectangle there is one rectangle labeled 'configuration 1'; this rectangle has a 'parallel lines' symbol that indicates there may be more than one configuration instance; the symbol is labeled 'bNumConfigurations=1' indicating that this device has only one configuration. Inside the 'configuration 1' rectangle there are two rectangles labeled 'control endpoint' and 'interface 0'. Inside the 'control endpoint' rectangle there are two rectangles labeled 'endpoint 0 IN' and 'endpoint 0 OUT. The 'interface 0' rectangle has a 'parallel lines' symbol that indicates there may be more than one interface instance; the symbol is labeled 'bNumInterfaces=1' indicating that this configuration has only one interface. Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints.](./img/usb-endpoint.svg) 4 | 5 | We will not need to deal with endpoint descriptors in this exercise but they are specified in section 9.6.6, Endpoint, of the USB specification. 6 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-event-handling.md: -------------------------------------------------------------------------------- 1 | # Event Handling 2 | 3 | Below the `idle` function you'll see a `#[task]` handler, a function. This *task* is bound to the POWER_CLOCK interrupt signal and will be executed, function-call style, every time the interrupt signal is raised by the hardware. 4 | 5 | ✅ Run the `events` application. Then connect a micro-USB cable to your PC/laptop then connect the other end to the DK (port J3). You'll see the "POWER event occurred" message after the cable is connected. 6 | 7 | Note that all tasks will be prioritized over the `idle` function so the execution of `idle` will be interrupted (paused) by the `on_power_event` task. When the `on_power_event` task finishes (returns) the execution of the `idle` will be resumed. This will become more obvious in the next section. 8 | 9 | Try this: add an infinite loop to the end of `init` so that it never returns. Now run the program and connect the USB cable. What behavior do you observe? How would you explain this behavior? (hint: look at the `rtic-expansion.rs` file: under what conditions is the `init` function executed?) 10 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-exercise.md: -------------------------------------------------------------------------------- 1 | # nRF52 USB Exercise 2 | 3 | In this exercise you'll learn to: 4 | 5 | - work with registers and peripherals from Rust 6 | - handle external events in embedded Rust applications using RTIC 7 | - debug event driven applications 8 | - test `no_std` code 9 | 10 | To put these concepts and techniques in practice you'll write a toy USB device application that gets enumerated and configured by the host. This embedded application will run in a fully event driven fashion: only doing work when the host asks for it. 11 | 12 | You will need an nRF52840 Development Kit for this exercise, but not the nRF USB dongle. 13 | 14 | ## The nRF52840 Development Kit 15 | 16 | The board has two USB ports: J2 and J3 and an on-board J-Link programmer / debugger -- [there are instructions to identify the ports in a previous section][id-ports]. USB port J2 is the J-Link's USB port. USB port J3 is the nRF52840's USB port. Connect the Development Kit to your computer using both ports. 17 | 18 | [id-ports]: ./nrf52-hardware.md#nrf52840-development-kit-dk 19 | 20 | ## Exercise Steps 21 | 22 | You will need to complete the exercise steps in order. It's OK if you don't get them all finished, but you must complete one before starting the next one. You can look at the solution for each step if you get stuck. 23 | 24 | If you are reading the book view, the steps are listed on the left in the sidebar (use the hamburger if that is hidden). If you are reading the source on Github, go back to the [SUMMARY.md](./SUMMARY.md) file to see the steps. 25 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-extra-info.md: -------------------------------------------------------------------------------- 1 | # Extra Info 2 | 3 | The following chapters contain extra detail about DMA on the nRF52, the USB stack, and how we protect against stack overflows. You do not require them to complete the exercises, but you may find them interesting reading. 4 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-hello-world.md: -------------------------------------------------------------------------------- 1 | # Hello, world! 2 | 3 | In this section, we'll set up the integration in VS Code and run the first program. 4 | 5 | ✅ Open the [`nrf52-code/usb-app`](../../nrf52-code/usb-app) folder in VS Code and open the `src/bin/hello.rs` file. 6 | 7 | > Note: To ensure full rust-analyzer support, do not open the whole `rust-exercises` folder. 8 | 9 | Give rust-analyzer some time to analyze the file and its dependency graph. When it's done, a "Run" button will appear over the `main` function. If it doesn't appear on its own, type something in the file, delete and save. This should trigger a re-load. 10 | 11 | ✅ Click the "Run" button to run the application on the microcontroller. 12 | 13 | If you are not using VS code run the `cargo run --bin hello` command from the [`nrf52-code/usb-app`](../../nrf52-code/usb-app) folder. 14 | 15 | > __NOTE:__ Recent version of the nRF52840-DK have flash-read-out protection to stop people dumping the contents of flash on an nRF52 they received pre-programmed, so if you have problems immediately after first plugging your board in, see [this page](./nrf52-tools.md#setup-check). 16 | > 17 | > If you run into an error along the lines of "Debug power request failed" retry the operation and the error should disappear. 18 | 19 | The `usb-app` package has been configured to cross-compile applications to the ARM Cortex-M architecture and then run them using the `probe-rs` custom Cargo runner. The `probe-rs` tool will load and run the embedded application on the microcontroller and collect logs from the microcontroller. 20 | 21 | The `probe-rs` process will terminate when the microcontroller enters the "halted" state. From the embedded application, one can enter the "halted" state using by performing a CPU breakpoint with a special argument that indicates 'success'. For convenience, an `exit` function is provided in the `dk` Board Support Package (BSP). This function is divergent like `std::process::exit` (`fn() -> !`) and can be used to halt the device and terminate the `probe-rs` process. 22 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-inspecting-descriptors.md: -------------------------------------------------------------------------------- 1 | # Inspecting the Descriptors 2 | 3 | There's a tool built into our `cargo xtask` called `usb-descriptors`, it prints all the descriptors reported by your application 4 | 5 | ## ✅ Run this tool 6 | 7 | Your output should look like this: 8 | 9 | ```console 10 | $ cargo xtask usb-descriptors 11 | DeviceDescriptor { 12 | bLength: 18, 13 | bDescriptorType: 1, 14 | bcdUSB: 512, 15 | bDeviceClass: 0, 16 | bDeviceSubClass: 0, 17 | bDeviceProtocol: 0, 18 | bMaxPacketSize: 64, 19 | idVendor: 8224, 20 | idProduct: 1815, 21 | bcdDevice: 256, 22 | iManufacturer: 0, 23 | iProduct: 0, 24 | iSerialNumber: 0, 25 | bNumConfigurations: 1, 26 | } 27 | address: 22 28 | config0: ConfigDescriptor { 29 | bLength: 9, 30 | bDescriptorType: 2, 31 | wTotalLength: 18, 32 | bNumInterfaces: 1, 33 | bConfigurationValue: 42, 34 | iConfiguration: 0, 35 | bmAttributes: 192, 36 | bMaxPower: 250, 37 | extra: None, 38 | } 39 | iface0: [ 40 | InterfaceDescriptor { 41 | bLength: 9, 42 | bDescriptorType: 4, 43 | bInterfaceNumber: 0, 44 | bAlternateSetting: 0, 45 | bNumEndpoints: 0, 46 | bInterfaceClass: 0, 47 | bInterfaceSubClass: 0, 48 | bInterfaceProtocol: 0, 49 | iInterface: 0, 50 | }, 51 | ] 52 | ``` 53 | 54 | The output above corresponds to the descriptor values we suggested. If you used different values, e.g. for `bMaxPower`, you'll a slightly different output. 55 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-interface-descriptor.md: -------------------------------------------------------------------------------- 1 | # Interface descriptor 2 | 3 | ![USB hierarchy diagram showing the relationship between configurations, interfaces and endpoints. The diagram consists of nested rectangles. In this version of the diagram the 'interface 0' rectangle and the 'bNumEndpoints' label are highlighted in blue. The outermost rectangle is labeled 'device' and represents the complete USB device. Inside the 'device' rectangle there is one rectangle labeled 'configuration 1'; this rectangle has a 'parallel lines' symbol that indicates there may be more than one configuration instance; the symbol is labeled 'bNumConfigurations=1' indicating that this device has only one configuration. Inside the 'configuration 1' rectangle there are two rectangles labeled 'control endpoint' and 'interface 0'. Inside the 'control endpoint' rectangle there are two rectangles labeled 'endpoint 0 IN' and 'endpoint 0 OUT. The 'interface 0' rectangle has a 'parallel lines' symbol that indicates there may be more than one interface instance; the symbol is labeled 'bNumInterfaces=1' indicating that this configuration has only one interface. Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints.](./img/usb-interface.svg) 4 | 5 | The interface descriptor describes one of the device interfaces to the host. The descriptor contains the following information about a particular interface: 6 | 7 | - its interface number -- this is a zero-based index 8 | - its alternate setting -- this allows configuring the interface 9 | - its number of endpoints 10 | - class, subclass and protocol -- these define the interface (HID, or TTY ACM, or DFU, etc.) according to the USB specification 11 | 12 | The number of endpoints can be zero and endpoint zero must not be accounted when counting endpoints. 13 | 14 | > The full format of the interface descriptor is specified in section 9.6.5, Interface, of the USB specification. 15 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-interfaces.md: -------------------------------------------------------------------------------- 1 | # Interface 2 | 3 | We have covered configurations and endpoints but what is an *interface*? 4 | 5 | ![USB hierarchy diagram showing the relationship between configurations, interfaces and endpoints. The diagram consists of nested rectangles. In this version of the diagram the 'interface 0' rectangle and the 'bNumEndpoints' label are highlighted in blue. The outermost rectangle is labeled 'device' and represents the complete USB device. Inside the 'device' rectangle there is one rectangle labeled 'configuration 1'; this rectangle has a 'parallel lines' symbol that indicates there may be more than one configuration instance; the symbol is labeled 'bNumConfigurations=1' indicating that this device has only one configuration. Inside the 'configuration 1' rectangle there are two rectangles labeled 'control endpoint' and 'interface 0'. Inside the 'control endpoint' rectangle there are two rectangles labeled 'endpoint 0 IN' and 'endpoint 0 OUT. The 'interface 0' rectangle has a 'parallel lines' symbol that indicates there may be more than one interface instance; the symbol is labeled 'bNumInterfaces=1' indicating that this configuration has only one interface. Inside the 'interface 0' rectangle there are three rectangles labeled 'endpoint 1 IN', 'endpoint 2 IN' and 'endpoint 2 OUT'. Between these three rectangle there is a label that says 'bNumEndpoints=3'; it indicates that this interface has only three endpoints.](./img/usb-interface.svg) 6 | 7 | An interface is closest to a USB device's function. For example, a USB mouse may expose a single HID (Human Interface Device) interface to report user input to the host. USB devices can expose multiple interfaces within a configuration. For example, the nRF52840 Dongle could expose both a CDC ACM interface (AKA virtual serial port) *and* a HID interface; the first interface could be used for (`defmt::println!`-style) logs; and the second one could provide a RPC (Remote Procedure Call) interface to the host for controlling the nRF52840's radio. 8 | 9 | An interface is made up of one or more *endpoints*. To give an example, a HID interface can use two (interrupt) endpoints, one IN and one OUT, for bidirectional communication with the host. A single endpoint cannot be used by more than one interface with the exception of the special "endpoint 0", which can be (and usually is) shared by all interfaces. 10 | 11 | For detailed information about interfaces check section 9.6.5, Interface, of the USB specification. 12 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-listing-usb-devices.md: -------------------------------------------------------------------------------- 1 | # Listing USB Devices 2 | 3 | As we showed in [Preparation/Software Tools](./nrf52-tools.md), we can use `cyme` to list USB devices on our system. 4 | 5 | ✅ To list all USB devices, run `cyme` from the top-level checkout. 6 | 7 | ```console 8 | $ cyme 9 | (...) random other USB devices will be listed 10 | 2 15  0x1366 0x1051 J-Link 001050255503 12.0 Mb/s 11 | ``` 12 | 13 | The goal of this exercise is to get the nRF52840 SoC to show in this list. The embedded application will use the USB Vendor ID (VID) 0x1209 and USB Product ID (PID) 0x0001, as defined in [`nrf52-code/consts`](../../nrf52-code/consts): 14 | 15 | ```console 16 | $ cyme 17 | (...) random other USB devices will be listed 18 | 2 15  0x1366 0x1051 J-Link 001050255503 12.0 Mb/s 19 | 2 16  0x1209 0x0001 composite_device - 12.0 Mb/s 20 | ```` 21 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-rtic-hello.md: -------------------------------------------------------------------------------- 1 | # RTIC hello 2 | 3 | RTIC, Real-Time Interrupt-driven Concurrency, is a framework for building event-driven, time-sensitive applications. 4 | 5 | ✅ Open the [`nrf52-code/usb-app/src/bin/rtic-hello.rs`](../../nrf52-code/usb-app/src/bin/rtic-hello.rs) file. 6 | 7 | RTIC applications are written in RTIC's Domain Specific Language (DSL). The DSL extends Rust syntax with custom attributes like `#[init]` and `#[idle]`. 8 | 9 | RTIC makes a clearer distinction between the application's initialization phase, the `#[init]` function, and the application's main loop or main logic, the `#[idle]` function. The initialization phase runs with interrupts disabled and interrupts are re-enabled before the `idle` function is executed. 10 | 11 | `rtic::app` is a procedural macro that generates extra Rust code, in addition to the user's functions. The fully expanded version of the macro can be found in the file `target/rtic-expansion.rs`. This file will contain the expansion of the procedural macro for the last compiled RTIC application. 12 | 13 | ✅ Build the `rtic-hello` example and look at the generated `rtic-expansion.rs` file. 14 | 15 | You can use `rustfmt` on `target/rtic-expansion.rs` to make the generated code easier to read. Among other things, the file should contain the following lines. Note that interrupts are disabled during the execution of the `init` function: 16 | 17 | ```rust ignore 18 | #[doc(hidden)] 19 | #[no_mangle] 20 | unsafe extern "C" fn main() -> ! { 21 | rtic::export::interrupt::disable(); 22 | let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); 23 | #[inline(never)] 24 | fn __rtic_init_resources(f: F) 25 | where 26 | F: FnOnce(), 27 | { 28 | f(); 29 | } 30 | let mut executors_size = 0; 31 | extern "C" { 32 | pub static _stack_start: u32; 33 | pub static __ebss: u32; 34 | } 35 | let stack_start = &_stack_start as *const _ as u32; 36 | let ebss = &__ebss as *const _ as u32; 37 | if stack_start > ebss { 38 | if rtic::export::msp::read() <= ebss { 39 | panic!("Stack overflow after allocating executors"); 40 | } 41 | } 42 | __rtic_init_resources(|| { 43 | let (shared_resources, local_resources) = 44 | init(init::Context::new(core.into(), executors_size)); 45 | rtic::export::interrupt::enable(); 46 | }); 47 | idle(idle::Context::new()) 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-set-config.md: -------------------------------------------------------------------------------- 1 | # SET_CONFIGURATION (Linux & macOS) 2 | 3 | On Linux and macOS, the host will likely send a SET_CONFIGURATION request right after enumeration to put the device in the `Configured` state. For now you can stall the request. It is not necessary at this stage because the device has already been enumerated. 4 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-usb-enumeration.md: -------------------------------------------------------------------------------- 1 | # USB Enumeration 2 | 3 | A USB device, like the nRF52840, can be one of these three states: 4 | 5 | * Default 6 | * Address 7 | * Configured 8 | 9 | After being powered the device will start in the __Default__ state. The enumeration process will take the device from the __Default__ state to the __Address__ state. As a result of the enumeration process the device will be assigned an address, in the range `1..=127`, by the host. 10 | 11 | The USB protocol is complex so we'll leave out many details and focus only on the concepts required to get enumeration and configuration working. There are also several USB specific terms so we recommend checking chapter 2, "Terms and Abbreviations", of the USB specification (linked at the bottom of this document) every now and then. 12 | 13 | Each OS may perform the enumeration process slightly differently but the process will always involve these host actions: 14 | 15 | * A USB reset, to put the device in the __Default__ state, regardless of what state it was in. 16 | * A `GET_DESCRIPTOR` request, to get the device descriptor. 17 | * A `SET_ADDRESS` request, to assign an address to the device. 18 | 19 | These host actions will be perceived as *events* by the nRF52840 and these *events* will cause some bits to be set in the relevant register, and then an *interrupt* to be fired. During this exercise, we will gradually parse and handle these events and learn more about Embedded Rust along the way. 20 | 21 | There are more USB concepts involved that we'll need to cover, like descriptors, configurations, interfaces and endpoints but for now let's see how to handle USB events. 22 | 23 | For each step of the course, we've prepared a `usb-.rs` file that gives you a base structure and hints on how to proceed. The matching `usb-.rs` in `usb-app-solutions` contains a sample solution should you need it. Switch from `usb-.rs` to `usb-.rs` when instructed and continue working from there. Please keep the USB cable plugged into J3 through all these exercises. 24 | -------------------------------------------------------------------------------- /exercise-book/src/nrf52-usb-usb-specification.md: -------------------------------------------------------------------------------- 1 | # The USB Specification 2 | 3 | The USB 2.0 specification is available free of charge from . On the right, you will see a link like `usb_20_yyyymmdd.zip`. Download and unpack the zip file, and the core specification can be found within as a file called `usb_20.pdf` (alongside a bunch of errata and additional specifications). Note that the date on the cover page is *April 27, 2000* - and actually, the portions of the specification we are implementing are unchanged from the earlier USB 1.1 specification. 4 | -------------------------------------------------------------------------------- /exercise-book/src/realtime-v8r-preparation.md: -------------------------------------------------------------------------------- 1 | # Bare-Metal Firmware on Cortex-R52 - Preparation 2 | 3 | This chapter contains information about the QEMU-based exercises, the required software and an installation guide. 4 | 5 | This example uses the `armv8r-none-eabihf` target. 6 | 7 | ## Required Software 8 | 9 | ### QEMU, version 9 10 | 11 | Available for Windows, macOS or Linux from 12 | 13 | Note that version 8 or lower will not work. It must be version 9 or higher to support the Cortex-R52. 14 | 15 | Ensure that once installed you have `qemu-system-arm` on your path. 16 | 17 | ### Ferrocene or Rust 18 | 19 | If you use Ferrocene, you will need `stable-25.02.0` or newer. A 20 | `criticalup.toml` file is provided, you can just `criticalup install` in the 21 | example directory and an appropriate toolchain will be provided. 22 | 23 | If you use Rust, the `armv8r-none-eabihf` target is only in Tier 3, so you will 24 | need a nightly release from after March 2024. You will also need to compile the 25 | standard library from source - see the [README](../../qemu-code/uart-driver) for 26 | more details. 27 | 28 | -------------------------------------------------------------------------------- /exercise-book/src/realtime-withoutstd.md: -------------------------------------------------------------------------------- 1 | # Working without the Standard Library 2 | 3 | This section has some exercises which introduce ways to move away from `libstd` and write applications which only use `libcore` (or `liballoc`). This is important when writing safety-critical systems. -------------------------------------------------------------------------------- /exercise-book/src/simple-db-knowledge.md: -------------------------------------------------------------------------------- 1 | # Knowledge 2 | 3 | This section explains concepts necessary to solve the simpleDB exercise. 4 | 5 | We also recommend using the official Rust documentation to figure out unfamiliar concepts. If you ever feel completely stuck, or if you haven’t understood something specific, please hail the trainers quickly. 6 | 7 | ## Derives 8 | 9 | `#[derive(PartialEq, Eq)]` 10 | 11 | This enables comparison between 2 instances of the type, by comparing every field/variant. This enables the `assert_eq!` macro, which relies on equality being defined. `Eq` for total equality isn’t strictly necessary for this example, but it is good practice to derive it if it applies. 12 | 13 | `#[derive(Debug)]` 14 | 15 | This enables the automatic generation of a debug formatting function for the type. The `assert_eq!` macro requires this for testing. 16 | 17 | ## Control flow and pattern matching, returning values 18 | 19 | This exercise involves handling a number of cases. You are already familiar with `if/else` and a basic form of `match`. Here, we’ll introduce you to `if let`, and `let else`: 20 | 21 | ```rust, ignore 22 | if let Some(message) = message.strip_prefix("PREFIX:") { 23 | // Executes if the above pattern is a match. 24 | } 25 | // The variable `message` is NOT available here. 26 | 27 | let Some(message) = message.strip_prefix("PREFIX:") else { 28 | // Executes if the above pattern is NOT a match. 29 | // Must have an early return in this block. 30 | } 31 | // The variable `message` is still available here. 32 | ``` 33 | 34 | ### When to use what? 35 | 36 | `if let` is like a pattern-matching `match` block with only one arm. So, if your `match` only has one arm of interest, consider an `if let` or `let else` instead (depending on whether the pattern match means success, or the pattern match means there's an error). 37 | 38 | `match` can be used to handle more fine grained and complex pattern matching, especially when there are several, equally ranked possibilities. The match arms may have to include a catch all `_ =>` arm, for every possible case that is not explicitly spelled out. The order of the match arms matter: The catch all branch needs to be last, otherwise, it catches all… 39 | 40 | ### Returning Values from branches and match arms 41 | 42 | All match arms always need to produce a value the same type (or they diverge with a `return` statement). 43 | -------------------------------------------------------------------------------- /exercise-book/src/tcp-server-log.md: -------------------------------------------------------------------------------- 1 | 2 | # Share data between connections 3 | 4 | In this exercise we will take our interactive server and add a common log for *lengths of messages* that each client sends us. 5 | We will explore synchronization primitives that Rust offers in its Standard Library. 6 | 7 | ## After completing this exercise you are able to 8 | 9 | - share data between threads using `Mutex`es 10 | 11 | - use reference-counting to ensure data stays available across multiple threads 12 | 13 | - use scoped threads to avoid runtime reference counting 14 | 15 | - use channels and message passing to share data among threads by communicating 16 | 17 | ## Tasks 18 | 19 | ### Part 1 20 | 21 | 1. Add a log to store length of messages: `let mut log: Vec = vec![];` 22 | 2. Pass it to a `handle_client` function and record a length of each incoming line of text: 23 | ```rust ignore 24 | log.push(line.len()); 25 | ``` 26 | 3. Resolve lifetime issues by using a reference-counting pointer. 27 | 4. Resolve mutability issues by using a mutex 28 | 29 | ### Part 2 30 | 31 | 5. Use the [`thread::scope`](https://doc.rust-lang.org/stable/std/thread/fn.scope.html) function to get rid of reference counting for `log` vector 32 | 33 | ### Part 3 34 | 35 | 6. Instead of sharing `log` vector use a [`mpsc::channel`](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html) to send length of lines from worker threads. 36 | 7. Create a separate thread that listens for new channel messages and updates the vector accordingly. 37 | -------------------------------------------------------------------------------- /exercise-book/theme/head.hbs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /exercise-solutions/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver= "2" 3 | members = [ 4 | "fizzbuzz", 5 | "rustlatin/*", 6 | "simple-db/*", 7 | "green-yellow", 8 | "urls-match-result", 9 | "shapes-part-1", 10 | "shapes-part-2", 11 | "shapes-part-3", 12 | "tcp-server-exercises", 13 | "async-chat", 14 | "kani-linked-list", 15 | "iterators", 16 | "calculator/*", 17 | ] 18 | -------------------------------------------------------------------------------- /exercise-solutions/async-chat/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /exercise-solutions/async-chat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-chat" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1", features = ["io-std", "io-util", "macros", "net", "rt-multi-thread", "signal", "sync"] } 8 | -------------------------------------------------------------------------------- /exercise-solutions/async-chat/src/client.rs: -------------------------------------------------------------------------------- 1 | use tokio::{ 2 | io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader}, 3 | net::{TcpStream, ToSocketAddrs}, 4 | }; 5 | 6 | type Result = std::result::Result>; 7 | 8 | #[tokio::main] 9 | pub(crate) async fn main() -> Result<()> { 10 | try_main("127.0.0.1:8080").await 11 | } 12 | 13 | async fn try_main(addr: impl ToSocketAddrs) -> Result<()> { 14 | let stream = TcpStream::connect(addr).await?; 15 | let (reader, mut writer) = stream.into_split(); 16 | let mut lines_from_server = BufReader::new(reader).lines(); 17 | let mut lines_from_stdin = BufReader::new(stdin()).lines(); 18 | loop { 19 | tokio::select! { 20 | line = lines_from_server.next_line() => match line { 21 | Ok(Some(line)) => { 22 | println!("{}", line); 23 | }, 24 | Ok(None) => break, 25 | Err(e) => eprintln!("Error {:?}:", e), 26 | }, 27 | line = lines_from_stdin.next_line() => match line { 28 | Ok(Some(line)) => { 29 | writer.write_all(line.as_bytes()).await?; 30 | writer.write_all(b"\n").await?; 31 | }, 32 | Ok(None) => break, 33 | Err(e) => eprintln!("Error {:?}:", e), 34 | } 35 | } 36 | } 37 | 38 | println!("Server disconnected! Hit enter to quit."); 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /exercise-solutions/async-chat/src/main.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod server; 3 | 4 | type Result = std::result::Result>; 5 | 6 | fn main() -> Result<()> { 7 | let mut args = std::env::args(); 8 | match (args.nth(1).as_ref().map(String::as_str), args.next()) { 9 | (Some("client"), None) => client::main(), 10 | (Some("server"), None) => server::main(), 11 | _ => Err("Usage: a-chat [client|server]".into()), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /exercise-solutions/calculator/calc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "calc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /exercise-solutions/connected-mailbox/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "proc-macro2" 7 | version = "1.0.63" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 10 | dependencies = [ 11 | "unicode-ident", 12 | ] 13 | 14 | [[package]] 15 | name = "quote" 16 | version = "1.0.27" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 19 | dependencies = [ 20 | "proc-macro2", 21 | ] 22 | 23 | [[package]] 24 | name = "simple-db" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "thiserror", 28 | ] 29 | 30 | [[package]] 31 | name = "syn" 32 | version = "2.0.16" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" 35 | dependencies = [ 36 | "proc-macro2", 37 | "quote", 38 | "unicode-ident", 39 | ] 40 | 41 | [[package]] 42 | name = "tcp-server" 43 | version = "0.1.0" 44 | dependencies = [ 45 | "simple-db", 46 | ] 47 | 48 | [[package]] 49 | name = "thiserror" 50 | version = "1.0.40" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 53 | dependencies = [ 54 | "thiserror-impl", 55 | ] 56 | 57 | [[package]] 58 | name = "thiserror-impl" 59 | version = "1.0.40" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 62 | dependencies = [ 63 | "proc-macro2", 64 | "quote", 65 | "syn", 66 | ] 67 | 68 | [[package]] 69 | name = "unicode-ident" 70 | version = "1.0.8" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 73 | -------------------------------------------------------------------------------- /exercise-solutions/connected-mailbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver= "2" 3 | members = ["simple-db", "tcp-server"] 4 | -------------------------------------------------------------------------------- /exercise-solutions/connected-mailbox/simple-db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "simple-db" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | thiserror = "1.0" 9 | -------------------------------------------------------------------------------- /exercise-solutions/connected-mailbox/tcp-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "tcp-server" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | simple-db = {path = "../simple-db"} 9 | -------------------------------------------------------------------------------- /exercise-solutions/connected-mailbox/tcp-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::io::{self, prelude::*}; 3 | use std::net::{TcpListener, TcpStream}; 4 | use std::time::Duration; 5 | 6 | const DEFAULT_TIMEOUT: Option = Some(Duration::from_millis(1000)); 7 | 8 | fn main() -> io::Result<()> { 9 | let listener = TcpListener::bind("127.0.0.1:7878")?; 10 | 11 | let mut storage = VecDeque::new(); 12 | 13 | // accept connections and process them one at a time 14 | for stream in listener.incoming() { 15 | match stream { 16 | Ok(stream) => { 17 | println!("Got client {:?}", stream.peer_addr()); 18 | if let Err(e) = handle_client(stream, &mut storage) { 19 | println!("Error handling client: {:?}", e); 20 | } 21 | } 22 | Err(e) => { 23 | println!("Error connecting: {:?}", e); 24 | } 25 | } 26 | } 27 | 28 | Ok(()) 29 | } 30 | 31 | /// Process a single connection from a single client. 32 | /// 33 | /// Drops the stream when it has finished. 34 | fn handle_client(mut stream: TcpStream, storage: &mut VecDeque) -> io::Result<()> { 35 | stream.set_read_timeout(DEFAULT_TIMEOUT)?; 36 | stream.set_write_timeout(DEFAULT_TIMEOUT)?; 37 | 38 | let mut buffer = String::new(); 39 | stream.read_to_string(&mut buffer)?; 40 | println!("Received: {:?}", buffer); 41 | 42 | let command = match simple_db::parse(&buffer) { 43 | Ok(s) => s, 44 | Err(e) => { 45 | println!("Error parsing command: {:?}", e); 46 | writeln!(stream, "Error: {}!", e)?; 47 | return Ok(()); 48 | } 49 | }; 50 | 51 | println!("Got command {:?}", command); 52 | 53 | match command { 54 | simple_db::Command::Publish(message) => { 55 | storage.push_back(message); 56 | writeln!(stream, "OK")?; 57 | } 58 | simple_db::Command::Retrieve => match storage.pop_front() { 59 | Some(message) => writeln!(stream, "Got: {:?}", message)?, 60 | None => writeln!(stream, "Error: Queue empty!")?, 61 | }, 62 | } 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /exercise-solutions/fizzbuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "fizzbuzz" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/fizzbuzz/src/examples/fizzbuzz.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | for i in 1..=100 { 3 | println!("{}", fizzbuzz(i)); 4 | } 5 | } 6 | 7 | fn fizzbuzz(i: u32) -> String { 8 | if i % 3 == 0 && i % 5 == 0 { 9 | format!("FizzBuzz") 10 | } else if i % 3 == 0 { 11 | format!("Fizz") 12 | } else if i % 5 == 0 { 13 | format!("Buzz") 14 | } else { 15 | format!("{}", i) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /exercise-solutions/fizzbuzz/src/examples/fizzbuzz_match.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | for i in 1..=100 { 3 | println!("{}", fizzbuzz(i)); 4 | } 5 | } 6 | 7 | fn fizzbuzz(i: u32) -> String { 8 | 9 | let remainders = (i%3, i%5); 10 | 11 | match remainders { 12 | (0, 0) => format!("FizzBuzz"), 13 | (0, _) => format!("Fizz"), 14 | (_, 0) => format!("Buzz"), 15 | (_, _) => format!("{}", i), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /exercise-solutions/fizzbuzz/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("This is the solution for the fizzbuzz exercises."); 3 | println!("Run like 'cargo run --example fizzbuzz' to run `fizzbuzz` solution"); 4 | println!("Run like 'cargo run --example fizzbuzz-match' to run `fizzbuzz with match` solution"); 5 | } 6 | -------------------------------------------------------------------------------- /exercise-solutions/green-yellow/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "green-yellow" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = "0.8.5" 10 | -------------------------------------------------------------------------------- /exercise-solutions/green-yellow/src/bin/step2.rs: -------------------------------------------------------------------------------- 1 | //! Green and Yellow, Step 2 2 | 3 | fn calc_green_and_yellow(_guess: &[u8; 4], _secret: &[u8; 4]) -> String { 4 | let result = ["⬜"; 4]; 5 | 6 | result.join("") 7 | } 8 | 9 | fn main() { 10 | println!("{}", calc_green_and_yellow(&[1, 2, 3, 4], &[1, 2, 4, 4])); 11 | } 12 | -------------------------------------------------------------------------------- /exercise-solutions/green-yellow/src/bin/step3.rs: -------------------------------------------------------------------------------- 1 | //! Green and Yellow, Step 3 2 | 3 | fn calc_green_and_yellow(guess: &[u8; 4], secret: &[u8; 4]) -> String { 4 | let mut result = ["⬜"; 4]; 5 | 6 | for i in 0..guess.len() { 7 | if guess[i] == secret[i] { 8 | result[i] = "🟩"; 9 | } 10 | } 11 | 12 | result.join("") 13 | } 14 | 15 | fn main() { 16 | println!("{}", calc_green_and_yellow(&[1, 2, 3, 4], &[1, 2, 4, 4])); 17 | } 18 | -------------------------------------------------------------------------------- /exercise-solutions/iterators/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | name = "iterators" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/iterators/numbers.txt: -------------------------------------------------------------------------------- 1 | //ignore everything that is not a number 2 | 1 3 | 2 4 | 3 5 | 4 6 | five 7 | 6 8 | 7 9 | ∞ 10 | 9 11 | X 12 | 10 13 | 11 -------------------------------------------------------------------------------- /exercise-solutions/iterators/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::File; 3 | use std::io::{BufRead, BufReader}; 4 | 5 | fn main() -> Result<(), Box> { 6 | let f = File::open("numbers.txt")?; 7 | let reader = BufReader::new(f); 8 | 9 | let sum_of_odd_numbers: i32 = reader 10 | .lines() 11 | .filter_map(|line| line.ok()) 12 | .filter_map(|s| s.parse().ok()) 13 | .filter(|num| num % 2 != 0) 14 | .sum(); 15 | 16 | println!("Sum is {}", sum_of_odd_numbers); 17 | assert_eq!(sum_of_odd_numbers, 31); 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /exercise-solutions/kani-linked-list/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": [ 3 | "kani" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /exercise-solutions/kani-linked-list/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kani-linked-list" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | kani = { version = "=0.56", git = "https://github.com/model-checking/kani", tag = "kani-0.56.0", optional = true } 8 | 9 | [dev-dependencies] 10 | kani-verifier = "=0.56.0" 11 | 12 | [lints.rust] 13 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust_analyzer)', 'cfg(kani)'] } 14 | -------------------------------------------------------------------------------- /exercise-solutions/multi-threaded-mailbox/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "proc-macro2" 7 | version = "1.0.63" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" 10 | dependencies = [ 11 | "unicode-ident", 12 | ] 13 | 14 | [[package]] 15 | name = "quote" 16 | version = "1.0.27" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 19 | dependencies = [ 20 | "proc-macro2", 21 | ] 22 | 23 | [[package]] 24 | name = "simple-db" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "thiserror", 28 | ] 29 | 30 | [[package]] 31 | name = "syn" 32 | version = "2.0.16" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" 35 | dependencies = [ 36 | "proc-macro2", 37 | "quote", 38 | "unicode-ident", 39 | ] 40 | 41 | [[package]] 42 | name = "thiserror" 43 | version = "1.0.40" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" 46 | dependencies = [ 47 | "thiserror-impl", 48 | ] 49 | 50 | [[package]] 51 | name = "thiserror-impl" 52 | version = "1.0.40" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" 55 | dependencies = [ 56 | "proc-macro2", 57 | "quote", 58 | "syn", 59 | ] 60 | 61 | [[package]] 62 | name = "unicode-ident" 63 | version = "1.0.8" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 66 | 67 | [[package]] 68 | name = "with-arc" 69 | version = "0.1.0" 70 | dependencies = [ 71 | "simple-db", 72 | ] 73 | 74 | [[package]] 75 | name = "with-scoped-threads" 76 | version = "0.1.0" 77 | dependencies = [ 78 | "simple-db", 79 | ] 80 | -------------------------------------------------------------------------------- /exercise-solutions/multi-threaded-mailbox/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver= "2" 3 | members = ["simple-db", "with-arc", "with-scoped-threads"] 4 | -------------------------------------------------------------------------------- /exercise-solutions/multi-threaded-mailbox/simple-db/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "simple-db" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | thiserror = "1.0" 9 | -------------------------------------------------------------------------------- /exercise-solutions/multi-threaded-mailbox/with-arc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "with-arc" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | simple-db = {path = "../simple-db"} 9 | -------------------------------------------------------------------------------- /exercise-solutions/multi-threaded-mailbox/with-scoped-threads/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "with-scoped-threads" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | simple-db = {path = "../simple-db"} 9 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step1" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step1/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | fn rustlatin(sentence: &str) -> Vec { 4 | // ^^^^^^^ 5 | // The correct return type needs to be added by you, 6 | // depending on what the vector's exact type is. 7 | 8 | let mut collection_of_words = Vec::new(); 9 | // ^^^^^^^^^^^^ 10 | // When you first open this file RA is not able to infer 11 | // the type of this vector. Once you do the implementation, 12 | // the type should appear here automatically. 13 | 14 | // Your implementation goes here: 15 | // Iterate over the sentence to split it into words. 16 | for word in sentence.split(' ') { 17 | collection_of_words.push(word.to_string()) 18 | } 19 | // Push the words into the vector. 20 | // Correct the return type of the vector 21 | collection_of_words 22 | } 23 | 24 | #[test] 25 | fn correct_splitting() { 26 | assert_eq!( 27 | vec!["This", "sentence", "needs", "to", "be", "split"], 28 | rustlatin("This sentence needs to be split") 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step2" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | fn rustlatin(sentence: &str) -> Vec { 4 | // ^^^^^^^ 5 | // The correct return type needs to be added by you, 6 | // depending on what the vector's exact type is. 7 | let mut collection_of_words = Vec::new(); 8 | 9 | for word in sentence.split(' ') { 10 | // Your implementation goes here: 11 | // Add the suffix "rs" to each word before pushing it to the vector 12 | let mut word = word.to_string(); 13 | word.push_str("rs"); 14 | collection_of_words.push(word); 15 | } 16 | collection_of_words 17 | } 18 | 19 | #[test] 20 | fn concatenated() { 21 | assert_eq!( 22 | vec!["dors", "yours", "likers", "rustrs"], 23 | rustlatin("do you like rust") 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step3" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step3/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | fn rustlatin(sentence: &str) -> Vec { 4 | // ^^^^^^^ 5 | // The correct return type needs to be added by you, 6 | // depending on what the vector's exact type is. 7 | let mut collection_of_chars = Vec::new(); 8 | 9 | for word in sentence.split(' ') { 10 | // Your implementation goes here: 11 | // Add the first char of each word to the vector. 12 | // Correct the return type of the vector. 13 | let first_char = word.chars().next().unwrap(); 14 | collection_of_chars.push(first_char); 15 | } 16 | collection_of_chars 17 | } 18 | 19 | #[test] 20 | fn return_the_char() { 21 | assert_eq!( 22 | vec!['n', 't', 'd', 'b', 'i', 'a', 'r', 'v'], 23 | rustlatin("note the difference between iterator and return values") 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step4" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/rustlatin/step4/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; 4 | // ^^^^^^^^^ The vowels are contained in an array, because the length never changes. 5 | // It's a global const because it will not be modified in any way and it's 6 | // small enough that the way that const variables are copied into each 7 | // usage location isn't a problem. 8 | 9 | /// Performs a "rust-latin" conversion on the given string 10 | fn rustlatin(sentence: &str) -> String { 11 | let mut collection_of_words = Vec::new(); 12 | 13 | for word in sentence.split(' ') { 14 | let latinized_word = latinize(word); 15 | collection_of_words.push(latinized_word) 16 | } 17 | collection_of_words.join(" ") 18 | } 19 | 20 | /// adds prefix "sr" and suffix "rs" according to the rules 21 | fn latinize(word: &str) -> String { 22 | let first_char = word.chars().next().unwrap(); 23 | if VOWELS.contains(&first_char) { 24 | let mut result = "sr".to_string(); 25 | result.push_str(word); 26 | result 27 | } else { 28 | let mut result = word.to_string(); 29 | result.push_str("rs"); 30 | result 31 | } 32 | } 33 | 34 | #[test] 35 | fn test_latinizer() { 36 | assert_eq!(latinize("rust"), "rustrs"); 37 | assert_eq!(latinize("helps"), "helpsrs"); 38 | assert_eq!(latinize("you"), "yours"); 39 | assert_eq!(latinize("avoid"), "sravoid"); 40 | } 41 | 42 | #[test] 43 | fn correct_translation() { 44 | // Why can we compare `&str` and `String` here? 45 | // https://doc.rust-lang.org/stable/std/string/struct.String.html#impl-PartialEq%3C%26%27a%20str%3E 46 | assert_eq!( 47 | "rustrs helpsrs yours sravoid sra lotrs srof srirritating bugsrs", 48 | rustlatin("rust helps you avoid a lot of irritating bugs") 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /exercise-solutions/shapes-part-1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "shapes-part-1" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/shapes-part-2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "shapes-part-2" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/shapes-part-3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "shapes-part-3" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | num = { version = "0.4.0", default-features = false } 9 | -------------------------------------------------------------------------------- /exercise-solutions/simple-db/step2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "simple-db-step2" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/simple-db/step2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[derive(Eq, PartialEq, Debug)] 2 | pub enum Command { 3 | Publish(String), 4 | Retrieve, 5 | } 6 | 7 | #[derive(Eq, PartialEq, Debug)] 8 | pub enum Error { 9 | UnexpectedNewline, 10 | IncompleteMessage, 11 | EmptyMessage, 12 | UnknownCommand, 13 | UnexpectedPayload, 14 | MissingPayload, 15 | } 16 | 17 | // Tests go here! 18 | -------------------------------------------------------------------------------- /exercise-solutions/simple-db/step4a/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "simple-db-step4a" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/simple-db/step4a/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[derive(Eq, PartialEq, Debug)] 2 | pub enum Command { 3 | Publish(String), 4 | Retrieve, 5 | Command, // introduced only temporarily 6 | } 7 | 8 | #[derive(Eq, PartialEq, Debug)] 9 | pub enum Error { 10 | UnexpectedNewline, 11 | IncompleteMessage, 12 | EmptyMessage, 13 | UnknownCommand, 14 | UnexpectedPayload, 15 | MissingPayload, 16 | } 17 | 18 | pub fn parse(input: &str) -> Result { 19 | let Some(message) = input.strip_suffix('\n') else { 20 | return Err(Error::IncompleteMessage); 21 | }; 22 | 23 | if message.contains('\n') { 24 | return Err(Error::UnexpectedNewline); 25 | } 26 | 27 | Ok(Command::Command) 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | 34 | #[test] 35 | fn test_trailing_data() { 36 | let line = "PUBLISH The message\n is wrong \n"; 37 | let result: Result = parse(line); 38 | let expected = Err(Error::UnexpectedNewline); 39 | assert_eq!(result, expected); 40 | } 41 | 42 | #[test] 43 | fn test_missing_nl() { 44 | let line = "PUBLISH"; 45 | let result: Result = parse(line); 46 | let expected = Err(Error::IncompleteMessage); 47 | assert_eq!(result, expected); 48 | } 49 | 50 | #[test] 51 | fn test_correct_nl() { 52 | let line = "PUBLISH \n"; 53 | let result: Result = parse(line); 54 | let expected = Ok(Command::Command); 55 | assert_eq!(result, expected); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /exercise-solutions/simple-db/step4b/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "simple-db-step4b" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/simple-db/step4c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "simple-db-step4c" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-solutions/tcp-server-exercises/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | name = "tcp-server-exercises" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /exercise-solutions/tcp-server-exercises/src/bin/echo-log-arc-mutex.rs: -------------------------------------------------------------------------------- 1 | //! A solution to the TCP Server exercise, using Arc and Mutex. 2 | //! 3 | //! Opens a TCP port, readings incoming strings, stores them in a shared log 4 | //! and then echoes them back. 5 | 6 | use std::{ 7 | io::{self, BufRead as _, BufReader, BufWriter, Write as _}, 8 | net::{TcpListener, TcpStream}, 9 | sync::{Arc, Mutex}, 10 | thread, 11 | }; 12 | 13 | fn handle_client(stream: TcpStream, log: &Mutex>) -> Result<(), io::Error> { 14 | let reader = BufReader::new(&stream); 15 | let mut writer = BufWriter::new(&stream); 16 | for line in reader.lines() { 17 | let line = line?; 18 | // the code block here forces the MutexGuard drop to unlock the mutex 19 | { 20 | let mut log = log.lock().unwrap(); 21 | log.push(line.len()); 22 | } 23 | writer.write_all(line.as_bytes())?; 24 | writer.write_all(b"\n")?; 25 | writer.flush()?; 26 | } 27 | Ok(()) 28 | } 29 | 30 | fn main() -> Result<(), io::Error> { 31 | let log = Arc::new(Mutex::new(vec![])); 32 | 33 | let listener = TcpListener::bind("127.0.0.1:7878")?; 34 | for stream in listener.incoming() { 35 | let Ok(stream) = stream else { 36 | eprintln!("Bad connection"); 37 | continue; 38 | }; 39 | 40 | let log = Arc::clone(&log); 41 | thread::spawn(move || { 42 | handle_client(stream, &log).unwrap(); 43 | }); 44 | } 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /exercise-solutions/tcp-server-exercises/src/bin/echo-log-scope.rs: -------------------------------------------------------------------------------- 1 | //! A solution to the TCP Server exercise, using scoped threads. 2 | //! 3 | //! Opens a TCP port, readings incoming strings, stores them in a shared log 4 | //! and then echoes them back. 5 | 6 | use std::{ 7 | io::{self, BufRead as _, BufReader, BufWriter, Write as _}, 8 | net::{TcpListener, TcpStream}, 9 | sync::Mutex, 10 | thread, 11 | }; 12 | 13 | fn handle_client(stream: TcpStream, log: &Mutex>) -> Result<(), io::Error> { 14 | let reader = BufReader::new(&stream); 15 | let mut writer = BufWriter::new(&stream); 16 | for line in reader.lines() { 17 | let line = line?; 18 | { 19 | let mut log = log.lock().unwrap(); 20 | log.push(line.len()); 21 | } 22 | writer.write_all(line.as_bytes())?; 23 | writer.write_all(b"\n")?; 24 | writer.flush()?; 25 | } 26 | Ok(()) 27 | } 28 | 29 | fn main() -> Result<(), io::Error> { 30 | let log = Mutex::new(vec![]); 31 | 32 | let listener = TcpListener::bind("127.0.0.1:7878")?; 33 | thread::scope(|scope| { 34 | for stream in listener.incoming() { 35 | let Ok(stream) = stream else { 36 | eprintln!("Bad connection"); 37 | continue; 38 | }; 39 | 40 | scope.spawn(|| { 41 | handle_client(stream, &log).unwrap(); 42 | }); 43 | } 44 | }); 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /exercise-solutions/tcp-server-exercises/src/bin/echo.rs: -------------------------------------------------------------------------------- 1 | //! A solution to the TCP Server exercise, using non-scoped threads. 2 | //! 3 | //! Opens a TCP port, readings incoming strings and then echoes them back. 4 | //! The incoming strings are not logged. 5 | 6 | use std::{ 7 | io::{self, BufRead as _, BufReader, BufWriter, Write as _}, 8 | net::{TcpListener, TcpStream}, 9 | thread, 10 | }; 11 | 12 | /// There are multiple ways to implement handle client function. 13 | /// You can use `try_clone` to make multiple variable referring to the same 14 | /// underlying TCP stream. 15 | /// Or you can use the fact that `Read` *and* `Write` traits are both 16 | /// implemented for `&TcpStream` like we do above. 17 | /// 18 | /// Using `BufWriter` can be convenient because we can call `write` or 19 | /// `write_all` multiple times, and the writes to underlying TCP stream will 20 | /// only be done when the internal buffer is full (or when we call `flush`). 21 | /// Without a buffered writer we would construct our output explicitly before 22 | /// performing a write. 23 | /// 24 | /// Here's another alternative implementation: 25 | /// 26 | /// ```rust 27 | /// # use std::{io::{self, BufReader, BufRead, Read, Write}, net::TcpStream}; 28 | /// fn handle_client(stream: TcpStream) -> Result<(), io::Error> { 29 | /// let mut writer = stream.try_clone()?; 30 | /// let reader = BufReader::new(stream); 31 | /// for line in reader.lines() { 32 | /// let line = line?; 33 | /// let line = format!("{}\n", line); 34 | /// writer.write_all(line.as_bytes())?; 35 | /// } 36 | /// Ok(()) 37 | /// } 38 | /// ``` 39 | fn handle_client(stream: TcpStream) -> Result<(), io::Error> { 40 | let reader = BufReader::new(&stream); 41 | let mut writer = BufWriter::new(&stream); 42 | for line in reader.lines() { 43 | let line = line?; 44 | writer.write_all(line.as_bytes())?; 45 | writer.write_all(b"\n")?; 46 | writer.flush()?; 47 | } 48 | Ok(()) 49 | } 50 | 51 | fn main() -> Result<(), io::Error> { 52 | let listener = TcpListener::bind("127.0.0.1:7878")?; 53 | for stream in listener.incoming() { 54 | let Ok(stream) = stream else { 55 | eprintln!("Bad connection"); 56 | continue; 57 | }; 58 | 59 | thread::spawn(|| { 60 | handle_client(stream).unwrap(); 61 | }); 62 | } 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "urls_match_result" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | url = { version = "2", default-features = false } 9 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/examples/step_1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let read_result = std::fs::read_to_string("src/data/content.txt"); 3 | 4 | match read_result { 5 | Ok(_str) => println!("File opened and read"), 6 | Err(e) => panic!("Problem opening and reading the file: {:?}", e), 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/examples/step_2.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), std::io::Error> { 2 | let _file_contents = std::fs::read_to_string("src/data/content.txt")?; 3 | println!("File opened and read"); 4 | Ok(()) 5 | } 6 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/examples/step_3.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), std::io::Error> { 2 | let file_contents = std::fs::read_to_string("src/data/content.txt")?; 3 | println!("File opened and read"); 4 | 5 | let mut number = 0; 6 | 7 | for _line in file_contents.lines() { 8 | number += 1; 9 | } 10 | 11 | println!("{}", number); 12 | 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/examples/step_4.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), std::io::Error> { 2 | let file_contents = std::fs::read_to_string("src/data/content.txt")?; 3 | println!("File opened and read"); 4 | 5 | for line in file_contents.lines() { 6 | if !line.is_empty() { 7 | println!("{}", line) 8 | } 9 | } 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/examples/step_5.rs: -------------------------------------------------------------------------------- 1 | fn parse_url(line: &str) -> Option { 2 | match url::Url::parse(&line) { 3 | Ok(u) => Some(u), 4 | Err(_e) => None, 5 | } 6 | } 7 | 8 | fn main() -> Result<(), std::io::Error> { 9 | let file_contents = std::fs::read_to_string("src/data/content.txt")?; 10 | println!("File opened and read"); 11 | 12 | for line in file_contents.lines() { 13 | match parse_url(line) { 14 | Some(url) => { 15 | println!("Is a URL: {}", url); 16 | } 17 | None => { 18 | println!("Not a URL"); 19 | } 20 | } 21 | } 22 | 23 | Ok(()) 24 | } 25 | 26 | #[test] 27 | fn correct_url() { 28 | assert!(parse_url("https://example.com").is_some()) 29 | } 30 | 31 | #[test] 32 | fn no_url() { 33 | assert!(parse_url("abcdf").is_none()) 34 | } 35 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/src/data/content.txt: -------------------------------------------------------------------------------- 1 | This file contains a title and list of URLs and empty lines 2 | https://docs.rs/lyon_core/0.8.0/lyon_core/ 3 | 4 | https://docs.rs/gdnative-core/0.7.0/gdnative_core/ 5 | 6 | https://docs.rs/little-endian/1.0.0/little_endian/ 7 | https://docs.rs/grin_keychain/3.0.0/grin_keychain/ 8 | -------------------------------------------------------------------------------- /exercise-solutions/urls-match-result/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("This is the solution for the urls-match-result exercise."); 3 | println!("Run like 'cargo run --example step_3' to run the Step 3 solution"); 4 | } 5 | -------------------------------------------------------------------------------- /exercise-templates/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver= "2" 3 | members = [ 4 | "urls-match-result", 5 | "rustlatin/*", 6 | "tcp-echo-server", 7 | "async-chat/*", 8 | "iterators", 9 | ] 10 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-chat" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1", features = ["full"] } 8 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step1/src/client.rs: -------------------------------------------------------------------------------- 1 | use tokio::{ 2 | io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader}, 3 | net::{TcpStream, ToSocketAddrs}, 4 | }; 5 | 6 | type Result = std::result::Result>; 7 | 8 | #[tokio::main] 9 | pub(crate) async fn main() -> Result<()> { 10 | try_main("127.0.0.1:8080").await 11 | } 12 | 13 | async fn try_main(addr: impl ToSocketAddrs) -> Result<()> { 14 | let stream = TcpStream::connect(addr).await?; 15 | let (reader, mut writer) = stream.into_split(); 16 | let mut lines_from_server = BufReader::new(reader).lines(); 17 | let mut lines_from_stdin = BufReader::new(stdin()).lines(); 18 | loop { 19 | tokio::select! { 20 | line = lines_from_server.next_line() => match line { 21 | Ok(Some(line)) => { 22 | println!("{}", line); 23 | }, 24 | Ok(None) => break, 25 | Err(e) => eprintln!("Error {:?}:", e), 26 | }, 27 | line = lines_from_stdin.next_line() => match line { 28 | Ok(Some(line)) => { 29 | writer.write_all(line.as_bytes()).await?; 30 | writer.write_all(b"\n").await?; 31 | }, 32 | Ok(None) => break, 33 | Err(e) => eprintln!("Error {:?}:", e), 34 | } 35 | } 36 | } 37 | 38 | println!("Server disconnected! Hit enter to quit."); 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step1/src/main.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod server; 3 | 4 | type Result = std::result::Result>; 5 | 6 | fn main() -> Result<()> { 7 | let mut args = std::env::args(); 8 | match (args.nth(1).as_ref().map(String::as_str), args.next()) { 9 | (Some("client"), None) => client::main(), 10 | (Some("server"), None) => server::main(), 11 | _ => Err("Usage: a-chat [client|server]".into()), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step1/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::hash_map::{Entry, HashMap}, 3 | future::Future, 4 | }; 5 | 6 | use tokio::sync::{mpsc, oneshot}; 7 | 8 | use tokio::{ 9 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 10 | net::tcp::OwnedWriteHalf, 11 | net::{TcpListener, TcpStream, ToSocketAddrs}, 12 | task, 13 | }; 14 | 15 | type Result = std::result::Result>; 16 | type Sender = mpsc::UnboundedSender; 17 | type Receiver = mpsc::UnboundedReceiver; 18 | 19 | #[tokio::main] 20 | pub(crate) async fn main() -> Result<()> { 21 | accept_loop("127.0.0.1:8080").await 22 | } 23 | 24 | async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { 25 | // 1 26 | let listener = TcpListener::bind(addr).await?; // 2 27 | 28 | loop { 29 | // 3 30 | let (stream, _) = listener.accept().await?; 31 | // TODO 32 | } 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async-chat-2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1", features = ["full"] } 8 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step2/src/client.rs: -------------------------------------------------------------------------------- 1 | use tokio::{ 2 | io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader}, 3 | net::{TcpStream, ToSocketAddrs}, 4 | }; 5 | 6 | type Result = std::result::Result>; 7 | 8 | #[tokio::main] 9 | pub(crate) async fn main() -> Result<()> { 10 | try_main("127.0.0.1:8080").await 11 | } 12 | 13 | async fn try_main(addr: impl ToSocketAddrs) -> Result<()> { 14 | let stream = TcpStream::connect(addr).await?; 15 | let (reader, mut writer) = stream.into_split(); 16 | let mut lines_from_server = BufReader::new(reader).lines(); 17 | let mut lines_from_stdin = BufReader::new(stdin()).lines(); 18 | loop { 19 | tokio::select! { 20 | line = lines_from_server.next_line() => match line { 21 | Ok(Some(line)) => { 22 | println!("{}", line); 23 | }, 24 | Ok(None) => break, 25 | Err(e) => eprintln!("Error {:?}:", e), 26 | }, 27 | line = lines_from_stdin.next_line() => match line { 28 | Ok(Some(line)) => { 29 | writer.write_all(line.as_bytes()).await?; 30 | writer.write_all(b"\n").await?; 31 | }, 32 | Ok(None) => break, 33 | Err(e) => eprintln!("Error {:?}:", e), 34 | } 35 | } 36 | } 37 | 38 | println!("Server disconnected! Hit enter to quit."); 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step2/src/main.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod server; 3 | 4 | type Result = std::result::Result>; 5 | 6 | fn main() -> Result<()> { 7 | let mut args = std::env::args(); 8 | match (args.nth(1).as_ref().map(String::as_str), args.next()) { 9 | (Some("client"), None) => client::main(), 10 | (Some("server"), None) => server::main(), 11 | _ => Err("Usage: a-chat [client|server]".into()), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /exercise-templates/async-chat/step2/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::hash_map::{Entry, HashMap}, 3 | future::Future, 4 | }; 5 | 6 | use tokio::sync::{mpsc, oneshot}; 7 | 8 | use tokio::{ 9 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 10 | net::tcp::OwnedWriteHalf, 11 | net::{TcpListener, TcpStream, ToSocketAddrs}, 12 | task, 13 | }; 14 | 15 | type Result = std::result::Result>; 16 | type Sender = mpsc::UnboundedSender; 17 | type Receiver = mpsc::UnboundedReceiver; 18 | 19 | #[tokio::main] 20 | pub(crate) async fn main() -> Result<()> { 21 | accept_loop("127.0.0.1:8080").await 22 | } 23 | 24 | async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { 25 | let listener = TcpListener::bind(addr).await?; 26 | 27 | while let Ok((stream, _socket_addr)) = listener.accept().await { 28 | println!("Accepting from: {}", stream.peer_addr()?); 29 | task::spawn(connection_loop(stream)); 30 | } 31 | Ok(()) 32 | } 33 | 34 | async fn connection_loop(stream: TcpStream) -> Result<()> { 35 | let (reader, writer) = stream.into_split(); 36 | let reader = BufReader::new(reader); 37 | let mut lines = reader.lines(); 38 | 39 | let name = match lines.next_line().await { 40 | Ok(Some(line)) => line, 41 | Ok(None) => return Err("peer disconnected immediately".into()), 42 | Err(e) => return Err(Box::new(e)), 43 | }; 44 | 45 | println!("user {} connected", name); 46 | 47 | while let Ok(Some(line)) = lines.next_line().await { 48 | let (dest, msg) = match line.find(':') { 49 | None => continue, 50 | Some(idx) => (&line[..idx], line[idx + 1..].trim()), 51 | }; 52 | let dest: Vec = dest 53 | .split(',') 54 | .map(|name| name.trim().to_string()) 55 | .collect(); 56 | let msg: String = msg.trim().to_string(); 57 | 58 | println!("User {} sent message: {}", name, msg) 59 | } 60 | 61 | Ok(()) 62 | } 63 | 64 | async fn connection_writer_loop( 65 | messages: &mut Receiver, 66 | stream: &mut OwnedWriteHalf, 67 | ) -> Result<()> { 68 | loop { 69 | let msg = messages.recv().await; 70 | match msg { 71 | Some(msg) => stream.write_all(msg.as_bytes()).await?, 72 | None => break, 73 | } 74 | } 75 | Ok(()) 76 | } 77 | -------------------------------------------------------------------------------- /exercise-templates/iterators/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | name = "iterators" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-templates/iterators/numbers.txt: -------------------------------------------------------------------------------- 1 | //ignore everything that is not a number 2 | 1 3 | 2 4 | 3 5 | 4 6 | five 7 | 6 8 | 7 9 | ∞ 10 | 9 11 | X 12 | 10 13 | 11 -------------------------------------------------------------------------------- /exercise-templates/iterators/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs::File; 3 | use std::io::BufReader; 4 | 5 | fn main() -> Result<(), Box> { 6 | let f = File::open("numbers.txt")?; 7 | let reader = BufReader::new(f); 8 | 9 | // Write your iterator chain here 10 | let sum_of_odd_numbers: i32 = todo!("use reader.lines() and Iterator methods"); 11 | 12 | assert_eq!(sum_of_odd_numbers, 31); 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step1/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step1" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step1/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | fn rustlatin(sentence: &str) -> Vec<()> { 4 | // ^^^^^^^ 5 | // The correct return type needs to be added by you, 6 | // depending on what the vector's exact type is. 7 | 8 | let mut collection_of_words = Vec::new(); 9 | // ^^^^^^^^^^^^ 10 | // When you first open this file RA is not able to infer 11 | // the type of this vector. Once you do the implementation, 12 | // the type should appear here automatically. 13 | 14 | // Your implementation goes here: 15 | // Iterate over the sentence to split it into words. 16 | // Push the words into the vector. 17 | // Correct the return type of the vector 18 | 19 | collection_of_words 20 | } 21 | 22 | #[test] 23 | fn correct_splitting() { 24 | assert_eq!( 25 | vec!["This", "sentence", "needs", "to", "be", "split"], 26 | rustlatin("This sentence needs to be split") 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step2" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | fn rustlatin(sentence: &str) -> Vec<()> { 4 | // ^^^^^^^ 5 | // The correct return type needs to be added by you, 6 | // depending on what the vector's exact type is. 7 | let mut collection_of_words = Vec::new(); 8 | 9 | for word in sentence.split(' ') { 10 | // Your implementation goes here: 11 | // Add the suffix "rs" to each word before pushing it to the vector 12 | // Correct the return type of the function. 13 | } 14 | collection_of_words 15 | } 16 | 17 | #[test] 18 | fn concatenated() { 19 | assert_eq!( 20 | vec!["dors", "yours", "likers", "rustrs"], 21 | rustlatin("do you like rust") 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step3" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step3/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | fn rustlatin(sentence: &str) -> Vec<()> { 4 | // ^^^^^^^ 5 | // The correct return type needs to be added by you, 6 | // depending on what the vector's exact type is. 7 | let mut collection_of_chars = Vec::new(); 8 | 9 | for word in sentence.split(' ') { 10 | // Your implementation goes here: 11 | // Add the first char of each word to the vector. 12 | // Correct the return type of the vector. 13 | } 14 | collection_of_chars 15 | } 16 | 17 | #[test] 18 | fn return_the_char() { 19 | assert_eq!( 20 | vec!['n', 't', 'd', 'b', 'i', 'a', 'r', 'v'], 21 | rustlatin("note the difference between iterator and return values") 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step4/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "rustlatin-step4" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-templates/rustlatin/step4/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; 4 | // ^^^^^^^^^ The vowels are contained in an array, because the length never changes. 5 | // It's a global const because it will not be modified in any way and it's 6 | // small enough that the way that const variables are copied into each 7 | // usage location isn't a problem. 8 | 9 | /// Performs a "rust-latin" conversion on the given string 10 | fn rustlatin(sentence: &str) -> String { 11 | let mut collection_of_words = Vec::new(); 12 | 13 | for word in sentence.split(' ') { 14 | // Your implementation goes here... 15 | // delete this, and push the latinized words into the vector 16 | collection_of_words.push("") 17 | } 18 | collection_of_words.join(" ") 19 | } 20 | 21 | /// adds prefix "sr" and suffix "rs" according to the rules 22 | fn latinize() { 23 | // You need to add the right arguments and return type, then implement 24 | // this function. 25 | unimplemented!(); 26 | } 27 | 28 | #[test] 29 | fn test_latinizer() { 30 | // Uncomment these test cases 31 | // assert_eq!(latinize("rust"), "rustrs"); 32 | // assert_eq!(latinize("helps"), "helpsrs"); 33 | // assert_eq!(latinize("you"), "yours"); 34 | // assert_eq!(latinize("avoid"), "sravoid"); 35 | } 36 | 37 | #[test] 38 | fn correct_translation() { 39 | // Why can we compare `&str` and `String` here? 40 | // https://doc.rust-lang.org/stable/std/string/struct.String.html#impl-PartialEq%3C%26%27a%20str%3E 41 | assert_eq!( 42 | "rustrs helpsrs yours sravoid sra lotrs srof srirritating bugsrs", 43 | rustlatin("rust helps you avoid a lot of irritating bugs") 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /exercise-templates/tcp-echo-server/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "tcp-echo-server" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /exercise-templates/tcp-echo-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "tcp-echo-server" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /exercise-templates/tcp-echo-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::prelude::*; 2 | use std::net::{TcpListener, TcpStream}; 3 | use std::time::Duration; 4 | 5 | const DEFAULT_TIMEOUT: Option = Some(Duration::from_millis(1000)); 6 | 7 | fn main() -> std::io::Result<()> { 8 | let listener = TcpListener::bind("127.0.0.1:7878")?; 9 | 10 | // accept connections and process them one at a time 11 | for stream in listener.incoming() { 12 | match stream { 13 | Ok(stream) => { 14 | println!("Got client {:?}", stream.peer_addr()); 15 | if let Err(e) = handle_client(stream) { 16 | println!("Error handling client: {:?}", e); 17 | } 18 | } 19 | Err(e) => { 20 | println!("Error connecting: {:?}", e); 21 | } 22 | } 23 | } 24 | Ok(()) 25 | } 26 | 27 | /// Process a single connection from a single client. 28 | /// 29 | /// Drops the stream when it has finished. 30 | fn handle_client(mut stream: TcpStream) -> Result<(), std::io::Error> { 31 | stream.set_read_timeout(DEFAULT_TIMEOUT)?; 32 | stream.set_write_timeout(DEFAULT_TIMEOUT)?; 33 | 34 | let mut buffer = String::new(); 35 | stream.read_to_string(&mut buffer)?; 36 | println!("Received: {:?}", buffer); 37 | writeln!(stream, "Thank you for {buffer:?}!")?; 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /exercise-templates/urls-match-result/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "urls_match_result" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | url = "2" 9 | -------------------------------------------------------------------------------- /exercise-templates/urls-match-result/src/data/content.txt: -------------------------------------------------------------------------------- 1 | This file contains a title and list of URLs and empty lines 2 | https://docs.rs/lyon_core/0.8.0/lyon_core/ 3 | 4 | https://docs.rs/gdnative-core/0.7.0/gdnative_core/ 5 | 6 | https://docs.rs/little-endian/1.0.0/little_endian/ 7 | https://docs.rs/grin_keychain/3.0.0/grin_keychain/ 8 | -------------------------------------------------------------------------------- /exercise-templates/urls-match-result/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _file_contents = std::fs::read_to_string("src/lib/content.txt").unwrap(); 3 | // ^^^^^^^^^^^^^^ 4 | // std::fs::read_to_string yields a Result, 5 | // and a quick way to get to the value of type String is 6 | // to use the unwrap() method on the Result. 7 | } 8 | -------------------------------------------------------------------------------- /nrf52-code/boards/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock -------------------------------------------------------------------------------- /nrf52-code/boards/dk-solution/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk-solution/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "dk" 6 | version = "0.0.0" 7 | 8 | [dependencies] 9 | cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } 10 | cortex-m-rt = "0.7.5" 11 | cortex-m-semihosting = "0.5.0" 12 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 13 | defmt-rtt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219" } 14 | embedded-hal = "1.0" 15 | grounded = { version = "0.2.0", features = ["cas"] } 16 | hal = { package = "nrf52840-hal", version = "0.18.0" } 17 | 18 | [features] 19 | advanced = [] 20 | radio = [] 21 | usbd = [] 22 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk-solution/README.md: -------------------------------------------------------------------------------- 1 | # `dk` 2 | 3 | Board Support Package (BSP) for the nRF52840 Development Kit (DK) 4 | 5 | See 6 | 7 | This copy contains support for Buttons 1 to 4. 8 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk-solution/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let out_dir = PathBuf::from(env::var("OUT_DIR")?); 5 | 6 | // put memory layout (linker script) in the linker search path 7 | fs::copy("memory.x", out_dir.join("memory.x"))?; 8 | 9 | println!("cargo:rustc-link-search={}", out_dir.display()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk-solution/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 1024K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 5 | } 6 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk-solution/src/errata.rs: -------------------------------------------------------------------------------- 1 | /// USBD cannot be enabled 2 | pub unsafe fn e187a() { 3 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 4 | (0x4006_ED14 as *mut u32).write_volatile(3); 5 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 6 | } 7 | 8 | /// USBD cannot be enabled 9 | pub unsafe fn e187b() { 10 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 11 | (0x4006_ED14 as *mut u32).write_volatile(0); 12 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 13 | } 14 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk-solution/src/peripheral.rs: -------------------------------------------------------------------------------- 1 | //! Low level access to the nRF52840 peripheral 2 | 3 | pub use hal::pac::{POWER, USBD}; 4 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "dk" 6 | version = "0.0.0" 7 | 8 | [dependencies] 9 | cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } 10 | cortex-m-rt = "0.7.5" 11 | cortex-m-semihosting = "0.5.0" 12 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 13 | defmt-rtt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219" } 14 | embedded-hal = "1.0" 15 | grounded = { version = "0.2.0", features = ["cas"] } 16 | hal = { package = "nrf52840-hal", version = "0.18.0" } 17 | 18 | [features] 19 | advanced = [] 20 | radio = [] 21 | usbd = [] 22 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk/README.md: -------------------------------------------------------------------------------- 1 | # `dk` 2 | 3 | Board Support Package (BSP) for the nRF52840 Development Kit (DK) 4 | 5 | See 6 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let out_dir = PathBuf::from(env::var("OUT_DIR")?); 5 | 6 | // put memory layout (linker script) in the linker search path 7 | fs::copy("memory.x", out_dir.join("memory.x"))?; 8 | 9 | println!("cargo:rustc-link-search={}", out_dir.display()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 1024K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 5 | } 6 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk/src/errata.rs: -------------------------------------------------------------------------------- 1 | /// USBD cannot be enabled 2 | pub unsafe fn e187a() { 3 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 4 | (0x4006_ED14 as *mut u32).write_volatile(3); 5 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 6 | } 7 | 8 | /// USBD cannot be enabled 9 | pub unsafe fn e187b() { 10 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 11 | (0x4006_ED14 as *mut u32).write_volatile(0); 12 | (0x4006_EC00 as *mut u32).write_volatile(0x9375); 13 | } 14 | -------------------------------------------------------------------------------- /nrf52-code/boards/dk/src/peripheral.rs: -------------------------------------------------------------------------------- 1 | //! Low level access to the nRF52840 peripheral 2 | 3 | pub use hal::pac::{POWER, USBD}; 4 | -------------------------------------------------------------------------------- /nrf52-code/boards/dongle-fw/README.md: -------------------------------------------------------------------------------- 1 | # nRF52840 USB Dongle Firmware 2 | 3 | This folder is empty on Github, but in a release .zip archive it will contain: 4 | 5 | * Puzzle Firmware - see <../../puzzle-fw> 6 | * Loopback Firmware - see <../../loopback-fw> 7 | 8 | ## Loading Firmware 9 | 10 | You can load one firmware image at a time into your USB dongle. Load the 11 | firmware image with `nrfdfu`: 12 | 13 | ```console 14 | $ cargo install nrfdfu 15 | $ # Now press the dongle's 'Reset' button - the red LED should come on... 16 | $ nrfdfu ./nrf52-code/boards/dongle-fw/puzzle-fw 17 | [INFO nrfdfu] Sending init packet... 18 | [INFO nrfdfu] Sending firmware image of size 37568... 19 | [INFO nrfdfu] Done. 20 | ``` 21 | 22 | ## Compiling Firmware 23 | 24 | Enter the source directory for the firmware, and run `cargo build --release`. 25 | Note that if you compile your own puzzle firmware, you won't have the same 26 | secret message as everyone else because it's not in the source code anywhere 27 | (it's a Github Secret). 28 | -------------------------------------------------------------------------------- /nrf52-code/boards/dongle/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/boards/dongle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "dongle" 6 | version = "0.0.0" 7 | 8 | [dependencies] 9 | cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } 10 | cortex-m-rt = "0.7.5" 11 | cortex-m-semihosting = "0.5.0" 12 | critical-section = "1.2.0" 13 | defmt = "1" 14 | defmt-rtt = "1" 15 | embedded-hal = "1.0" 16 | grounded = { version = "0.2.0", features = ["cas"] } 17 | hal = { package = "nrf52840-hal", version = "0.18.0" } 18 | heapless = "0.8.0" 19 | panic-probe = { version = "0.3.0", features = ["print-defmt"] } 20 | -------------------------------------------------------------------------------- /nrf52-code/boards/dongle/README.md: -------------------------------------------------------------------------------- 1 | # `dongle` 2 | 3 | Board Support Package (BSP) for the nRF52840 USB Dongle 4 | 5 | See 6 | -------------------------------------------------------------------------------- /nrf52-code/boards/dongle/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, error::Error, fs, path::PathBuf}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let out_dir = PathBuf::from(env::var("OUT_DIR")?); 5 | 6 | // put memory layout (linker script) in the linker search path 7 | fs::copy("memory.x", out_dir.join("memory.x"))?; 8 | 9 | println!("cargo:rustc-link-search={}", out_dir.display()); 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /nrf52-code/boards/dongle/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | /* 4 | * Start after the bootloader stub, and stop before the bootloader proper 5 | * at 0xE0000 6 | */ 7 | FLASH : ORIGIN = 0x00001000, LENGTH = 0xE0000 - 0x1000 8 | RAM : ORIGIN = 0x20000000, LENGTH = 256K 9 | } 10 | -------------------------------------------------------------------------------- /nrf52-code/consts/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/consts/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "consts" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /nrf52-code/consts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "consts" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Constants used in the embedded training" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /nrf52-code/consts/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | /// A USB VID we randomly picked for the demo code on the dongle 4 | pub const USB_VID_DEMO: u16 = 0x1209; 5 | 6 | /// USB PID for the nRF52840 in USB mode when running the RTIC demo 7 | pub const USB_PID_RTIC_DEMO: u16 = 0x0001; 8 | 9 | /// USB PID for the Dongle in Loopback mode 10 | pub const USB_PID_DONGLE_LOOPBACK: u16 = 0x0002; 11 | 12 | /// USB PID for the Dongle in Puzzle mode 13 | pub const USB_PID_DONGLE_PUZZLE: u16 = 0x0003; 14 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | # set custom cargo runner to flash & run on embedded target when we call `cargo run` 3 | # for more information, check out https://github.com/probe-rs 4 | runner = [ 5 | "probe-rs", 6 | "run", 7 | "--chip=nRF52840_xxAA", 8 | "--allow-erase-all", 9 | "--log-format=oneline" 10 | ] 11 | 12 | rustflags = [ 13 | "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script 14 | "-C", "linker=flip-link", # adds stack overflow protection 15 | "-C", "link-arg=-Tdefmt.x", # defmt support 16 | ] 17 | 18 | [build] 19 | # cross-compile to this target 20 | target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU 21 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /nrf52-code/hal-app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "cwd": "${workspaceRoot}", 6 | // TODO to debug a different program the app name ("hello") needs to be changed 7 | "executable": "./target/thumbv7em-none-eabihf/debug/hello", 8 | "name": "Debug Microcontroller (launch)", 9 | "request": "launch", 10 | "preLaunchTask": "rust: cargo build", 11 | "type": "cortex-debug", 12 | "runToEntryPoint": "main", 13 | "configFiles": [ 14 | "interface/jlink.cfg", 15 | ], 16 | "servertype": "openocd", 17 | "openOCDLaunchCommands": [ 18 | "transport select swd", 19 | "source [find target/nrf52.cfg]" 20 | ], 21 | // commands only supported in OpenOCD 0.11.0; also due to how the `rtt-target` crate works 22 | // these commands need to run _after_ the target executes the `rtt_init` macro so running 23 | // these commands when the device is halted on `main` will fail 24 | // "postLaunchCommands": [ 25 | // // FIXME(?) to work with a newer version (>0.3.7) of the cortex-debug extension the 26 | // // escaped backslashes (`\\`) may need to be removed 27 | // "monitor rtt setup 0x20000000 262144 \\\"SEGGER RTT\\\"", 28 | // "monitor rtt start", 29 | // "monitor rtt server start 8765 0", 30 | // ], 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // override the default setting (`cargo check --all-targets`) which produces the following error 3 | // "can't find crate for `test`" when the default compilation target is a no_std target 4 | // with these changes RA will call `cargo check --bins` on save 5 | "rust-analyzer.checkOnSave.allTargets": false, 6 | "rust-analyzer.checkOnSave.extraArgs": [ 7 | "--bins" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "hal_app" 6 | version = "0.0.0" 7 | description = "Solutions for the nRF52 HAL exercises" 8 | 9 | [dependencies] 10 | cortex-m = {version = "0.7.7", features = ["critical-section-single-core"]} 11 | cortex-m-rt = "0.7.5" 12 | dk = { path = "../boards/dk", features = ["radio"] } 13 | heapless = "0.8" 14 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 15 | defmt-rtt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219" } 16 | 17 | # optimise a little bit 18 | [profile.dev] 19 | opt-level = 1 20 | 21 | # enable LTO and turn on debug 22 | [profile.release] 23 | codegen-units = 1 24 | debug = 2 25 | debug-assertions = false 26 | incremental = false 27 | lto = "fat" 28 | opt-level = 3 29 | overflow-checks = false 30 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/src/bin/blinky.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::time::Duration; 5 | 6 | use cortex_m_rt::entry; 7 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 8 | use hal_app as _; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | // to enable more verbose logs, set the `DEFMT_LOG` environment variable. 13 | 14 | let board = dk::init().unwrap(); 15 | 16 | let mut led = board.leds._1; 17 | let mut timer = board.timer; 18 | 19 | for _ in 0..10 { 20 | led.toggle(); 21 | timer.wait(Duration::from_secs(1)); 22 | defmt::debug!("LED toggled @ {=u64:tus}", dk::uptime_us()); 23 | } 24 | 25 | dk::exit() 26 | } 27 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/src/bin/buttons.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::time::Duration; 5 | 6 | use cortex_m_rt::entry; 7 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 8 | use hal_app as _; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | let board = dk::init().unwrap(); 13 | 14 | let mut led = board.leds._1; 15 | let mut timer = board.timer; 16 | // Uncomment the line below 17 | // 👇 18 | // let mut button = board.buttons._1; 19 | 20 | defmt::println!("Polling button every 100ms"); 21 | loop { 22 | // Replace `true` with `button.is_pressed()` 23 | // 👇 24 | if true { 25 | led.on(); 26 | } else { 27 | led.off(); 28 | } 29 | timer.wait(Duration::from_millis(100)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/src/bin/hello.rs: -------------------------------------------------------------------------------- 1 | // this program does not use the standard library to avoid heap allocations. 2 | // only the `core` library functions are available. 3 | #![no_std] 4 | // this program uses a custom entry point instead of `fn main()` 5 | #![no_main] 6 | 7 | use cortex_m_rt::entry; 8 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 9 | use hal_app as _; 10 | 11 | // the custom entry point 12 | // 👇🏾 13 | #[entry] 14 | fn main() -> ! { 15 | // ˆˆˆ 16 | // ! is the 'never' type: this function never returns 17 | 18 | // initializes the peripherals 19 | dk::init().unwrap(); 20 | 21 | defmt::println!("Hello, world!"); // 👋🏾 22 | 23 | dk::exit(); 24 | } 25 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/src/bin/led.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 7 | use hal_app as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | // to enable more verbose logs, set the `DEFMT_LOG` environment variable. 12 | 13 | let board = dk::init().unwrap(); 14 | 15 | let mut leds = board.leds; 16 | leds._1.on(); 17 | leds._2.off(); 18 | leds._3.off(); 19 | leds._4.on(); 20 | 21 | // this program does not `exit`; use Ctrl+C to terminate it 22 | loop { 23 | asm::nop(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/src/bin/panic.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 7 | use hal_app as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | dk::init().unwrap(); 12 | 13 | foo(); 14 | 15 | loop { 16 | asm::bkpt(); 17 | } 18 | } 19 | 20 | #[inline(never)] 21 | fn foo() { 22 | asm::nop(); 23 | bar(); 24 | } 25 | 26 | #[inline(never)] 27 | fn bar() { 28 | let i = index(); 29 | let array = [0, 1, 2]; 30 | let x = array[i]; // out of bounds access 31 | 32 | defmt::println!("{}", x); 33 | } 34 | 35 | fn index() -> usize { 36 | 3 37 | } 38 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/src/bin/stack_overflow.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 7 | use hal_app as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | // board initialization 12 | dk::init().unwrap(); 13 | 14 | fib(100); 15 | 16 | loop { 17 | asm::bkpt(); 18 | } 19 | } 20 | 21 | #[inline(never)] 22 | fn fib(n: u32) -> u32 { 23 | // allocate and initialize one kilobyte of stack memory to provoke stack overflow 24 | let use_stack = [0xAA; 1024]; 25 | defmt::println!("allocating [{}; 1024]; round #{}", use_stack[1023], n); 26 | 27 | if n < 2 { 28 | 1 29 | } else { 30 | fib(n - 1) + fib(n - 2) // recursion 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nrf52-code/hal-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use cortex_m_rt::exception; 4 | 5 | /// Our custom panic handler. 6 | #[panic_handler] 7 | fn panic(info: &core::panic::PanicInfo) -> ! { 8 | defmt::error!("{}", defmt::Display2Format(info)); 9 | dk::fail(); 10 | } 11 | 12 | /// The default HardFault handler just spins, so replace it. 13 | #[exception] 14 | unsafe fn HardFault(_ef: &cortex_m_rt::ExceptionFrame) -> ! { 15 | defmt::error!("HardFault!"); 16 | dk::fail(); 17 | } 18 | 19 | // this prevents the panic message being printed *twice* when `defmt::panic!` is invoked 20 | #[defmt::panic_handler] 21 | fn defmt_panic() -> ! { 22 | dk::fail(); 23 | } 24 | 25 | defmt::timestamp!("{=u64:tus}", dk::uptime_us()); 26 | -------------------------------------------------------------------------------- /nrf52-code/loopback-fw/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | # set custom cargo runner to flash & run on embedded target when we call `cargo run` 3 | # for more information, check out https://github.com/probe-rs 4 | runner = "nrfdfu" 5 | linker = "flip-link" # adds stack overflow protection 6 | rustflags = [ 7 | "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script 8 | "-C", "link-arg=-Tdefmt.x", # defmt support 9 | ] 10 | 11 | [build] 12 | # cross-compile to this target 13 | target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU 14 | -------------------------------------------------------------------------------- /nrf52-code/loopback-fw/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/loopback-fw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "loopback-fw" 6 | version = "0.0.0" 7 | 8 | [dependencies] 9 | consts = { path = "../consts" } 10 | defmt = "1" 11 | defmt-rtt = "1" 12 | dongle = { path = "../boards/dongle" } 13 | heapless = { version = "0.8.0", features = ["defmt-03"] } 14 | rtic = { version = "2.1.2", features = ["thumbv7-backend"] } 15 | rtic-monotonics = { version = "2.0.3", features = ["cortex-m-systick"] } 16 | usb-device = { version = "0.3.2", features = ["defmt"] } 17 | usbd-hid = { version = "0.8.2", features = ["defmt"] } 18 | usbd-serial = "0.2.2" 19 | -------------------------------------------------------------------------------- /nrf52-code/puzzle-fw/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | # set custom cargo runner to DFU the dongle 3 | runner = "nrfdfu" 4 | rustflags = [ 5 | "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script 6 | "-C", "linker=flip-link", # adds stack overflow protection 7 | "-C", "link-arg=-Tdefmt.x", # defmt support 8 | ] 9 | 10 | [build] 11 | # cross-compile to this target 12 | target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU 13 | -------------------------------------------------------------------------------- /nrf52-code/puzzle-fw/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/puzzle-fw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "puzzle-fw" 6 | version = "0.0.0" 7 | 8 | [dependencies] 9 | consts = { path = "../consts" } 10 | defmt = "1" 11 | defmt-rtt = "1" 12 | dongle = { path = "../boards/dongle" } 13 | heapless = { version = "0.8.0", features = ["defmt-03"] } 14 | rtic = { version = "2.1.2", features = ["thumbv7-backend"] } 15 | rtic-monotonics = { version = "2.0.3", features = ["cortex-m-systick"] } 16 | usb-device = { version = "0.3.2", features = ["defmt"] } 17 | usbd-hid = { version = "0.8.2", features = ["defmt"] } 18 | usbd-serial = "0.2.2" 19 | 20 | [build-dependencies] 21 | rand = "0.8.5" 22 | -------------------------------------------------------------------------------- /nrf52-code/puzzle-fw/build.rs: -------------------------------------------------------------------------------- 1 | //! Configure the puzzle firmware 2 | 3 | use rand::prelude::*; 4 | 5 | fn main() { 6 | // We avoid \ to prevent escaping issues 7 | const PLAIN_LETTERS: &str = r##"0123456789 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##; 8 | 9 | let maybe_msg = std::env::var("HIDDEN_MESSAGE"); 10 | let plaintext = maybe_msg.as_deref().unwrap_or("This is an example message"); 11 | 12 | let mut rng = rand::thread_rng(); 13 | let mut cipher_letters: Vec = PLAIN_LETTERS.bytes().collect(); 14 | cipher_letters.shuffle(&mut rng); 15 | let cipher_letters_str = std::str::from_utf8(&cipher_letters).unwrap(); 16 | 17 | let mut dict = std::collections::HashMap::new(); 18 | for (from, &to) in PLAIN_LETTERS.bytes().zip(cipher_letters.iter()) { 19 | dict.insert(from, to); 20 | } 21 | 22 | println!("from: {:?}", PLAIN_LETTERS); 23 | println!("to: {:?}", cipher_letters_str); 24 | println!("plaintext: {:?}", plaintext); 25 | 26 | let encoded: Vec = plaintext.bytes().map(|byte| dict[&byte]).collect(); 27 | let encoded_str = std::str::from_utf8(&encoded).unwrap(); 28 | println!("secret: {:?}", encoded_str); 29 | 30 | output_data("ENCODED_MESSAGE.txt", encoded_str); 31 | output_data("PLAIN_LETTERS.txt", PLAIN_LETTERS); 32 | output_data("CIPHER_LETTERS.txt", cipher_letters_str); 33 | 34 | println!("cargo:rerun-if-env-changed=HIDDEN_MESSAGE"); 35 | } 36 | 37 | fn output_data(filename: &str, value: &str) { 38 | let out_dir: std::path::PathBuf = std::env::var_os("OUT_DIR").unwrap().into(); 39 | let filepath = out_dir.join(filename); 40 | std::fs::write(filepath, value).unwrap(); 41 | } 42 | 43 | // End of file 44 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | # set custom cargo runner to flash & run on embedded target when we call `cargo run` 3 | # for more information, check out https://github.com/probe-rs 4 | runner = [ 5 | "probe-rs", 6 | "run", 7 | "--chip=nRF52840_xxAA", 8 | "--allow-erase-all", 9 | "--log-format=oneline" 10 | ] 11 | 12 | rustflags = [ 13 | "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script 14 | "-C", "linker=flip-link", # adds stack overflow protection 15 | "-C", "link-arg=-Tdefmt.x", # defmt support 16 | ] 17 | 18 | [build] 19 | # cross-compile to this target 20 | target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU 21 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/.vscode/.cortex-debug.registers.state.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /nrf52-code/radio-app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "preLaunchTask": "rust: cargo build", 6 | "type": "probe-rs-debug", 7 | "request": "launch", 8 | "name": "Run with probe-rs", 9 | "flashingConfig": { 10 | "flashingEnabled": true, 11 | }, 12 | "chip": "nRF52840_xxAA", 13 | "coreConfigs": [ 14 | { 15 | // Change this to the binary you want to debug 16 | "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/panic", 17 | "rttEnabled": true 18 | } 19 | ], 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /nrf52-code/radio-app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // override the default setting (`cargo check --all-targets`) which produces the following error 3 | // "can't find crate for `test`" when the default compilation target is a no_std target 4 | // with these changes RA will call `cargo check --bins` on save 5 | "rust-analyzer.checkOnSave.allTargets": false, 6 | "rust-analyzer.checkOnSave.extraArgs": [ 7 | "--bins" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "radio_app" 6 | version = "0.0.0" 7 | description = "Solutions for the nRF52 radio exercises" 8 | 9 | [dependencies] 10 | cortex-m = {version = "0.7.7", features = ["critical-section-single-core"]} 11 | cortex-m-rt = "0.7.5" 12 | dk = { path = "../boards/dk", features = ["radio"] } 13 | heapless = "0.8" 14 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 15 | defmt-rtt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219" } 16 | 17 | # optimise a little bit 18 | [profile.dev] 19 | opt-level = 1 20 | 21 | # enable LTO and turn on debug 22 | [profile.release] 23 | codegen-units = 1 24 | debug = 2 25 | debug-assertions = false 26 | incremental = false 27 | lto = "fat" 28 | opt-level = 3 29 | overflow-checks = false 30 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/blinky.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::time::Duration; 5 | 6 | use cortex_m_rt::entry; 7 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 8 | use radio_app as _; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | // to enable more verbose logs, set the `DEFMT_LOG` environment variable. 13 | 14 | let board = dk::init().unwrap(); 15 | 16 | let mut led = board.leds._1; 17 | let mut timer = board.timer; 18 | 19 | for _ in 0..10 { 20 | led.toggle(); 21 | timer.wait(Duration::from_secs(1)); 22 | defmt::debug!("LED toggled @ {=u64:tus}", dk::uptime_us()); 23 | } 24 | 25 | dk::exit() 26 | } 27 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/hello.rs: -------------------------------------------------------------------------------- 1 | // this program does not use the standard library to avoid heap allocations. 2 | // only the `core` library functions are available. 3 | #![no_std] 4 | // this program uses a custom entry point instead of `fn main()` 5 | #![no_main] 6 | 7 | use cortex_m_rt::entry; 8 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 9 | use radio_app as _; 10 | 11 | // the custom entry point 12 | // 👇🏾 13 | #[entry] 14 | fn main() -> ! { 15 | // ˆˆˆ 16 | // ! is the 'never' type: this function never returns 17 | 18 | // initializes the peripherals 19 | dk::init().unwrap(); 20 | 21 | defmt::println!("Hello, world!"); // 👋🏾 22 | 23 | dk::exit(); 24 | } 25 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/led.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 7 | use radio_app as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | // to enable more verbose logs, set the `DEFMT_LOG` environment variable. 12 | 13 | let board = dk::init().unwrap(); 14 | 15 | let mut leds = board.leds; 16 | leds._1.on(); 17 | leds._2.off(); 18 | leds._3.off(); 19 | leds._4.on(); 20 | 21 | // this program does not `exit`; use Ctrl+C to terminate it 22 | loop { 23 | asm::nop(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/panic.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 7 | use radio_app as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | dk::init().unwrap(); 12 | 13 | foo(); 14 | 15 | loop { 16 | asm::bkpt(); 17 | } 18 | } 19 | 20 | #[inline(never)] 21 | fn foo() { 22 | asm::nop(); 23 | bar(); 24 | } 25 | 26 | #[inline(never)] 27 | fn bar() { 28 | let i = index(); 29 | let array = [0, 1, 2]; 30 | let x = array[i]; // out of bounds access 31 | 32 | defmt::println!("{}", x); 33 | } 34 | 35 | fn index() -> usize { 36 | 3 37 | } 38 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-puzzle-1.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use dk::ieee802154::{Channel, Packet}; 7 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 8 | use radio_app as _; 9 | 10 | const TEN_MS: u32 = 10_000; 11 | 12 | #[entry] 13 | fn main() -> ! { 14 | let board = dk::init().unwrap(); 15 | let mut radio = board.radio; 16 | let mut timer = board.timer; 17 | 18 | // puzzle-fw uses channel 25 by default 19 | // NOTE if you ran `change-channel` then you may need to update the channel here 20 | radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel 21 | 22 | // the IEEE 802.15.4 packet that will carry our data 23 | let mut packet = Packet::new(); 24 | 25 | // first exchange a single packet with the Dongle 26 | // letter 'A' (uppercase) 27 | let input = 65; 28 | // let input = b'A'; // this is the same as above 29 | 30 | // TODO try other letters 31 | 32 | // single letter (byte) packet 33 | if let Ok(data) = dk::send_recv(&mut packet, &[input], &mut radio, &mut timer, TEN_MS) { 34 | // response should be one byte large 35 | if data.len() == 1 { 36 | let output = data[0]; 37 | 38 | defmt::println!("{:02x} -> {:02x}", input, output); 39 | // or cast to `char` for a more readable output 40 | defmt::println!("'{:?}' -> '{:?}'", input as char, output as char); 41 | } else { 42 | defmt::error!("response packet was not a single byte"); 43 | dk::exit() 44 | } 45 | } else { 46 | defmt::error!("no response or response packet was corrupted"); 47 | dk::exit() 48 | } 49 | 50 | // TODO next do the whole ASCII range [0, 127] 51 | // start small: just 'A' and 'B' at first 52 | // NOTE: `a..=b` means inclusive range; `a` and `b` are included in the range 53 | // `a..b` means open-ended range; `a` is included in the range but `b` isn't 54 | for _input in b'A'..=b'B' { 55 | // TODO similar procedure as above 56 | } 57 | 58 | dk::exit() 59 | } 60 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-puzzle-2.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | // NOTE you can use `FnvIndexMap` instead of `LinearMap`; the former may have better 7 | // lookup performance when the dictionary contains a large number of items but performance is 8 | // not important for this exercise 9 | use heapless::LinearMap; 10 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 11 | use radio_app as _; 12 | 13 | #[entry] 14 | fn main() -> ! { 15 | dk::init().unwrap(); 16 | 17 | // a dictionary with capacity for 2 elements 18 | let mut dict = LinearMap::<_, _, 2>::new(); 19 | // content types ^^ ^^ ^ capacity 20 | // (inferred by rust) 21 | 22 | // do some insertions 23 | dict.insert(b'A', b'*').expect("dictionary full"); 24 | dict.insert(b'B', b'/').expect("dictionary full"); 25 | 26 | // do some lookups 27 | let key = b'A'; 28 | let value = dict[&key]; // the key needs to be passed by reference 29 | 30 | defmt::println!("{} -> {}", key, value); 31 | // more readable 32 | defmt::println!("{:?} -> {:?}", key as char, value as char); 33 | 34 | // TODO try another insertion 35 | // TODO try looking up a key not contained in the dictionary 36 | // TODO try changing the capacity 37 | 38 | dk::exit() 39 | } 40 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-puzzle-3.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use dk::ieee802154::{Channel, Packet}; 7 | use heapless::LinearMap; 8 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 9 | use radio_app as _; 10 | 11 | const TEN_MS: u32 = 10_000; 12 | 13 | #[entry] 14 | fn main() -> ! { 15 | let board = dk::init().unwrap(); 16 | let mut radio = board.radio; 17 | let mut timer = board.timer; 18 | 19 | // puzzle-fw uses channel 25 by default 20 | // NOTE if you ran `change-channel` then you may need to update the channel here 21 | radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel 22 | 23 | // capacity (128) should be large enough for the ASCII range 24 | let dict = LinearMap::::new(); 25 | 26 | let mut packet = Packet::new(); 27 | // TODO do the whole ASCII range [0, 127] 28 | for input in b'A'..=b'B' { 29 | if let Ok(data) = dk::send_recv(&mut packet, &[input], &mut radio, &mut timer, TEN_MS) { 30 | // response should be one byte large 31 | if data.len() == 1 { 32 | let _output = data[0]; 33 | // TODO insert the key-value pair 34 | // dict.insert(/* ? */, /* ? */).expect("dictionary full"); 35 | } else { 36 | defmt::error!("response packet was not a single byte"); 37 | dk::exit() 38 | } 39 | } else { 40 | defmt::error!("no response or response packet was corrupted"); 41 | dk::exit() 42 | } 43 | } 44 | 45 | defmt::println!("{:?}", defmt::Debug2Format(&dict)); 46 | // ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log `heapless` 47 | // data structures (like `LinearMap` here) with `defmt` 48 | 49 | dk::exit() 50 | } 51 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-puzzle-4.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use core::str; 6 | 7 | use cortex_m_rt::entry; 8 | use heapless::Vec; 9 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 10 | use radio_app as _; 11 | 12 | #[entry] 13 | fn main() -> ! { 14 | dk::init().unwrap(); 15 | 16 | // a buffer with capacity for 2 bytes 17 | let mut buffer = Vec::::new(); 18 | // content type ^^ ^ capacity 19 | 20 | // do some insertions 21 | buffer.push(b'H').expect("buffer full"); 22 | buffer.push(b'i').expect("buffer full"); 23 | 24 | // look into the contents so far 25 | defmt::println!("{}", defmt::Debug2Format(&buffer)); 26 | // ^^^^^^^^^^^^^^^^^^^ this adapter is currently needed to log 27 | // `StandardRequest` with `defmt` 28 | 29 | // or more readable 30 | // NOTE utf-8 conversion works as long as you only push bytes in the ASCII range (0..=127) 31 | defmt::println!( 32 | "{}", 33 | str::from_utf8(&buffer).expect("content was not UTF-8") 34 | ); 35 | 36 | dk::exit() 37 | } 38 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-puzzle-5.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use core::str; 6 | 7 | use cortex_m_rt::entry; 8 | use dk::ieee802154::{Channel, Packet}; 9 | use heapless::Vec; 10 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 11 | use radio_app as _; 12 | 13 | const TEN_MS: u32 = 10_000; 14 | 15 | #[entry] 16 | fn main() -> ! { 17 | let board = dk::init().unwrap(); 18 | let mut radio = board.radio; 19 | let mut timer = board.timer; 20 | 21 | // puzzle-fw uses channel 25 by default 22 | // NOTE if you ran `change-channel` then you may need to update the channel here 23 | radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel 24 | 25 | let mut packet = Packet::new(); 26 | 27 | /* # Retrieve the secret string */ 28 | let Ok(secret) = dk::send_recv(&mut packet, &[], &mut radio, &mut timer, TEN_MS) else { 29 | defmt::error!("no response or response packet was corrupted"); 30 | dk::exit() 31 | }; 32 | 33 | defmt::println!( 34 | "ciphertext: {}", 35 | str::from_utf8(&secret).expect("packet was not valid UTF-8") 36 | ); 37 | 38 | /* # Decrypt the string */ 39 | let mut buf = Vec::::new(); 40 | 41 | // iterate over the bytes 42 | for input in secret.iter() { 43 | // process each byte 44 | // here we should do the reverse mapping; instead we'll do a shift for illustrative purposes 45 | let output = input + 1; 46 | buf.push(output).expect("buffer full"); 47 | } 48 | 49 | defmt::println!( 50 | "plaintext: {}", 51 | str::from_utf8(&buf).expect("buffer contains non-UTF-8 data") 52 | ); 53 | 54 | dk::exit() 55 | } 56 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-puzzle.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use core::str; 6 | 7 | use cortex_m_rt::entry; 8 | use dk::ieee802154::{Channel, Packet}; 9 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 10 | use radio_app as _; 11 | 12 | const TEN_MS: u32 = 10_000; 13 | 14 | #[entry] 15 | fn main() -> ! { 16 | let board = dk::init().unwrap(); 17 | let mut radio = board.radio; 18 | let mut timer = board.timer; 19 | 20 | // puzzle-fw uses channel 25 by default 21 | // NOTE if you ran `change-channel` then you may need to update the channel here 22 | radio.set_channel(Channel::_25); // <- must match the Dongle's listening channel 23 | 24 | let mut packet = Packet::new(); 25 | 26 | // try one of these 3 options 27 | let msg = b""; 28 | 29 | // these 3 lines are equivalent 30 | // let msg: &[u8; 1] = b"A"; 31 | // let msg: &[u8; 1] = &[b'A']; 32 | // let msg: &[u8; 1] = &[65]; 33 | 34 | // let msg = b"Hello?"; 35 | 36 | defmt::println!( 37 | "sending: {}", 38 | str::from_utf8(msg).expect("msg was not valid UTF-8 data") 39 | ); 40 | 41 | if let Ok(reply) = dk::send_recv(&mut packet, msg, &mut radio, &mut timer, TEN_MS) { 42 | defmt::println!( 43 | "received: {}", 44 | str::from_utf8(reply).expect("response was not valid UTF-8 data") 45 | ); 46 | } else { 47 | defmt::error!("no response or response packet was corrupted"); 48 | } 49 | dk::exit() 50 | } 51 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-recv.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use core::str; 6 | 7 | use cortex_m_rt::entry; 8 | use dk::ieee802154::{Channel, Error, Packet}; 9 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 10 | use radio_app as _; 11 | 12 | const TEN_MS: u32 = 10_000; 13 | 14 | #[entry] 15 | fn main() -> ! { 16 | // initializes the peripherals 17 | let board = dk::init().unwrap(); 18 | let mut radio = board.radio; 19 | let mut timer = board.timer; 20 | 21 | // NOTE if you ran `change-channel` then you may need to update the channel here 22 | radio.set_channel(Channel::_20); // <- must match the Dongle's listening channel 23 | 24 | let mut packet = Packet::new(); 25 | let msg = b"olleh"; 26 | packet.copy_from_slice(msg); 27 | 28 | defmt::println!( 29 | "sending: {}", 30 | str::from_utf8(msg).expect("message is not valid UTF-8") 31 | ); 32 | radio.send(&mut packet); 33 | 34 | // TODO try uncommenting this line 35 | // timer.wait(core::time::Duration::from_micros(1000)); 36 | 37 | let res = radio.recv_timeout(&mut packet, &mut timer, TEN_MS); 38 | 39 | match res { 40 | Ok(crc) => { 41 | defmt::println!( 42 | "received: {} (CRC = {:X})", 43 | // ^^ print as uppercase hexadecimal 44 | str::from_utf8(&packet).expect("response is not valid UTF-8"), 45 | crc 46 | ); 47 | } 48 | Err(Error::Crc(crc)) => defmt::error!("invalid CRC: {:X}", crc), 49 | Err(Error::Timeout) => defmt::error!("no response within {} ms", TEN_MS / 1_000), 50 | } 51 | 52 | dk::exit() 53 | } 54 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/radio-send.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use core::str; 6 | 7 | use cortex_m_rt::entry; 8 | use dk::ieee802154::{Channel, Packet, TxPower}; 9 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 10 | use radio_app as _; 11 | 12 | #[entry] 13 | fn main() -> ! { 14 | let board = dk::init().unwrap(); 15 | let mut radio = board.radio; 16 | 17 | // these are the default settings of the DK's radio 18 | // NOTE if you ran `change-channel` then you may need to update the channel here 19 | radio.set_channel(Channel::_20); // <- must match the Dongle's listening channel 20 | radio.set_txpower(TxPower::Pos8dBm); 21 | 22 | let mut packet = Packet::new(); 23 | 24 | // these three are equivalent 25 | let msg: &[u8; 5] = &[72, 101, 108, 108, 111]; 26 | // let msg: &[u8; 5] = &[b'H', b'e', b'l', b'l', b'o']; 27 | // let msg: &[u8; 5] = b"Hello"; 28 | 29 | defmt::println!( 30 | "sending: {}", 31 | str::from_utf8(msg).expect("msg is not valid UTF-8 data") 32 | ); 33 | 34 | packet.copy_from_slice(msg); 35 | 36 | radio.send(&mut packet); 37 | 38 | dk::exit(); 39 | } 40 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/bin/stack_overflow.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 7 | use radio_app as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | // board initialization 12 | dk::init().unwrap(); 13 | 14 | fib(100); 15 | 16 | loop { 17 | asm::bkpt(); 18 | } 19 | } 20 | 21 | #[inline(never)] 22 | fn fib(n: u32) -> u32 { 23 | // allocate and initialize one kilobyte of stack memory to provoke stack overflow 24 | let use_stack = [0xAA; 1024]; 25 | defmt::println!("allocating [{}; 1024]; round #{}", use_stack[1023], n); 26 | 27 | if n < 2 { 28 | 1 29 | } else { 30 | fib(n - 1) + fib(n - 2) // recursion 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nrf52-code/radio-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use cortex_m_rt::exception; 4 | 5 | /// Our custom panic handler. 6 | #[panic_handler] 7 | fn panic(info: &core::panic::PanicInfo) -> ! { 8 | defmt::error!("{}", defmt::Display2Format(info)); 9 | dk::fail(); 10 | } 11 | 12 | /// The default HardFault handler just spins, so replace it. 13 | #[exception] 14 | unsafe fn HardFault(_ef: &cortex_m_rt::ExceptionFrame) -> ! { 15 | defmt::error!("HardFault!"); 16 | dk::fail(); 17 | } 18 | 19 | // this prevents the panic message being printed *twice* when `defmt::panic!` is invoked 20 | #[defmt::panic_handler] 21 | fn defmt_panic() -> ! { 22 | dk::fail(); 23 | } 24 | 25 | defmt::timestamp!("{=u64:tus}", dk::uptime_us()); 26 | -------------------------------------------------------------------------------- /nrf52-code/usb-app-solutions/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | # set custom cargo runner to flash & run on embedded target when we call `cargo run` 3 | # for more information, check out https://github.com/probe-rs 4 | runner = [ 5 | "probe-rs", 6 | "run", 7 | "--chip=nRF52840_xxAA", 8 | "--allow-erase-all", 9 | "--log-format=oneline" 10 | ] 11 | 12 | rustflags = [ 13 | "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script 14 | "-C", "linker=flip-link", # adds stack overflow protection 15 | "-C", "link-arg=-Tdefmt.x", # defmt support 16 | ] 17 | 18 | [build] 19 | # cross-compile to this target 20 | target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU 21 | -------------------------------------------------------------------------------- /nrf52-code/usb-app-solutions/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/usb-app-solutions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // override the default setting (`cargo check --all-targets`) which produces the following error 3 | // "can't find crate for `test`" when the default compilation target is a no_std target 4 | // with these changes RA will call `cargo check --bins` on save 5 | "rust-analyzer.checkOnSave.allTargets": false, 6 | "rust-analyzer.checkOnSave.extraArgs": [ 7 | "--bins" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /nrf52-code/usb-app-solutions/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb-app" 6 | version = "0.0.0" 7 | 8 | [build-dependencies] 9 | consts = { path = "../consts" } 10 | quote = "1" 11 | usb2 = "0.0.1" 12 | 13 | [dependencies] 14 | consts = { path = "../consts" } 15 | cortex-m = "0.7.7" 16 | cortex-m-rt = "0.7.5" 17 | rtic = { version = "2", features = ["thumbv7-backend"] } 18 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 19 | defmt-rtt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219" } 20 | dk = { path = "../boards/dk", features = ["advanced"] } 21 | usb = { path = "../usb-lib-solutions/complete" } 22 | usb2 = "0.0.1" 23 | 24 | [dependencies.heapless] 25 | version = "0.7.16" 26 | features = ["defmt-impl"] 27 | 28 | # optimise a little bit 29 | [profile.dev] 30 | opt-level = 1 31 | 32 | # enable LTO and turn on debug 33 | [profile.release] 34 | codegen-units = 1 35 | debug = 2 36 | debug-assertions = false 37 | incremental = false 38 | lto = "fat" 39 | opt-level = 3 40 | overflow-checks = false 41 | 42 | [patch.crates-io] 43 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 44 | -------------------------------------------------------------------------------- /nrf52-code/usb-app-solutions/src/bin/task-state.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 5 | use usb_app as _; 6 | 7 | #[rtic::app(device = dk, peripherals = false)] 8 | mod app { 9 | use cortex_m::asm; 10 | use dk::peripheral::POWER; 11 | 12 | #[local] 13 | struct MyLocalResources { 14 | power: POWER, 15 | counter: usize, 16 | } 17 | 18 | #[shared] 19 | struct MySharedResources {} 20 | 21 | #[init] 22 | fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources) { 23 | let board = dk::init().unwrap(); 24 | 25 | let power = board.power; 26 | 27 | power.intenset.write(|w| w.usbdetected().set_bit()); 28 | 29 | defmt::println!("USBDETECTED interrupt enabled"); 30 | 31 | ( 32 | MySharedResources {}, 33 | MyLocalResources { 34 | power, 35 | counter: 0, // <- initialize the new resource 36 | }, 37 | ) 38 | } 39 | 40 | #[idle] 41 | fn idle(_cx: idle::Context) -> ! { 42 | loop { 43 | defmt::println!("idle: going to sleep"); 44 | asm::wfi(); 45 | defmt::println!("idle: woke up"); 46 | } 47 | } 48 | 49 | #[task(binds = POWER_CLOCK, local = [power, counter])] 50 | // ^^^^^^^ we want to access the resource from here 51 | fn on_power_event(cx: on_power_event::Context) { 52 | defmt::println!("POWER event occurred"); 53 | 54 | *cx.local.counter += 1; 55 | 56 | defmt::println!( 57 | "on_power_event: cable connected {} time{}", 58 | *cx.local.counter, 59 | if *cx.local.counter != 1 { "s" } else { "" } 60 | ); 61 | 62 | // clear the interrupt flag; otherwise this task will run again after it returns 63 | cx.local.power.events_usbdetected.reset(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /nrf52-code/usb-app-solutions/src/bin/usb-1.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use dk::{ 5 | peripheral::USBD, 6 | usbd::{self, Event}, 7 | }; 8 | 9 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 10 | use usb_app as _; 11 | 12 | #[rtic::app(device = dk, peripherals = false)] 13 | mod app { 14 | use super::*; 15 | 16 | #[local] 17 | struct MyLocalResources { 18 | usbd: USBD, 19 | } 20 | 21 | #[shared] 22 | struct MySharedResources {} 23 | 24 | #[init] 25 | fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources) { 26 | let board = dk::init().unwrap(); 27 | 28 | // initialize the USBD peripheral 29 | // NOTE this will block if the USB cable is not connected to port J3 30 | usbd::init(board.power, &board.usbd); 31 | 32 | defmt::println!("USBD initialized"); 33 | 34 | (MySharedResources {}, MyLocalResources { usbd: board.usbd }) 35 | } 36 | 37 | #[task(binds = USBD, local = [usbd])] 38 | fn handle_usb_interrupt(cx: handle_usb_interrupt::Context) { 39 | while let Some(event) = usbd::next_event(cx.local.usbd) { 40 | on_event(cx.local.usbd, event) 41 | } 42 | } 43 | } 44 | 45 | /// Handle a USB event (in interrupt context) 46 | fn on_event(_usbd: &USBD, event: Event) { 47 | defmt::debug!("USB: {} @ {=u64:tus}", event, dk::uptime_us()); 48 | 49 | match event { 50 | Event::UsbReset => { 51 | defmt::println!("returning to the Default state"); 52 | } 53 | Event::UsbEp0Setup => { 54 | defmt::println!("usb-1 exercise complete"); 55 | dk::exit(); 56 | } 57 | Event::UsbEp0DataDone => todo!(), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /nrf52-code/usb-app-solutions/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use cortex_m_rt::exception; 4 | 5 | /// Our custom panic handler. 6 | #[panic_handler] 7 | fn panic(info: &core::panic::PanicInfo) -> ! { 8 | defmt::error!("{}", defmt::Display2Format(info)); 9 | dk::fail(); 10 | } 11 | 12 | /// The default HardFault handler just spins, so replace it. 13 | #[exception] 14 | unsafe fn HardFault(_ef: &cortex_m_rt::ExceptionFrame) -> ! { 15 | defmt::error!("HardFault!"); 16 | dk::fail(); 17 | } 18 | 19 | // this prevents the panic message being printed *twice* when `defmt::panic!` is invoked 20 | #[defmt::panic_handler] 21 | fn defmt_panic() -> ! { 22 | dk::fail(); 23 | } 24 | 25 | defmt::timestamp!("{=u64:tus}", dk::uptime_us()); 26 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.thumbv7em-none-eabihf] 2 | # set custom cargo runner to flash & run on embedded target when we call `cargo run` 3 | # for more information, check out https://github.com/probe-rs 4 | runner = [ 5 | "probe-rs", 6 | "run", 7 | "--chip=nRF52840_xxAA", 8 | "--allow-erase-all", 9 | "--log-format=oneline" 10 | ] 11 | 12 | linker = "flip-link" # adds stack overflow protection 13 | rustflags = [ 14 | "-C", "link-arg=-Tlink.x", # use the cortex-m-rt linker script 15 | "-C", "link-arg=-Tdefmt.x", # defmt support 16 | ] 17 | 18 | [build] 19 | # cross-compile to this target 20 | target = "thumbv7em-none-eabihf" # = ARM Cortex-M4 with FPU 21 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "preLaunchTask": "rust: cargo build", 6 | "type": "probe-rs-debug", 7 | "request": "launch", 8 | "name": "Run with probe-rs", 9 | "flashingConfig": { 10 | "flashingEnabled": true, 11 | }, 12 | "chip": "nRF52840_xxAA", 13 | "coreConfigs": [ 14 | { 15 | // Change this to the binary you want to debug 16 | "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/rtic-hello", 17 | "rttEnabled": true 18 | } 19 | ], 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /nrf52-code/usb-app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // override the default setting (`cargo check --all-targets`) which produces the following error 3 | // "can't find crate for `test`" when the default compilation target is a no_std target 4 | // with these changes RA will call `cargo check --bins` on save 5 | "rust-analyzer.checkOnSave.allTargets": false, 6 | "rust-analyzer.checkOnSave.extraArgs": [ 7 | "--bins" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb-app" 6 | version = "0.0.0" 7 | 8 | [build-dependencies] 9 | consts = { path = "../consts" } 10 | quote = "1" 11 | usb2 = "0.0.1" 12 | 13 | [dependencies] 14 | consts = { path = "../consts" } 15 | cortex-m = "0.7.7" 16 | cortex-m-rt = "0.7.5" 17 | rtic = { version = "2", features = ["thumbv7-backend"] } 18 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 19 | defmt-rtt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219" } 20 | dk = { path = "../boards/dk", features = ["advanced"] } 21 | usb = { path = "../usb-lib" } 22 | usb2 = "0.0.1" 23 | 24 | [dependencies.heapless] 25 | version = "0.7.16" 26 | features = ["defmt-impl"] 27 | 28 | # optimise a little bit 29 | [profile.dev] 30 | opt-level = 1 31 | 32 | # enable LTO and turn on debug 33 | [profile.release] 34 | codegen-units = 1 35 | debug = 2 36 | debug-assertions = false 37 | incremental = false 38 | lto = "fat" 39 | opt-level = 3 40 | overflow-checks = false 41 | 42 | [patch.crates-io] 43 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 44 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/src/bin/hello.rs: -------------------------------------------------------------------------------- 1 | // this program does not use the standard library to avoid heap allocations. 2 | // only the `core` library functions are available. 3 | #![no_std] 4 | // this program uses a custom entry point instead of `fn main()` 5 | #![no_main] 6 | 7 | use cortex_m_rt::entry; 8 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 9 | use usb_app as _; 10 | 11 | // the custom entry point 12 | // 👇🏾 13 | #[entry] 14 | fn main() -> ! { 15 | // ˆˆˆ 16 | // ! is the 'never' type: this function never returns 17 | 18 | // initializes the peripherals 19 | dk::init().unwrap(); 20 | 21 | defmt::println!("Hello, world!"); // 👋🏾 22 | 23 | dk::exit(); 24 | } 25 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/src/bin/rtic-hello.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 5 | use usb_app as _; 6 | 7 | #[rtic::app(device = dk, peripherals = false)] 8 | mod app { 9 | #[local] 10 | struct MyLocalResources {} 11 | 12 | #[shared] 13 | struct MySharedResources {} 14 | 15 | #[init] 16 | fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources) { 17 | dk::init().unwrap(); 18 | 19 | defmt::println!("Hello"); 20 | (MySharedResources {}, MyLocalResources {}) 21 | } 22 | 23 | #[idle] 24 | fn idle(_cx: idle::Context) -> ! { 25 | defmt::println!("world!"); 26 | dk::fail(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/src/bin/stack_overflow.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 7 | use usb_app as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | // board initialization 12 | dk::init().unwrap(); 13 | 14 | fib(100); 15 | 16 | loop { 17 | asm::bkpt(); 18 | } 19 | } 20 | 21 | #[inline(never)] 22 | fn fib(n: u32) -> u32 { 23 | // allocate and initialize one kilobyte of stack memory to provoke stack overflow 24 | let use_stack = [0xAA; 1024]; 25 | defmt::println!("allocating [{}; 1024]; round #{}", use_stack[1023], n); 26 | 27 | if n < 2 { 28 | 1 29 | } else { 30 | fib(n - 1) + fib(n - 2) // recursion 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/src/bin/task-state.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 5 | use usb_app as _; 6 | 7 | #[rtic::app(device = dk, peripherals = false)] 8 | mod app { 9 | use cortex_m::asm; 10 | use dk::peripheral::POWER; 11 | 12 | #[local] 13 | struct MyLocalResources { 14 | power: POWER, 15 | } 16 | 17 | #[shared] 18 | struct MySharedResources {} 19 | 20 | #[init] 21 | fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources) { 22 | let board = dk::init().unwrap(); 23 | 24 | let power = board.power; 25 | 26 | power.intenset.write(|w| w.usbdetected().set_bit()); 27 | 28 | defmt::println!("USBDETECTED interrupt enabled"); 29 | 30 | (MySharedResources {}, MyLocalResources { power }) 31 | } 32 | 33 | #[idle] 34 | fn idle(_cx: idle::Context) -> ! { 35 | loop { 36 | defmt::println!("idle: going to sleep"); 37 | asm::wfi(); 38 | defmt::println!("idle: woke up"); 39 | } 40 | } 41 | 42 | #[task(binds = POWER_CLOCK, local = [power])] 43 | // ^^^^^^^ resource access list 44 | fn on_power_event(cx: on_power_event::Context) { 45 | defmt::println!("POWER event occurred"); 46 | 47 | // clear the interrupt flag; otherwise this task will run again after it returns 48 | cx.local.power.events_usbdetected.reset(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/src/bin/usb-1.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use dk::{ 5 | peripheral::USBD, 6 | usbd::{self, Event}, 7 | }; 8 | 9 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 10 | use usb_app as _; 11 | 12 | #[rtic::app(device = dk, peripherals = false)] 13 | mod app { 14 | use super::*; 15 | 16 | #[local] 17 | struct MyLocalResources { 18 | usbd: USBD, 19 | } 20 | 21 | #[shared] 22 | struct MySharedResources {} 23 | 24 | #[init] 25 | fn init(_cx: init::Context) -> (MySharedResources, MyLocalResources) { 26 | let board = dk::init().unwrap(); 27 | 28 | // initialize the USBD peripheral 29 | // NOTE this will block if the USB cable is not connected to port J3 30 | usbd::init(board.power, &board.usbd); 31 | 32 | defmt::println!("USBD initialized"); 33 | 34 | (MySharedResources {}, MyLocalResources { usbd: board.usbd }) 35 | } 36 | 37 | #[task(binds = USBD, local = [usbd])] 38 | fn handle_usb_interrupt(cx: handle_usb_interrupt::Context) { 39 | while let Some(event) = usbd::next_event(cx.local.usbd) { 40 | on_event(cx.local.usbd, event) 41 | } 42 | } 43 | } 44 | 45 | /// Handle a USB event (in interrupt context) 46 | fn on_event(_usbd: &USBD, event: Event) { 47 | defmt::debug!("USB: {} @ {=u64:tus}", event, dk::uptime_us()); 48 | 49 | match event { 50 | Event::UsbReset => todo!(), 51 | Event::UsbEp0Setup => todo!(), 52 | Event::UsbEp0DataDone => todo!(), 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/src/bin/vec.rs: -------------------------------------------------------------------------------- 1 | #![deny(unused_must_use)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use heapless::Vec; 7 | // this imports `src/lib.rs`to retrieve our global logger + panicking-behavior 8 | use usb_app as _; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | dk::init().unwrap(); 13 | 14 | // a stack-allocated `Vec` with capacity for 6 bytes 15 | let mut buffer = Vec::::new(); 16 | // content type ^^ ^ capacity 17 | 18 | // `heapless::Vec` exposes the same API surface as `std::Vec` but some of its methods return a 19 | // `Result` to indicate whether the operation failed due to the `heapless::Vec` being full 20 | defmt::println!("start: {:?}", &buffer[..]); 21 | 22 | buffer.push(0).expect("buffer full"); 23 | defmt::println!("after `push`: {:?}", &buffer[..]); 24 | 25 | buffer.extend_from_slice(&[1, 2, 3]).expect("buffer full"); 26 | defmt::println!("after `extend`: {:?}", &buffer[..]); 27 | 28 | // TODO try this operation 29 | // buffer.extend_from_slice(&[4, 5, 6, 7]).expect("buffer full"); 30 | 31 | // TODO try changing the capacity of the `heapless::Vec` 32 | 33 | dk::exit() 34 | } 35 | -------------------------------------------------------------------------------- /nrf52-code/usb-app/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use cortex_m_rt::exception; 4 | 5 | /// Our custom panic handler. 6 | #[panic_handler] 7 | fn panic(info: &core::panic::PanicInfo) -> ! { 8 | defmt::error!("{}", defmt::Display2Format(info)); 9 | dk::fail(); 10 | } 11 | 12 | /// The default HardFault handler just spins, so replace it. 13 | #[exception] 14 | unsafe fn HardFault(_ef: &cortex_m_rt::ExceptionFrame) -> ! { 15 | defmt::error!("HardFault!"); 16 | dk::fail(); 17 | } 18 | 19 | // this prevents the panic message being printed *twice* when `defmt::panic!` is invoked 20 | #[defmt::panic_handler] 21 | fn defmt_panic() -> ! { 22 | dk::fail(); 23 | } 24 | 25 | defmt::timestamp!("{=u64:tus}", dk::uptime_us()); 26 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/complete/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/complete/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "usb" 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/complete/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb" 6 | version = "0.0.0" 7 | description = "Library for parsing USB requests" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-descriptor-config/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-descriptor-config/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "usb" 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-descriptor-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb" 6 | version = "0.0.0" 7 | description = "Library for parsing USB requests" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-descriptor-config/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo::rustc-check-cfg=cfg(TODO)"); 3 | } 4 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-device/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-device/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "usb" 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-device/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb" 6 | version = "0.0.0" 7 | description = "Library for parsing USB requests" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/get-device/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo::rustc-check-cfg=cfg(TODO)"); 3 | } 4 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/set-config/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/set-config/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "usb" 7 | version = "0.0.0" 8 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/set-config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb" 6 | version = "0.0.0" 7 | description = "Library for parsing USB requests" 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib-solutions/set-config/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo::rustc-check-cfg=cfg(TODO)"); 3 | } 4 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | license = "MIT OR Apache-2.0" 5 | name = "usb" 6 | version = "0.0.0" 7 | description = "Library for parsing USB requests" 8 | 9 | [dependencies] 10 | defmt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219", version = "1.0.1" } 11 | defmt-rtt = { git = "https://github.com/knurling-rs/defmt/", rev = "177c219" } 12 | -------------------------------------------------------------------------------- /nrf52-code/usb-lib/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo::rustc-check-cfg=cfg(TODO)"); 3 | } 4 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.armv8r-none-eabihf] 2 | runner = "qemu-system-arm -machine mps3-an536 -cpu cortex-r52 -semihosting -nographic -kernel" 3 | 4 | [build] 5 | target = ["armv8r-none-eabihf"] 6 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // override the default setting (`cargo check --all-targets`) which produces the following error 3 | // "can't find crate for `test`" when the default compilation target is a no_std target 4 | "rust-analyzer.check.allTargets": false, 5 | // When using Ferrocene 25.02 you have to disable proc-macro support because 6 | // there's no proc-macro server 7 | "rust-analyzer.procMacro.enable": false, 8 | } 9 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uart-exercise" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Ferrous Systems"] 6 | license = "MIT OR Apache-2.0" 7 | description = "A simple ARMv8-R UART driver exercise that runs in QEMU and compiles with Ferrocene" 8 | 9 | [dependencies] 10 | bitbybit = "1.3.3" 11 | cortex-ar = { version = "0.1.0", features = ["critical-section-single-core"] } 12 | cortex-r-rt = "0.2.0" 13 | critical-section = "1.2.0" 14 | derive-mmio = "0.3.0" 15 | semihosting = { version = "0.1.20", features = ["stdio"] } 16 | 17 | [profile.release] 18 | opt-level = "s" 19 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/README.md: -------------------------------------------------------------------------------- 1 | # Rust UART Driver exercise 2 | 3 | This folder contains a small Rust no-std application, which is designed to run 4 | inside a QEMU emulation of an Armv8-R Cortex-R52 system. We build the code using 5 | the `armv8r-none-eabihf` target. 6 | 7 | The application talks to the outside world through a UART driver. We have 8 | provided two - a working one, and a template one that doesn't work which you 9 | need to fix. 10 | 11 | ## Prerequisites 12 | 13 | This demo is designed to run with Ferrocene, which ships the 14 | `armv8r-none-eabihf` target. 15 | 16 | ### Ferrrocene 17 | 18 | Run: 19 | 20 | ```bash 21 | criticalup install 22 | cargo run 23 | ``` 24 | 25 | To edit in VSCode using Ferrocene, run: 26 | 27 | ```bash 28 | RUSTC=$(criticalup which rustc) code . 29 | ``` 30 | 31 | Or on Windows: 32 | 33 | ```console 34 | C:\Project> criticalup which rustc 35 | C:\Users\steve\AppData\Roaming\criticalup\toolchains\xyz\bin\rustc.exe 36 | C:\Project> set RUSTC=C:\Users\steve\AppData\Roaming\criticalup\toolchains\xyz\bin\rustc.exe 37 | C:\Project> code . 38 | ``` 39 | 40 | ### Rust 41 | 42 | If you want to run it with the upstream Rust compiler, you will need to use 43 | `nightly`, and tell `cargo` to build the standard library from source: 44 | 45 | ```bash 46 | cargo +nightly run -Zbuild-std=core 47 | ``` 48 | 49 | ## License 50 | 51 | Licensed under either of 52 | 53 | * Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or 54 | ) 55 | * MIT license ([LICENSE-MIT](../LICENSE-MIT) or 56 | ) at your option. 57 | 58 | ## Contribution 59 | 60 | Unless you explicitly state otherwise, any contribution intentionally submitted 61 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 62 | dual licensed as above, without any additional terms or conditions. 63 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/build.rs: -------------------------------------------------------------------------------- 1 | //! # Build script for the QEMU Ferrocene demo project 2 | //! 3 | //! This script only executes when using `cargo` to build the project. 4 | 5 | use std::io::Write; 6 | 7 | fn main() { 8 | // Put `memory.x` file in our output directory and ensure it's on the 9 | // linker search path. 10 | let out = &std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); 11 | std::fs::File::create(out.join("memory.x")) 12 | .unwrap() 13 | .write_all(include_bytes!("memory.x")) 14 | .unwrap(); 15 | println!("cargo:rustc-link-search={}", out.display()); 16 | // We need to link with this file, which comes from cortex-r-rt. 17 | println!("cargo:rustc-link-arg=-Tlink.x"); 18 | } 19 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/criticalup.toml: -------------------------------------------------------------------------------- 1 | manifest-version = 1 2 | 3 | [products.ferrocene] 4 | release = "stable-25.02.0" 5 | packages = [ 6 | "rustc-${rustc-host}", 7 | "rust-std-${rustc-host}", 8 | "cargo-${rustc-host}", 9 | "rustfmt-${rustc-host}", 10 | "rust-src", 11 | "rust-std-armv8r-none-eabihf", 12 | ] 13 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY { 2 | QSPI : ORIGIN = 0x08000000, LENGTH = 8M 3 | DDR : ORIGIN = 0x20000000, LENGTH = 128M 4 | } 5 | 6 | REGION_ALIAS("VECTORS", QSPI); 7 | REGION_ALIAS("CODE", QSPI); 8 | REGION_ALIAS("DATA", DDR); 9 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Support for the uart-driver example for QEMU's Armv8-R Virtual Machine 2 | //! 3 | //! Written by Jonathan Pallant at Ferrous Systems 4 | //! 5 | //! Copyright (c) Ferrous Systems, 2025 6 | 7 | #![no_std] 8 | 9 | pub mod uart_driver; 10 | pub mod uart_driver_solution; 11 | 12 | use cortex_r_rt as _; 13 | 14 | /// The clock speed of the peripheral subsystem on an SSE-300 SoC an on MPS3 board. 15 | /// 16 | /// Probably right for an MPS3-536 17 | pub const PERIPHERAL_CLOCK: u32 = 25_000_000; 18 | 19 | /// Called when the application raises an unrecoverable `panic!`. 20 | /// 21 | /// Prints the panic to the console and then exits QEMU using a semihosting 22 | /// breakpoint. 23 | #[panic_handler] 24 | fn panic(info: &core::panic::PanicInfo) -> ! { 25 | semihosting::println!("PANIC: {:?}", info); 26 | semihosting::process::exit(1); 27 | } 28 | -------------------------------------------------------------------------------- /qemu-code/uart-driver/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A uart-driver example program for QEMU's Armv8-R Virtual Machine 2 | //! 3 | //! Written by Jonathan Pallant at Ferrous Systems 4 | //! 5 | //! Copyright (c) Ferrous Systems, 2025 6 | 7 | #![no_std] 8 | #![no_main] 9 | 10 | use core::fmt::Write; 11 | 12 | use uart_exercise::PERIPHERAL_CLOCK; 13 | 14 | // 👇 change over which driver is imported, so you can test your solution! 15 | use uart_exercise::uart_driver::Uart; 16 | // use uart_exercise::uart_driver_solution::Uart; 17 | 18 | /// The entry-point to the Rust application. 19 | /// 20 | /// It is called by the start-up assembly code in `cortex-r-rt` and thus 21 | /// exported as a C-compatible symbol. 22 | #[no_mangle] 23 | pub extern "C" fn kmain() { 24 | if let Err(e) = main() { 25 | panic!("main returned {:?}", e); 26 | } 27 | } 28 | 29 | /// The main function of our Rust application. 30 | /// 31 | /// Called by [`kmain`]. 32 | fn main() -> Result<(), core::fmt::Error> { 33 | let mut uart0 = unsafe { Uart::new_uart0() }; 34 | uart0.enable(115200, PERIPHERAL_CLOCK); 35 | writeln!(uart0, "Hello, this is Rust!")?; 36 | // Print a multiplication square, using floating point 37 | for x in 1..=10 { 38 | for y in 1..=10 { 39 | let z = f64::from(x) * f64::from(y); 40 | write!(uart0, "{z:>8.2} ")?; 41 | } 42 | writeln!(uart0)?; 43 | } 44 | // Now crash the program 45 | panic!("I am a panic"); 46 | } 47 | 48 | // End of file 49 | -------------------------------------------------------------------------------- /tools/tcp-client/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "tcp-client" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /tools/tcp-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "tcp-client" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tools/tcp-client/README.md: -------------------------------------------------------------------------------- 1 | # TCP Client 2 | 3 | Usage: 4 | 5 | $ cargo run -- "PUBLISH This is my message" 6 | $ cargo run -- "RETRIEVE" 7 | -------------------------------------------------------------------------------- /tools/tcp-client/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::prelude::*; 2 | use std::net::{Shutdown, TcpStream}; 3 | 4 | fn main() -> std::io::Result<()> { 5 | let arg = std::env::args().nth(1); 6 | 7 | let message = match arg { 8 | Some(msg) => msg, 9 | None => String::from("Hello!"), 10 | }; 11 | // or: 12 | // arg.unwrap_or_default(String::from("Hello!")); 13 | 14 | let mut stream = TcpStream::connect("127.0.0.1:7878")?; 15 | 16 | writeln!(stream, "{}", message)?; 17 | 18 | stream.shutdown(Shutdown::Write)?; 19 | 20 | let mut buffer = String::new(); 21 | 22 | stream.read_to_string(&mut buffer)?; 23 | 24 | println!("{}", buffer); 25 | 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /xtask/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path ./Cargo.toml --" -------------------------------------------------------------------------------- /xtask/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ferrous Systems"] 3 | edition = "2021" 4 | name = "xtask" 5 | version = "0.0.0" 6 | 7 | [dependencies] 8 | color-eyre = "0.6" 9 | ctrlc = "3.4" 10 | hidapi = { git = "https://github.com/ruabmbua/hidapi-rs/", rev = "1a1d1a7", default-features = false, features = ["linux-native-basic-udev"] } 11 | consts = { path = "../nrf52-code/consts" } 12 | serialport = { version = "4.7", default-features = false } 13 | nusb = "0.1.14" 14 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod tasks; 4 | 5 | use std::env; 6 | 7 | fn main() -> color_eyre::Result<()> { 8 | color_eyre::install()?; 9 | 10 | // first arg is the name of the executable; skip it 11 | let args = env::args().skip(1).collect::>(); 12 | let args = args.iter().map(|arg| &arg[..]).collect::>(); 13 | 14 | match &args[..] { 15 | ["change-channel", channel] => tasks::change_channel(channel), 16 | ["serial-term"] => tasks::serial_term(), 17 | ["usb-descriptors"] => tasks::usb_descriptors(), 18 | ["usb-list"] => tasks::usb_list(), 19 | _ => { 20 | eprintln!( 21 | "cargo xtask 22 | Workshop-specific tools 23 | 24 | USAGE: 25 | cargo xtask [COMMAND] 26 | 27 | COMMANDS: 28 | change-channel [NUMBER] change the nRF Dongle to a different radio channel (NUMBER is 11..=26) 29 | serial-term displays the log output of the Dongle 30 | usb-descriptors print the USB descriptors for VID {vid:04x} PID {pid:04x} 31 | usb-list list all connected USB devices; highlights workshop devices 32 | 33 | ", 34 | vid = consts::USB_VID_DEMO, 35 | pid = consts::USB_PID_RTIC_DEMO, 36 | ); 37 | 38 | Ok(()) 39 | } 40 | } 41 | } 42 | --------------------------------------------------------------------------------