diff --git a/.github/bors.toml b/.github/bors.toml index ca42be0a5..0d1c64c5d 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -1,4 +1,22 @@ block_labels = ["needs-decision"] delete_merged_branches = true required_approvals = 1 -status = ["continuous-integration/travis-ci/push"] +status = [ + "build-book", + "build-f3discovery-doc", + "build-f3discovery-chapter (05-led-roulette)", + "build-f3discovery-chapter (06-hello-world)", + "build-f3discovery-chapter (07-registers)", + "build-f3discovery-chapter (08-leds-again)", + "build-f3discovery-chapter (09-clocks-and-timers)", + "build-f3discovery-chapter (11-usart)", + "build-f3discovery-chapter (14-i2c)", + "build-f3discovery-chapter (15-led-compass)", + "build-f3discovery-chapter (16-punch-o-meter)", + "build-microbit-doc", + "build-microbit-chapter (05-led-roulette)", + "build-microbit-chapter (07-uart)", + "build-microbit-chapter (08-i2c)", + "build-microbit-chapter (09-led-compass)", + "build-microbit-chapter (10-punch-o-meter)", +] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..0f87548f8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,164 @@ +name: CI + +on: + push: + branches: [ staging, trying, master ] + pull_request: + +jobs: + # Check build succeeds for each f3discovery chapter containing example code. + build-f3discovery-chapter: + runs-on: ubuntu-20.04 + strategy: + matrix: + chapter: + - 05-led-roulette + - 06-hello-world + - 07-registers + - 08-leds-again + - 09-clocks-and-timers + - 11-usart + - 14-i2c + - 15-led-compass + - 16-punch-o-meter + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabihf + - name: Build chapter + working-directory: f3discovery/src/${{ matrix.chapter }} + run: cargo build --target thumbv7em-none-eabihf + - name: Build chapter examples + working-directory: f3discovery/src/${{ matrix.chapter }} + run: cargo build --target thumbv7em-none-eabihf --examples + + # Check build succeeds for f3discovery docs. + build-f3discovery-doc: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabihf + - name: Build docs + run: cargo doc --target thumbv7em-none-eabihf + working-directory: f3discovery + + # Check a build succeeds for each microbit chapter that contains example code. + build-microbit-chapter: + runs-on: ubuntu-20.04 + strategy: + matrix: + chapter: + - 05-led-roulette + - 07-uart + - 08-i2c + - 09-led-compass + - 10-punch-o-meter + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv6m-none-eabi + - run: rustup target add thumbv7em-none-eabihf + - name: Build chapter micro:bit v1 + working-directory: microbit/src/${{ matrix.chapter }} + run: cargo build --features v1 --target thumbv6m-none-eabi + - name: Build chapter micro:bit v2 + working-directory: microbit/src/${{ matrix.chapter }} + run: cargo build --features v2 --target thumbv7em-none-eabihf + + # Check build succeeds for microbit docs. + build-microbit-doc: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv6m-none-eabi + - run: rustup target add thumbv7em-none-eabihf + - name: Build docs for micro:bit v1 + run: cargo doc --features v1 --target thumbv6m-none-eabi + working-directory: microbit + - name: Build docs for micro:bit v2 + run: cargo doc --features v2 --target thumbv7em-none-eabihf + working-directory: microbit + + # Build the book HTML itself and optionally publish it. + build-book: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabihf + - run: rustup target add thumbv6m-none-eabi + + - name: Install Python dependencies + run: | + pip3 install --user python-dateutil linkchecker + - name: Put pip binary directory into path + run: echo "~/.local/bin" >> $GITHUB_PATH + + - name: Cache Cargo installed binaries + uses: actions/cache@v1 + id: cache-cargo + with: + path: ~/cargo-bin + key: cache-cargo + - name: Install mdbook + if: steps.cache-cargo.outputs.cache-hit != 'true' + uses: actions-rs/install@v0.1 + with: + crate: mdbook + version: latest + - name: Copy mdbook to cache directory + if: steps.cache-cargo.outputs.cache-hit != 'true' + run: | + mkdir ~/cargo-bin + cp ~/.cargo/bin/mdbook ~/cargo-bin + - name: Put new cargo binary directory into path + run: echo "~/cargo-bin" >> $GITHUB_PATH + + - name: Build f3discovery book + working-directory: f3discovery + run: mkdir target && mdbook build + - name: Check microbit links + working-directory: f3discovery + run: linkchecker --ignore-url "print.html" book + + - name: Build microbit book + working-directory: microbit + run: mkdir target && mdbook build + - name: Check microbit links + working-directory: microbit + run: linkchecker --ignore-url "print.html" book + + - name: Build front page + run: mdbook build + - name: Check links + run: linkchecker book + + - name: Collect books + run: | + mv f3discovery/book book/f3discovery + mv microbit/book book/microbit + + - name: Deploy book + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: book + force_orphan: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3fc021a01..000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: rust - -install: - - bash ci/install.sh - - export PATH="$PATH:$PWD/gcc/bin" - -script: - - bash ci/script.sh - -after_success: - - bash ci/after-success.sh - -cache: cargo - -before_cache: - # Travis can't cache files that are not readable by "others" - - chmod -R a+r $HOME/.cargo - -branches: - only: - - master - - staging - - trying - -notifications: - email: - on_success: never diff --git a/README.md b/README.md index b61601222..f7bed39f8 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,31 @@ # `Discovery` > Discover the world of microcontrollers through Rust +Discover the world of microcontrollers through [Rust](https://www.rust-lang.org/)! + +There are currently two versions of this book. Both of them provide an +introduction to microcontrollers and how to use Rust with them. +The first is older and uses an F3 Discovery circuit board, while +the second is newer and uses a micro:bit circuit board instead. + +- Read the newer book, using a micro:bit: + https://docs.rust-embedded.org/discovery/microbit +- Read the older book, using an F3 discovery board: + https://docs.rust-embedded.org/discovery/f3discovery +- Start working on the examples from this repository +- You've got questions? + - Have a look at our [discussions section on + GitHub](https://github.com/rust-embedded/discovery/discussions) + - Maybe it has already been answered + - If not, start a new discussion +- You've found an issue? + - Have a look at our [issues on + GitHub](https://github.com/rust-embedded/discovery/issues) + - Maybe there is already a workaround + - If not, please open a new one - or even better - a [pull + request](https://github.com/rust-embedded/discovery/pulls) for solving + it +- Have fun and enjoy! This project is developed and maintained by the [Resources team][team]. diff --git a/ci/after-success.sh b/ci/after-success.sh deleted file mode 100644 index cee45e0ae..000000000 --- a/ci/after-success.sh +++ /dev/null @@ -1,21 +0,0 @@ -set -euxo pipefail - -main() { - mdbook build - - bash ga.sh - - mkdir ghp-import - - curl -Ls https://github.com/davisp/ghp-import/archive/master.tar.gz | - tar --strip-components 1 -C ghp-import -xz - - ./ghp-import/ghp_import.py book - - set +x - git push -fq https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git gh-pages && echo OK -} - -if [ $TRAVIS_BRANCH = master ]; then - main -fi diff --git a/ci/install.sh b/ci/install.sh deleted file mode 100644 index 602f22491..000000000 --- a/ci/install.sh +++ /dev/null @@ -1,23 +0,0 @@ -set -euxo pipefail - -main() { - # install latest mdbook v0.2.x release - local tag=$(git ls-remote --tags --refs --exit-code https://github.com/rust-lang-nursery/mdbook \ - | cut -d/ -f3 \ - | grep -E '^v0.2.[0-9]+$' \ - | sort --version-sort \ - | tail -n1) - local tag="v0.2.1" - curl -LSfs https://japaric.github.io/trust/install.sh | \ - sh -s -- \ - --force \ - --git rust-lang-nursery/mdBook \ - --tag $tag \ - --target x86_64-unknown-linux-musl - - rustup target add thumbv7em-none-eabihf - - pip install linkchecker --user -} - -main diff --git a/ci/script.sh b/ci/script.sh deleted file mode 100644 index acfe2d2d0..000000000 --- a/ci/script.sh +++ /dev/null @@ -1,69 +0,0 @@ -set -euxo pipefail - -main() { - # test that building the book works - mdbook build - - # mdbook doesn't handle relative links correctly in print.html so skip it. - linkchecker --ignore-url "print.html" book - - # now check this as a directory of the bookshelf - rm -rf shelf - mkdir shelf - mv book shelf - # Skipping bad relative link errors in print.html again here. - linkchecker --ignore-url "print.html" shelf - - mv shelf/book . - rmdir shelf - - # first (fast) pass: check that examples compile - for chapter in $(echo src/*); do - if [ ! -f $chapter/Cargo.toml ]; then - continue - fi - - pushd $chapter - case $(basename $chapter) in - 05-led-roulette | 06-hello-world) - RUSTFLAGS="-D rust_2018_compatibility -D rust_2018_idioms" cargo check --target thumbv7em-none-eabihf - ;; - WIP-async-io-the-future) - popd - continue - ;; - *) - RUSTFLAGS="-D rust_2018_compatibility -D rust_2018_idioms" cargo check - ;; - esac - popd - done - - # second (slow) pass: check that examples link - for chapter in $(echo src/*); do - if [ ! -f $chapter/Cargo.toml ]; then - continue - fi - - pushd $chapter - case $(basename $chapter) in - 05-led-roulette | 06-hello-world) - cargo build --target thumbv7em-none-eabihf - cargo build --target thumbv7em-none-eabihf --release - ;; - WIP-async-io-the-future) - popd - continue - ;; - *) - cargo build - cargo build --release - ;; - esac - popd - done -} - -if [ $TRAVIS_BRANCH != master ]; then - main -fi diff --git a/Cargo.toml b/f3discovery/Cargo.toml similarity index 100% rename from Cargo.toml rename to f3discovery/Cargo.toml diff --git a/f3discovery/book.toml b/f3discovery/book.toml new file mode 100644 index 000000000..fbda71fad --- /dev/null +++ b/f3discovery/book.toml @@ -0,0 +1,9 @@ +[book] +title = "Discovery" +description = "Discover the world of microcontrollers through Rust" +author = "Rust Embedded Resources Team" +language = "en" + +[output.html] +additional-css = ["custom.css"] +git-repository-url = "https://github.com/rust-embedded/discovery/" diff --git a/f3discovery/custom.css b/f3discovery/custom.css new file mode 100644 index 000000000..179afa563 --- /dev/null +++ b/f3discovery/custom.css @@ -0,0 +1,6 @@ +/* Add this style to the image if it's unreadable +when the dark theme is applied */ +img.white_bg { + background-color: white; + padding: 1em; +} diff --git a/f3discovery/src/.cargo/config.toml b/f3discovery/src/.cargo/config.toml new file mode 100644 index 000000000..ddff17f3b --- /dev/null +++ b/f3discovery/src/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.thumbv7em-none-eabihf] +runner = "arm-none-eabi-gdb -q" +# runner = "gdb-multiarch -q" +# runner = "gdb -q" +rustflags = [ + "-C", "link-arg=-Tlink.x", +] + +[build] +target = "thumbv7em-none-eabihf" + diff --git a/src/01-background/README.md b/f3discovery/src/01-background/README.md similarity index 100% rename from src/01-background/README.md rename to f3discovery/src/01-background/README.md diff --git a/src/02-requirements/README.md b/f3discovery/src/02-requirements/README.md similarity index 99% rename from src/02-requirements/README.md rename to f3discovery/src/02-requirements/README.md index d60dd4dad..f3a5482ab 100644 --- a/src/02-requirements/README.md +++ b/f3discovery/src/02-requirements/README.md @@ -190,4 +190,4 @@ better off starting with the [quickstart] project template. 異なる開発ボードを持っていたり、自分を初心者とは考えていない場合は、[quickstart]プロジェクトテンプレートから始めた方が良いでしょう。 -[quickstart]: https://docs.rs/cortex-m-quickstart +[quickstart]: https://rust-embedded.github.io/cortex-m-quickstart/cortex_m_quickstart/ diff --git a/src/03-setup/README.md b/f3discovery/src/03-setup/README.md similarity index 82% rename from src/03-setup/README.md rename to f3discovery/src/03-setup/README.md index 9afd43ee4..c3a7b98e4 100644 --- a/src/03-setup/README.md +++ b/f3discovery/src/03-setup/README.md @@ -35,10 +35,10 @@ several MBs in size. - [STM32F3DISCOVERY User Manual][um] - [STM32F303VC Datasheet][ds] - [STM32F303VC Reference Manual][rm] -- [LSM303DLHC] -- [L3GD20] +- [LSM303DLHC] \* +- [L3GD20] \* -[L3GD20]: http://www.st.com/resource/en/datasheet/l3gd20.pdf +[L3GD20]: https://www.st.com/content/ccc/resource/technical/document/application_note/2c/d9/a7/f8/43/48/48/64/DM00119036.pdf/files/DM00119036.pdf/jcr:content/translations/en.DM00119036.pdf [LSM303DLHC]: http://www.st.com/resource/en/datasheet/lsm303dlhc.pdf [ds]: http://www.st.com/resource/en/datasheet/stm32f303vc.pdf [rm]: http://www.st.com/resource/en/reference_manual/dm00043574.pdf @@ -60,7 +60,7 @@ should work but we have listed the version we have tested. - Rust 1.31以上のツールチェイン -- [`itmdump`] v0.2.1 +- [`itmdump`] >=0.3.1 (`cargo install itm`). Tested versions: 0.3.1. @@ -144,26 +144,70 @@ rustc 1.31.0 (abe02cefd 2018-12-04) ### `itmdump` + ``` console -$ cargo install itm --vers 0.3.1 +cargo install itm +``` +Verify the version is >=0.3.1 +``` $ itmdump -V itmdump 0.3.1 ``` ### `cargo-binutils` +Install `llvm-tools-preview` + ``` console -$ rustup component add llvm-tools-preview +rustup component add llvm-tools-preview +``` + +Install `cargo-binutils` +``` +cargo install cargo-binutils +``` + +#### Verify tools are installed -$ cargo install cargo-binutils --vers 0.1.4 +Run the following commands at your terminal +``` console +cargo new test-size +``` +``` +cd test-size +``` +``` +cargo run +``` +``` +cargo size -- --version +``` -$ cargo size -- -version +The results should be something like: +``` +~ +$ cargo new test-size + Created binary (application) `test-size` package + +~ +$ cd test-size + +~/test-size (main) +$ cargo run + Compiling test-size v0.1.0 (~/test-size) + Finished dev [unoptimized + debuginfo] target(s) in 0.26s + Running `target/debug/test-size` +Hello, world! + +~/test-size (main) +$ cargo size -- --version + Finished dev [unoptimized + debuginfo] target(s) in 0.00s LLVM (http://llvm.org/): - LLVM version 8.0.0svn + LLVM version 11.0.0-rust-1.50.0-stable Optimized build. Default target: x86_64-unknown-linux-gnu - Host CPU: skylake + Host CPU: znver2 ``` diff --git a/src/03-setup/linux.md b/f3discovery/src/03-setup/linux.md similarity index 82% rename from src/03-setup/linux.md rename to f3discovery/src/03-setup/linux.md index c782f40fd..260ca1a35 100644 --- a/src/03-setup/linux.md +++ b/f3discovery/src/03-setup/linux.md @@ -27,7 +27,7 @@ ``` console -$ sudo apt-get install \ +sudo apt-get install \ gdb-multiarch \ minicom \ openocd @@ -49,7 +49,7 @@ $ sudo apt-get install \ ``` console -$ sudo apt-get install \ +sudo apt-get install \ gdb-arm-none-eabi \ minicom \ openocd @@ -67,13 +67,13 @@ $ sudo apt-get install \ **注記** `arm-none-eabi-gdb`は、ARM Cortex-Mプログラムをデバッグするために使用するGDBのコマンドです。 ``` console -$ sudo dnf install \ - arm-none-eabi-gdb \ +sudo dnf install \ minicom \ - openocd + openocd \ + gdb ``` -- Arch Linux +### Arch Linux ## オプションのパッケージ -- Ubuntu / Debian +### Ubuntu / Debian ``` console -$ sudo apt-get install \ +sudo apt-get install \ bluez \ rfkill ``` -- Fedora +### Fedora ``` console -$ sudo dnf install \ +sudo dnf install \ bluez \ rfkill ``` -- Arch Linux +### Arch Linux ``` console -$ sudo pacman -S \ +sudo pacman -S \ bluez \ bluez-utils \ rfkill @@ -179,25 +182,36 @@ These rules let you use USB devices like the F3 and the Serial module without ro 下記の内容で、`/etc/udev/rules.d`ディレクトリに2つのファイルを作成します。 +Execute `lsusb`: ``` console -$ cat /etc/udev/rules.d/99-ftdi.rules +lsusb | grep ST-LINK +``` +It should result in something like: ``` +$ lsusb | grep ST-LINK +Bus 003 Device 003: ID 0483:374b STMicroelectronics ST-LINK/V2.1 +``` +So the `idVendor` is `0483` and `idProduct` is `374b`. +### Create `/etc/udev/rules.d/99-openocd.rules`: +``` console +sudo vi /etc/udev/rules.d/99-openocd.rules +``` +With the contents: ``` text -# FT232 - USB <-> Serial Converter -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE:="0666" +# STM32F3DISCOVERY - ST-LINK/V2.1 +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666" ``` +#### For older devices with OPTIONAL USB <-> FT232 based Serial Module +Create `/etc/udev/rules.d/99-ftdi.rules`: ``` console -$ cat /etc/udev/rules.d/99-openocd.rules +sudo vi /etc/udev/rules.d/99-openocd.rules ``` - +With the contents: ``` text -# STM32F3DISCOVERY rev A/B - ST-LINK/V2 -ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666" - -# STM32F3DISCOVERY rev C+ - ST-LINK/V2-1 -ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666" +# FT232 - USB <-> Serial Converter +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE:="0666" ``` @@ -205,7 +219,7 @@ ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666" その後、udevルールをリロードします。 ``` console -$ sudo udevadm control --reload-rules +sudo udevadm control --reload-rules ``` diff --git a/src/03-setup/macos.md b/f3discovery/src/03-setup/macos.md similarity index 100% rename from src/03-setup/macos.md rename to f3discovery/src/03-setup/macos.md diff --git a/src/03-setup/verify.md b/f3discovery/src/03-setup/verify.md similarity index 100% rename from src/03-setup/verify.md rename to f3discovery/src/03-setup/verify.md diff --git a/src/03-setup/windows.md b/f3discovery/src/03-setup/windows.md similarity index 93% rename from src/03-setup/windows.md rename to f3discovery/src/03-setup/windows.md index f05f5dc1d..a24d89f31 100644 --- a/src/03-setup/windows.md +++ b/f3discovery/src/03-setup/windows.md @@ -12,9 +12,14 @@ ARMはWindows向けに`.exe`インストーラを提供しています。[ここ インストールプロセスが終了する直前に"環境変数にパスを追加"オプションを選択します。 その後、ツールが`%PATH%`にあることを確認します。 +Verify gcc is installed: ``` console -$ arm-none-eabi-gcc -v +arm-none-eabi-gcc -v +``` +The results should be something like: +``` (..) +$ arm-none-eabi-gcc -v gcc version 5.4.1 20160919 (release) (..) ``` @@ -40,6 +45,11 @@ Verify that OpenOCD is in yout `%PATH%` with: --> OpenOCDが`%PATH%`にあることを確認します。 +Verify OpenOCD is installed and in your `%PATH%` with: +``` console +openocd -v +``` +The results should be something like: ``` console $ openocd -v Open On-Chip Debugger 0.10.0 diff --git a/src/04-meet-your-hardware/README.md b/f3discovery/src/04-meet-your-hardware/README.md similarity index 100% rename from src/04-meet-your-hardware/README.md rename to f3discovery/src/04-meet-your-hardware/README.md diff --git a/f3discovery/src/05-led-roulette/Cargo.toml b/f3discovery/src/05-led-roulette/Cargo.toml new file mode 100644 index 000000000..848f236c4 --- /dev/null +++ b/f3discovery/src/05-led-roulette/Cargo.toml @@ -0,0 +1,12 @@ +[package] +authors = [ + "Jorge Aparicio ", + "Christopher J. McClellan ", + "Wink Saville ", + "Christopher J. McClellan ", + "Wink Saville >, ActiveHigh>; 8]; + +pub fn init() -> (Delay, LedArray) { + let device_periphs = pac::Peripherals::take().unwrap(); + let mut reset_and_clock_control = device_periphs.RCC.constrain(); + + let core_periphs = cortex_m::Peripherals::take().unwrap(); + let mut flash = device_periphs.FLASH.constrain(); + let clocks = reset_and_clock_control.cfgr.freeze(&mut flash.acr); + let delay = Delay::new(core_periphs.SYST, clocks); + + // initialize user leds + let mut gpioe = device_periphs.GPIOE.split(&mut reset_and_clock_control.ahb); + let leds = Leds::new( + gpioe.pe8, + gpioe.pe9, + gpioe.pe10, + gpioe.pe11, + gpioe.pe12, + gpioe.pe13, + gpioe.pe14, + gpioe.pe15, + &mut gpioe.moder, + &mut gpioe.otyper, + ); + + (delay, leds.into_array()) +} diff --git a/src/05-led-roulette/build-it.md b/f3discovery/src/05-led-roulette/build-it.md similarity index 100% rename from src/05-led-roulette/build-it.md rename to f3discovery/src/05-led-roulette/build-it.md diff --git a/src/05-led-roulette/debug-it.md b/f3discovery/src/05-led-roulette/debug-it.md similarity index 100% rename from src/05-led-roulette/debug-it.md rename to f3discovery/src/05-led-roulette/debug-it.md diff --git a/f3discovery/src/05-led-roulette/examples/my-solution.rs b/f3discovery/src/05-led-roulette/examples/my-solution.rs new file mode 100644 index 000000000..80cc49f65 --- /dev/null +++ b/f3discovery/src/05-led-roulette/examples/my-solution.rs @@ -0,0 +1,22 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use aux5::{Delay, DelayMs, LedArray, OutputSwitch, entry}; + +#[entry] +fn main() -> ! { + let (mut delay, mut leds): (Delay, LedArray) = aux5::init(); + + let ms = 50_u8; + loop { + for curr in 0..8 { + let next = (curr + 1) % 8; + + leds[next].on().ok(); + delay.delay_ms(ms); + leds[curr].off().ok(); + delay.delay_ms(ms); + } + } +} diff --git a/f3discovery/src/05-led-roulette/examples/the-led-and-delay-abstractions.rs b/f3discovery/src/05-led-roulette/examples/the-led-and-delay-abstractions.rs new file mode 100644 index 000000000..ec07d1ffd --- /dev/null +++ b/f3discovery/src/05-led-roulette/examples/the-led-and-delay-abstractions.rs @@ -0,0 +1,20 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use aux5::{entry, Delay, DelayMs, LedArray, OutputSwitch}; + +#[entry] +fn main() -> ! { + let (mut delay, mut leds): (Delay, LedArray) = aux5::init(); + + let half_period = 500_u16; + + loop { + leds[0].on().ok(); + delay.delay_ms(half_period); + + leds[0].off().ok(); + delay.delay_ms(half_period); + } +} diff --git a/src/05-led-roulette/flash-it.md b/f3discovery/src/05-led-roulette/flash-it.md similarity index 100% rename from src/05-led-roulette/flash-it.md rename to f3discovery/src/05-led-roulette/flash-it.md diff --git a/src/05-led-roulette/my-solution.md b/f3discovery/src/05-led-roulette/my-solution.md similarity index 69% rename from src/05-led-roulette/my-solution.md rename to f3discovery/src/05-led-roulette/my-solution.md index 1def81203..1132c4bc8 100644 --- a/src/05-led-roulette/my-solution.md +++ b/f3discovery/src/05-led-roulette/my-solution.md @@ -11,29 +11,7 @@ 私の解答は、次の通りです。 ``` rust -#![deny(unsafe_code)] -#![no_main] -#![no_std] - -use aux5::{entry, prelude::*, Delay, Leds}; - -#[entry] -fn main() -> ! { - let (mut delay, mut leds): (Delay, Leds) = aux5::init(); - - let ms = 50_u8; - loop { - for curr in 0..8 { - let next = (curr + 1) % 8; - - leds[next].on(); - delay.delay_ms(ms); - leds[curr].off(); - delay.delay_ms(ms); - } - } -} - +{{#include examples/my-solution.rs}} ``` @@ -65,48 +43,53 @@ that using the `size` command on the release binary: ``` console $ # size target/thumbv7em-none-eabihf/debug/led-rouletteと等価です $ cargo size --target thumbv7em-none-eabihf --bin led-roulette -- -A + Finished dev [unoptimized + debuginfo] target(s) in 0.02s led-roulette : section size addr -.vector_table 392 0x8000000 -.text 16404 0x8000188 -.rodata 2924 0x80041a0 +.vector_table 404 0x8000000 +.text 21144 0x8000194 +.rodata 3144 0x800542c .data 0 0x20000000 .bss 4 0x20000000 -.debug_str 602185 0x0 -.debug_abbrev 24134 0x0 -.debug_info 553143 0x0 -.debug_ranges 112744 0x0 -.debug_macinfo 86 0x0 -.debug_pubnames 56467 0x0 -.debug_pubtypes 94866 0x0 +.uninit 0 0x20000004 +.debug_abbrev 19160 0x0 +.debug_info 471239 0x0 +.debug_aranges 18376 0x0 +.debug_ranges 102536 0x0 +.debug_str 508618 0x0 +.debug_pubnames 76975 0x0 +.debug_pubtypes 112797 0x0 .ARM.attributes 58 0x0 -.debug_frame 174812 0x0 -.debug_line 354866 0x0 -.debug_loc 534 0x0 -.comment 75 0x0 -Total 1993694 +.debug_frame 55848 0x0 +.debug_line 282067 0x0 +.debug_loc 845 0x0 +.comment 147 0x0 +Total 1673362 + $ cargo size --target thumbv7em-none-eabihf --bin led-roulette --release -- -A + Finished release [optimized + debuginfo] target(s) in 0.03s led-roulette : section size addr -.vector_table 392 0x8000000 -.text 1826 0x8000188 -.rodata 84 0x80008ac +.vector_table 404 0x8000000 +.text 5380 0x8000194 +.rodata 564 0x8001698 .data 0 0x20000000 .bss 4 0x20000000 -.debug_str 23334 0x0 -.debug_loc 6964 0x0 -.debug_abbrev 1337 0x0 -.debug_info 40582 0x0 -.debug_ranges 2936 0x0 -.debug_macinfo 1 0x0 -.debug_pubnames 5470 0x0 -.debug_pubtypes 10016 0x0 +.uninit 0 0x20000004 +.debug_loc 9994 0x0 +.debug_abbrev 1821 0x0 +.debug_info 74974 0x0 +.debug_aranges 600 0x0 +.debug_ranges 6848 0x0 +.debug_str 52828 0x0 +.debug_pubnames 20821 0x0 +.debug_pubtypes 18891 0x0 .ARM.attributes 58 0x0 -.debug_frame 164 0x0 -.debug_line 9081 0x0 -.comment 18 0x0 -Total 102267 +.debug_frame 1088 0x0 +.debug_line 15307 0x0 +.comment 19 0x0 +Total 209601 ``` diff --git a/src/05-led-roulette/src/main.rs b/f3discovery/src/05-led-roulette/src/main.rs similarity index 100% rename from src/05-led-roulette/src/main.rs rename to f3discovery/src/05-led-roulette/src/main.rs diff --git a/src/05-led-roulette/target b/f3discovery/src/05-led-roulette/target similarity index 100% rename from src/05-led-roulette/target rename to f3discovery/src/05-led-roulette/target diff --git a/src/05-led-roulette/the-challenge.md b/f3discovery/src/05-led-roulette/the-challenge.md similarity index 100% rename from src/05-led-roulette/the-challenge.md rename to f3discovery/src/05-led-roulette/the-challenge.md diff --git a/src/05-led-roulette/the-led-and-delay-abstractions.md b/f3discovery/src/05-led-roulette/the-led-and-delay-abstractions.md similarity index 100% rename from src/05-led-roulette/the-led-and-delay-abstractions.md rename to f3discovery/src/05-led-roulette/the-led-and-delay-abstractions.md diff --git a/f3discovery/src/06-hello-world/Cargo.toml b/f3discovery/src/06-hello-world/Cargo.toml new file mode 100644 index 000000000..1a19db077 --- /dev/null +++ b/f3discovery/src/06-hello-world/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = [ + "Jorge Aparicio ", + "Wink Saville ", + "Wink Saville ITM { let p = cortex_m::Peripherals::take().unwrap(); diff --git a/src/06-hello-world/panic.md b/f3discovery/src/06-hello-world/panic.md similarity index 100% rename from src/06-hello-world/panic.md rename to f3discovery/src/06-hello-world/panic.md diff --git a/src/06-hello-world/src/main.rs b/f3discovery/src/06-hello-world/src/main.rs similarity index 100% rename from src/06-hello-world/src/main.rs rename to f3discovery/src/06-hello-world/src/main.rs diff --git a/src/06-hello-world/target b/f3discovery/src/06-hello-world/target similarity index 100% rename from src/06-hello-world/target rename to f3discovery/src/06-hello-world/target diff --git a/src/14-i2c/.cargo/config b/f3discovery/src/07-registers/.cargo/config similarity index 71% rename from src/14-i2c/.cargo/config rename to f3discovery/src/07-registers/.cargo/config index f27a4a749..f5a1d9520 100644 --- a/src/14-i2c/.cargo/config +++ b/f3discovery/src/07-registers/.cargo/config @@ -1,8 +1,8 @@ [target.thumbv7em-none-eabihf] runner = "arm-none-eabi-gdb -q -x openocd.gdb" -rustflags = [ - "-C", "link-arg=-Tlink.x", -] +#rustflags = [ +# "-C", "link-arg=-Tlink.x", +#] [build] target = "thumbv7em-none-eabihf" diff --git a/f3discovery/src/07-registers/Cargo.toml b/f3discovery/src/07-registers/Cargo.toml new file mode 100644 index 000000000..2a9d1f086 --- /dev/null +++ b/f3discovery/src/07-registers/Cargo.toml @@ -0,0 +1,11 @@ +[package] +authors = [ + "Jorge Aparicio ", + "Wink Saville - +

diff --git a/src/08-leds-again/auxiliary/Cargo.toml b/f3discovery/src/08-leds-again/auxiliary/Cargo.toml similarity index 92% rename from src/08-leds-again/auxiliary/Cargo.toml rename to f3discovery/src/08-leds-again/auxiliary/Cargo.toml index 7be83fb17..a2c67f3b4 100644 --- a/src/08-leds-again/auxiliary/Cargo.toml +++ b/f3discovery/src/08-leds-again/auxiliary/Cargo.toml @@ -5,7 +5,7 @@ name = "aux8" version = "0.1.0" [dependencies] -cortex-m = "0.5.6" +cortex-m = "0.6.3" cortex-m-rt = "0.6.3" panic-itm = "0.4.0" diff --git a/src/08-leds-again/auxiliary/src/lib.rs b/f3discovery/src/08-leds-again/auxiliary/src/lib.rs similarity index 100% rename from src/08-leds-again/auxiliary/src/lib.rs rename to f3discovery/src/08-leds-again/auxiliary/src/lib.rs diff --git a/src/08-leds-again/configuration.md b/f3discovery/src/08-leds-again/configuration.md similarity index 100% rename from src/08-leds-again/configuration.md rename to f3discovery/src/08-leds-again/configuration.md diff --git a/src/11-usart/openocd.gdb b/f3discovery/src/08-leds-again/openocd.gdb similarity index 90% rename from src/11-usart/openocd.gdb rename to f3discovery/src/08-leds-again/openocd.gdb index cafe49b1b..df15d79d3 100644 --- a/src/11-usart/openocd.gdb +++ b/f3discovery/src/08-leds-again/openocd.gdb @@ -5,6 +5,6 @@ monitor tpiu config internal itm.txt uart off 8000000 monitor itm port 0 on load break DefaultHandler -break UserHardFault +break HardFault break main continue diff --git a/src/08-leds-again/power.md b/f3discovery/src/08-leds-again/power.md similarity index 100% rename from src/08-leds-again/power.md rename to f3discovery/src/08-leds-again/power.md diff --git a/src/08-leds-again/src/main.rs b/f3discovery/src/08-leds-again/src/main.rs similarity index 100% rename from src/08-leds-again/src/main.rs rename to f3discovery/src/08-leds-again/src/main.rs diff --git a/src/08-leds-again/target b/f3discovery/src/08-leds-again/target similarity index 100% rename from src/08-leds-again/target rename to f3discovery/src/08-leds-again/target diff --git a/src/08-leds-again/the-solution.md b/f3discovery/src/08-leds-again/the-solution.md similarity index 100% rename from src/08-leds-again/the-solution.md rename to f3discovery/src/08-leds-again/the-solution.md diff --git a/src/16-punch-o-meter/.cargo/config b/f3discovery/src/09-clocks-and-timers/.cargo/config similarity index 71% rename from src/16-punch-o-meter/.cargo/config rename to f3discovery/src/09-clocks-and-timers/.cargo/config index f27a4a749..f5a1d9520 100644 --- a/src/16-punch-o-meter/.cargo/config +++ b/f3discovery/src/09-clocks-and-timers/.cargo/config @@ -1,8 +1,8 @@ [target.thumbv7em-none-eabihf] runner = "arm-none-eabi-gdb -q -x openocd.gdb" -rustflags = [ - "-C", "link-arg=-Tlink.x", -] +#rustflags = [ +# "-C", "link-arg=-Tlink.x", +#] [build] target = "thumbv7em-none-eabihf" diff --git a/src/09-clocks-and-timers/Cargo.toml b/f3discovery/src/09-clocks-and-timers/Cargo.toml similarity index 100% rename from src/09-clocks-and-timers/Cargo.toml rename to f3discovery/src/09-clocks-and-timers/Cargo.toml diff --git a/src/09-clocks-and-timers/README.md b/f3discovery/src/09-clocks-and-timers/README.md similarity index 100% rename from src/09-clocks-and-timers/README.md rename to f3discovery/src/09-clocks-and-timers/README.md diff --git a/src/09-clocks-and-timers/auxiliary/Cargo.toml b/f3discovery/src/09-clocks-and-timers/auxiliary/Cargo.toml similarity index 58% rename from src/09-clocks-and-timers/auxiliary/Cargo.toml rename to f3discovery/src/09-clocks-and-timers/auxiliary/Cargo.toml index ead8ae4c5..f928adb4b 100644 --- a/src/09-clocks-and-timers/auxiliary/Cargo.toml +++ b/f3discovery/src/09-clocks-and-timers/auxiliary/Cargo.toml @@ -5,10 +5,11 @@ name = "aux9" version = "0.1.0" [dependencies] -cortex-m = "0.5.6" +cortex-m = "0.6.3" cortex-m-rt = "0.6.3" panic-itm = "0.4.0" +stm32f3-discovery = "0.6.0" -[dependencies.f3] -features = ["rt"] -version = "0.6.1" +[dependencies.stm32f3] +version = "0.12.1" +features = ["stm32f303", "rt"] diff --git a/f3discovery/src/09-clocks-and-timers/auxiliary/src/lib.rs b/f3discovery/src/09-clocks-and-timers/auxiliary/src/lib.rs new file mode 100644 index 000000000..3819f61f8 --- /dev/null +++ b/f3discovery/src/09-clocks-and-timers/auxiliary/src/lib.rs @@ -0,0 +1,43 @@ +//! Initialization code + +#![no_std] + +#[allow(unused_extern_crates)] // NOTE(allow) rust-lang/rust#53964 +extern crate panic_itm; // panic handler + +pub use cortex_m::asm::{bkpt, nop}; +pub use cortex_m_rt::entry; +pub use stm32f3::stm32f303::{rcc, tim6, RCC, TIM6}; +pub use stm32f3_discovery::switch_hal; + +use stm32f3_discovery::{ + leds::Leds, + stm32f3xx_hal::{prelude::*, stm32}, +}; + +pub fn init() -> ( + Leds, + &'static rcc::RegisterBlock, + &'static tim6::RegisterBlock, +) { + let p = stm32::Peripherals::take().unwrap(); + + let mut rcc = p.RCC.constrain(); + + let mut gpioe = p.GPIOE.split(&mut rcc.ahb); + + let leds = Leds::new( + gpioe.pe8, + gpioe.pe9, + gpioe.pe10, + gpioe.pe11, + gpioe.pe12, + gpioe.pe13, + gpioe.pe14, + gpioe.pe15, + &mut gpioe.moder, + &mut gpioe.otyper, + ); + + (leds, unsafe { &*RCC::ptr() }, unsafe { &*TIM6::ptr() }) +} diff --git a/src/09-clocks-and-timers/busy-waiting.md b/f3discovery/src/09-clocks-and-timers/busy-waiting.md similarity index 100% rename from src/09-clocks-and-timers/busy-waiting.md rename to f3discovery/src/09-clocks-and-timers/busy-waiting.md diff --git a/src/09-clocks-and-timers/for-loop-delays.md b/f3discovery/src/09-clocks-and-timers/for-loop-delays.md similarity index 95% rename from src/09-clocks-and-timers/for-loop-delays.md rename to f3discovery/src/09-clocks-and-timers/for-loop-delays.md index e82c2cd95..73e118e67 100644 --- a/src/09-clocks-and-timers/for-loop-delays.md +++ b/f3discovery/src/09-clocks-and-timers/for-loop-delays.md @@ -32,7 +32,7 @@ value of `ms`. - Fix the `delay` function to generate delays proportional to its input `ms`. - Tweak the `delay` function to make the LED roulette spin at a rate of approximately 5 cycles in 4 seconds (800 milliseconds period). -- The processor inside the microcontroller is clocked at 8 MHz and executes most instructions in one +- The processor inside the microcontroller is clocked at 72 MHz and executes most instructions in one "tick", a cycle of its clock. How many (`for`) loops do you *think* the `delay` function must do to generate a delay of 1 second? - How many `for` loops does `delay(1000)` actually do? diff --git a/src/09-clocks-and-timers/initialization.md b/f3discovery/src/09-clocks-and-timers/initialization.md similarity index 100% rename from src/09-clocks-and-timers/initialization.md rename to f3discovery/src/09-clocks-and-timers/initialization.md diff --git a/src/09-clocks-and-timers/nop.md b/f3discovery/src/09-clocks-and-timers/nop.md similarity index 97% rename from src/09-clocks-and-timers/nop.md rename to f3discovery/src/09-clocks-and-timers/nop.md index 769750c64..4fd08683e 100644 --- a/src/09-clocks-and-timers/nop.md +++ b/f3discovery/src/09-clocks-and-timers/nop.md @@ -43,7 +43,7 @@ And this time `delay` won't be compiled away by LLVM when you compile your progr 今回は、プログラムをリリースモードでコンパイルしても、`delay`はLLVMによって削除されません。 ``` console -$ cargo objdump --bin clocks-and-timers --release -- -d -no-show-raw-insn +$ cargo objdump --bin clocks-and-timers --release -- -d --no-show-raw-insn clocks-and-timers: file format ELF32-arm-little Disassembly of section .text: diff --git a/src/09-clocks-and-timers/one-shot-timer.md b/f3discovery/src/09-clocks-and-timers/one-shot-timer.md similarity index 100% rename from src/09-clocks-and-timers/one-shot-timer.md rename to f3discovery/src/09-clocks-and-timers/one-shot-timer.md diff --git a/src/07-registers/openocd.gdb b/f3discovery/src/09-clocks-and-timers/openocd.gdb similarity index 90% rename from src/07-registers/openocd.gdb rename to f3discovery/src/09-clocks-and-timers/openocd.gdb index cafe49b1b..df15d79d3 100644 --- a/src/07-registers/openocd.gdb +++ b/f3discovery/src/09-clocks-and-timers/openocd.gdb @@ -5,6 +5,6 @@ monitor tpiu config internal itm.txt uart off 8000000 monitor itm port 0 on load break DefaultHandler -break UserHardFault +break HardFault break main continue diff --git a/src/09-clocks-and-timers/putting-it-all-together.md b/f3discovery/src/09-clocks-and-timers/putting-it-all-together.md similarity index 87% rename from src/09-clocks-and-timers/putting-it-all-together.md rename to f3discovery/src/09-clocks-and-timers/putting-it-all-together.md index ba224c92d..3d13443b9 100644 --- a/src/09-clocks-and-timers/putting-it-all-together.md +++ b/f3discovery/src/09-clocks-and-timers/putting-it-all-together.md @@ -6,7 +6,7 @@ #![no_main] #![no_std] -use aux9::{entry, tim6}; +use aux9::{entry, switch_hal::OutputSwitch, tim6}; #[inline(never)] fn delay(tim6: &tim6::RegisterBlock, ms: u16) { @@ -26,7 +26,8 @@ fn delay(tim6: &tim6::RegisterBlock, ms: u16) { #[entry] fn main() -> ! { - let (mut leds, rcc, tim6) = aux9::init(); + let (leds, rcc, tim6) = aux9::init(); + let mut leds = leds.into_array(); // TIM6のタイマの電源を入れます。 rcc.apb1enr.modify(|_, w| w.tim6en().set_bit()); @@ -47,9 +48,9 @@ fn main() -> ! { for curr in 0..8 { let next = (curr + 1) % 8; - leds[next].on(); + leds[next].on().unwrap(); delay(tim6, ms); - leds[curr].off(); + leds[curr].off().unwrap(); delay(tim6, ms); } } diff --git a/src/09-clocks-and-timers/src/main.rs b/f3discovery/src/09-clocks-and-timers/src/main.rs similarity index 63% rename from src/09-clocks-and-timers/src/main.rs rename to f3discovery/src/09-clocks-and-timers/src/main.rs index d1c2cc932..ba7f5032f 100644 --- a/src/09-clocks-and-timers/src/main.rs +++ b/f3discovery/src/09-clocks-and-timers/src/main.rs @@ -1,7 +1,7 @@ #![no_main] #![no_std] -use aux9::{entry, tim6}; +use aux9::{entry, switch_hal::OutputSwitch, tim6}; #[inline(never)] fn delay(tim6: &tim6::RegisterBlock, ms: u16) { @@ -10,7 +10,8 @@ fn delay(tim6: &tim6::RegisterBlock, ms: u16) { #[entry] fn main() -> ! { - let (mut leds, rcc, tim6) = aux9::init(); + let (leds, rcc, tim6) = aux9::init(); + let mut leds = leds.into_array(); // TIMGを実装して下さい @@ -19,9 +20,9 @@ fn main() -> ! { for curr in 0..8 { let next = (curr + 1) % 8; - leds[next].on(); + leds[next].on().unwrap(); delay(tim6, ms); - leds[curr].off(); + leds[curr].off().unwrap(); delay(tim6, ms); } } diff --git a/src/09-clocks-and-timers/target b/f3discovery/src/09-clocks-and-timers/target similarity index 100% rename from src/09-clocks-and-timers/target rename to f3discovery/src/09-clocks-and-timers/target diff --git a/src/10-serial-communication/README.md b/f3discovery/src/10-serial-communication/README.md similarity index 95% rename from src/10-serial-communication/README.md rename to f3discovery/src/10-serial-communication/README.md index b8749c19a..c74415910 100644 --- a/src/10-serial-communication/README.md +++ b/f3discovery/src/10-serial-communication/README.md @@ -9,7 +9,7 @@ @@ -84,7 +84,7 @@ microcontroller). Today's laptops/PCs don't support the serial communication protocol. So you can't directly connect your laptop to the microcontroller. But that's where the serial module comes in. This module will sit between the two and expose a serial interface to the microcontroller and an USB interface to -your laptop. The microcontroller will see your laptop as another serial device and your laptop +your computer. The microcontroller will see your computer as another serial device and your computer will see the microcontroller as a virtual serial device. --> @@ -100,5 +100,7 @@ offers. Pick a route: それでは、シリアルモジュールと使用しているOSが提供するシリアル通信ツールについて、詳しく学びましょう。ルートを選んで下さい。 -- [*nix](nix-tooling.md) +- [\*nix](nix-tooling.md) - [Windows](windows-tooling.md) + +[ASC]: https://en.wikipedia.org/wiki/Asynchronous_serial_communication diff --git a/src/10-serial-communication/loopbacks.md b/f3discovery/src/10-serial-communication/loopbacks.md similarity index 100% rename from src/10-serial-communication/loopbacks.md rename to f3discovery/src/10-serial-communication/loopbacks.md diff --git a/src/10-serial-communication/nix-tooling.md b/f3discovery/src/10-serial-communication/nix-tooling.md similarity index 97% rename from src/10-serial-communication/nix-tooling.md rename to f3discovery/src/10-serial-communication/nix-tooling.md index c11c9eac4..cd94505e0 100644 --- a/src/10-serial-communication/nix-tooling.md +++ b/f3discovery/src/10-serial-communication/nix-tooling.md @@ -53,7 +53,7 @@ $ echo 'Hello, world!' > /dev/ttyUSB0 シリアルモジュールのTX(赤色)LEDが点滅するのが見えたはずです。非常に速い速度で、ちょうど1回だけ! -## minicom +## All revisions: minicom # 解答例 ```rust #![deny(unsafe_code)] +======= +>>>>>>> upstream/master:f3discovery/src/11-usart/examples/echo.rs #![no_main] #![no_std] #[allow(unused_imports)] use aux11::{entry, iprint, iprintln}; -use heapless::{consts, Vec}; +use heapless::Vec; #[entry] fn main() -> ! { - let (usart1, mono_timer, itm) = aux11::init(); + let (usart1, _mono_timer, _itm) = aux11::init(); +<<<<<<< HEAD:src/11-usart/my-solution.md // 32バイト容量のバッファ let mut buffer: Vec = Vec::new(); +======= + // A buffer with 32 bytes of capacity + let mut buffer: Vec = Vec::new(); +>>>>>>> upstream/master:f3discovery/src/11-usart/examples/echo.rs loop { buffer.clear(); @@ -29,7 +37,9 @@ fn main() -> ! { // バッファが満杯 for byte in b"error: buffer full\n\r" { while usart1.isr.read().txe().bit_is_clear() {} - usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte))); + usart1 + .tdr + .write(|w| w.tdr().bits(u16::from(*byte))); } break; @@ -40,7 +50,9 @@ fn main() -> ! { // 返信 for byte in buffer.iter().rev().chain(&[b'\n', b'\r']) { while usart1.isr.read().txe().bit_is_clear() {} - usart1.tdr.write(|w| w.tdr().bits(u16::from(*byte))); + usart1 + .tdr + .write(|w| w.tdr().bits(u16::from(*byte))); } break; @@ -48,4 +60,3 @@ fn main() -> ! { } } } -``` diff --git a/f3discovery/src/11-usart/examples/receive-a-single-byte.rs b/f3discovery/src/11-usart/examples/receive-a-single-byte.rs new file mode 100644 index 000000000..333959042 --- /dev/null +++ b/f3discovery/src/11-usart/examples/receive-a-single-byte.rs @@ -0,0 +1,21 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +#[allow(unused_imports)] +use aux11::{entry, iprint, iprintln}; + +#[entry] +fn main() -> ! { + let (usart1, _mono_timer, _itm) = aux11::init(); + + loop { + // Wait until there's data available + while usart1.isr.read().rxne().bit_is_clear() {} + + // Retrieve the data + let _byte = usart1.rdr.read().rdr().bits() as u8; + + aux11::bkpt(); + } +} diff --git a/f3discovery/src/11-usart/examples/reverse-string.rs b/f3discovery/src/11-usart/examples/reverse-string.rs new file mode 100644 index 000000000..181655290 --- /dev/null +++ b/f3discovery/src/11-usart/examples/reverse-string.rs @@ -0,0 +1,26 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +#[allow(unused_imports)] +use aux11::{entry, iprint, iprintln}; +use heapless::Vec; + +#[entry] +fn main() -> ! { + let (usart1, _mono_timer, _itm) = aux11::init(); + + // A buffer with 32 bytes of capacity + let mut buffer: Vec = Vec::new(); + + loop { + buffer.clear(); + + // TODO Receive a user request. Each user request ends with ENTER + // NOTE `buffer.push` returns a `Result`. Handle the error by responding + // with an error message. + + // TODO Send back the reversed string + } +} + diff --git a/f3discovery/src/11-usart/examples/the-answer.rs b/f3discovery/src/11-usart/examples/the-answer.rs new file mode 100644 index 000000000..90fd2831f --- /dev/null +++ b/f3discovery/src/11-usart/examples/the-answer.rs @@ -0,0 +1,46 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use core::fmt::{self, Write}; + +#[allow(unused_imports)] +use aux11::{entry, iprint, iprintln, usart1}; + +macro_rules! uprint { + ($serial:expr, $($arg:tt)*) => { + $serial.write_fmt(format_args!($($arg)*)).ok() + }; +} + +macro_rules! uprintln { + ($serial:expr, $fmt:expr) => { + uprint!($serial, concat!($fmt, "\n")) + }; + ($serial:expr, $fmt:expr, $($arg:tt)*) => { + uprint!($serial, concat!($fmt, "\n"), $($arg)*) + }; +} + +struct SerialPort { + usart1: &'static mut usart1::RegisterBlock, +} + +impl fmt::Write for SerialPort { + fn write_str(&mut self, s: &str) -> fmt::Result { + // TODO implement this + // hint: this will look very similar to the previous program + Ok(()) + } +} + +#[entry] +fn main() -> ! { + let (usart1, _mono_timer, _itm) = aux11::init(); + + let mut serial = SerialPort { usart1 }; + + uprintln!(serial, "The answer is {}", 40 + 2); + + loop {} +} diff --git a/f3discovery/src/11-usart/my-solution.md b/f3discovery/src/11-usart/my-solution.md new file mode 100644 index 000000000..c88913176 --- /dev/null +++ b/f3discovery/src/11-usart/my-solution.md @@ -0,0 +1,5 @@ +# My solution + +```rust +{{#include examples/echo.rs}} +``` diff --git a/src/08-leds-again/openocd.gdb b/f3discovery/src/11-usart/openocd.gdb similarity index 90% rename from src/08-leds-again/openocd.gdb rename to f3discovery/src/11-usart/openocd.gdb index cafe49b1b..df15d79d3 100644 --- a/src/08-leds-again/openocd.gdb +++ b/f3discovery/src/11-usart/openocd.gdb @@ -5,6 +5,6 @@ monitor tpiu config internal itm.txt uart off 8000000 monitor itm port 0 on load break DefaultHandler -break UserHardFault +break HardFault break main continue diff --git a/src/11-usart/receive-a-single-byte.md b/f3discovery/src/11-usart/receive-a-single-byte.md similarity index 100% rename from src/11-usart/receive-a-single-byte.md rename to f3discovery/src/11-usart/receive-a-single-byte.md diff --git a/src/11-usart/reverse-a-string.md b/f3discovery/src/11-usart/reverse-a-string.md similarity index 100% rename from src/11-usart/reverse-a-string.md rename to f3discovery/src/11-usart/reverse-a-string.md diff --git a/src/11-usart/send-a-single-byte.md b/f3discovery/src/11-usart/send-a-single-byte.md similarity index 100% rename from src/11-usart/send-a-single-byte.md rename to f3discovery/src/11-usart/send-a-single-byte.md diff --git a/src/11-usart/send-a-string.md b/f3discovery/src/11-usart/send-a-string.md similarity index 98% rename from src/11-usart/send-a-string.md rename to f3discovery/src/11-usart/send-a-string.md index 70e671f2f..695d4a57d 100644 --- a/src/11-usart/send-a-string.md +++ b/f3discovery/src/11-usart/send-a-string.md @@ -27,4 +27,4 @@ your laptop. -最後に、*リリース*モードでプログラムをビルドし、再度、一気に実行して下さい。今回はどうなりますか? \ No newline at end of file +最後に、*リリース*モードでプログラムをビルドし、再度、一気に実行して下さい。今回はどうなりますか? diff --git a/src/11-usart/src/main.rs b/f3discovery/src/11-usart/src/main.rs similarity index 55% rename from src/11-usart/src/main.rs rename to f3discovery/src/11-usart/src/main.rs index 418dedb0e..1446c4ede 100644 --- a/src/11-usart/src/main.rs +++ b/f3discovery/src/11-usart/src/main.rs @@ -1,4 +1,3 @@ -#![deny(unsafe_code)] #![no_main] #![no_std] @@ -7,10 +6,12 @@ use aux11::{entry, iprint, iprintln}; #[entry] fn main() -> ! { - let (usart1, mono_timer, itm) = aux11::init(); + let (usart1, _mono_timer, _itm) = aux11::init(); // 1文字送信します - usart1.tdr.write(|w| w.tdr().bits(u16::from(b'X'))); + usart1 + .tdr + .write(|w| w.tdr().bits(u16::from(b'X')) ); loop {} } diff --git a/src/11-usart/target b/f3discovery/src/11-usart/target similarity index 100% rename from src/11-usart/target rename to f3discovery/src/11-usart/target diff --git a/src/11-usart/uprintln.md b/f3discovery/src/11-usart/uprintln.md similarity index 100% rename from src/11-usart/uprintln.md rename to f3discovery/src/11-usart/uprintln.md diff --git a/src/12-bluetooth-setup/README.md b/f3discovery/src/12-bluetooth-setup/README.md similarity index 100% rename from src/12-bluetooth-setup/README.md rename to f3discovery/src/12-bluetooth-setup/README.md diff --git a/f3discovery/src/12-bluetooth-setup/at-commands.md b/f3discovery/src/12-bluetooth-setup/at-commands.md new file mode 100644 index 000000000..4a295784d --- /dev/null +++ b/f3discovery/src/12-bluetooth-setup/at-commands.md @@ -0,0 +1,62 @@ +## AT commands + +The Bluetooth module and the F3 need to be configured to communicate at the same baud rate. The tutorial code initializes the UART1 serial device to a baud rate of 115200. The HC-05 Bluetooth module is configured at a baud rate of 9600 by default. + +The Bluetooth module supports an AT mode that allows you to examine and change its configuration and settings. To utilize the AT mode, connect the Bluetooth module to the F3 and FTDI as shown in the following diagram. + +

+ +

+ +Recommended steps to enter AT mode: + +- Disconnect the F3 and FTDI from your computer. +- Connect F3's GND pin to the Bluetooth's GND pin using a Female/Female (F/F) wire + (preferably, a black one). +- Connect F3's 5V pin to the Bluetooth's VCC pin using a F/F wire (preferably, a + red one). +- Connect the FTDI RXI pin to the Bluetooth's TXD pin using a Female/Male (F/M) wire. +- Connect the FTDI TXO pin to the Bluetooth's RXD pin using a Female/Male (F/M) wire. +- Now connect the FTDI to your computer via USB cable. +- Next connect the F3 to your computer via USB cable while simultaneously pressing and holding the button on the Bluetooth module (kinda tricky). +- Now, release the button and the Bluetooth module will enter AT mode. You can confirm this by observing that the red LED on the Bluetooth module is blinking in a slow pattern (approx 1-2 seconds on/off). + +The AT mode always operates at a baud rate of 38400, so configure your terminal program for that baud rate and connect to the FTDI device. + +When your serial connection is established, you may get a bunch of `ERROR: (0)` repeatedly being displayed. If this happens, just hit ENTER to stop the errors. + +### Sanity check + +``` +$ at +OK +OK +(etc...) +``` + +Answers `OK` repeatedly until you hit ENTER again. + +### Rename the device + +``` +$ at+name=ferris +OK +``` + +### Query for the current baud rate of the Bluetooth module + +``` +at+uart? ++UART:9600,0,0 +OK ++UART:9600,0,0 +OK +(etc ...) +``` + +### Change the baud rate + +``` +$ at+uart=115200,0,0 +OK +``` diff --git a/src/12-bluetooth-setup/linux.md b/f3discovery/src/12-bluetooth-setup/linux.md similarity index 99% rename from src/12-bluetooth-setup/linux.md rename to f3discovery/src/12-bluetooth-setup/linux.md index 5962a9c8c..da7bace0d 100644 --- a/src/12-bluetooth-setup/linux.md +++ b/f3discovery/src/12-bluetooth-setup/linux.md @@ -66,14 +66,14 @@ $ sudo /etc/init.d/bluetooth start Bluetoothをアンブロックする必要があるかもしれません。`rfkill list`の出力に依存します。 ``` console -$ rkfill list +$ rfkill list 9: hci0: Bluetooth Soft blocked: yes # <-- Hard blocked: no $ sudo rfkill unblock bluetooth -$ rkfill list +$ rfkill list 9: hci0: Bluetooth Soft blocked: no # <-- Hard blocked: no diff --git a/src/12-bluetooth-setup/loopback.md b/f3discovery/src/12-bluetooth-setup/loopback.md similarity index 100% rename from src/12-bluetooth-setup/loopback.md rename to f3discovery/src/12-bluetooth-setup/loopback.md diff --git a/src/13-serial-over-bluetooth/README.md b/f3discovery/src/13-serial-over-bluetooth/README.md similarity index 95% rename from src/13-serial-over-bluetooth/README.md rename to f3discovery/src/13-serial-over-bluetooth/README.md index 49fdea3ef..b149dd70d 100644 --- a/src/13-serial-over-bluetooth/README.md +++ b/f3discovery/src/13-serial-over-bluetooth/README.md @@ -19,13 +19,13 @@ minicom/PuTTYでBluetoothモジュールが動くことを検証します。マ diff --git a/f3discovery/src/14-i2c/.cargo/config b/f3discovery/src/14-i2c/.cargo/config new file mode 100644 index 000000000..f5a1d9520 --- /dev/null +++ b/f3discovery/src/14-i2c/.cargo/config @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = "arm-none-eabi-gdb -q -x openocd.gdb" +#rustflags = [ +# "-C", "link-arg=-Tlink.x", +#] + +[build] +target = "thumbv7em-none-eabihf" diff --git a/src/14-i2c/Cargo.toml b/f3discovery/src/14-i2c/Cargo.toml similarity index 100% rename from src/14-i2c/Cargo.toml rename to f3discovery/src/14-i2c/Cargo.toml diff --git a/src/14-i2c/README.md b/f3discovery/src/14-i2c/README.md similarity index 97% rename from src/14-i2c/README.md rename to f3discovery/src/14-i2c/README.md index e74a8fc21..a2a030b5e 100644 --- a/src/14-i2c/README.md +++ b/f3discovery/src/14-i2c/README.md @@ -45,7 +45,7 @@ I2Cは、Inter-Integrated Circuitを意味しており、*同期シリアル*通 クロック線は通信を同期するために使用するので、これは*同期*プロトコルです。

- +

diff --git a/src/14-i2c/read-several-registers.md b/f3discovery/src/14-i2c/read-several-registers.md similarity index 99% rename from src/14-i2c/read-several-registers.md rename to f3discovery/src/14-i2c/read-several-registers.md index 035ee0d1a..3d1069d4f 100644 --- a/src/14-i2c/read-several-registers.md +++ b/f3discovery/src/14-i2c/read-several-registers.md @@ -96,7 +96,7 @@ fn main() -> ! { // 磁力計のアドレスをR/WビットをWriteに設定して、ブロードキャストします i2c1.cr2.write(|w| { w.start().set_bit(); - w.sadd1().bits(MAGNETOMETER); + w.sadd().bits(MAGNETOMETER); w.rd_wrn().clear_bit(); w.nbytes().bits(1); w.autoend().clear_bit() diff --git a/src/14-i2c/src/main.rs b/f3discovery/src/14-i2c/src/main.rs similarity index 96% rename from src/14-i2c/src/main.rs rename to f3discovery/src/14-i2c/src/main.rs index b65603cbb..28976bb1e 100644 --- a/src/14-i2c/src/main.rs +++ b/f3discovery/src/14-i2c/src/main.rs @@ -6,7 +6,7 @@ use aux14::{entry, iprint, iprintln, prelude::*}; // スレーブアドレス -const MAGNETOMETER: u8 = 0b001_1110; +const MAGNETOMETER: u16 = 0b0011_1100; // 磁力計レジスタのアドレス const OUT_X_H_M: u8 = 0x03; diff --git a/src/14-i2c/target b/f3discovery/src/14-i2c/target similarity index 100% rename from src/14-i2c/target rename to f3discovery/src/14-i2c/target diff --git a/src/14-i2c/the-general-protocol.md b/f3discovery/src/14-i2c/the-general-protocol.md similarity index 92% rename from src/14-i2c/the-general-protocol.md rename to f3discovery/src/14-i2c/the-general-protocol.md index 5e7f05805..56ad39c42 100644 --- a/src/14-i2c/the-general-protocol.md +++ b/f3discovery/src/14-i2c/the-general-protocol.md @@ -19,7 +19,7 @@ I2Cプロトコルは複数のデバイス間の通信をサポートしなけ マスターがスレーブにデータを送りたい場合、次のようになります。

- +

+ +> [Rust]でマイクロコントローラの世界を楽しもう! + +[Rust]: https://www.rust-lang.org/en-US/ + + + +この本は、よくあるC/C++ではなく、Rustを使ったマイクロコントローラの組込みシステム入門コースです。 + + + +## スコープ + + + +以下のトピックを取り上げます(ゆくゆくは、そうしたいです) + + + +- 「組込み」(Rust)プログラムの書き方、ビルド方法、フラッシュへの書き込み方法、デバッグ方法。 + + + +- マイクロコントローラで一般的な機能(「ペリフェラル」)。デジタル入出力、パルス幅変調(PWM)、アナログデジタル変換(ADC)、 + シリアル、I2C、SPIのような一般的な通信プロトコル、など。 + + + +- マルチタスク。協調的マルチタスク vs プリエンプティブマルチタスク、割り込み、スケジューラなど。 + + + +- 制御システム。センサ、キャリブレーション、デジタルフィルタ、アクチュエータ、開ループ制御、閉ループ制御、など。 + + + +## 進め方 + + + +- 初心者に優しく。マイクロコントローラや組込みシステムの開発経験は必要ありません。 + + + +- ハンズオン形式で。理論を実践するためにたくさんの演習をします。*あなた*はほとんどの作業をここで行います。 + + + +- ツール中心に。開発を容易にするツールをたくさん使用します。GDBを使った「実際の」デバッグとログ出力を早い段階で導入します。 + デバッグ機能としてLEDを使用するようなことは、ここではやりません。 + + + +## 目標としないこと + + + +この本でスコープ外のことは、以下の通りです。 + + + +- Rustを教えること。このトピックについては、既に多くの教材があります。マイクロコントローラと組込みシステムに集中します。 + + + +- 電気回路または電子機器の理論についての包括的なテキストであること。 + いくつかのデバイスがどのように動くか、を理解するための最低限の情報を提供します。 + + + +- Rustの低レベルな詳細を説明すること。リンカスクリプトやブートプロセス、 + また、最小限のRustプログラムにこれらの2つの要素を結合する方法については、説明しません。 + + + +また、この教材を他の開発ボードに移植するつもりもありません。この本は、STM32F3DISCOVERY開発ボード専用のものです。 + + + +## 問題の報告 + + + +この本のソースは[このレポジトリ]にあります。誤植やコードに問題を発見した場合は、[issueトラッカー]に報告して下さい。 + + + +[このレポジトリ]: https://github.com/rust-embedded/discovery +[issueトラッカー]: https://github.com/rust-embedded/discovery/issues + +> 訳注:和訳への問題報告は、下記にお願いいたします。 + +和訳のソースは[和訳レポジトリ]にあります。問題を発見した場合は、[和訳issue]に報告して下さい。 + +[和訳レポジトリ]: https://github.com/tomoyuki-nakabayashi/discovery +[和訳issue]: https://github.com/tomoyuki-nakabayashi/discovery/issues + + + +## 他の組込みRustの資料 + + + +このDiscovery本は、[組込みワーキンググループ]が提供する組込みRust資料の1つに過ぎません。 +[組込みRustの本棚]に、数多くの資料があります。そこには、[よくある質問と回答]のリストも有ります。 + + + +[組込みワーキンググループ]: https://github.com/rust-embedded/wg +[組込みRustの本棚]: https://docs.rust-embedded.org +[よくある質問と回答]: https://docs.rust-embedded.org/faq.html + + + +## Sponsored by + +

+ + + +

+ +Many thanks to [integer 32](http://integer32.com/) for sponsoring me to work on this book! Please +give them lots of work (they do Rust consulting!) so they'll have no choice but to hire more +Rustaceans <3. +======= +# `Discovery` + +Discover the world of microcontrollers through [Rust](https://www.rust-lang.org/)! + +There are currently two versions of this book. The first is older and uses an +F3 Discovery circuit board to introduce you to microcontrollers and Rust, while +the second is newer and uses a micro:bit circuit board instead. + +- [Read the newer book, using a micro:bit](https://docs.rust-embedded.org/discovery/microbit) +- [Read the older book, using an F3 discovery board](https://docs.rust-embedded.org/discovery/f3discovery) +- Start working on the examples from this repository +- You've got questions? + - Have a look at our [discussions section on + GitHub](https://github.com/rust-embedded/discovery/discussions) + - Maybe it has already been answered + - If not, start a new discussion +- You've found an issue? + - Have a look at our [issues on + GitHub](https://github.com/rust-embedded/discovery/issues) + - Maybe there is already a workaround + - If not, please open a new one - or even better - a [pull + request](https://github.com/rust-embedded/discovery/pulls) for solving + it +- Have fun and enjoy! +>>>>>>> upstream/master diff --git a/f3discovery/src/SUMMARY.md b/f3discovery/src/SUMMARY.md new file mode 100644 index 000000000..9b1433e25 --- /dev/null +++ b/f3discovery/src/SUMMARY.md @@ -0,0 +1,89 @@ +<<<<<<< HEAD +[導入](README.md) +- [背景](01-background/README.md) +- [ハードウェア/知識の要求](02-requirements/README.md) +- [開発環境の構築](03-setup/README.md) + - [Linux](03-setup/linux.md) + - [Windows](03-setup/windows.md) + - [macOS](03-setup/macos.md) + - [インストールの確認](03-setup/verify.md) +- [ハードウェアとの出会い](04-meet-your-hardware/README.md) +- [LEDルーレット](05-led-roulette/README.md) + - [ビルド](05-led-roulette/build-it.md) + - [Flashへの書き込み](05-led-roulette/flash-it.md) + - [デバッグ](05-led-roulette/debug-it.md) + - [`led`と`delay`の抽象化](05-led-roulette/the-led-and-delay-abstractions.md) + - [課題](05-led-roulette/the-challenge.md) + - [解答例](05-led-roulette/my-solution.md) +- [Hello, world!](06-hello-world/README.md) + - [`panic!`](06-hello-world/panic.md) +- [レジスタ](07-registers/README.md) + - [RTRM](07-registers/rtrm.md) + - [(誤った)最適化](07-registers/optimization.md) + - [`0xBAAAAAAD`番地](07-registers/bad-address.md) + - [異なる場所での不気味な動作](07-registers/spooky-action-at-a-distance.md) + - [型安全な操作](07-registers/type-safe-manipulation.md) +- [LED、再び](08-leds-again/README.md) + - [電源](08-leds-again/power.md) + - [設定](08-leds-again/configuration.md) + - [解答例](08-leds-again/the-solution.md) +- [クロックと時間](09-clocks-and-timers/README.md) + - [`for`ループで遅延](09-clocks-and-timers/for-loop-delays.md) + - [NOP](09-clocks-and-timers/nop.md) + - [ワンショットタイマ](09-clocks-and-timers/one-shot-timer.md) + - [初期化](09-clocks-and-timers/initialization.md) + - [ビジーウェイト](09-clocks-and-timers/busy-waiting.md) + - [全てをまとめる](09-clocks-and-timers/putting-it-all-together.md) +- [シリアル通信](10-serial-communication/README.md) + - [*nixのツール](10-serial-communication/nix-tooling.md) + - [Windowsのツール](10-serial-communication/windows-tooling.md) + - [ループバック](10-serial-communication/loopbacks.md) +- [USART](11-usart/README.md) + - [1バイト送信](11-usart/send-a-single-byte.md) + - [文字列送信](11-usart/send-a-string.md) + - [バッファオーバーラン](11-usart/buffer-overrun.md) + - [`uprintln!`](11-usart/uprintln.md) + - [1バイト受信](11-usart/receive-a-single-byte.md) + - [エコーサーバー](11-usart/echo-server.md) + - [文字列の反転](11-usart/reverse-a-string.md) + - [解答例](11-usart/my-solution.md) +- [Bluetooth設定](12-bluetooth-setup/README.md) + - [Linux](12-bluetooth-setup/linux.md) + - [ループバック](12-bluetooth-setup/loopback.md) + +- [Bluetooth経由のシリアル](13-serial-over-bluetooth/README.md) +- [I2C](14-i2c/README.md) + - [一般的なプロトコル](14-i2c/the-general-protocol.md) + - [LSM303DLHC](14-i2c/lsm303dlhc.md) + - [1つのレジスタを読む](14-i2c/read-a-single-register.md) + - [解答例](14-i2c/the-solution.md) + - [複数のレジスタを読む](14-i2c/read-several-registers.md) +- [LEDコンパス](15-led-compass/README.md) + - [第一弾](15-led-compass/take-1.md) + - [解答例1](15-led-compass/solution-1.md) + - [第二弾](15-led-compass/take-2.md) + - [解答例2](15-led-compass/solution-2.md) + - [大きさ](15-led-compass/magnitude.md) + - [キャリブレーション](15-led-compass/calibration.md) +- [パンチングマシン](16-punch-o-meter/README.md) + - [重力は上を向いている?](16-punch-o-meter/gravity-is-up.md) + - [課題](16-punch-o-meter/the-challenge.md) + - [解答例](16-punch-o-meter/my-solution.md) +- [もっと楽しむために](explore.md) + +--- + +[トラブルシューティング](appendix/1-general-troubleshooting/README.md) + + + + + + + + + +--- +======= +[Choose Your Discovery](README.md) +>>>>>>> upstream/master diff --git a/src/WIP-async-io-the-future/.cargo/config b/f3discovery/src/WIP-async-io-the-future/.cargo/config similarity index 100% rename from src/WIP-async-io-the-future/.cargo/config rename to f3discovery/src/WIP-async-io-the-future/.cargo/config diff --git a/src/WIP-async-io-the-future/.gdbinit b/f3discovery/src/WIP-async-io-the-future/.gdbinit similarity index 100% rename from src/WIP-async-io-the-future/.gdbinit rename to f3discovery/src/WIP-async-io-the-future/.gdbinit diff --git a/src/WIP-async-io-the-future/Cargo.toml b/f3discovery/src/WIP-async-io-the-future/Cargo.toml similarity index 100% rename from src/WIP-async-io-the-future/Cargo.toml rename to f3discovery/src/WIP-async-io-the-future/Cargo.toml diff --git a/src/WIP-async-io-the-future/README.md b/f3discovery/src/WIP-async-io-the-future/README.md similarity index 100% rename from src/WIP-async-io-the-future/README.md rename to f3discovery/src/WIP-async-io-the-future/README.md diff --git a/src/WIP-async-io-the-future/another-challenge.md b/f3discovery/src/WIP-async-io-the-future/another-challenge.md similarity index 100% rename from src/WIP-async-io-the-future/another-challenge.md rename to f3discovery/src/WIP-async-io-the-future/another-challenge.md diff --git a/src/WIP-async-io-the-future/more-challenges.md b/f3discovery/src/WIP-async-io-the-future/more-challenges.md similarity index 100% rename from src/WIP-async-io-the-future/more-challenges.md rename to f3discovery/src/WIP-async-io-the-future/more-challenges.md diff --git a/src/WIP-async-io-the-future/my-other-solution.md b/f3discovery/src/WIP-async-io-the-future/my-other-solution.md similarity index 100% rename from src/WIP-async-io-the-future/my-other-solution.md rename to f3discovery/src/WIP-async-io-the-future/my-other-solution.md diff --git a/src/WIP-async-io-the-future/my-solution.md b/f3discovery/src/WIP-async-io-the-future/my-solution.md similarity index 100% rename from src/WIP-async-io-the-future/my-solution.md rename to f3discovery/src/WIP-async-io-the-future/my-solution.md diff --git a/src/WIP-async-io-the-future/pg/Cargo.toml b/f3discovery/src/WIP-async-io-the-future/pg/Cargo.toml similarity index 100% rename from src/WIP-async-io-the-future/pg/Cargo.toml rename to f3discovery/src/WIP-async-io-the-future/pg/Cargo.toml diff --git a/src/WIP-async-io-the-future/pg/src/lib.rs b/f3discovery/src/WIP-async-io-the-future/pg/src/lib.rs similarity index 100% rename from src/WIP-async-io-the-future/pg/src/lib.rs rename to f3discovery/src/WIP-async-io-the-future/pg/src/lib.rs diff --git a/src/WIP-async-io-the-future/serial.md b/f3discovery/src/WIP-async-io-the-future/serial.md similarity index 100% rename from src/WIP-async-io-the-future/serial.md rename to f3discovery/src/WIP-async-io-the-future/serial.md diff --git a/src/WIP-async-io-the-future/src/main.rs b/f3discovery/src/WIP-async-io-the-future/src/main.rs similarity index 100% rename from src/WIP-async-io-the-future/src/main.rs rename to f3discovery/src/WIP-async-io-the-future/src/main.rs diff --git a/src/WIP-async-io-the-future/the-challenge.md b/f3discovery/src/WIP-async-io-the-future/the-challenge.md similarity index 100% rename from src/WIP-async-io-the-future/the-challenge.md rename to f3discovery/src/WIP-async-io-the-future/the-challenge.md diff --git a/src/WIP-async-io-the-future/timer.md b/f3discovery/src/WIP-async-io-the-future/timer.md similarity index 100% rename from src/WIP-async-io-the-future/timer.md rename to f3discovery/src/WIP-async-io-the-future/timer.md diff --git a/src/appendix/1-general-troubleshooting/README.md b/f3discovery/src/appendix/1-general-troubleshooting/README.md similarity index 76% rename from src/appendix/1-general-troubleshooting/README.md rename to f3discovery/src/appendix/1-general-troubleshooting/README.md index 1156a8612..4dc88086e 100644 --- a/src/appendix/1-general-troubleshooting/README.md +++ b/f3discovery/src/appendix/1-general-troubleshooting/README.md @@ -29,6 +29,7 @@ in procedure 'init' in procedure 'ocd_bouncer' ``` +<<<<<<< HEAD:src/appendix/1-general-troubleshooting/README.md #### 原因と解決策 @@ -47,9 +48,32 @@ in procedure 'ocd_bouncer' - Linux:デバイスを開くためのパーミッションがないかもしれません。`sudo`をつけてもう1度試して下さい。 もしこれがうまくいくようであれば、OpenOCDをroot権限なしで実行するために、[これらの手順]が使えます。 - Windows:ST-LINK USBドライバがない可能性があります。インストール手順は[こちら]です。 +======= +#### Cause + +The device is not (properly) connected or not the correct ST-LINK interface +configuration is used. + +#### Fix + +Linux: + +- Check the USB connection using `lsusb`. +- You may not have enough permission to open the device. Try again with `sudo`. + If that works, you can use [these instructions] to make OpenOCD work without + root privilege. +- You might be using the wrong interface configuration for your ST-LINK. + Try `interface/stlink-v2.cfg` instead of `interface/stlink-v2-1.cfg`. +>>>>>>> upstream/master:f3discovery/src/appendix/1-general-troubleshooting/README.md @@ -220,9 +244,49 @@ Or, `itmdump` was called **after** the `monitor tpiu` was issued thus making - 次に、`itmdump`を起動します。 - 次に、`monitor tpiu`コマンドを実行するGDBセッションを起動します。 +<<<<<<< HEAD:src/appendix/1-general-troubleshooting/README.md ## Cargoの問題 +======= + +### can't connect to OpenOCD - "Error: couldn't bind [telnet] to socket: Address already in use" + +#### Symptoms + +Upon trying to establish a *new connection* with the device you get an error +that looks something like this: + +``` +$ openocd -f (..) +(..) +Error: couldn't bind telnet to socket: Address already in use +``` + +#### Cause + +One or more of the ports OpenOCD requires access to, 3333, 4444, or 6666, is in use by another process. Each of these ports is used for another aspect: 3333 for gdb, 4444 for telnet, 6666 for remote procedure call (RPC) commands to TCL + +#### Fix + +You can go two routes for fixing this. A) Kill any process that's using one of those ports. B) Specify different ports you know to be free for OpenOCD to use. + +Solution A + +Mac: +- Get a list of processes using ports by running `sudo lsof -PiTCP -sTCP:LISTEN` +- Kill the process(es) blocking the key ports by noting their pid(s) and running `kill [pid]` for each. (Assuming you can confirm they're not running anything mission-critical on your machine!) + +Solution B + +All: +- Send configuration details to OpenOCD when starting it up so that it uses a different port from the default for any of the processes. +- For example, to do its telnet features on 4441 instead of the default 4444, you would run `openocd -f interface/stlink-v2-1.cfg -f target/stm32f3x.cfg -c "telnet_port 4441"` +- More details on OpenOCD's Configuration Stage can be found in their [official docs online]: http://openocd.org/doc/html/Server-Configuration.html + + +## Cargo problems +>>>>>>> upstream/master:f3discovery/src/appendix/1-general-troubleshooting/README.md ### "can't find crate for `core`" diff --git a/f3discovery/src/appendix/2-how-to-use-gdb/README.md b/f3discovery/src/appendix/2-how-to-use-gdb/README.md new file mode 100644 index 000000000..4153c6dcc --- /dev/null +++ b/f3discovery/src/appendix/2-how-to-use-gdb/README.md @@ -0,0 +1,89 @@ +# How to use GDB + +Below are some useful GDB commands that can help us debug our programs. This assumes you have [flashed a program](../../05-led-roulette/flash-it.md) onto your microcontroller and attached to an OpenOCD session. + +## General Debugging + +> **NOTE:** Many of the commands you see below can be executed using a short form. For example, `continue` can simply be used as `c`, or `break $location` can be used as `b $location`. Once you have experience with the commands below, try to see how short you can get the commands to go before GDB doesn't recognize them! + + +### Dealing with Breakpoints + +* `break $location`: Set a breakpoint at a place in your code. The value of `$location` can include: + * `break *main` - Break on the exact address of the function `main` + * `break *0x080012f2` - Break on the exact memory location `0x080012f2` + * `break 123` - Break on line 123 of the currently displayed file + * `break main.rs:123` - Break on line 123 of the file `main.rs` +* `info break`: Display current breakpoints +* `delete`: Delete all breakpoints + * `delete $n`: Delete breakpoint `$n` (`n` being a number. For example: `delete $2`) +* `clear`: Delete breakpoint at next instruction + * `clear main.rs:$function`: Delete breakpoint at entry of `$function` in `main.rs` + * `clear main.rs:123`: Delete breakpoint on line 123 of `main.rs` +* `enable`: Enable all set breakpoints + * `enable $n`: Enable breakpoint `$n` +* `disable`: Disable all set breakpoints + * `disable $n`: Disable breakpoint `$n` + +### Controlling Execution + +* `continue`: Begin or continue execution of your program +* `next`: Execute the next line of your program + * `next $n`: Repeat `next` `$n` number times +* `nexti`: Same as `next` but with machine instructions instead +* `step`: Execute the next line, if the next line includes a call to another function, step into that code + * `step $n`: Repeat `step` `$n` number times +* `stepi`: Same as `step` but with machine instructions instead +* `jump $location`: Resume execution at specified location: + * `jump 123`: Resume execution at line 123 + * `jump 0x080012f2`: Resume execution at address 0x080012f2 + +### Printing Information + +* `print /$f $data` - Print the value contained by the variable `$data`. Optionally format the output with `$f`, which can include: + ```txt + x: hexadecimal + d: signed decimal + u: unsigned decimal + o: octal + t: binary + a: address + c: character + f: floating point + ``` + * `print /t 0xA`: Prints the hexadecimal value `0xA` as binary (0b1010) +* `x /$n$u$f $address`: Examine memory at `$address`. Optionally, `$n` define the number of units to display, `$u` unit size (bytes, halfwords, words, etc), `$f` any `print` format defined above + * `x /5i 0x080012c4`: Print 5 machine instructions staring at address `0x080012c4` + * `x/4xb $pc`: Print 4 bytes of memory starting where `$pc` currently is pointing +* `disassemble $location` + * `disassemble /r main`: Disassemble the function `main`, using `/r` to show the bytes that make up each instruction + + +### Looking at the Symbol Table + +* `info functions $regex`: Print the names and data types of functions matched by `$regex`, omit `$regex` to print all functions + * `info functions main`: Print names and types of defined functions that contain the word `main` +* `info address $symbol`: Print where `$symbol` is stored in memory + * `info address GPIOC`: Print the memory address of the variable `GPIOC` +* `info variables $regex`: Print names and types of global variables matched by `$regex`, omit `$regex` to print all global variables +* `ptype $data`: Print more detailed information about `$data` + * `ptype cp`: Print detailed type information about the variable `cp` + +### Poking around the Program Stack + +* `backtrace $n`: Print trace of `$n` frames, or omit `$n` to print all frames + * `backtrace 2`: Print trace of first 2 frames +* `frame $n`: Select frame with number or address `$n`, omit `$n` to display current frame +* `up $n`: Select frame `$n` frames up +* `down $n`: Select frame `$n` frames down +* `info frame $address`: Describe frame at `$address`, omit `$address` for currently selected frame +* `info args`: Print arguments of selected frame +* `info registers $r`: Print the value of register `$r` in selected frame, omit `$r` for all registers + * `info registers $sp`: Print the value of the stack pointer register `$sp` in the current frame + +### Controlling OpenOCD Remotely + +* `monitor reset run`: Reset the CPU, starting execution over again + * `monitor reset`: Same as above +* `monitor reset init`: Reset the CPU, halting execution at the start +* `monitor targets`: Display information and state of current target diff --git a/src/assets/bluetooth-serial.png b/f3discovery/src/assets/bluetooth-serial.png similarity index 100% rename from src/assets/bluetooth-serial.png rename to f3discovery/src/assets/bluetooth-serial.png diff --git a/src/assets/bluetooth.jpg b/f3discovery/src/assets/bluetooth.jpg similarity index 100% rename from src/assets/bluetooth.jpg rename to f3discovery/src/assets/bluetooth.jpg diff --git a/src/assets/emf.svg b/f3discovery/src/assets/emf.svg similarity index 100% rename from src/assets/emf.svg rename to f3discovery/src/assets/emf.svg diff --git a/src/assets/f3-bluetooth-loopback.png b/f3discovery/src/assets/f3-bluetooth-loopback.png similarity index 100% rename from src/assets/f3-bluetooth-loopback.png rename to f3discovery/src/assets/f3-bluetooth-loopback.png diff --git a/src/assets/f3-bluetooth-power-only.png b/f3discovery/src/assets/f3-bluetooth-power-only.png similarity index 100% rename from src/assets/f3-bluetooth-power-only.png rename to f3discovery/src/assets/f3-bluetooth-power-only.png diff --git a/src/assets/f3-bluetooth.png b/f3discovery/src/assets/f3-bluetooth.png similarity index 100% rename from src/assets/f3-bluetooth.png rename to f3discovery/src/assets/f3-bluetooth.png diff --git a/src/assets/f3-l3gd20.png b/f3discovery/src/assets/f3-l3gd20.png similarity index 100% rename from src/assets/f3-l3gd20.png rename to f3discovery/src/assets/f3-l3gd20.png diff --git a/src/assets/f3-lsm303dlhc.png b/f3discovery/src/assets/f3-lsm303dlhc.png similarity index 100% rename from src/assets/f3-lsm303dlhc.png rename to f3discovery/src/assets/f3-lsm303dlhc.png diff --git a/src/assets/f3-serial-production.png b/f3discovery/src/assets/f3-serial-production.png similarity index 100% rename from src/assets/f3-serial-production.png rename to f3discovery/src/assets/f3-serial-production.png diff --git a/src/assets/f3-serial.png b/f3discovery/src/assets/f3-serial.png similarity index 100% rename from src/assets/f3-serial.png rename to f3discovery/src/assets/f3-serial.png diff --git a/src/assets/f3-swd.png b/f3discovery/src/assets/f3-swd.png similarity index 100% rename from src/assets/f3-swd.png rename to f3discovery/src/assets/f3-swd.png diff --git a/src/assets/f3.jpg b/f3discovery/src/assets/f3.jpg similarity index 100% rename from src/assets/f3.jpg rename to f3discovery/src/assets/f3.jpg diff --git a/src/assets/gdb-layout-asm.png b/f3discovery/src/assets/gdb-layout-asm.png similarity index 100% rename from src/assets/gdb-layout-asm.png rename to f3discovery/src/assets/gdb-layout-asm.png diff --git a/f3discovery/src/assets/gdb-layout-split-1.png b/f3discovery/src/assets/gdb-layout-split-1.png new file mode 100644 index 000000000..f3d6df494 Binary files /dev/null and b/f3discovery/src/assets/gdb-layout-split-1.png differ diff --git a/f3discovery/src/assets/gdb-layout-split-2.png b/f3discovery/src/assets/gdb-layout-split-2.png new file mode 100644 index 000000000..db2ecdaef Binary files /dev/null and b/f3discovery/src/assets/gdb-layout-split-2.png differ diff --git a/f3discovery/src/assets/gdb-layout-split-3.png b/f3discovery/src/assets/gdb-layout-split-3.png new file mode 100644 index 000000000..b3e5210e5 Binary files /dev/null and b/f3discovery/src/assets/gdb-layout-split-3.png differ diff --git a/f3discovery/src/assets/gdb-layout-split-4.png b/f3discovery/src/assets/gdb-layout-split-4.png new file mode 100644 index 000000000..01ddc6458 Binary files /dev/null and b/f3discovery/src/assets/gdb-layout-split-4.png differ diff --git a/src/assets/gdb-layout-src.png b/f3discovery/src/assets/gdb-layout-src.png similarity index 100% rename from src/assets/gdb-layout-src.png rename to f3discovery/src/assets/gdb-layout-src.png diff --git a/src/assets/integer32.svg b/f3discovery/src/assets/integer32.svg similarity index 100% rename from src/assets/integer32.svg rename to f3discovery/src/assets/integer32.svg diff --git a/src/assets/jumper-wires.jpg b/f3discovery/src/assets/jumper-wires.jpg similarity index 100% rename from src/assets/jumper-wires.jpg rename to f3discovery/src/assets/jumper-wires.jpg diff --git a/f3discovery/src/assets/minicom.png b/f3discovery/src/assets/minicom.png new file mode 100644 index 000000000..598305b7a Binary files /dev/null and b/f3discovery/src/assets/minicom.png differ diff --git a/src/assets/putty-console.png b/f3discovery/src/assets/putty-console.png similarity index 100% rename from src/assets/putty-console.png rename to f3discovery/src/assets/putty-console.png diff --git a/f3discovery/src/assets/putty-session-choose-serial.png b/f3discovery/src/assets/putty-session-choose-serial.png new file mode 100644 index 000000000..3f5680d4f Binary files /dev/null and b/f3discovery/src/assets/putty-session-choose-serial.png differ diff --git a/f3discovery/src/assets/putty-settings.png b/f3discovery/src/assets/putty-settings.png new file mode 100644 index 000000000..7b9e0ff06 Binary files /dev/null and b/f3discovery/src/assets/putty-settings.png differ diff --git a/src/assets/quadrant-i.png b/f3discovery/src/assets/quadrant-i.png similarity index 100% rename from src/assets/quadrant-i.png rename to f3discovery/src/assets/quadrant-i.png diff --git a/src/assets/quadrants.png b/f3discovery/src/assets/quadrants.png similarity index 100% rename from src/assets/quadrants.png rename to f3discovery/src/assets/quadrants.png diff --git a/src/assets/serial-loopback.png b/f3discovery/src/assets/serial-loopback.png similarity index 100% rename from src/assets/serial-loopback.png rename to f3discovery/src/assets/serial-loopback.png diff --git a/src/assets/serial.jpg b/f3discovery/src/assets/serial.jpg similarity index 100% rename from src/assets/serial.jpg rename to f3discovery/src/assets/serial.jpg diff --git a/src/assets/setup-windows.png b/f3discovery/src/assets/setup-windows.png similarity index 100% rename from src/assets/setup-windows.png rename to f3discovery/src/assets/setup-windows.png diff --git a/src/assets/st-link.png b/f3discovery/src/assets/st-link.png similarity index 100% rename from src/assets/st-link.png rename to f3discovery/src/assets/st-link.png diff --git a/src/assets/timing-diagram.png b/f3discovery/src/assets/timing-diagram.png similarity index 100% rename from src/assets/timing-diagram.png rename to f3discovery/src/assets/timing-diagram.png diff --git a/src/assets/usb-cable.jpg b/f3discovery/src/assets/usb-cable.jpg similarity index 100% rename from src/assets/usb-cable.jpg rename to f3discovery/src/assets/usb-cable.jpg diff --git a/src/explore.md b/f3discovery/src/explore.md similarity index 90% rename from src/explore.md rename to f3discovery/src/explore.md index 94b7e0691..9c0acd7c3 100644 --- a/src/explore.md +++ b/f3discovery/src/explore.md @@ -247,6 +247,47 @@ protocol in PCs and smartphones. Whereas inside cars you'll find plenty of CAN なぜなら、USBは、PCやスマートフォンの至るところで使われているプロトコルだからです。 一方、車の内部では、多くのCAN「バス」を見つけられます。デジタルセンサにはSPIを使うものがあり、他のものはI2CやSMBUSを使います。 +## General Embedded-Relevant Topics + +These topics cover items that are not specific to our device, or the hardware on +it. Instead, they discuss useful techniques that could be used on embedded +systems. + +### Gyroscopes + +As part of our Punch-o-meter exercise, we used the Accelerometer to measure +changes in acceleration in three dimensions. Our board also features a sensor +called a Gyroscope, which allows us to measure changes in "spin" in three +dimensions. + +This can be very useful when trying to build certain systems, such as a robot +that wants to avoid tipping over. Additionally, the data from a sensor like a +gyroscope can also be combined with data from accelerometer using a technique +called Sensor Fusion (see below for more information). + +### Servo and Stepper Motors + +While some motors are used primarily just to spin in one direction or the other, +for example driving a remote control car forwards or backwards, it is sometimes +useful to measure more precisely how a motor rotates. + +Our microcontroller can be used to drive Servo or Stepper motors, which allow +for more precise control of how many turns are being made by the motor, or +can even position the motor in one specific place, for example if we wanted to +move the arms of a clock to a particular direction. + +### Sensor fusion + +The STM32F3DISCOVERY contains three motion sensors: an accelerometer, a +gyroscope and a magnetometer. On their own these measure: (proper) acceleration, +angular speed and (the Earth's) magnetic field. But these magnitudes can be +"fused" into something more useful: a "robust" measurement of the orientation of +the board. Where robust means with less measurement error than a single sensor +would be capable of. + +This idea of deriving more reliable data from different sources is known as +sensor fusion. + --- diff --git a/f3discovery/src/openocd.gdb b/f3discovery/src/openocd.gdb new file mode 100644 index 000000000..7dc8dd77f --- /dev/null +++ b/f3discovery/src/openocd.gdb @@ -0,0 +1,36 @@ +# Connect to gdb remote server +target remote :3333 + +# Load will flash the code +load + +# Enable demangling asm names on disassembly +set print asm-demangle on + +# Enable pretty printing +set print pretty on + +# Disable style sources as the default colors can be hard to read +set style sources off + +# Initialize monitoring so iprintln! macro output +# is sent from the itm port to itm.txt +monitor tpiu config internal itm.txt uart off 8000000 + +# Turn on the itm port +monitor itm port 0 on + +# Set a breakpoint at main, aka entry +break main + +# Set a breakpoint at DefaultHandler +break DefaultHandler + +# Set a breakpoint at HardFault +break HardFault + +# Continue running until we hit the main breakpoint +continue + +# Step from the trampoline code in entry into main +step diff --git a/ga.sh b/ga.sh deleted file mode 100644 index 7d14331b2..000000000 --- a/ga.sh +++ /dev/null @@ -1,24 +0,0 @@ -set -euxo pipefail - -GA_CODE=$(cat < - - -EOF -) - -GA_CODE=$(echo $GA_CODE | sed -e 's/\n//g') - -for f in $(find book -name '*.html'); do - echo $f - sed -i -e "s@\(\)@$GA_CODE\n\1@" $f -done diff --git a/microbit/Cargo.toml b/microbit/Cargo.toml new file mode 100644 index 000000000..9f01f2441 --- /dev/null +++ b/microbit/Cargo.toml @@ -0,0 +1,14 @@ +[workspace] +members = [ + "src/03-setup", + "src/05-led-roulette", + "src/07-uart", + "src/08-i2c", + "src/09-led-compass", + "src/10-punch-o-meter", +] + +[profile.release] +codegen-units = 1 +debug = true +lto = true diff --git a/microbit/book.toml b/microbit/book.toml new file mode 100644 index 000000000..9c7f86d26 --- /dev/null +++ b/microbit/book.toml @@ -0,0 +1,8 @@ +[book] +title = "Discovery" +description = "Discover the world of microcontrollers through Rust" +author = "Rust Embedded Resources Team" +language = "en" + +[output.html] +git-repository-url = "https://github.com/rust-embedded/discovery/" diff --git a/microbit/src/01-background/README.md b/microbit/src/01-background/README.md new file mode 100644 index 000000000..81595fc3a --- /dev/null +++ b/microbit/src/01-background/README.md @@ -0,0 +1,82 @@ +# Background + +## What's a microcontroller? + +A microcontroller is a *system* on a chip. Whereas your computer is made up of several discrete +components: a processor, RAM, storage, an Ethernet port, etc.; a microcontroller has all those types +of components built into a single "chip" or package. This makes it possible to build systems with +fewer parts. + +## What can you do with a microcontroller? + +Lots of things! Microcontrollers are the central part of what are known as "*embedded* systems". +Embedded systems are everywhere, but you don't usually notice them. They control the machines that +wash your clothes, print your documents, and cook your food. Embedded systems keep the buildings +that you live and work in at a comfortable temperature, and control the components that make the +vehicles you travel in stop and go. + +Most embedded systems operate without user intervention. Even if they expose a user interface like a +washing machine does; most of their operation is done on their own. + +Embedded systems are often used to *control* a physical process. To make this possible, they have +one or more devices to tell them about the state of the world ("sensors"), and one or more +devices which allow them to change things ("actuators"). For example, a building climate control +system might have: + +- Sensors which measure temperature and humidity in various locations. +- Actuators which control the speed of fans. +- Actuators which cause heat to be added or removed from the building. + +## When should I use a microcontroller? + +Many of the embedded systems listed above could be implemented with a computer running Linux (for +example a "Raspberry Pi"). Why use a microcontroller instead? Sounds like it might be harder to +develop a program. + +Some reasons might include: + +**Cost.** A microcontroller is much cheaper than a general purpose computer. Not only is the +microcontroller cheaper; it also requires many fewer external electrical components to operate. +This makes Printed Circuit Boards (PCB) smaller and cheaper to design and manufacture. + +**Power consumption.** Most microcontrollers consume a fraction of the power of a full blown +processor. For applications which run on batteries, that makes a huge difference. + +**Responsiveness.** To accomplish their purpose, some embedded systems must always react within a +limited time interval (e.g. the "anti-lock" breaking system of a car). If the system misses this +type of *deadline*, a catastrophic failure might occur. Such a deadline is called a "hard real time" +requirement. An embedded system which is bound by such a deadline is referred to as a "hard +real-time system". A general purpose computer and OS usually has many software components which +share the computer's processing resources. This makes it harder to guarantee execution of a program +within tight time constraints. + +**Reliability.** In systems with fewer components (both hardware and software), there is less to go +wrong! + +## When should I *not* use a microcontroller? + +Where heavy computations are involved. To keep their power consumption low, microcontrollers have +very limited computational resources available to them. For example, some microcontrollers don't +even have hardware support for floating point operations. On those devices, performing a simple +addition of single precision numbers can take hundreds of CPU cycles. + +## Why use Rust and not C? + +Hopefully, I don't need to convince you here as you are probably familiar with the language +differences between Rust and C. One point I do want to bring up is package management. C lacks an +official, widely accepted package management solution whereas Rust has Cargo. This makes development +*much* easier. And, IMO, easy package management encourages code reuse because libraries can be +easily integrated into an application which is also a good thing as libraries get more "battle +testing". + +## Why should I not use Rust? + +Or why should I prefer C over Rust? + +The C ecosystem is way more mature. Off the shelf solution for several problems already exist. If +you need to control a time sensitive process, you can grab one of the existing commercial Real Time +Operating Systems (RTOS) out there and solve your problem. There are no commercial, production-grade +RTOSes in Rust yet so you would have to either create one yourself or try one of the ones that are +in development. You can find a list of those in the [Awesome Embedded Rust] repository. + +[Awesome Embedded Rust]: https://github.com/rust-embedded/awesome-embedded-rust#real-time-operating-system-rtos diff --git a/microbit/src/02-requirements/README.md b/microbit/src/02-requirements/README.md new file mode 100644 index 000000000..689f4ce5f --- /dev/null +++ b/microbit/src/02-requirements/README.md @@ -0,0 +1,64 @@ +# Hardware/knowledge requirements + +The primary knowledge requirement to read this book is to know *some* Rust. It's +hard for me to quantify *some* but at least I can tell you that you don't need +to fully grok generics, but you do need to know how to *use* closures. You also +need to be familiar with the idioms of the [2018 edition], in particular with +the fact that `extern crate` is not necessary in the 2018 edition. + +[2018 edition]: https://rust-lang-nursery.github.io/edition-guide/ + +Also, to follow this material you'll need the following hardware: + +- A [micro:bit v2] board, alternatively a [micro:bit v1.5] board, the book + will refer to the v1.5 as just v1. + +[micro:bit v2]: https://tech.microbit.org/hardware/ +[micro:bit v1.5]: https://tech.microbit.org/hardware/1-5-revision/ + +(You can purchase this board from several [electronics][0] [suppliers][1]) + +[0]: https://microbit.org/buy/ +[1]: https://www.mouser.com/microbit/_/N-aez3t?P=1y8um0l + +

+ +

+ +> **NOTE** This is an image of a micro:bit v2, the front of the v1 looks slightly different + +- One micro-B USB cable, required to make the micro:bit board work. + Make sure that the cable supports data transfer as some cables only support charging devices. + +

+ +

+ +> **NOTE** You may already have a cable like this, as some micro:bit kits ship with such cables. +> Some USB cables used to charge mobile devices may also work, if they are micro-B and have the +> capability to transmit data. + +> **FAQ**: Wait, why do I need this specific hardware? + +It makes my life and yours much easier. + +The material is much, much more approachable if we don't have to worry about hardware differences. +Trust me on this one. + +> **FAQ**: Can I follow this material with a different development board? + +Maybe? It depends mainly on two things: your previous experience with microcontrollers and/or +whether a high level crate already exists, like the [`nrf52-hal`], for your development board +somewhere. You can look through the [Awesome Embedded Rust HAL list] for your microcontroller, +if you intend to use a different one. + +[`nrf52-hal`]: https://docs.rs/nrf52-hal +[Awesome Embedded Rust]: https://github.com/rust-embedded/awesome-embedded-rust#hal-implementation-crates + +With a different development board, this text would lose most if not all its beginner friendliness +and "easy to follow"-ness, IMO. + +If you have a different development board and you don't consider yourself a total beginner, you are +better off starting with the [quickstart] project template. + +[quickstart]: https://rust-embedded.github.io/cortex-m-quickstart/cortex_m_quickstart/ diff --git a/microbit/src/03-setup/.cargo/config b/microbit/src/03-setup/.cargo/config new file mode 100644 index 000000000..6260c5718 --- /dev/null +++ b/microbit/src/03-setup/.cargo/config @@ -0,0 +1,4 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] diff --git a/microbit/src/03-setup/Cargo.toml b/microbit/src/03-setup/Cargo.toml new file mode 100644 index 000000000..45466cf5b --- /dev/null +++ b/microbit/src/03-setup/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rtt-check" +version = "0.1.0" +authors = ["Henrik Böving "] +edition = "2018" + +[dependencies] +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +rtt-target = { version = "0.3.1", features = ["cortex-m"] } +panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } diff --git a/microbit/src/03-setup/Embed.toml b/microbit/src/03-setup/Embed.toml new file mode 100644 index 000000000..d84d0ac75 --- /dev/null +++ b/microbit/src/03-setup/Embed.toml @@ -0,0 +1,12 @@ +[default.probe] +protocol = "Swd" + +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 + +[default.rtt] +enabled = true + +[default.gdb] +enabled = false diff --git a/microbit/src/03-setup/README.md b/microbit/src/03-setup/README.md new file mode 100644 index 000000000..bf1c12d5a --- /dev/null +++ b/microbit/src/03-setup/README.md @@ -0,0 +1,93 @@ +# Setting up a development environment + +Dealing with microcontrollers involves several tools as we'll be dealing with an architecture +different from your computer's and we'll have to run and debug programs on a "remote" device. + +## Documentation + +Tooling is not everything though. Without documentation, it is pretty much impossible to work with +microcontrollers. + +We'll be referring to all these documents throughout this book: + +- [LSM303AGR] + +[LSM303AGR]: https://www.st.com/resource/en/datasheet/lsm303agr.pdf + +## Tools + +We'll use all the tools listed below. Where a minimum version is not specified, any recent version +should work but we have listed the version we have tested. + +- Rust 1.53.0 or a newer toolchain. + +- `gdb-multiarch`. Tested version: 10.2. Other versions will most likely work as well though + If your distribution/platform does not have `gdb-multiarch` available `arm-none-eabi-gdb` + will do the trick as well. Furthermore, some normal `gdb` binaries are built with multiarch + capabilities as well, you can find further information about this in the sub chapters. + +- [`cargo-binutils`]. Version 0.3.3 or newer. + +[`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils + +- [`cargo-embed`]. Version 0.11.0 or newer. + +[`cargo-embed`]: https://github.com/probe-rs/cargo-embed + +- `minicom` on Linux and macOS. Tested version: 2.7.1. Other versions will most likely work as well though + +- `PuTTY` on Windows. + +Next, follow OS-agnostic installation instructions for a few of the tools: + +### `rustc` & Cargo + +Install rustup by following the instructions at [https://rustup.rs](https://rustup.rs). + +If you already have rustup installed double check that you are on the stable +channel and your stable toolchain is up-to-date. `rustc -V` should return a date +newer than the one shown below: + +``` console +$ rustc -V +rustc 1.53.0 (53cb7b09b 2021-06-17) +``` + +### `cargo-binutils` + +``` console +$ rustup component add llvm-tools-preview + +$ cargo install cargo-binutils --vers 0.3.3 + +$ cargo size --version +cargo-size 0.3.3 +``` + +### `cargo-embed` + +```console +$ cargo install cargo-embed --vers 0.11.0 + +$ cargo embed --version +cargo-embed 0.11.0 +git commit: crates.io +``` + +### This repository + +Since this book also contains some small Rust code bases used in various chapters +you will also have to download its source code. You can do this in one of the following ways: + +* Visit the [repository](https://github.com/rust-embedded/discovery/), click the green "Code" button and then the + "Download Zip" one +* Clone it using git (if you know git you presumably already have it installed) from the same repository as linked in + the zip approach + +### OS specific instructions + +Now follow the instructions specific to the OS you are using: + +- [Linux](linux.md) +- [Windows](windows.md) +- [macOS](macos.md) diff --git a/microbit/src/03-setup/build.rs b/microbit/src/03-setup/build.rs new file mode 100644 index 000000000..c8d8c9141 --- /dev/null +++ b/microbit/src/03-setup/build.rs @@ -0,0 +1,30 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory (wherever `Cargo.toml` is). However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! a rebuild of the application with new memory settings is ensured after updating `memory.x`. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/microbit/src/03-setup/linux.md b/microbit/src/03-setup/linux.md new file mode 100644 index 000000000..304ce2638 --- /dev/null +++ b/microbit/src/03-setup/linux.md @@ -0,0 +1,81 @@ +# Linux + +Here are the installation commands for a few Linux distributions. + +## Ubuntu 20.04 or newer / Debian 10 or newer + +> **NOTE** `gdb-multiarch` is the GDB command you'll use to debug your ARM +> Cortex-M programs +``` console +$ sudo apt-get install \ + gdb-multiarch \ + minicom +``` + +## Fedora 32 or newer +> **NOTE** `gdb` is the GDB command you'll use to debug your ARM +> Cortex-M programs +``` console +$ sudo dnf install \ + gdb \ + minicom +``` + +## Arch Linux + +> **NOTE** `arm-none-eabi-gdb` is the GDB command you'll use to debug your ARM +> Cortex-M programs +``` console +$ sudo pacman -S \ + arm-none-eabi-gdb \ + minicom +``` + +## Other distros + +> **NOTE** `arm-none-eabi-gdb` is the GDB command you'll use to debug your ARM +> Cortex-M programs + +For distros that don't have packages for [ARM's pre-built +toolchain](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads), +download the "Linux 64-bit" file and put its `bin` directory on your path. +Here's one way to do it: + +``` console +$ mkdir -p ~/local && cd ~/local +$ tar xjf /path/to/downloaded/file/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 +``` + +Then, use your editor of choice to append to your `PATH` in the appropriate +shell init file (e.g. `~/.zshrc` or `~/.bashrc`): + +``` +PATH=$PATH:$HOME/local/gcc-arm-none-eabi-9-2020-q2-update/bin +``` + +## udev rules + +These rules let you use USB devices like the micro:bit without root privilege, i.e. `sudo`. + +Create this file in `/etc/udev/rules.d` with the content shown below. + +``` console +$ cat /etc/udev/rules.d/99-microbit.rules +``` + +``` text +# CMSIS-DAP for microbit +SUBSYSTEM=="usb", ATTR{idVendor}=="0d28", ATTR{idProduct}=="0204", MODE:="666" +``` + +Then reload the udev rules with: + +``` console +$ sudo udevadm control --reload-rules +``` + +If you had any board plugged to your computer, unplug them and then plug them in again. + +Now, go to the [next section]. + +[next section]: verify.md diff --git a/microbit/src/03-setup/macos.md b/microbit/src/03-setup/macos.md new file mode 100644 index 000000000..45f6a17d8 --- /dev/null +++ b/microbit/src/03-setup/macos.md @@ -0,0 +1,18 @@ +# macOS + +All the tools can be installed using [Homebrew]: + +[Homebrew]: http://brew.sh/ + +``` console +$ # Arm GCC toolchain +$ brew tap ArmMbed/homebrew-formulae +$ brew install arm-none-eabi-gcc + +$ # Minicom +$ brew install minicom +``` + +That's all! Go to the [next section]. + +[next section]: verify.md diff --git a/microbit/src/03-setup/memory.x b/microbit/src/03-setup/memory.x new file mode 100644 index 000000000..9e2ab65f6 --- /dev/null +++ b/microbit/src/03-setup/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/microbit/src/03-setup/src/main.rs b/microbit/src/03-setup/src/main.rs new file mode 100644 index 000000000..981cc40d6 --- /dev/null +++ b/microbit/src/03-setup/src/main.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +use panic_rtt_target as _; +use rtt_target::{rtt_init_print, rprintln}; + +use cortex_m_rt::entry; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("Hello World"); + loop {} +} diff --git a/microbit/src/03-setup/verify.md b/microbit/src/03-setup/verify.md new file mode 100644 index 000000000..363e9b401 --- /dev/null +++ b/microbit/src/03-setup/verify.md @@ -0,0 +1,82 @@ +# Verify the installation + +Let's verify that all the tools were installed correctly. + +## Linux only + +### Verify permissions + +Connect the micro:bit to your computer using a USB cable. + +The micro:bit should now appear as a USB device (file) in `/dev/bus/usb`. Let's find out how it got +enumerated: + +``` console +$ lsusb | grep -i "NXP ARM mbed" +Bus 001 Device 065: ID 0d28:0204 NXP ARM mbed +$ # ^^^ ^^^ +``` + +In my case, the micro:bit got connected to the bus #1 and got enumerated as the device #65. This means the +file `/dev/bus/usb/001/065` *is* the micro:bit. Let's check its permissions: + +``` console +$ ls -l /dev/bus/usb/001/065 +crw-rw-rw-. 1 root root 189, 64 Sep 5 14:27 /dev/bus/usb/001/065 +``` + +The permissions should be `crw-rw-rw-`. If it's not ... then check your [udev +rules] and try re-loading them with: + +[udev rules]: linux.md#udev-rules + +``` console +$ sudo udevadm control --reload-rules +``` + +# All + +## Verifying cargo-embed +First, connect the micro:bit to your Computer using a USB cable. + +At least an orange LED right next to the USB port of the micro:bit should light up. +Furthermore, if you have never flashed another program on to your micro:bit, the default +program the micro:bit ships with should start blinking the red LEDs on its back, you +can ignore them. + +Next up you will have to modify `Embed.toml` in the `src/03-setup` directory of the +book's source code. In the `default.general` section you will find two commented out +chip variants: + +```toml +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 +``` + +If you are working with the micro:bit v2 board uncomment the first, for the v1 +uncomment the second line. + +Next run one of these commands: + +``` +$ # make sure you are in src/03-setup of the books source code +$ # If you are working with micro:bit v2 +$ rustup target add thumbv7em-none-eabihf +$ cargo embed --target thumbv7em-none-eabihf + +$ # If you are working with micro:bit v1 +$ rustup target add thumbv6m-none-eabi +$ cargo embed --target thumbv6m-none-eabi +``` + +If everything works correctly cargo-embed should first compile the small example program +in this directory, then flash it and finally open a nice text based user interface that +prints Hello World. + +(If it does not, check out [general troubleshooting] instructions.) + +[general troubleshooting]: ../appendix/1-general-troubleshooting/index.html + +This output is coming from the small Rust program you just flashed on to your micro:bit. +Everything is working properly and you can continue with the next chapters! diff --git a/microbit/src/03-setup/windows.md b/microbit/src/03-setup/windows.md new file mode 100644 index 000000000..70913c83d --- /dev/null +++ b/microbit/src/03-setup/windows.md @@ -0,0 +1,23 @@ +# Windows + +## `arm-none-eabi-gdb` + +ARM provides `.exe` installers for Windows. Grab one from [here][gcc], and follow the instructions. +Just before the installation process finishes tick/select the "Add path to environment variable" +option. Then verify that the tools are in your `%PATH%`: + +``` console +$ arm-none-eabi-gcc -v +(..) +gcc version 5.4.1 20160919 (release) (..) +``` + +[gcc]: https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads + +## PuTTY + +Download the latest `putty.exe` from [this site] and place it somewhere in your `%PATH%`. + +[this site]: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html + +[next section]: verify.md diff --git a/microbit/src/04-meet-your-hardware/README.md b/microbit/src/04-meet-your-hardware/README.md new file mode 100644 index 000000000..0b06ee427 --- /dev/null +++ b/microbit/src/04-meet-your-hardware/README.md @@ -0,0 +1,34 @@ +# Meet your hardware + +Let's get familiar with the hardware we'll be working with. + +## micro:bit + +

+ +

+ +Here are some of the many components on the board: + +- A [microcontroller]. +- A number of LEDs, most notably the LED matrix on the back +- Two user buttons as well as a reset button (the one next to the USB port). +- One USB port. +- A sensor that is both a [magnetometer] and an [accelerometer] + +[microcontroller]: https://en.wikipedia.org/wiki/Microcontroller +[accelerometer]: https://en.wikipedia.org/wiki/Accelerometer +[magnetometer]: https://en.wikipedia.org/wiki/Magnetometer + +Of these components, the most important is the microcontroller (sometimes +shortened to "MCU" for "microcontroller unit"), which is the bigger of the two +black squares sitting on the side of the board with the USB port. The MCU is +what runs your code. You might sometimes read about "programming a board", when +in reality what we are doing is programming the MCU that is installed on the board. + +If you happen to be interested in a more in detail description of the board you +can checkout the [micro:bit website](https://tech.microbit.org/hardware/). + +Since the MCU is so important, let's take a closer look at the one sitting on our board. +Note that only one of the following two sections applies to your board, depending on whether +you are working with a micro:bit v2 or v1. diff --git a/microbit/src/04-meet-your-hardware/microbit-v1.md b/microbit/src/04-meet-your-hardware/microbit-v1.md new file mode 100644 index 000000000..81a5937da --- /dev/null +++ b/microbit/src/04-meet-your-hardware/microbit-v1.md @@ -0,0 +1,66 @@ +# Nordic nRF51822 (the "nRF51", micro:bit v1) + +Our MCU has 48 tiny metal **pins** sitting right underneath it (it's a so called [QFN48] chip). +These pins are connected to **traces**, the little "roads" that act as the wires connecting components +together on the board. The MCU can dynamically alter the electrical properties +of the pins. This works similar to a light switch altering how electrical +current flows through a circuit. By enabling or disabling electrical current to +flow through a specific pin, an LED attached to that pin (via the traces) can +be turned on and off. + +Each manufacturer uses a different part numbering scheme, but many will allow +you to determine information about a component simply by looking at the part +number. Looking at our MCU's part number (`N51822 QFAAH3 1951LN`, you probably cannot +see it with your bare eye, but it is on the chip), the `n` at the +front hints to us that this is a part manufactured by [Nordic Semiconductor]. +Looking up the part number on their website we quickly find the [product page]. +There we learn that our chip's main marketing point is that it is a +"Bluetooth Low Energy and 2.4 GHz SoC" (SoC being short for "System on a Chip"), +which explains the RF in the product name since RF is short for radio frequency. +If we search through the documentation of the chip linked on the [product page] +for a bit we find the [product specification] which contains chapter 10 "Ordering Information" +dedicated to explaining the weird chip naming. Here we learn that: + +[QFN48]: https://en.wikipedia.org/wiki/Flat_no-leads_package +[Nordic Semiconductor]: https://www.nordicsemi.com/ +[product page]: https://www.nordicsemi.com/products/nrf51822 +[product specification]: https://infocenter.nordicsemi.com/pdf/nRF51822_PS_v3.3.pdf + +- The `N51` is the MCU's series, indicating that there are other `nRF51` MCUs +- The `822` is the part code +- The `QF` is the package code, in this case short for `QFN48` +- The `AA` is the variant code, indicating how much RAM and flash memory the MCU has, + in our case 256 kilobyte flash and 16 kilobyte RAM +- The `H3` is the build code, indicating the hardware version (`H`) as well as the product configuration (`3`) +- The `1951LN` is a tracking code, hence it might differ on your chip + +The product specification does of course contain a lot more useful information about +the chip, for example that it is based on an ARM® Cortex™-M0 32-bit processor. + +### Arm? Cortex-M0? + +If our chip is manufactured by Nordic, then who is Arm? And if our chip is the +nRF51822, what is the Cortex-M0? + +You might be surprised to hear that while "Arm-based" chips are quite +popular, the company behind the "Arm" trademark ([Arm Holdings][]) doesn't +actually manufacture chips for purchase. Instead, their primary business +model is to just *design* parts of chips. They will then license those designs to +manufacturers, who will in turn implement the designs (perhaps with some of +their own tweaks) in the form of physical hardware that can then be sold. +Arm's strategy here is different from companies like Intel, which both +designs *and* manufactures their chips. + +Arm licenses a bunch of different designs. Their "Cortex-M" family of designs +are mainly used as the core in microcontrollers. For example, the Cortex-M0 +(the core our chip is based on) is designed for low cost and low power usage. +The Cortex-M7 is higher cost, but with more features and performance. + +Luckily, you don't need to know too much about different types of processors +or Cortex designs for the sake of this book. However, you are hopefully now a +bit more knowledgeable about the terminology of your device. While you are +working specifically with an nRF51822, you might find yourself reading +documentation and using tools for Cortex-M-based chips, as the nRF51822 is +based on a Cortex-M design. + +[Arm Holdings]: https://www.arm.com/ diff --git a/microbit/src/04-meet-your-hardware/microbit-v2.md b/microbit/src/04-meet-your-hardware/microbit-v2.md new file mode 100644 index 000000000..6c52287ec --- /dev/null +++ b/microbit/src/04-meet-your-hardware/microbit-v2.md @@ -0,0 +1,67 @@ +# Nordic nRF52833 (the "nRF52", micro:bit v2) + +Our MCU has 73 tiny metal **pins** sitting right underneath it (it's a so called [aQFN73] chip). +These pins are connected to **traces**, the little "roads" that act as the wires connecting components +together on the board. The MCU can dynamically alter the electrical properties +of the pins. This works similar to a light switch altering how electrical +current flows through a circuit. By enabling or disabling electrical current to +flow through a specific pin, an LED attached to that pin (via the traces) can +be turned on and off. + +Each manufacturer uses a different part numbering scheme, but many will allow +you to determine information about a component simply by looking at the part +number. Looking at our MCU's part number (`N52833 QIAAA0 2024AL`, you probably cannot +see it with your bare eye, but it is on the chip), the `n` at the +front hints to us that this is a part manufactured by [Nordic Semiconductor]. +Looking up the part number on their website we quickly find the [product page]. +There we learn that our chip's main marketing point is that it is a +"Bluetooth Low Energy and 2.4 GHz SoC" (SoC being short for "System on a Chip"), +which explains the RF in the product name since RF is short for radio frequency. +If we search through the documentation of the chip linked on the [product page] +for a bit we find the [product specification] which contains chapter 10 "Ordering Information" +dedicated to explaining the weird chip naming. Here we learn that: + +[aQFN73]: https://en.wikipedia.org/wiki/Flat_no-leads_package +[Nordic Semiconductor]: https://www.nordicsemi.com/ +[product page]: https://www.nordicsemi.com/products/nrf52833 +[product specification]: https://infocenter.nordicsemi.com/pdf/nRF52833_PS_v1.3.pdf + +- The `N52` is the MCU's series, indicating that there are other `nRF52` MCUs +- The `833` is the part code +- The `QI` is the package code, short for `aQFN73` +- The `AA` is the variant code, indicating how much RAM and flash memory the MCU has, + in our case 512 kilobyte flash and 128 kilobyte RAM +- The `A0` is the build code, indicating the hardware version (`A`) as well as the product configuration (`0`) +- The `2024AL` is a tracking code, hence it might differ on your chip + +The product specification does of course contain a lot more useful information about +the chip, for example that it is based on an ARM® Cortex™-M4 32-bit processor. + + +## Arm? Cortex-M4? + +If our chip is manufactured by Nordic, then who is Arm? And if our chip is the +nRF52833, what is the Cortex-M4? + +You might be surprised to hear that while "Arm-based" chips are quite +popular, the company behind the "Arm" trademark ([Arm Holdings][]) doesn't +actually manufacture chips for purchase. Instead, their primary business +model is to just *design* parts of chips. They will then license those designs to +manufacturers, who will in turn implement the designs (perhaps with some of +their own tweaks) in the form of physical hardware that can then be sold. +Arm's strategy here is different from companies like Intel, which both +designs *and* manufactures their chips. + +Arm licenses a bunch of different designs. Their "Cortex-M" family of designs +are mainly used as the core in microcontrollers. For example, the Cortex-M4 +(the core our chip is based on) is designed for low cost and low power usage. +The Cortex-M7 is higher cost, but with more features and performance. + +Luckily, you don't need to know too much about different types of processors +or Cortex designs for the sake of this book. However, you are hopefully now a +bit more knowledgeable about the terminology of your device. While you are +working specifically with an nRF52833, you might find yourself reading +documentation and using tools for Cortex-M-based chips, as the nRF52833 is +based on a Cortex-M design. + +[Arm Holdings]: https://www.arm.com/ diff --git a/microbit/src/04-meet-your-hardware/terminology.md b/microbit/src/04-meet-your-hardware/terminology.md new file mode 100644 index 000000000..a18e38bfd --- /dev/null +++ b/microbit/src/04-meet-your-hardware/terminology.md @@ -0,0 +1,72 @@ +# Rust Embedded terminology +Before we dive into programming the micro:bit let's have a quick look +at the libraries and terminology that will be important for all the +future chapters. + +## Abstraction layers +For any fully supported microcontroller/board with a microcontroller +you will usually hear the following terms being used for their levels +of abstraction: + +### Peripheral Access Crate (PAC) +The job of the PAC is to provide a safe (ish) direct interface to the +peripherals of the chip, allowing you to configure +every last bit however you want (of course also in wrong ways). Usually +you only ever have to deal with the PAC if either the layers that are +higher up don't fulfill your needs or when you are developing them. +The PAC we are (implicitly) going to use is either the one for the [nRF52] +or for the [nRF51]. + +### The Hardware Abstraction Layer (HAL) +The job of the HAL is to build up on top of +the chip's PAC and provide an abstraction that is actually usable for +someone who does not know about all the special behaviour of this chip. +Usually they abstract whole peripherals away into single structs that can +for example be used to send data around via the peripheral. We are +going to use the [nRF52-hal] or the [nRF51-hal] respectively. + +### The Board Support Crate (historically called Board Support Package, or BSP) +The job of the BSP is to abstract a whole board +(such as the micro:bit) away at once. That means it has to provide +abstractions to use both the microcontroller as well as the sensors, +LEDs etc. that might be present on the board. Quite often (especially +with custom-made boards) you will just be working with a HAL for the +chip and build the drivers for the sensors either yourself or +search for them on crates.io. Luckily for us though, the micro:bit +does actually have a [BSP] so we are going to use that on top of our +HAL as well. + +[nrF52]: https://crates.io/crates/nrf52833-pac +[nrF51]: https://crates.io/crates/nrf51 +[nrF52-hal]: https://crates.io/crates/nrf52833-hal +[nrF51-hal]: https://crates.io/crates/nrf51-hal +[BSP]: https://crates.io/crates/microbit + +## Unifying the layers + +Next we are going to have a look at a very central piece of software +in the Rust Embedded world: [`embedded-hal`]. As its name suggests it +relates to the 2nd level of abstraction we got to know: the HALs. +The idea behind [`embedded-hal`] is to provide a set of traits that +describe behaviour which is usually shared across all implementations +of a specific peripheral in all the HALs. For example one would always +expect to have functions that are capable of turning the power on a pin +either on or off. For example to switch an LED on and off on the board. +This allows us to write a driver for, say a temperature sensor, that +can be used on any chip for which an implementation of the [`embedded-hal`] traits exists, +simply by writing the driver in such a way that it only relies on the +[`embedded-hal`] traits. Drivers that are written in such a way are called +platform agnostic and luckily for us most of the drivers on crates.io +are actually platform agnostic. + +[`embedded-hal`]: https://crates.io/crates/embedded-hal + + +## Further reading + +If you want to learn more about these levels of abstraction, Franz Skarman, +a.k.a. [TheZoq2], held a talk about this topic during Oxidize 2020, called +[An Overview of the Embedded Rust Ecosystem]. + +[TheZoq2]: https://github.com/TheZoq2/ +[An Overview of the Embedded Rust Ecosystem]: https://www.youtube.com/watch?v=vLYit_HHPaY diff --git a/microbit/src/05-led-roulette/.cargo/config b/microbit/src/05-led-roulette/.cargo/config new file mode 100644 index 000000000..6260c5718 --- /dev/null +++ b/microbit/src/05-led-roulette/.cargo/config @@ -0,0 +1,4 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] diff --git a/microbit/src/05-led-roulette/Cargo.toml b/microbit/src/05-led-roulette/Cargo.toml new file mode 100644 index 000000000..7c24ba408 --- /dev/null +++ b/microbit/src/05-led-roulette/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "led-roulette" +version = "0.1.0" +authors = ["Henrik Böving "] +edition = "2018" + +[dependencies.microbit-v2] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + + +[dependencies.microbit] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + +[dependencies] +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +panic-halt = "0.2.0" +#rtt-target = { version = "0.3.1", features = ["cortex-m"] } +#panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } + +[features] +v2 = ["microbit-v2"] +v1 = ["microbit"] diff --git a/microbit/src/05-led-roulette/Embed.toml b/microbit/src/05-led-roulette/Embed.toml new file mode 100644 index 000000000..22375a6fd --- /dev/null +++ b/microbit/src/05-led-roulette/Embed.toml @@ -0,0 +1,12 @@ +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 + +[default.reset] +halt_afterwards = true + +[default.rtt] +enabled = false + +[default.gdb] +enabled = true diff --git a/microbit/src/05-led-roulette/README.md b/microbit/src/05-led-roulette/README.md new file mode 100644 index 000000000..d9f86e5a8 --- /dev/null +++ b/microbit/src/05-led-roulette/README.md @@ -0,0 +1,59 @@ +# LED roulette + +Alright, let's start by building the following application: + +

+

+ +I'm going to give you a high level API to implement this app but don't worry we'll do low level +stuff later on. The main goal of this chapter is to get familiar with the *flashing* and debugging +process. + +The starter code is in the `src` directory of the book repository. Inside that directory there are more +directories named after each chapter of this book. Most of those directories are starter Cargo +projects. + +Now, jump into the `src/05-led-roulette` directory. Check the `src/main.rs` file: + +``` rust +{{#include src/main.rs}} +``` + +Microcontroller programs are different from standard programs in two aspects: `#![no_std]` and +`#![no_main]`. + +The `no_std` attribute says that this program won't use the `std` crate, which assumes an underlying +OS; the program will instead use the `core` crate, a subset of `std` that can run on bare metal +systems (i.e., systems without OS abstractions like files and sockets). + +The `no_main` attribute says that this program won't use the standard `main` interface, which is +tailored for command line applications that receive arguments. Instead of the standard `main` we'll +use the `entry` attribute from the [`cortex-m-rt`] crate to define a custom entry point. In this +program we have named the entry point "main", but any other name could have been used. The entry +point function must have signature `fn() -> !`; this type indicates that the function can't return +-- this means that the program never terminates. + +[`cortex-m-rt`]: https://crates.io/crates/cortex-m-rt + +If you are a careful observer, you'll also notice there is a `.cargo` directory in the Cargo project +as well. This directory contains a Cargo configuration file (`.cargo/config`) that tweaks the +linking process to tailor the memory layout of the program to the requirements of the target device. +This modified linking process is a requirement of the `cortex-m-rt` crate. + +Furthermore, there is also an `Embed.toml` file + +```toml +{{#include Embed.toml}} +``` + +This file tells `cargo-embed` that: + +* we are working with either a nrf52833 or nrf51822, you will again have to remove the comments from the + chip you are using, just like you did in chapter 3. +* we want to halt the chip after we flashed it so our program does not instantly jump to the loop +* we want to disable RTT, RTT being a protocol that allows the chip to send text to a debugger. + You have in fact already seen RTT in action, it was the protocol that sent "Hello World" in chapter 3. +* we want to enable GDB, this will be required for the debugging procedure + +Alright, let's start by building this program. diff --git a/microbit/src/05-led-roulette/build-it.md b/microbit/src/05-led-roulette/build-it.md new file mode 100644 index 000000000..2f85bfa22 --- /dev/null +++ b/microbit/src/05-led-roulette/build-it.md @@ -0,0 +1,120 @@ +# Build it + +The first step is to build our "binary" crate. Because the microcontroller has a different +architecture than your computer we'll have to cross compile. Cross compiling in Rust land is as simple +as passing an extra `--target` flag to `rustc`or Cargo. The complicated part is figuring out the +argument of that flag: the *name* of the target. + +As we already know the microcontroller on the micro:bit v2 has a Cortex-M4F processor in it, the one on v1 a Cortex-M0. +`rustc` knows how to cross-compile to the Cortex-M architecture and provides several different targets that cover the different processors +families within that architecture: + +- `thumbv6m-none-eabi`, for the Cortex-M0 and Cortex-M1 processors +- `thumbv7m-none-eabi`, for the Cortex-M3 processor +- `thumbv7em-none-eabi`, for the Cortex-M4 and Cortex-M7 processors +- `thumbv7em-none-eabihf`, for the Cortex-M4**F** and Cortex-M7**F** processors +- `thumbv8m.main-none-eabi`, for the Cortex-M33 and Cortex-M35P processors +- `thumbv8m.main-none-eabihf`, for the Cortex-M33**F** and Cortex-M35P**F** processors + +For the micro:bit v2, we'll use the `thumbv7em-none-eabihf` target, for v1 the `thumbv6m-none-eabi` one. +Before cross-compiling you have to download a pre-compiled version of the standard library +(a reduced version of it, actually) for your target. That's done using `rustup`: + +``` console +# For micro:bit v2 +$ rustup target add thumbv7em-none-eabihf +# For micro:bit v1 +$ rustup target add thumbv6m-none-eabi +``` + +You only need to do the above step once; `rustup` will re-install a new standard library +(`rust-std` component) whenever you update your toolchain. Therefore you can skip this step, if you have already added the necessary target +while [verifying your setup]. + +[veryfing your setup]: ../03-setup/verify.html#verifying-cargo-embed + + +With the `rust-std` component in place you can now cross compile the program using Cargo: + +``` console +# make sure you are in the `src/05-led-roulette` directory + +# For micro:bit v2 +$ cargo build --features v2 --target thumbv7em-none-eabihf + Compiling semver-parser v0.7.0 + Compiling typenum v1.12.0 + Compiling cortex-m v0.6.3 + (...) + Compiling microbit-v2 v0.10.1 + Finished dev [unoptimized + debuginfo] target(s) in 33.67s + +# For micro:bit v1 +$ cargo build --features v1 --target thumbv6m-none-eabi + Compiling fixed v1.2.0 + Compiling syn v1.0.39 + Compiling cortex-m v0.6.3 + (...) + Compiling microbit v0.10.1 + Finished dev [unoptimized + debuginfo] target(s) in 22.73s +``` + +> **NOTE** Be sure to compile this crate *without* optimizations. The provided Cargo.toml +> file and build command above will ensure optimizations are off. + +OK, now we have produced an executable. This executable won't blink any LEDs, +it's just a simplified version that we will build upon later in the chapter. +As a sanity check, let's verify that the produced executable is actually an ARM binary: + +``` console +# For micro:bit v2 +# equivalent to `readelf -h target/thumbv7em-none-eabihf/debug/led-roulette` +$ cargo readobj --features v2 --target thumbv7em-none-eabihf --bin led-roulette -- --file-headers + Finished dev [unoptimized + debuginfo] target(s) in 0.01s +ELF Header: + Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 + Class: ELF32 + Data: 2's complement, little endian + Version: 1 (current) + OS/ABI: UNIX - System V + ABI Version: 0 + Type: EXEC (Executable file) + Machine: ARM + Version: 0x1 + Entry point address: 0x117 + Start of program headers: 52 (bytes into file) + Start of section headers: 793112 (bytes into file) + Flags: 0x5000400 + Size of this header: 52 (bytes) + Size of program headers: 32 (bytes) + Number of program headers: 4 + Size of section headers: 40 (bytes) + Number of section headers: 21 + Section header string table index: 19 + +# For micro:bit v1 +# equivalent to `readelf -h target/thumbv6m-none-eabi/debug/led-roulette` +$ cargo readobj --features v1 --target thumbv6m-none-eabi --bin led-roulette -- --file-headers + Finished dev [unoptimized + debuginfo] target(s) in 0.01s +ELF Header: + Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 + Class: ELF32 + Data: 2's complement, little endian + Version: 1 (current) + OS/ABI: UNIX - System V + ABI Version: 0 + Type: EXEC (Executable file) + Machine: ARM + Version: 0x1 + Entry point address: 0xC1 + Start of program headers: 52 (bytes into file) + Start of section headers: 693196 (bytes into file) + Flags: 0x5000200 + Size of this header: 52 (bytes) + Size of program headers: 32 (bytes) + Number of program headers: 4 + Size of section headers: 40 (bytes) + Number of section headers: 22 + Section header string table index: 20 +``` + +Next, we'll flash the program into our microcontroller. diff --git a/microbit/src/05-led-roulette/build.rs b/microbit/src/05-led-roulette/build.rs new file mode 100644 index 000000000..c8d8c9141 --- /dev/null +++ b/microbit/src/05-led-roulette/build.rs @@ -0,0 +1,30 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory (wherever `Cargo.toml` is). However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! a rebuild of the application with new memory settings is ensured after updating `memory.x`. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/microbit/src/05-led-roulette/debug-it.md b/microbit/src/05-led-roulette/debug-it.md new file mode 100644 index 000000000..ef84f9e29 --- /dev/null +++ b/microbit/src/05-led-roulette/debug-it.md @@ -0,0 +1,236 @@ +# Debug it +## How does this even work? +Before we debug our little program let's take a moment to quickly understand what is actually +happening here. In the previous chapter we already discussed the purpose of the second chip +on the board as well as how it talks to our computer, but how can we actually use it? + +The little option `default.gb.enabled = true` in `Embed.toml` made `cargo-embed` open a so-called "GDB stub" after flashing, +this is a server that our GDB can connect to and send commands like "set a breakpoint at address X" to. The server can then decide +on its own how to handle this command. In the case of the `cargo-embed` GDB stub it will forward the +command to the debugging probe on the board via USB which then does the job of actually talking to the +MCU for us. + +## Let's debug! + +Since `cargo-embed` is blocking our current shell we can simply open a new one and cd back into our project +directory. Once we are there we first have to open the binary in gdb like this: + +```shell +# For micro:bit v2 +$ gdb target/thumbv7em-none-eabihf/debug/led-roulette + +# For micro:bit v1 +$ gdb target/thumbv6m-none-eabi/debug/led-roulette +``` + +> **NOTE** Depending on which GDB you installed you will have to use a different command to launch it, +> check out [chapter 3] if you forgot which one it was. + +[chapter 3]: ../03-setup/index.md#tools + +> **NOTE**: If `cargo-embed` prints a lot of warnings here don't worry about it. As of now it does not fully +> implement the GDB protocol and thus might not recognize all the commands your GDB is sending to it, +> as long as it does not crash, you are fine. + +Next we will have to connect to the GDB stub. It runs on `localhost:1337` per default so in order to +connect to it run the following: + +```shell +(gdb) target remote :1337 +Remote debugging using :1337 +0x00000116 in nrf52833_pac::{{impl}}::fmt (self=0xd472e165, f=0x3c195ff7) at /home/nix/.cargo/registry/src/github.com-1ecc6299db9ec823/nrf52833-pac-0.9.0/src/lib.rs:157 +157 #[derive(Copy, Clone, Debug)] +``` + +Next what we want to do is get to the main function of our program. +We will do this by first setting a breakpoint there and the continuing +program execution until we hit the breakpoint: + +``` +(gdb) break main +Breakpoint 1 at 0x104: file src/05-led-roulette/src/main.rs, line 9. +Note: automatically using hardware breakpoints for read-only addresses. +(gdb) continue +Continuing. + +Breakpoint 1, led_roulette::__cortex_m_rt_main_trampoline () at src/05-led-roulette/src/main.rs:9 +9 #[entry] +``` + +Breakpoints can be used to stop the normal flow of a program. The `continue` command will let the +program run freely *until* it reaches a breakpoint. In this case, until it reaches the `main` +function because there's a breakpoint there. + +Note that GDB output says "Breakpoint 1". Remember that our processor can only use a limited amount of these +breakpoints, so it's a good idea to pay attention to these messages. If you happen to run out of breakpoints, +you can list all the current ones with `info break` and delete desired ones with `delete `. + +For a nicer debugging experience, we'll be using GDB's Text User Interface (TUI). To enter into that +mode, on the GDB shell enter the following command: + +``` +(gdb) layout src +``` + +> **NOTE** Apologies Windows users. The GDB shipped with the GNU ARM Embedded Toolchain doesn't +> support this TUI mode `:-(`. + +![GDB session](../assets/gdb-layout-src.png "GDB TUI") + +GDB's break command does not only work for function names, it can also break at certain line numbers. +If we wanted to break in line 13 we can simply do: + +``` +(gdb) break 13 +Breakpoint 2 at 0x110: file src/05-led-roulette/src/main.rs, line 13. +(gdb) continue +Continuing. + +Breakpoint 2, led_roulette::__cortex_m_rt_main () at src/05-led-roulette/src/main.rs:13 +(gdb) +``` +At any point you can leave the TUI mode using the following command: + +``` +(gdb) tui disable +``` + +We are now "on" the `_y = x` statement; that statement hasn't been executed yet. This means that `x` +is initialized but `_y` is not. Let's inspect those stack/local variables using the `print` command: + +``` +(gdb) print x +$1 = 42 +(gdb) print &x +$2 = (*mut i32) 0x20003fe8 +(gdb) +``` + +As expected, `x` contains the value `42`. The command `print &x` prints the address of the variable `x`. +The interesting bit here is that GDB output shows the type of the reference: `i32*`, a pointer to an `i32` value. + +If we want to continue the program execution line by line we can do that using the `next` command +so let's proceed to the `loop {}` statement: + +``` +(gdb) next +16 loop {} +``` + +And `_y` should now be initialized. + +``` +(gdb) print _y +$5 = 42 +``` + +Instead of printing the local variables one by one, you can also use the `info locals` command: + +``` +(gdb) info locals +x = 42 +_y = 42 +(gdb) +``` + +If we use `next` again on top of the `loop {}` statement, we'll get stuck because the program will +never pass that statement. Instead, we'll switch to the disassemble view with the `layout asm` +command and advance one instruction at a time using `stepi`. You can always switch back into Rust +source code view later by issuing the `layout src` command again. + +> **NOTE**: If you used the `next` or `continue` command by mistake and GDB got stuck, you can get unstuck by hitting `Ctrl+C`. + +``` +(gdb) layout asm +``` + +![GDB session](../assets/gdb-layout-asm.png "GDB disassemble") + +If you are not using the TUI mode, you can use the `disassemble /m` command to disassemble the +program around the line you are currently at. + +``` +(gdb) disassemble /m +Dump of assembler code for function _ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E: +10 fn main() -> ! { + 0x0000010a <+0>: sub sp, #8 + 0x0000010c <+2>: movs r0, #42 ; 0x2a + +11 let _y; +12 let x = 42; + 0x0000010e <+4>: str r0, [sp, #0] + +13 _y = x; + 0x00000110 <+6>: str r0, [sp, #4] + +14 +15 // infinite loop; just so we don't leave this stack frame +16 loop {} +=> 0x00000112 <+8>: b.n 0x114 <_ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E+10> + 0x00000114 <+10>: b.n 0x114 <_ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E+10> + +End of assembler dump. +``` + +See the fat arrow `=>` on the left side? It shows the instruction the processor will execute next. + +If not inside the TUI mode on each `stepi` command GDB will print the statement and the line number +of the instruction the processor will execute next. + +``` +(gdb) stepi +16 loop {} +(gdb) stepi +16 loop {} +``` + +One last trick before we move to something more interesting. Enter the following commands into GDB: + +``` +(gdb) monitor reset +(gdb) c +Continuing. + +Breakpoint 1, led_roulette::__cortex_m_rt_main_trampoline () at src/05-led-roulette/src/main.rs:9 +9 #[entry] +(gdb) +``` + +We are now back at the beginning of `main`! + +`monitor reset` will reset the microcontroller and stop it right at the program entry point. +The following `continue` command will let the program run freely until it reaches the `main` +function that has a breakpoint on it. + +This combo is handy when you, by mistake, skipped over a part of the program that you were +interested in inspecting. You can easily roll back the state of your program back to its very +beginning. + +> **The fine print**: This `reset` command doesn't clear or touch RAM. That memory will retain its +> values from the previous run. That shouldn't be a problem though, unless your program behavior +> depends on the value of *uninitialized* variables but that's the definition of Undefined Behavior +> (UB). + +We are done with this debug session. You can end it with the `quit` command. + +``` +(gdb) quit +A debugging session is active. + + Inferior 1 [Remote target] will be detached. + +Quit anyway? (y or n) y +Detaching from program: $PWD/target/thumbv7em-none-eabihf/debug/led-roulette, Remote target +Ending remote debugging. +[Inferior 1 (Remote target) detached] +``` + +> **NOTE** If the default GDB CLI is not to your liking check out [gdb-dashboard]. It uses Python to +> turn the default GDB CLI into a dashboard that shows registers, the source view, the assembly view +> and other things. + +[gdb-dashboard]: https://github.com/cyrus-and/gdb-dashboard#gdb-dashboard + +If you want to learn more about what GDB can do, check out the section [How to use GDB](../appendix/2-how-to-use-gdb/). + +What's next? The high level API I promised. diff --git a/microbit/src/05-led-roulette/flash-it.md b/microbit/src/05-led-roulette/flash-it.md new file mode 100644 index 000000000..ca86b4e86 --- /dev/null +++ b/microbit/src/05-led-roulette/flash-it.md @@ -0,0 +1,48 @@ +# Flash it + +Flashing is the process of moving our program into the microcontroller's (persistent) memory. Once +flashed, the microcontroller will execute the flashed program every time it is powered on. + +In this case, our `led-roulette` program will be the *only* program in the microcontroller memory. +By this I mean that there's nothing else running on the microcontroller: no OS, no "daemon", +nothing. `led-roulette` has full control over the device. + +Flashing the binary itself is quite simple thanks to `cargo embed`. + +Before executing that command though, let's look into what it actually does. If you look at the side of your micro:bit +with the USB connector facing upwards you will notice, that there are actually 2 black squares on there +(on the micro:bit v2 there is a third and biggest one, which is a speaker), one is our MCU +we already talked about but what purpose does the other one serve? The other chip has 3 main purposes: + +1. Provide power from the USB connector to our MCU +2. Provide a serial to USB bridge for our MCU (we will look into that in a later chapter) +3. Being a programmer/debugger (this is the relevant purpose for now) + +Basically this chip acts as a bridge between our computer (to which it is connected via USB) and the MCU (to which it is +connected via traces and communicates with using the SWD protocol). This bridge enables us to flash new binaries on to +the MCU, inspect its state via a debugger and other things. + +So lets flash it! + +```console +# For micro:bit v2 +$ cargo embed --features v2 --target thumbv7em-none-eabihf + (...) + Erasing sectors ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 4.21KiB/s (eta 0s ) + Programming pages ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 2.71KiB/s (eta 0s ) + Finished flashing in 0.608s + +# For micro:bit v1 +$ cargo embed --features v1 --target thumbv6m-none-eabi + (...) + Erasing sectors ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 4.14KiB/s (eta 0s ) + Programming pages ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 2.69KiB/s (eta 0s ) + Finished flashing in 0.614s +``` + + +You will notice that `cargo-embed` blocks after outputting the last line, this is intended and you should not close it +since we need it in this state for the next step: debugging it! Furthermore, you will have noticed that the `cargo build` +and `cargo embed` are actually passed the same flags, this is because `cargo embed` actually executes the build and then +flashes the resulting binary on to the chip, hence you can leave out the `cargo build` step in the future if you +want to flash your code right away. diff --git a/microbit/src/05-led-roulette/it-blinks.md b/microbit/src/05-led-roulette/it-blinks.md new file mode 100644 index 000000000..4fe091636 --- /dev/null +++ b/microbit/src/05-led-roulette/it-blinks.md @@ -0,0 +1,109 @@ +# It blinks + +## Delaying +Now we're going to take a brief look into delay abstractions provided by `embedded-hal` +before combining this with the GPIO abstractions from the previous chapter in order to +finally make an LED blink. + +`embedded-hal` provides us with two abstractions to delay the execution of our program: +[`DelayUs`] and [`DelayMs`]. Both of them essentially work the exact same way except +that they accept different units for their delay function. + +[`DelayUs`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/blocking/delay/trait.DelayUs.html +[`DelayMs`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/blocking/delay/trait.DelayMs.html + +Inside our MCU, several so-called "timers" exist. They can do various things regarding time for us, +including simply pausing the execution of our program for a fixed amount of time. A very +simple delay-based program that prints something every second might for example look like this: + +```rs +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::{rtt_init_print, rprintln}; +use panic_rtt_target as _; +use microbit::board::Board; +use microbit::hal::timer::Timer; +use microbit::hal::prelude::*; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let mut board = Board::take().unwrap(); + + let mut timer = Timer::new(board.TIMER0); + + loop { + timer.delay_ms(1000u16); + rprintln!("1000 ms passed"); + } +} +``` + +Note that we changed our panic implementation from `panic_halt` to +`panic_rtt_target` here. This will require you to uncomment the two +RTT lines from `Cargo.toml` and comment the `panic-halt` one out, +since Rust only allows one panic implementation at a time. + +In order to actually see the prints we have to change `Embed.toml` like this: +``` +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 + +[default.reset] +halt_afterwards = false + +[default.rtt] +enabled = true + +[default.gdb] +enabled = false +``` + +And now after putting the code into `src/main.rs` and another quick `cargo embed` (again with the same flags you used before) +you should see "`1000 ms passed`" being sent to your console every second from your MCU. + +## Blinking + +Now we've arrived at the point where we can combine our new knowledge about GPIO and delay abstractions +in order to actually make an LED on the back of the micro:bit blink. The resulting program is really just +a mash-up of the one above and the one that turned an LED on in the last section and looks like this: + +```rs +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::{rtt_init_print, rprintln}; +use panic_rtt_target as _; +use microbit::board::Board; +use microbit::hal::timer::Timer; +use microbit::hal::prelude::*; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let mut board = Board::take().unwrap(); + + let mut timer = Timer::new(board.TIMER0); + + board.display_pins.col1.set_low().unwrap(); + let mut row1 = board.display_pins.row1; + + loop { + row1.set_low().unwrap(); + rprintln!("Dark!"); + timer.delay_ms(1_000_u16); + row1.set_high().unwrap(); + rprintln!("Light!"); + timer.delay_ms(1_000_u16); + } +} +``` + +And after putting the code into `src/main.rs` and a final `cargo embed` (with the proper flags) +you should see the LED we light up before blinking as well as a print, every time the LED changes from off to on and vice versa. diff --git a/microbit/src/05-led-roulette/light-it-up.md b/microbit/src/05-led-roulette/light-it-up.md new file mode 100644 index 000000000..2428509fc --- /dev/null +++ b/microbit/src/05-led-roulette/light-it-up.md @@ -0,0 +1,91 @@ +# Light it up +## embedded-hal + +In this chapter we are going to make one of the many LEDs on the back of the micro:bit light up since this is +basically the "Hello World" of embedded programming. In order to get this task done we will use one of the traits +provided by `embedded-hal`, specifically the [`OutputPin`] trait which allows us to turn a pin on or off. + +[`OutputPin`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/digital/v2/trait.OutputPin.html + +## The micro:bit LEDs + +On the back of the micro:bit you can see a 5x5 square of LEDs, usually called an LED matrix. This matrix alignment is +used so that instead of having to use 25 separate pins to drive every single one of the LEDs, we can just use 10 (5+5) pins in +order to control which column and which row of our matrix lights up. + +> **NOTE** that the micro:bit v1 team implemented this a little differently. Their [schematic page] says +> that it is actually implemented as a 3x9 matrix but a few columns simply remain unused. + +Usually in order to determine which specific pins we have to control in +order to light a specific LED up we would now have to read the +[micro:bit v2 schematic] or the [micro:bit v1 schematic] respectively. +Luckily for us though we can use the aforementioned micro:bit BSP +which abstracts all of this nicely away from us. + +[schematic page]: https://tech.microbit.org/hardware/schematic/ +[micro:bit v2 schematic]: https://github.com/microbit-foundation/microbit-v2-hardware/blob/main/V2/MicroBit_V2.0.0_S_schematic.PDF +[micro:bit v1 schematic]: https://github.com/bbcmicrobit/hardware/blob/master/V1.5/SCH_BBC-Microbit_V1.5.PDF + +## Actually lighting it up! + +The code required to light up an LED in the matrix is actually quite simple but it requires a bit of setup. First take +a look at it and then we can go through it step by step: + +```rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_halt as _; +use microbit::board::Board; +use microbit::hal::prelude::*; + +#[entry] +fn main() -> ! { + let mut board = Board::take().unwrap(); + + board.display_pins.col1.set_low().unwrap(); + board.display_pins.row1.set_high().unwrap(); + + loop {} +} +``` + +The first few lines until the main function just do some basic imports and setup we already looked at before. +However, the main function looks pretty different to what we have seen up to now. + +The first line is related to how most HALs written in Rust work internally. +As discussed before they are built on top of PAC crates which own (in the Rust sense) +all the peripherals of a chip. `let mut board = Board::take().unwrap();` basically takes all +these peripherals from the PAC and binds them to a variable. In this specific case we are +not only working with a HAL but with an entire BSP, so this also takes ownership +of the Rust representation of the other chips on the board. + +> **NOTE**: If you are wondering why we have to call `unwrap()` here, in theory it is possible for `take()` to be called +> more than once. This would lead to the peripherals being represented by two separate variables and thus lots of +> possible confusing behaviour because two variables modify the same resource. In order to avoid this, PACs are +> implemented in a way that it would panic if you tried to take the peripherals twice. + +Now we can light the LED connected to `row1`, `col1` up by setting the `row1` pin to high (i.e. switching it on). +The reason we can leave `col1` set to low is because of how the LED matrix circuit works. Furthermore, `embedded-hal` is +designed in a way that every operation on hardware can possibly return an error, even just toggling a pin on or off. Since +that is highly unlikely in our case, we can just `unwrap()` the result. + +## Testing it + +Testing our little program is quite simple. First put it into `src/mains.rs`. Afterwards we simply have to run the +`cargo embed` command from the last section again, let it flash and just like before. Then open our GDB and connect +to the GDB stub: + +``` +$ # Your GDB debug command from the last section +(gdb) target remote :1337 +Remote debugging using :1337 +cortex_m_rt::Reset () at /home/nix/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.12/src/lib.rs:489 +489 pub unsafe extern "C" fn Reset() -> ! { +(gdb) +``` + +If we now let the program run via the GDB `continue` command, one of the LEDs on the back of the micro:bit should light +up. diff --git a/microbit/src/05-led-roulette/memory.x b/microbit/src/05-led-roulette/memory.x new file mode 100644 index 000000000..9e2ab65f6 --- /dev/null +++ b/microbit/src/05-led-roulette/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/microbit/src/05-led-roulette/my-solution.md b/microbit/src/05-led-roulette/my-solution.md new file mode 100644 index 000000000..a37ba2731 --- /dev/null +++ b/microbit/src/05-led-roulette/my-solution.md @@ -0,0 +1,190 @@ +# My solution + +What solution did you come up with? + +Here's mine, it's probably one of the simplest (but of course not most +beautiful) way to generate the required matrix: + +``` rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; +use microbit::{ + board::Board, + display::blocking::Display, + hal::Timer, +}; + +const PIXELS: [(usize, usize); 16] = [ + (0,0), (0,1), (0,2), (0,3), (0,4), (1,4), (2,4), (3,4), (4,4), + (4,3), (4,2), (4,1), (4,0), (3,0), (2,0), (1,0) +]; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + + let board = Board::take().unwrap(); + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + let mut leds = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ]; + + let mut last_led = (0,0); + + loop { + for current_led in PIXELS.iter() { + leds[last_led.0][last_led.1] = 0; + leds[current_led.0][current_led.1] = 1; + display.show(&mut timer, leds, 30); + last_led = *current_led; + } + } +} +``` + +One more thing! Check that your solution also works when compiled in "release" mode: + +``` console +# For micro:bit v2 +$ cargo embed --features v2 --target thumbv7em-none-eabihf --release + (...) + +# For micro:bit v1 +$ cargo embed --features v1 --target thumbv6m-none-eabi --release + (...) +``` + +If you want to debug your "release" mode binary you'll have to use a different GDB command: + +``` console +# For micro:bit v2 +$ gdb target/thumbv7em-none-eabihf/release/led-roulette + +# For micro:bit v1 +$ gdb target/thumbv6m-none-eabi/release/led-roulette +``` + +Binary size is something we should always keep an eye on! How big is your solution? You can check +that using the `size` command on the release binary: + +``` console +# For micro:bit v2 +$ cargo size --features v2 --target thumbv7em-none-eabihf -- -A + Finished dev [unoptimized + debuginfo] target(s) in 0.02s +led-roulette : +section size addr +.vector_table 256 0x0 +.text 26984 0x100 +.rodata 2732 0x6a68 +.data 0 0x20000000 +.bss 1092 0x20000000 +.uninit 0 0x20000444 +.debug_abbrev 33941 0x0 +.debug_info 494113 0x0 +.debug_aranges 23528 0x0 +.debug_ranges 130824 0x0 +.debug_str 498781 0x0 +.debug_pubnames 143351 0x0 +.debug_pubtypes 124464 0x0 +.ARM.attributes 58 0x0 +.debug_frame 69128 0x0 +.debug_line 290580 0x0 +.debug_loc 1449 0x0 +.comment 109 0x0 +Total 1841390 + + +$ cargo size --features v2 --target thumbv7em-none-eabihf --release -- -A + Finished release [optimized + debuginfo] target(s) in 0.02s +led-roulette : +section size addr +.vector_table 256 0x0 +.text 6332 0x100 +.rodata 648 0x19bc +.data 0 0x20000000 +.bss 1076 0x20000000 +.uninit 0 0x20000434 +.debug_loc 9036 0x0 +.debug_abbrev 2754 0x0 +.debug_info 96460 0x0 +.debug_aranges 1120 0x0 +.debug_ranges 11520 0x0 +.debug_str 71325 0x0 +.debug_pubnames 32316 0x0 +.debug_pubtypes 29294 0x0 +.ARM.attributes 58 0x0 +.debug_frame 2108 0x0 +.debug_line 19303 0x0 +.comment 109 0x0 +Total 283715 + +# micro:bit v1 +$ cargo size --features v1 --target thumbv6m-none-eabi -- -A + Finished dev [unoptimized + debuginfo] target(s) in 0.02s +led-roulette : +section size addr +.vector_table 168 0x0 +.text 28584 0xa8 +.rodata 2948 0x7050 +.data 0 0x20000000 +.bss 1092 0x20000000 +.uninit 0 0x20000444 +.debug_abbrev 30020 0x0 +.debug_info 373392 0x0 +.debug_aranges 18344 0x0 +.debug_ranges 89656 0x0 +.debug_str 375887 0x0 +.debug_pubnames 115633 0x0 +.debug_pubtypes 86658 0x0 +.ARM.attributes 50 0x0 +.debug_frame 54144 0x0 +.debug_line 237714 0x0 +.debug_loc 1499 0x0 +.comment 109 0x0 +Total 1415898 + +$ cargo size --features v1 --target thumbv6m-none-eabi --release -- -A + Finished release [optimized + debuginfo] target(s) in 0.02s +led-roulette : +section size addr +.vector_table 168 0x0 +.text 4848 0xa8 +.rodata 648 0x1398 +.data 0 0x20000000 +.bss 1076 0x20000000 +.uninit 0 0x20000434 +.debug_loc 9705 0x0 +.debug_abbrev 3235 0x0 +.debug_info 61908 0x0 +.debug_aranges 1208 0x0 +.debug_ranges 5784 0x0 +.debug_str 57358 0x0 +.debug_pubnames 22959 0x0 +.debug_pubtypes 18891 0x0 +.ARM.attributes 50 0x0 +.debug_frame 2316 0x0 +.debug_line 18444 0x0 +.comment 19 0x0 +Total 208617 + +``` + +> **NOTE** The Cargo project is already configured to build the release binary using LTO. + +Know how to read this output? The `text` section contains the program instructions. On the other hand, +the `data` and `bss` sections contain variables statically allocated in RAM (`static` variables). +If you remember back in the specification of the microcontroller on your micro:bit, you should +notice that its flash memory is actually far too small to contain this binary, so how is this possible? +As we can see from the size statistics most of the binary is actually made up of debugging related +sections, those are however not flashed to the microcontroller at any time, after all they aren't +relevant for the execution. diff --git a/microbit/src/05-led-roulette/src/main.rs b/microbit/src/05-led-roulette/src/main.rs new file mode 100644 index 000000000..bb8c08ca6 --- /dev/null +++ b/microbit/src/05-led-roulette/src/main.rs @@ -0,0 +1,17 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_halt as _; +use microbit as _; + +#[entry] +fn main() -> ! { + let _y; + let x = 42; + _y = x; + + // infinite loop; just so we don't leave this stack frame + loop {} +} diff --git a/microbit/src/05-led-roulette/the-challenge.md b/microbit/src/05-led-roulette/the-challenge.md new file mode 100644 index 000000000..613c56a31 --- /dev/null +++ b/microbit/src/05-led-roulette/the-challenge.md @@ -0,0 +1,60 @@ +# The challenge + +You are now well armed to face a challenge! Your task will be to implement the application I showed +you at the beginning of this chapter. + +

+

+ +If you can't exactly see what's happening here it is in a much slower version: + +

+

+ +Since working with the LED pins separately is quite annoying +(especially if you have to use basically all of them like here) +you can use the display API provided by the BSP. It works like this: + +```rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; +use microbit::{ + board::Board, + display::blocking::Display, + hal::{prelude::*, Timer}, +}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + + let board = Board::take().unwrap(); + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + let light_it_all = [ + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + ]; + + loop { + // Show light_it_all for 1000ms + display.show(&mut timer, light_it_all, 1000); + // clear the display again + display.clear(); + timer.delay_ms(1000_u32); + } +} +``` + +Equipped with this API your task basically boils down to just having +to calculate the proper image matrix and passing it into the BSP. diff --git a/microbit/src/06-serial-communication/README.md b/microbit/src/06-serial-communication/README.md new file mode 100644 index 000000000..8714b7df4 --- /dev/null +++ b/microbit/src/06-serial-communication/README.md @@ -0,0 +1,55 @@ +# Serial communication + + +

+ +

+
+ +

+This is what we'll be using. I hope your computer has one! +

+ +Nah, don't worry. This connector, the DE-9, went out of fashion on PCs quite some time ago; it got +replaced by the Universal Serial Bus (USB). We won't be dealing with the DE-9 connector itself but +with the communication protocol that this cable is/was usually used for. + +So what's this [*serial communication*][ASC]? It's an *asynchronous* communication protocol where two +devices exchange data *serially*, as in one bit at a time, using two data lines (plus a common +ground). The protocol is asynchronous in the sense that neither of the shared lines carries a clock +signal. Instead, both parties must agree on how fast data will be sent along the wire *before* the +communication occurs. This protocol allows *duplex* communication as data can be sent from A to B +and from B to A simultaneously. + +We'll be using this protocol to exchange data between the microcontroller and your computer. Now you might +be asking yourself why exactly we aren't using RTT for this like we did before. RTT is a protocol that is meant +to be used solely for debugging. You will most definitely not be able to find a device that actually uses RTT +to communicate with some other device in production. However, serial communication is used quite often. For +example some GPS receivers send the positioning information they receive via serial communication. + +The next practical question you probably want to ask is: How fast can we send data through this +protocol? + +This protocol works with frames. Each frame has one *start* bit, 5 to 9 bits of payload (data) and 1 +to 2 *stop bits*. The speed of the protocol is known as *baud rate* and it's quoted in bits per +second (bps). Common baud rates are: 9600, 19200, 38400, 57600 and 115200 bps. + +To actually answer the question: With a common configuration of 1 start bit, 8 bits of data, 1 +stop bit and a baud rate of 115200 bps one can, in theory, send 11,520 frames per second. Since each +one frame carries a byte of data that results in a data rate of 11.52 KB/s. In practice, the data +rate will probably be lower because of processing times on the slower side of the communication (the +microcontroller). + +Today's computers don't support the serial communication protocol. So you can't directly connect +your computer to the microcontroller. Luckily for us though, the debug probe on the micro:bit has a so-called +USB-to-serial converter. This means that the converter will sit between the two and expose a serial interface to +the microcontroller and a USB interface to your computer. The microcontroller will see your computer as +another serial device and your computer will see the microcontroller as a virtual serial device. + +Now, let's get familiar with the serial module and the serial communication tools that your OS +offers. Pick a route: + +- [\*nix](nix-tooling.md) +- [Windows](windows-tooling.md) + +[ASC]: https://en.wikipedia.org/wiki/Asynchronous_serial_communication diff --git a/microbit/src/06-serial-communication/nix-tooling.md b/microbit/src/06-serial-communication/nix-tooling.md new file mode 100644 index 000000000..f4f0f4717 --- /dev/null +++ b/microbit/src/06-serial-communication/nix-tooling.md @@ -0,0 +1,93 @@ +# \*nix tooling + +## Connecting the micro:bit board + +If you connect the micro:bit board to your computer you +should see a new TTY device appear in `/dev`. + +``` console +$ # Linux +$ dmesg | tail | grep -i tty +[63712.446286] cdc_acm 1-1.7:1.1: ttyACM0: USB ACM device +``` + +This is the USB <-> Serial device. On Linux, it's named `tty*` (usually +`ttyACM*` or `ttyUSB*`). +On Mac OS `ls /dev/cu.usbmodem*` will show the serial device. + +But what exactly is `ttyACM0`? It's a file of course! +Everything is a file in \*nix: + +``` +$ ls -l /dev/ttyACM0 +crw-rw----. 1 root plugdev 166, 0 Jan 21 11:56 /dev/ttyACM0 +``` + +You can send out data by simply writing to this file: + +``` console +$ echo 'Hello, world!' > /dev/ttyACM0 +``` + +You should see the orange LED on the micro:bit, right next to the USB port, blink for a moment, +whenever you enter this command. + +## minicom + +We'll use the program `minicom` to interact with the serial device using the keyboard. + +We must configure `minicom` before we use it. There are quite a few ways to do that but we'll use a +`.minirc.dfl` file in the home directory. Create a file in `~/.minirc.dfl` with the following +contents: + +``` console +$ cat ~/.minirc.dfl +pu baudrate 115200 +pu bits 8 +pu parity N +pu stopbits 1 +pu rtscts No +pu xonxoff No +``` + +> **NOTE** Make sure this file ends in a newline! Otherwise, `minicom` will fail to read it. + +That file should be straightforward to read (except for the last two lines), but nonetheless let's +go over it line by line: + +- `pu baudrate 115200`. Sets baud rate to 115200 bps. +- `pu bits 8`. 8 bits per frame. +- `pu parity N`. No parity check. +- `pu stopbits 1`. 1 stop bit. +- `pu rtscts No`. No hardware control flow. +- `pu xonxoff No`. No software control flow. + +Once that's in place, we can launch `minicom`. + +``` console +$ # NOTE you may need to use a different device here +$ minicom -D /dev/ttyACM0 -b 115200 +``` + +This tells `minicom` to open the serial device at `/dev/ttyACM0` and set its +baud rate to 115200. A text-based user interface (TUI) will pop out. + +

+ +

+ +You can now send data using the keyboard! Go ahead and type something. Note that +the text UI will *not* echo back what you type. If you pay attention to the yellow LED +on top of the micro:bit though, you will notice that it blinks whenever you type something. + +## `minicom` commands + +`minicom` exposes commands via keyboard shortcuts. On Linux, the shortcuts start with `Ctrl+A`. On +Mac, the shortcuts start with the `Meta` key. Some useful commands below: + +- `Ctrl+A` + `Z`. Minicom Command Summary +- `Ctrl+A` + `C`. Clear the screen +- `Ctrl+A` + `X`. Exit and reset +- `Ctrl+A` + `Q`. Quit with no reset + +> **NOTE** Mac users: In the above commands, replace `Ctrl+A` with `Meta`. diff --git a/src/10-serial-communication/windows-tooling.md b/microbit/src/06-serial-communication/windows-tooling.md similarity index 75% rename from src/10-serial-communication/windows-tooling.md rename to microbit/src/06-serial-communication/windows-tooling.md index 5e3c75ced..61bf256dd 100644 --- a/src/10-serial-communication/windows-tooling.md +++ b/microbit/src/06-serial-communication/windows-tooling.md @@ -1,17 +1,27 @@ +<<<<<<< HEAD:src/10-serial-communication/windows-tooling.md # Windowsのツール シリアルモジュールを差し込む前に、ターミナルで次のコマンドを実行して下さい。 +======= +Start by unplugging your micro:bit. + +Before plugging the micro:bit, run the following command on the terminal: +>>>>>>> upstream/master:microbit/src/06-serial-communication/windows-tooling.md ``` console $ mode ``` +<<<<<<< HEAD:src/10-serial-communication/windows-tooling.md @@ -19,6 +29,7 @@ the `COM` *ports* `mode` outputs *before* plugging the serial module. このコマンドは、ノートPCに接続されているデバイスの一覧を表示します。`COM`から名前が始まるデバイスが、シリアルデバイスです。 このデバイスがこれから使うデバイスの種類です。シリアルモジュールを差し込む*前に*`mode`が出力した全ての`COM`*ポート*をメモして下さい。 +<<<<<<< HEAD:src/10-serial-communication/windows-tooling.md +======= +Now, plug the micro:bit and run the `mode` command again. If you see a new +`COM` port appear on the list then you have that's the COM port assigned to the +serial functionality on the micro:bit. +>>>>>>> upstream/master:microbit/src/06-serial-communication/windows-tooling.md それでは、`putty`を起動します。GUIが現れます。 @@ -66,8 +82,12 @@ that the serial port is configured as follows:

+<<<<<<< HEAD:src/10-serial-communication/windows-tooling.md diff --git a/microbit/src/07-uart/.cargo/config b/microbit/src/07-uart/.cargo/config new file mode 100644 index 000000000..6260c5718 --- /dev/null +++ b/microbit/src/07-uart/.cargo/config @@ -0,0 +1,4 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] diff --git a/microbit/src/07-uart/Cargo.toml b/microbit/src/07-uart/Cargo.toml new file mode 100644 index 000000000..4da2b15a2 --- /dev/null +++ b/microbit/src/07-uart/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "uart" +version = "0.1.0" +authors = ["Henrik Böving "] +edition = "2018" + +[dependencies.microbit-v2] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + + +[dependencies.microbit] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + +[dependencies] +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +rtt-target = { version = "0.3.1", features = ["cortex-m"] } +panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } +nb = "1.0.0" +heapless = "0.7.7" +embedded-hal = "0.2.6" + +[features] +v2 = ["microbit-v2"] +v1 = ["microbit"] diff --git a/microbit/src/07-uart/Embed.toml b/microbit/src/07-uart/Embed.toml new file mode 100644 index 000000000..f5117ac42 --- /dev/null +++ b/microbit/src/07-uart/Embed.toml @@ -0,0 +1,12 @@ +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 + +[default.reset] +halt_afterwards = false + +[default.rtt] +enabled = true + +[default.gdb] +enabled = false diff --git a/microbit/src/07-uart/README.md b/microbit/src/07-uart/README.md new file mode 100644 index 000000000..62bc49e62 --- /dev/null +++ b/microbit/src/07-uart/README.md @@ -0,0 +1,20 @@ +# UART + +The microcontroller has a peripheral called UART, which stands for Universal +Asynchronous Receiver/Transmitter. This peripheral can be configured to work with +several communication protocols like the serial communication protocol. + + +Throughout this chapter, we'll use serial communication to exchange information between the +microcontroller and your computer. + +> **NOTE** that on the micro:bit v2 we will use the so called UARTE peripheral which behaves +> just like a regular UART, except that the HAL has to talk to it differently. +> However, this will of course not be our concern. + +## Setup +As always from now on you will have to modify the `Embed.toml` to match your micro:bit version: + +```toml +{{#include Embed.toml}} +``` diff --git a/microbit/src/07-uart/build.rs b/microbit/src/07-uart/build.rs new file mode 100644 index 000000000..c8d8c9141 --- /dev/null +++ b/microbit/src/07-uart/build.rs @@ -0,0 +1,30 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory (wherever `Cargo.toml` is). However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! a rebuild of the application with new memory settings is ensured after updating `memory.x`. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/microbit/src/07-uart/echo-server.md b/microbit/src/07-uart/echo-server.md new file mode 100644 index 000000000..6ffd260d9 --- /dev/null +++ b/microbit/src/07-uart/echo-server.md @@ -0,0 +1,7 @@ +# Echo server + +Let's merge transmission and reception into a single program and write an echo server. An echo +server sends back to the client the same text it receives. For this application, the microcontroller +will be the server and you and your computer will be the client. + +This should be straightforward to implement. (hint: do it byte by byte) diff --git a/microbit/src/07-uart/memory.x b/microbit/src/07-uart/memory.x new file mode 100644 index 000000000..9e2ab65f6 --- /dev/null +++ b/microbit/src/07-uart/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/microbit/src/07-uart/my-solution.md b/microbit/src/07-uart/my-solution.md new file mode 100644 index 000000000..4cf80ca17 --- /dev/null +++ b/microbit/src/07-uart/my-solution.md @@ -0,0 +1,83 @@ +# My solution + +```rust +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use core::fmt::Write; +use heapless::{Vec, consts}; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::prelude::*, + hal::uart, + hal::uart::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::prelude::*, + hal::uarte, + hal::uarte::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +mod serial_setup; +#[cfg(feature = "v2")] +use serial_setup::UartePort; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let mut serial = { + uart::Uart::new( + board.UART0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ) + }; + + #[cfg(feature = "v2")] + let mut serial = { + let serial = uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + UartePort::new(serial) + }; + + // A buffer with 32 bytes of capacity + let mut buffer: Vec = Vec::new(); + + loop { + buffer.clear(); + + loop { + // We assume that the receiving cannot fail + let byte = nb::block!(serial.read()).unwrap(); + + if buffer.push(byte).is_err() { + writeln!(serial, "error: buffer full").unwrap(); + break; + } + + if byte == 13 { + for byte in buffer.iter().rev().chain(&[b'\n', b'\r']) { + nb::block!(serial.write(*byte)).unwrap(); + } + break; + } + } + nb::block!(serial.flush()).unwrap() + } +} +``` diff --git a/microbit/src/07-uart/naive-approch-write.md b/microbit/src/07-uart/naive-approch-write.md new file mode 100644 index 000000000..9fda52bc4 --- /dev/null +++ b/microbit/src/07-uart/naive-approch-write.md @@ -0,0 +1,142 @@ +# Naive approach and `write!` + +## Naive approach + +You probably came up with a program similar to the following: + +```rs +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::prelude::*, + hal::uart, + hal::uart::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::prelude::*, + hal::uarte, + hal::uarte::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +mod serial_setup; +#[cfg(feature = "v2")] +use serial_setup::UartePort; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let mut serial = { + uart::Uart::new( + board.UART0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ) + }; + + #[cfg(feature = "v2")] + let mut serial = { + let serial = uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + UartePort::new(serial) + }; + + for byte in b"The quick brown fox jumps over the lazy dog.\r\n".iter() { + nb::block!(serial.write(*byte)).unwrap(); + } + nb::block!(serial.flush()).unwrap(); + + loop {} +} +``` + +While this is a perfectly valid implementation, at some point +you might want to have all the nice perks of `print!` such +as argument formatting and so on. If you are wondering how to do that, read on. + +## `write!` and `core::fmt::Write` +The `core::fmt::Write` trait allows us to use any struct that implements +it in basically the same way as we use `print!` in the `std` world. +In this case, the `Uart` struct from the `nrf` HAL does implement `core::fmt::Write` +so we can refactor our previous program into this: + +```rs +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; +use core::fmt::Write; + +#[cfg(feature = "v1")] +use microbit::{ + hal::prelude::*, + hal::uart, + hal::uart::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::prelude::*, + hal::uarte, + hal::uarte::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +mod serial_setup; +#[cfg(feature = "v2")] +use serial_setup::UartePort; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let mut serial = { + uart::Uart::new( + board.UART0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ) + }; + + #[cfg(feature = "v2")] + let mut serial = { + let serial = uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + UartePort::new(serial) + }; + + write!(serial, "The quick brown fox jumps over the lazy dog.\r\n").unwrap(); + nb::block!(serial.flush()).unwrap(); + + loop {} +} +``` + +If you were to flash this program onto your micro:bit, you'll +see that it is functionally equivalent to the iterator-based +program you came up with. diff --git a/microbit/src/07-uart/receive-a-single-byte.md b/microbit/src/07-uart/receive-a-single-byte.md new file mode 100644 index 000000000..c6571c82e --- /dev/null +++ b/microbit/src/07-uart/receive-a-single-byte.md @@ -0,0 +1,75 @@ +# Receive a single byte + +So far we can send data from the microcontroller to your computer. It's time to try the opposite: receiving +data from your computer. Luckily `embedded-hal` has again got us covered with this one: + +``` rust +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::{rtt_init_print, rprintln}; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::prelude::*, + hal::uart, + hal::uart::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::prelude::*, + hal::uarte, + hal::uarte::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +mod serial_setup; +#[cfg(feature = "v2")] +use serial_setup::UartePort; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let mut serial = { + uart::Uart::new( + board.UART0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ) + }; + + #[cfg(feature = "v2")] + let mut serial = { + let serial = uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + UartePort::new(serial) + }; + + loop { + let byte = nb::block!(serial.read()).unwrap(); + rprintln!("{}", byte); + } +} +``` + +The only part that changed, compared to our send byte program, is the loop +at the end of `main()`. Here we use the `read()` function, provided by `embedded-hal`, +in order to wait until a byte is available and read it. Then we print that byte +into our RTT debugging console to see whether stuff is actually arriving. + +Note that if you flash this program and start typing characters inside `minicom` to +send them to your microcontroller you'll only be able to see numbers inside your +RTT console since we are not converting the `u8` we received into an actual `char`. +Since the conversion from `u8` to `char` is quite simple, I'll leave this task to +you if you really do want to see the characters inside the RTT console. diff --git a/microbit/src/07-uart/reverse-a-string.md b/microbit/src/07-uart/reverse-a-string.md new file mode 100644 index 000000000..7a4c6dbab --- /dev/null +++ b/microbit/src/07-uart/reverse-a-string.md @@ -0,0 +1,79 @@ +# Reverse a string + +Alright, next let's make the server more interesting by having it respond to the client with the +reverse of the text that they sent. The server will respond to the client every time they press the +ENTER key. Each server response will be in a new line. + +This time you'll need a buffer; you can use [`heapless::Vec`]. Here's the starter code: + +[`heapless::Vec`]: https://docs.rs/heapless/0.7.7/heapless/struct.Vec.html + +``` rust +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use core::fmt::Write; +use heapless::Vec; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::prelude::*, + hal::uart, + hal::uart::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::prelude::*, + hal::uarte, + hal::uarte::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +mod serial_setup; +#[cfg(feature = "v2")] +use serial_setup::UartePort; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let mut serial = { + uart::Uart::new( + board.UART0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ) + }; + + #[cfg(feature = "v2")] + let mut serial = { + let serial = uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + UartePort::new(serial) + }; + + // A buffer with 32 bytes of capacity + let mut buffer: Vec = Vec::new(); + + loop { + buffer.clear(); + + // TODO Receive a user request. Each user request ends with ENTER + // NOTE `buffer.push` returns a `Result`. Handle the error by responding + // with an error message. + + // TODO Send back the reversed string + } +} +``` diff --git a/microbit/src/07-uart/send-a-single-byte.md b/microbit/src/07-uart/send-a-single-byte.md new file mode 100644 index 000000000..bfd4799db --- /dev/null +++ b/microbit/src/07-uart/send-a-single-byte.md @@ -0,0 +1,64 @@ +# Send a single byte + +Our first task will be to send a single byte from the microcontroller to the computer over the serial +connection. + +In order to do that we will use the following snippet (this one is already in `07-uart/src/main.rs`): + +``` rust +{{#include src/main.rs}} +``` + +The most prevalent new thing here is obviously the `cfg` directives to conditionally include/exclude +parts of the code. This is mostly just because we want to work with a regular UART for the micro:bit v1 +and with the UARTE for micro:bit v2. + +You will also have noticed that this is the first time we are including some code that is not from a library, +namely the `serial_setup` module. Its only purpose is to provide a nice wrapper around the UARTE +so we can use it the exact same way as the UART via the [`embedded_hal::serial`] traits. If you want, you can +check out what exactly the module does, but it is not required to understand this chapter in general. + +[`embedded_hal::serial`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/serial/index.html + +Apart from those differences, the initialization procedures for the UART and the UARTE are quite similar so we'll +discuss the initialization of just UARTE. The UARTE is initialized with this piece of code: +```rs +uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, +); +``` +This function takes ownership of the UARTE peripheral representation in Rust (`board.UARTE0`) and the TX/RX pins +on the board (`board.uart.into()`) so nobody else can mess with either the UARTE peripheral or our pins while +we are using them. After that we pass two configuration options to the constructor: the baudrate (that one should be +familiar) as well as an option called "parity". Parity is a way to allow serial communication lines to check whether +the data they received was corrupted during transmission. We don't want to use that here so we simply exclude it. +Then we wrap it up in the `UartePort` type so we can use it the same way as the micro:bit v1's `serial`. + +After the initialization, we send our `X` via the newly created uart instance. The `block!` macro here is the `nb::block!` +macro. `nb` is a (quoting from its description) "Minimal and reusable non-blocking I/O layer". It allows us to write +code that can conduct hardware operations in the background while we go and do other work (non-blocking). However, +in this and many other cases we have no interest in doing some other work so we just call `block!` which will wait until +the I/O operation is done and has either succeeded or failed and then continue execution normally. + +Last but not least, we `flush()` the serial port. This is because an implementor of the `embedded-hal::serial` traits may +decide to buffer output until it has received a certain number of bytes to send (this is the case with the UARTE implementation). +Calling `flush()` forces it to write the bytes it currently has right now instead of waiting for more. + +## Testing it + +Before flashing this you should make sure to start your minicom/PuTTY as the data we receive via our serial +communication is not backed up or anything, we have to view it live. Once your serial monitor is up you can +flash the program just like in chapter 5: +``` +# For micro:bit v2 +$ cargo embed --features v2 --target thumbv7em-none-eabihf + (...) + +# For micro:bit v1 +$ cargo embed --features v1 --target thumbv6m-none-eabi +``` + +And after the flashing is finished, you should see the character `X` show up on your minicom/PuTTY terminal, congrats! diff --git a/microbit/src/07-uart/send-a-string.md b/microbit/src/07-uart/send-a-string.md new file mode 100644 index 000000000..fbf808e46 --- /dev/null +++ b/microbit/src/07-uart/send-a-string.md @@ -0,0 +1,8 @@ +# Send a string + +The next task will be to send a whole string from the microcontroller to your computer. + +I want you to send the string `"The quick brown fox jumps over the lazy dog."` from the microcontroller to +your computer. + +It's your turn to write the program. diff --git a/microbit/src/07-uart/src/main.rs b/microbit/src/07-uart/src/main.rs new file mode 100644 index 000000000..0434ff883 --- /dev/null +++ b/microbit/src/07-uart/src/main.rs @@ -0,0 +1,57 @@ +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::prelude::*, + hal::uart, + hal::uart::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::prelude::*, + hal::uarte, + hal::uarte::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +mod serial_setup; +#[cfg(feature = "v2")] +use serial_setup::UartePort; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let mut serial = { + uart::Uart::new( + board.UART0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ) + }; + + #[cfg(feature = "v2")] + let mut serial = { + let serial = uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + UartePort::new(serial) + }; + + nb::block!(serial.write(b'X')).unwrap(); + nb::block!(serial.flush()).unwrap(); + + loop {} +} diff --git a/microbit/src/07-uart/src/serial_setup.rs b/microbit/src/07-uart/src/serial_setup.rs new file mode 100644 index 000000000..eb3997aed --- /dev/null +++ b/microbit/src/07-uart/src/serial_setup.rs @@ -0,0 +1,46 @@ +use core::fmt; +use embedded_hal::blocking::serial as bserial; +use embedded_hal::serial; +use microbit::hal::uarte::{Error, Instance, Uarte, UarteRx, UarteTx}; + +static mut TX_BUF: [u8; 1] = [0; 1]; +static mut RX_BUF: [u8; 1] = [0; 1]; + +pub struct UartePort(UarteTx, UarteRx); + +impl UartePort { + pub fn new(serial: Uarte) -> UartePort { + let (tx, rx) = serial + .split(unsafe { &mut TX_BUF }, unsafe { &mut RX_BUF }) + .unwrap(); + UartePort(tx, rx) + } +} + +impl fmt::Write for UartePort { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.write_str(s) + } +} + +impl serial::Write for UartePort { + type Error = Error; + + fn write(&mut self, b: u8) -> nb::Result<(), Self::Error> { + self.0.write(b) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.0.flush() + } +} + +impl bserial::write::Default for UartePort {} + +impl serial::Read for UartePort { + type Error = Error; + + fn read(&mut self) -> nb::Result { + self.1.read() + } +} diff --git a/microbit/src/08-i2c/.cargo/config b/microbit/src/08-i2c/.cargo/config new file mode 100644 index 000000000..6260c5718 --- /dev/null +++ b/microbit/src/08-i2c/.cargo/config @@ -0,0 +1,4 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] diff --git a/microbit/src/08-i2c/Cargo.toml b/microbit/src/08-i2c/Cargo.toml new file mode 100644 index 000000000..f4e9b771e --- /dev/null +++ b/microbit/src/08-i2c/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Henrik Böving "] +edition = "2018" +name = "i2c" +version = "0.1.0" + +[dependencies.microbit-v2] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + + +[dependencies.microbit] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + +[dependencies] +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +rtt-target = { version = "0.3.1", features = ["cortex-m"] } +panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } +nb = "1.0.0" +heapless = "0.7.7" +lsm303agr = "0.2.2" +embedded-hal = "0.2.6" + +[features] +v2 = ["microbit-v2"] +v1 = ["microbit"] diff --git a/microbit/src/08-i2c/Embed.toml b/microbit/src/08-i2c/Embed.toml new file mode 100644 index 000000000..f5117ac42 --- /dev/null +++ b/microbit/src/08-i2c/Embed.toml @@ -0,0 +1,12 @@ +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 + +[default.reset] +halt_afterwards = false + +[default.rtt] +enabled = true + +[default.gdb] +enabled = false diff --git a/microbit/src/08-i2c/README.md b/microbit/src/08-i2c/README.md new file mode 100644 index 000000000..adcdef0de --- /dev/null +++ b/microbit/src/08-i2c/README.md @@ -0,0 +1,32 @@ +# I2C + +We just saw the serial communication protocol. It's a widely used protocol because it's very +simple and this simplicity makes it easy to implement on top of other protocols like Bluetooth and +USB. + +However, its simplicity is also a downside. More elaborated data exchanges, like reading a digital +sensor, would require the sensor vendor to come up with another protocol on top of it. + +(Un)Luckily for us, there are *plenty* of other communication protocols in the embedded space. Some +of them are widely used in digital sensors. + +The micro:bit board we are using has two motion sensors in it: an accelerometer and a magnetometer. +Both of these sensors are packaged into a single component and can be accessed via an I2C bus. + +I2C stands for Inter-Integrated Circuit and is a *synchronous* *serial* communication protocol. It +uses two lines to exchange data: a data line (SDA) and a clock line (SCL). Because a clock line is +used to synchronize the communication, this is a *synchronous* protocol. + +

+ +

+ +This protocol uses a *master* *slave* model where the master is the device that *starts* and +drives the communication with a slave device. Several devices, both masters and slaves, can be +connected to the same bus at the same time. A master device can communicate with a specific slave +device by first broadcasting its *address* to the bus. This address can be 7 bits or 10 bits long. +Once a master has *started* a communication with a slave, no other device can make use of the bus +until the master *stops* the communication. + +The clock line determines how fast data can be exchanged and it usually operates at a frequency of +100 kHz (standard mode) or 400 kHz (fast mode). diff --git a/microbit/src/08-i2c/build.rs b/microbit/src/08-i2c/build.rs new file mode 100644 index 000000000..c8d8c9141 --- /dev/null +++ b/microbit/src/08-i2c/build.rs @@ -0,0 +1,30 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory (wherever `Cargo.toml` is). However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! a rebuild of the application with new memory settings is ensured after updating `memory.x`. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/microbit/src/08-i2c/lsm303agr.md b/microbit/src/08-i2c/lsm303agr.md new file mode 100644 index 000000000..35e90c2c4 --- /dev/null +++ b/microbit/src/08-i2c/lsm303agr.md @@ -0,0 +1,26 @@ +# LSM303AGR + +Both of the motion sensors on the micro:bit, the magnetometer and the accelerometer, are packaged in a single +component: the LSM303AGR integrated circuit. These two sensors can be accessed via an I2C bus. Each +sensor behaves like an I2C slave and has a *different* address. + +Each sensor has its own memory where it stores the results of sensing its environment. Our +interaction with these sensors will mainly involve reading their memory. + +The memory of these sensors is modeled as byte addressable registers. These sensors can be +configured too; that's done by writing to their registers. So, in a sense, these sensors are very +similar to the peripherals *inside* the microcontroller. The difference is that their registers are +not mapped into the microcontrollers' memory. Instead, their registers have to be accessed via the +I2C bus. + +The main source of information about the LSM303AGR is its [Data Sheet]. Read through it to see how +one can read the sensors' registers. That part is in: + +[Data Sheet]: https://www.st.com/resource/en/datasheet/lsm303agr.pdf + +> Section 6.1.1 I2C Operation - Page 38 - LSM303AGR Data Sheet + +The other part of the documentation relevant to this book is the description of the registers. That +part is in: + +> Section 8 Register description - Page 46 - LSM303AGR Data Sheet diff --git a/microbit/src/08-i2c/memory.x b/microbit/src/08-i2c/memory.x new file mode 100644 index 000000000..9e2ab65f6 --- /dev/null +++ b/microbit/src/08-i2c/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/microbit/src/08-i2c/my-solution.md b/microbit/src/08-i2c/my-solution.md new file mode 100644 index 000000000..0a48da813 --- /dev/null +++ b/microbit/src/08-i2c/my-solution.md @@ -0,0 +1,110 @@ +# My solution + +```rust +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::twi, + pac::twi0::frequency::FREQUENCY_A, + hal::uart, + hal::uart::{Baudrate, Parity}, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::twim, + pac::twim0::frequency::FREQUENCY_A, + hal::uarte, + hal::uarte::{Baudrate, Parity}, +}; + +use microbit::hal::prelude::*; +use lsm303agr::{AccelOutputDataRate, MagOutputDataRate, Lsm303agr}; +use heapless::{consts, Vec, String}; +use nb::block; +use core::fmt::Write; + +#[cfg(feature = "v2")] +mod serial_setup; +#[cfg(feature = "v2")] +use serial_setup::UartePort; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let mut serial = { + uart::Uart::new( + board.UART0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ) + }; + + #[cfg(feature = "v2")] + let mut serial = { + let serial = uarte::Uarte::new( + board.UARTE0, + board.uart.into(), + Parity::EXCLUDED, + Baudrate::BAUD115200, + ); + UartePort::new(serial) + }; + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz50).unwrap(); + sensor.set_mag_odr(MagOutputDataRate::Hz50).unwrap(); + let mut sensor = sensor.into_mag_continuous().ok().unwrap(); + + loop { + let mut buffer: Vec = Vec::new(); + + loop { + let byte = block!(serial.read()).unwrap(); + + if buffer.push(byte).is_err() { + write!(serial, "error: buffer full\r\n").unwrap(); + break; + } + + if byte == 13 { + break; + } + } + + let command_string = String::from_utf8(buffer).unwrap(); + if command_string.as_str().trim() == "accelerometer" { + while !sensor.accel_status().unwrap().xyz_new_data { + } + + let data = sensor.accel_data().unwrap(); + write!(serial, "Accelerometer: x {} y {} z {}\r\n", data.x, data.y, data.z).unwrap(); + } else if command_string.as_str().trim() == "magnetometer" { + while !sensor.mag_status().unwrap().xyz_new_data { + } + + let data = sensor.mag_data().unwrap(); + write!(serial, "Magnetometer: x {} y {} z {}\r\n", data.x, data.y, data.z).unwrap(); + } else { + write!(serial, "error: command not detected\r\n").unwrap(); + } + } +} +``` diff --git a/microbit/src/08-i2c/read-a-single-register.md b/microbit/src/08-i2c/read-a-single-register.md new file mode 100644 index 000000000..a83c00bc0 --- /dev/null +++ b/microbit/src/08-i2c/read-a-single-register.md @@ -0,0 +1,58 @@ +# Read a single register + +Let's put all that theory into practice! + +First things first we need to know the slave addresses of both the accelerometer +and the magnetometer inside the chip, these can be found in the LSM303AGR's +datasheet on page 39 and are: + +- 0011001 for the accelerometer +- 0011110 for the magnetometer + +> **NOTE** Remember that these are only the 7 leading bits of the address, +> the 8th bit is going to be the bit that determines whether we are +> performing a read or write. + +Next up we'll need a register to read from. Lots of I2C chips out there will +provide some sort of device identification register for their masters to read. +This is done since considering the thousands (or even millions) of I2C chips +out there it is highly likely that at some point two chips with the same address +will end up being built (after all the address is "only" 7 bit wide). With +this device ID register a driver could then make sure that it is indeed talking +to a LSM303AGR and not some other chip that just happens to have the same address. +As you can read in the LSM303AGR's datasheet (specifically on page 46 and 61) +it does provide two registers called `WHO_AM_I_A` at address `0x0f` and `WHO_AM_I_M` +at address `0x4f` which contain some bit patterns that are unique to the device +(The A is as in accelerometer and the M is as in magnetometer). + +The only thing missing now is the software part, i.e. which API of the `microbit`/the HAL +crates we should use for this. However, if you read through the datasheet of the nRF chip +you are using you will soon find out that they don't actually have an I2C peripheral. +Luckily for us though, they have I2C-compatible ones called TWI (Two Wire Interface) +and TWIM (depending on which chip you use, just like UART and UARTE). + +Now if we put the documentation of the [`twi(m)` module] from the `microbit` crate +together with all the other information we have gathered so far we'll end up with this +piece of code to read out and print the two device IDs: + +[`twi(m)` module]: https://docs.rs/microbit-v2/0.11.0/microbit/hal/twim/index.html + +``` rust +{{#include src/main.rs}} +``` + +Apart from the initialization, this piece of code should be straight forward if you +understood the I2C protocol as described before. The initialization here works similarly +to the one from the UART chapter. +We pass the peripheral as well as the pins that are used to communicate with the chip to the constructor; and then the frequency we wish the bus to operate on, in this case 100 kHz (`K100`). + +## Testing it +As always you have to modify `Embed.toml` to fit your MCU and can then use: +```console +# For micro:bit v2 +$ cargo embed --features v2 --target thumbv7em-none-eabihf + +# For micro:bit v1 +$ cargo embed --features v1 --target thumbv6m-none-eabi +``` +in order to test our little example program. diff --git a/microbit/src/08-i2c/src/main.rs b/microbit/src/08-i2c/src/main.rs new file mode 100644 index 000000000..1ad7c8aca --- /dev/null +++ b/microbit/src/08-i2c/src/main.rs @@ -0,0 +1,52 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::{rtt_init_print, rprintln}; +use panic_rtt_target as _; + +use microbit::hal::prelude::*; + +#[cfg(feature = "v1")] +use microbit::{ + hal::twi, + pac::twi0::frequency::FREQUENCY_A, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::twim, + pac::twim0::frequency::FREQUENCY_A, +}; + +const ACCELEROMETER_ADDR: u8 = 0b0011001; +const MAGNETOMETER_ADDR: u8 = 0b0011110; + +const ACCELEROMETER_ID_REG: u8 = 0x0f; +const MAGNETOMETER_ID_REG: u8 = 0x4f; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + + #[cfg(feature = "v1")] + let mut i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let mut i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut acc = [0]; + let mut mag = [0]; + + // First write the address + register onto the bus, then read the chip's responses + i2c.write_read(ACCELEROMETER_ADDR, &[ACCELEROMETER_ID_REG], &mut acc).unwrap(); + i2c.write_read(MAGNETOMETER_ADDR, &[MAGNETOMETER_ID_REG], &mut mag).unwrap(); + + rprintln!("The accelerometer chip's id is: {:#b}", acc[0]); + rprintln!("The magnetometer chip's id is: {:#b}", mag[0]); + + loop {} +} diff --git a/microbit/src/08-i2c/src/serial_setup.rs b/microbit/src/08-i2c/src/serial_setup.rs new file mode 100644 index 000000000..eb3997aed --- /dev/null +++ b/microbit/src/08-i2c/src/serial_setup.rs @@ -0,0 +1,46 @@ +use core::fmt; +use embedded_hal::blocking::serial as bserial; +use embedded_hal::serial; +use microbit::hal::uarte::{Error, Instance, Uarte, UarteRx, UarteTx}; + +static mut TX_BUF: [u8; 1] = [0; 1]; +static mut RX_BUF: [u8; 1] = [0; 1]; + +pub struct UartePort(UarteTx, UarteRx); + +impl UartePort { + pub fn new(serial: Uarte) -> UartePort { + let (tx, rx) = serial + .split(unsafe { &mut TX_BUF }, unsafe { &mut RX_BUF }) + .unwrap(); + UartePort(tx, rx) + } +} + +impl fmt::Write for UartePort { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.write_str(s) + } +} + +impl serial::Write for UartePort { + type Error = Error; + + fn write(&mut self, b: u8) -> nb::Result<(), Self::Error> { + self.0.write(b) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.0.flush() + } +} + +impl bserial::write::Default for UartePort {} + +impl serial::Read for UartePort { + type Error = Error; + + fn read(&mut self) -> nb::Result { + self.1.read() + } +} diff --git a/microbit/src/08-i2c/the-challenge.md b/microbit/src/08-i2c/the-challenge.md new file mode 100644 index 000000000..d266981f2 --- /dev/null +++ b/microbit/src/08-i2c/the-challenge.md @@ -0,0 +1,12 @@ +# The challenge + +The challenge for this chapter is, to build a small application that +communicates with the outside world via the serial interface introduced +in the last chapter. It should be able to receive the commands "magnetometer" +as well as "accelerometer" and then print the corresponding sensor data +in response. This time no template code will be provided since all you need +is already provided in the [UART](../07-uart/index.md) and this chapter. However, here are a few clues: + +- You might be interested in `heapless::String` since we are working with strings now +- You will (obviously) have to read the documentation of the magnetometer API, however + it's more or less equivalent to the accelerometer one diff --git a/microbit/src/08-i2c/the-general-protocol.md b/microbit/src/08-i2c/the-general-protocol.md new file mode 100644 index 000000000..471228b41 --- /dev/null +++ b/microbit/src/08-i2c/the-general-protocol.md @@ -0,0 +1,42 @@ +# General protocol + +The I2C protocol is more elaborate than the serial communication protocol because it has to support +communication between several devices. Let's see how it works using examples: + +## Master -> Slave + +If the master wants to send data to the slave: + +

+ +

+ +1. Master: Broadcast START +2. M: Broadcast slave address (7 bits) + the R/W (8th) bit set to WRITE +3. Slave: Responds ACK (ACKnowledgement) +4. M: Send one byte +5. S: Responds ACK +6. Repeat steps 4 and 5 zero or more times +7. M: Broadcast STOP OR (broadcast RESTART and go back to (2)) + +> **NOTE** The slave address could have been 10 bits instead of 7 bits long. Nothing else would have +> changed. + +## Master <- Slave + +If the master wants to read data from the slave: + +

+ +

+ +1. M: Broadcast START +2. M: Broadcast slave address (7 bits) + the R/W (8th) bit set to READ +3. S: Responds with ACK +4. S: Send byte +5. M: Responds with ACK +6. Repeat steps 4 and 5 zero or more times +7. M: Broadcast STOP OR (broadcast RESTART and go back to (2)) + +> **NOTE** The slave address could have been 10 bits instead of 7 bits long. Nothing else would have +> changed. diff --git a/microbit/src/08-i2c/using-a-driver.md b/microbit/src/08-i2c/using-a-driver.md new file mode 100644 index 000000000..1f420b25a --- /dev/null +++ b/microbit/src/08-i2c/using-a-driver.md @@ -0,0 +1,104 @@ +# Using a driver + +As we already discussed in chapter 5 `embedded-hal` provides abstractions +which can be used to write platform independent code that can interact with +hardware. In fact all the methods we have used to interact with hardware +in chapter 7 and up until now in chapter 8 were from traits, defined by `embedded-hal`. +Now we'll make actual use of the traits `embedded-hal` provides for the first time. + +It would be pointless to implement a driver for our LSM303AGR for every platform +embedded Rust supports (and new ones that might eventually pop up). To avoid this a driver +can be written that consumes generic types that implement `embedded-hal` traits in order to provide +a platform agnostic version of a driver. Luckily for us this has already been done in the +[`lsm303agr`] crate. Hence reading the actual accelerometer and magnetometer values will now +be basically a plug and play experience (plus reading a bit of documentation). In fact the `crates.io` +page already provides us with everything we need to know in order to read accelerometer data but using a Raspberry Pi. We'll +just have to adapt it to our chip: + +[`lsm303agr`]: https://crates.io/crates/lsm303agr + +```rust +use linux_embedded_hal::I2cdev; +use lsm303agr::{AccelOutputDataRate, Lsm303agr}; + +fn main() { + let dev = I2cdev::new("/dev/i2c-1").unwrap(); + let mut sensor = Lsm303agr::new_with_i2c(dev); + sensor.init().unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz50).unwrap(); + loop { + if sensor.accel_status().unwrap().xyz_new_data { + let data = sensor.accel_data().unwrap(); + println!("Acceleration: x {} y {} z {}", data.x, data.y, data.z); + } + } +} +``` + +Because we already know how to create an instance of an object that implements +the [`embedded_hal::blocking::i2c`] traits from the [previous page](read-a-single-register.md), this is quite trivial: + +[`embedded_hal::blocking::i2c`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/blocking/i2c/index.html + +```rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::{rtt_init_print, rprintln}; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::twi, + pac::twi0::frequency::FREQUENCY_A, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::twim, + pac::twim0::frequency::FREQUENCY_A, +}; + +use lsm303agr::{ + AccelOutputDataRate, Lsm303agr, +}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + // Code from documentation + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz50).unwrap(); + loop { + if sensor.accel_status().unwrap().xyz_new_data { + let data = sensor.accel_data().unwrap(); + // RTT instead of normal print + rprintln!("Acceleration: x {} y {} z {}", data.x, data.y, data.z); + } + } +} +``` + +Just like the last snippet you should just be able to try this out like this: +```console +# For micro:bit v2 +$ cargo embed --features v2 --target thumbv7em-none-eabihf + +# For micro:bit v1 +$ cargo embed --features v1 --target thumbv6m-none-eabi +``` + +Furthermore if you (physically) move around your micro:bit a little you should see the +acceleration numbers that are being printed change. diff --git a/microbit/src/09-led-compass/.cargo/config b/microbit/src/09-led-compass/.cargo/config new file mode 100644 index 000000000..6260c5718 --- /dev/null +++ b/microbit/src/09-led-compass/.cargo/config @@ -0,0 +1,4 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] diff --git a/microbit/src/09-led-compass/Cargo.toml b/microbit/src/09-led-compass/Cargo.toml new file mode 100644 index 000000000..beab33610 --- /dev/null +++ b/microbit/src/09-led-compass/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "led-compass" +version = "0.1.0" +authors = ["Henrik Böving "] +edition = "2018" + +[dependencies.microbit-v2] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + + +[dependencies.microbit] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + +[dependencies] +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +rtt-target = { version = "0.3.1", features = ["cortex-m"] } +panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } +lsm303agr = "0.2.2" +libm = "0.2.1" +embedded-hal = "0.2.6" + +[features] +v2 = ["microbit-v2"] +v1 = ["microbit"] diff --git a/microbit/src/09-led-compass/Embed.toml b/microbit/src/09-led-compass/Embed.toml new file mode 100644 index 000000000..f5117ac42 --- /dev/null +++ b/microbit/src/09-led-compass/Embed.toml @@ -0,0 +1,12 @@ +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 + +[default.reset] +halt_afterwards = false + +[default.rtt] +enabled = true + +[default.gdb] +enabled = false diff --git a/microbit/src/09-led-compass/README.md b/microbit/src/09-led-compass/README.md new file mode 100644 index 000000000..413393c97 --- /dev/null +++ b/microbit/src/09-led-compass/README.md @@ -0,0 +1,20 @@ +# LED compass + +In this section, we'll implement a compass using the LEDs on the micro:bit. Like proper compasses, our LED +compass must point north somehow. It will do that by turning on one of its outer LEDs; the LED turned on +should point towards north. + +Magnetic fields have both a magnitude, measured in Gauss or Teslas, and a *direction*. The +magnetometer on the micro:bit measures both the magnitude and the direction of an external magnetic field +but it reports back the *decomposition* of said field along *its axes*. + +The magnetometer has three axes associated to it. The X and Y axes basically span the plane that is the floor. +The Z axis is pointing "out" of the floor, so upwards. + +You should already be able to write a program that continuously prints the magnetometer +data on the RTT console from the [I2C chapter](../08-i2c/index.md). After you wrote that +program, locate where north is at your current location. Then line up your micro:bit with +that direction and observe how the sensor's measurements look. + +Now rotate the board 90 degrees while keeping it parallel to the ground. What X, Y and Z values do +you see this time? Then rotate it 90 degrees again. What values do you see? diff --git a/microbit/src/09-led-compass/build.rs b/microbit/src/09-led-compass/build.rs new file mode 100644 index 000000000..c8d8c9141 --- /dev/null +++ b/microbit/src/09-led-compass/build.rs @@ -0,0 +1,30 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory (wherever `Cargo.toml` is). However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! a rebuild of the application with new memory settings is ensured after updating `memory.x`. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/microbit/src/09-led-compass/calibration.md b/microbit/src/09-led-compass/calibration.md new file mode 100644 index 000000000..d62eda277 --- /dev/null +++ b/microbit/src/09-led-compass/calibration.md @@ -0,0 +1,37 @@ +# Calibration + +One very important thing to do before using a sensor and trying to develop +an application using it is verifying that it's output is actually correct. +If this does not happen to be the case we need to calibrate the sensor +(alternatively it could also be broken but that's rather unlikely in this case). + +In my case on two different micro:bit's the magnetometer, without calibration, +was quite a bit off of what it is supposed to measure. Hence for the purposes +of this chapter we will just assume that the sensor has to be calibrated. + +The calibration involves quite a bit of math (matrices) so we won't cover it here but this +[Design Note] describes the procedure if you are interested. + +[Design Note]: https://www.st.com/resource/en/design_tip/dt0103-compensating-for-magnetometer-installation-error-and-hardiron-effects-using-accelerometerassisted-2d-calibration-stmicroelectronics.pdf + +Luckily for us though the group that built the original software for the +micro:bit already implemented a calibration mechanism in C++ over [here]. + +[here]: https://github.com/lancaster-university/codal-microbit-v2/blob/006abf5566774fbcf674c0c7df27e8a9d20013de/source/MicroBitCompassCalibrator.cpp + +You can find a translation of it to Rust in `src/calibration.rs`. The usage +is demonstrated in the default `src/main.rs` file. The way the calibration +works is illustrated in this video: + +

+

+ +You have to basically tilt the micro:bit until all the LEDs on the LED matrix light up. + +If you do not want to play the game every time you restart your application during development +feel free to modify the `src/main.rs` template to just use the same static calibration +once you got the first one. + +Now where we got the sensor calibration out of the way let's look into +actually building this application! diff --git a/microbit/src/09-led-compass/magnitude.md b/microbit/src/09-led-compass/magnitude.md new file mode 100644 index 000000000..45cf58a10 --- /dev/null +++ b/microbit/src/09-led-compass/magnitude.md @@ -0,0 +1,91 @@ +# Magnitude + +We have been working with the direction of the magnetic field but what is its real magnitude? +According to the documentation about the [`mag_data()`] function the `x` `y` `z` values we are +getting are in nanotesla. That means the only thing we have to compute in order to get the +magnitude of the magnetic field in nanotesla is the magnitude of the 3D vector that our `x` `y` `z` +values describe. As you might remember from school this is simply: + +``` rust +// core doesn't have this function yet so we use libm, just like with +// atan2f from before. +use libm::sqrtf; +let magnitude = sqrtf(x * x + y * y + z * z); +``` + +[`mag_data()`]: https://docs.rs/lsm303agr/0.2.2/lsm303agr/struct.Lsm303agr.html#method.mag_data + + +Putting all this together in a program: + +``` rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; + +mod calibration; +use crate::calibration::calc_calibration; +use crate::calibration::calibrated_measurement; + +use libm::sqrtf; + +use microbit::{display::blocking::Display, hal::Timer}; + +#[cfg(feature = "v1")] +use microbit::{hal::twi, pac::twi0::frequency::FREQUENCY_A}; + +#[cfg(feature = "v2")] +use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A}; + +use lsm303agr::{AccelOutputDataRate, Lsm303agr, MagOutputDataRate}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_mag_odr(MagOutputDataRate::Hz10).unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz10).unwrap(); + let mut sensor = sensor.into_mag_continuous().ok().unwrap(); + + let calibration = calc_calibration(&mut sensor, &mut display, &mut timer); + rprintln!("Calibration: {:?}", calibration); + rprintln!("Calibration done, entering busy loop"); + loop { + while !sensor.mag_status().unwrap().xyz_new_data {} + let mut data = sensor.mag_data().unwrap(); + data = calibrated_measurement(data, &calibration); + let x = data.x as f32; + let y = data.y as f32; + let z = data.z as f32; + let magnitude = sqrtf(x * x + y * y + z * z); + rprintln!("{} nT, {} mG", magnitude, magnitude/100.0); + } +} +``` + +This program will report the magnitude (strength) of the magnetic field in nanotesla (`nT`) and milligauss (`mG`). The +magnitude of the Earth's magnetic field is in the range of `250 mG` to `650 mG` (the magnitude +varies depending on your geographical location) so you should see a value in that range or close to +that range -- I see a magnitude of around `340 mG`. + +Some questions: + +Without moving the board, what value do you see? Do you always see the same value? + +If you rotate the board, does the magnitude change? Should it change? diff --git a/microbit/src/09-led-compass/memory.x b/microbit/src/09-led-compass/memory.x new file mode 100644 index 000000000..9e2ab65f6 --- /dev/null +++ b/microbit/src/09-led-compass/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/microbit/src/09-led-compass/solution-1.md b/microbit/src/09-led-compass/solution-1.md new file mode 100644 index 000000000..45e543c70 --- /dev/null +++ b/microbit/src/09-led-compass/solution-1.md @@ -0,0 +1,75 @@ +# Solution 1 + +``` rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; + +mod calibration; +use crate::calibration::calc_calibration; +use crate::calibration::calibrated_measurement; + +mod led; +use crate::led::Direction; +use crate::led::direction_to_led; + +use microbit::{display::blocking::Display, hal::Timer}; + +#[cfg(feature = "v1")] +use microbit::{hal::twi, pac::twi0::frequency::FREQUENCY_A}; + +#[cfg(feature = "v2")] +use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A}; + +use lsm303agr::{AccelOutputDataRate, Lsm303agr, MagOutputDataRate}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_mag_odr(MagOutputDataRate::Hz10).unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz10).unwrap(); + let mut sensor = sensor.into_mag_continuous().ok().unwrap(); + + let calibration = calc_calibration(&mut sensor, &mut display, &mut timer); + rprintln!("Calibration: {:?}", calibration); + rprintln!("Calibration done, entering busy loop"); + loop { + while !sensor.mag_status().unwrap().xyz_new_data {} + let mut data = sensor.mag_data().unwrap(); + data = calibrated_measurement(data, &calibration); + + let dir = match (data.x > 0, data.y > 0) { + // Quadrant I + (true, true) => Direction::NorthEast, + // Quadrant II + (false, true) => Direction::NorthWest, + // Quadrant III + (false, false) => Direction::SouthWest, + // Quadrant IV + (true, false) => Direction::SouthEast, + }; + + // use the led module to turn the direction into an LED arrow + // and the led display functions from chapter 5 to display the + // arrow + display.show(&mut timer, direction_to_led(dir), 100); + } +} +``` diff --git a/microbit/src/09-led-compass/solution-2.md b/microbit/src/09-led-compass/solution-2.md new file mode 100644 index 000000000..243910e68 --- /dev/null +++ b/microbit/src/09-led-compass/solution-2.md @@ -0,0 +1,89 @@ +# Solution 2 + +``` rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; + +mod calibration; +use crate::calibration::calc_calibration; +use crate::calibration::calibrated_measurement; + +mod led; +use crate::led::Direction; +use crate::led::direction_to_led; + +// You'll find this useful ;-) +use core::f32::consts::PI; +use libm::atan2f; + +use microbit::{display::blocking::Display, hal::Timer}; + +#[cfg(feature = "v1")] +use microbit::{hal::twi, pac::twi0::frequency::FREQUENCY_A}; + +#[cfg(feature = "v2")] +use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A}; + +use lsm303agr::{AccelOutputDataRate, Lsm303agr, MagOutputDataRate}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_mag_odr(MagOutputDataRate::Hz10).unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz10).unwrap(); + let mut sensor = sensor.into_mag_continuous().ok().unwrap(); + + let calibration = calc_calibration(&mut sensor, &mut display, &mut timer); + rprintln!("Calibration: {:?}", calibration); + rprintln!("Calibration done, entering busy loop"); + loop { + while !sensor.mag_status().unwrap().xyz_new_data {} + let mut data = sensor.mag_data().unwrap(); + data = calibrated_measurement(data, &calibration); + + // use libm's atan2f since this isn't in core yet + let theta = atan2f(data.x as f32, data.y as f32); + + // Figure out the direction based on theta + let dir = if theta < -7. * PI / 8. { + Direction::South + } else if theta < -5. * PI / 8. { + Direction::SouthWest + } else if theta < -3. * PI / 8. { + Direction::West + } else if theta < -PI / 8. { + Direction::NorthWest + } else if theta < PI / 8. { + Direction::North + } else if theta < 3. * PI / 8. { + Direction::NorthEast + } else if theta < 5. * PI / 8. { + Direction::East + } else if theta < 7. * PI / 8. { + Direction::SouthEast + } else { + Direction::South + }; + + display.show(&mut timer, direction_to_led(dir), 100); + } +} +``` diff --git a/microbit/src/09-led-compass/src/calibration.rs b/microbit/src/09-led-compass/src/calibration.rs new file mode 100644 index 000000000..90c0c4c72 --- /dev/null +++ b/microbit/src/09-led-compass/src/calibration.rs @@ -0,0 +1,259 @@ +//! Translated from + +use core::fmt::Debug; +use embedded_hal::blocking::delay::DelayUs; +use embedded_hal::blocking::i2c::{Write, WriteRead}; +use libm::{fabsf, sqrtf}; +use lsm303agr::interface::I2cInterface; +use lsm303agr::mode::MagContinuous; +use lsm303agr::Lsm303agr; +use lsm303agr::Measurement; +use microbit::display::blocking::Display; + +const PERIMETER_POINTS: usize = 25; +const PIXEL1_THRESHOLD: i32 = 200; +const PIXEL2_THRESHOLD: i32 = 600; +const CALIBRATION_INCREMENT: i32 = 200; + +#[derive(Debug)] +pub struct Calibration { + center: Measurement, + scale: Measurement, + radius: u32, +} + +impl Default for Calibration { + fn default() -> Calibration { + Calibration { + center: Measurement { x: 0, y: 0, z: 0 }, + scale: Measurement { + x: 1024, + y: 1024, + z: 1024, + }, + radius: 0, + } + } +} + +pub fn calc_calibration( + sensor: &mut Lsm303agr, MagContinuous>, + display: &mut Display, + timer: &mut T, +) -> Calibration +where + T: DelayUs, + I: Write + WriteRead, + E: Debug, +{ + let data = get_data(sensor, display, timer); + return calibrate(&data); +} + +fn get_data( + sensor: &mut Lsm303agr, MagContinuous>, + display: &mut Display, + timer: &mut T, +) -> [Measurement; 25] +where + T: DelayUs, + I: Write + WriteRead, + E: Debug, +{ + let mut leds = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ]; + let mut cursor = (2, 2); + let mut data = [Measurement { x: 0, y: 0, z: 0 }; PERIMETER_POINTS]; + let mut samples = 0; + + while samples < PERIMETER_POINTS { + while !sensor.accel_status().unwrap().xyz_new_data {} + let accel_data = sensor.accel_data().unwrap(); + let x = accel_data.x; + let y = accel_data.y; + if x < -PIXEL2_THRESHOLD { + cursor.1 = 0; + } else if x < -PIXEL1_THRESHOLD { + cursor.1 = 1; + } else if x > PIXEL2_THRESHOLD { + cursor.1 = 4; + } else if x > PIXEL1_THRESHOLD { + cursor.1 = 3; + } else { + cursor.1 = 2; + } + + if y < -PIXEL2_THRESHOLD { + cursor.0 = 0; + } else if y < -PIXEL1_THRESHOLD { + cursor.0 = 1; + } else if y > PIXEL2_THRESHOLD { + cursor.0 = 4; + } else if y > PIXEL1_THRESHOLD { + cursor.0 = 3; + } else { + cursor.0 = 2; + } + + // Turn the y axis properly + cursor.0 = 4 - cursor.0; + + if leds[cursor.0][cursor.1] != 1 { + leds[cursor.0][cursor.1] = 1; + while !sensor.mag_status().unwrap().xyz_new_data {} + let mag_data = measurement_to_enu(sensor.mag_data().unwrap()); + data[samples] = mag_data; + samples += 1; + } + display.show(timer, leds, 200); + } + return data; +} + +fn difference_square(a: Measurement, b: Measurement) -> f32 { + let dx = (a.x - b.x) as f32; + let dy = (a.y - b.y) as f32; + let dz = (a.z - b.z) as f32; + + (dx * dx) + (dy * dy) + (dz * dz) +} + +fn measure_score(center: Measurement, data: &[Measurement]) -> f32 { + let mut min_d = difference_square(center, data[0]); + let mut max_d = min_d; + + for point in data[1..].iter() { + let d = difference_square(center, *point); + if d < min_d { + min_d = d; + } + + if d > max_d { + max_d = d; + } + } + + max_d - min_d +} + +fn calibrate(data: &[Measurement]) -> Calibration { + // Approximate a center for the data + let mut center = Measurement { x: 0, y: 0, z: 0 }; + let mut best = center; + + for point in data { + center.x += point.x; + center.y += point.y; + center.z += point.z; + } + + center.x = center.x / data.len() as i32; + center.y = center.y / data.len() as i32; + center.z = center.z / data.len() as i32; + + let mut current = center; + let mut score = measure_score(current, data); + + // Calculate a fixpoint position + loop { + for x in [-CALIBRATION_INCREMENT, 0, CALIBRATION_INCREMENT] { + for y in [-CALIBRATION_INCREMENT, 0, CALIBRATION_INCREMENT] { + for z in [-CALIBRATION_INCREMENT, 0, CALIBRATION_INCREMENT] { + let mut attempt = current; + attempt.x += x; + attempt.y += y; + attempt.z += z; + + let attempt_score = measure_score(attempt, data); + if attempt_score < score { + score = attempt_score; + best = attempt; + } + } + } + } + + if best == current { + break; + } + + current = best; + } + + spherify(current, data) +} + +fn spherify(center: Measurement, data: &[Measurement]) -> Calibration { + let mut radius = 0; + for point in data { + let d = sqrtf(difference_square(center, *point)) as u32; + if d > radius { + radius = d; + } + } + + let mut scale: f32 = 0.0; + let mut weight_x = 0.0; + let mut weight_y = 0.0; + let mut weight_z = 0.0; + + for point in data { + let d = sqrtf(difference_square(center, *point)); + let s = (radius as f32 / d) - 1.0; + scale = scale.max(s); + + let dx = point.x - center.x; + let dy = point.y - center.y; + let dz = point.z - center.z; + + weight_x += s * fabsf(dx as f32 / d); + weight_y += s * fabsf(dy as f32 / d); + weight_z += s * fabsf(dz as f32 / d); + } + + let wmag = sqrtf((weight_x * weight_x) + (weight_y * weight_y) + (weight_z * weight_z)); + let scale_x = 1.0 + scale * (weight_x / wmag); + let scale_y = 1.0 + scale * (weight_y / wmag); + let scale_z = 1.0 + scale * (weight_z / wmag); + + Calibration { + center, + radius, + scale: Measurement { + x: (1024.0 * scale_x) as i32, + y: (1024.0 * scale_y) as i32, + z: (1024.0 * scale_z) as i32, + }, + } +} + +pub fn calibrated_measurement(measurement: Measurement, calibration: &Calibration) -> Measurement { + let mut out = measurement_to_enu(measurement); + out = Measurement { + x: ((out.x - calibration.center.x) * calibration.scale.x) >> 10, + y: ((out.y - calibration.center.y) * calibration.scale.y) >> 10, + z: ((out.z - calibration.center.z) * calibration.scale.z) >> 10, + }; + enu_to_cartesian(out) +} + +fn measurement_to_enu(measurement: Measurement) -> Measurement { + Measurement { + x: -measurement.y, + y: -measurement.x, + z: measurement.z, + } +} + +fn enu_to_cartesian(measurement: Measurement) -> Measurement { + Measurement { + x: -measurement.y, + y: measurement.x, + z: measurement.z, + } +} diff --git a/microbit/src/09-led-compass/src/led.rs b/microbit/src/09-led-compass/src/led.rs new file mode 100644 index 000000000..00e5fea5e --- /dev/null +++ b/microbit/src/09-led-compass/src/led.rs @@ -0,0 +1,88 @@ +#[derive(Debug)] +pub enum Direction{ + North, + NorthEast, + East, + SouthEast, + South, + SouthWest, + West, + NorthWest, +} + +const NORTH: [[u8; 5]; 5] = [ + [0, 0, 1, 0, 0], + [0, 1, 1, 1, 0], + [1, 0, 1, 0, 1], + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0], +]; + +const NORTH_EAST: [[u8; 5]; 5] = [ + [1, 1, 1, 0, 0], + [1, 1, 0, 0, 0], + [1, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 1], +]; + +const EAST: [[u8; 5]; 5] = [ + [0, 0, 1, 0, 0], + [0, 1, 0, 0, 0], + [1, 1, 1, 1, 1], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 0], +]; + +const SOUTH_EAST: [[u8; 5]; 5] = [ + [0, 0, 0, 0, 1], + [0, 0, 0, 1, 0], + [1, 0, 1, 0, 0], + [1, 1, 0, 0, 0], + [1, 1, 1, 0, 0], +]; + +const SOUTH: [[u8; 5]; 5] = [ + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0], + [1, 0, 1, 0, 1], + [0, 1, 1, 1, 0], + [0, 0, 1, 0, 0], +]; + +const SOUTH_WEST: [[u8; 5]; 5] = [ + [1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 1, 0, 1], + [0, 0, 0, 1, 1], + [0, 0, 1, 1, 1], +]; + +const WEST: [[u8; 5]; 5] = [ + [0, 0, 1, 0, 0], + [0, 0, 0, 1, 0], + [1, 1, 1, 1, 1], + [0, 0, 0, 1, 0], + [0, 0, 1, 0, 0], +]; + +const NORTH_WEST: [[u8; 5]; 5] = [ + [0, 0, 1, 1, 1], + [0, 0, 0, 1, 1], + [0, 0, 1, 0, 1], + [0, 1, 0, 0, 0], + [1, 0, 0, 0, 0], +]; + +pub fn direction_to_led(direction: Direction) -> [[u8; 5]; 5] { + match direction { + Direction::North => NORTH, + Direction::NorthEast => NORTH_EAST, + Direction::East => EAST, + Direction::SouthEast => SOUTH_EAST, + Direction::South => SOUTH, + Direction::SouthWest => SOUTH_WEST, + Direction::West => WEST, + Direction::NorthWest => NORTH_WEST, + } +} diff --git a/microbit/src/09-led-compass/src/main.rs b/microbit/src/09-led-compass/src/main.rs new file mode 100644 index 000000000..944e66461 --- /dev/null +++ b/microbit/src/09-led-compass/src/main.rs @@ -0,0 +1,52 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; + +mod calibration; +use crate::calibration::calc_calibration; +use crate::calibration::calibrated_measurement; + +use microbit::{display::blocking::Display, hal::Timer}; + +#[cfg(feature = "v1")] +use microbit::{hal::twi, pac::twi0::frequency::FREQUENCY_A}; + +#[cfg(feature = "v2")] +use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A}; + +use lsm303agr::{AccelOutputDataRate, Lsm303agr, MagOutputDataRate}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_mag_odr(MagOutputDataRate::Hz10).unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz10).unwrap(); + let mut sensor = sensor.into_mag_continuous().ok().unwrap(); + + let calibration = calc_calibration(&mut sensor, &mut display, &mut timer); + rprintln!("Calibration: {:?}", calibration); + rprintln!("Calibration done, entering busy loop"); + loop { + while !sensor.mag_status().unwrap().xyz_new_data {} + let mut data = sensor.mag_data().unwrap(); + data = calibrated_measurement(data, &calibration); + rprintln!("x: {}, y: {}, z: {}", data.x, data.y, data.z); + } +} diff --git a/microbit/src/09-led-compass/take-1.md b/microbit/src/09-led-compass/take-1.md new file mode 100644 index 000000000..fda17c241 --- /dev/null +++ b/microbit/src/09-led-compass/take-1.md @@ -0,0 +1,94 @@ +# Take 1 + +What's the simplest way in which we can implement the LED compass, even if it's not perfect? + +For starters, we'd only care about the X and Y components of the magnetic field because when you +look at a compass you always hold it in horizontal position and thus the compass is in the XY plane. + +

+ +

+ +If we only looked at the signs of the X and Y components we could determine to which quadrant the +magnetic field belongs to. Now the question of course is which direction (north, north-east, etc.) +do the 4 quadrants represent. In order to figure this out we can just rotate the micro:bit and observe +how the quadrant changes whenever we point in another direction. + +After experimenting a bit we can find out that if we point the micro:bit in e.g. north-east direction, +both the X and the Y component are always positive. Based on this information you should be able to +figure out which direction the other quadrants represent. + +Once you figured out the relation between quadrant and direction you should be able to +complete the template from below. + +```rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; + +mod calibration; +use crate::calibration::calc_calibration; +use crate::calibration::calibrated_measurement; + +mod led; +use led::Direction; + +use microbit::{display::blocking::Display, hal::Timer}; + +#[cfg(feature = "v1")] +use microbit::{hal::twi, pac::twi0::frequency::FREQUENCY_A}; + +#[cfg(feature = "v2")] +use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A}; + +use lsm303agr::{AccelOutputDataRate, Lsm303agr, MagOutputDataRate}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_mag_odr(MagOutputDataRate::Hz10).unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz10).unwrap(); + let mut sensor = sensor.into_mag_continuous().ok().unwrap(); + + let calibration = calc_calibration(&mut sensor, &mut display, &mut timer); + rprintln!("Calibration: {:?}", calibration); + rprintln!("Calibration done, entering busy loop"); + loop { + while !sensor.mag_status().unwrap().xyz_new_data {} + let mut data = sensor.mag_data().unwrap(); + data = calibrated_measurement(data, &calibration); + + let dir = match (data.x > 0, data.y > 0) { + // Quadrant ??? + (true, true) => Direction::NorthEast, + // Quadrant ??? + (false, true) => panic!("TODO"), + // Quadrant ??? + (false, false) => panic!("TODO"), + // Quadrant ??? + (true, false) => panic!("TODO"), + }; + + // use the led module to turn the direction into an LED arrow + // and the led display functions from chapter 5 to display the + // arrow + } +} +``` diff --git a/microbit/src/09-led-compass/take-2.md b/microbit/src/09-led-compass/take-2.md new file mode 100644 index 000000000..87f07ddf0 --- /dev/null +++ b/microbit/src/09-led-compass/take-2.md @@ -0,0 +1,94 @@ +# Take 2 + +This time, we'll use math to get the precise angle that the magnetic field forms with the X and Y +axes of the magnetometer. + +We'll use the `atan2` function. This function returns an angle in the `-PI` to `PI` range. The +graphic below shows how this angle is measured: + +

+ +

+ +Although not explicitly shown in this graph the X axis points to the right and the Y axis points up. + +Here's the starter code. `theta`, in radians, has already been computed. You need to pick which LED +to turn on based on the value of `theta`. + +```rs +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; + +mod calibration; +use crate::calibration::calc_calibration; +use crate::calibration::calibrated_measurement; + +mod led; +use crate::led::Direction; +use crate::led::direction_to_led; + +// You'll find this useful ;-) +use core::f32::consts::PI; +use libm::atan2f; + +use microbit::{display::blocking::Display, hal::Timer}; + +#[cfg(feature = "v1")] +use microbit::{hal::twi, pac::twi0::frequency::FREQUENCY_A}; + +#[cfg(feature = "v2")] +use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A}; + +use lsm303agr::{AccelOutputDataRate, Lsm303agr, MagOutputDataRate}; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_mag_odr(MagOutputDataRate::Hz10).unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz10).unwrap(); + let mut sensor = sensor.into_mag_continuous().ok().unwrap(); + + let calibration = calc_calibration(&mut sensor, &mut display, &mut timer); + rprintln!("Calibration: {:?}", calibration); + rprintln!("Calibration done, entering busy loop"); + loop { + while !sensor.mag_status().unwrap().xyz_new_data {} + let mut data = sensor.mag_data().unwrap(); + data = calibrated_measurement(data, &calibration); + + // use libm's atan2f since this isn't in core yet + let theta = atan2f(data.x as f32, data.y as f32); + + // Figure out the direction based on theta + let dir = Direction::NorthEast; + + display.show(&mut timer, direction_to_led(dir), 100); + } +} +``` + +Suggestions/tips: + +- A whole circle rotation equals 360 degrees. +- `PI` radians is equivalent to 180 degrees. +- If `theta` was zero, which direction are you pointing at? +- If `theta` was, instead, very close to zero, which direction are you pointing at? +- If `theta` kept increasing, at what value would you change the direction diff --git a/microbit/src/09-led-compass/target b/microbit/src/09-led-compass/target new file mode 120000 index 000000000..6bcd2fc5d --- /dev/null +++ b/microbit/src/09-led-compass/target @@ -0,0 +1 @@ +../../target \ No newline at end of file diff --git a/microbit/src/10-punch-o-meter/.cargo/config b/microbit/src/10-punch-o-meter/.cargo/config new file mode 100644 index 000000000..6260c5718 --- /dev/null +++ b/microbit/src/10-punch-o-meter/.cargo/config @@ -0,0 +1,4 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] diff --git a/microbit/src/10-punch-o-meter/Cargo.toml b/microbit/src/10-punch-o-meter/Cargo.toml new file mode 100644 index 000000000..7131c45a6 --- /dev/null +++ b/microbit/src/10-punch-o-meter/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "punch-o-meter" +version = "0.1.0" +authors = ["Henrik Böving "] +edition = "2018" + +[dependencies.microbit-v2] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + + +[dependencies.microbit] +version = "0.12.0" +git = "https://github.com/nrf-rs/microbit/" +optional = true + +[dependencies] +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +rtt-target = { version = "0.3.1", features = ["cortex-m"] } +panic-rtt-target = { version = "0.1.2", features = ["cortex-m"] } +lsm303agr = "0.2.2" +nb = "1.0.0" + +[features] +v2 = ["microbit-v2"] +v1 = ["microbit"] diff --git a/microbit/src/10-punch-o-meter/Embed.toml b/microbit/src/10-punch-o-meter/Embed.toml new file mode 100644 index 000000000..f5117ac42 --- /dev/null +++ b/microbit/src/10-punch-o-meter/Embed.toml @@ -0,0 +1,12 @@ +[default.general] +# chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 +# chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 + +[default.reset] +halt_afterwards = false + +[default.rtt] +enabled = true + +[default.gdb] +enabled = false diff --git a/microbit/src/10-punch-o-meter/README.md b/microbit/src/10-punch-o-meter/README.md new file mode 100644 index 000000000..d5edee39a --- /dev/null +++ b/microbit/src/10-punch-o-meter/README.md @@ -0,0 +1,12 @@ +# Punch-o-meter + +In this section we'll be playing with the accelerometer that's in the board. + +What are we building this time? A punch-o-meter! We'll be measuring the power of your jabs. Well, +actually the maximum acceleration that you can reach because acceleration is what accelerometers +measure. Strength and acceleration are proportional though so it's a good approximation. + +As we already know from previous chapters the accelerometer is built inside the LSM303AGR package. +And just like the magnetometer, it is accessible using the I2C bus. It also has the same coordinate +system as the magnetometer. + diff --git a/microbit/src/10-punch-o-meter/build.rs b/microbit/src/10-punch-o-meter/build.rs new file mode 100644 index 000000000..c8d8c9141 --- /dev/null +++ b/microbit/src/10-punch-o-meter/build.rs @@ -0,0 +1,30 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory (wherever `Cargo.toml` is). However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! a rebuild of the application with new memory settings is ensured after updating `memory.x`. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/microbit/src/10-punch-o-meter/gravity-is-up.md b/microbit/src/10-punch-o-meter/gravity-is-up.md new file mode 100644 index 000000000..05c9ac825 --- /dev/null +++ b/microbit/src/10-punch-o-meter/gravity-is-up.md @@ -0,0 +1,28 @@ +# Gravity is up? + +What's the first thing we'll do? + +Perform a sanity check! + +You should already be able to write a program that continuously prints the accelerometer +data on the RTT console from the [I2C chapter](../08-i2c/index.md). Do you observe something +interesting even when holding the board parallel to the floor with the LED side facing down? + +What you should see like this is that both the X and Y values are rather close to 0, while the +Z value is at around 1000. Which is weird because the board is not moving yet its acceleration is +non-zero. What's going on? This must be related to the gravity, right? Because the acceleration of +gravity is `1 g` (aha, `1 g` = 1000 from the accelerometer). But the gravity pulls objects downwards +so the acceleration along the Z axis should be negative not positive + +Did the program get the Z axis backwards? Nope, you can test rotating the board to align the gravity +to the X or Y axis but the acceleration measured by the accelerometer is always pointing up. + +What happens here is that the accelerometer is measuring the *proper acceleration* of the board not +the acceleration *you* are observing. This proper acceleration is the acceleration of the board as +seen from an observer that's in free fall. An observer that's in free fall is moving toward the +center of the Earth with an acceleration of `1g`; from its point of view the board is actually +moving upwards (away from the center of the Earth) with an acceleration of `1g`. And that's why the +proper acceleration is pointing up. This also means that if the board was in free fall, the +accelerometer would report a proper acceleration of zero. Please, don't try that at home. + +Yes, physics is hard. Let's move on. diff --git a/microbit/src/10-punch-o-meter/memory.x b/microbit/src/10-punch-o-meter/memory.x new file mode 100644 index 000000000..9e2ab65f6 --- /dev/null +++ b/microbit/src/10-punch-o-meter/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/microbit/src/10-punch-o-meter/my-solution.md b/microbit/src/10-punch-o-meter/my-solution.md new file mode 100644 index 000000000..f10705132 --- /dev/null +++ b/microbit/src/10-punch-o-meter/my-solution.md @@ -0,0 +1,102 @@ +# My solution + +``` rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::{rtt_init_print, rprintln}; +use panic_rtt_target as _; + +#[cfg(feature = "v1")] +use microbit::{ + hal::twi, + pac::twi0::frequency::FREQUENCY_A, +}; + +#[cfg(feature = "v2")] +use microbit::{ + hal::twim, + pac::twim0::frequency::FREQUENCY_A, +}; + +use lsm303agr::{ + AccelScale, AccelOutputDataRate, Lsm303agr, +}; + +use microbit::hal::timer::Timer; +use microbit::hal::prelude::*; +use nb::Error; + +#[entry] +fn main() -> ! { + const THRESHOLD: f32 = 0.5; + + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + #[cfg(feature = "v1")] + let i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; + + #[cfg(feature = "v2")] + let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; + + let mut countdown = Timer::new(board.TIMER0); + let mut delay = Timer::new(board.TIMER1); + let mut sensor = Lsm303agr::new_with_i2c(i2c); + sensor.init().unwrap(); + sensor.set_accel_odr(AccelOutputDataRate::Hz50).unwrap(); + // Allow the sensor to measure up to 16 G since human punches + // can actually be quite fast + sensor.set_accel_scale(AccelScale::G16).unwrap(); + + let mut max_g = 0.; + let mut measuring = false; + + loop { + while !sensor.accel_status().unwrap().xyz_new_data {} + // x acceleration in g + let g_x = sensor.accel_data().unwrap().x as f32 / 1000.0; + + if measuring { + // Check the status of our contdown + match countdown.wait() { + // countdown isn't done yet + Err(Error::WouldBlock) => { + if g_x > max_g { + max_g = g_x; + } + }, + // Countdown is done + Ok(_) => { + // Report max value + rprintln!("Max acceleration: {}g", max_g); + + // Reset + max_g = 0.; + measuring = false; + }, + // Since the nrf52 and nrf51 HAL have Void as an error type + // this path cannot occur, as Void is an empty type + Err(Error::Other(_)) => { + unreachable!() + } + } + } else { + // If acceleration goes above a threshold, we start measuring + if g_x > THRESHOLD { + rprintln!("START!"); + + measuring = true; + max_g = g_x; + // The documentation notes that the timer works at a frequency + // of 1 Mhz, so in order to wait for 1 second we have to + // set it to 1_000_000 ticks. + countdown.start(1_000_000_u32); + } + } + delay.delay_ms(20_u8); + } +} +``` diff --git a/microbit/src/10-punch-o-meter/src/main.rs b/microbit/src/10-punch-o-meter/src/main.rs new file mode 100644 index 000000000..bc1ce25c5 --- /dev/null +++ b/microbit/src/10-punch-o-meter/src/main.rs @@ -0,0 +1,15 @@ +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use rtt_target::rtt_init_print; +use panic_rtt_target as _; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let board = microbit::Board::take().unwrap(); + + loop {} +} diff --git a/microbit/src/10-punch-o-meter/target b/microbit/src/10-punch-o-meter/target new file mode 120000 index 000000000..6bcd2fc5d --- /dev/null +++ b/microbit/src/10-punch-o-meter/target @@ -0,0 +1 @@ +../../target \ No newline at end of file diff --git a/microbit/src/10-punch-o-meter/the-challenge.md b/microbit/src/10-punch-o-meter/the-challenge.md new file mode 100644 index 000000000..10b1c101e --- /dev/null +++ b/microbit/src/10-punch-o-meter/the-challenge.md @@ -0,0 +1,28 @@ +# The challenge + +To keep things simple, we'll measure the acceleration only in the X axis while the board remains +horizontal. That way we won't have to deal with subtracting that *fictitious* `1g` we observed +before which would be hard because that `1g` could have X Y Z components depending on how the board +is oriented. + +Here's what the punch-o-meter must do: + +- By default, the app is not "observing" the acceleration of the board. +- When a significant X acceleration is detected (i.e. the acceleration goes above some threshold), + the app should start a new measurement. +- During that measurement interval, the app should keep track of the maximum acceleration observed +- After the measurement interval ends, the app must report the maximum acceleration observed. You + can report the value using the `rprintln!` macro. + +Give it a try and let me know how hard you can punch `;-)`. + +> **NOTE** There are two additional APIs that should be useful for this task we haven't discussed yet. +> First the [`set_accel_scale`] one which you need to measure high g values. +> Secondly the [`Countdown`] trait from `embedded_hal`. If you decide to use this to keep your measurement +> intervals you will have to pattern match on the [`nb::Result`] type instead of using the `block!` macro + we have seen in previous chapters. + + +[`set_accel_scale`]: https://docs.rs/lsm303agr/0.2.2/lsm303agr/struct.Lsm303agr.html#method.set_accel_scale +[`Countdown`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/timer/trait.CountDown.html +[`nb::Result`]: https://docs.rs/nb/1.0.0/nb/type.Result.html diff --git a/microbit/src/README.md b/microbit/src/README.md new file mode 100644 index 000000000..76969055e --- /dev/null +++ b/microbit/src/README.md @@ -0,0 +1,67 @@ +# Discovery + +> Discover the world of microcontrollers through [Rust]! + +[Rust]: https://www.rust-lang.org/ + +This book is an introductory course on microcontroller-based embedded systems that uses Rust as the +teaching language rather than the usual C/C++. + +## Scope + +The following topics will be covered (eventually, I hope): + +- How to write, build, flash and debug an "embedded" (Rust) program. + +- Functionality ("peripherals") commonly found in microcontrollers: Digital input and output, Pulse + Width Modulation (PWM), Analog to Digital Converters (ADC), common communication protocols like + Serial, I2C and SPI, etc. + +- Multitasking concepts: cooperative vs preemptive multitasking, interrupts, schedulers, etc. + +- Control systems concepts: sensors, calibration, digital filters, actuators, open loop control, + closed loop control, etc. + +## Approach + +- Beginner friendly. No previous experience with microcontrollers or embedded systems is required. + +- Hands on. Plenty of exercises to put the theory into practice. *You* will be doing most of the + work here. + +- Tool centered. We'll make plenty use of tooling to ease development. "Real" debugging, with GDB, + and logging will be introduced early on. Using LEDs as a debugging mechanism has no place here. + +## Non-goals + +What's out of scope for this book: + +- Teaching Rust. There's plenty of material on that topic already. We'll focus on microcontrollers + and embedded systems. + +- Being a comprehensive text about electric circuit theory or electronics. We'll just cover the + minimum required to understand how some devices work. + +- Covering details such as linker scripts and the boot process. For example, we'll use existing tools + to help get your code onto your board, but not go into detail about how those tools work. + +Also I don't intend to port this material to other development boards; this book will make exclusive +use of the micro:bit development board. + +## Reporting problems + +The source of this book is in [this repository]. If you encounter any typo or problem with the code +report it on the [issue tracker]. + +[this repository]: https://github.com/rust-embedded/discovery +[issue tracker]: https://github.com/rust-embedded/discovery/issues + +## Other embedded Rust resources + +This Discovery book is just one of several embedded Rust resources provided by the +[Embedded Working Group]. The full selection can be found at [The Embedded Rust Bookshelf]. This +includes the list of [Frequently Asked Questions]. + +[Embedded Working Group]: https://github.com/rust-embedded/wg +[The Embedded Rust Bookshelf]: https://docs.rust-embedded.org +[Frequently Asked Questions]: https://docs.rust-embedded.org/faq.html diff --git a/microbit/src/SUMMARY.md b/microbit/src/SUMMARY.md new file mode 100644 index 000000000..9acaecec4 --- /dev/null +++ b/microbit/src/SUMMARY.md @@ -0,0 +1,69 @@ +<<<<<<< HEAD +[Choose Your Discovery](README.md) +======= +[Introduction](README.md) +- [Background](01-background/README.md) +- [Hardware/knowledge requirements](02-requirements/README.md) +- [Setting up a development environment](03-setup/README.md) + - [Linux](03-setup/linux.md) + - [Windows](03-setup/windows.md) + - [macOS](03-setup/macos.md) + - [Verify the installation](03-setup/verify.md) +- [Meet your hardware](04-meet-your-hardware/README.md) + - [micro:bit v2](04-meet-your-hardware/microbit-v2.md) + - [micro:bit v1](04-meet-your-hardware/microbit-v1.md) + - [Rust Embedded terminology](04-meet-your-hardware/terminology.md) +- [LED roulette](05-led-roulette/README.md) + - [Build it](05-led-roulette/build-it.md) + - [Flash it](05-led-roulette/flash-it.md) + - [Debug it](05-led-roulette/debug-it.md) + - [Light it up](05-led-roulette/light-it-up.md) + - [It blinks](05-led-roulette/it-blinks.md) + - [The challenge](05-led-roulette/the-challenge.md) + - [My solution](05-led-roulette/my-solution.md) +- [Serial communication](06-serial-communication/README.md) + - [\*nix tooling](06-serial-communication/nix-tooling.md) + - [Windows tooling](06-serial-communication/windows-tooling.md) +- [UART](07-uart/README.md) + - [Send a single byte](07-uart/send-a-single-byte.md) + - [Send a string](07-uart/send-a-string.md) + - [Naive approach and `write!`](07-uart/naive-approch-write.md) + - [Receive a single byte](07-uart/receive-a-single-byte.md) + - [Echo server](07-uart/echo-server.md) + - [Reverse a string](07-uart/reverse-a-string.md) + - [My solution](07-uart/my-solution.md) +- [I2C](08-i2c/README.md) + - [The general protocol](08-i2c/the-general-protocol.md) + - [LSM303AGR](08-i2c/lsm303agr.md) + - [Read a single register](08-i2c/read-a-single-register.md) + - [Using a driver](08-i2c/using-a-driver.md) + - [The challenge](08-i2c/the-challenge.md) + - [My solution](08-i2c/my-solution.md) +- [LED compass](09-led-compass/README.md) + - [Calibration](09-led-compass/calibration.md) + - [Take 1](09-led-compass/take-1.md) + - [Solution 1](09-led-compass/solution-1.md) + - [Take 2](09-led-compass/take-2.md) + - [Solution 2](09-led-compass/solution-2.md) + - [Magnitude](09-led-compass/magnitude.md) +- [Punch-o-meter](10-punch-o-meter/README.md) + - [Gravity is up?](10-punch-o-meter/gravity-is-up.md) + - [The challenge](10-punch-o-meter/the-challenge.md) + - [My solution](10-punch-o-meter/my-solution.md) +- [What's left for you to explore](explore.md) + +--- + +[General troubleshooting](appendix/1-general-troubleshooting/README.md) +[How to use GDB](appendix/2-how-to-use-gdb/README.md) + + + + + + + + + +--- +>>>>>>> rewrite diff --git a/microbit/src/appendix/1-general-troubleshooting/README.md b/microbit/src/appendix/1-general-troubleshooting/README.md new file mode 100644 index 000000000..8b3f5309b --- /dev/null +++ b/microbit/src/appendix/1-general-troubleshooting/README.md @@ -0,0 +1,60 @@ +# General troubleshooting + +## `cargo-embed` problems +Most `cargo-embed` problems are either related to not having installed the `udev` +rules properly (on Linux) or having selected the wrong chip configuration in `Embed.toml` so +make sure you got both of those right. + +If the above does not work out for you, you can open an issue in the [`discovery` issue tracker]. +Alternatively you can also visit the [Rust Embedded matrix channel] or the [probe-rs matrix channel] +and ask for help there. + +[`discovery` issue tracker]: https://github.com/rust-embedded/discovery/issues +[Rust Embedded matrix channel]: https://matrix.to/#/#rust-embedded:matrix.org +[probe-rs matrix channel]: https://matrix.to/#/#probe-rs:matrix.org + +## Cargo problems + +### "can't find crate for `core`" + +#### Symptoms + +``` + Compiling volatile-register v0.1.2 + Compiling rlibc v1.0.0 + Compiling r0 v0.1.0 +error[E0463]: can't find crate for `core` + +error: aborting due to previous error + +error[E0463]: can't find crate for `core` + +error: aborting due to previous error + +error[E0463]: can't find crate for `core` + +error: aborting due to previous error + +Build failed, waiting for other jobs to finish... +Build failed, waiting for other jobs to finish... +error: Could not compile `r0`. + +To learn more, run the command again with --verbose. +``` + +#### Cause + +You forgot to install the proper target for your microcontroller (`thumbv7em-none-eabihf` for v2 +and `thumbv6m-none-eabi` for v1). + +#### Fix + +Install the proper target. + +``` console +# micro:bit v2 +$ rustup target add thumbv7em-none-eabihf + +# micro:bit v1 +$ rustup target add thumbv6m-none-eabi +``` diff --git a/microbit/src/appendix/2-how-to-use-gdb/README.md b/microbit/src/appendix/2-how-to-use-gdb/README.md new file mode 100644 index 000000000..6b066f9a0 --- /dev/null +++ b/microbit/src/appendix/2-how-to-use-gdb/README.md @@ -0,0 +1,87 @@ +# How to use GDB + +Below are some useful GDB commands that can help us debug our programs. This assumes you have [flashed a program](../../05-led-roulette/flash-it.md) onto your microcontroller and attached GDB to a `cargo-embed` session. + +## General Debugging + +> **NOTE:** Many of the commands you see below can be executed using a short form. For example, `continue` can simply be used as `c`, or `break $location` can be used as `b $location`. Once you have experience with the commands below, try to see how short you can get the commands to go before GDB doesn't recognize them! + + +### Dealing with Breakpoints + +* `break $location`: Set a breakpoint at a place in your code. The value of `$location` can include: + * `break *main` - Break on the exact address of the function `main` + * `break *0x080012f2` - Break on the exact memory location `0x080012f2` + * `break 123` - Break on line 123 of the currently displayed file + * `break main.rs:123` - Break on line 123 of the file `main.rs` +* `info break`: Display current breakpoints +* `delete`: Delete all breakpoints + * `delete $n`: Delete breakpoint `$n` (`n` being a number. For example: `delete $2`) +* `clear`: Delete breakpoint at next instruction + * `clear main.rs:$function`: Delete breakpoint at entry of `$function` in `main.rs` + * `clear main.rs:123`: Delete breakpoint on line 123 of `main.rs` +* `enable`: Enable all set breakpoints + * `enable $n`: Enable breakpoint `$n` +* `disable`: Disable all set breakpoints + * `disable $n`: Disable breakpoint `$n` + +### Controlling Execution + +* `continue`: Begin or continue execution of your program +* `next`: Execute the next line of your program + * `next $n`: Repeat `next` `$n` number times +* `nexti`: Same as `next` but with machine instructions instead +* `step`: Execute the next line, if the next line includes a call to another function, step into that code + * `step $n`: Repeat `step` `$n` number times +* `stepi`: Same as `step` but with machine instructions instead +* `jump $location`: Resume execution at specified location: + * `jump 123`: Resume execution at line 123 + * `jump 0x080012f2`: Resume execution at address 0x080012f2 + +### Printing Information + +* `print /$f $data` - Print the value contained by the variable `$data`. Optionally format the output with `$f`, which can include: + ```txt + x: hexadecimal + d: signed decimal + u: unsigned decimal + o: octal + t: binary + a: address + c: character + f: floating point + ``` + * `print /t 0xA`: Prints the hexadecimal value `0xA` as binary (0b1010) +* `x /$n$u$f $address`: Examine memory at `$address`. Optionally, `$n` define the number of units to display, + `$u` unit size (bytes, halfwords, words, etc.), `$f` any `print` format defined above + * `x /5i 0x080012c4`: Print 5 machine instructions staring at address `0x080012c4` + * `x/4xb $pc`: Print 4 bytes of memory starting where `$pc` currently is pointing +* `disassemble $location` + * `disassemble /r main`: Disassemble the function `main`, using `/r` to show the bytes that make up each instruction + + +### Looking at the Symbol Table + +* `info functions $regex`: Print the names and data types of functions matched by `$regex`, omit `$regex` to print all functions + * `info functions main`: Print names and types of defined functions that contain the word `main` +* `info address $symbol`: Print where `$symbol` is stored in memory + * `info address GPIOC`: Print the memory address of the variable `GPIOC` +* `info variables $regex`: Print names and types of global variables matched by `$regex`, omit `$regex` to print all global variables +* `ptype $data`: Print more detailed information about `$data` + * `ptype cp`: Print detailed type information about the variable `cp` + +### Poking around the Program Stack + +* `backtrace $n`: Print trace of `$n` frames, or omit `$n` to print all frames + * `backtrace 2`: Print trace of first 2 frames +* `frame $n`: Select frame with number or address `$n`, omit `$n` to display current frame +* `up $n`: Select frame `$n` frames up +* `down $n`: Select frame `$n` frames down +* `info frame $address`: Describe frame at `$address`, omit `$address` for currently selected frame +* `info args`: Print arguments of selected frame +* `info registers $r`: Print the value of register `$r` in selected frame, omit `$r` for all registers + * `info registers $sp`: Print the value of the stack pointer register `$sp` in the current frame + +### Controlling `cargo-embed` Remotely + +* `monitor reset`: Reset the CPU, starting execution over again diff --git a/microbit/src/assets/gdb-layout-asm.png b/microbit/src/assets/gdb-layout-asm.png new file mode 100644 index 000000000..50fa77f9d Binary files /dev/null and b/microbit/src/assets/gdb-layout-asm.png differ diff --git a/microbit/src/assets/gdb-layout-src.png b/microbit/src/assets/gdb-layout-src.png new file mode 100644 index 000000000..45dc23edb Binary files /dev/null and b/microbit/src/assets/gdb-layout-src.png differ diff --git a/microbit/src/assets/microbit-v2.jpg b/microbit/src/assets/microbit-v2.jpg new file mode 100644 index 000000000..a633f330a Binary files /dev/null and b/microbit/src/assets/microbit-v2.jpg differ diff --git a/microbit/src/assets/minicom.png b/microbit/src/assets/minicom.png new file mode 100644 index 000000000..598305b7a Binary files /dev/null and b/microbit/src/assets/minicom.png differ diff --git a/microbit/src/assets/putty-console.png b/microbit/src/assets/putty-console.png new file mode 100644 index 000000000..9e5fa74fd Binary files /dev/null and b/microbit/src/assets/putty-console.png differ diff --git a/src/assets/putty-settings.png b/microbit/src/assets/putty-settings.png similarity index 100% rename from src/assets/putty-settings.png rename to microbit/src/assets/putty-settings.png diff --git a/microbit/src/assets/quadrants.png b/microbit/src/assets/quadrants.png new file mode 100644 index 000000000..4d9feab3d Binary files /dev/null and b/microbit/src/assets/quadrants.png differ diff --git a/microbit/src/assets/roulette_fast.mp4 b/microbit/src/assets/roulette_fast.mp4 new file mode 100644 index 000000000..fec3f6c05 Binary files /dev/null and b/microbit/src/assets/roulette_fast.mp4 differ diff --git a/microbit/src/assets/roulette_slow.mp4 b/microbit/src/assets/roulette_slow.mp4 new file mode 100644 index 000000000..a7aae2a19 Binary files /dev/null and b/microbit/src/assets/roulette_slow.mp4 differ diff --git a/microbit/src/assets/usb-cable.jpg b/microbit/src/assets/usb-cable.jpg new file mode 100644 index 000000000..8d22037d8 Binary files /dev/null and b/microbit/src/assets/usb-cable.jpg differ diff --git a/microbit/src/explore.md b/microbit/src/explore.md new file mode 100644 index 000000000..df22ee33f --- /dev/null +++ b/microbit/src/explore.md @@ -0,0 +1,282 @@ +# What's left for you to explore + +We have barely scratched the surface! There's lots of stuff left for you to +explore. + +> **NOTE:** If you're reading this, and you'd like to help add examples or +> exercises to the Discovery book for any of the items below, or any other +> relevant embedded topics, we'd love to have your help! +> +> Please [open an issue] if you would like to help, but need assistance or +> mentoring for how to contribute this to the book, or open a Pull Request +> adding the information! + +[open an issue]: https://github.com/rust-embedded/discovery/issues/new + +## Topics about embedded software + +These topics discuss strategies for writing embedded software. Although many +problems can be solved in different ways, these sections talk about some +strategies, and when they make sense (or don't make sense) to use. + +### Multitasking + +All our programs executed a single task. How could we achieve multitasking in a +system with no OS, and thus no threads. There are two main approaches to +multitasking: preemptive multitasking and cooperative multitasking. + +In preemptive multitasking a task that's currently being executed can, at any point in time, be +*preempted* (interrupted) by another task. On preemption, the first task will be suspended and the +processor will instead execute the second task. At some point the first task will be resumed. +Microcontrollers provide hardware support for preemption in the form of *interrupts*. + +In cooperative multitasking a task that's being executed will run until it reaches a *suspension +point*. When the processor reaches that suspension point it will stop executing the current task and +instead go and execute a different task. At some point the first task will be resumed. The main +difference between these two approaches to multitasking is that in cooperative multitasking *yields* +execution control at *known* suspension points instead of being forcefully preempted at any point of +its execution. + +### Sleeping + +All our programs have been continuously polling peripherals to see if there's +anything that needs to be done. However, sometimes there's nothing to be done! +At those times, the microcontroller should "sleep". + +When the processor sleeps, it stops executing instructions and this saves power. +It's almost always a good idea to save power so your microcontroller should be +sleeping as much as possible. But, how does it know when it has to wake up to +perform some action? "Interrupts" (see below for what exactly those are) +are one of the events that wake up the microcontroller but there are others +and the `wfi` and `wfe` are the instructions that make the processor "sleep". + +## Topics related to microcontroller capabilities + +Microcontrollers (like our nRF52/nRF51) have many capabilities. However, many share similar +capabilities that can be used to solve all sorts of different problems. + +These topics discuss some of those capabilities, and how they can be used effectively +in embedded development. + +### Direct Memory Access (DMA). + +This peripheral is a kind of *asynchronous* `memcpy`. If you are working with +a micro:bit v2 you have actually already used this, the HAL does this for you +with the UARTE and TWIM peripherals. A DMA peripheral can be used to perform bulk +transfers of data. Either from RAM to RAM, from a peripheral, like a UARTE, to RAM +or from RAM to a peripheral. You can schedule a DMA transfer, like read 256 bytes +from UARTE into this buffer, leave it running in the background and then poll some +register to see if it has completed so you can do other stuff while the transfer +is ongoing. For more information as to how this is implemented you can checkout the +`serial_setup` module from the UART chapter. If that isn't enough yet you could even +try and dive into the code of the [`nrf52-hal`]. + +[`nrf52-hal`]: https://github.com/nrf-rs/nrf-hal + +### Interrupts + +In order to interact with the real world, it is often necessary for the +microcontroller to respond *immediately* when some kind of event occurs. + +Microcontrollers have the ability to be interrupted, meaning when a certain event +occurs, it will stop whatever it is doing at the moment, to instead respond to that +event. This can be very useful when we want to stop a motor when a button is pressed, +or measure a sensor when a timer finishes counting down. + +Although these interrupts can be very useful, they can also be a bit difficult +to work with properly. We want to make sure that we respond to events quickly, +but also allow other work to continue as well. + +In Rust, we model interrupts similar to the concept of threading on desktop Rust +programs. This means we also must think about the Rust concepts of `Send` and `Sync` +when sharing data between our main application, and code that executes as part of +handling an interrupt event. + +### Pulse Width Modulation (PWM) + +In a nutshell, PWM is turning on something and then turning it off periodically +while keeping some proportion ("duty cycle") between the "on time" and the "off +time". When used on a LED with a sufficiently high frequency, this can be used +to dim the LED. A low duty cycle, say 10% on time and 90% off time, will make +the LED very dim wheres a high duty cycle, say 90% on time and 10% off time, +will make the LED much brighter (almost as if it were fully powered). + +In general, PWM can be used to control how much *power* is given to some +electric device. With proper (power) electronics between a microcontroller and +an electrical motor, PWM can be used to control how much power is given to the +motor thus it can be used to control its torque and speed. Then you can add an +angular position sensor and you got yourself a closed loop controller that can +control the position of the motor at different loads. + +PWM is already abstracted within the [`embedded-hal` `Pwm` trait] and you will +again find implementations of this in the [`nrf52-hal`]. + +[`embedded-hal` `Pwm` trait]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/trait.Pwm.html + +### Digital inputs + +We have used the microcontroller pins as digital outputs, to drive LEDs. But +these pins can also be configured as digital inputs. As digital inputs, these +pins can read the binary state of switches (on/off) or buttons (pressed/not +pressed). + +Again digital inputs are abstracted within the [`embedded-hal` `InputPin` trait] +and of course the [`nrf52-hal`] does have an implementation for them. + +(*spoilers* reading the binary state of switches / buttons is not as +straightforward as it sounds ;-) ) + +[`embedded-hal` `InputPin` trait]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/digital/v2/trait.InputPin.html + +### Analog-to-Digital Converters (ADC) + +There are a lot of digital sensors out there. You can use a protocol like I2C +and SPI to read them. But analog sensors also exist! These sensors just output a +voltage level that's proportional to the magnitude they are sensing. + +The ADC peripheral can be used to convert that "analog" voltage level, say `1.25` +Volts, into a "digital" number, say in the `[0, 65535]` range, that the processor +can use in its calculations. + +Again the [`embedded-hal` `adc` module] as well as the [`nrf52-hal`] got you covered. + +[`embedded-hal` `adc` module]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/adc/index.html + +### Digital-to-Analog Converters (DAC) + +As you might expect a DAC is exactly the opposite of ADC. You can write some +digital value into a register to produce a voltage in the `[0, 3.3V]` range +(assuming a `3.3V` power supply) on some "analog" pin. When this analog pin is +connected to some appropriate electronics and the register is written to at some +constant, fast rate (frequency) with the right values you can produce sounds or +even music! + +### Real Time Clock (RTC) + +This peripheral can be used to track time in "human format". Seconds, minutes, +hours, days, months and years. This peripheral handles the translation from +"ticks" to these human friendly units of time. It even handles leap years and +Daylight Save Time for you! + +### Other communication protocols + +- SPI, abstracted within the [`embedded-hal` `spi` module] and implemented by the [`nrf52-hal`] +- I2S, currently not abstracted within the `embedded-hal` but implemented by the [`nrf52-hal`] +- Ethernet, there does exist a small TCP/IP stack named [`smoltcp`] which is implemented for some + chips but the ones on the micro:bit don't feature an Ethernet peripheral +- USB, there is some experimental work on this, for example with the [`usb-device`] crate +- Bluetooth, there does exist an incomplete BLE stack named [`rubble`] which does support nrf chips. +- SMBUS, neither abstracted in `embedded-hal` nor implemented by the [`nrf52-hal`] at the moment. +- CAN, neither abstracted in `embedded-hal` nor implemented by the [`nrf52-hal`] at the moment +- IrDA, neither abstracted in `embedded-hal` nor implemented by the [`nrf52-hal`] at the moment + +[`embedded-hal` `spi` module]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/spi/index.html +[`smoltcp`]: https://github.com/smoltcp-rs/smoltcp +[`usb-device`]: https://github.com/mvirkkunen/usb-device +[`rubble`]: https://github.com/jonas-schievink/rubble + +Different applications use different communication protocols. User facing +applications usually have a USB connector because USB is a ubiquitous +protocol in PCs and smartphones. Whereas inside cars you'll find plenty of CAN +"buses". Some digital sensors use SPI, others use I2C and others, SMBUS. + +If you happen to be interested in developing abstractions in the `embedded-hal` or +implementations of peripherals in general, don't be shy to open an issue in the HAL +repositories. Alternatively you could also join the [Rust Embedded matrix channel] +and get into contact with most of the people who built the stuff from above. + +## General Embedded-Relevant Topics + +These topics cover items that are not specific to our device, or the hardware on +it. Instead, they discuss useful techniques that could be used on embedded +systems. + +### Gyroscopes + +As part of our Punch-o-meter exercise, we used the Accelerometer to measure +changes in acceleration in three dimensions. But there are other motion +sensors such as gyroscopes, which allows us to measure changes in "spin" in three +dimensions. + +This can be very useful when trying to build certain systems, such as a robot +that wants to avoid tipping over. Additionally, the data from a sensor like a +gyroscope can also be combined with data from accelerometer using a technique +called Sensor Fusion (see below for more information). + +### Servo and Stepper Motors + +While some motors are used primarily just to spin in one direction or the other, +for example driving a remote control car forwards or backwards, it is sometimes +useful to measure more precisely how a motor rotates. + +Our microcontroller can be used to drive Servo or Stepper motors, which allow +for more precise control of how many turns are being made by the motor, or +can even position the motor in one specific place, for example if we wanted to +move the arms of a clock to a particular direction. + +### Sensor fusion + +The micro:bit contains two motion sensors: an accelerometer and a magnetometer. +On their own these measure: (proper) acceleration and (the Earth's) magnetic field. +But these magnitudes can be "fused" into something more useful: a "robust" measurement +of the orientation of the board. Where robust means with less measurement error than +a single sensor would be capable of. + +This idea of deriving more reliable data from different sources is known as +sensor fusion. + +--- + +So where to next? There are several options: + +- You could check out the examples in the [`microbit`] board support crate. All those examples work for + the micro:bit board you have. + +[`microbit`]: https://github.com/nrf-rs/microbit/ + +- You could join the [Rust Embedded matrix channel], lots of people who contribute or work on embedded software + hang out there. Including for example the people who wrote the `microbit` BSP, the `nrf52-hal`, `embedded-hal` etc. + +[Rust Embedded matrix channel]: https://matrix.to/#/#rust-embedded:matrix.org + +- If you are looking for a general overview of what is available in Rust Embedded right now check out the [Awesome Rust Embedded] + list + +[Awesome Rust Embedded]: https://github.com/rust-embedded/awesome-embedded-rust/ + +- You could check out [Real-Time Interrupt-driven Concurrency]. A very efficient preemptive multitasking framework + that supports task prioritization and dead lock free execution. + +[Real-Time Interrupt-driven Concurrency]: https://rtic.rs + +- You could check out more abstractions of the [`embedded-hal`] project and maybe even try and write your own + platform agnostic driver based on it. + +[`embedded-hal`]: https://github.com/rust-embedded/embedded-hal + +- You could try running Rust on a different development board. The easiest way to get started is to + use the [`cortex-m-quickstart`] Cargo project template. + +[`cortex-m-quickstart`]: https://docs.rs/cortex-m-quickstart/0.3.1/cortex_m_quickstart/ + +- You could try out [this motion sensors demo][madgwick]. Details about the implementation and + source code are available in [this blog post][wd-1-2]. + +[madgwick]: https://mobile.twitter.com/japaricious/status/962770003325005824 +[wd-1-2]: http://blog.japaric.io/wd-1-2-l3gd20-lsm303dlhc-madgwick/ + +- You could check out [this blog post][brave-new-io] which describes how Rust type system can + prevent bugs in I/O configuration. + +[brave-new-io]: http://blog.japaric.io/brave-new-io/ + +- You could check out [japaric's blog] for miscellaneous topics about embedded development with Rust. + +[japaric's blog]: http://blog.japaric.io + + +- You could join the [Weekly driver initiative] and help us write generic drivers on top of the + `embedded-hal` traits and that work for all sorts of platforms (ARM Cortex-M, AVR, MSP430, RISCV, + etc.) + +[Weekly driver initiative]: https://github.com/rust-lang-nursery/embedded-wg/issues/39 diff --git a/src/05-led-roulette/.cargo/config b/src/05-led-roulette/.cargo/config deleted file mode 100644 index 01d25c8b3..000000000 --- a/src/05-led-roulette/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb -q" -rustflags = [ - "-C", "link-arg=-Tlink.x", -] diff --git a/src/05-led-roulette/Cargo.toml b/src/05-led-roulette/Cargo.toml deleted file mode 100644 index 43ac7579f..000000000 --- a/src/05-led-roulette/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "led-roulette" -version = "0.1.0" - -[dependencies] -aux5 = { path = "auxiliary" } diff --git a/src/05-led-roulette/auxiliary/Cargo.toml b/src/05-led-roulette/auxiliary/Cargo.toml deleted file mode 100644 index 617de1e25..000000000 --- a/src/05-led-roulette/auxiliary/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "aux5" -version = "0.1.0" - -[dependencies] -cortex-m = "0.5.6" -panic-halt = "0.2.0" -cortex-m-rt = "0.6.3" - -[dependencies.f3] -features = ["rt"] -version = "0.6.1" diff --git a/src/05-led-roulette/auxiliary/src/lib.rs b/src/05-led-roulette/auxiliary/src/lib.rs deleted file mode 100644 index 2218b18e5..000000000 --- a/src/05-led-roulette/auxiliary/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Initialization code - -#![no_std] - -#[allow(unused_extern_crates)] // NOTE(allow) bug rust-lang/rust#53964 -extern crate panic_halt; // panic handler - -pub use cortex_m_rt::entry; -pub use f3::{ - hal::{delay::Delay, prelude}, - led::Leds, -}; - -use f3::hal::{prelude::*, stm32f30x}; - -pub fn init() -> (Delay, Leds) { - let cp = cortex_m::Peripherals::take().unwrap(); - let dp = stm32f30x::Peripherals::take().unwrap(); - - let mut flash = dp.FLASH.constrain(); - let mut rcc = dp.RCC.constrain(); - - let clocks = rcc.cfgr.freeze(&mut flash.acr); - - let delay = Delay::new(cp.SYST, clocks); - - let leds = Leds::new(dp.GPIOE.split(&mut rcc.ahb)); - - (delay, leds) -} diff --git a/src/06-hello-world/.cargo/config b/src/06-hello-world/.cargo/config deleted file mode 100644 index 115130a23..000000000 --- a/src/06-hello-world/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb -q -x openocd.gdb" -rustflags = [ - "-C", "link-arg=-Tlink.x", -] diff --git a/src/06-hello-world/Cargo.toml b/src/06-hello-world/Cargo.toml deleted file mode 100644 index f9260db58..000000000 --- a/src/06-hello-world/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "hello-world" -version = "0.1.0" - -[dependencies] -aux6 = { path = "auxiliary" } diff --git a/src/06-hello-world/openocd.gdb b/src/06-hello-world/openocd.gdb deleted file mode 100644 index 9143f177e..000000000 --- a/src/06-hello-world/openocd.gdb +++ /dev/null @@ -1,6 +0,0 @@ -target remote :3333 -set print asm-demangle on -set print pretty on -load -break main -continue diff --git a/src/07-registers/.cargo/config b/src/07-registers/.cargo/config deleted file mode 100644 index b17774193..000000000 --- a/src/07-registers/.cargo/config +++ /dev/null @@ -1,8 +0,0 @@ -[target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb -q -x openocd.gdb" -rustflags = [ - "-C", "link-arg=-Tlink.x", -] - -[build] -target = "thumbv7em-none-eabihf" \ No newline at end of file diff --git a/src/07-registers/Cargo.toml b/src/07-registers/Cargo.toml deleted file mode 100644 index 2b4fbaca9..000000000 --- a/src/07-registers/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "registers" -version = "0.1.0" - -[dependencies] -aux7 = { path = "auxiliary" } diff --git a/src/07-registers/auxiliary/Cargo.toml b/src/07-registers/auxiliary/Cargo.toml deleted file mode 100644 index 08db809a9..000000000 --- a/src/07-registers/auxiliary/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "aux7" -version = "0.1.0" - -[dependencies] -cortex-m = "0.5.6" -cortex-m-rt = "0.6.3" -panic-itm = "0.4.0" - -[dependencies.f3] -features = ["rt"] -version = "0.6.1" \ No newline at end of file diff --git a/src/07-registers/auxiliary/src/lib.rs b/src/07-registers/auxiliary/src/lib.rs deleted file mode 100644 index ed49cfbb7..000000000 --- a/src/07-registers/auxiliary/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Initialization code - -#![deny(warnings)] -#![no_std] - -#[allow(unused_extern_crates)] // NOTE(allow) bug rust-lang/rust#53964 -extern crate panic_itm; // panic handler - -pub use cortex_m::{asm::bkpt, iprint, iprintln, peripheral::ITM}; -pub use cortex_m_rt::entry; -use f3::{ - hal::{ - prelude::*, - stm32f30x::gpioc, - stm32f30x::{self, GPIOE}, - }, - led::Leds, -}; - -#[inline(never)] -pub fn init() -> (ITM, &'static gpioc::RegisterBlock) { - let cp = cortex_m::Peripherals::take().unwrap(); - let dp = stm32f30x::Peripherals::take().unwrap(); - - let mut rcc = dp.RCC.constrain(); - - Leds::new(dp.GPIOE.split(&mut rcc.ahb)); - - (cp.ITM, unsafe { &*GPIOE::ptr() }) -} diff --git a/src/08-leds-again/.cargo/config b/src/08-leds-again/.cargo/config deleted file mode 100644 index b17774193..000000000 --- a/src/08-leds-again/.cargo/config +++ /dev/null @@ -1,8 +0,0 @@ -[target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb -q -x openocd.gdb" -rustflags = [ - "-C", "link-arg=-Tlink.x", -] - -[build] -target = "thumbv7em-none-eabihf" \ No newline at end of file diff --git a/src/09-clocks-and-timers/.cargo/config b/src/09-clocks-and-timers/.cargo/config deleted file mode 100644 index b17774193..000000000 --- a/src/09-clocks-and-timers/.cargo/config +++ /dev/null @@ -1,8 +0,0 @@ -[target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb -q -x openocd.gdb" -rustflags = [ - "-C", "link-arg=-Tlink.x", -] - -[build] -target = "thumbv7em-none-eabihf" \ No newline at end of file diff --git a/src/09-clocks-and-timers/auxiliary/src/lib.rs b/src/09-clocks-and-timers/auxiliary/src/lib.rs deleted file mode 100644 index e52f0032d..000000000 --- a/src/09-clocks-and-timers/auxiliary/src/lib.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Initialization code - -#![no_std] - -#[allow(unused_extern_crates)] // NOTE(allow) rust-lang/rust#53964 -extern crate panic_itm; // panic handler - -pub use cortex_m::asm::{bkpt, nop}; -pub use cortex_m_rt::entry; -pub use f3::{ - hal::stm32f30x::{rcc, tim6}, - led::Leds, -}; - -use f3::hal::{ - prelude::*, - stm32f30x::{self, RCC, TIM6}, -}; - -pub fn init() -> ( - Leds, - &'static rcc::RegisterBlock, - &'static tim6::RegisterBlock, -) { - let p = stm32f30x::Peripherals::take().unwrap(); - - let mut rcc = p.RCC.constrain(); - - let leds = Leds::new(p.GPIOE.split(&mut rcc.ahb)); - - (leds, unsafe { &*RCC::ptr() }, unsafe { &*TIM6::ptr() }) -} diff --git a/src/11-usart/auxiliary/src/lib.rs b/src/11-usart/auxiliary/src/lib.rs deleted file mode 100644 index 1a0a6a16c..000000000 --- a/src/11-usart/auxiliary/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Initialization code - -#![no_std] - -#[allow(unused_extern_crates)] // NOTE(allow) bug rust-lang/rust53964 -extern crate panic_itm; // panic handler - -pub use cortex_m::{asm::bkpt, iprint, iprintln, peripheral::ITM}; -pub use cortex_m_rt::entry; -pub use f3::hal::{prelude, serial::Serial, stm32f30x::usart1, time::MonoTimer}; - -use f3::hal::{ - prelude::*, - stm32f30x::{self, USART1}, -}; - -pub fn init() -> (&'static mut usart1::RegisterBlock, MonoTimer, ITM) { - let cp = cortex_m::Peripherals::take().unwrap(); - let dp = stm32f30x::Peripherals::take().unwrap(); - - let mut flash = dp.FLASH.constrain(); - let mut rcc = dp.RCC.constrain(); - - let clocks = rcc.cfgr.freeze(&mut flash.acr); - - let mut gpioa = dp.GPIOA.split(&mut rcc.ahb); - - let tx = gpioa.pa9.into_af7(&mut gpioa.moder, &mut gpioa.afrh); - let rx = gpioa.pa10.into_af7(&mut gpioa.moder, &mut gpioa.afrh); - - Serial::usart1(dp.USART1, (tx, rx), 115_200.bps(), clocks, &mut rcc.apb2); - - unsafe { - ( - &mut *(USART1::ptr() as *mut _), - MonoTimer::new(cp.DWT, clocks), - cp.ITM, - ) - } -} diff --git a/src/12-bluetooth-setup/at-commands.md b/src/12-bluetooth-setup/at-commands.md deleted file mode 100644 index df7abaa14..000000000 --- a/src/12-bluetooth-setup/at-commands.md +++ /dev/null @@ -1,45 +0,0 @@ -## AT commands - -> **NOTE** incomplete - -

- -

- -Entering AT mode: - -- Power off the Bluetooth module -- Press and hold the button on the Bluetooth module -- Power on the Bluetooth module -- Now, release the button - -> **TODO** blinking pattern - -The AT mode always operates at a baud rate of 38,400. - -Commands (via minicom) - -- Sanity check - -``` -$ at -OK -OK -(..) -``` - -Answers `OK` repeatedly until you hit ENTER again. - -- Rename - -``` -$ at+name=ferris -OK -``` - -- Change the baud rate - -``` -$ at+uart=115200,0,0 -OK -``` diff --git a/src/14-i2c/auxiliary/Cargo.toml b/src/14-i2c/auxiliary/Cargo.toml deleted file mode 100644 index 6d8b296bd..000000000 --- a/src/14-i2c/auxiliary/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "aux14" -version = "0.1.0" - -[dependencies] -cortex-m = "0.5.6" -cortex-m-rt = "0.6.3" -panic-itm = "0.4.0" - -[dependencies.f3] -features = ["rt"] -version = "0.6.1" diff --git a/src/14-i2c/openocd.gdb b/src/14-i2c/openocd.gdb deleted file mode 100644 index cafe49b1b..000000000 --- a/src/14-i2c/openocd.gdb +++ /dev/null @@ -1,10 +0,0 @@ -target remote :3333 -set print asm-demangle on -set print pretty on -monitor tpiu config internal itm.txt uart off 8000000 -monitor itm port 0 on -load -break DefaultHandler -break UserHardFault -break main -continue diff --git a/src/15-led-compass/auxiliary/src/lib.rs b/src/15-led-compass/auxiliary/src/lib.rs deleted file mode 100644 index efa6bcbe4..000000000 --- a/src/15-led-compass/auxiliary/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Initialization code - -#![no_std] - -#[allow(unused_extern_crates)] // NOTE(allow) bug rust-lang/rust#53964 -extern crate panic_itm; // panic handler - -pub use cortex_m::{asm::bkpt, iprint, iprintln, peripheral::ITM}; -pub use cortex_m_rt::entry; -pub use f3::{ - hal::{delay::Delay, prelude, stm32f30x::i2c1}, - led::{Direction, Leds}, - lsm303dlhc::I16x3, -}; - -use f3::{ - hal::{i2c::I2c, prelude::*, stm32f30x}, - Lsm303dlhc, -}; - -pub fn init() -> (Leds, Lsm303dlhc, Delay, ITM) { - let cp = cortex_m::Peripherals::take().unwrap(); - let dp = stm32f30x::Peripherals::take().unwrap(); - - let mut flash = dp.FLASH.constrain(); - let mut rcc = dp.RCC.constrain(); - - let clocks = rcc.cfgr.freeze(&mut flash.acr); - - let gpioe = dp.GPIOE.split(&mut rcc.ahb); - let leds = Leds::new(gpioe); - - let mut gpiob = dp.GPIOB.split(&mut rcc.ahb); - let scl = gpiob.pb6.into_af4(&mut gpiob.moder, &mut gpiob.afrl); - let sda = gpiob.pb7.into_af4(&mut gpiob.moder, &mut gpiob.afrl); - - let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 400.khz(), clocks, &mut rcc.apb1); - - let lsm303dlhc = Lsm303dlhc::new(i2c).unwrap(); - - let delay = Delay::new(cp.SYST, clocks); - - (leds, lsm303dlhc, delay, cp.ITM) -} diff --git a/src/15-led-compass/openocd.gdb b/src/15-led-compass/openocd.gdb deleted file mode 100644 index cafe49b1b..000000000 --- a/src/15-led-compass/openocd.gdb +++ /dev/null @@ -1,10 +0,0 @@ -target remote :3333 -set print asm-demangle on -set print pretty on -monitor tpiu config internal itm.txt uart off 8000000 -monitor itm port 0 on -load -break DefaultHandler -break UserHardFault -break main -continue diff --git a/src/16-punch-o-meter/openocd.gdb b/src/16-punch-o-meter/openocd.gdb deleted file mode 100644 index cafe49b1b..000000000 --- a/src/16-punch-o-meter/openocd.gdb +++ /dev/null @@ -1,10 +0,0 @@ -target remote :3333 -set print asm-demangle on -set print pretty on -monitor tpiu config internal itm.txt uart off 8000000 -monitor itm port 0 on -load -break DefaultHandler -break UserHardFault -break main -continue diff --git a/src/README.md b/src/README.md index 63236c68f..701d75564 100644 --- a/src/README.md +++ b/src/README.md @@ -1,170 +1,24 @@ -# Discovery - - - -> [Rust]でマイクロコントローラの世界を楽しもう! - -[Rust]: https://www.rust-lang.org/en-US/ - - - -この本は、よくあるC/C++ではなく、Rustを使ったマイクロコントローラの組込みシステム入門コースです。 - - - -## スコープ - - - -以下のトピックを取り上げます(ゆくゆくは、そうしたいです) - - - -- 「組込み」(Rust)プログラムの書き方、ビルド方法、フラッシュへの書き込み方法、デバッグ方法。 - - - -- マイクロコントローラで一般的な機能(「ペリフェラル」)。デジタル入出力、パルス幅変調(PWM)、アナログデジタル変換(ADC)、 - シリアル、I2C、SPIのような一般的な通信プロトコル、など。 - - - -- マルチタスク。協調的マルチタスク vs プリエンプティブマルチタスク、割り込み、スケジューラなど。 - - - -- 制御システム。センサ、キャリブレーション、デジタルフィルタ、アクチュエータ、開ループ制御、閉ループ制御、など。 - - - -## 進め方 - - - -- 初心者に優しく。マイクロコントローラや組込みシステムの開発経験は必要ありません。 - - - -- ハンズオン形式で。理論を実践するためにたくさんの演習をします。*あなた*はほとんどの作業をここで行います。 - - - -- ツール中心に。開発を容易にするツールをたくさん使用します。GDBを使った「実際の」デバッグとログ出力を早い段階で導入します。 - デバッグ機能としてLEDを使用するようなことは、ここではやりません。 - - - -## 目標としないこと - - - -この本でスコープ外のことは、以下の通りです。 - - - -- Rustを教えること。このトピックについては、既に多くの教材があります。マイクロコントローラと組込みシステムに集中します。 - - - -- 電気回路または電子機器の理論についての包括的なテキストであること。 - いくつかのデバイスがどのように動くか、を理解するための最低限の情報を提供します。 - - - -- Rustの低レベルな詳細を説明すること。リンカスクリプトやブートプロセス、 - また、最小限のRustプログラムにこれらの2つの要素を結合する方法については、説明しません。 - - - -また、この教材を他の開発ボードに移植するつもりもありません。この本は、STM32F3DISCOVERY開発ボード専用のものです。 - - - -## 問題の報告 - - - -この本のソースは[このレポジトリ]にあります。誤植やコードに問題を発見した場合は、[issueトラッカー]に報告して下さい。 - - - -[このレポジトリ]: https://github.com/rust-embedded/discovery -[issueトラッカー]: https://github.com/rust-embedded/discovery/issues - -> 訳注:和訳への問題報告は、下記にお願いいたします。 - -和訳のソースは[和訳レポジトリ]にあります。問題を発見した場合は、[和訳issue]に報告して下さい。 - -[和訳レポジトリ]: https://github.com/tomoyuki-nakabayashi/discovery -[和訳issue]: https://github.com/tomoyuki-nakabayashi/discovery/issues - - - -## 他の組込みRustの資料 - - - -このDiscovery本は、[組込みワーキンググループ]が提供する組込みRust資料の1つに過ぎません。 -[組込みRustの本棚]に、数多くの資料があります。そこには、[よくある質問と回答]のリストも有ります。 - - - -[組込みワーキンググループ]: https://github.com/rust-embedded/wg -[組込みRustの本棚]: https://docs.rust-embedded.org -[よくある質問と回答]: https://docs.rust-embedded.org/faq.html - - - -## Sponsored by - -

- - - -

- -Many thanks to [integer 32](http://integer32.com/) for sponsoring me to work on this book! Please -give them lots of work (they do Rust consulting!) so they'll have no choice but to hire more -Rustaceans <3. +# `Discovery` + +Discover the world of microcontrollers through [Rust](https://www.rust-lang.org/)! + +There are currently two versions of this book. The first is older and uses an +F3 Discovery circuit board to introduce you to microcontrollers and Rust, while +the second is newer and uses a micro:bit circuit board instead. + +- [Read the newer book, using a micro:bit](https://docs.rust-embedded.org/discovery/microbit) +- [Read the older book, using an F3 discovery board](https://docs.rust-embedded.org/discovery/f3discovery) +- Start working on the examples from this repository +- You've got questions? + - Have a look at our [discussions section on + GitHub](https://github.com/rust-embedded/discovery/discussions) + - Maybe it has already been answered + - If not, start a new discussion +- You've found an issue? + - Have a look at our [issues on + GitHub](https://github.com/rust-embedded/discovery/issues) + - Maybe there is already a workaround + - If not, please open a new one - or even better - a [pull + request](https://github.com/rust-embedded/discovery/pulls) for solving + it +- Have fun and enjoy! diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 84b8fed68..eede7bbdc 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,85 +1 @@ -[導入](README.md) -- [背景](01-background/README.md) -- [ハードウェア/知識の要求](02-requirements/README.md) -- [開発環境の構築](03-setup/README.md) - - [Linux](03-setup/linux.md) - - [Windows](03-setup/windows.md) - - [macOS](03-setup/macos.md) - - [インストールの確認](03-setup/verify.md) -- [ハードウェアとの出会い](04-meet-your-hardware/README.md) -- [LEDルーレット](05-led-roulette/README.md) - - [ビルド](05-led-roulette/build-it.md) - - [Flashへの書き込み](05-led-roulette/flash-it.md) - - [デバッグ](05-led-roulette/debug-it.md) - - [`led`と`delay`の抽象化](05-led-roulette/the-led-and-delay-abstractions.md) - - [課題](05-led-roulette/the-challenge.md) - - [解答例](05-led-roulette/my-solution.md) -- [Hello, world!](06-hello-world/README.md) - - [`panic!`](06-hello-world/panic.md) -- [レジスタ](07-registers/README.md) - - [RTRM](07-registers/rtrm.md) - - [(誤った)最適化](07-registers/optimization.md) - - [`0xBAAAAAAD`番地](07-registers/bad-address.md) - - [異なる場所での不気味な動作](07-registers/spooky-action-at-a-distance.md) - - [型安全な操作](07-registers/type-safe-manipulation.md) -- [LED、再び](08-leds-again/README.md) - - [電源](08-leds-again/power.md) - - [設定](08-leds-again/configuration.md) - - [解答例](08-leds-again/the-solution.md) -- [クロックと時間](09-clocks-and-timers/README.md) - - [`for`ループで遅延](09-clocks-and-timers/for-loop-delays.md) - - [NOP](09-clocks-and-timers/nop.md) - - [ワンショットタイマ](09-clocks-and-timers/one-shot-timer.md) - - [初期化](09-clocks-and-timers/initialization.md) - - [ビジーウェイト](09-clocks-and-timers/busy-waiting.md) - - [全てをまとめる](09-clocks-and-timers/putting-it-all-together.md) -- [シリアル通信](10-serial-communication/README.md) - - [*nixのツール](10-serial-communication/nix-tooling.md) - - [Windowsのツール](10-serial-communication/windows-tooling.md) - - [ループバック](10-serial-communication/loopbacks.md) -- [USART](11-usart/README.md) - - [1バイト送信](11-usart/send-a-single-byte.md) - - [文字列送信](11-usart/send-a-string.md) - - [バッファオーバーラン](11-usart/buffer-overrun.md) - - [`uprintln!`](11-usart/uprintln.md) - - [1バイト受信](11-usart/receive-a-single-byte.md) - - [エコーサーバー](11-usart/echo-server.md) - - [文字列の反転](11-usart/reverse-a-string.md) - - [解答例](11-usart/my-solution.md) -- [Bluetooth設定](12-bluetooth-setup/README.md) - - [Linux](12-bluetooth-setup/linux.md) - - [ループバック](12-bluetooth-setup/loopback.md) - -- [Bluetooth経由のシリアル](13-serial-over-bluetooth/README.md) -- [I2C](14-i2c/README.md) - - [一般的なプロトコル](14-i2c/the-general-protocol.md) - - [LSM303DLHC](14-i2c/lsm303dlhc.md) - - [1つのレジスタを読む](14-i2c/read-a-single-register.md) - - [解答例](14-i2c/the-solution.md) - - [複数のレジスタを読む](14-i2c/read-several-registers.md) -- [LEDコンパス](15-led-compass/README.md) - - [第一弾](15-led-compass/take-1.md) - - [解答例1](15-led-compass/solution-1.md) - - [第二弾](15-led-compass/take-2.md) - - [解答例2](15-led-compass/solution-2.md) - - [大きさ](15-led-compass/magnitude.md) - - [キャリブレーション](15-led-compass/calibration.md) -- [パンチングマシン](16-punch-o-meter/README.md) - - [重力は上を向いている?](16-punch-o-meter/gravity-is-up.md) - - [課題](16-punch-o-meter/the-challenge.md) - - [解答例](16-punch-o-meter/my-solution.md) -- [もっと楽しむために](explore.md) - ---- - -[トラブルシューティング](appendix/1-general-troubleshooting/README.md) - - - - - - - - - ---- +[Choose Your Discovery](README.md) diff --git a/src/assets/minicom.png b/src/assets/minicom.png deleted file mode 100644 index 6605d5ed6..000000000 Binary files a/src/assets/minicom.png and /dev/null differ diff --git a/triagebot.toml b/triagebot.toml new file mode 100644 index 000000000..fa0824ac5 --- /dev/null +++ b/triagebot.toml @@ -0,0 +1 @@ +[assign]